2012-08-17 3 views
1

하나의 함수가 다른 함수를 호출하면 프로그래밍 방식으로 어떻게 결정할 수 있습니까? 어느 기능도 수정할 수 없습니다.파이썬에서 함수가 다른 함수를 호출하는지 결정

는 여기에 내가 (source_calls_target를) 원하는 무엇 :

>>> def check(): 
>>>  pass 
>>> def test_check(): 
>>>  check() 
>>> def source_calls_target(source, target): 
>>>  # if source() calls target() somewhere, return True 
>>>  ??? 
>>> source_calls_target(test_check, check) 
True 
>>> source_calls_target(check, test_check) 
False 

를 이상적으로, 나는 실제로 target()를 호출하고 싶지 않아요.

이상적으로 source의 정의 내에 target()에 대한 호출이 표시되는지 확인하고 싶습니다. 조건문에 따라 실제로 호출 할 수도 있고 호출하지 않을 수도 있습니다.

+4

어떻게 단위 테스트이 관련이 당신에게 도움이 될 것입니다? 단위 테스트는 사양을 준수하여 동작을 결정하는 것입니다. – Marcin

+0

@Marcin 이렇게하면 test_check에 check()을 호출해야한다는 사양을 준수하는지 확인합니다. – devtk

+6

@devtk, 이것은 단위 코드 테스트가 아닌 코드 커버리지 분석에 가장 적합합니다. 유닛 테스트는'test_check()'가'check()'를 호출하는지 상관하지 않습니다. 'test_check()'의 반환 값과 부작용 만 다루고 있습니다. –

답변

4

당신이에 대한 액세스 권한을 가진 보장 할 수 있다면 ...

import sys 
from functools import partial 

def trace_calls(call_list, frame, event, arg): 
    if event != 'call': 
     return 
    call_list.append(frame.f_code) 

def test_call(caller, called): 
    traces = [] 
    old = sys.gettrace() 
    sys.settrace(partial(trace_calls, traces)) 
    try: 
     caller() 
    finally: 
     sys.settrace(old) 

    try: 
     idx = traces.index(called.func_code) 
    except ValueError: 
     return False 

    if idx and traces[idx-1] == caller.func_code: 
     return True 

    return False 

그리고 테스트 : 광산은 단순한 기능이지만, 대상 기능의 직접 실행 호출자의 소스 기능이 있다면 그것은 확인 통화 이름으로 항상 것을

import ast 
call_names = [c.func.id for c in ast.walk(ast.parse(inspect.getsource(source))) 
       if isinstance(c, ast.Call)] 
return 'target' in call_names 

주, 그래서 전화가 특정 (SECURITY) 기능을 비활성화하는 것입니다 여부를 알 어려운 (잠재적으로 불가능)입니다 : 소스 코드, 당신은 ast.parse을 사용할 수 있습니다 이온 또는 다른 동일한 이름의. 소스 코드가없는

는 유일한 방법은 분해 경유 :

import dis 
def ops(code): 
    i, n = 0, len(code) 
    while i < n: 
     op = ord(code[i]) 
     i += 1 
     if op == dis.EXTENDED_ARG: 
      ext = ord(code[i]) + ord(code[i+1])*256 
      op = ord(code[i + 2]) 
      i += 3 
     else: 
      ext = 0 
     if op >= dis.HAVE_ARGUMENT: 
      arg = ord(code[i]) + ord(code[i+1])*256 + ext*65536 
      i += 2 
      yield op, arg 
     else: 
      yield op, None 

source_ops = list(ops(source.func_code.co_code)) 

문제는 함수가 가 다른 기능을 호출하거나 참조를로드 여부를 알려 실제로는 불가능이다 그것; 다른 함수가 map 또는 reduce 등으로 전달되면 호출되지만 다른 함수로 전달 될 수 있습니다. 실질적으로 분별 것은 함수가 source.func_code.co_names에있는 경우 다음 호출 할 수 있다고 가정하는 것입니다

'target' in source.func_code.co_names 
+0

고마워! 이것은 내가 염두에 두었던 대답이며, 구현 방법을 잘 모르고있었습니다. 그것을 줄 것이다. – devtk

+0

함수가 들여 쓰기되면 폭탄이 나옵니다. 예를 들어 클래스 내에있는 경우입니다. :/ – devtk

+0

@devtk ['textwrap.dedent'] (http://docs.python.org/library/textwrap.html#textwrap.dedent)를 사용하여 해당 문제를 해결할 수 있습니다. – ecatmur

1

아마 매우 추한 방법으로 만 작동이 :

import profile 

def source_calls_target(source, target): 
    pro = profile.Profile() 
    pro.run(source.__name__ + '()') 
    pro.create_stats() 
    return target.__name__ in [ele[2] for ele in pro.stats] 
+0

같은 이름의 함수가 여러 개 있으면 어떻게됩니까? – Luke

+0

그것은 실패합니다 -하지만 제가 말했듯이 이것은 추악한 해결책입니다. –

3

다음은 간단한 예를 사용하여 sys.settrace이다(). 소스 함수가 ​​호출되도록 요구합니다. 드문 경우지만 두 개의 다른 함수가 동일한 코드 객체를 공유 할 수 있기 때문에 작동하지 않을 수도 있습니다.

import sys 

def check(): 
    pass 

def test_check(): 
    check() 

def source_calls_target(source, target): 
    orig_trace = sys.gettrace() 
    try: 
     tracer = Tracer(target) 
     sys.settrace(tracer.trace) 
     source() 
     return tracer.called 
    finally: 
     sys.settrace(orig_trace) 

class Tracer: 
    def __init__(self, target): 
     self.target = target 
     self.called = False 

    def trace(self, frame, event, arg): 
     if frame.f_code == self.target.func_code: 
      self.called = True 

print source_calls_target(test_check, check) 
print source_calls_target(check, test_check) 
+0

이것은 대상이 어떤 시점에서 호출되었지만 소스별로 직접 호출되지 않은 경우에만 알려줍니까? – jdi

+0

수정하십시오. target이 source에 의해 _directly_인지 여부를 알고 싶으면 해당 frame.f_back.f_code == self.source.func_code를 확인할 수 있습니다 (소스를 Tracer 인스턴스에 전달해야 함). – Luke

+0

매우 독창적입니다. 감사합니다 :) 누군가가 실제로 목표를 호출하지 않고 그것을 할 수 있는지를 알 수 있습니다. – devtk

1

나는 @ Luke의 좋은 대답과 매우 흡사하게 보일 무언가를 찾고 있었다.

def a(): 
    b() 

def b(): 
    pass 

def c(): 
    a() 


test_call(c,a) #True 
test_call(a,b) #True 
test_call(c,b) #False 
관련 문제