주어진 데코레이터로 파이썬 클래스의 모든 메소드를 얻는 방법


79

@ decorator2로 장식 된 주어진 클래스 A의 모든 메서드를 얻는 방법은 무엇입니까?

class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

2
"decorator2"소스 코드를 제어 할 수 있습니까?
ascobol

8
흥미를 유지하기 위해 아니오라고합시다. 하지만 솔루션이 훨씬 더 쉬워지면이 솔루션에도 관심이 있습니다.
kraiz

16
+1 : "계속 흥미롭게":이 방법으로 더 배우기
Lauritz V. Thaulow

10
@ S.Lott : 검색을 통해 배우는 것이 적습니다 . 아래의 상위 답변을보십시오. 그것은 프로그래머 자원으로서의 가치를 높이는 SO에 대한 좋은 공헌이 아닌가? 나는 주된 이유 것을 주장 하는 이유 "흥미를 유지"하는 대답이 좋은, 그래서있다가, 싶었 @kraiz입니다. 여기로 돌아가는 두 개의 링크를 계산하지 않는 한 연결된 질문 에 대한 답변에는 아래 답변에 포함 된 정보 의 10 분1 이 포함되지 않습니다 .
Lauritz V. Thaulow

답변:


116

방법 1 : 기본 등록 데코레이터

나는 이미이 질문에 대답했습니다 : Python에서 배열 인덱스로 함수 호출 =)


방법 2 : 소스 코드 구문 분석

당신이 가정하고 싶은 것에 대한 하나의 해석 인 클래스 정의에 대한 통제권이 없다면 , 이것은 불가능합니다 (코드 읽기-반영없이), 예를 들어 데코레이터는 작동하지 않는 데코레이터가 될 수 있기 때문입니다. 내 링크 된 예제에서) 수정되지 않은 함수를 반환합니다. (그래도 데코레이터를 래핑 / 재정의 할 수 있도록 허용하는 경우 방법 3 : 데코레이터를 "자기 인식"으로 변환을 참조하십시오 . 그러면 우아한 솔루션을 찾을 수 있습니다.)

끔찍한 끔찍한 해킹이지만 inspect모듈을 사용 하여 소스 코드 자체를 읽고 파싱 할 수 있습니다. 이것은 인터랙티브 인터프리터에서는 작동하지 않을 것입니다. 왜냐하면 inspect 모듈은 인터랙티브 모드에서 소스 코드 제공을 거부 할 것이기 때문입니다. 그러나 아래는 개념 증명입니다.

#!/usr/bin/python3

import inspect

def deco(func):
    return func

def deco2():
    def wrapper(func):
        pass
    return wrapper

class Test(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decoratorName):
    sourcelines = inspect.getsourcelines(cls)[0]
    for i,line in enumerate(sourcelines):
        line = line.strip()
        if line.split('(')[0].strip() == '@'+decoratorName: # leaving a bit out
            nextLine = sourcelines[i+1]
            name = nextLine.split('def')[1].split('(')[0].strip()
            yield(name)

효과가있다!:

>>> print(list(  methodsWithDecorator(Test, 'deco')  ))
['method']

참고 하나는 예를 들어, 구문 분석 및 파이썬 구문에 지불 관심을 가지고 @deco하고 @deco(...유효한 결과가 있지만, @deco2우리는 단지를 요청할 경우 반환되지해야합니다 'deco'. http://docs.python.org/reference/compound_stmts.html 의 공식 파이썬 구문에 따르면 데코레이터는 다음과 같습니다.

decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE

우리는 같은 사건을 다룰 필요가 없다는 것에 안도의 한숨을 내쉬고 @(deco)있습니다. 하지만 @getDecorator(...)예를 들어 , 정말 정말 복잡한 데코레이터가있는 경우에는 여전히 도움이되지 않습니다.

def getDecorator():
    return deco

따라서 코드를 구문 분석하는이 최선의 전략은 이와 같은 경우를 감지 할 수 없습니다. 이 방법을 사용하는 경우 실제로 추구하는 것은 정의의 방법 위에 쓰여진 것입니다 getDecorator. 이 경우에는 .

사양에 따르면 @foo1.bar2.baz3(...)데코레이터로 사용하는 것도 유효합니다 . 이 방법을 확장하여 작업 할 수 있습니다. <function object ...>많은 노력을 기울여이 메서드를 확장 하여 함수의 이름 이 아닌 a를 반환 할 수도 있습니다 . 그러나이 방법은 끔찍하고 끔찍합니다.


방법 3 : 데코레이터를 "자기 인식"으로 변환

당신이 제어 할 수없는 경우 장식의 정의 (당신이 원하는 무엇의 또 다른 해석입니다) 당신은 장식이 적용되는 방식을 제어해야하기 때문에, 모든 이러한 문제는 멀리 이동합니다. 따라서, 당신은으로 장식을 수정할 수 있습니다 포장 하여 생성, 그것을 자신의 장식을 사용할 것을 당신의 기능을 장식합니다. 내가 다시 한번 그 말을하자 : 당신은 우리의 경우에 수를 "계몽"당신이 통제 할 수없는 장식을 장식 실내 장식을 만들 수 있습니다 그것은 전에 만하고 있던 일을 또한 APPEND .decorator호출 가능한 그것을 반환에 대한 메타 데이터 속성 , "이 기능이 장식되었는지 여부를 추적 할 수 있습니다. function.decorator를 확인합시다!".클래스의 메서드를 반복하고 데코레이터에 적절한 .decorator속성 이 있는지 확인하기 만하면 됩니다! =) 여기에 설명 된대로 :

def makeRegisteringDecorator(foreignDecorator):
    """
        Returns a copy of foreignDecorator, which is identical in every
        way(*), except also appends a .decorator property to the callable it
        spits out.
    """
    def newDecorator(func):
        # Call to newDecorator(method)
        # Exactly like old decorator, but output keeps track of what decorated it
        R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done
        R.decorator = newDecorator # keep track of decorator
        #R.original = func         # might as well keep track of everything!
        return R

    newDecorator.__name__ = foreignDecorator.__name__
    newDecorator.__doc__ = foreignDecorator.__doc__
    # (*)We can be somewhat "hygienic", but newDecorator still isn't signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it's not a big issue

    return newDecorator

에 대한 데모 @decorator:

deco = makeRegisteringDecorator(deco)

class Test2(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decorator):
    """ 
        Returns all methods in CLS with DECORATOR as the
        outermost decorator.

        DECORATOR must be a "registering decorator"; one
        can make any decorator "registering" via the
        makeRegisteringDecorator function.
    """
    for maybeDecorated in cls.__dict__.values():
        if hasattr(maybeDecorated, 'decorator'):
            if maybeDecorated.decorator == decorator:
                print(maybeDecorated)
                yield maybeDecorated

효과가있다!:

>>> print(list(   methodsWithDecorator(Test2, deco)   ))
[<function method at 0x7d62f8>]

그러나 "등록 된 데코레이터"는 가장 바깥 쪽 데코레이터 여야합니다. 그렇지 않으면 .decorator속성 주석이 손실됩니다. 예를 들어 기차에서

@decoOutermost
@deco
@decoInnermost
def func(): ...

decoOutermost"더 내부"래퍼에 대한 참조를 유지하지 않는 한 노출하는 메타 데이터 만 볼 수 있습니다 .

참고 : 위의 메소드는 적용된 데코레이터와 입력 함수 및 데코레이터 팩토리 인수.decorator전체 스택을 추적 하는를 빌드 할 수도 있습니다 . =) 예를 들어 주석 처리 된 줄을 고려하면 R.original = func이와 같은 방법을 사용하여 모든 래퍼 레이어를 추적 할 수 있습니다. 이것은 내가 데코레이터 라이브러리를 작성하면 개인적으로 할 일입니다. 왜냐하면 깊은 내성을 허용하기 때문입니다.

@foo와 사이에도 차이가 있습니다 @bar(...). 둘 다 사양에 정의 된 "데코레이터 표현" foo이지만 bar(...), 이는 데코레이터이며 동적으로 생성 된 데코레이터 를 반환 한 다음 적용됩니다. 따라서 별도의 기능이 필요할 것 makeRegisteringDecoratorFactory같은 약간이다, makeRegisteringDecorator하지만 더 META를 :

def makeRegisteringDecoratorFactory(foreignDecoratorFactory):
    def newDecoratorFactory(*args, **kw):
        oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw)
        def newGeneratedDecorator(func):
            modifiedFunc = oldGeneratedDecorator(func)
            modifiedFunc.decorator = newDecoratorFactory # keep track of decorator
            return modifiedFunc
        return newGeneratedDecorator
    newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__
    newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
    return newDecoratorFactory

에 대한 데모 @decorator(...):

def deco2():
    def simpleDeco(func):
        return func
    return simpleDeco

deco2 = makeRegisteringDecoratorFactory(deco2)

print(deco2.__name__)
# RESULT: 'deco2'

@deco2()
def f():
    pass

이 생성기 공장 래퍼도 작동합니다.

>>> print(f.decorator)
<function deco2 at 0x6a6408>

보너스 방법 # 3으로 다음을 시도해 보겠습니다.

def getDecorator(): # let's do some dispatching!
    return deco

class Test3(object):
    @getDecorator()
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

결과:

>>> print(list(   methodsWithDecorator(Test3, deco)   ))
[<function method at 0x7d62f8>]

보시다시피 method2와 달리 @deco는 클래스에 명시 적으로 작성되지 않았더라도 올바르게 인식됩니다. method2와 달리 메서드가 런타임에 추가되거나 (수동으로, 메타 클래스를 통해) 상속 된 경우에도 작동합니다.

클래스를 데코 레이트 할 수도 있습니다. 따라서 메소드와 클래스를 데코 레이팅하는 데 사용되는 데코레이터를 "계몽"한 다음 분석하려는 클래스 본문 내에 클래스를 작성하면 methodsWithDecorator데코 레이팅 된 클래스를 다음 과 같이 반환합니다. 장식 된 방법. 이를 기능이라고 생각할 수 있지만 데코레이터에 대한 인수를 검사하여이를 무시하는 논리를 쉽게 작성할 수 있습니다. 즉 .original, 원하는 의미를 얻기 위해.


1
이것은 분명하지 않은 솔루션의 문제에 대한 훌륭한 답변 이므로이 답변에 대한 현상금을 열었습니다. 미안 해요 더 줄 수있는 담당자가 부족 해요!
Niall Douglas

2
@NiallDouglas : 감사합니다. =) (중요한 수의 편집 후 답변이 자동으로 "community-wiki"로 변환되는 방법을 몰랐습니다. 그래서 대부분의
찬성 투표에

흠, 이것은 원래 데코레이터가 속성 (또는 하나의 수정 된 형태) 일 때 작동하지 않는 것 같습니다. 어떤 아이디어?
StevenMurray

이것은 정말 좋은 대답입니다! 최고 @ninjagecko
Brijesh Lakkad

15

방법 2 : 소스 코드 구문 분석에서 @ninjagecko의 탁월한 답변을 확장하려면 ast검사 모듈이 소스 코드에 액세스 할 수 있는 한 Python 2.6에 도입 된 모듈을 사용하여 자체 검사를 수행 할 수 있습니다.

def findDecorators(target):
    import ast, inspect
    res = {}
    def visit_FunctionDef(node):
        res[node.name] = [ast.dump(e) for e in node.decorator_list]

    V = ast.NodeVisitor()
    V.visit_FunctionDef = visit_FunctionDef
    V.visit(compile(inspect.getsource(target), '?', 'exec', ast.PyCF_ONLY_AST))
    return res

약간 더 복잡한 데코레이션 방법을 추가했습니다.

@x.y.decorator2
def method_d(self, t=5): pass

결과 :

> findDecorators(A)
{'method_a': [],
 'method_b': ["Name(id='decorator1', ctx=Load())"],
 'method_c': ["Name(id='decorator2', ctx=Load())"],
 'method_d': ["Attribute(value=Attribute(value=Name(id='x', ctx=Load()), attr='y', ctx=Load()), attr='decorator2', ctx=Load())"]}

1
훌륭하고 소스 파싱이 올바르게 수행되고 적절한 경고가 있습니다. =) 파이썬 문법을 개선하거나 수정하기로 결정했다면 (예를 들어, 감독처럼 보이는 장식 자 표현에 대한 표현 제한을 제거함으로써) 이것은 순방향 호환이 될 것입니다.
ninjagecko 2012

@ninjagecko 데코레이터 표현 제한에 빠진 유일한 사람이 아니 어서 기쁩니다! 메서드 내에서 데코 레이팅 된 함수 클로저를 바인딩 할 때 가장 자주 발생합니다. 바보 같은 두 단계로 턴은 ... 변수에 바인드
셰인 홀로 웨이에게


0

아마도 데코레이터가 너무 복잡하지 않은 경우 (하지만 덜 해키적인 방법이 있는지 모르겠습니다).

def decorator1(f):
    def new_f():
        print "Entering decorator1", f.__name__
        f()
    new_f.__name__ = f.__name__
    return new_f

def decorator2(f):
    def new_f():
        print "Entering decorator2", f.__name__
        f()
    new_f.__name__ = f.__name__
    return new_f


class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

print A.method_a.im_func.func_code.co_firstlineno
print A.method_b.im_func.func_code.co_firstlineno
print A.method_c.im_func.func_code.co_firstlineno

불행히도 이것은 다음 줄의 줄 번호 만 반환합니다 : def new_f():(첫 번째 줄, 줄 4), def new_f():(두 번째 줄, 줄 11) 및 def method_a(self):. 새 함수를 첫 번째 줄로 정의하여 항상 데코레이터를 작성하는 규칙이없는 한, 원하는 실제 줄을 찾는 데 어려움을 겪을 것이며, 또한 독 스트링을 작성하지 않아야합니다 ... 실제 데코레이터의 이름을 찾기 위해 한 줄씩 위로 이동할 때 들여 쓰기를 확인하는 메서드를 사용하여 독 스트링을 작성합니다.
ninjagecko

수정을해도 정의 된 함수가 데코레이터에 없으면 작동하지 않습니다. 또한 데코레이터가 호출 가능한 객체가 될 수 있으므로이 메서드는 예외를 throw 할 수도 있습니다.
ninjagecko

"... 데코레이터가 너무 복잡하지 않은 경우 ..."-두 개의 데코 레이팅 된 방법에 대해 행 번호가 같으면 아마 동일하게 장식 된 것입니다. 아마. (음, co_filename도 확인해야합니다).

0

이 문제를 해결하는 간단한 방법은 전달되는 각 함수 / 메서드를 데이터 세트 (예 : 목록)에 추가하는 코드를 데코레이터에 넣는 것입니다.

def deco(foo):
    functions.append(foo)
    return foo

이제와 모든 기능 데코 장식이 추가됩니다 기능 .


0

ninjagecko의 방법 2의 단순한 변형을 추가하고 싶지 않습니다.

동일한 코드이지만 생성기 대신 목록 이해력을 사용합니다.

def methodsWithDecorator(cls, decoratorName):

    sourcelines = inspect.getsourcelines(cls)[0]
    return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
                    for i, line in enumerate(sourcelines)
                    if line.split('(')[0].strip() == '@'+decoratorName]

0

데코레이터를 제어 할 수있는 경우 함수 대신 데코레이터 클래스를 사용할 수 있습니다.

class awesome(object):
    def __init__(self, method):
        self._method = method
    def __call__(self, obj, *args, **kwargs):
        return self._method(obj, *args, **kwargs)
    @classmethod
    def methods(cls, subject):
        def g():
            for name in dir(subject):
                method = getattr(subject, name)
                if isinstance(method, awesome):
                    yield name, method
        return {name: method for name,method in g()}

class Robot(object):
   @awesome
   def think(self):
      return 0

   @awesome
   def walk(self):
      return 0

   def irritate(self, other):
      return 0

그리고 내가 전화 awesome.methods(Robot)하면 반환

{'think': <mymodule.awesome object at 0x000000000782EAC8>, 'walk': <mymodulel.awesome object at 0x000000000782EB00>}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.