V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
saximi
V2EX  ›  Python

请教一个关于__getattr__的问题

  •  
  •   saximi · 2017-08-23 22:54:18 +08:00 · 2276 次点击
    这是一个创建于 2431 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

    你的第一个实现,onInstance 的__init__方法中 aClass 是一个 str ——'X is',既你之后调用__call__也是会报错的。
    lrxiao
        12
    lrxiao  
       2017-08-24 00:35:15 +08:00
    @saximi ..你还是在调用 self.__getattr__("wrapper")啊 你 self.wrapper 是个__dict__里的 instance 就不循环了
    u2386
        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 查问题。
    saximi
        14
    saximi  
    OP
       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
        15
    saximi  
    OP
       2017-08-24 01:21:39 +08:00
    @u2386 谢谢,才知道有 pdb 这个工具,我用的是 Visual Studio Code 这个 IDE,但是坑爹的是始终搞不定单步调试功能,一按 F5 就整个程序全部执行,设置的断点从未生效,如果可以单步执行的话,就更容易发现问题了。
    wwqgtxx
        16
    wwqgtxx  
       2017-08-24 08:34:28 +08:00 via iPhone
    @saximi 的确“使用 getattr 本身并不等于死循环”,但是如果调用父类的__getattr__会在最大层面上避免出现递归循环
    第二,我说的不要直接用 getattr 是对于 self 这个对象的,即不要在你自己的__getattr__调用 self.xxx 或者 getattr(self,"xxx"),你的代码应该改成 return getattr(super(self,类名).__getattr__("wrapped"),attr)这样才不会导致死循环
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2227 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:39 · PVG 08:39 · LAX 17:39 · JFK 20:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.