def Private(*privates):
def onDecorator(aClass):
class onInstance:
def __init__(self, aClass,*args):
print("onInstance init")
self.aClass=aClass
print("onInstance init over")
def __call__(self, *args, **kargs):
print("call")
self.wrapped = self.aClass(*args, **kargs)
def __getattr__(self, attr):
if attr in privates:
raise TypeError('private attribute fetch: ' + attr)
else:
print("getarrt")
return getattr(self.wrapped, attr)
return onInstance
return onDecorator
if name == 'main':
@Private('data', 'size')
class Doubler:
def __init__(self, label, start):
print("Doubler init")
self.label = label
self.data = start
X = Doubler('X is', [1, 2, 3])
print("X.label1=",X.label)
上面的程序输出如下:
onInstance init
onInstance init over
getarrt
getarrt #后续跟着无数个 getarrt,出现死循环
但是如果把__call__方法删除,并对__init__方法修改成如下后,就不会出现 print("getarrt")语句的死循环了:
def Private(*privates):
def onDecorator(aClass):
class onInstance:
def __init__(self, *args,**kargs):
print("onInstance init")
self.wrapped=aClass(*args,**kargs)
print("onInstance init over")
def __getattr__(self, attr):
if attr in privates:
raise TypeError('private attribute fetch: ' + attr)
else:
print("getarrt")
return getattr(self.wrapped, attr)
return onInstance
return onDecorator
if name == 'main':
@Private('data', 'size')
class Doubler:
def __init__(self, label, start):
print("Doubler init")
self.label = label
self.data = start
X = Doubler('X is', [1, 2, 3])
print("X.label1=",X.label)
输出如下:
onInstance init
Doubler init
onInstance init over
getarrt
X.label1= X is
我的问题如下:
1、第一段代码会导致反复打印"getarrt"是 getattr(self.wrapped, attr) 这个语句中的 self.wrapped 导致的对吧, 但是 self.wrapped 在__call__方法中是有声明的,虽然__call__方法还未得到执行,这样也会被__getattr__视作未定义的属性而捕获么?
2、第二段代码做了修改后,为何 getattr(self.wrapped, attr)不会导致对__getattr_的递归调用了?
1
Trim21 2017-08-23 22:57:22 +08:00
点进来一看第一行果然又在代码块外头...
|
2
saximi OP @Trim21 我觉得论坛不是很好用,当贴代码的时候,我都是直接文本贴进来然后选 markdown,然后就是这个效果了,连变量前面的两个下划线都显示不出来。 有没有简便的方法可以让贴出来的代码格式规范呢
|
3
wwqgtxx 2017-08-23 23:18:36 +08:00 via iPhone
在__getattr__中永远不要直接使用 getattr()或者 self.xxx ,应该使用 super(self,类名).__getattr__来访问
|
5
saximi OP @Trim21 万分感谢! 现在还有个问题,就是论坛如何贴图呢? 据说要装插件,但是我的 CHROME 和 FIREFOX 都无法安装上插件,不知道是怎么回事,还有其他方法可以发图么?
|
6
lrxiao 2017-08-23 23:53:41 +08:00
1. self.__getattr__("wrapper")
2. 因为 self.__getattr__只对__dict__/__base__.__dict__...找不到负责 hook 请用__getattribute__ |
9
saximi OP @lrxiao 我是在 PYTHON3 下执行的。我把“ return getattr(self.wrapped, attr) ” 这一句改成“ object.__getattribute__(self.wrapped, attr)”后,还是一样死循环呢。
|
10
saximi OP @wwqgtxx 您是说在__getattr__方法中使用 super(self,类名).__getattr__ ? 这样不就是死循环了么?__getattr__重载时又调用了自己
|
11
u2386 2017-08-24 00:32:04 +08:00
先不说别的。
你的第一个实现,onInstance 的__init__方法中 aClass 是一个 str ——'X is',既你之后调用__call__也是会报错的。 |
12
lrxiao 2017-08-24 00:35:15 +08:00
@saximi ..你还是在调用 self.__getattr__("wrapper")啊 你 self.wrapper 是个__dict__里的 instance 就不循环了
|
13
u2386 2017-08-24 00:45:56 +08:00
以下摘自[https://docs.python.org/2/reference/datamodel.html#object.__getattribute__]
object.__getattribute__(self, name) Called unconditionally to implement attribute accesses for instances of the class. If the class also defines getattr(), the latter will not be called unless getattribute() either calls it explicitly or raises an AttributeError. This method should return the (computed) attribute value or raise an AttributeError exception. In order to avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.getattribute(self, name). 所以第一个实现,通过__getattr__里查找一个不存在的属性 wrapped,但是在最后又一次使用 self.wrapped,递归就发生了。 第二个实现之所以没有问题,是因为 wrapped 已经存在,并且是 aClass 的实例。 另:推荐 pdb 查问题。 |
14
saximi OP @u2386
@lrxiao 感谢大家!我终于明白了,我对代码中的错误总结如下: 1、第一段代码,类 onInstance 的__init__方法中,错误地传入了 aClass 参数,这样传入后该参数并不是装饰器 onDecorator 参数中的类 aClass,实际上却会对应到类 Doubler 的第一个位置参数 label。 2、第一段代码,因为程序执行到 X.label 时始终没有触发类 onInstance 的__call__方法,所以在__call__方法中才首次赋值的 self.wrapped 并未出现在 onInstance 的__dict__中, 从而在__getattr__中的 self.wrapped 会导致死循环。我之前一直以为只要在类定义中写出来的变量都会出现在类的__dict__中,原来是要被执行后才会加入__dict__的,受教了! 另外,上面有朋友提到的“在__getattr__中永远不要直接使用 getattr()或者 self.xxx ”,这句话要辩证地看, 一方面使用 getattr 本身并不等于死循环,只要确保方法的参数不会递归调用__getattr__即可;另一方面只要确保 self.xxx 已经被赋值,就会出现在 self.__dict__中,从而就不会递归调用__getattr__了。 |