简体   繁体   中英

Inner decorators in Python class

I want to create an inner decorator using it inside my Python class for wrapping the selenium frame switching in and out operations. So I tried this:

class MyPage(object):
    # ...
    def framed(self, frame_query: str):
        def decorator(function):
            @functools.wraps(function)
            def wrapper(*args, **kwargs):
                frame = self.driver.find_element(By.XPATH, frame_query)
                self.driver.switch_to.frame(frame)
                out = function(*args, **kwargs)
                self.driver.switch_to.default_content()
                return out
            return wrapper
        return decorator
    # ...
    @framed('//myxpath/iframe')
    def framed_function(self):
        # ...

But then I receive this error:

TypeError: framed() missing 1 required positional argument: 'frame_query'

Obviously it is expecting 2 parameters including the self but in the decorator context it doesn't know anything about the self, so I had to define and inner function inside ´framed_function´ making the solution a lot less elegant:

# My workaround
def framed_function(self):
    @framed('//myxpath/iframe')
    def actual_framed():
        #...
    actual_framed()

Suggestions?

When defining methods for a class, there is no class object yet. There certainly are no instances yet of that class, so there is nothing for a self to be bound to either . Instead, methods are bound to an instance at the time they are looked up on an instance object, see the Python descriptor HOWTO . Just return a new function object from your decorator that will be bound instead.

Also, Python has no privacy model, so there is no concept of 'inner' either (for classes or methods or anything). Just put your decorator outside of the class, this makes it simpler to maintain and reuse, and avoids polluting your class API.

This means that your wrapped() function will become a method, and it'll be passed the self argument when bound. Use that, and pass it on to the wrapped function object explicitly (since that function was not bound):

def framed(frame_query: str):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(self, *args, **kwargs):
            frame = self.driver.find_element(By.XPATH, frame_query)
            self.driver.switch_to.frame(frame)
            out = function(self, *args, **kwargs)
            self.driver.switch_to.default_content()
            return out
        return wrapper
    return decorator

class MyPage(object):
    @framed('//myxpath/iframe')
    def framed_function(self):

So framed() returns decorator() , which in turn is used to decorate framed_function() ; doing so returns wrapper() , which is added to the actual MyPage class object produced. On instances of MyPage() then, accessing instance.framed_function() will bind wrapper() to that instance, so that self is bound to that instance object. Within wrapper you then have access to the instance as self , as well as to the original function object (unbound) and to the frame_query string.

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