__set__ method is not called for class attribute access

R

Ryan

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
 
P

Peter Otten

Ryan said:
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 :-/

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 __metaclass__(type):
.... x = RevealAccess(10, 'var "x"')
....Retrieving var "x"
42

However:
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.
 
P

Peter Otten

Duncan said:
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:
.... 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
 
F

Fuzzyman

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
 
F

Fuzzyman

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.

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
 
E

Eric Snow

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.

-eric
 
F

Fuzzyman

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
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top