将自身传递给装饰器对象

Pass self to decorator object

有这样的装饰器对象

class wait_for_page_load(object):

    def __init__(self, driver, time_to_wait=20):
        self.driver = driver
        self.time_to_wait = time_to_wait

    def __call__(self, function):
        @functools.wraps(function)
        def wrapper(*args):
            old_page = self.driver.find_element_by_tag_name('html')
            function(*args)
            WebDriverWait(self.driver, self.time_to_wait).until(staleness_of(old_page))
        return wrapper

我想将它应用于另一个 class 的方法,像这样:

class VehiclePage(object):

    def __init__(self, driver):
        self.driver = driver

    @wait_for_page_load(self.driver)
    def open(self):
        self.driver.get('%s/vehicles/' % BASE_URL)

这给了我一个错误。有没有办法将 self.driver 传递给装饰器?

简答:不,没有。

长答案: driver 属性是在实例化 class 时设置的。但是,当 class 被解释时,装饰器是 运行 。也就是说,当解释器在加载模块时首先读取它。此时,您还没有准备好任何实例。要执行此类操作,您将不得不重构代码。

此外,即使这样做有效,您最终也会对所有对象使用装饰器的单个实例 class。可能不是你所期望的。

不过,一个简单的解决方法是在 __init__ 中应用装饰器。虽然不是很优雅,但如果你真的需要应用装饰器,那将是可行的。

def __init__(self, driver):
    self.driver = driver
    self.open = wait_for_page_load(self.driver)(self.open)

但是我相信您需要通过调用 types.MethodType 将包装器绑定到 class - 老实说,重新组织代码可能更好。

您不必将 self 传递给装饰器对象。如果装饰器 returns 一个函数,那么该函数将在调用时访问 self 。例如

def pass_value(function):
    def wrapper(self):
        function(self, self.value)
    return wrapper

class Printer(object):
    def __init__(self, value):
        self.value = value

    @pass_value
    def print_(self, v):
        print v

Printer("blah").print_()

这种方法的一个问题是它需要 self 实现一个特定的接口(比如有一个字段叫做 driver,而不是直接将驱动程序传递给装饰器)。

你的装饰器会变成:

def wait_for_page_load(time_to_wait=20):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(self, *args):
            old_page = self.driver.find_element_by_tag_name('html')
            function(self, *args)
            WebDriverWait(self.driver, time_to_wait).until(staleness_of(old_page))
        return wrapper
    return decorator

用作:

@wait_for_page_load() # brackets are needed
def open(self):
    ...