Então você não pode fazer isso. Use funções normais.
DrTyrsa
1
Qual é o sentido de atribuir um nome a uma função anônima?
John La Rooy
2
@gnibbler Você pode usar o nome para se referir à função. y () é mais fácil de usar do que (lambda: 0) () no REPL.
Thomas Jung
Então, qual é a vantagem de y=lambda...mais def y:, então?
John La Rooy
@gnibbler Algum contexto: eu queria definir uma função def g (f, e) que chame f no caso feliz ee se um erro for detectado. Dependendo do cenário, é possível gerar uma exceção ou retornar algum valor válido. Para usar g, eu queria escrever g (lambda x: x * 2, lambda e: raise e) ou, alternativamente, g (lambda x: x * 2, lambda e: 0).
Thomas Jung
Respostas:
169
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 <10else 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):
Se você não se importa que tipo de exceção é lançada, o seguinte também funciona: 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.
Warren Spencer
Ótima solução @WarrenSpencer. A maioria dos códigos não possui muitos erros de divisão zero, por isso é tão distinto como se você pudesse escolher o tipo.
jwg 29/09/17
2
y = 1/0é solução inteligente super-se Tipo de exceção é irrelevante
Saher Ahwal
3
Alguém pode nos falar sobre o que realmente está acontecendo nas soluções de 'arte escura / estômago forte'?
É bastante hacky, mas para escrever testes nos quais você deseja zombar de funções, isso funciona perfeitamente !!!
Kannan Ekanath
8
Funciona, mas você não deve fazê-lo.
Augurar 17/04
1
Isso não funciona para mim, eu recebo um SyntaxErrorno Python 2.7.11.
22816 Nick Sweeting
Também estou recebendo o erro acima (SyntaxError) no Python 2.7.5
Dinesh
1
isso é específico do python 3, no entanto, não acho que o python 2 permita isso.
Saher Ahwal
16
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 raiseinstrução (ou qualquer outra instrução, nesse caso), mas levanta outro desafio: executar o objeto de código. A maneira usual seria usar a execinstruçã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. lambdaTodos 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: 0retorna 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, zcria 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, zsubstitui 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: 0objeto!
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 typesmódulo (que exigiria usar a importinstruçã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: 0e substituir o valor de seu __code__atributo.
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.
No meu caso eu recebo: TypeError: <lambda>() takes exactly 1 positional argument (2 given). Você tem certeza do IndexError?
Jovik
4
Sim. Você talvez forneceu o número errado de argumentos? Se você precisar de uma função lambda que possa receber qualquer número de argumentos, use 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.)
Kyle Strand
3
Eu expandi isso um pouco: lambda x: {}["I want to show this message. Called with: %s" % x] Produz: KeyError: 'I want to show this message. Called with: foo'
ErlVolton 14/10
@ErlVolton Clever! Embora usando isso em qualquer lugar exceto em um one-off roteiro parece ser uma idéia terrível ...
Kyle Strand
Estou temporariamente usando testes de unidade para um projeto em que não me incomodei em fazer uma zombaria real do meu criador de logs. Isso aumenta se você tentar registrar um erro ou crítica. Então ... Sim terrível, embora :) consensual
ErlVolton
10
Gostaria de dar uma explicação da ATUALIZAÇÃO 3 da resposta fornecida por Marcelo Cantos:
LOAD_FAST(var_num)Pushes a reference to the local co_varnames[var_num] onto the stack.
Então, colocamos a referência xna 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_VARARGSe 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 xif se for uma instância de exceção ou criamos
uma instância xe 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:
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,inException
Melhorias
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:
y=lambda...
maisdef y:
, então?Respostas:
Há mais de uma maneira de esfolar um Python:
Lambdas aceitam declarações. Como
raise ex
é uma declaração, você pode escrever um raiser de propósito geral:Mas se seu objetivo é evitar um
def
, obviamente isso não é suficiente. No entanto, permite gerar exceções condicionais, por exemplo: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):
E uma solução forte para o estômago python3 :
Graças @WarrenSpencer por apontar uma resposta muito simples, se você não se importa que exceção é levantada:
y = lambda: 1/0
.fonte
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 é irrelevanteE se:
fonte
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 araise
instrução (ou qualquer outra instrução, nesse caso), mas levanta outro desafio: executar o objeto de código. A maneira usual seria usar aexec
instrução, mas isso o levará de volta ao problema original, a saber, que você não pode executar instruções em umlambda
(ou umeval()
, 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 retornaNone
;a
map()
chamada obtém o resultado delambda: 0
, e usando olambda x, y, z
substitui seu__code__
objeto pelo resultado dacompile()
chamada. O resultado dessa operação de mapa é uma lista com uma entrada, a retornada porlambda x, y, z
, e é por isso que precisamos dissolambda
: se__setattr__
usássemos imediatamente, perderíamos a referência aolambda: 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 aimport
instrução antes da suaeval
), poderá reduzir um pouco esse código: usandotypes.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ícialambda: 0
e substituir o valor de seu__code__
atributo.fonte
Funções criadas com formulários lambda não podem conter instruções .
fonte
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.
fonte
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, uselambda : [][0]
; para dois, usolambda 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:
Explicação
lambda: 0
é uma instância dabuiltins.function
classetype(lambda: 0)
é abuiltins.function
classe(lambda: 0).__code__
é umcode
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:type((lambda: 0).__code__)
é a classe de código.Então, quando dizemos
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 oflags
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.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.Então, colocamos a referência
x
na pilha. Avarnames
é 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.O TOS é o topo da pilha. Como colocamos o primeiro argumento (
x
) da nossa função na pilha eargc
é 1, aumentamos ox
if se for uma instância de exceção ou criamos uma instânciax
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:
Chamamos o construtor do objeto de código:
Passamos o objeto de código e um dicionário vazio para o construtor de um objeto de função:
Vamos chamar ajuda em um objeto de função para ver o que os argumentos significam.
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.
Melhorias
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:
Quando executamos, obteremos o mesmo resultado de antes. É apenas mais curto.
fonte