개발/Python

[파이썬] 데코레이터를 사용하여 변수를 범위에 삽입하는 방법은 무엇입니까?

MinorMan 2021. 4. 21. 08:11
반응형

<질문>

[면책 조항 : 내가하고 싶은 일을하는 더 비단뱀적인 방법이있을 수 있지만, 여기서 파이썬의 범위 지정이 어떻게 작동하는지 알고 싶습니다.]

다른 함수의 범위에 이름을 삽입하는 것과 같은 작업을 수행하는 데코레이터를 만드는 방법을 찾으려고합니다 (예 : 이름이 데코레이터의 범위를 벗어나지 않도록합니다). 예를 들어, 이름이 지정된 변수를 인쇄하라는 함수가있는 경우var정의되지 않은 경우 호출되는 데코레이터 내에서 정의하고 싶습니다. 다음은 중단되는 예입니다.

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            var = value
            res = f(*args, **kwargs)
            return res
        return inner_dec
    return msg_decorator

@decorator_factory(c)
def msg_printer():
    print var

msg_printer()

인쇄하고 싶습니다 "Message"하지만 다음을 제공합니다.

NameError: global name 'var' is not defined

역 추적은 심지어 어디를 가리켜var정의됩니다.

 in inner_dec(*args, **kwargs)
      8         def inner_dec(*args, **kwargs):
      9             var = value
---> 10             res = f(*args, **kwargs)
     11             return res
     12         return inner_dec

그래서 왜 찾을 수 없는지 이해가 안 돼var.

이런 식으로 할 방법이 있습니까?


<답변1>

당신은 할 수 없습니다. 범위가 지정된 이름 (클로저)은 컴파일 시간에 결정되며 런타임에 더 이상 추가 할 수 없습니다.

달성하고자하는 최선의 방법은글로벌이름, 함수 사용개인적인글로벌 네임 스페이스 :

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            g = f.__globals__  # use f.func_globals for py < 2.6
            sentinel = object()

            oldvalue = g.get('var', sentinel)
            g['var'] = value

            try:
                res = f(*args, **kwargs)
            finally:
                if oldvalue is sentinel:
                    del g['var']
                else:
                    g['var'] = oldvalue

            return res
        return inner_dec
    return msg_decorator

f.__globals__래핑 된 함수의 전역 네임 스페이스이므로 데코레이터가 다른 모듈에있는 경우에도 작동합니다. 만약var이미 글로벌로 정의 된 경우 새 값으로 대체되고 함수 호출 후 글로벌이 복원됩니다.

이것은 할당되지 않고 주변 범위에서 찾을 수없는 함수의 모든 이름이 대신 전역으로 표시되기 때문에 작동합니다.

데모:

>>> c = 'Message'
>>> @decorator_factory(c)
... def msg_printer():
...     print var
... 
>>> msg_printer()
Message
>>> 'var' in globals()
False

하지만 장식하는 대신에var글로벌 범위에서직접.

전역을 변경하는 것은 스레드로부터 안전하지 않으며 동일한 모듈의 다른 함수에 대한 일시적인 호출도 동일한 전역으로 표시됩니다.


<답변2>

전역 변수를 사용하지 않고 원하는 것을 할 수있는 깔끔한 방법이 있습니다. 상태 비 저장 및 스레드 안전을 원하면 선택의 여지가 없습니다.

"kwargs"변수를 사용하십시오.

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
    def inner_dec(*args, **kwargs):
        kwargs["var"] = value
        res = f(*args, **kwargs)
        return res
    return inner_dec
return msg_decorator

@decorator_factory(c)
def msg_printer(*args, **kwargs):
    print kwargs["var"]

msg_printer()

<답변3>

당신은 할 수 없습니다. 파이썬은어휘 범위 지정. 즉, 식별자의 의미는 소스 코드를 볼 때 물리적으로 둘러싼 범위에 따라서 만 결정됩니다.


<답변4>

여기에 주입하는 방법이 있습니다배수@Martijn Pieters가하는 것과 다소 유사한 방식으로 함수의 범위에 변수를 추가합니다.his answer. 더 일반적인 솔루션이기 때문에 주로 게시하고 있습니다.아니이를 수행하기 위해 여러 번 적용해야합니다. 그의 (및 다른 많은) 답변에서 요구하는 것처럼.

from functools import wraps

def inject_variables(context):
    """ Decorator factory. """
    def variable_injector(func):
        @wraps(func)
        def decorator(*args, **kwargs):
            try:
                func_globals = func.__globals__  # Python 2.6+
            except AttributeError:
                func_globals = func.func_globals  # Earlier versions.

            saved_values = func_globals.copy()  # Shallow copy of dict.
            func_globals.update(context)

            try:
                result = func(*args, **kwargs)
            finally:
                func_globals = saved_values  # Undo changes.

            return result

        return decorator

    return variable_injector

if __name__ == '__main__':
    namespace = {'a': 5, 'b': 3}

    @inject_variables(namespace)
    def test():
        print('a:', a)
        print('b:', b)

    test()

<답변5>

Python은 어휘 범위가 지정되어 있으므로 잠재적으로 불쾌한 부작용없이 원하는 작업을 수행 할 수있는 깨끗한 방법이 없습니다. 데코레이터를 통해 var를 함수에 전달하는 것이 좋습니다.

c = 'Message'

def decorator_factory(value):
    def msg_decorator(f):
        def inner_dec(*args, **kwargs):
            res = f(value, *args, **kwargs)
            return res
        inner_dec.__name__ = f.__name__
        inner_dec.__doc__ = f.__doc__
        return inner_dec
    return msg_decorator

@decorator_factory(c)
def msg_printer(var):
    print var

msg_printer()  # prints 'Message'

<답변6>

최신 정보__globals__나를 위해 작동합니다.

def f():
    print(a)


def with_context(**kw):
    def deco(fn):
        g = fn.__globals__
        g.update(kw)
        return fn

    return deco


with_context(a=3)(f)() # 3

<답변7>

흥미로운 게시물이 즉석에서 함수를 만들어 다른 솔루션을 제공한다는 사실을 발견했습니다. 원래:

def wrapper(func):
    cust_globals = func.__globals__.copy()

    # Update cust_globals to your liking

    # Return a new function
    return types.FunctionType(
        func.__code__, cust_globals, func.__name__, func.__defaults__, func.__closure__
    )

보다https://hardenedapple.github.io/stories/computers/python_function_override/


<답변8>

다음은 데코레이터를 사용하여 함수 범위에 변수를 추가하는 간단한 데모입니다.

>>> def add_name(name):
...     def inner(func):
...         # Same as defining name within wrapped
...         # function.
...         func.func_globals['name'] = name
...
...         # Simply returns wrapped function reference.
...         return func
... 
...     return inner
...
>>> @add_name("Bobby")
... def say_hello():
...     print "Hello %s!" % name
...
>>> print say_hello()
Hello Bobby!
>>>

<답변9>

파이썬 함수가 객체라고 가정하면 다음을 수행 할 수 있습니다.

#!/usr/bin/python3


class DecorClass(object):
    def __init__(self, arg1, arg2):
        self.a1 = arg1
        self.a2 = arg2

    def __call__(self, function):
        def wrapped(*args):
            print('inside class decorator >>')
            print('class members: {0}, {1}'.format(self.a1, self.a2))
            print('wrapped function: {}'.format(args))
            function(*args, self.a1, self.a2)
        return wrapped


    @DecorClass(1, 2)
    def my_function(f1, f2, *args):
        print('inside decorated function >>')
        print('decorated function arguments: {0}, {1}'.format(f1, f2))
        print('decorator class args: {}'.format(args))


    if __name__ == '__main__':
        my_function(3, 4)

결과는 다음과 같습니다.

inside class decorator >>
class members: 1, 2
wrapped function: (3, 4)
inside decorated function >>
decorated function arguments: 3, 4
decorator class args: (1, 2)

여기에 더 많은 설명http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html


<답변10>

def merge(d1, d2):
    d = d1.copy()
    d.update(d2)
    return d

# A decorator to inject variables
def valueDecorator(*_args, **_kargs):
    def wrapper(f):
        def wrapper2(*args, **kargs):
            return f(*args, **kargs)
        wrapper2.__name__ = f.__name__
        wrapper2.__doc__ = f.__doc__
        oldVars = getattr(f, 'Vars', [])
        oldNamedVars = getattr(f, 'NamedVars', {})
        wrapper2.Vars = oldVars + list(_args)
        wrapper2.NamedVars = merge(oldNamedVars, _kargs)
        return wrapper2
    return wrapper

@valueDecorator(12, 13, a=2)
@valueDecorator(10, 11, a=1)
def func():
    print(func.Vars)
    print(func.NamedVars)

전역 범위를 수정하는 대신 주석이 달린 함수 자체를 변경하는 것이 더 합리적입니다.


<답변11>

전역을 사용하는 솔루션에 문제가 있습니다.

동시 요청이 여러 개있는 경우 전역 컨텍스트를 덮어 쓸 수 있습니다. 불가능하다고 생각했지만 얼마 후 요청이 빠르지 않으면 컨텍스트 (전역)의 변화를 포착했습니다. 더 나은 해결책은 kwargs를 사용하여 변수를 전달하는 것입니다.

def is_login(old_fuction):
    def new_function(request, *args, **kwargs):
        secret_token = request.COOKIES.get('secret_token')
        if secret_token:
            items = SomeModel.objects.get(cookie = secret_token)
            if len(items) > 0:
                item = items[0]
                kwargs['current_user'] = item
                return old_fuction(request, *args, **kwargs)
            else:
                return HttpResponse('error')
        return HttpResponse(status=404)
    return new_function

@is_login  
def some_func(request, current_user):
    return HttpResponse(current_user.name)

데코 레이팅 된 각 함수에 추가 매개 변수를 추가해야합니다.

반응형