【发布时间】:2016-01-01 07:25:22
【问题描述】:
我正在尝试使用魔术方法 __getattr__ 和 __setattr__ 创建一个圆形类,我的 __getattr__ 似乎在工作,但是当我实现 __setattr__ 时(它应该只允许x 和 y 如果值是 int 则设置,并在用户尝试将属性 area、circumference 和 distance 设置为 circle 时引发 AttributeError),我的__getattr__ 抛出最大递归错误。当我将其注释掉时,__getattr__ 然后就可以正常工作了。
from math import pi, hypot, sqrt
'''
Circle class using __getattr__, and __setattr__ (rename circle2)
'''
# __getattr__(self, name): Automatically called when the attribute name
# is accessed and the object has no such attribute.
# __setattr__(self, name, value): Automatically called when an attempt is made to bind the attribute name to value.
class Circle:
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
self.area = pi * self.r * self.r
self.circumference = 2 * pi * self.r
self.distance_to_origin = abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r)
def __getattr__(self, name):
if name in ["x", "y", "r", "area", "circumference", "distance_to_origin"]:
print('__get if statement') # check getattr working
return getattr(self, name)
else:
print('Not an attribute')
return None
'''
def __setattr__(self, name, value):
print(name, value)
if name in ['x', 'y']:
if isinstance(value, int):
print('we can set x,y')
self.__dict__[name] = value
else: # value isn't an int
raise TypeError('Expected an int')
elif name in ['area', 'circumference', 'distance_to_origin']:
raise RuntimeError('Cannot set attribute')
'''
if __name__ == '__main__':
circle = Circle(x=3, y=4, r=5)
# print(circle.x)
print(circle.__getattr__('x'))
# print(circle.y)
print(circle.__getattr__('y'))
# print(circle.r)
print(circle.__getattr__('r'))
# print(circle.area)
print(circle.__getattr__('area'))
# print(circle.circumference)
print(circle.__getattr__('circumference'))
# print(circle.distance_to_origin)
print(circle.__getattr__('distance_to_origin'))
# print(circle.test)
'''
tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"),
('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"),
('circle.area = 23.4', "print('Setting circle.area fails')"),
('circle.circumference = 23.4', "print('Setting circle.circumference fails')"),
('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"),
('circle.z = 5.6', "print('Setting circle.z fails')"),
('print(circle.z)', "print('Printing circle.z fails')")]
for test in tests:
try:
exec(test[0])
except:
exec(test[1])
'''
将__setattr__注释掉,测试代码:
if __name__ == '__main__':
circle = Circle(x=3, y=4, r=5)
# print(circle.x)
print(circle.__getattr__('x'))
# print(circle.y)
print(circle.__getattr__('y'))
# print(circle.r)
print(circle.__getattr__('r'))
# print(circle.area)
print(circle.__getattr__('area'))
# print(circle.circumference)
print(circle.__getattr__('circumference'))
# print(circle.distance_to_origin)
print(circle.__getattr__('distance_to_origin'))
打印出来:
__get if statement
3
__get if statement
4
__get if statement
5
__get if statement
78.53981633974483
__get if statement
31.41592653589793
__get if statement
0.0
【问题讨论】:
-
if name == ...几乎可以肯定是if name in ...,if value is int:几乎肯定应该是if isinstance(value, int):。不是递归错误的原因,但这是一个糟糕的代码。 -
另外,在
__getattr__中使用getattr而不在实例上设置任何内容(因此它仍然不是现有值)应该会导致递归错误。 -
@ShadowRanger 我一直在 in 和 == 之间切换,这个版本碰巧还有 ==,但我已经改变了它。你能重复你的第二条评论吗,我一直在重读,但我不明白你在说什么。
-
getattr(x, 'abc')通过与x.abc完全相同的代码路径。如果x.abc不存在,则调用__getattr__以尝试满足缺少的属性(因此,如果您首先在__getattr__中,则知道该属性不存在)。如果“找不到属性处理程序”然后转过身来,不加修改地要求自己提供相同的属性,最终结果是可以预测的。你不能在没有设置值的情况下再次请求它;该属性仍然不存在。 -
@ShadowRanger 好的,所以
__getattr__只有在我试图访问不存在的属性时才应该被调用?因此,如果我要执行circle.test,那么将运行__getattr__以尝试查找属性test?
标签: python oop python-3.x getattr setattr