Python警告如果方法没有被调用

4
假设您想在对象bar上调用一个名为foo的方法,但在键入方法调用时,您直觉地将foo视为属性,并键入了bar.foo而不是带有括号的bar.foo()。现在,两者在语法上都是正确的,因此不会引发错误,但在语义上却有很大区别。这种情况已经发生过多次(我在Ruby中的经验使情况更糟),并导致我长时间的混乱调试。

是否有一种方法可以让Python解释器在这种情况下打印警告-每当您访问可调用的属性,但实际上没有调用它时?

记录一下-我考虑过覆盖__getattribute__,但这样做会很麻烦,而且最终无法实现目标,因为通过()进行函数调用发生在__getattribute__返回之后。


你想要一个全局设置,在所有情况下都会发出警告吗? - Alex Hall
我想不出更好的方法,所以是的,我猜我会。 - Maciej Satkiewicz
正如我们在文档中所读到的,函数(可调用对象)是Python的一等公民。如果您找到了解决方案,那将非常有趣,尽管我相信这会带来更多的伤害而不是好处。您更可能想要像访问属性一样访问方法,而不是因为拼写错误而被警告不要调用它。祝你好运! - Gomes J. A.
2个回答

2

这很具有挑战性,但我认为我做到了!我的代码并不是非常复杂,但你需要对元类非常了解。

元类和包装器(WarnIfNotCalled.py):

class Notifier:                                                             
    def __init__(self, name, obj, callback):                                
        self.callback = callback                                            
        self.name = name                                                    
        self.obj = obj                                                      
        self.called = False                                                 
    def __call__(self, *args, **kwargs):                                    
        self.callback(self.obj, *args, **kwargs)                            
        self.called = True                                                  
    def __del__(self):                                                      
        if not self.called:                                                 
            print("Warning! {} function hasn't been called!".format(self.name))

class WarnIfNotCalled(type):                                                
    def __new__(cls, name, bases, dct):                                     
        dct_func = {}                                                       
        for name, val in dct.copy().items():                                
            if name.startswith('__') or not callable(val):                  
                continue                                                    
            else:                                                           
                dct_func[name] = val                                        
                del dct[name]                                               
        def getattr(self, name):                                            
            if name in dct_func:                                            
                return Notifier(name, self, dct_func[name])                 
        dct['__getattr__'] = getattr                                        
        return super(WarnIfNotCalled, cls).__new__(cls, name, bases, dct) 

使用起来非常简单 - 只需要指定一个元类即可

from WarnIfNotCalled import WarnIfNotCalled

class A(metaclass = WarnIfNotCalled):                                       
    def foo(self):                                                          
        print("foo has been called")                                        
    def bar(self, x):                                                       
        print("bar has been called and x =", x)

如果您没有忘记调用这些函数,一切都会像往常一样正常工作。
a = A()                                                                         

a.foo()                                                                         
a.bar(5)

输出:

foo has been called
bar has been called and x = 5

但是如果您确实忘记了:

a = A()                                                                         

a.foo                                                                           
a.bar  

你看到以下内容:
Warning! foo function hasn't been called!
Warning! bar function hasn't been called!

愉快地进行调试吧!


1
好的,我认为这不是完美的解决方案,因为它需要设置类的元类。尽管如此,我还是要向你致敬,因为你面对了挑战!我正在努力寻找我的解决方案,希望我能够发布与你一样好的东西。 - Right leg
谢谢您的回复!我认为这非常接近我要找的东西。据我所知,每次调用a.foo都会返回一个新的Notifier实例,然后我们依靠__del__钩子来通知我们是否调用了它。这可靠吗?我可以假设__del__总是会被执行吗?那么像x = a.foo[a.foo for a in aas]这样的东西呢?更重要的是,我想能够快速地在许多类中调试这样的东西 - 我该怎么做?我猜在脚本开头替换type.__new__方法可能会起作用,但我认为这是不可能的... - Maciej Satkiewicz

2

这并非所有情况下都可行,因为有时您不想调用该方法,例如,您可能希望将其存储为可调用对象以供稍后使用,如callback = object.method

但是,您可以使用静态分析工具,例如pylint或PyCharm(我推荐的),如果您编写的语句看起来毫无意义,例如没有任何赋值的object.method,它们会向您发出警告。

此外,如果您编写了x = obj.get_x,但实际上想要的是get_x(),那么稍后当您尝试使用x时,静态分析工具可能会警告您(如果您很幸运),x是一个方法,但需要X的实例。


谢谢你的回答!我同意,有时候你确实想要存储可调用对象 - 但通常情况下你不会经常这样做,因此在调试时记录所有这种情况的实例可能是一个可靠的工具。我尝试了pylint,但我猜我运气不太好 - 它忽略了像这个x = obj.get_x的例子。其他情况可能是列表推导式 - 写[obj.get_x for obj in objs]而不是[obj.get_x() for obj in objs] - 这对于静态分析工具来说似乎非常棘手。我刚刚尝试了PyCharm,但效果并不好。顺便说一句,我更喜欢简单的Sublime而不是IDE;P - Maciej Satkiewicz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接