[Python-ideas] Add a way to test for a descriptor in Python Code
Joy Diamond
python.gem at gmail.com
Tue Oct 30 04:40:40 EDT 2018
Greetings,
I am trying to emulate attribute lookup, and want to test if symbol found
in __dict__ is an attribute or not in python code (i.e.: does its have a
`tp_descr_get` slot?)
Reading the documentation carefully, I am supposed to test if the attribute
has a `.__get__` method; however, I see no way to test for this properly
(easily).
Currently the only way I know to test for this is (See code at end of this
message):
any('__get__' in m.__dict__ for m in type(v).__mro__)
Which seems terribly inefficient.
The documentation at:
https://docs.python.org/2/howto/descriptor.html
https://docs.python.org/3/howto/descriptor.html
Both says:
"""
For classes, the machinery is in type.__getattribute__() which transforms
B.x into B.__dict__['x'].__get__(None, B). In pure Python, it looks like:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
"""
However, the call to `hasattr(v, '__get__')` appears to me to be incorrect.
The question is *NOT* whether 'v' has an attribute '__get__'; *BUT* whether
`v` has a symbol `__get__` in any of the classes in it's method resolution
order.
Looking at `type_getattro` in "Objects/typeobject.c" here:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3177
Reads:
meta_get = Py_TYPE(meta_attribute)->tp_descr_get
So I really want to know if the `tp_descr_get` slot is set or not.
(Which is a different question than whether `v` has a `__get__` attribute).
The code below shows that:
1. The valid value of `Point.y` is <Not_A_Descriptor>
2. The valid value is returned by `Point.y`, `type.__getattribute__(Point,
y)`, and `fixed__Type__getattribute`
3. The invalid value of `Point.y` is `2` as returned by the [emulated]
`__getattribute__` documented
https://docs.python.org/3/howto/descriptor.html
So I am requesting:
1. An efficient python way to test for `tp_descr_get` slot (and other
descriptor) slots.
2. Fix the documentation referenced above.
Thanks,
Joy Diamond.
NOTE #1: This email describes the very subtle difference between whether
an instance has a `__get__` symbol or not, which I believe is *NOT* the
same question as whether `hasattr(instance, "__get__")` returns true or
not. The first question is does it have the symbol `__get_` [Which python
put in the `tp_descr_slot`] while `hasattr` answers the question does it
have the `__get__` attribute.
NOTE #2: Also using `hasattr(type(v), "__get__")` would not answer the
question I want, because then it might find a `__get__` method in the
meta-class of `type(v)` which again would return an incorrect answer.
Example program that shows that using `hasattr(v, '__get__')` is not a
valid way to test if something is a descriptor (works in python 2, python
3, and pypy):
def get_1(self, a, b):
return 1
def get_2(a, b):
return 2
class Descriptor(object):
__get__ = get_1
class Not_A_Descriptor(object):
def __init__(self):
self.__get__ = get_2
def __repr__(self):
return '<Not_A_Descriptor instance>'
class Point(object):
__slots__ = (())
x = Descriptor()
y = Not_A_Descriptor()
#
# Copied from https://docs.python.org/3/howto/descriptor.html
#
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
#
# My fixed version
#
def fixed__Type__getattribute(self, key):
"FIXED: Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if any('__get__' in m.__dict__ for m in type(v).__mro__):
return v.__get__(None, self)
return v
print('Point.x: %s' % Point.x)
print('Point.y: %s' % Point.y)
print("type.__getattribute__(Point, 'x'): %s" %
type.__getattribute__(Point, 'x'))
print("type.__getattribute__(Point, 'y'): %s" %
type.__getattribute__(Point, 'y'))
print("__getattribute__(Point, 'x'): %s" % __getattribute__(Point, 'x'))
print("__getattribute__(Point, 'y'): %s ***WRONG***" %
__getattribute__(Point, 'y'))
print("fixed__Type__getattribute(Point, 'x'): %s" %
fixed__Type__getattribute(Point, 'x'))
print("fixed__Type__getattribute(Point, 'y'): %s ***CORRECT***" %
fixed__Type__getattribute(Point, 'y'))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181030/38465b46/attachment.html>
More information about the Python-ideas
mailing list