Python __getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
我正试图使用神奇的方法Python __getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,python,oop,python-3.x,getattr,setattr,Python,Oop,Python 3.x,Getattr,Setattr,我正试图使用神奇的方法\uuuuu getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(如果值为int,则应仅允许设置x和y的值,并在用户尝试设置属性区域、周长和距离到圆时,提高属性错误),我的\uuuu getattr\uuuu抛出最大递归错误。当我注释掉它时,\uu
\uuuuu getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(如果值为int,则应仅允许设置x
和y
的值,并在用户尝试设置属性区域
、周长
和距离
到圆
时,提高属性错误
),我的\uuuu getattr\uuuu
抛出最大递归错误。当我注释掉它时,\uuuu getattr\uuuuu
就可以正常工作了
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])
'''
注释掉\uuuu setattr\uuuu
后,测试代码:
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
改进的解决方案
根据这里的讨论,这是一个较短的改进版本。实现了与原始解决方案相同的功能:
from math import pi, hypot, sqrt
class Circle:
def __init__(self, x, y, r):
self.x = x
self.y = y
super().__setattr__('r', r)
super().__setattr__('area', pi * self.r * self.r)
super().__setattr__('circumference', 2 * pi * self.r)
super().__setattr__('distance_to_origin',
abs(sqrt(self.x * self.x + self.y * self.y) - self.r))
def __setattr__(self, name, value):
if name in ['x', 'y']:
if isinstance(value, int):
print('we can set x,y')
super().__setattr__(name, value)
else: # value isn't an int
raise TypeError('Expected an int for: {}'.format(name))
else:
raise AttributeError('Cannot set attribute: {}'.format(name))
解决方案
避免将\uuuu getattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
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._intialized = False
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 * self.x + self.y * self.y) - self.r)
self._intialized = True
def __setattr__(self, name, value):
if name in ['_intialized']:
self.__dict__[name] = value
return
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 for: {}'.format(name))
elif not self._intialized:
self.__dict__[name] = value
elif name in ['area', 'circumference', 'distance_to_origin']:
raise AttributeError('Cannot set attribute: {}'.format(name))
if __name__ == '__main__':
circle = Circle(x=3, y=4, r=5)
print('x:', circle.x)
print('y:', circle.y)
print('r:', circle.r)
print('area:', circle.area)
print('circumference:', circle.circumference)
print('distance_to_origin:', circle.distance_to_origin)
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])
输出看起来不错:
python get_set_attr.py
we can set x,y
we can set x,y
x: 3
y: 4
r: 5
area: 78.53981633974483
circumference: 31.41592653589793
distance_to_origin: 0.0
Setting circle.x to non-integer fails
Setting circle.y to non-integer fails
Setting circle.area fails
Setting circle.circumference fails
Setting circle.distance_to_origin fails
Printing circle.z fails
变异
这将允许使用任何其他名称设置属性:
circle.xyz = 100
但事实并非如此:
circle.xyz
Traceback (most recent call last):
File "get_set_attr.py", line 62, in <module>
circle.xyz
AttributeError: 'Circle' object has no attribute 'xyz'
何时使用\uuu getattr\uuuu()
?
当您访问一个不存在的属性时,Python会引发一个AttributeError
:
class A:
pass
a = A()
a.xyz
....
AttributeError: 'A' object has no attribute 'xyz'
只有当属性不存在时,Python才会调用\uuu getattr\uuu()
。
一个用例是围绕另一个对象的包装器,而不是使用继承。
例如,我们可以定义一个ListWrapper
,它使用列表,但只允许白名单属性:
class ListWrapper:
_allowed_attrs = set(['append', 'extend'])
def __init__(self, value=None):
self._wrapped = list(value) if value is not None else []
def __getattr__(self, name):
if name in self._allowed_attrs:
return getattr(self._wrapped, name)
else:
raise AttributeError('No attribute {}.'.format(name))
def __repr__(self):
return repr(self._wrapped)
我们可以像列表一样使用它:
>>> my_list = ListWrapper('abc')
>>> my_list
['a', 'b', 'c']
附加元素:
>>> my_list.append('x')
>>> my_list
['a', 'b', 'c', 'x']
但是除了在\u allowed\u attrs
中定义的属性外,我们不能使用任何其他属性:
my_list.index('a')
...
AttributeError: No attribute index.
发言人说:
当属性查找未在常规位置找到属性时调用(即,它不是实例属性,也未在类树中找到)。name是属性名称。此方法应返回(计算的)属性值或引发AttributeError
异常
注意,如果该属性是通过正常机制找到的,则不会调用\uuuuGetAttr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu()
(这是\uuuuuuuuuuuuuuuuuuuuuuuu
将无法访问实例的其他属性。请注意,至少对于实例变量,您可以通过不在实例属性字典中插入任何值(而是将其插入另一个对象)来伪造total control。请参阅
下面的方法,以获得对属性访问的完全控制权
您可能对代码中导致问题的两个问题感兴趣
您不能在\uuuu init\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
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)
您没有在中检查r
。这导致r
被默默忽略,然后当访问r
时,在中设置区域
,初始化
,getattr()
调用getattr()
调用getattr()
等等(因为未设置r
),这导致了递归
elif name in ['area', 'circumference', 'distance_to_origin']:
raise RuntimeError('Cannot set attribute')
这是固定代码。更改已在下面的注释中标记为mod
#!/usr/bin/python3
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
# mod : can't set via self.__getattr__
super().__setattr__("r", r)
super().__setattr__("area", pi * self.r * self.r)
super().__setattr__("circumference", 2 * pi * self.r)
super().__setattr__("distance_to_origin", abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r))
def __getattr__(self, name):
print("===== get:", 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("===== set:", name, value)
if name in ['x', 'y']:
if isinstance(value, int):
print('we can set x,y')
super().__setattr__(name, value) # mod : better
else: # value isn't an int
raise TypeError('Expected an int')
elif name in ['r', 'area', 'circumference', 'distance_to_origin']: # mod : add 'r'
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])
'''
如果name==…
几乎肯定应该是如果name in…
,如果value为int:
几乎肯定应该是如果isinstance(value,int):
。这不是递归错误的原因,但这是非常糟糕的代码。另外,在\uuuuu getattr\uuuuuuu>中使用它本身应该会导致递归错误。@ShadowRanger我一直在和==之间,这个版本碰巧仍然有==,但后来我更改了它。你能重复你的第二条评论吗?我一直在重读它,但我不明白你说的话。getattr(x,'abc'))
通过与x.abc
完全相同的代码路径。如果x.abc
不存在,则调用\uuuuu getattr\uuuuu
以尝试满足缺少的属性(这样,如果您首先在\uu getattr\uuuuu
中,您就知道该属性不存在)。如果“找不到属性处理程序”然后转过身,不做任何修改,在自身上请求相同的属性,最终结果是可预测的。你不能在没有设置值的情况下再次请求它;该属性仍然不存在。@ShadowRanger好的,所以只有当我试图访问一个不存在的属性时才应该调用\u getattr\uuuuu
执行循环。test
然后运行\uuu getattr\uuuu
以尝试查找属性test
?为什么最好避免使用\uu getattr\uuuuu
?如果要执行循环,您不需要它吗?test
(循环的一个不存在的属性)若要返回该属性,则不退出?Python会为您执行此操作。请尝试对我的变体执行circle.test
。它会引发AttributeError:“circle”对象没有属性“test”
。无需自行实现。那么何时需要同时实现\uu getattr\uuuuuuuuuuuuuuuuuuuu
和\uuuuuuu setattr\uuuuuuuuuuuu
?我在想您“我想把\uuuuu setattr\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu_
elif name in ['area', 'circumference', 'distance_to_origin']:
raise RuntimeError('Cannot set attribute')
#!/usr/bin/python3
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
# mod : can't set via self.__getattr__
super().__setattr__("r", r)
super().__setattr__("area", pi * self.r * self.r)
super().__setattr__("circumference", 2 * pi * self.r)
super().__setattr__("distance_to_origin", abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r))
def __getattr__(self, name):
print("===== get:", 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("===== set:", name, value)
if name in ['x', 'y']:
if isinstance(value, int):
print('we can set x,y')
super().__setattr__(name, value) # mod : better
else: # value isn't an int
raise TypeError('Expected an int')
elif name in ['r', 'area', 'circumference', 'distance_to_origin']: # mod : add 'r'
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])
'''