[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