[Python-Dev] Re: Simulating Class (was Re: Does Python have Class methods)

James_Althoff@i2.com James_Althoff@i2.com
Fri, 18 May 2001 12:10:11 -0700


Python-dev'ers,

Pardon the intrusion, but Aahz Maruch suggested that I post this to the
python-dev list.  The message below illustrates "yet another class method
recipe" that Costas synthesized (and which I then modified very slightly)
from various posts following another discussion on python-list about class
methods (as we all await the "type/class healing" stuff some of you are
working on -- go team!).  This variant uses explicit "metaclasses" (defined
as regular classes) whose instances ("meta objects") point to class objects
(since they cannot *be* class objects in current Python).   Anyway, I think
the approach has some nice properties.

Best regards,

Jim


----- Forwarded by James Althoff/AMER/i2Tech on 05/18/01 11:23 AM -----
                                                                                                               
                    James Althoff                                                                              
                                         To:     python-list@python.org                                        
                    05/14/01 02:09       cc:                                                                   
                    PM                   Subject:     Re: Simulating Class (was Re: Does Python have Class     
                                         methods)(Document link: James Althoff)                                
                                                                                                               



Costas writes:
>Ok, so after looking thru how Python works and comments from people, I
>came up with what I believe may be the best way to implement Class
>methods and Class variables.
>
><snip>
>
>Costas

I think this idea is quite good.  I would amend it very slightly by
suggesting the convention of defining *three* separate names in the
enclosing module:

1) the name of the enclosing class
2) the name of the singleton instance of the enclosing class
3) the name of the enclosed class

To support this, I would propose using a naming convention as below.

If one is interested in defining a class Spam, then use the following
names:

1) SpamMetaClass  -- names the enclosing class
2) SpamMeta  --  names a singleton instance of the enclosing class
3) Spam  --  names the enclosed class

Use the name SpamMetaClass when you need to derive a subclass of
SpamMetaClass, e.g.,

class SpecialSpamMetaClass(SpamMetaClass): pass

Use the name SpamMeta to invoke a class method, e.g.,

SpamMeta.aClassMethod()

Use the name Spam to make instances as usual, e.g.,

s = Spam()

(and to derive a subclass of Spam).

Although SpamMetaClass is not a metaclass in the sense of Smalltalk or Ruby
-- that is to say, the class Spam is not an instance of SpamMetaClass --
nonetheless, SpamMetaClass still acts as a "higher level" class that
provides methods on behalf of the class Spam where said methods are 1)
independent of any particular instance of Spam and 2) allow for
factory-method-style creation of Spam instances -- these being two very
important attributes of the metaclass concept.  Plus "meta" is a nice,
short name.  :-)   Plus using "MetaClass" to refer to the class and "Meta"
to refer to the singleton instance of "MetaClass" is reasonably clear and
succinct, I think.

One nice thing about the proposed recipe is that the SpamMeta object is a
real class instance of a real class.  This means that -- unlike when using
the "module function" recipe -- we get inheritance of methods, and --
unlike when using the "callable wrapper class" recipe -- we also get
override of methods.

The example below illustrates both of these important capabilities.


class Class1MetaClass:  # Base metaclass

    # Define "class methods" for Class1

    def whoami(self):
        print 'Class1MetaClass.whoami:', self

    def new(self):  # Factory method
        """Return a new instance"""
        return self.Class1()

    def newList(self,n=3):  # Another factory method
        """Return a list of new instances"""
        l = []
        for i in range(n):
            newInstance = self.new()
            l.append(newInstance)
        return l

    # Define Class1 & its "instance methods"

    class Class1:  # Base class

        def whoami(self):
            print 'Class1.whoami:', self


Class1Meta = Class1MetaClass()  # Make & name the singleton metaclass
instance
Class1 = Class1Meta.Class1  # Make the Class1 name accessible


class Class2MetaClass(Class1MetaClass):  # Derived metaclass

    # Define "class methods" for Class2 -- Override Class1 "class methods"

    def whoami(self):
        print 'Class2MetaClass.whoami:', self

    def new(self):  # Override the factory method
        return self.Class2()

    # Define Class2 & its "instance methods"

    class Class2(Class1):  # Derived class

        def whoami(self):
            print 'Class2.whoami:', self

Class2Meta = Class2MetaClass()  # Make & name the singleton metaclass
instance
Class2 = Class2Meta.Class2  # Make the Class2 name accessible


# Test

Class1Meta.whoami()  # invoke "class method" of base class
Class2Meta.whoami()  # invoke "class method" of derived class

Class1().whoami()  # make an instance & invoke "instance method"
Class2().whoami()

print Class1Meta.newList()  # factory method
print Class2Meta.newList()  # inherit factory method with override

>>> reload(meta6)
Class1MetaClass.whoami: <meta6.Class1MetaClass instance at 00810DBC>
Class2MetaClass.whoami: <meta6.Class2MetaClass instance at 00812D6C>
Class1.whoami: <meta6.Class1 instance at 0081058C>
Class2.whoami: <meta6.Class2 instance at 0081058C>
[<meta6.Class1 instance at 0081147C>, <meta6.Class1 instance at 0081151C>,
<meta6.Class1 instance at
 0081009C>]
[<meta6.Class2 instance at 0081147C>, <meta6.Class2 instance at 00810CCC>,
<meta6.Class2 instance at
 0081009C>]
<module 'meta6' from 'c:\_dev\python20\meta6.py'>


Jim