没有任何描述符魔法的python属性查找?
Posted
技术标签:
【中文标题】没有任何描述符魔法的python属性查找?【英文标题】:python attribute lookup without any descriptor magic? 【发布时间】:2010-12-10 07:58:27 【问题描述】:我已经开始在我编写的代码中更广泛地使用 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
编辑,2009 年 10 月 28 日,星期三。
Denis 的回答给了我一个约定,可以在我的描述符类中使用来获取描述符对象本身。但是,我有一个完整的描述符类层次结构,我不想每个 __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)
【问题讨论】:
有时你想要描述符对象?这违反了对描述符的核心期望:它们应该看起来像属性。为什么要打破这个基本预期?为什么要这样做?为什么要创造如此复杂的东西? 我正在做的事情对我来说并没有那么复杂,但我想你可以说我正在试验这个设计。在我目前的特殊情况下,我有一个描述符,它返回游戏中武器的强度。该值是描述符状态(武器强度)和实例(船的健康)的函数。有不同种类的武器;通常我只想要值结果,但在少数情况下,我需要知道它是一种什么样的武器——描述符的类型。如果描述符具有不属于描述符协议的方法,并且您想调用它们怎么办? 【参考方案1】:假设我们想要获取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
in 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)
如果obj
是一个普通对象(例如,不是类),则type_lookup(type(obj), 'prop')
在 CPython 在obj.prop
使用它时以相同的方式返回描述符。
【讨论】:
【参考方案2】:inspect
库提供了一个无需任何描述符魔法即可检索属性的函数:inspect.getattr_static
。
文档:https://docs.python.org/3/library/inspect.html#fetching-attributes-statically
(这是一个老问题,但我在尝试记住如何做到这一点时不断遇到它,所以我发布了这个答案,以便我可以再次找到它!)
【讨论】:
【参考方案3】:以上方法
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>
【讨论】:
【参考方案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>
当您需要在子类中扩展它时,访问描述符可能很有用,但有一个 better way 可以做到这一点。
【讨论】:
没意识到;很高兴知道。函数本身将是该模式的一个例外,但可能是唯一的例外。将不得不戳入内置描述符。 虽然它没有准确回答我的问题,但您的回答确实帮助我解决了我的潜在问题。我会接受的。 函数是这种模式的一个例外(我假设你说的是方法)?不,c.method
从描述中返回绑定方法,而C.method
返回未绑定方法。这是相同的模式。
函数不完全遵循这种模式,但非常相似:function.__get__(None, cls)
返回未绑定的方法,而不是自身。
staticmethod
和 classmethod
也打破了这个。以上是关于没有任何描述符魔法的python属性查找?的主要内容,如果未能解决你的问题,请参考以下文章