【问题标题】:Is there a simpler syntax for member decorators for methods?方法的成员装饰器是否有更简单的语法?
【发布时间】:2020-01-10 01:24:29
【问题描述】:

我更改了 class,它有一个必须在运行许多其他函数之前运行的函数。 “prior-to-others”功能现在是一个装饰器。但是我想出的语法似乎很不直观。

以前是这样的:

class Session:
    def __init__(self, ts):
        self.tempo_throttlers = [TempoThrottler(t) for t in ts]
        ...

    def _wait_on_throttlers(self):
        for th in self.tempo_throttlers:
            if not th.isallowed():
                time.sleep(th.mustwait())
            th.consume()
        ...

    def request1(self):
        self._wait_on_throttlers()
        ...

    def request2(self):
        self._wait_on_throttlers()
        ...

现在是这样的:

class Session:
    def __init__(self, ts):
        self.tempo_throttlers = [TempoThrottler(t) for t in ts]
        ...

    def _wait_on_throttlers(self):
        for th in self.tempo_throttlers:
            if not th.isallowed():
                time.sleep(th.mustwait())
            th.consume()
        ...

    def _throttled(f):
        def inner(self, *args, **kwargs):
            self._wait_on_throttlers()
            return f(self, *args, **kwargs)
        return inner

    @_throttled
    def request1(self):
        ...

    @_throttled
    def request2(self):
        ...

而且,虽然我认为这个装饰器的使用使代码更加清晰,但这个装饰器的实现需要一些工作。它也非常脆弱且难以阅读。例如,如果将内部返回行return f(self, *args, **kwargs) 更改为return self.f(*args, **kwargs),则它将不再起作用。

这似乎与类元素的编译顺序有关。我还担心这会在未来的 Python 版本中中断。我正在使用 Python 3.6.8。

是否有一种可接受和/或推荐的方法来制作此类方法的类成员装饰器,从而减少反直觉和不那么脆弱?

为了一个最小的可重现示例... 可以被认为是一个pass 语句,TempThrottler 类可以定义为下面(这不是实际的实现,但足以满足上面的示例):

class TempoThrottler:
    def __init__(self, t):
        pass
    def isallowed(self):
        from random import randint
        return (True, False)[randint(0,1)]
    def mustwait(self):
        return 1
    def consume(self):
        pass

【问题讨论】:

  • 装饰器不必在课堂上,因此将它们移出课堂可能会有所改善。
  • 如果您将f(self, ...) 更改为self.f(...),它应该会中断。 f 在第一种情况下是对函数的引用,但在第二种情况下是属性,并且由于self 没有属性'f',因此会出错。
  • @martineau 装饰器使用对象的数据。它的全部意义在于调用wait_on_throttlers。会话的节流状态是会话状态的一部分。
  • 因为它通过 _wait_on_throttlers() 参数调用 _wait_on_throttlers(),所以它不需要在类的主体中执行它。如果它使用类中定义的任何东西但没有作为参数传递给它,那将是一个问题。
  • 如果你能提供一个minimal reproducible example 我会试着给你看。为了清楚起见,我的意思是将 _throttled(f) 装饰器移出课堂。 _wait_on_throttlers() 方法可能需要在里面说(但没有真正的代码,很难说)。

标签: python python-3.x python-decorators


【解决方案1】:

下面是一个可运行的示例,说明了我对如何将装饰器函数完全移出类的建议:

from random import randint
import time


class TempoThrottler:
    def __init__(self, t):
        pass
    def isallowed(self):
 #       return [True, False](randint(0,1))
        return [True, False][randint(0,1)]
    def mustwait(self):
        return 1
    def consume(self):
        pass


# Decorator not in class.
def _throttled(f):
    def inner(self, *args, **kwargs):
        self._wait_on_throttlers()
        return f(self, *args, **kwargs)
    return inner


class Session:
    def __init__(self, ts):
        self.tempo_throttlers = [TempoThrottler(t) for t in ts]
        ...

    def _wait_on_throttlers(self):
        for th in self.tempo_throttlers:
            if not th.isallowed():
                time.sleep(th.mustwait())
            th.consume()
        ...

    @_throttled
    def request1(self):
        print('in request1()')
        ...

    @_throttled
    def request2(self):
        print('in request2()')
        ...


s = Session(range(3))
s.request1()  # -> in request1()
s.request2()  # -> in request2()

【讨论】:

  • 我明白我误解了您的原始评论。这很好。我实际上并没有意识到它被定义为成员函数的事实并没有在内部使用。
  • 装饰器与类紧密耦合,因此 IMO 将其放入类中是合乎逻辑的 - 但如图所示,没有要求,这样做也没有任何其他优势最终使它成为个人喜好。
  • 要真正简化语法——实际上几乎可以摆脱它——你可以编写一个 class 装饰器来自动装饰它所应用的类的方法,只要它们可以以某种方式以编程方式识别。一种使用某种命名约定的简单方法,但至少还有几种方法可以想到。
  • 我想我将通过制作一个参数化装饰器precall_decorator 来解决装饰器是特定于类的事实,该装饰器采用一个要预先调用的函数。然后将_throttled = precall_decorator(_wait_on_temp_throttlers) 设置为类变量。这样,在类外部定义的precall_decorator 不会与任何类耦合。
【解决方案2】:

虽然这可能更复杂,但乍一看,为了解决这个问题

  1. 装饰器未绑定到类的实例
  2. 这个装饰器与类耦合

我要将装饰器参数化并将其移到课堂之外。然后可以将专用于特定预调用方法的装饰器实例分配给类变量。然后这个类变量就可以作为实际的装饰器了。

def precall_decorator(precall_f):
    def decor(f):
        def inner(self, *args, **kwargs):
            precall_f(self)
            return f(self, *args, **kwargs)
        return inner
    return decor

class Session:
    def __init__(self, ts):
        self.tempo_throttlers = [TempoThrottler(t) for t in ts]
        ...

    def _wait_on_throttlers(self):
        for th in self.tempo_throttlers:
            if not th.isallowed():
                time.sleep(th.mustwait())
            th.consume()
        ...

    _throttled = precall_decorator(_wait_on_throttlers)

    @_throttled
    def request1(self):
        ...

    @_throttled
    def request2(self):
        ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2018-10-08
    • 1970-01-01
    • 2012-07-24
    • 1970-01-01
    相关资源
    最近更新 更多