@ decorator2로 장식 된 주어진 클래스 A의 모든 메서드를 얻는 방법은 무엇입니까?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
@ decorator2로 장식 된 주어진 클래스 A의 모든 메서드를 얻는 방법은 무엇입니까?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
답변:
나는 이미이 질문에 대답했습니다 : Python에서 배열 인덱스로 함수 호출 =)
당신이 가정하고 싶은 것에 대한 하나의 해석 인 클래스 정의에 대한 통제권이 없다면 , 이것은 불가능합니다 (코드 읽기-반영없이), 예를 들어 데코레이터는 작동하지 않는 데코레이터가 될 수 있기 때문입니다. 내 링크 된 예제에서) 수정되지 않은 함수를 반환합니다. (그래도 데코레이터를 래핑 / 재정의 할 수 있도록 허용하는 경우 방법 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를 반환 할 수도 있습니다 . 그러나이 방법은 끔찍하고 끔찍합니다.
당신이 제어 할 수없는 경우 장식의 정의 (당신이 원하는 무엇의 또 다른 해석입니다) 당신은 장식이 적용되는 방식을 제어해야하기 때문에, 모든 이러한 문제는 멀리 이동합니다. 따라서, 당신은으로 장식을 수정할 수 있습니다 포장 하여 생성, 그것을 자신의 장식을 사용할 것을 당신의 기능을 장식합니다. 내가 다시 한번 그 말을하자 : 당신은 우리의 경우에 수를 "계몽"당신이 통제 할 수없는 장식을 장식 실내 장식을 만들 수 있습니다 그것은 전에 만하고 있던 일을 또한 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
, 원하는 의미를 얻기 위해.
방법 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())"]}
아마도 데코레이터가 너무 복잡하지 않은 경우 (하지만 덜 해키적인 방법이 있는지 모르겠습니다).
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의 방법 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]
데코레이터를 제어 할 수있는 경우 함수 대신 데코레이터 클래스를 사용할 수 있습니다.
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>}