2014-10-06 2 views
3

저는이 문제로 오래 동안 고심하고 있습니다. 필자가 작성한 코드 중 일부에서는 여러 파일을 작성하고 필요에 따라 선택적으로 디렉토리 트리를 작성해야합니다. 내 생각은 다음과 같습니다 : 예외 IOError를 catch하고 첫 번째 인수가 ENOENT 인 경우 디렉토리 구조를 만들고 파일을 다시 쓰려고 시도합니다.컨텍스트 관리자와 데코레이터를 섞은 예외가 발견되지 않았습니다.

나는 비교적 작은 재시도 함수를 작성했지만, 예외를 던질 수있는 "모든"코드로 일반화하고 싶습니다. 내가 open() 예외를 throw 할 때 실패하는 것을 보여주기 위해 최소한의 코드를 강화했습니다

def retry(f): 
    def wrapper(*args, **kwargs): 
     try: 
      return f(*args, **kwargs) 
     except: 
      print "Gotcha here!" 
    return wrapper 

def update(file, value): 
    @contextmanager 
    @retry 
    def safeopen(file, mode): 
     with open(file, mode) as f: 
      yield f 
    try: 
     with safeopen(file, 'w') as f: 
      f.write(value) 
    except: 
     print "Gotcha there!" 

update('tests/nonexisting/dummy.txt', 'Dummy line') 

: 나는 이런 식으로 뭔가에 올 때까지 그것은 모든 일했다. 이 코드에서 예외는 update()의 except 블록에서만 잡히고 wrapper()이 아니므로 항상 Gotcha here이긴하지만 나는 항상 Gotcha there!을 얻습니다. 나는 @decorator와 @contextmanager 라인을 교환하려고 시도하지 않았다. 래퍼가 호출되는지 확인하고 확인했습니다. 그냥 그것은 f()에서 예외를 잡는다.

내가 뭘 잘못하고 있니?

답변

1

일반적인 기능을 가진 @contextmanager 데코레이터를 혼합하는 것이 문제입니다. @retry 데코레이터는 일반적인 함수이지만 @contextmanager 생성기를 꾸미기 위해 사용하고 있습니다. @contextmanager 함수를 호출하면 해당 함수 본문이 실제로 실행되지 않으므로 예상대로 작동하지 않습니다. 대신 GeneratorContextManager 개체가 반환됩니다. 직접 또는 with 문을 사용하여 GeneratorContextManager__enter__ 메서드를 호출해야만 함수 본문이 실행됩니다.

from contextlib import contextmanager 


def retry(f): 
    def wrapper(*args, **kwargs): 
     try: 
      print("in wrapper") 
      return f(*args, **kwargs) 
     except: 
      print "Gotcha here!" 
     finally: 
      print "done" 
    return wrapper 

@contextmanager 
@retry 
def safeopen(file, mode): 
    print("in safe open") 
    with open(file, mode) as f: 
     yield f 

def update(file, value): 
    try: 
     print("CALLING SAFE OPEN") 
     with safeopen(file, 'w') as f: 
      f.write(value) 
    except: 
     print "Gotcha there!" 

update('tests/nonexisting/dummy.txt', 'Dummy line') 

그것은 출력 : 우리가 이제까지 safeopen의 시체를 입력하기 전에 safeopen 컨텍스트 관리자이기 때문에

당신이 볼 수 있듯이
CALLING SAFE OPEN 
in wrapper 
done 
in safe open 
Gotcha there! 

, 우리는 retry 래퍼를 종료

이 예제를 고려 . GeneratorContextManager 개체가 실제로 반환되고 본문이 실행되었지만 너무 늦었습니다. with 문의 일부로 평가 될 때까지는 아닙니다. retry이 종료되었습니다.

는이 문제를 해결하려면, 당신은 할 필요가 retry@contextmanager도하고 safeopen 컨텍스트 매니저 장식하는 데 사용

from contextlib import contextmanager 


def retry(f): 
    @contextmanager 
    def wrapper(*args, **kwargs): 
     try: 
      print("in wrapper") 
      with f(*args, **kwargs) as out: 
       yield out 
     except: 
      print "Gotcha here!" 
     finally: 
      print "done" 
    return wrapper 

@retry 
@contextmanager 
def safeopen(file, mode): 
    print("in safe open") 
    with open(file, mode) as f: 
     yield f 

def update(file, value): 
    print("CALLING SAFE OPEN") 
    with safeopen(file, 'w') as f: 
     f.write(value) 

update('tests/nonexisting/dummy.txt', 'Dummy line') 

출력 :

CALLING SAFE OPEN 
in wrapper 
in safe open 
Gotcha here! 
done 

편집 :

데코레이터의 순서를 바꾸면 retry

def retry(f): 
    def wrapper(*args, **kwargs): 
     try: 
      print("in wrapper") 
      return next(f(*args, **kwargs)) # Call next on the generator object 
     except: 
      print "Gotcha here!" 
     finally: 
      print "done" 
    return wrapper 

@contextmanager 
@retry 
def safeopen(file, mode): 
    print("in safe open") 
    with open(file, mode) as f: 
     yield f 
+0

이 자세한 설명은 매우 * 많은 * 감사 : 지금은 오히려 컨텍스트 매니저보다, 발전기 기능을 장식하고 있기 때문에 직접 safeopen을 장식, 당신은 retry 구현이 조금 더 간단 할 수 있습니다. 나는 정말로 이런 것을 의심했다.나는 장식 자의 순서가 중요하기 때문에'retry()'가 ** @ contextManager ** 장식 자에 의해 "포장"되기 전에'safeopen()'만 꾸며야한다고 생각했다. 나는 파이썬이 내가 이해하는 것과 다르게 작동한다고 생각한다. 당신의 예제는 또한 데코레이터가 함수 또는 생성기에서 작동하는지 여부를 알고 있어야한다는 것을 의미합니다. –

+1

@Nasha 그렇습니다. 데코레이터는 장식하고있는 기능의 종류를 알고 있어야합니다. 데코레이터의 순서에 관해서도,'contextmanager' 데코레이터 안에'재시도'데코레이터를 넣더라도 컨텍스트 매니저가 아닌 생성기 함수를 꾸미고 있습니다. 함수를 호출 할 때 생성자 함수 본문은 실행되지 않습니다. 실행될 바디에 대해 반환 된 생성자 객체에서'next'를 호출해야합니다. 그래서, 당신이 할 수있는 일은'재 시도'(retry) 안에'return next (f (* args, ** kwargs))'를 호출하는 것입니다. 그건 내 대답에 실제로 사용되는 방법보다 바람직 할 것이다. – dano

+0

@ Nasha 데코레이터를 반대로하여 예제 코드를 보여주기 위해 필자의 대답을 편집했습니다. – dano

관련 문제