Получить имя метода функции или класса из декоратора в Python - PullRequest
0 голосов
/ 19 июня 2020

У меня есть следующий декоратор.

def allow_disable_in_tests(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        version = ??? # name of the func of method’s class name
        need_to_switch_off_in_tests = cache.get('switch_off_in_tests', version=version)

        if settings.IM_IN_TEST_MODE and need_to_switch_off_in_tests:
            return None

        value = func(*args, **kwargs)
        return value
    return wrapper

Есть 2 типа объектов, которые этот декоратор может принимать как func:

  1. Автономная функция.

  2. Метод класса (связанный метод, stati c метод и метод класса - все возможно)

Вопрос в том, как получить из внутреннего декоратора имя функции:

  1. в случае func - это отдельная функция.

  2. имя класса метода в случае func - метод класса

Это будет использоваться в version.

Декоратор должен уметь работать с обоими типами объектов.

Спасибо

1 Ответ

2 голосов
/ 19 июня 2020

Вы можете использовать __qualname__ и __module__ для получения этой информации. __qualname__ будет описывать, где класс определен в модуле в соответствии с классом или функцией, в которой он был определен.

Однако вы помещаете тестовый лог c в производственный код, что немного похоже на код запаха. Вам было бы лучше использовать функции исправления обезьяны вашей среды тестирования, чтобы исправить эти функции при запуске вашего набора тестов. Например, с pytest:

import pytest
from functools import wraps
from inspect import signature

class FuncPatch:
    def __init__(self, parent, name, retval=None):
        self.parent = parent
        self.name = name
        self.retval = retval

def get_things_to_patch():
    import mymodule
    return (
        FuncPatch(mymodule, 'my_func'),
        FuncPatch(mymodule.MyClass, 'method'),
        FuncPatch(mymodule.MyClass, 'static'),
        FuncPatch(mymodule.MyClass, 'class_', retval='special'),
    )

def create_test_function(func, retval, decorator=None):
    func = getattr(func, '__func__', func) # unwrap if classmethod or normal method
    sig = signature(func)
    @wraps(func)
    def f(*args, **kwargs):
        # check func was called with correct params raises TypeError if wrong
        sig.bind(*args, **kwargs)
        return retval
    if decorator:
        f = decorator(f)
    return f

@pytest.fixture
def patch_all_the_things(monkeypatch):
    for patch in get_things_to_patch():
        decorator = None
        if (isinstance(patch.parent, type)
                and not callable(patch.parent.__dict__[patch.name])
        ):
            # quick hack to detect staticmethod or classmethod
            decorator = type(patch.parent.__dict__[patch.name])

        to_patch = getattr(patch.parent, patch.name)
        func = create_test_function(to_patch, patch.retval, decorator)
        monkeypatch.setattr(patch.parent, patch.name, func)

# things to test
def my_func():
    return 'my_func'

class MyClass:
    @staticmethod
    def static():
        return 'static'
    @classmethod
    def class_(cls):
        return 'class'
    def method(self):
        return 'method'

# actual tests
def test_my_func(patch_all_the_things):
    assert my_func() is None

def test_my_class(patch_all_the_things):
    assert MyClass().method() is None
    assert MyClass.method(MyClass()) is None
    assert MyClass.static() is None
    assert MyClass.class_() == 'special'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...