简体   繁体   中英

Python warn if method has not been called

Suppose you want to call a method foo on object bar , but somehow while typing the method invocation you intuitively treated foo as a property and you typed bar.foo instead of bar.foo() (with parenthesis). Now, both are syntactically correct, so no error is raised, but semantically very different. It happened to me several times already (my experience in Ruby makes it even worse) and caused me dearly in terms of long and confusing debugging sessions.

Is there a way to make Python interpreter print a warning in such cases - whenever you access an attribute which is callable, but you haven't actually called it?

For the record - I thought about overriding __getattribute__ but it's messy and ultimately won't achieve the goal since function invocation via () happens after __getattribute__ has returned.

This can't be done in all cases because sometimes you don't want to call the method, eg you might want to store it as a callable to be used later, like callback = object.method .

But you can use static analysis tools such as pylint or PyCharm (my recommendation) that warn you if you write a statement that looks pointless, eg object.method without any assignment.

Furthermore if you write x = obj.get_x but meant get_x() , then later when you try to use x a static analysis tool may be able to warn you (if you're lucky) that x is a method but an instance of X is expected.

It was quite challenging, but I think i get it done! My code isn't very complicated, but you need to be well aware of metaclasses.

Metaclass and wrapper (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) 

It's very easy to use - just specify a metaclass

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)

If you didn't forget to call these functions, everything works as usual

a = A()                                                                         

a.foo()                                                                         
a.bar(5)

Output:

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

But if you DID forget:

a = A()                                                                         

a.foo                                                                           
a.bar  

You see the following

Warning! foo function hasn't been called!
Warning! bar function hasn't been called!

Happy debugging!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM