In trying to support user requests for C++-like behavior of wrapped static data members, I noticed the following little assymetry:
#define a property class ... class Prop(object): ... def __get__(self, obj, type=None): ... print '__get__', (self, obj, type) ... return 'value' ... ... def __set__(self, obj, type=None): ... print '__set__', (self, obj, type) ... ... def __delete__(self, obj, type=None): ... print '__delete__', (self, obj, type) ... # use it in a class Y ... class Y(object): ... x = Prop() ... a = Y()
a.x # all accesses to a.x are intercepted __get__ (<__main__.Prop object at 0x00877BC8>, <__main__.Y object at 0x00878108>, <class '__main__.Y'>) 'value' a.x = 42 __set__ (<__main__.Prop object at 0x00877BC8>, <__main__.Y object at 0x00878108>, 42)
Y.x # Prop intercepts reads of the class attribute __get__ (<__main__.Prop object at 0x00877BC8>, None, <class '__main__.Y'>) 'value' Y.x = 1 # But not assignments Y.x 1
class mc(object.__class__): # to intercept Y.x assignment ... x = Prop() # I have to define this ... class Y(object): ... __metaclass__ = mc ... Y.x # now all accesses to Y.x are intercepted __get__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.Y'>, <class '__main__.mc'>) 'value' Y.x = 1 __set__ (<__main__.Prop object at 0x00876AB8>, <class '__main__.Y'>, 1) a = Y() # But not accesses to a.x a.x Traceback (most recent call last): File "<stdin>", line 1, in ? AttributeError: 'Y' object has no attribute 'x'
As you can see, the only way to intercept assignment to Y.x is to stick a property Y's class, i.e. the metaclass (or to modify __setattr__ in the metaclass, but it amounts to the same thing). In C++, a mutable static data member can be modified via the class Y::x = 1; or an instance of the class a.x = 1; I notice that Python supports this sort of dual access for reading attributes and calling static functions, but getting that behavior for mutable attributes seems unreasonably difficult: I need a property in the metaclass *and* in the class. 1. To throw out a straw-man suggestion, what about adding an additional protocol __set2__ which, if found, will be called instead of __set__ both for reading _and_ writing attributes on the class? 2. What are the optional type=None arguments for? It seems as though only the middle argument (obj) is ever None. I just copied this protocol out of descrintro.html 3. Is there documentation for __delete__ anywhere? -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams wrote
3. Is there documentation for __delete__ anywhere?
It seems like it's only in the whatsnew22.tex docs. __get__ and __set__ are mentioned in the Doc/ext/newtypes.tex file, but that section is commented out with % XXX Descriptors need to be explained in more detail somewhere, but % not here. Unfortunately they're not anywhere else, either. Any suggestions for where they should be documented? -- Anthony Baxter <anthony@interlink.com.au> It's never too late to have a happy childhood.
Anthony Baxter <anthony@interlink.com.au> writes:
David Abrahams wrote
3. Is there documentation for __delete__ anywhere?
It seems like it's only in the whatsnew22.tex docs.
__get__ and __set__ are mentioned in the Doc/ext/newtypes.tex file, but that section is commented out with
% XXX Descriptors need to be explained in more detail somewhere, but % not here.
Unfortunately they're not anywhere else, either. Any suggestions for where they should be documented?
Probably http://www.python.org/dev/doc/devel/ref/attribute-access.html is the most logical place for them. -- Dave Abrahams Boost Consulting www.boost-consulting.com
I notice that Python supports this sort of dual access for reading attributes and calling static functions, but getting that behavior for mutable attributes seems unreasonably difficult: I need a property in the metaclass *and* in the class.
I disagree that it is *unreasonably* difficult. Given that Python tries to do with a single notation ('.') where C++ has two notations ('.' and '::') to disambiguate cases, not to mention declarations, I think it is reasonable that this unusual case requires a little more effort; you should be glad that it's possible at all. :-)
1. To throw out a straw-man suggestion, what about adding an additional protocol __set2__ which, if found, will be called instead of __set__ both for reading _and_ writing attributes on the class?
Let me throw out this straw-man right away: I'm not excited about this. You can write a metaclass that implements this generically though.
2. What are the optional type=None arguments for? It seems as though only the middle argument (obj) is ever None. I just copied this protocol out of descrintro.html
Only __get__ has both obj and type as arguments; __set__ has obj and value, __delete__ only obj. __get__ has obj and type because it can be used for instance and class attribute access. When called for a class, obj is None because it is unavailable; but when called for an instance, type is set to obj's class, for the convenience of descriptors that aren't interested in the instance (like staticmethod and classmethod).
3. Is there documentation for __delete__ anywhere?
Apparently not, but it's easy to guess what it does if you know __getattr__, __setattr__ and __delattr__. It's __delete__ and not __del__ because __del__ is already taken. In an early alpha release it was actually __del__, but that didn't work very well. :-) --Guido van Rossum (home page: http://www.python.org/~guido/)
Guido van Rossum <guido@python.org> writes:
I notice that Python supports this sort of dual access for reading attributes and calling static functions, but getting that behavior for mutable attributes seems unreasonably difficult: I need a property in the metaclass *and* in the class.
I disagree that it is *unreasonably* difficult. Given that Python tries to do with a single notation ('.') where C++ has two notations ('.' and '::') to disambiguate cases
FWIW, it doesn't disambiguate anything: class C { static int x; }; C c; int y = C::x; int z = c.x; The compiler already knows which of 'C' and 'c' is a typename and which is an object. Maybe it's just a pointless syntax difference...
not to mention declarations, I think it is reasonable that this unusual case requires a little more effort; you should be glad that it's possible at all. :-)
My heart is full of gladness!
1. To throw out a straw-man suggestion, what about adding an additional protocol __set2__ which, if found, will be called instead of __set__ both for reading _and_ writing attributes on the class?
Let me throw out this straw-man right away: I'm not excited about this. You can write a metaclass that implements this generically though.
I considered these two options: option 1: introduce a separate metaclass for every wrapped class so that we have a place to stick a property object. option 1a: only do this if the user supplies a special extra template parameter to the class_<...> declaration option 2: Implement a special property type which allows us to easily identify property attributes which correspond to Boost.Python static data members Implement a special __setattr__ in the Boost.Python metaclass which looks up the attribute on the class to see if it has this special type; if so, it is called, and otherwise the default __setattr__ behavior takes effect. I was going to go with option 1. Are you suggesting option 2?
2. What are the optional type=None arguments for? It seems as though only the middle argument (obj) is ever None. I just copied this protocol out of descrintro.html
Only __get__ has both obj and type as arguments; __set__ has obj and value, __delete__ only obj.
__get__ has obj and type because it can be used for instance and class attribute access. When called for a class, obj is None because it is unavailable; but when called for an instance, type is set to obj's class, for the convenience of descriptors that aren't interested in the instance (like staticmethod and classmethod).
Yes, but type is always non-None, it seems. Look at descrintro.html again. It says: Example: coding super in Python. As an illustration of the power of the new system, here's a fully functional implementation of the super() built-in class in pure Python. This may also help clarify the semantics of super() by spelling out the search in ample detail. The print statement at the bottom of the following code prints "DCBA". class Super(object): def __init__(self, type, obj=None): self.__type__ = type self.__obj__ = obj def __get__(self, obj, type=None): ^^^^^^^^^ if self.__obj__ is None and obj is not None: return Super(self.__type__, obj) else: return self Also, PEP 252 says: - __get__(): a function callable with one or two arguments that retrieves the attribute value from an object. What's that about? Does __get__ ever get called with just one argument (excluding self)? If so, when?
3. Is there documentation for __delete__ anywhere?
Apparently not, but it's easy to guess what it does if you know __getattr__, __setattr__ and __delattr__. It's __delete__ and not __del__ because __del__ is already taken. In an early alpha release it was actually __del__, but that didn't work very well. :-)
It's easy enough to guess what it does by instrumenting it, too. However, it's been out in a released Python for several versions now and I get tired of guessing every time I have to write a new one of these ;-). I think it's time there were some official docs. -- Dave Abrahams Boost Consulting www.boost-consulting.com
FWIW, it doesn't disambiguate anything:
I'm not prepared to argue about C++ with you. :-)
I considered these two options:
option 1: introduce a separate metaclass for every wrapped class so that we have a place to stick a property object.
option 1a: only do this if the user supplies a special extra template parameter to the class_<...> declaration
option 2: Implement a special property type which allows us to easily identify property attributes which correspond to Boost.Python static data members
Implement a special __setattr__ in the Boost.Python metaclass which looks up the attribute on the class to see if it has this special type; if so, it is called, and otherwise the default __setattr__ behavior takes effect.
I was going to go with option 1. Are you suggesting option 2?
It would seem to be less work than having a separate metaclass for each class that has a static data item. But it's really up to you.
2. What are the optional type=None arguments for? It seems as though only the middle argument (obj) is ever None. I just copied this protocol out of descrintro.html
Only __get__ has both obj and type as arguments; __set__ has obj and value, __delete__ only obj.
__get__ has obj and type because it can be used for instance and class attribute access. When called for a class, obj is None because it is unavailable; but when called for an instance, type is set to obj's class, for the convenience of descriptors that aren't interested in the instance (like staticmethod and classmethod).
Yes, but type is always non-None, it seems.
Yes, that's what I said.
Look at descrintro.html again. It says:
Example: coding super in Python.
As an illustration of the power of the new system, here's a fully functional implementation of the super() built-in class in pure Python. This may also help clarify the semantics of super() by spelling out the search in ample detail. The print statement at the bottom of the following code prints "DCBA".
class Super(object): def __init__(self, type, obj=None): self.__type__ = type self.__obj__ = obj def __get__(self, obj, type=None): ^^^^^^^^^ if self.__obj__ is None and obj is not None: return Super(self.__type__, obj) else: return self
Also, PEP 252 says:
- __get__(): a function callable with one or two arguments that retrieves the attribute value from an object.
What's that about? Does __get__ ever get called with just one argument (excluding self)? If so, when?
When you call it yourself (rather than when it is called as a result of __getattribute__). I suppose this is a bit redundant.
3. Is there documentation for __delete__ anywhere?
Apparently not, but it's easy to guess what it does if you know __getattr__, __setattr__ and __delattr__. It's __delete__ and not __del__ because __del__ is already taken. In an early alpha release it was actually __del__, but that didn't work very well. :-)
It's easy enough to guess what it does by instrumenting it, too. However, it's been out in a released Python for several versions now and I get tired of guessing every time I have to write a new one of these ;-). I think it's time there were some official docs.
So volunteer some. --Guido van Rossum (home page: http://www.python.org/~guido/)
Guido van Rossum <guido@python.org> writes:
I considered these two options:
option 1: introduce a separate metaclass for every wrapped class so that we have a place to stick a property object.
option 1a: only do this if the user supplies a special extra template parameter to the class_<...> declaration
option 2: Implement a special property type which allows us to easily identify property attributes which correspond to Boost.Python static data members
Implement a special __setattr__ in the Boost.Python metaclass which looks up the attribute on the class to see if it has this special type; if so, it is called, and otherwise the default __setattr__ behavior takes effect.
I was going to go with option 1. Are you suggesting option 2?
It would seem to be less work than having a separate metaclass for each class that has a static data item. But it's really up to you.
My reasoning was: it favors speed and code size over data size. The only downside I can see is that an extra object (the metaclass object) is created for every wrapped class. It's also simpler to implement. However, I think it might also cost an extra property object per wrapped class, unless I can find a way to get the class and its instance to share the property. Hmm, feels a bit hack-ish to me now that you mention it. Maybe option 2 is better after all. But then, do I want to implement tp_getattr or tp_getattro? I don't recall the differences.
Yes, but type is always non-None, it seems.
Yes, that's what I said.
Look at descrintro.html again. It says:
Example: coding super in Python.
As an illustration of the power of the new system, here's a fully functional implementation of the super() built-in class in pure Python. This may also help clarify the semantics of super() by spelling out the search in ample detail. The print statement at the bottom of the following code prints "DCBA".
class Super(object): def __init__(self, type, obj=None): self.__type__ = type self.__obj__ = obj def __get__(self, obj, type=None): ^^^^^^^^^ if self.__obj__ is None and obj is not None: return Super(self.__type__, obj) else: return self
Also, PEP 252 says:
- __get__(): a function callable with one or two arguments that retrieves the attribute value from an object.
What's that about? Does __get__ ever get called with just one argument (excluding self)? If so, when?
When you call it yourself (rather than when it is called as a result of __getattribute__).
When do you call it yourself?
I suppose this is a bit redundant.
I guess; I don't know. I'm trying to understand whether "callable with one or two arguments" is really part of the requirements for use as a property method, or just something that got thrown in there.
3. Is there documentation for __delete__ anywhere?
Apparently not, but it's easy to guess what it does if you know __getattr__, __setattr__ and __delattr__. It's __delete__ and not __del__ because __del__ is already taken. In an early alpha release it was actually __del__, but that didn't work very well. :-)
It's easy enough to guess what it does by instrumenting it, too. However, it's been out in a released Python for several versions now and I get tired of guessing every time I have to write a new one of these ;-). I think it's time there were some official docs.
So volunteer some.
I'm anxious to, and I will -- just as soon as I can get a clear picture of what I should say. Gee, I guess "someone" should document __getattribute__ while I'm in there. Are there any other obvious omissions in that neighborhood? -- Dave Abrahams Boost Consulting www.boost-consulting.com
David Abrahams <dave@boost-consulting.com> writes:
It's easy enough to guess what it does by instrumenting it, too. However, it's been out in a released Python for several versions now and I get tired of guessing every time I have to write a new one of these ;-). I think it's time there were some official docs.
So volunteer some.
I'm anxious to, and I will -- just as soon as I can get a clear picture of what I should say. Gee, I guess "someone" should document __getattribute__ while I'm in there. Are there any other obvious omissions in that neighborhood?
Didn't want this hanging over my head so I submitted the doc patch already. I guess youse guys can clean it up if I made any conceptual or formatting errors. -- Dave Abrahams Boost Consulting www.boost-consulting.com
However, I think it might also cost an extra property object per wrapped class, unless I can find a way to get the class and its instance to share the property. Hmm, feels a bit hack-ish to me now that you mention it. Maybe option 2 is better after all. But then, do I want to implement tp_getattr or tp_getattro? I don't recall the differences.
Definitely tp_getattro. The difference is that tp_getattr takes a C string argument and tp_getattr takes a Python string object.
When you call it yourself (rather than when it is called as a result of __getattribute__).
When do you call it yourself?
I've never called it myself except in the test suite.
I suppose this is a bit redundant.
I guess; I don't know. I'm trying to understand whether "callable with one or two arguments" is really part of the requirements for use as a property method, or just something that got thrown in there.
The latter. The type argument was an afterthought -- I had originally not thought about class attribute access at all. --Guido van Rossum (home page: http://www.python.org/~guido/)
Guido van Rossum <guido@python.org> writes:
However, I think it might also cost an extra property object per wrapped class, unless I can find a way to get the class and its instance to share the property. Hmm, feels a bit hack-ish to me now that you mention it. Maybe option 2 is better after all. But then, do I want to implement tp_getattr or tp_getattro? I don't recall the differences.
Definitely tp_getattro. The difference is that tp_getattr takes a C ^^^^^^^^^^ string argument and tp_getattr takes a Python string object. ^^^^^^^^^^ I guess I can figure out which you mean by looking at the source (Luke).
When you call it yourself (rather than when it is called as a result of __getattribute__).
When do you call it yourself?
I've never called it myself except in the test suite.
I suppose this is a bit redundant.
I guess; I don't know. I'm trying to understand whether "callable with one or two arguments" is really part of the requirements for use as a property method, or just something that got thrown in there.
The latter. The type argument was an afterthought -- I had originally not thought about class attribute access at all.
OK; I hope you like the doc patch I submitted, then. It doesn't require "callable with one argument", only two. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (3)
-
Anthony Baxter
-
David Abrahams
-
Guido van Rossum