Мой декоратор работает для обычных функций, но не для instaces - PullRequest
0 голосов
/ 08 февраля 2019

У меня есть рабочий декоратор для запуска метода через некоторое время True, и он отлично работает на обычной функции.Проблема возникает, когда я пытаюсь украсить функцию экземпляра.

Это декоратор:

from threading import Thread


def run_in_while_true(f):
    def decorator(break_condition=False):
        def wrapper(*args, **kwargs):
            while True:
                if break_condition:
                    return
                f(*args, **kwargs)
         return wrapper
    return decorator


class A(object):
    @run_in_while_true
    def print_ch(self, ch):
         print ch

@run_in_while_true
def print_with_dec(ch):
     print ch


print_with_dec()('f')  # Call 1
# If i would want to pass a break condition i would write this
print_with_dec(1==1 and 2*2==4)('f')

a = A()
a.print_ch()('4')  # Call 2

`

Вызов 1 выполняется, как ожидается, и печатает fa lot,Вызов 2 по какой-то причине получает параметр self, в котором break_condition имеет значение, и потому что проверка для break_condition является истинной, и функция возвращает.

Каким образом мне нужно изменить декоратор, чтобы он также работал с объектами?Заранее спасибо

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Спасибо всем, кто помог, после изучения того, как вызывается функция из объекта, я написал эти финальные декораторы.Они оба работают для обычных функций и методов объектов.Один запускает функцию в цикле, пока не будет выполнено условие, другой запускает первый в потоке, поэтому программа не ожидает.

Декораторы

def loop_in_while_oop(f):
    """ Runs a function in a loop, params have to be passed by name"""
    def decorated(self=None, break_if=None, *args,**kwargs):
        """
        :param self: Will be passed automatically if needed
        :param break_if: Lambada expression for when to stop running the while loop
        """
        done = break_if if callable(break_if) else lambda: break_if
        while not done():
            if self is not None:
                f(self, *args, **kwargs)
            else:
                f(*args, **kwargs)
    return decorated

def loop_in_thread_oop(f):
    """ Runs function in a loop in a thread, MUST: pass arguments by name"""
    def decorated(self=None, break_if=lambda: False, *args, **kwargs):
        """
        :param self: Will be passed automatically if needed
        :param break_if: Lambada expression for when to stop running the while loop, if value not passed will run forever
        """
        f1 = loop_in_while_oop(f)
        t = Thread(target=f1, args=args, kwargs=dict(self=self, break_if=break_if, **kwargs))
        t.start()
    return decorated

Использование декораторов

class SomeObj(object):

    @loop_in_thread_oop
    def print_c(self, c):
        print c


@loop_in_thread_oop
def p1(f):
    print f


@loop_in_thread_oop
def p2(f):
    print f

if __name__ == '__main__':
    a = SomeObj()
    start = time.time()
    a.print_c(c='a')  # Will run forever because break_if was not specified
    p1(f='3', break_if=lambda: time.time() - start > 3)  # Will stop after 3 seconds
    p2(f='5', break_if=lambda: time.time() - start > 5)  # Will stop after 5 seconds

Вывод:

  • Между 0-3 секундами: Печать a35 (Порядок не постоянен)
  • Между 3-5 секундами: Печать a5 (Порядок не постоянен)
  • Между 3-5 секундами: Печать a
0 голосов
/ 08 февраля 2019

Ваш результирующий код действительно выглядит странно:

a.print_ch()('4')  # Call 2

Это потому, что у вас есть один дополнительный слой в декораторе:

def run_in_while_true(f):
    def decorator(break_condition=False):
        def wrapper(*args, **kwargs):

Декоратор @run_in_while_true собираетсяreturn decorator, который должен быть вызван для возврата wrapper, который должен быть вызван для оценки результата.@run_in_while_true вызывается автоматически как часть украшения.Два других требуют двух наборов паренов, как показано в вашем коде.

Это проблема, потому что вызов метода, такой как a.print_ch(), автоматически передает этот вызов первому вызову:

a.print_ch()('4')
# is much the same as
# A.print_ch(self=a)('4')

, который объясняет, почему вы получаете свой инвокант в вашем break_condition.

Я бы посоветовал вам попытаться объединить две внутренние функции.Просто передайте именованный параметр, например break_condition или break_when или break_if, в функцию / метод, и обертка перехватит это одно значение: import functools

def run_until_done(func):
    @functools.wraps
    def wrapper(*args, break_if=None, **kwargs):
        done = break_if if callable(break_if) else lambda: break_if

        while not done():
            func(*args, **kwargs)
     return wrapper

@run_until_done
def print_with_dec(ch):
    print ch

print_with_dec('4', break_if=lambda: 1==1 and is_done())
...