例外を発生させるラムダ式を定義する


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、2011年

@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

Pythonのスキンを作成する方法は複数あります。

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
OMGそれはどんなダークアートですか?
CodeColorist 2014

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 2013年

8
動作しますが、それを行うべきではありません。
オーグラ

1
これは私にとっては機能しませんSyntaxError。Python2.7.11を使用しています。
Nick Sweeting

Python 2.7.5で上記のエラー(SyntaxError)も発生しています
Dinesh

1
これはpython 3に固有のものですが、python 2がそれを許可するとは思いません。
Saher Ahwal、2018年

16

実は方法はありますが、かなり工夫されています。

compile()組み込み関数を使用してコードオブジェクトを作成できます。これにより、raiseステートメント(またはそのほかのステートメント)を使用できますが、コードオブジェクトの実行という別の課題が生じます。通常の方法はexecステートメントを使用することですが、それによって元の問題に戻ります。つまり、ステートメントをa 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()の呼び出しを。このマップ操作の結果は、によって返される1つのエントリを持つリストlambda x, y, zです。これが必要な理由です。すぐにlambda使用する__setattr__と、lambda: 0オブジェクトへの参照が失われます。

  • 最後に、map()呼び出しによって返されたリストの最初の(そして唯一の)要素が実行され、コードオブジェクトが呼び出され、最終的に必要な例外が発生します。

動作しますが(Python 2.6でテスト済み)、間違いなくきれいではありません。

最後に、typesモジュールへのアクセス権がある場合(のimport前のステートメントを使用する必要がありますeval)、このコードを少し短くするtypes.FunctionType()ことができます。これを使用すると、特定のコードオブジェクトを実行する関数を作成できるため、ダミー関数を作成してlambda: 0その__code__属性の値を置き換えるハックは必要ありません。


15

ラムダフォームで作成された関数にステートメントを含めることはできません


2
これは事実ですが、実際には質問に答えるものではありません。ラムダ式から例外を発生させるのは簡単です(そして、それをキャッチすることは、非常に工夫されたトリックを使用することで達成できる場合があります。stackoverflow.com / a / 50916686 / 2560053またはstackoverflow.com/a/50961836/2560053を参照してください)。
Thomas Baruchel

15

必要なのが任意の例外を発生させるラムダ式である場合は、不正な式でこれを実現できます。たとえばlambda x: [][0]、空のリストの最初の要素にアクセスしようとすると、IndexErrorが発生します。

注意:これはハッキングであり、機能ではありません。しない別の人間が参照するか、使用する可能性のある任意の(非コードゴルフ)のコードでこれを使用しています。


私の場合、次のようになりますTypeError: <lambda>() takes exactly 1 positional argument (2 given)。IndexErrorを確認しますか?
Jovik 2013年

4
うん。おそらく間違った数の引数を提供しましたか?引数をいくつでも取ることができるラムダ関数が必要な場合は、を使用してくださいlambda *x: [][0]。(元のバージョンは1つの引数のみを受け取ります。引数がない場合は、を使用しlambda : [][0]、2つの場合は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賢い!1回限りのスクリプト以外でこれを使用することはひどい考えのように思えますが...
カイルストランド

ロガーの実際のモックを作成する必要がないプロジェクトの単体テストで一時的に使用しています。エラーまたはクリティカルをログに記録しようとすると発生します。だから...はい、ひどいですが、同意があります:)
ErlVolton

10

Marcelo Cantosが提供した回答のUPDATE 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)あるbuiltins.functionクラスでは。
(lambda: 0).__code__ある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
  • flags = 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です。

最も重要な引数は、codestring命令オペコードを含むものです。それらの意味を見てみましょう。

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

オペコードのドキュメントは、https: //docs.python.org/3.8/library/dis.html#python-bytecode-instructionsにあり ます。最初のバイトはのオペコードでLOAD_FAST、2番目のバイトはその引数、つまり0です。

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

参照をxスタックにプッシュします。これvarnamesは、「x」のみを含む文字列のリストです。定義している関数の唯一の引数をスタックにプッシュします。

次のバイトはのオペコードで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関数の最初の引数()をスタックにプッシュし、1であるargcため、 xそれが例外インスタンスである場合は発生させ、またはのインスタンスを作成しxてそれ以外の場合は発生させます。

最後のバイト、つまり0は使用されません。有効なオペコードではありません。そこにないかもしれません。

コードスニペットに戻ります。

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 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.