2012-02-21 5 views
10

코드베이스에는 광범위하게 사용되는 데코레이터가 몇 가지 있습니다.광범위하게 재사용 된 데코레이터가있는 시스템 프로파일 링

런타임 프로필을 만들면 콜 그래프의 대부분이 시간 유리처럼 보입니다. 많은 함수는 하나의 함수 (데코레이터)를 호출하고 많은 함수를 호출합니다. 이것은 제가 바라는 것보다 덜 유용한 프로필입니다.

이 상황을 해결할 수있는 방법이 있습니까? 데코레이터를 제거하는 것은 옵션이 아닙니다. 필요한 기능을 제공합니다.

우리는 사실 이후에 cProfile 데이터에서 데코레이터를 수동으로 제거하는 것으로 생각했지만 호출자 -> 데코레이터 -> 호출 수신자를 파기하는 caller-> callee 관계로 데이터가 요약되기 때문에 가능한 것처럼 보이지 않습니다 관계.

+0

왜 유용하지 않습니다 (functools.wraps은 여전히 ​​문서화 문자열, 모듈 이름 같은 것들의 통과 등을 허용하기 위해 여기에 사용됩니다) ? 프로파일 링 정보가 데코레이터를 가리키면 데코레이터 구현을 개선하면 커다란 변화가 일어날 수 있습니다. – jcollado

+4

jcollado : 데코레이터는 런타임의 사소한 부분이지만 callees는 그렇지 않기 때문에.이러한 호출 수신자의 "진정한 호출자"는 무엇인지 숨기며 최적화 방법을 결정할 때 중요한 정보가 될 수 있습니다. – bukzor

답변

6

new 라이브러리 (또는 Python 2.6 이상에서는 types)와 같은 것을 사용하면 이론적으로 동적으로 코드 개체를 생성 한 다음 함수와 함께 변경된 기본 제공 이름을 가진 코드 개체를 기반으로 함수 개체를 만들 수 있습니다 너 포장하고 있었어.

이렇게하면 <func>.__code__.co_name (일반적으로 읽기 전용)만큼 깊은 것들을 조작 할 수 있습니다.


import functools 
import types 

def metadec(func): 

    @functools.wraps(func) 
    def wrapper(*args, **kwargs): 
     # do stuff 
     return func(*args, **kwargs) 

    c = wrapper.func_code 
    fname = "%s__%s" % (func.__name__, wrapper.__name__) 

    code = types.CodeType(
       c.co_argcount, 
       c.co_nlocals, 
       c.co_stacksize, 
       c.co_flags, 
       c.co_code,   
       c.co_consts,   
       c.co_names, 
       c.co_varnames, 
       c.co_filename, 
       fname, # change the name 
       c.co_firstlineno, 
       c.co_lnotab, 
       c.co_freevars, 
       c.co_cellvars, 
      ) 

    return types.FunctionType(
      code, # Use our updated code object 
      wrapper.func_globals, 
      fname, # Use the updated name 
      wrapper.func_defaults, 
      wrapper.func_closure, 
     ) 


In [1]: from metadec import metadec 

In [2]: @metadec 
    ...: def foobar(x): 
    ...:  print(x) 
    ...:  
    ...:  

In [3]: foobar.__name__ 
Out[3]: 'foobar__wrapper' 

In [4]: foobar(1) 
1 
+0

실무 코드는 이론을 대답으로 만듭니다. – bukzor

+0

내 업그레이드 된 대답보기. :-) – kindall

5

필자는 프로파일 링을 복잡하게하는 데코레이터 자체가 아니라 데코레이터가 만든 래퍼 함수을 추측합니다. 그리고 모든 래퍼 함수가 같은 이름을 가지기 때문에 이런 일이 발생합니다. 이 문제를 해결하려면 데코레이터가 래퍼 함수의 이름을 변경하게하십시오.

def decorator(func): 

    def wrapper(*args): 
     print "enter func", func.__name__ 
     return func(*args) 

    wrapper.__name__ += "_" + func.__name__ 
    return wrapper 

또한 functools.wraps()를 사용할 수 있지만 다음 래퍼 함수의 이름은 포장 년대 함수의 이름과 일치합니다. 프로필 작성에는 괜찮은 것 같아요.

이제 함수의 코드 객체에도 이름이 있습니다. 파이썬은 스택에 함수에 대한 참조를 저장하지 않고 코드 객체에만 저장합니다. 따라서 프로파일 러가 스택 프레임에서 래퍼 함수의 이름을 얻으면이 이름을 가져옵니다. 일반적인 방법으로 정의 된 래퍼는 각 래퍼 함수에 대한 코드 개체와 함수 개체를 명시 적으로 다시 작성하지 않으면 함수 개체가 다른 경우에도 코드 개체를 공유합니다. 이것은 상당히 더 많은 작업이며 매우 CPython에 특화되어있다. 그러나 여기 당신이 그것에 대해 갈 수있는 방법은 다음과 같습니다

from types import FunctionType, CodeType  

def decorator(func): 

    def wrapper(*args): 
     print "enter func", func.__name__ 
     return func(*args) 

    name = wrapper.__name__ + "_" + func.__name__ 

    func_code = wrapper.func_code 
    new_code = CodeType(
      func_code.co_argcount, func_code.co_nlocals, func_code.co_stacksize, 
      func_code.co_flags, func_code.co_code, func_code.co_consts, 
      func_code.co_names, func_code.co_varnames, func_code.co_filename, 
      name, func_code.co_firstlineno, func_code.co_lnotab, 
      func_code.co_freevars, func_code.co_cellvars) 
    wrapper = FunctionType(
      new_code, wrapper.func_globals, name, wrapper.func_defaults, 
      wrapper.func_closure) 

    return wrapper 

두 함수의 이름과 코드 객체의 이름은 wrapper_originalfuncname 여기에 설정되어 있으며 따라서 프로파일 러의 포장 기능을 별도로 계산해야한다. 원래 함수의 이름으로 쉽게 설정할 수 있으므로 런타임은 원래 함수 대신 롤백됩니다.

+0

데코레이터가'functools.wraps()'와 같이 만들어 졌다고 가정하면,'__name__'은 이미 설정되어있을 것입니다. – Amber

+0

프로파일 러가 스택 프레임에서 함수 이름을 얻는다면 실제로 함수의 이름이 아닌 코드 객체의 이름이 될 것이기 때문에이 대답을 썼습니다. – kindall

+0

나는 광산도 업데이트했다. (나는 실수로 kwargs로 cellvars와 freevars를 넘겨 줬다. posargs가 될 때, segfault로 만들었을 것이다.) 나는'functools.wraps()'를'__module__','__doc__' 등과 같은 방식으로 전달하기 때문에 거기에'functools.wraps()'를 유지할 것입니다. – Amber