Observer implementation question ('Patterns in Python')

A

Anders Dahnielson

Sorry if this is a somewhat silly question...

I'm using the Observer implementation found in 'Patterns in
Python' [1] and find it really neat. But, I have not yet fully groked
the new-style class, classic class and property differences involved.
So can somebody please explain to me why this works (using a classic
class):

class C:
TheEvent = Event()
def OnTheEvent(self):
self.TheEvent(self, context)

instance = C()
instance.TheEvent += callback
instance.OnTheEvent()
instance.TheEvent -= callback

While this do not (using new-style class):

class C(object):
TheEvent = Event()
def OnTheEvent(self):
self.TheEvent(self, context)

instance = C()
instance.TheEvent += callback
instance.OnTheEvent()
instance.TheEvent -= callback

Raising an AttributeError :

Traceback (most recent call last):
File "sig_test.py", line 7, in ?
instance.TheEvent += callback
AttributeError: can't set attribute

So far I've figured out that it is the Event class (subclassing the
built-in property type) that is the culprit, but not why. (See
'Patterns in Python' [1] for code listing of the Event class.)

I would be grateful if some helpful soul could explain it.

[1] http://www.suttoncourtenay.org.uk/duncan/accu/pythonpatterns.html#observer
 
J

James Stroud

Anders said:
Sorry if this is a somewhat silly question...

I'm using the Observer implementation found in 'Patterns in
Python' [1] and find it really neat. But, I have not yet fully groked
the new-style class, classic class and property differences involved.
So can somebody please explain to me why this works (using a classic
class):

class C:
TheEvent = Event()
def OnTheEvent(self):
self.TheEvent(self, context)

instance = C()
instance.TheEvent += callback
instance.OnTheEvent()
instance.TheEvent -= callback

While this do not (using new-style class):

class C(object):
TheEvent = Event()
def OnTheEvent(self):
self.TheEvent(self, context)

instance = C()
instance.TheEvent += callback
instance.OnTheEvent()
instance.TheEvent -= callback

Raising an AttributeError :

Traceback (most recent call last):
File "sig_test.py", line 7, in ?
instance.TheEvent += callback
AttributeError: can't set attribute

So far I've figured out that it is the Event class (subclassing the
built-in property type) that is the culprit, but not why. (See
'Patterns in Python' [1] for code listing of the Event class.)

I would be grateful if some helpful soul could explain it.

[1] http://www.suttoncourtenay.org.uk/duncan/accu/pythonpatterns.html#observer

This is because the object derived class C is behaving properly with
respect to its property attributes. __iadd__() is supposed to set the
attribute, but no setter is called because the property (an Event())
does not have a setter defined. The most straightforward fix would not
be to define a setter in construction of Event instances, but would
rather to come up with some other way than __iadd__ to specify changing
the state of the delegates:

# was __iadd__()
def append(self, callback):
self.__delegates = [ cb
for cb in self.__delegates
if getattr(cb, 'im_self', None) != callback]
# If callback is callable, remove the last
# matching callback
if callable(callback):
for i in range(len(self.__delegates)-1, -1, -1):
if self.__delegates == callback:
del self.__delegates
break

[...]

d.append(function)

The use of __iadd__ & __isub__ as described in the article allows a neat
shorthand, but does not function correctly in the context of new style
classes.

James

--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com
 
S

Stargaming

On Fri, 12 Oct 2007 16:55:05 -0700, James Stroud wrote:

[snip]
The use of __iadd__ & __isub__ as described in the article allows a neat
shorthand, but does not function correctly in the context of new style
classes.

James

But still, this call on a descriptor::

obj.descr += val

is resolved into::

descr.__get__(obj, obj.__class__).__iadd__(val)

under the condition that ``descr.__get__``'s return value supports
`__iadd__`. After this is fully evaluated, `__set__` gets issued (as in
``descr.__set__(obj, descr_get_iadd_stuff_here)``).

Besides, you could perfectly do this without any descriptors at all::

class E(object):
def __iadd__(self, val):
print "__iadd__(%s, %s)" % (self, val)
class C(object):
e = E()
c = C()
c.e += 42
# __iadd__(<__main__.E object at 0x...>, 42)

Adding `__call__` to this implementation should be easy.

HTH,
Stargaming
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top