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
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.
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
> 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
True, and in fact that's why your suggestion of using two descriptors, one
at each level works. Thanks for pointing out my mistake.
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/
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/
>> 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.
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/
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/