Prothon Prototypes vs Python Classes

Joe Mason joe at notcharles.ca
Sat Mar 27 22:37:25 EST 2004


In article <569c605ld1cj8fc1emolk08ete0s1prls1 at 4ax.com>, David MacQuigg wrote:
> Playing with Prothon today, I am fascinated by the idea of eliminating
> classes in Python.  I'm trying to figure out what fundamental benefit
> there is to having classes.  Is all this complexity unecessary?

Complexity's ok if it's in the right place - down deep in the heart of
things where you only have to get it right once.  If you simplify the
language, it can push the complexity out to the API level where
everybody has to deal with it.  (However, see
http://www.ai.mit.edu/docs/articles/good-news/subsection3.2.1.html for a
counterargument.)

Ed Suominen just posted a situation where classes and instances need to
be treated separately.  He has a class, "AutoText", with various
subclasses (which I'll call "AutoA", "AutoB", etc. because I forget the
details).  He wants to notice whenever somebody creates a subclass of
AutoText and add it to a list so that he can have an AutoAll function
which creates just one instance of each.  (Or something like that.)

    # all examples below will use the same AutoAll()
    autoall = []
    def AutoAll():
        "Return an instance of each 'class'"
        l = []
        for i in autoall:
            l.append(i())
        return l

In Python, the easiest way to do this is with metaclasses, which is a
really advanced topic.  In Prothon, it's simple to "notice" when a
subclass is created, because __init__ gets called!  So you just say:

    # This is library code - complexity goes here
    AutoText = Base()
    with AutoText:
        def .__init__():
            Base.__init__()
            autoall.append(self)
    
    # Users of the library create new subclasses
    AutoA = AutoText()

    AutoB = AutoText()
    with AutoB:
        def .__init__(someparams):
            # do stuff with someparams
            AutoText.__init__()

The problem with this is that it doesn't work.  In AutoAll(), when you
call i() and create a new instance of AutoText, it adds it to autoall,
so now you have an infinite loop as autoall keeps growing.

So you need to build in some way to distinguish classes to add to
autoall from ones you don't.  Say, a register() method:

    # library code
    AutoText = Base()
    with AutoText:
        # __init__ no longer needed
        def .register():
            autoall.append(self)

    # user code
    AutoA = AutoText()
    with AutoA: .register()

    AutoB = AutoText()
    with AutoB:
        def .__init__(someparams):
            # do stuff with someparams
            AutoText.__init__()
        .register()

That's not all that bad, really - if you ignore metaclasses, the only
way I can think of to do this in standard Python is the same, since you
only have a hook when an object is created, not a class:

    # library code
    class AutoText(Base):
        def register(klass):
            autoall.append(klass)
        register = classmethod(register)
    AutoText.register()

    # user code
    class AutoA(AutoText): pass
    AutoA.register()

    class AutoB(AutoText):
        def __init__(self, someparams):
            # do stuff with someparams
            AutoText.__init__(self)
    AutoB.register()
 
But with metaclasses, you can set a metaclass on AutoText which
automatically registers each subclass as it's created, so that the
explicit register() method can be skipped.

    # library code - the most complex yet
    class AutoRegister(type):
        def __init__(klass, name, bases, dict):
            autoall.append(klass)

    class AutoText(Base):
        __metaclass__ = AutoRegister
        def __init__(self, text):
            Base.__init__(self, tet)

    # user code - the simplist yet
    class AutoA(AutoText): pass

    class AutoB(AutoText):
        def __init__(self, someparams):
            AutoText.__init__(self)

So here's one point where the simplification of prototypes actually ends
up making the user code more complicated than the full Python version.
As a user of that code, you have no reason at all to care about
__metaclass__ and what it does - you just need to know that AutoAll()
magically creates an instance of AutoText and all it's subclasses.

If you ever need to actually look at the definition of AutoText, though,
you'll see this __metaclass__ thing which is a little harder to
understand.  In the simpler version, the basics of the library are
easier, which means if you have to debug it you'll have an easier time,
but you have to keep calling register().  Which complexity is better is
a matter of philosophy.  (In this particular case, I think they're both
pretty good tradeoffs.)

This is obviously pretty specific to Python - most prototype based OO
languages don't have the option of metaclasses.  But someone else said
that prototype-based languages "tend to grow features that mimic
class-based ones", or soemthing like that.  I think this is a good
example.

Joe



More information about the Python-list mailing list