예외를 발생시키는 람다 식 정의


137

다음과 같은 람다 식을 작성하는 방법

def x():
    raise Exception()

다음은 허용되지 않습니다 :

y = lambda : raise Exception()

2
그래서 당신은 그렇게 할 수 없습니다. 정상적인 기능을 사용하십시오.
DrTyrsa

1
익명 함수에 이름을 부여하는 요점은 무엇입니까?
John La Rooy

2
@gnibbler 이름을 사용하여 기능을 참조 할 수 있습니다. REPL에서 y ()가 (lambda : 0) ()보다 사용하기 쉽습니다.
Thomas Jung

그렇다면 그 y=lambda...이상의 장점은 무엇 def y:입니까?
John La Rooy

@ gnibbler 일부 컨텍스트 : 행복한 경우 f를 호출하고 오류가 감지되면 e를 호출하는 함수 def g (f, e)를 정의하고 싶었습니다. 시나리오에 따라 e는 예외를 발생 시키거나 유효한 값을 리턴 할 수 있습니다. g를 사용하기 위해 g (lambda x : x * 2, lambda e : raise e) 또는 대안 적으로 g (lambda x : x * 2, lambda e : 0)를 작성하고 싶었습니다.
Thomas Jung

답변:


169

파이썬을 스키닝하는 방법은 여러 가지가 있습니다 :

y = lambda: (_ for _ in ()).throw(Exception('foobar'))

람다는 진술을 받아들입니다. raise ex성명서 이므로 범용 모금자를 작성할 수 있습니다.

def raise_(ex):
    raise ex

y = lambda: raise_(Exception('foobar'))

그러나 귀하의 목표가를 피하는 것이라면 def분명히 자르지 않습니다. 그러나 다음과 같이 조건부로 예외를 발생시킬 수 있습니다.

y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))

또는 명명 된 함수를 정의하지 않고 예외를 발생시킬 수 있습니다. 당신이 필요로하는 것은 강한 위 (및 주어진 코드의 2.x)입니다.

type(lambda:0)(type((lambda:0).func_code)(
  1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())

그리고 python3 강한 위 솔루션 :

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

어떤 예외가 발생했는지 신경 쓰지 않으면 매우 간단한 답변을 지적 해 주신 @WarrenSpencer에게 감사드립니다 : y = lambda: 1/0.


117
세상에 무슨 어두운 예술이야?
CodeColorist

11
어떤 유형의 예외가 발생하는지 상관하지 않으면 다음도 작동합니다 lambda: 1 / 0. 일반적인 예외 대신 ZeroDivisionError가 발생합니다. 예외가 전파되도록 허용되면 코드를 디버깅하는 누군가가 ZeroDivisionErrors를보기 시작하는 것이 이상하게 보일 수 있습니다.
워렌 스펜서

훌륭한 솔루션 @ WarrenSpencer. 대부분의 코드에는 제로 나누기 오류가 많지 않으므로 직접 유형을 선택할 수있는 것처럼 독특합니다.
jwg

2
y = 1/0예외 유형과 관련이없는 경우 매우 현명한 솔루션입니다
Saher Ahwal

3
누구나 '어둠의 예술 / 강한 위장'솔루션에서 실제로 진행되고있는 일을 통해 우리에게 이야기 할 수 있습니까?
decvalts

56

어때요?

lambda x: exec('raise(Exception(x))')

12
그것은 매우 해킹이지만 함수를 모방하려는 테스트를 작성하는 것은 깔끔합니다!
Kannan Ekanath

8
작동하지만 그렇게해서는 안됩니다.
augurar

1
이것은 나를 위해 작동하지 않습니다, 나는 SyntaxError파이썬 2.7.11을 얻습니다.
Nick Sweeting

또한 Python 2.7.5에서 위의 오류 (SyntaxError)가 발생합니다.
Dinesh

1
이것은 파이썬 3에만 해당되지만 파이썬 2가 허용한다고 생각하지 않습니다.
Saher Ahwal

16

실제로, 방법이 있지만, 그것은 매우 고안되었습니다.

compile()내장 함수를 사용하여 코드 객체를 만들 수 있습니다 . 이를 통해 raise명령문 (또는 그 문제에 대한 다른 명령문)을 사용할 수 있지만 코드 오브젝트 실행이라는 또 다른 문제가 발생합니다. 일반적인 방법은 exec문 을 사용하는 것이지만 원래 문제로 돌아갑니다. 즉, lambda(또는 eval()그 문제에 대해서는 문을 실행할 수 없음 )

해결책은 해킹입니다. lambda명령문 의 결과와 같은 호출 가능 항목에는 모두 __code__대체 할 수 있는 속성 이 있습니다. 따라서 호출 가능을 작성하고 __code__값을 위에서 코드 오브젝트로 바꾸면 명령문을 사용하지 않고 평가할 수있는 것을 얻을 수 있습니다. 그러나이 모든 것을 달성하면 코드가 매우 모호해집니다.

map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()

위의 내용은 다음과 같습니다.

  • compile()호는 예외를 발생하는 부호 객체를 생성;

  • lambda: 0반환 아무것도하지 않는다하지만 0의 값을 돌려 호출 -이 나중에 위의 코드 개체를 실행하는 데 사용됩니다;

  • 는 나머지 인수와 함께 첫 번째 인수 lambda x, y, z__setattr__메소드 를 호출하고 첫 번째 인수를 반환 하는 함수를 작성합니다 ! 때문에, 필요 __setattr__자체가 반환 None;

  • map()호출의 결과를 소요 lambda: 0하고 사용 lambda x, y, z을 대체 그것의 __code__의 결과에 객체 compile()호출. 이 맵 연산의 결과는에 의해 리턴 된 하나의 항목이있는 목록 lambda x, y, z입니다. 이것이 우리가 이것을 필요로하는 이유입니다. 우리가 즉시 lambda사용 __setattr__하면 lambda: 0객체에 대한 참조를 잃게됩니다 !

  • 마지막으로, map()호출에 의해 리턴 된 목록의 첫 번째 (및 유일한) 요소 가 실행되어 코드 오브젝트가 호출되어 궁극적으로 원하는 예외가 발생합니다.

작동하지만 (파이썬 2.6에서 테스트 됨) 확실히 그렇지는 않습니다.

마지막 참고 사항 : types모듈에 액세스 할 수있는 경우 ( import앞에 명령문 을 사용해야하는 경우 eval)이 코드를 약간 줄일 types.FunctionType()수 있습니다 .이를 사용 하면 주어진 코드 객체를 실행하는 함수를 만들 수 있으므로 더미 함수를 만들고 속성 lambda: 0값을 바꾸는 해킹이 필요하지 않습니다 __code__.



15

원하는 모든 것이 임의의 예외를 발생시키는 람다 식이면 잘못된 식으로이를 수행 할 수 있습니다. 예를 들어 lambda x: [][0]빈 목록의 첫 번째 요소에 액세스하려고하면 IndexError가 발생합니다.

참고 : 이것은 기능이 아닌 해킹입니다. 다른 사람이 보거나 사용할 수있는 (골프가 아닌) 코드에는이 코드를 사용 하지 마십시오 .


제 경우에는 다음을 얻습니다 TypeError: <lambda>() takes exactly 1 positional argument (2 given). IndexError를 확인 하시겠습니까?
Jovik

4
네. 잘못된 수의 인수를 제공 했습니까? 여러 개의 인수를 사용할 수있는 람다 함수가 필요한 경우을 사용하십시오 lambda *x: [][0]. (원래 버전은 하나의 인수 만 취합니다 lambda : [][0]. 인수가 없으면 ;을 사용하십시오 ; 두 개를 사용하십시오 lambda x,y: [][0]; 등)
Kyle Strand

3
나는 이것을 조금 확장했다 : lambda x: {}["I want to show this message. Called with: %s" % x] 생산 : KeyError: 'I want to show this message. Called with: foo'
ErlVolton

@ErlVolton Clever! 일회성 스크립트를 제외하고는 어디에서나 이것을 사용하는 것은 끔찍한 생각처럼 보입니다 ...
Kyle Strand

나는 로거를 실제로 모방하려고하지 않은 프로젝트에 대해 단위 테스트에서 일시적으로 사용하고 있습니다. 오류 또는 치명적을 기록하려고하면 발생합니다. 그래서 ... 네 끔찍한, 합의 :) 비록
ErlVolton

10

Marcelo Cantos가 제공 한 답변 의 업데이트 3 에 대해 설명하고 싶습니다 .

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

설명

lambda: 0builtins.function클래스 의 인스턴스입니다 .
type(lambda: 0)는 IS builtins.function클래스.
(lambda: 0).__code__A는 code객체. 목적은 무엇보다도 컴파일 된 바이트 코드를 보유하고 개체입니다. CPython https://github.com/python/cpython/blob/master/Include/code.h에 정의되어 있습니다 . 메소드는 https://github.com/python/cpython/blob/master/Objects/codeobject.c에서 구현됩니다 . 코드 객체에 대한 도움말을 실행할 수 있습니다.
code

Help on code object:

class code(object)
 |  code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring,
 |        constants, names, varnames, filename, name, firstlineno,
 |        lnotab[, freevars[, cellvars]])
 |  
 |  Create a code object.  Not for the faint of heart.

type((lambda: 0).__code__)코드 클래스입니다.
우리가 말할 때

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')

다음과 같은 인수를 사용하여 코드 객체의 생성자를 호출합니다.

  • argcount = 1
  • kwonlyargcount = 0
  • nlocals = 1
  • 스택 크기 = 1
  • 플래그 = 67
  • codestring = b '| \ 0 \ 202 \ 1 \ 0'
  • 상수 = ()
  • 이름 = ()
  • varnames = ( 'x',)
  • 파일 이름 = ''
  • 이름 = ''
  • firstlineno = 1
  • lnotab = b ''

PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h 의 정의에서 인수의 의미에 대해 읽을 수 있습니다 . flags인수 의 값은 67입니다 CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.

가장 importand 인수는 codestring명령어 opcode를 포함합니다. 그들이 무엇을 의미하는지 봅시다.

>>> import dis
>>> dis.dis(b'|\0\202\1\0')
          0 LOAD_FAST                0 (0)
          2 RAISE_VARARGS            1
          4 <0>

opcode 문서는 https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions 에서 찾을 수 있습니다 . 첫 번째 바이트는의 opcode LOAD_FAST이고 두 번째 바이트는 인수입니다 (예 : 0).

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.

따라서 참조를 x스택으로 푸시합니다 . 은 varnames오직 'x'를 포함하는 문자열의리스트이다. 우리는 정의하는 함수의 유일한 인수를 스택에 푸시합니다.

다음 바이트는 opcode RAISE_VARARGS이고 다음 바이트는 인수입니다. 1

RAISE_VARARGS(argc)
    Raises an exception using one of the 3 forms of the raise statement, depending on the value of argc:
        0: raise (re-raise previous exception)
        1: raise TOS (raise exception instance or type at TOS)
        2: raise TOS1 from TOS (raise exception instance or type at TOS1 with __cause__ set to TOS)

TOS는 스택의 최상위입니다. x함수 의 첫 번째 인수 ( )를 스택에 푸시하고 argc1 x이므로 예외 인스턴스 인 경우를 발생 시키거나 인스턴스를 작성 x하고 그렇지 않으면 발생시킵니다.

마지막 바이트, 즉 0은 사용되지 않습니다. 유효한 opcode가 아닙니다. 거기에 없을 수도 있습니다.

우리는 코드 스 니펫으로 돌아가서

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

코드 객체의 생성자를 호출했습니다.

type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')

코드 객체와 빈 사전을 함수 객체의 생성자에 전달합니다.

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)

함수 객체에 대해 help를 호출하여 인수가 무엇을 의미하는지 봅시다.

Help on class function in module builtins:

class function(object)
 |  function(code, globals, name=None, argdefs=None, closure=None)
 |  
 |  Create a function object.
 |  
 |  code
 |    a code object
 |  globals
 |    the globals dictionary
 |  name
 |    a string that overrides the name from the code object
 |  argdefs
 |    a tuple that specifies the default argument values
 |  closure
 |    a tuple that supplies the bindings for free variables

그런 다음 Exception 인스턴스를 인수로 전달하는 생성 된 함수를 호출합니다. 결과적으로 우리는 예외를 발생시키는 람다 함수를 호출했습니다. 스 니펫을 실행하고 실제로 의도 한대로 작동하는지 봅시다.

>>> type(lambda: 0)(type((lambda: 0).__code__)(
...     1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
... )(Exception())
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "", line 1, in 
Exception

개량

우리는 바이트 코드의 마지막 바이트가 쓸모가 없다는 것을 알았습니다. 이 복잡한 표현을 복잡하게 어지럽히 지 마십시오. 그 바이트를 제거하자. 또한 약간의 골프를 원한다면 Exception 인스턴스화를 생략하고 대신 Exception 클래스를 인수로 전달할 수 있습니다. 이러한 변경으로 인해 다음 코드가 생성됩니다.

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)

우리가 그것을 실행할 때 우리는 이전과 같은 결과를 얻을 것입니다. 더 짧아요

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.