Поиск атрибутов python без какой-либо магии дескриптора?
Я начал более широко использовать протокол дескриптора 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 ответа
Большинство дескрипторов выполняют свою работу, когда к ним обращаются только как к атрибуту экземпляра. Так что удобно возвращать себя при обращении к классу:
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>
Доступ к дескриптору может быть полезен, когда вам нужно расширить его в подклассе, но есть лучший способ сделать это.
inspect
Библиотека предоставляет функцию для извлечения атрибута без какой-либо магии дескриптора: inspect.getattr_static
,
Документация: https://docs.python.org/3/library/inspect.html
(Это старый вопрос, но я продолжаю сталкиваться с ним, пытаясь вспомнить, как это сделать, поэтому я публикую этот ответ, чтобы найти его снова!)
Вышеуказанный метод
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 обходится "магия дескриптора". Также обратите внимание, что если мы присваиваем свойство новой переменной класса, оно ведет себя так же, как оригинал с "магией дескриптора", но если назначено переменной экземпляра, свойство ведет себя как любой нормальный объект и также обходит "магию дескриптора".
>>> test.__class__.classvar = test.__class__.__dict__['value']
>>> test.classvar
3
>>> test.instvar = test.__class__.__dict__['value']
>>> test.instvar
<property object at 0x00000216A39D0778>
Допустим, мы хотим получить дескриптор для 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
это обычный объект (не класс, например).