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.