Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

__set__ method is not called for class attribute access

12 views
Skip to first unread message

Ryan

unread,
Aug 5, 2011, 11:29:06 AM8/5/11
to
In the context of descriptors, the __set__ method is not called for
class attribute access. __set__ is only
called to set the attribute on an instance instance of the owner class
to a new value, value. WHY? Is there some other mechanism for
accomplishing this outcome. This subtle difference from __get__cost me
some time to track down. Might think about pointing that out the
documentation.


class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""

def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name

def __get__(self, obj, objtype):
print 'Retrieving', self.name
return self.val

def __set__(self, obj, val):
print 'Updating' , self.name
self.val = val

class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5

print MyClass.x
MyClass.x = 20
print MyClass.x
MyClass.x = 30
print MyClass.x

Retrieving var "x"
10
20
30

I am at a lost on how to intercept class attribute sets. Can anyone
help :-/

Ryan

Peter Otten

unread,
Aug 5, 2011, 12:10:52 PM8/5/11
to pytho...@python.org
Ryan wrote:

A class is just an instance of its metaclass, so you could move the
descriptor into the metaclass to see the expected behaviour in the class:

>>> class A:
... class __metaclass__(type):
... x = RevealAccess(10, 'var "x"')
...
>>> A.x = 42
Updating var "x"
>>> A.x
Retrieving var "x"
42

However:

>>> A().x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'

So you'd need two descriptors if you want to intercept variable access on
both the instance and class level.


Duncan Booth

unread,
Aug 5, 2011, 12:16:09 PM8/5/11
to
Ryan <hen...@yahoo.com> wrote:

The descriptor protocol only works when a value is being accessed or set
on an instance and there is no instance attribute of that name so the
value is fetched from the underlying class.

Classes are instances too, so you may get some of what you want from:

class MyMeta(type):


x = RevealAccess(10, 'var "x"')

class MyClass(object):
__metaclass__ = MyMeta
y = 5

>>> MyClass.x = 20
Updating var "x"
>>> MyClass.x
Retrieving var "x"
20

However note that if you do this you can access "x" only directly on the
class not on an instance of the class:

>>> MyClass().x

Traceback (most recent call last):

File "<pyshell#16>", line 1, in <module>
MyClass().x
AttributeError: 'MyClass' object has no attribute 'x'


--
Duncan Booth http://kupuguy.blogspot.com

Peter Otten

unread,
Aug 5, 2011, 1:00:37 PM8/5/11
to
Duncan Booth wrote:

> The descriptor protocol only works when a value is being accessed or set
> on an instance and there is no instance attribute of that name so the
> value is fetched from the underlying class.

Unlike normal class attributes a descriptor is not shaded by an instance
attribute:

>>> class A(object):
... def set_x(self, value): self.__dict__["x"] = value/2.0
... def get_x(self): return self.__dict__["x"] * 2.0
... x = property(get_x, set_x)
...
>>> a = A()
>>> a.x = 42
>>> a.__dict__["x"]
21.0
>>> a.x
42.0
>>> A.x = "something completely different"
>>> a.x
21.0

Duncan Booth

unread,
Aug 7, 2011, 8:52:48 PM8/7/11
to
Peter Otten <__pet...@web.de> wrote:

True, and in fact that's why your suggestion of using two descriptors, one
at each level works. Thanks for pointing out my mistake.

Fuzzyman

unread,
Aug 10, 2011, 2:33:09 PM8/10/11
to
On Aug 5, 12:29 pm, Ryan <heni...@yahoo.com> wrote:
> In the context of descriptors, the __set__ method is not called for
> class attribute access. __set__ is only
> called to set the attribute on an instance instance of the owner class
> to a new value, value. WHY?

It's an unfortunate asymmetry in the descriptor protocol. You can
write "class descriptors" with behaviour on get, but not on set.

As others point out, metaclasses are an ugly way round this
(particularly given that *basically* a class can only have one
metaclass - so if you're inheriting from a class that already has a
custom metaclass you can't use this technique).

Michael Foord
--
http://voidspace.org.uk/

Fuzzyman

unread,
Aug 10, 2011, 2:36:20 PM8/10/11
to
On Aug 5, 1:16 pm, Duncan Booth <duncan.bo...@invalid.invalid> wrote:

That's not true. Properties, for example, can be got or set even when
they shadow an instance attribute. You're (probably) thinking of
__getattr__ which isn't invoked when an instance attribute exists.

Also, the descriptor protocol *is* invoked for getting an attribute
from a class - but not when setting a class attribute. An unfortunate
asymmetry. It just wasn't considered as a use case when the descriptor
protocol was implemented.

Michael
--
http://voidspace.org.uk/

Duncan Booth

unread,
Aug 10, 2011, 3:35:11 PM8/10/11
to
Fuzzyman <fuzz...@gmail.com> wrote:

>> The descriptor protocol only works when a value is being accessed or set
>> on an instance and there is no instance attribute of that name so the
>> value is fetched from the underlying class.
>>
>
> That's not true. Properties, for example, can be got or set even when
> they shadow an instance attribute. You're (probably) thinking of
> __getattr__ which isn't invoked when an instance attribute exists.

Yes, Peter Otten already corrected me on that point last Friday and I
posted thanking him on Sunday.

Eric Snow

unread,
Aug 10, 2011, 4:27:20 PM8/10/11
to Fuzzyman, pytho...@python.org

Keep in mind that you can still do something like this:

class XMeta(Base.__class__):
"Customize here"

class X(Base, metaclass=XMeta):
"Do your stuff."

They you would put your descriptor hacking in XMeta and still take
advantage of the original metaclass.

-eric

>
> Michael Foord
> --
> http://voidspace.org.uk/

> --
> http://mail.python.org/mailman/listinfo/python-list
>

Fuzzyman

unread,
Aug 10, 2011, 4:40:03 PM8/10/11
to
On Aug 10, 5:27 pm, Eric Snow <ericsnowcurren...@gmail.com> wrote:

> On Wed, Aug 10, 2011 at 8:33 AM, Fuzzyman <fuzzy...@gmail.com> wrote:
> > On Aug 5, 12:29 pm, Ryan <heni...@yahoo.com> wrote:
> >> In the context of descriptors, the __set__ method is not called for
> >> class attribute access. __set__ is only
> >> called to set the attribute on an instance instance of the owner class
> >> to a new value, value. WHY?
>
> > It's an unfortunate asymmetry in the descriptor protocol. You can
> > write "class descriptors" with behaviour on get, but not on set.
>
> > As others point out, metaclasses are an ugly way round this
> > (particularly given that *basically* a class can only have one
> > metaclass - so if you're inheriting from a class that already has a
> > custom metaclass you can't use this technique).
>
> Keep in mind that you can still do something like this:
>
> class XMeta(Base.__class__):
>     "Customize here"
>
> class X(Base, metaclass=XMeta):
>     "Do your stuff."
>
> They you would put your descriptor hacking in XMeta and still take
> advantage of the original metaclass.

Yeah, the way round the "more than one metaclass problem" is to have
your new metaclass inherit from the first one. That's not a general
purpose solution though.

Michael
--
http://voidspace.org.uk/

0 new messages