请教一个关于__getattr__的问题

2017-08-23 22:54:18 +08:00
 saximi

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_的递归调用了?

2387 次点击
所在节点    Python
16 条回复
Trim21
2017-08-23 22:57:22 +08:00
点进来一看第一行果然又在代码块外头...
saximi
2017-08-23 23:16:18 +08:00
@Trim21 我觉得论坛不是很好用,当贴代码的时候,我都是直接文本贴进来然后选 markdown,然后就是这个效果了,连变量前面的两个下划线都显示不出来。 有没有简便的方法可以让贴出来的代码格式规范呢
wwqgtxx
2017-08-23 23:18:36 +08:00
在__getattr__中永远不要直接使用 getattr()或者 self.xxx ,应该使用 super(self,类名).__getattr__来访问
Trim21
2017-08-23 23:25:08 +08:00
@saximi
```
code block here
```
saximi
2017-08-23 23:42:03 +08:00
@Trim21 万分感谢! 现在还有个问题,就是论坛如何贴图呢? 据说要装插件,但是我的 CHROME 和 FIREFOX 都无法安装上插件,不知道是怎么回事,还有其他方法可以发图么?
lrxiao
2017-08-23 23:53:41 +08:00
1. self.__getattr__("wrapper")

2. 因为 self.__getattr__只对__dict__/__base__.__dict__...找不到负责 hook 请用__getattribute__
lrxiao
2017-08-23 23:55:06 +08:00
@lrxiao s/只对 /只对找不到
Trim21
2017-08-23 23:55:39 +08:00
@saximi 把图片上传到微博,获取到真正的图片地址 然后![](url here)
saximi
2017-08-24 00:04:45 +08:00
@lrxiao 我是在 PYTHON3 下执行的。我把“ return getattr(self.wrapped, attr) ” 这一句改成“ object.__getattribute__(self.wrapped, attr)”后,还是一样死循环呢。
saximi
2017-08-24 00:05:50 +08:00
@wwqgtxx 您是说在__getattr__方法中使用 super(self,类名).__getattr__ ? 这样不就是死循环了么?__getattr__重载时又调用了自己
u2386
2017-08-24 00:32:04 +08:00
先不说别的。

你的第一个实现,onInstance 的__init__方法中 aClass 是一个 str ——'X is',既你之后调用__call__也是会报错的。
lrxiao
2017-08-24 00:35:15 +08:00
@saximi ..你还是在调用 self.__getattr__("wrapper")啊 你 self.wrapper 是个__dict__里的 instance 就不循环了
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 查问题。
saximi
2017-08-24 01:13:24 +08:00
@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__了。
saximi
2017-08-24 01:21:39 +08:00
@u2386 谢谢,才知道有 pdb 这个工具,我用的是 Visual Studio Code 这个 IDE,但是坑爹的是始终搞不定单步调试功能,一按 F5 就整个程序全部执行,设置的断点从未生效,如果可以单步执行的话,就更容易发现问题了。
wwqgtxx
2017-08-24 08:34:28 +08:00
@saximi 的确“使用 getattr 本身并不等于死循环”,但是如果调用父类的__getattr__会在最大层面上避免出现递归循环
第二,我说的不要直接用 getattr 是对于 self 这个对象的,即不要在你自己的__getattr__调用 self.xxx 或者 getattr(self,"xxx"),你的代码应该改成 return getattr(super(self,类名).__getattr__("wrapped"),attr)这样才不会导致死循环

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/385303

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX