好吧,如果我从我的发现中理解正确的话。 (因为不知道描述符,所以才喜欢帮忙,还在学习中)
首先,让我们看看__getattr__ 和__getattribute__。
让我们有一个空类A
class A:
pass
如果我初始化一个对象并尝试调用一个属性,因为目前没有,我们得到AttributeError。
a = A()
a.some_property
会发生以下情况:
简单的流程检查:
class FlowDemo:
def __init__(self):
self.inited_property = True
def __getattribute__(self, item):
if item in ('__class__', '__len__') : # For less spam of getting this attribute, if you want, you can remove condition.
print('Get Attribute', item)
# Call default behavior
return super().__getattribute__(item)
def __getattr__(self, item):
print('Get Attr', item)
if item == 'some_magic_name':
return "It's magic!"
raise AttributeError
fd = FlowDemo()
fd.inited_property
# Get Attribute inited_property
# True
fd.some_magic_property
# Get Attribute some_magic_name
# Get Attr some_magic_name
# "It's magic!"
fd.some_property
# Get Attribute some_property
# Get Attr some_property
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# File "stack-class-property-and-descriptors.py", line 67, in # __getattr__
# raise AttributeError
# AttributeError
这大概是可以理解的,包括使用。但可以肯定的是,我将举一个例子。此逻辑用作数据库结果的动态表示(将属性映射到普通字典、列表等)。
但它也可以只是用于访问属性(property)的逻辑,例如访问计数器或验证(但这适用于__setattr__ 和__setattribute__)
描述符呢?
首先让我们看一下数据描述符,它们对我来说更容易理解。
这是具有__get__ 和__set__ 和__delete__ 之一或两者的类或解码器。
一旦定义了这个,python,当在属性定义中使用它并且不返回一个类但它通过__get__获得的值时,在声明一个值时不会覆盖已经声明的类,而是使用它的__set__.
例子:
class WeekDayDescriptor:
def __init__(self):
self.__week_day = 0
def __get__(self, instance, owner=None):
return self.__week_day
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Value must be int')
if not (0 <= value < 6):
raise ValueError('Value must be in range 0 - 6')
self.__week_day = value
class Calendar:
week_day = WeekDayDescriptor()
def __init__(self, week_day):
self.week_day = week_day
演示:
c = Calendar(9)
# ValueError: Value must be in range 0-6
c = Calendar('6')
# TypeError: Value must be int
c = Calendar(3)
c.week_day = 6
c.week_day = 10
# ValueError: Value must be in range 0-6
c.week_day = 'monday'
# TypeError: Value must be int
装饰器描述符:
class Calendar:
@property
def week_day(self):
return self.__week_day
@week_day.setter
def week_day(self, week_day):
if not isinstance(week_day, int):
raise TypeError('Value must be int')
if not (0 <= week_day < 6):
raise ValueError('Value must be in range 0 - 6')
self.__week_day = week_day
def __init__(self, week_day):
self.week_day = week_day
pass
现在对于非数据描述符...
非数据描述符是只有__get__ 的描述符。
据我了解,每个方法都会自动拥有自己的描述符,这要归功于函数获得对对象的引用 - self。
我们可以为函数/方法编写自己的描述符,但这并不是那么简单,我们必须帮助自己并绕过它。
def function_as_method(self, value):
print(self, value)
class HelperDescriptor:
def __get__(self, instance, owner):
def wrapper(*args, **kwargs):
return function_as_method(instance, *args, **kwargs)
return wrapper
class Foo:
baz = HelperDescriptor()
>>> bar = Foo()
>>> bar.baz(1)
<__main__.Foo object at 0x7f64f7768b70> 1
Source of last code block, but in czech lang.
最后,您提到的问题是,当我们将 __get__ 设置为 None 时,您仍然会获得对该函数的引用。
很简单,python并没有直接区分原始数据类型和函数,都是有值的变量(或属性/属性)。无论是 value 还是 callable 都是另一回事。
def f(): return True
print(type(f), f())
# <class 'function'> True
f = 123
print(type(f), f)
# <class 'int'> 123
因此,当我们请求obj.func 方法或直接调用obj.func() 时,首先调用前两个更改魔法-__getattribute__ 和__getattr__。
如果我们调用一个方法,它只有在我们获得内存中的一个函数的引用后才会被调用。
再举个简单的例子:
def func(self, value):
print('Printing:', value)
class PrintDescriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
def wrapper(*args, **kwargs):
print(f"Calling '{self.name}' method")
return func(instance, *args, **kwargs)
return wrapper
class B:
foo = PrintDescriptor('foo')
bar = PrintDescriptor('bar')
def __getattribute__(self, item):
if item not in ('__len__', '__class__', '__dict__'):
print('Get Attribute', item)
return super().__getattribute__(item)
演示:
b = B()
b.foo
# Get Attribute foo
# <function PrintDescriptor.__get__.<locals>.wrapper at 0x7f774a782ee0>
b.foo(2)
# Get Attribute foo
# Calling 'foo' method
# Printing: 2
b.bar(4)
# Get Attribute bar
# Calling 'bar' method
# Printing: 4
来源: