给出了人为的Python类:
class Item(object):
def __getattr__(self, name):
print("In __getattr__ looking for '{}'".format(name))
if name.startswith("exists"):
return "Here's a dummy string"
message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
raise AttributeError(message)
def thing_a(self):
return self.thing_b()
def thing_b(self):
return self.nonexistent_thing()
调用Item().thing_a()
会产生预期的输出:
In __getattr__ looking for 'nonexistent_thing'
Traceback (most recent call last):
File "debug.py", line 13, in <module>
Item().thing_a()
File "debug.py", line 8, in thing_a
return self.thing_b()
File "debug.py", line 11, in thing_b
return self.nonexistent_thing()
File "debug.py", line 5, in __getattr__
raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'nonexistent_thing'
但是,我确实希望thing_a
,thing_b
和nonexistent_thing
为属性。因此,如果我将代码更改为:
class Item(object):
def __getattr__(self, name):
print("In __getattr__ looking for '{}'".format(name))
if name.startswith("exists"):
return "Here's a dummy string"
message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
raise AttributeError(message)
@property
def thing_a(self):
return self.thing_b
@property
def thing_b(self):
return self.nonexistent_thing
并致电Item().thing_a
,我得到了意外的结果:
In __getattr__ looking for 'nonexistent_thing'
In __getattr__ looking for 'thing_b'
In __getattr__ looking for 'thing_a'
Traceback (most recent call last):
File "debug.py", line 15, in <module>
Item().thing_a
File "debug.py", line 5, in __getattr__
raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'thing_a'
嗯...... print()
的输出似乎与执行顺序相反,并且AttributeError
的消息是给thing_a
的,确实存在。
在尝试找出答案的过程中,我发现将AttributeError
更改为普通的Exception
可以使一切正常:
In __getattr__ looking for 'nonexistent_thing'
Traceback (most recent call last):
File "debug.py", line 15, in <module>
Item().thing_a
File "debug.py", line 9, in thing_a
return self.thing_b
File "debug.py", line 13, in thing_b
return self.nonexistent_thing
File "debug.py", line 5, in __getattr__
raise Exception(message)
Exception: 'Item' object has no attribute 'nonexistent_thing'
因此,看来AttributeError
本身在@property
上的表现不佳,可能是因为它被property
对象捕获了。但是AttributeError
似乎是__getattr__
筹集的最合适的例外,并且不带子集筹集Exception
是不理想的。
有人能阐明这个问题,并且知道让@property
和__getattr__
与AttributeError
和谐相处的方法吗?
我已经在Python 2.7和3.6中进行了尝试。
编辑:为__getattr__
添加了(人为设计的)逻辑以响应name
以"exists"
开头的情况。我确实希望它在nonexistent_thing
时失败,但我希望它在正确的时间以正确的AttributeError
升高name
。
答案 0 :(得分:1)
这个难题的缺失之处在于@property
是descriptor(一个实现__get__
/ __set__
/ __del__
来参与属性查找的对象)。
描述符的实现是通过__getattribute__
魔术方法完成的。
the docs(重点是我的)中实现的重要部分:
无条件调用以实现类实例的属性访问。如果该类还定义了
__getattr__()
,则除非__getattribute__()
显式调用或引发AttributeError ,否则不会调用后者。
为了进一步说明您的示例,
class Item(object):
def __getattribute__(self, name):
print(f'__getattribute__ {name}')
try:
return super().__getattribute__(name)
except Exception as e:
print(f'{e} during __getattribute__ {name}')
raise
def __getattr__(self, name):
print("In __getattr__ looking for '{}'".format(name))
# useful logic here which might return a value...
message = "'{}' object has no attribute '{}'".format(type(self).__name__, name)
raise AttributeError(message)
@property
def thing_a(self):
return self.thing_b
@property
def thing_b(self):
return self.nonexistent_thing
Item().thing_a
呼叫顺序如下:
- __getattribute__(thing_a)
- property.__get__(...)
- thing_a
- __getattriute__(thing_b)
- property.__get__(...)
- thing_B
- __getattribute__(nonexistent_thing)
- (raised AE) -> __getattr__(nonexistent_thing)
- (raised AE) -> __getattr_(thing_b)
- (raised AE) -> getattr(thing_a)
在__getattr__
中的描述符解析期间,对AttributeError
的调用是对__getattribute__
的响应。
这是上面的python代码段的输出:
__getattribute__ thing_a
__getattribute__ thing_b
__getattribute__ nonexistent_thing
'Item' object has no attribute 'nonexistent_thing' during __getattribute__ nonexistent_thing
In __getattr__ looking for 'nonexistent_thing'
'Item' object has no attribute 'nonexistent_thing' during __getattribute__ thing_b
In __getattr__ looking for 'thing_b'
'Item' object has no attribute 'thing_b' during __getattribute__ thing_a
In __getattr__ looking for 'thing_a'
Traceback (most recent call last):
File "t.py", line 25, in <module>
Item().thing_a
File "t.py", line 14, in __getattr__
raise AttributeError(message)
AttributeError: 'Item' object has no attribute 'thing_a'