Как разделить декораторов от функции в Python

Не переусердствуйте. Как и в течение десятилетий, передайте числовое смещение от стандартной фактической эпохи 1 января 1970 года, полуночи по Гринвичу / UTC / & amp; c в секундах (или миллисекундах) с этой эпохи. JavaScript нравится, Java нравится, C нравится, а Интернету нравится.

58
задан Peter Mortensen 20 May 2018 в 13:47
поделиться

6 ответов

В общем случае вы не можете, потому что

@with_connection
def spam(connection):
    # Do something

эквивалентно

def spam(connection):
    # Do something

spam = with_connection(spam)

, что означает, что «исходный» спам может больше не существовать. Хак (не очень красивый) будет выглядеть так:

def with_connection(f):
    def decorated(*args, **kwargs):
        f(get_connection(...), *args, **kwargs)
    decorated._original = f
    return decorated

@with_connection
def spam(connection):
    # Do something

spam._original(testcon) # calls the undecorated function
38
ответ дан 24 November 2019 в 18:45
поделиться

Вместо того, чтобы делать ...

def with_connection(f):
    def decorated(*args, **kwargs):
        f(get_connection(...), *args, **kwargs)
    return decorated

@with_connection
def spam(connection):
    # Do something

orig_spam = magic_hack_of_a_function(spam)

Вы могли бы просто сделать ...

def with_connection(f):
    ...

def spam_f(connection):
    ...

spam = with_connection(spam_f)

... это весь синтаксис @decorator - тогда вы, очевидно, сможете получить доступ к исходному spam_f обычным образом.

6
ответ дан 24 November 2019 в 18:45
поделиться

Решение balpha можно сделать более универсальным с помощью этого мета-декоратора:

def include_original(dec):
    def meta_decorator(f):
        decorated = dec(f)
        decorated._original = f
        return decorated
    return meta_decorator

Затем вы можете украсить свои декораторы @include_original, и у каждого будет тестируемая (недекорированная) версия, спрятанная внутри это.

@include_original
def shout(f):
    def _():
        string = f()
        return string.upper()
    return _



@shout
def function():
    return "hello world"

>>> print function()
HELLO_WORLD
>>> print function._original()
hello world
31
ответ дан 24 November 2019 в 18:45
поделиться

Обычный подход к тестированию таких функций - сделать любые зависимости, такие как get_connection, настраиваемыми. Затем вы можете переопределить его с помощью макета во время тестирования. В основном то же самое, что и внедрение зависимостей в мире Java, но намного проще благодаря динамической природе Pythons.

Код для этого может выглядеть примерно так:

# decorator definition
def with_connection(f):
    def decorated(*args, **kwargs):
        f(with_connection.connection_getter(), *args, **kwargs)
    return decorated

# normal configuration
with_connection.connection_getter = lambda: get_connection(...)

# inside testsuite setup override it
with_connection.connection_getter = lambda: "a mock connection"

В зависимости от вашего кода вы можете найти лучший объект, чем декоратор, который можно вставить заводская функция включена. Проблема с его использованием в декораторе заключается в том, что вам придется не забывать восстановить его до старого значения в методе teardown.

2
ответ дан 24 November 2019 в 18:45
поделиться

Вот, FuglyHackThatWillWorkForYourExampleButICantPromiseAnythingElse:

 orig_spam = spam.func_closure[0].cell_contents

Изменить : для функций / методов, оформленных более одного раза и с более сложными декораторами, вы можете попробовать использовать следующий код. Он основан на том факте, что декорированные функции имеют __name__d иначе, чем исходная функция.

def search_for_orig(decorated, orig_name):
    for obj in (c.cell_contents for c in decorated.__closure__):
        if hasattr(obj, "__name__") and obj.__name__ == orig_name:
            return obj
        if hasattr(obj, "__closure__") and obj.__closure__:
            found = search_for_orig(obj, orig_name)
            if found:
                return found
    return None

 >>> search_for_orig(spam, "spam")
 <function spam at 0x027ACD70>

Однако это не доказательство дурака. Он потерпит неудачу, если имя функции, возвращаемой декоратором, совпадает с именем декорированного. Порядок проверок hasattr () также является эвристическим, существуют цепочки декорирования, которые в любом случае возвращают неверные результаты.

Он потерпит неудачу, если имя функции, возвращаемой декоратором, совпадает с именем декорированного. Порядок проверок hasattr () также является эвристическим, существуют цепочки декорирования, которые в любом случае возвращают неверные результаты.

Он потерпит неудачу, если имя функции, возвращаемой декоратором, совпадает с именем декорированного. Порядок проверок hasattr () также является эвристическим, существуют цепочки декорирования, которые в любом случае возвращают неверные результаты.

18
ответ дан 24 November 2019 в 18:45
поделиться

Добавьте декоратор, не выполняющий никаких действий:

def do_nothing(f):
    return f

После определения или импорта with_connection, но до Чтобы перейти к методам, которые используют его в качестве декоратора, добавьте:

if TESTING:
    with_connection = do_nothing

Затем, если вы установите глобальное ТЕСТИРОВАНИЕ на True, вы замените with_connection на декоратор, не требующий действий.

1
ответ дан 24 November 2019 в 18:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: