programing

자기 주장이 있는 클래스 메서드 데코레이터?

minecode 2022. 12. 20. 21:34
반응형

자기 주장이 있는 클래스 메서드 데코레이터?

클래스 메서드에서 클래스 필드를 어떻게 인수로 장식자에게 전달합니까?제가 하고 싶은 일은 다음과 같습니다.

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization("some_attr", self.url)
    def get(self):
        do_work()

합격하기 위해 자아가 존재하지 않는다고 불평한다.self.url데코레이터에게.이 문제를 해결할 방법이 있나요?

예. 클래스 정의 시 인스턴스 속성을 전달하는 대신 런타임에 확인합니다.

def check_authorization(f):
    def wrapper(*args):
        print args[0].url
        return f(*args)
    return wrapper

class Client(object):
    def __init__(self, url):
        self.url = url

    @check_authorization
    def get(self):
        print 'get'

>>> Client('http://www.google.com').get()
http://www.google.com
get

데코레이터는 메서드 인수를 대행 수신합니다.첫 번째 인수는 인스턴스이므로 여기서 속성을 읽습니다.속성 이름을 문자열로 데코레이터에 전달하고 다음을 사용할 수 있습니다.getattrAtribute name을 하드코드하지 않는 경우:

def check_authorization(attribute):
    def _check_authorization(f):
        def wrapper(self, *args):
            print getattr(self, attribute)
            return f(self, *args)
        return wrapper
    return _check_authorization

보다 간결한 예는 다음과 같습니다.

#/usr/bin/env python3
from functools import wraps

def wrapper(method):
    @wraps(method)
    def _impl(self, *method_args, **method_kwargs):
        method_output = method(self, *method_args, **method_kwargs)
        return method_output + "!"
    return _impl

class Foo:
    @wrapper
    def bar(self, word):
        return word

f = Foo()
result = f.bar("kitty")
print(result)

인쇄 대상:

kitty!
from re import search
from functools import wraps

def is_match(_lambda, pattern):
    def wrapper(f):
        @wraps(f)
        def wrapped(self, *f_args, **f_kwargs):
            if callable(_lambda) and search(pattern, (_lambda(self) or '')): 
                f(self, *f_args, **f_kwargs)
        return wrapped
    return wrapper

class MyTest(object):

    def __init__(self):
        self.name = 'foo'
        self.surname = 'bar'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'foo')
    def my_rule(self):
        print 'my_rule : ok'

    @is_match(lambda x: x.name, 'foo')
    @is_match(lambda x: x.surname, 'bar')
    def my_rule2(self):
        print 'my_rule2 : ok'



test = MyTest()
test.my_rule()
test.my_rule2()

출력: my_rule2: OK

또 다른 선택사항은 통사당(통사당)을 버리고 그 안에 장식하는 것이다.__init__클래스입니다.

def countdown(number):
    def countdown_decorator(func):
        def func_wrapper():
            for index in reversed(range(1, number+1)):
                print(index)
            func()
        return func_wrapper
    return countdown_decorator

class MySuperClass():
    def __init__(self, number):
        self.number = number
        self.do_thing = countdown(number)(self.do_thing)
    
    def do_thing(self):
        print('im doing stuff!')


myclass = MySuperClass(3)

myclass.do_thing()

인쇄하는 방법

3
2
1
im doing stuff!

이 문제가 상당히 오래된 문제인 것은 알지만, 아래의 회피책은 이전에 제안되지 않았습니다.여기서 문제가 되는 것은 당신이 접속할 수 없다는 것이다.self클래스 블록에서는 사용할 수 있지만 클래스 메서드에서는 사용할 수 있습니다.

기능을 몇 번 반복할 수 있는 더미 데코레이터를 만들어 봅시다.

import functools
def repeat(num_rep):
    def decorator_repeat(func):
        @functools.wraps(func)
        def wrapper_repeat(*args, **kwargs):
            for _ in range(num_rep):
                value = func(*args, **kwargs)
            return 
        return wrapper_repeat
    return decorator_repeat
class A:
    def __init__(self, times, name):
        self.times = times
        self.name = name
    
    def get_name(self):
        @repeat(num_rep=self.times)
        def _get_name():
            print(f'Hi {self.name}')
        _get_name()

오래된 질문인 것은 알지만, 이 해결책은 아직 언급되지 않았습니다. 8년이 지난 오늘날에도 도움이 될 수 있기를 바랍니다.

그럼, 포장지를 싸는 건 어때요?예를 들어 init에서는 데코레이터를 변경할 수 없고, 그 메서드를 꾸밀 수도 없다고 가정합니다(@property decored 등).자신을 캡처한 후 원래 데코레이터를 호출하여 런타임 속성을 전달하는 커스텀 클래스 고유의 데코레이터를 생성할 수 있습니다.

다음으로 동작 예를 나타냅니다(f스트링에는 python 3.6이 필요합니다).

import functools

# imagine this is at some different place and cannot be changed
def check_authorization(some_attr, url):
        def decorator(func):
                @functools.wraps(func)
                def wrapper(*args, **kwargs):
                        print(f"checking authorization for '{url}'...")
                        return func(*args, **kwargs)
                return wrapper
        return decorator

# another dummy function to make the example work
def do_work():
        print("work is done...")

###################
# wrapped wrapper #
###################
def custom_check_authorization(some_attr):
        def decorator(func):
                # assuming this will be used only on this particular class
                @functools.wraps(func)
                def wrapper(self, *args, **kwargs):
                        # get url
                        url = self.url
                        # decorate function with original decorator, pass url
                        return check_authorization(some_attr, url)(func)(self, *args, **kwargs)
                return wrapper
        return decorator
        
#############################
# original example, updated #
#############################
class Client(object):
        def __init__(self, url):
                self.url = url
    
        @custom_check_authorization("some_attr")
        def get(self):
                do_work()

# create object
client = Client(r"https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments")

# call decorated function
client.get()

출력:

checking authorisation for 'https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments'...
work is done...

그럴수는 없어요.없습니다.self인스턴스가 존재하지 않기 때문에 클래스 본문에 포함되어 있습니다.넘겨야 할 것 같은데, 예를 들면...str인스턴스에서 조회할 속성 이름이 포함되어 있으며 반환된 함수가 이를 수행하거나 완전히 다른 메서드를 사용할 수 있습니다.

기능용 데코레이터를 메서드 데코레이터로 바꿀 수 있는 범용 유틸리티가 있으면 매우 편리합니다.한 시간 동안 고민하다가 생각해낸 게 있어요

from typing import Callable
Decorator = Callable[[Callable], Callable]

def decorate_method(dec_for_function: Decorator) -> Decorator:

    def dec_for_method(unbounded_method) -> Callable:
        # here, `unbounded_method` will be a unbounded function, whose
        # invokation must have its first arg as a valid `self`. When it 
        # return, it also must return an unbounded method.
        def decorated_unbounded_method(self, *args, **kwargs):
            @dec_for_function
            def bounded_method(*args, **kwargs):
                return unbounded_method(self, *args, **kwargs)
            return bounded_method(*args, **kwargs)

        return decorated_unbounded_method

    return dec_for_method

사용법은 다음과 같습니다.

# for any decorator (with or without arguments)
@some_decorator_with_arguments(1, 2, 3)
def xyz(...): ...

# use it on a method:
class ABC:
  @decorate_method(some_decorator_with_arguments(1, 2, 3))
  def xyz(self, ...): ...

테스트:

def dec_for_add(fn):
    """This decorator expects a function: (x,y) -> int.

    If you use it on a method (self, x, y) -> int, it will fail at runtime.
    """
    print(f"decorating: {fn}")
    def add_fn(x,y):
        print(f"Adding {x} + {y} by using {fn}")
        return fn(x,y)
    return add_fn


@dec_for_add
def add(x,y):
    return x+y

add(1,2)  # OK!


class A:
    @dec_for_add
    def f(self, x, y):
        # ensure `self` is still a valid instance
        assert isinstance(self, A)
        return x+y

# TypeError: add_fn() takes 2 positional arguments but 3 were given
# A().f(1,2)
    

class A:
    @decorate_method(dec_for_add)
    def f(self, x, y):
        # ensure `self` is still a valid instance
        assert isinstance(self, A)
        return x+y

# Now works!!
A().f(1,2)

언급URL : https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments

반응형