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


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.