[Zope] ZClass, Base Classes and logic (QUEST)

Martijn Pieters mj@digicool.com
Sat, 11 Mar 2000 12:24:08 +0100


From: "Oscar Picasso" <picasso@videotron.ca>
> Hi,
>
> Most of the time when someone complains about DTML syntax or the learning
curve
> and so on, the answer is we should code our logic in python if we know
this
> language. That's what I am trying to do because I love Python.
>
> However, many times I don't know how to import my logic in Zope.
>
> example:
>
> I wrote a very simple module MyTest.py:
> ##################################
> class SuperClass:
> def __init__(self):
> self.a='This is a in SuperClass'
>
> class MyClass(SuperClass):
> def __init__(self):
> SuperClass.__init__(self)
> self.b='This is b in Myclass'
> ##################################
> In the python interpreter I do the following things:
> >>>import MyTest
> >>>import MyTest.MyClass
> >>>t=MyTest.MyClass.MyClass()
> >>>t.a
> 'This is a in SuperClass'
> >>>t.b
> 'This is b in Myclass'
>
> That's what I expected. Now I intall the package in zope.  I make a ZClass
which
> only Base class in MyClass. Its metatype is MyClassClass, so it appears on
the
> popup menu near the Add button.
>
> But when I try to add an instance I get the following message:
>
> Error Type: TypeError
>           Error Value: unbound method must be called with class instance
1st
>           argument
>
> I guess that the problem is the line
> SuperClass.__init__(self)
>
> ==>however I don't understand why it works fine with the python
interpreter and
> why not in Zope.
>

This is a side effect of using Extension Classes. From ExtensionClass.stx:

  Overriding methods inherited from Python base classes

    A problem occurs when trying to overide methods inherited from
    Python base classes.  Consider the following example::

      from ExtensionClass import Base

      class Spam:

        def __init__(self, name):
            self.name=name

      class ECSpam(Base, Spam):

        def __init__(self, name, favorite_color):
            Spam.__init__(self,name)
            self.favorite_color=favorite_color

    This implementation will fail when an 'ECSpam' object is
    instantiated.  The problem is that 'ECSpam.__init__' calls
    'Spam.__init__', and 'Spam.__init__' can only be called with a
    Python instance (an object of type '"instance"') as the first
    argument.  The first argument passed to 'Spam.__init__' will be an
    'ECSpam' instance (an object of type 'ECSPam').

    To overcome this problem, extension classes provide a class method
    'inheritedAttribute' that can be used to obtain an inherited
    attribute that is suitable for calling with an extension class
    instance.  Using the 'inheritedAttribute' method, the above
    example can be rewritten as::

      from ExtensionClass import Base

      class Spam:

        def __init__(self, name):
            self.name=name

      class ECSpam(Base, Spam):

        def __init__(self, name, favorite_color):
            ECSpam.inheritedAttribute('__init__')(self,name)
            self.favorite_color=favorite_color

    This isn't as pretty but does provide the desired result.

--
Another way, and I used this, is by bypassing the method binding, by
accessing the unbound method. The above example could also be:

        def __init__(self, name, favorite_color):
            ECSpam.__init__.im_func(self, name)
            self.favorite_color=favorite_color


> Others behaviors of Zope:
> suppose i add the following line in the MyClass __init__ function:
> self.d='I am d'
>
> No problem when I run it with the interpreter:
> >>>t.d
> 'I am d'
>
> In zope:
> <dtml-var "MyClassInstance.d">
> renders:
> Error Type: AttributeError
>           Error Value: d
>
> I get the right value of d only with the new instances of MyClass. On the
other
> hand if MyClass has a property sheet, all the instances are updated if
change
> the property sheet.
>
> ==>So again, why these differences in the behavior of ZClass instances
depending
> of which part we modify (the python Base Class or the Zope ZClass)?

The propertysheet variables become class variables, which are persisted as
well. So when you retrieve an instance, the persisted class that described
the behaviour of that instance is taken from the ZODB, and the new persisted
class has the .d attribute there.

In your python class however, .d is a instance variable, it gets set when a
new instance is created and __init__ is called. And exisiting instances
don't get __init__ called again, after all they were initialized already.
They only get their current state restored, and that state never included
the d attribute. Only on new instances, where __init__ is called, does d get
set.

You could get around this by making d a class variable, by adding d='I am d'
to you class definition. Alternatively, you could use the __setstate__
method, it is called when the instance is retrieved from the ZODB. It
revives the state of the object, and you'll have to remember to call
Persitant.__setstate__ as well:

    def __setstate__(self, state):
        Persistent.__setstate__(self, state)
        if not hasattr(self, 'd'):
            self.d = 'I am d'

>
> I really love Zope but I prefer code in python. However I hit often the
wall
> when I try to put the pieces together. I think that speaking about
> documentation : clear API docs would be, for me the most important.
> What behavior could I expect from the differents parts of Zope.
>
> Example of docs I find useful with other 'languages':
> Python Library Reference
> Java API documentation

We are working on this. Two projects that will work towards solving this:

  http://www.zope.org/Members/Amos/Documentation/Plan
  http://www.zope.org/Members/michel/Projects/Interfaces/FrontPage

Martijn Pieters
| Software Engineer    mailto:mj@digicool.com
| Digital Creations  http://www.digicool.com/
| Creators of Zope       http://www.zope.org/
|   The Open Source Web Application Server
---------------------------------------------