Como posso escrever uma expressão lambda equivalente a:
def x():
raise Exception()
O seguinte não é permitido:
y = lambda : raise Exception()
y=lambda...
mais def y:
, então?
Como posso escrever uma expressão lambda equivalente a:
def x():
raise Exception()
O seguinte não é permitido:
y = lambda : raise Exception()
y=lambda...
mais def y:
, então?
Respostas:
Há mais de uma maneira de esfolar um Python:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambdas aceitam declarações. Como raise ex
é uma declaração, você pode escrever um raiser de propósito geral:
def raise_(ex):
raise ex
y = lambda: raise_(Exception('foobar'))
Mas se seu objetivo é evitar um def
, obviamente isso não é suficiente. No entanto, permite gerar exceções condicionais, por exemplo:
y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
Como alternativa, você pode gerar uma exceção sem definir uma função nomeada. Tudo o que você precisa é de um estômago forte (e 2.x para o código fornecido):
type(lambda:0)(type((lambda:0).func_code)(
1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())
E uma solução forte para o estômago python3 :
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Graças @WarrenSpencer por apontar uma resposta muito simples, se você não se importa que exceção é levantada: y = lambda: 1/0
.
lambda: 1 / 0
. Você acabará lançando um ZeroDivisionError em vez de uma exceção regular. Lembre-se de que, se a exceção for permitida, pode parecer estranho alguém depurando seu código para começar a ver um monte de ZeroDivisionErrors.
y = 1/0
é solução inteligente super-se Tipo de exceção é irrelevante
E se:
lambda x: exec('raise(Exception(x))')
SyntaxError
no Python 2.7.11.
Na verdade, existe um caminho, mas é muito artificial.
Você pode criar um objeto de código usando a compile()
função interna. Isso permite que você use a raise
instrução (ou qualquer outra instrução, nesse caso), mas levanta outro desafio: executar o objeto de código. A maneira usual seria usar a exec
instrução, mas isso o levará de volta ao problema original, a saber, que você não pode executar instruções em um lambda
(ou um eval()
, nesse caso).
A solução é um hack. lambda
Todos os callables, como o resultado de uma instrução, têm um atributo __code__
que pode ser substituído. Portanto, se você criar uma chamada e substituir seu __code__
valor pelo objeto de código acima, obtém algo que pode ser avaliado sem o uso de instruções. Conseguir tudo isso, no entanto, resulta em código muito obscuro:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
O acima faz o seguinte:
a compile()
chamada cria um objeto de código que gera a exceção;
o lambda: 0
retorna uma chamada que não faz nada além de retornar o valor 0 - isso é usado para executar o objeto de código acima posteriormente;
o lambda x, y, z
cria uma função que chama o __setattr__
método do primeiro argumento com os argumentos restantes e retorna o primeiro argumento! Isso é necessário, porque __setattr__
ele retorna None
;
a map()
chamada obtém o resultado de lambda: 0
, e usando o lambda x, y, z
substitui seu __code__
objeto pelo resultado da compile()
chamada. O resultado dessa operação de mapa é uma lista com uma entrada, a retornada por lambda x, y, z
, e é por isso que precisamos disso lambda
: se __setattr__
usássemos imediatamente, perderíamos a referência ao lambda: 0
objeto!
finalmente, o primeiro (e único) elemento da lista retornado pela map()
chamada é executado, resultando na chamada do objeto de código e, finalmente, gerando a exceção desejada.
Funciona (testado no Python 2.6), mas definitivamente não é bonito.
Uma última observação: se você tiver acesso ao types
módulo (que exigiria usar a import
instrução antes da sua eval
), poderá reduzir um pouco esse código: usando types.FunctionType()
você pode criar uma função que executará o objeto de código fornecido, para ganhar é necessário o hack de criar uma função fictícia lambda: 0
e substituir o valor de seu __code__
atributo.
Funções criadas com formulários lambda não podem conter instruções .
Se tudo o que você deseja é uma expressão lambda que gera uma exceção arbitrária, você pode fazer isso com uma expressão ilegal. Por exemplo, lambda x: [][0]
tentará acessar o primeiro elemento em uma lista vazia, o que gerará um IndexError.
ATENÇÃO : Este é um hack, não um recurso. Não use isso em nenhum código (que não seja código-golf) que outro ser humano possa ver ou usar.
TypeError: <lambda>() takes exactly 1 positional argument (2 given)
. Você tem certeza do IndexError?
lambda *x: [][0]
. (A versão original só tem um argumento, pois nenhum argumento, use lambda : [][0]
; para dois, uso lambda x,y: [][0]
; etc.)
lambda x: {}["I want to show this message. Called with: %s" % x]
Produz: KeyError: 'I want to show this message. Called with: foo'
Gostaria de dar uma explicação da ATUALIZAÇÃO 3 da resposta fornecida por Marcelo Cantos:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
lambda: 0
é uma instância da builtins.function
classe
type(lambda: 0)
é a builtins.function
classe
(lambda: 0).__code__
é um code
objeto.
Um code
objeto é um objeto que contém o código de código compilado entre outras coisas. É definido aqui no CPython https://github.com/python/cpython/blob/master/Include/code.h . Seus métodos são implementados aqui https://github.com/python/cpython/blob/master/Objects/codeobject.c . Podemos executar a ajuda no objeto de código:
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__)
é a classe de código.
Então, quando dizemos
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
estamos chamando o construtor do objeto de código com os seguintes argumentos:
Você pode ler sobre o significado dos argumentos na definição de PyCodeObject
https://github.com/python/cpython/blob/master/Include/code.h . O valor de 67 para o flags
argumento é por exemploCO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE
.
O argumento mais importante e o codestring
que contém códigos de instrução. Vamos ver o que eles significam.
>>> import dis
>>> dis.dis(b'|\0\202\1\0')
0 LOAD_FAST 0 (0)
2 RAISE_VARARGS 1
4 <0>
A documentação dos opcodes pode ser encontrada aqui
https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . O primeiro byte é o código de operação LOAD_FAST
, o segundo byte é o seu argumento, ou seja, 0.
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
Então, colocamos a referência x
na pilha. A varnames
é uma lista de cadeias contendo apenas 'x'. Empurraremos o único argumento da função que estamos definindo para a pilha.
O próximo byte é o opcode para RAISE_VARARGS
e o próximo byte é o seu argumento, ou seja, 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)
O TOS é o topo da pilha. Como colocamos o primeiro argumento ( x
) da nossa função na pilha e argc
é 1, aumentamos o x
if se for uma instância de exceção ou criamos
uma instância x
e aumentamos o contrário.
O último byte, ou seja, 0, não é usado. Não é um código de operação válido. Pode muito bem não estar lá.
Voltando ao snippet de código, estamos a qualquer momento:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Chamamos o construtor do objeto de código:
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Passamos o objeto de código e um dicionário vazio para o construtor de um objeto de função:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)
Vamos chamar ajuda em um objeto de função para ver o que os argumentos significam.
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
Em seguida, chamamos a função construída que passa uma instância de Exception como argumento. Conseqüentemente, chamamos uma função lambda que gera uma exceção. Vamos executar o trecho e ver se ele realmente funciona como pretendido.
>>> 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
Vimos que o último byte do bytecode é inútil. Não vamos desordenar essa expressão complicada com agulhas. Vamos remover esse byte. Além disso, se quisermos jogar um pouco de golfe, poderíamos omitir a instanciação de Exception e, em vez disso, passar a classe Exception como argumento. Essas alterações resultariam no seguinte código:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)
Quando executamos, obteremos o mesmo resultado de antes. É apenas mais curto.