поиск атрибутов python без какой-либо магии дескриптора? - PullRequest
6 голосов
/ 27 октября 2009

Я начал более широко использовать протокол дескриптора Python в коде, который писал. Как правило, магия поиска по умолчанию в Python - это то, что я хочу, но иногда я обнаруживаю, что хочу получить сам объект дескриптора вместо результатов его __get__ метода. Желание узнать тип дескриптора или состояние доступа, хранящееся в дескрипторе, или что-то подобное.

Я написал код ниже, чтобы обойти пространства имен в правильном порядке и вернуть атрибут raw независимо от того, является ли он дескриптором или нет. Я удивлен тем, что не могу найти встроенную функцию или что-то в стандартной библиотеке, чтобы сделать это - я полагаю, что это должно быть там, и я просто не заметил этого или гуглил для правильного поискового запроса.

Есть ли где-нибудь функциональность в дистрибутиве Python, которая уже делает это (или что-то подобное)?

Спасибо!

from inspect import isdatadescriptor

def namespaces(obj):
    obj_dict = None
    if hasattr(obj, '__dict__'):
        obj_dict = object.__getattribute__(obj, '__dict__')

    obj_class = type(obj)
    return obj_dict, [t.__dict__ for t in obj_class.__mro__]

def getattr_raw(obj, name):
    # get an attribute in the same resolution order one would normally,
    # but do not call __get__ on the attribute even if it has one
    obj_dict, class_dicts = namespaces(obj)

    # look for a data descriptor in class hierarchy; it takes priority over
    # the obj's dict if it exists
    for d in class_dicts:
        if name in d and isdatadescriptor(d[name]):
            return d[name]

    # look for the attribute in the object's dictionary
    if obj_dict and name in obj_dict:
        return obj_dict[name]

    # look for the attribute anywhere in the class hierarchy
    for d in class_dicts:
        if name in d:
            return d[name]

    raise AttributeError

Редактировать Ср, 28 октября 2009 г.

Ответ Дениса дал мне соглашение об использовании в моих классах дескрипторов для получения самих объектов дескрипторов. Но у меня была целая иерархия классов дескрипторных классов, и я не хотел начинать каждую __get__ функцию с шаблона

def __get__(self, instance, instance_type):
    if instance is None: 
        return self
    ...

Чтобы избежать этого, я заставил корень дерева классов дескрипторов наследовать от следующего:

def decorate_get(original_get):
    def decorated_get(self, instance, instance_type):
        if instance is None:
            return self
        return original_get(self, instance, instance_type)
    return decorated_get

class InstanceOnlyDescriptor(object):
    """All __get__ functions are automatically wrapped with a decorator which
    causes them to only be applied to instances. If __get__ is called on a 
    class, the decorator returns the descriptor itself, and the decorated
    __get__ is not called.
    """
    class __metaclass__(type):
        def __new__(cls, name, bases, attrs):
            if '__get__' in attrs:
                attrs['__get__'] = decorate_get(attrs['__get__'])
            return type.__new__(cls, name, bases, attrs)

Ответы [ 4 ]

12 голосов
/ 27 октября 2009

Большинство дескрипторов выполняют свою работу, когда к ним обращаются только как к атрибуту экземпляра. Поэтому удобно возвращать себя при обращении к классу:

class FixedValueProperty(object):
    def __init__(self, value):
        self.value = value
    def __get__(self, inst, cls):
        if inst is None:
            return self
        return self.value

Это позволяет вам получить сам дескриптор:

>>> class C(object):
...     prop = FixedValueProperty('abc')
... 
>>> o = C()
>>> o.prop
'abc'
>>> C.prop
<__main__.FixedValueProperty object at 0xb7eb290c>
>>> C.prop.value
'abc'
>>> type(o).prop.value
'abc'

Обратите внимание, что это работает и для (большинства?) Встроенных дескрипторов:

>>> class C(object):
...     @property
...     def prop(self):
...         return 'abc'
... 
>>> C.prop
<property object at 0xb7eb0b6c>
>>> C.prop.fget
<function prop at 0xb7ea36f4>

Доступ к дескриптору может быть полезен, когда вам нужно расширить его в подклассе, но есть лучший способ сделать это.

3 голосов
/ 09 июня 2017

Библиотека inspect предоставляет функцию для извлечения атрибута без какой-либо магии дескриптора: inspect.getattr_static.

Документация: https://docs.python.org/3/library/inspect.html#fetching-attributes-statically

(Это старый вопрос, но я постоянно сталкиваюсь с ним, пытаясь вспомнить, как это сделать, поэтому я публикую этот ответ, чтобы найти его снова!)

0 голосов
/ 14 января 2018

Допустим, мы хотим получить дескриптор для obj.prop, где type(obj) is C.

C.prop обычно работает, потому что дескриптор обычно возвращает себя при доступе через C (то есть, связан с C). Но C.prop может вызвать дескриптор в своем метаклассе. Если бы prop не присутствовало в obj, obj.prop повысило бы AttributeError, в то время как C.prop могло бы не быть. Так что лучше использовать inspect.getattr_static(obj, 'prop').

Если вас это не устраивает, вот метод, специфичный для CPython (из _PyObject_GenericGetAttrWithDict в Objects/object.c):

import ctypes, _ctypes

_PyType_Lookup = ctypes.pythonapi._PyType_Lookup
_PyType_Lookup.argtypes = (ctypes.py_object, ctypes.py_object)
_PyType_Lookup.restype = ctypes.c_void_p

def type_lookup(ty, name):
    """look for a name through the MRO of a type."""
    if not isinstance(ty, type):
        raise TypeError('ty must be a type')

    result = _PyType_Lookup(ty, name)
    if result is None:
        raise AttributeError(name)

    return _ctypes.PyObj_FromPtr(result)

type_lookup(type(obj), 'prop') возвращает дескриптор таким же образом, когда CPython использует его в obj.prop, если obj - обычный объект (например, не класс).

0 голосов
/ 25 января 2016

вышеуказанный метод

class FixedValueProperty(object):
    def __init__(self, value):
        self.value = value
    def __get__(self, inst, cls):
        if inst is None:
            return self
        return self.value

Отличный метод всякий раз, когда вы управляете кодом свойства, но в некоторых случаях, например, когда свойство является частью библиотеки, контролируемой кем-то другим, где полезен другой подход. Этот альтернативный подход также может быть полезен в других ситуациях, таких как реализация сопоставления объектов, обход пространства имен, как описано в вопросе, или других специализированных библиотек.

Рассмотрим класс с простым свойством:

class ClassWithProp:

    @property
    def value(self):
        return 3
>>>test=ClassWithProp()
>>>test.value
3
>>>test.__class__.__dict__.['value']
<property object at 0x00000216A39D0778>

При доступе из класса объектов-контейнеров dict обходится «магия дескриптора». Также обратите внимание, что если мы присваиваем свойство новой переменной класса, оно ведет себя так же, как оригинал с «магией дескриптора», но если назначено переменной экземпляра, свойство ведет себя как любой нормальный объект, а также обходит «магию дескриптора». 1011 *

>>> test.__class__.classvar =  test.__class__.__dict__['value']
>>> test.classvar
3
>>> test.instvar = test.__class__.__dict__['value']
>>> test.instvar
<property object at 0x00000216A39D0778>
...