İstisna oluşturan bir lambda ifadesi tanımlama


137

Eşdeğer bir lambda ifadesini nasıl yazabilirim:

def x():
    raise Exception()

Aşağıdakilere izin verilmez:

y = lambda : raise Exception()

2
Yani bunu yapamazsın. Normal işlevleri kullanın.
DrTyrsa

1
Anonim bir işleve ad vermenin anlamı nedir?
John La Rooy

2
@gnibbler Adı işleve başvurmak için kullanabilirsiniz. y () 'nin kullanımı REPL'deki (lambda: 0) ()' dan daha kolaydır.
Thomas Jung

Yani avantajı nedir y=lambda...üzerine def y:o zaman?
John La Rooy

@gnibbler Bazı bağlamlar: mutlu durumda f'yi çağıran bir hata def g (f, e) ve bir hata tespit edildiğinde e fonksiyonunu tanımlamak istedim. Senaryoya bağlı olarak, bir istisna oluşturabilir veya geçerli bir değer döndürebilir. G kullanmak için g (lambda x: x * 2, lambda e: zam e) veya alternatif olarak g (lambda x: x * 2, lambda e: 0) yazmak istedim.
Thomas Jung

Yanıtlar:


169

Bir Python'u kaplamanın birden fazla yolu vardır:

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

Lambdas ifadeleri kabul eder. Yana raise exbir ifadedir, bir genel amaçlı yükselten yazabiliriz:

def raise_(ex):
    raise ex

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

Ancak hedefiniz a'dan kaçınmaksa def, bu kesinlikle onu kesmez. Bununla birlikte, istisnaları koşullu olarak yükseltmenize izin verir, örneğin:

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

Alternatif olarak, adlandırılmış bir işlevi tanımlamadan bir istisna oluşturabilirsiniz. Tek ihtiyacınız olan güçlü bir mide (ve verilen kod için 2.x):

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

Ve bir python3 güçlü mide çözeltisi:

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

Eğer yükseltilir hangi istisna umursamazsak çok basit bir cevabı işaret için teşekkürler @WarrenSpencer: y = lambda: 1/0.


117
OMG ne karanlık sanat?
CodeColorist

11
Eğer istisna tipi atılır umurumda değil, aşağıdaki de çalışır: lambda: 1 / 0. Sonunda, düzenli bir istisna yerine bir ZeroDivisionError atılacaksınız. İstisnanın yayılmasına izin verilirse, bir grup ZeroDivisionErrors görmeye başlamak için kodunuzda hata ayıklayan birine garip gelebileceğini unutmayın.
Warren Spencer

@WarrenSpencer için harika bir çözüm. Çoğu kodda çok fazla sıfır bölme hatası yoktur, bu nedenle türü kendiniz seçebileceğiniz kadar belirgindir.
jwg

2
y = 1/0İstisna türü ilgisiz ise süper akıllı bir çözümdür
Saher Ahwal 25:03

3
Herkes 'karanlık sanat / güçlü mide' çözümlerinde neler olup bittiğini konuşabilir mi?
aralık

56

Nasıl olur:

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

12
Oldukça hacky ama sahte fonksiyonlar istediğiniz yere test yazmak için bu düzgün çalışır !!!
Kannan Ekanath

8
Çalışıyor ama yapmamalısınız.
augurar

1
Bu benim için işe yaramıyor SyntaxError, Python 2.7.11'de bir tane alıyorum.
Nick Sweeting

Ayrıca Python 2.7.5
Dinesh

1
Bu python 3 için özel ancak python 2 izin verir sanmıyorum.
Saher Ahwal

16

Aslında, bir yol var, ama çok çelişkili.

Yerleşik işlevi kullanarak bir kod nesnesi oluşturabilirsiniz compile(). Bu, raiseifadeyi (veya bu konuda başka bir ifadeyi) kullanmanıza izin verir , ancak başka bir zorluğu ortaya çıkarır: kod nesnesini yürütme. Her zamanki yol execifadeyi kullanmak olacaktır , ancak bu sizi orijinal soruna geri götürür, yani bir lambda(veya eval()bu konuda) ifadelerini çalıştıramazsınız .

Çözüm bir hack'tir. Bir lambdaifadenin sonucu gibi callables __code__, aslında değiştirilebilen bir özelliğe sahiptir. Bu nedenle, bir çağrılabilir oluşturursanız ve __code__değerini yukarıdaki kod nesnesiyle değiştirirseniz , ifadeleri kullanmadan değerlendirilebilecek bir şey elde edersiniz. Bununla birlikte, tüm bunları başarmak çok belirsiz bir kodla sonuçlanır:

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

Yukarıdakiler şunları yapar:

  • compile()Çağrı özel durum bir kod nesnesi oluşturur;

  • lambda: 0döner bir şey yapmaz ama değeri 0 döndürür bir çağrılabilir - Bu sonradan Yukarıdaki kod nesnesini çalıştırmak için kullanılır;

  • Bu , kalan argümanlarla ilk argüman yöntemini lambda x, y, zçağıran bir işlev oluşturur __setattr__ve İLK ARGUMENTİ DÖNDÜRÜR! Bu gereklidir, çünkü __setattr__kendisi geri döner None;

  • map()Çağrı sonucunu alır lambda: 0ve kullanma lambda x, y, zyerine geçer 's __code__sonucu ile nesne compile()çağrısı. Bu harita işleminin sonucu, bir girişi olan, bir girişi döndürülen bir listedir lambda x, y, z, bu yüzden buna ihtiyacımız vardır lambda: __setattr__hemen kullanırsak, lambda: 0nesneye olan referansı kaybederiz !

  • son olarak, map()çağrı tarafından döndürülen listenin ilk (ve yalnızca) öğesi yürütülür ve kod nesnesinin çağrılmasıyla sonuçlanır ve sonuçta istenen istisna yükselir.

Çalışıyor (Python 2.6'da test edildi), ancak kesinlikle hoş değil.

Son bir not: typesmodüle erişiminiz varsa ( importifadeyi kendinizden önce kullanmanız gerekir eval), bu kodu biraz kısaltabilirsiniz: kullanarak types.FunctionType(), verilen kod nesnesini yürütecek bir işlev oluşturabilirsiniz, böylece kukla bir işlev yaratma lambda: 0ve __code__özniteliğinin değerini değiştirme hackine ihtiyaç duymaz .



15

İstediğiniz tek şey, keyfi bir istisna oluşturan bir lambda ifadesi ise, bunu yasadışı bir ifadeyle yapabilirsiniz. Örneğin, lambda x: [][0]boş bir listedeki ilk öğeye erişmeye çalışır ve bu da bir IndexError hatası oluşturur.

LÜTFEN DİKKAT : Bu bir hack, bir özellik değil. Bunu , başka bir insanın görebileceği veya kullanabileceği herhangi bir (kodsuz golf) kodda kullanmayın.


Benim durumumda ben alıyorum: TypeError: <lambda>() takes exactly 1 positional argument (2 given). IndexError'dan emin misiniz?
Jovik

4
Evet. Belki de yanlış sayıda argüman sağladınız mı? Herhangi bir sayıda argüman alabilen bir lambda fonksiyonuna ihtiyacınız varsa kullanın lambda *x: [][0]. (Orijinal sürüm sadece bir argüman alır; hiçbir argüman lambda : [][0]için kullanmayın ; iki için kullanın lambda x,y: [][0]; vb.)
Kyle Strand

3
Bunu biraz genişlettim: lambda x: {}["I want to show this message. Called with: %s" % x] Üretiyor:KeyError: 'I want to show this message. Called with: foo'
ErlVolton

@ErlVolton Zeki! Bir kerelik bir senaryo dışında bunu her yerde kullanmak korkunç bir fikir gibi görünüyor ...
Kyle Strand

Kaydedicimin gerçek bir taklidini yapmak için uğraşmadığım bir proje için geçici olarak birim testlerinde kullanıyorum. Bir hata veya kritik günlüğü kaydetmeye çalışırsanız yükselir. Yani ... Evet, rızaya rağmen korkunç :)
ErlVolton

10

Marcelo Cantos tarafından verilen cevabın GÜNCELLEME 3'ü hakkında bir açıklama yapmak istiyorum :

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

açıklama

lambda: 0builtins.functionsınıfın bir örneğidir .
type(lambda: 0)olduğu builtins.functionsınıf.
(lambda: 0).__code__bir codenesnedir.
Bir codenesnenin diğer şeylerin yanı sıra, derlenmiş bayt kodu tutan bir nesnedir. Burada CPython https://github.com/python/cpython/blob/master/Include/code.h . Yöntemleri burada uygulanır https://github.com/python/cpython/blob/master/Objects/codeobject.c . Kod nesnesinde yardımı çalıştırabiliriz:

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__)kod sınıfıdır.
Yani dediğimizde

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

kod nesnesinin yapıcısını aşağıdaki bağımsız değişkenlerle çağırıyoruz:

  • argcount = 1
  • kwonlyargcount = 0
  • nlocals = 1
  • yığıt = 1
  • bayraklar = 67
  • codestring = b '| \ \ 202 \ 1 \ 0 0'
  • sabitleri = ()
  • adları = ()
  • varnames = (x ',)
  • Dosya adı = ''
  • name = ''
  • firstlineno = 1
  • lnotab = B ''

PyCodeObject Https://github.com/python/cpython/blob/master/Include/code.h tanımında argümanların ne anlama geldiğini okuyabilirsiniz . Bağımsız flagsdeğişken için 67 değeri örneğinCO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE .

En önemli argüman, codestringtalimat opcodlarını içeren argümandır . Ne anlama geldiğini görelim.

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

Opcodların belgelerini burada bulabilirsiniz https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . İlk bayt opcode LOAD_FAST, ikinci bayt argümanı yani 0.

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

Bu yüzden referansı xyığının üzerine itiyoruz. varnamesSadece 'x' içeren dizeler bir listedir. Tanımladığımız işlevin tek argümanını yığına iteceğiz.

Sonraki bayt opcode'dur RAISE_VARARGSve sonraki bayt argümanıdır yani 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)

Hizmet Şartları, yığının en tepesidir. İşlevimizin ilk argümanını ( x) yığına ittiğimizden ve argc1 olduğu için, xbunun bir istisna örneği olup olmadığını veya başka bir örneğini oluşturup xyükseltiriz.

Son bayt yani 0 kullanılmaz. Geçerli bir opcode değil. Orada olmayabilir de.

Kod pasajına geri dönersek, herhangi bir şey yapıyoruz:

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

Kod nesnesinin yapıcısını çağırdık:

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

Kod nesnesini ve boş bir sözlüğü bir işlev nesnesinin yapıcısına iletiriz:

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

Bağımsız değişkenlerin ne anlama geldiğini görmek için bir işlev nesnesine yardım diyelim.

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

Daha sonra argüman olarak bir İstisna örneğini geçiren inşa edilmiş işlevi çağırırız. Sonuç olarak bir istisna yaratan lambda fonksiyonunu aradık. Parçacığı çalıştıralım ve gerçekten de amaçlandığı gibi çalıştığını görelim.

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

İyileştirmeler

Bayt kodunun son baytının yararsız olduğunu gördük. Bu karmaşık ifadeyi iğrenç bir şekilde karıştırmayalım. Hadi şu baytı kaldıralım. Ayrıca biraz golf oynamak istiyorsak, İstisna örneğini atlayabilir ve bunun yerine İstisna sınıfını bir argüman olarak geçebiliriz. Bu değişiklikler aşağıdaki kodla sonuçlanır:

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

Çalıştırdığımızda, önceki ile aynı sonucu elde ederiz. Sadece daha kısa.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.