Существует ли более простой синтаксис для декораторов элементов для методов? - PullRequest
0 голосов
/ 10 января 2020

Я изменил class, у которого была функция, которую нужно было запустить до запуска ряда других функций. Функция «до других» теперь является декоратором. Но синтаксис, который я придумал, кажется очень не интуитивным.

Раньше это было примерно так:

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.

Существует ли принятый и / или рекомендуемый способ создания таких декораторов-членов класса для методов класса, которые были бы менее противоречивыми и менее fr agile?

Для Для минимального воспроизводимого примера , ... можно считать оператором 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

Ответы [ 2 ]

1 голос
/ 10 января 2020

Ниже приведен работоспособный пример, который иллюстрирует мое предложение о том, как можно было бы полностью убрать функцию декоратора из класса:

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()
0 голосов
/ 11 января 2020

Хотя это может быть более сложным, на первый взгляд, чтобы решить, что

  1. , декоратор не привязан к экземпляру класса
  2. , с которым этот декоратор связан класс

Я собираюсь сделать параметризованный декоратор и переместить его за пределы класса. Затем экземпляр декоратора, специализированный для определенного метода предварительного вызова c, может быть назначен переменной класса. И тогда эта переменная класса может быть использована в качестве фактического декоратора.

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):
        ...
...