Qualquer pessoa que mexa no Python por tempo suficiente foi mordida (ou rasgada em pedaços) pelo seguinte problema:
def foo(a=[]):
a.append(5)
return a
Novatos Python seria de esperar esta função para retornar sempre uma lista com apenas um elemento: [5]
. O resultado é muito diferente e muito surpreendente (para um iniciante):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Um gerente meu teve seu primeiro encontro com esse recurso e o chamou de "uma falha dramática no design" da linguagem. Respondi que o comportamento tinha uma explicação subjacente, e é realmente muito intrigante e inesperado se você não entende os aspectos internos. No entanto, não fui capaz de responder (a mim mesmo) à seguinte pergunta: qual é o motivo para vincular o argumento padrão na definição da função e não na execução da função? Duvido que o comportamento experiente tenha um uso prático (quem realmente usou variáveis estáticas em C, sem erros de criação?)
Editar :
Baczek fez um exemplo interessante. Juntamente com a maioria dos seus comentários e os de Utaal em particular, eu elaborei ainda mais:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Para mim, parece que a decisão de design foi relativa a onde colocar o escopo dos parâmetros: dentro da função ou "junto" com ela?
Fazer a ligação dentro da função significaria que x
está efetivamente ligado ao padrão especificado quando a função é chamada, não definida, algo que apresentaria uma falha profunda: a def
linha seria "híbrida" no sentido de que parte da ligação (de o objeto da função) aconteceria na definição e parte (atribuição de parâmetros padrão) no tempo de chamada da função.
O comportamento real é mais consistente: tudo nessa linha é avaliado quando essa linha é executada, ou seja, na definição da função.
fonte
[5]
" Eu sou um novato em Python e não esperaria isso, porque obviamentefoo([1])
retornarei[1, 5]
, não[5]
. O que você quis dizer é que um novato espera que a função chamada sem parâmetro sempre retorne[5]
.Respostas:
Na verdade, isso não é uma falha de design e não é por causa de componentes internos ou desempenho.
Ele vem simplesmente do fato de que as funções no Python são objetos de primeira classe, e não apenas um pedaço de código.
Assim que você pensa dessa maneira, faz completamente sentido: uma função é um objeto que está sendo avaliado em sua definição; os parâmetros padrão são como "dados do membro" e, portanto, seu estado pode mudar de uma chamada para outra - exatamente como em qualquer outro objeto.
De qualquer forma, o Effbot tem uma explicação muito boa dos motivos desse comportamento nos Valores padrão dos parâmetros no Python .
Achei isso muito claro e sugiro realmente lê-lo para um melhor conhecimento de como os objetos funcionais funcionam.
fonte
functions are objects
. No seu paradigma, a proposta seria implementar os valores padrão das funções como propriedades e não como atributos.Suponha que você tenha o seguinte código
Quando vejo a declaração de comer, a coisa menos surpreendente é pensar que, se o primeiro parâmetro não for fornecido, será igual à tupla
("apples", "bananas", "loganberries")
No entanto, suposto mais tarde no código, eu faço algo como
se os parâmetros padrão fossem vinculados à execução da função, e não à declaração da função, eu ficaria surpreso (de uma maneira muito ruim) ao descobrir que os frutos haviam sido alterados. Isso seria mais surpreendente do que descobrir que sua
foo
função acima estava alterando a lista.O verdadeiro problema está nas variáveis mutáveis, e todos os idiomas têm esse problema até certo ponto. Aqui está uma pergunta: suponha que em Java eu tenha o seguinte código:
Agora, meu mapa usa o valor da
StringBuffer
chave quando foi colocada no mapa ou armazena a chave por referência? De qualquer maneira, alguém está surpreso; ou a pessoa que tentou tirar o objeto doMap
uso de um valor idêntico àquele em que o colocou, ou a pessoa que parece não conseguir recuperar o objeto, mesmo que a chave usada seja literalmente o mesmo objeto que foi usado para colocá-lo no mapa (é por isso que o Python não permite que seus tipos de dados internos mutáveis sejam usados como chaves de dicionário).Seu exemplo é um bom exemplo de caso em que os novatos em Python serão surpreendidos e mordidos. Mas eu argumentaria que, se "consertássemos" isso, isso criaria apenas uma situação diferente em que eles seriam mordidos, e essa seria ainda menos intuitiva. Além disso, esse é sempre o caso quando se lida com variáveis mutáveis; você sempre encontra casos em que alguém pode esperar intuitivamente um ou o comportamento oposto, dependendo do código que está escrevendo.
Pessoalmente, gosto da abordagem atual do Python: os argumentos da função padrão são avaliados quando a função é definida e esse objeto é sempre o padrão. Suponho que eles poderiam usar um caso especial usando uma lista vazia, mas esse tipo de caixa especial causaria ainda mais espanto, sem mencionar que é incompatível com versões anteriores.
fonte
("blueberries", "mangos")
.some_random_function()
acrescenta afruits
vez de atribuir a ele, o comportamento deeat()
vai mudar. Tanto para o design maravilhoso atual. Se você usar um argumento padrão que é referenciado em outro lugar e depois modificar a referência de fora da função, estará solicitando problemas. O verdadeiro WTF é quando as pessoas definem um novo argumento padrão (uma lista literal ou uma chamada para um construtor) e ainda recebem pouco.global
e reatribuir explicitamente a tupla - não há absolutamente nada de surpreendente seeat
funcionar de maneira diferente depois disso.A parte relevante da documentação :
fonte
function.data = []
) ou, melhor ainda, criar um objeto.Não sei nada sobre o funcionamento interno do intérprete Python (e também não sou especialista em compiladores e intérpretes); portanto, não me culpe se propuser algo insensível ou impossível.
Desde que os objetos python sejam mutáveis , acho que isso deve ser levado em consideração ao projetar os argumentos padrão. Quando você instancia uma lista:
você espera obter uma nova lista referenciada por
a
.Por que o
a=[]
ininstanciar uma nova lista na definição de função e não na invocação? É como se você estivesse perguntando "se o usuário não fornecer o argumento, instancie uma nova lista e use-a como se tivesse sido produzida pelo chamador". Eu acho que isso é ambíguo:
usuário, você deseja
a
usar como padrão a data e hora correspondentes ao definir ou executarx
? Nesse caso, como no anterior, manterei o mesmo comportamento como se o argumento padrão "assignment" fosse a primeira instrução da função (datetime.now()
chamada na chamada de função). Por outro lado, se o usuário quisesse o mapeamento de tempo de definição, ele poderia escrever:Eu sei, eu sei: isso é um fechamento. Como alternativa, o Python pode fornecer uma palavra-chave para forçar a ligação no tempo de definição:
fonte
class {}
bloco seja interpretado como pertencente às instâncias :) Mas quando as classes são objetos de primeira classe, obviamente o natural é que seu conteúdo (na memória) reflita seu conteúdo (em código).Bem, a razão é simplesmente que as ligações são feitas quando o código é executado, e a definição da função é executada, bem ... quando as funções são definidas.
Compare isto:
Esse código sofre exatamente a mesma ocorrência inesperada. bananas é um atributo de classe e, portanto, quando você adiciona itens, é adicionado a todas as instâncias dessa classe. O motivo é exatamente o mesmo.
É apenas "Como funciona", e fazê-lo funcionar de maneira diferente no caso da função provavelmente seria complicado e, no caso da classe, provavelmente impossível, ou pelo menos diminua bastante a instanciação do objeto, pois você teria que manter o código da classe por perto e execute-o quando objetos forem criados.
Sim, é inesperado. Mas quando o centavo cai, ele se encaixa perfeitamente na maneira como o Python funciona em geral. Na verdade, é uma boa ajuda para o ensino e, depois de entender por que isso acontece, você entenderá o python muito melhor.
Dito isto, ele deve aparecer com destaque em qualquer bom tutorial sobre Python. Porque, como você mencionou, todo mundo corre para esse problema mais cedo ou mais tarde.
fonte
property
s - que na verdade são funções no nível de classe que agem como atributos normais, mas salvam o atributo na instância em vez da classe (usandoself.attribute = value
como Lennart disse).Por que você não introspectiva?
Estou realmente surpreso que ninguém tenha realizado a introspecção perspicaz oferecida pelo Python (
2
e3
aplique) em chamadas.Dada uma pequena função simples
func
definida como:Quando o Python o encontra, a primeira coisa que ele faz é compilá-lo para criar um
code
objeto para esta função. Enquanto essa etapa de compilação é concluída, o Python avalia * e, em seguida, armazena os argumentos padrão (uma lista vazia[]
aqui) no próprio objeto de função . Como resposta principal mencionada: a listaa
agora pode ser considerada um membro da funçãofunc
.Então, vamos fazer uma introspecção, um antes e um depois para examinar como a lista é expandida dentro do objeto de função. Estou usando
Python 3.x
para isso, para o Python 2 o mesmo se aplica (use__defaults__
oufunc_defaults
no Python 2; sim, dois nomes para a mesma coisa).Função Antes da Execução:
Depois que o Python executar essa definição, ele pegará todos os parâmetros padrão especificados (
a = []
aqui) e os apertará no__defaults__
atributo do objeto de função (seção relevante: Chamadas):Ok, uma lista vazia como entrada única
__defaults__
, exatamente como esperado.Função após a execução:
Vamos agora executar esta função:
Agora, vamos vê-los
__defaults__
novamente:Atônito? O valor dentro do objeto muda! As chamadas consecutivas à função agora serão simplesmente anexadas ao
list
objeto incorporado :Então, aí está, a razão pela qual essa 'falha' acontece é porque os argumentos padrão fazem parte do objeto de função. Não há nada de estranho acontecendo aqui, é tudo um pouco surpreendente.
A solução comum para combater isso é usar
None
como padrão e, em seguida, inicializar no corpo da função:Como o corpo da função é executado novamente a cada vez, você sempre obtém uma nova lista vazia se nenhum argumento foi passado
a
.Para verificar se a lista
__defaults__
é a mesma usada na função,func
basta alterar sua função para retornarid
a listaa
usada dentro do corpo da função. Em seguida, compare-o com a lista em__defaults__
(posição[0]
em__defaults__
) e você verá como eles realmente se referem à mesma instância da lista:Tudo com o poder da introspecção!
* Para verificar se o Python avalia os argumentos padrão durante a compilação da função, tente executar o seguinte:
como você notará,
input()
é chamado antes do processo de criação da função e vinculá-la ao nomebar
.fonte
id(...)
necessário para essa última verificação ou ois
operador responderia à mesma pergunta?is
funcionaria perfeitamente, useiid(val)
porque acho que pode ser mais intuitivo.None
como padrão limita severamente a utilidade da__defaults__
introspecção, então não acho que funcione bem como uma defesa de ter o__defaults__
trabalho da maneira que funciona. A avaliação preguiçosa faria mais para manter os padrões de função úteis dos dois lados.Eu costumava pensar que criar os objetos em tempo de execução seria a melhor abordagem. Estou menos certo agora, já que você perde alguns recursos úteis, embora possa valer a pena, independentemente simplesmente para evitar confusão de novatos. As desvantagens de fazer isso são:
1. Desempenho
Se a avaliação do tempo de chamada for usada, a função cara será chamada toda vez que sua função for usada sem argumento. Você pagaria um preço caro em cada chamada ou precisaria armazenar em cache manualmente o valor externamente, poluindo seu espaço para nome e adicionando verbosidade.
2. Forçando parâmetros vinculados
Um truque útil é vincular parâmetros de um lambda à ligação atual de uma variável quando o lambda é criado. Por exemplo:
Isso retorna uma lista de funções que retornam 0,1,2,3 ... respectivamente. Se o comportamento for alterado, eles serão vinculados
i
ao valor de tempo de chamada de i, para que você obtenha uma lista de funções que todas retornaram9
.A única maneira de implementar isso de outra forma seria criar um fechamento adicional com o i bound, ou seja:
3. Introspecção
Considere o código:
Podemos obter informações sobre os argumentos e padrões usando o comando
inspect
módulo, queEsta informação é muito útil para coisas como geração de documentos, metaprogramação, decoradores etc.
Agora, suponha que o comportamento dos padrões possa ser alterado para que isso seja equivalente a:
No entanto, perdemos a capacidade de introspecção e ver quais são os argumentos padrão . Como os objetos não foram construídos, nunca podemos alcançá-los sem realmente chamar a função. O melhor que podemos fazer é armazenar o código-fonte e retorná-lo como uma string.
fonte
5 pontos em defesa do Python
Simplicidade : o comportamento é simples no seguinte sentido: A maioria das pessoas cai nessa armadilha apenas uma vez, e não várias.
Consistência : Python sempre passa objetos, não nomes. O parâmetro padrão é, obviamente, parte do cabeçalho da função (não o corpo da função). Portanto, ele deve ser avaliado no tempo de carregamento do módulo (e somente no tempo de carregamento do módulo, a menos que esteja aninhado), não no tempo de chamada da função.
Utilidade : Como Frederik Lundh aponta em sua explicação sobre "Valores padrão de parâmetros em Python" , o comportamento atual pode ser bastante útil para programação avançada. (Use com moderação.)
Documentação suficiente : Na documentação mais básica do Python, o tutorial, o problema é anunciado em voz alta como um "Aviso importante" na primeira subseção da Seção "Mais sobre a definição de funções" . O aviso ainda usa negrito, que raramente é aplicado fora dos títulos. RTFM: Leia o manual fino.
Meta-aprendizagem : cair na armadilha é realmente um momento muito útil (pelo menos se você é um aprendiz reflexivo), porque posteriormente você entenderá melhor o ponto "Consistência" acima e isso lhe ensinará muito sobre Python.
fonte
Esse comportamento é fácil de explicar por:
Assim:
a
não muda - toda chamada de atribuição cria um novo objeto int - novo objeto é impressob
não muda - a nova matriz é criada a partir do valor padrão e impressac
alterações - a operação é realizada no mesmo objeto - e é impressafonte
__iadd__
, mas não funciona com int. Claro. :-)O que você está perguntando é por que isso:
não é internamente equivalente a isso:
exceto no caso de chamar explicitamente func (None, None), que ignoraremos.
Em outras palavras, em vez de avaliar os parâmetros padrão, por que não armazenar cada um deles e avaliá-los quando a função é chamada?
Provavelmente, uma resposta está aí - transformaria efetivamente todas as funções com parâmetros padrão em um fechamento. Mesmo que tudo esteja escondido no intérprete e não em um fechamento completo, os dados precisam ser armazenados em algum lugar. Seria mais lento e consumiria mais memória.
fonte
1) O chamado problema do "Argumento padrão mutável" é, em geral, um exemplo especial que demonstra que:
"Todas as funções com esse problema também sofrem de um problema de efeito colateral semelhante no parâmetro atual ",
que é contra as regras da programação funcional, geralmente indesejável e deve ser consertado juntos.
Exemplo:
Solução : a cópia de
uma solução absolutamente seguro é
copy
oudeepcopy
o objeto de entrada em primeiro lugar e, em seguida, fazer o que quer com a cópia.Muitos tipos mutáveis incorporados têm um método de cópia como
some_dict.copy()
ousome_set.copy()
ou podem ser copiados facilmente comosomelist[:]
oulist(some_list)
. Cada objeto também pode ser copiado porcopy.copy(any_object)
ou mais minuciosamentecopy.deepcopy()
(o último é útil se o objeto mutável for composto de objetos mutáveis). Alguns objetos são baseados fundamentalmente em efeitos colaterais como o objeto "arquivo" e não podem ser significativamente reproduzidos por cópia. copiandoProblema de exemplo para uma pergunta SO semelhante
Ele não deve ser salvo em nenhum atributo público de uma instância retornada por esta função. (Supondo que atributos privados de instância não devam ser modificados de fora desta classe ou subclasses por convenção. Ou seja,
_var1
é um atributo privado)Conclusão:
Os objetos dos parâmetros de entrada não devem ser modificados no local (mutados) nem devem ser vinculados a um objeto retornado pela função. (Se preferirmos a programação sem efeitos colaterais, o que é altamente recomendado. Consulte o Wiki sobre "efeito colateral" (os dois primeiros parágrafos são relevantes nesse contexto.).)
2)
Somente se o efeito colateral no parâmetro real for necessário, mas indesejável no parâmetro padrão, a solução útil será
def ...(var1=None):
if var1 is None:
var1 = []
Mais.3) Em alguns casos, o comportamento mutável dos parâmetros padrão é útil .
fonte
def f( a = None )
construção é recomendada quando você realmente quer dizer outra coisa. A cópia está correta, porque você não deve alterar argumentos. E quando o fazif a is None: a = [1, 2, 3]
, você copia a lista de qualquer maneira.Na verdade, isso não tem nada a ver com valores padrão, exceto que muitas vezes surge como um comportamento inesperado quando você escreve funções com valores padrão mutáveis.
Nenhum valor padrão à vista neste código, mas você obtém exatamente o mesmo problema.
O problema é que
foo
está modificando uma variável mutável transmitida pelo chamador, quando o chamador não espera isso. Código como este seria bom se a função fosse chamada algo comoappend_5
; o chamador chamaria a função para modificar o valor que eles transmitem e o comportamento seria esperado. Porém, é improvável que uma função desse tipo aceite um argumento padrão e provavelmente não retorne a lista (já que o chamador já tem uma referência a essa lista; a que acabou de passar).Seu original
foo
, com um argumento padrão, não deve ser modificadoa
se foi explicitamente passado ou obteve o valor padrão. Seu código deve deixar argumentos mutáveis em paz, a menos que fique claro no contexto / nome / documentação que os argumentos devem ser modificados. Usar valores mutáveis passados como argumentos como temporários locais é uma péssima idéia, se estamos em Python ou não e se há argumentos padrão envolvidos ou não.Se você precisar manipular destrutivamente um temporário local durante o cálculo de alguma coisa e precisar iniciar sua manipulação a partir de um valor de argumento, faça uma cópia.
fonte
append
mudara
"no local"). Que um mutável padrão não seja re-instanciado em cada chamada é o bit "inesperado" ... pelo menos para mim. :)cache={}
. No entanto, suspeito que esse "menor espanto" seja quando você não espera (ou deseja) a função que está chamando para alterar o argumento.cache={}
para completar.None
e atribuir o padrão real se o argumento forNone
não resolve esse problema (considero um anti-padrão por esse motivo). Se você corrigir o outro bug, evitando alterar os valores dos argumentos, com ou sem padrão, você nunca notará ou se importará com esse comportamento "surpreendente".Tópico já ocupado, mas pelo que li aqui, o seguinte me ajudou a perceber como está funcionando internamente:
fonte
a = a + [1]
sobrecargasa
... considere alterá-lo parab = a + [1] ; print id(b)
e adicionar uma linhaa.append(2)
. Isso tornará mais óbvio que+
em duas listas sempre cria uma nova lista (atribuída ab
), enquanto uma modificaçãoa
ainda pode ter a mesmaid(a)
.É uma otimização de desempenho. Como resultado dessa funcionalidade, quais dessas duas chamadas de função você acha mais rápidas?
Vou te dar uma dica. Aqui está a desmontagem (consulte http://docs.python.org/library/dis.html ):
#
1#
2Como você pode ver, não é um benefício de desempenho ao utilizar argumentos padrão imutáveis. Isso pode fazer a diferença se for uma função chamada com frequência ou se o argumento padrão demorar muito para ser construído. Além disso, lembre-se de que Python não é C. Em C, você tem constantes praticamente livres. No Python, você não tem esse benefício.
fonte
Python: o argumento padrão mutável
Os argumentos padrão são avaliados no momento em que a função é compilada em um objeto de função. Quando usado pela função, várias vezes por essa função, eles são e permanecem o mesmo objeto.
Quando são mutáveis, quando são mutados (por exemplo, adicionando um elemento a ele), eles permanecem mutados em chamadas consecutivas.
Eles permanecem mutantes porque são o mesmo objeto todas as vezes.
Código equivalente:
Como a lista está vinculada à função quando o objeto da função é compilado e instanciado, isso:
é quase exatamente equivalente a isso:
Demonstração
Aqui está uma demonstração - você pode verificar se eles são o mesmo objeto cada vez que são referenciados por
example.py
e executando-o com
python example.py
:Isso viola o princípio de "Mínimo espanto"?
Essa ordem de execução é frequentemente confusa para novos usuários do Python. Se você entende o modelo de execução Python, torna-se bastante esperado.
A instrução usual para novos usuários de Python:
Mas é por isso que a instrução usual para novos usuários é criar seus argumentos padrão como este:
Isso usa o singleton None como um objeto sentinela para informar à função se obtivemos ou não um argumento diferente do padrão. Se não obtivermos nenhum argumento, na verdade, queremos usar uma nova lista vazia,
[]
, como padrão.Como a seção do tutorial sobre fluxo de controle diz:
fonte
A resposta mais curta provavelmente seria "definição é execução", portanto, todo o argumento não faz sentido estrito. Como um exemplo mais artificial, você pode citar isto:
Espero que seja suficiente mostrar que não executar as expressões de argumento padrão no momento da execução da
def
instrução não é fácil ou não faz sentido, ou ambas.Eu concordo que é uma pegadinha quando você tenta usar construtores padrão, no entanto.
fonte
Uma solução simples usando None
fonte
Esse comportamento não é surpreendente se você levar o seguinte em consideração:
O papel de (2) foi abordado extensivamente neste segmento. (1) é provavelmente o fator causador de espanto, pois esse comportamento não é "intuitivo" quando proveniente de outras línguas.
(1) é descrito no tutorial do Python sobre classes . Em uma tentativa de atribuir um valor a um atributo de classe somente leitura:
Olhe para o exemplo original e considere os pontos acima:
Aqui
foo
está um objeto ea
é um atributo defoo
(disponível emfoo.func_defs[0]
). Comoa
é uma lista,a
é mutável e, portanto, é um atributo de leitura / gravação defoo
. É inicializado na lista vazia conforme especificado pela assinatura quando a função é instanciada e está disponível para leitura e gravação enquanto o objeto da função existir.Chamar
foo
sem substituir um padrão usa o valor desse padrãofoo.func_defs
. Nesse caso,foo.func_defs[0]
é usado paraa
o escopo de código do objeto de função. Alterações naa
alteraçãofoo.func_defs[0]
, que fazem parte dofoo
objeto e persistem entre a execução do código emfoo
.Agora, compare isso com o exemplo da documentação sobre como emular o comportamento padrão do argumento de outras linguagens , de modo que os padrões de assinatura da função sejam usados toda vez que a função for executada:
Levando em consideração (1) e (2) , pode-se perceber por que isso realiza o comportamento desejado:
foo
objeto da função é instanciado,foo.func_defs[0]
é definido comoNone
, um objeto imutável.L
na chamada de função),foo.func_defs[0]
(None
) fica disponível no escopo local comoL
.L = []
, a atribuição não pode ter êxitofoo.func_defs[0]
, porque esse atributo é somente leitura.L
é criada no escopo local e usada para o restante da chamada de função.foo.func_defs[0]
assim permanece inalterado para futuras invocações defoo
.fonte
Vou demonstrar uma estrutura alternativa para passar um valor de lista padrão para uma função (funciona igualmente bem com dicionários).
Como outros comentaram extensivamente, o parâmetro de lista é vinculado à função quando é definido, ao contrário de quando é executado. Como listas e dicionários são mutáveis, qualquer alteração nesse parâmetro afetará outras chamadas para esta função. Como resultado, as chamadas subseqüentes à função receberão essa lista compartilhada que pode ter sido alterada por outras chamadas para a função. Pior ainda, dois parâmetros estão usando o parâmetro compartilhado dessa função ao mesmo tempo, alheios às alterações feitas pela outra.
Método errado (provavelmente ...) :
Você pode verificar se eles são um e o mesmo objeto usando
id
:Por "Python Efetivo: 59 maneiras específicas de escrever um Python melhor", de Brett Slatkin, item 20: Use
None
e Docstrings para especificar argumentos padrão dinâmicos (p. 48)Essa implementação garante que cada chamada para a função receba a lista padrão ou a lista passada para a função.
Método preferido :
Pode haver casos de uso legítimos para o 'Método Errado', pelo qual o programador pretendia que o parâmetro da lista padrão fosse compartilhado, mas essa é provavelmente a exceção que a regra.
fonte
As soluções aqui são:
None
como seu valor padrão (ou nãoobject
) e ative-o para criar seus valores em tempo de execução; oulambda
como seu parâmetro padrão e chame-o dentro de um bloco try para obter o valor padrão (esse é o tipo de coisa para a qual a abstração lambda é).A segunda opção é boa porque os usuários da função podem transmitir uma chamada, que pode já existir (como a
type
)fonte
Quando fazemos isso:
... atribuímos o argumento
a
a uma lista sem nome , se o chamador não passar o valor de a.Para simplificar as coisas para esta discussão, vamos nomear temporariamente a lista sem nome. Que tal
pavlo
?A qualquer momento, se o chamador não nos disser o que
a
é, reutilizamospavlo
.Se
pavlo
é mutável (modificável), efoo
acaba modificando-o, um efeito que notamos na próxima vezfoo
é chamado sem especificara
.Então é isso que você vê (lembre-se,
pavlo
é inicializado para []):Agora
pavlo
é [5].A chamada
foo()
novamente modificapavlo
novamente:A especificação de
a
quando a chamadafoo()
garantepavlo
não é tocada.Então,
pavlo
ainda é[5, 5]
.fonte
Às vezes, exploro esse comportamento como uma alternativa ao seguinte padrão:
Se
singleton
for usado apenas poruse_singleton
, eu gosto do seguinte padrão como substituição:Eu usei isso para instanciar classes de clientes que acessam recursos externos e também para criar dictos ou listas para memorização.
Como não acho que esse padrão seja bem conhecido, faço um breve comentário para evitar futuros mal-entendidos.
fonte
_make_singleton
no tempo de def no exemplo de argumento padrão, mas no tempo de chamada no exemplo global. Uma substituição verdadeira usaria algum tipo de caixa mutável para o valor padrão do argumento, mas a adição do argumento cria uma oportunidade para passar valores alternativos.Você pode contornar isso substituindo o objeto (e, portanto, o empate pelo escopo):
Feio, mas funciona.
fonte
Pode ser verdade que:
é totalmente consistente manter os dois recursos acima e ainda fazer outro ponto:
As outras respostas, ou pelo menos algumas delas, fazem pontos 1 e 2, mas não 3, ou fazem o ponto 3 e minimizam os pontos 1 e 2. Mas todos os três são verdadeiros.
Pode ser verdade que trocar de cavalo no meio do caminho aqui exigiria uma quebra significativa, e que poderia haver mais problemas criados ao alterar o Python para lidar intuitivamente com o fragmento de abertura de Stefano. E pode ser verdade que alguém que conhecia bem o interior do Python pudesse explicar um campo minado de consequências. Contudo,
O comportamento existente não é pitonico e o Python é bem-sucedido porque muito pouco sobre a linguagem viola o princípio de menos espanto em qualquer lugar próximoisso mal. É um problema real, se seria sensato ou não arrancá-lo. É uma falha de design. Se você entende a linguagem muito melhor tentando rastrear o comportamento, posso dizer que o C ++ faz tudo isso e muito mais; você aprende muito navegando, por exemplo, erros sutis de ponteiro. Mas isso não é Pythonic: as pessoas que se preocupam com o Python o suficiente para perseverar diante desse comportamento são atraídas pela linguagem porque o Python tem muito menos surpresas do que outra linguagem. Dabblers e curiosos se tornam Pythonistas quando ficam surpresos com o pouco tempo necessário para que algo funcione - não por causa de um design fl - quero dizer, quebra-cabeça lógico oculto - que contraria as intuições de programadores que são atraídos por Python porque simplesmente funciona .
fonte
x=[]
significa "criar um objeto de lista vazio e vincular o nome 'x' a ele". Portanto,def f(x=[])
também é criada uma lista vazia. Nem sempre é vinculado a x, então, ao contrário, é vinculado ao substituto padrão. Mais tarde, quando f () é chamado, o padrão é eliminado e vinculado a x. Como a própria lista vazia foi esquivada, essa mesma lista é a única coisa disponível para vincular a x, independentemente de algo ter sido preso nela ou não. Como poderia ser de outra maneira?Esta não é uma falha de design . Quem tropeça nisso está fazendo algo errado.
Existem 3 casos em que você pode encontrar esse problema:
cache={}
, e não se espera que você chame a função com um argumento real.O exemplo na pergunta pode ser da categoria 1 ou 3. É estranho que ambos modifiquem a lista passada e a retornem; você deve escolher um ou outro.
fonte
cache={}
padrão é realmente uma solução apenas para entrevistas, no código real que você provavelmente deseja@lru_cache
!Esse "bug" me deu muitas horas extras de trabalho! Mas estou começando a ver um uso potencial dele (mas eu gostaria que ainda estivesse no momento da execução)
Vou dar o que vejo como um exemplo útil.
imprime o seguinte
fonte
Apenas mude a função para ser:
fonte
Eu acho que a resposta para essa pergunta está em como o python passa dados para o parâmetro (passa por valor ou por referência), não por mutabilidade ou como o python lida com a instrução "def".
Uma breve introdução. Primeiro, existem dois tipos de dados em python, um é o tipo de dados elementar simples, como números, e outro é objetos. Segundo, ao passar dados para parâmetros, o python passa o tipo de dados elementar por valor, ou seja, faz uma cópia local do valor para uma variável local, mas passa o objeto por referência, ou seja, ponteiros para o objeto.
Admitindo os dois pontos acima, vamos explicar o que aconteceu com o código python. É apenas por passar por referência para objetos, mas não tem nada a ver com mutável / imutável, ou sem dúvida o fato de que a instrução "def" é executada apenas uma vez quando é definida.
[] é um objeto, então python passa a referência de [] para
a
, ou seja,a
é apenas um ponteiro para [] que fica na memória como um objeto. Há apenas uma cópia de [] com, no entanto, muitas referências a ele. Para o primeiro foo (), a lista [] é alterada para 1 pelo método append. Mas observe que há apenas uma cópia do objeto de lista e esse objeto agora se torna 1 . Ao executar o segundo foo (), o que a página da web do effbot diz (os itens não são mais avaliados) está errado.a
é avaliado como o objeto de lista, embora agora o conteúdo do objeto seja 1 . Este é o efeito de passar por referência! O resultado de foo (3) pode ser facilmente derivado da mesma maneira.Para validar ainda mais minha resposta, vamos dar uma olhada em dois códigos adicionais.
====== No. 2 ========
[]
é um objeto, assim éNone
(o primeiro é mutável enquanto o segundo é imutável. Mas a mutabilidade não tem nada a ver com a questão). Nenhum está em algum lugar no espaço, mas sabemos que está lá e há apenas uma cópia de Nenhum lá. Portanto, toda vez que foo é chamado, os itens são avaliados (em oposição a alguma resposta que é avaliada apenas uma vez) como Nenhum, para ser claro, a referência (ou o endereço) de Nenhum. Em seguida, no foo, o item é alterado para [], ou seja, aponta para outro objeto que possui um endereço diferente.====== No. 3 =======
A invocação de foo (1) faz com que os itens aponte para um objeto de lista [] com um endereço, por exemplo, 11111111. o conteúdo da lista é alterado para 1 na função foo na sequela, mas o endereço não é alterado, ainda 11111111 Então foo (2, []) está chegando. Embora o [] in foo (2, []) tenha o mesmo conteúdo que o parâmetro padrão [] ao chamar foo (1), o endereço deles é diferente! Desde que nós fornecemos o parâmetro explicitamente,
items
tem que levar o endereço deste novo[]
, digamos 2222222, e devolvê-lo depois de fazer algumas alterações. Agora foo (3) é executado. desde apenasx
é fornecido, os itens precisam assumir seu valor padrão novamente. Qual é o valor padrão? Ele é definido ao definir a função foo: o objeto de lista localizado em 11111111. Portanto, os itens são avaliados como o endereço 11111111 com um elemento 1. A lista localizada em 2222222 também contém um elemento 2, mas não é apontado por nenhum item Mais. Conseqüentemente, um apêndice 3 faráitems
[1,3].Pelas explicações acima, podemos ver que a página da web do effbot recomendada na resposta aceita falhou em fornecer uma resposta relevante a esta pergunta. Além do mais, acho que um ponto na página do effbot está errado. Eu acho que o código sobre o UI.Button está correto:
Cada botão pode conter uma função distinta de retorno de chamada que exibirá valores diferentes de
i
. Eu posso fornecer um exemplo para mostrar isso:Se executarmos
x[7]()
, obteremos 7 conforme o esperado ex[9]()
forneceremos 9, outro valor dei
.fonte
x[7]()
é9
.TLDR: os padrões de tempo de definição são consistentes e estritamente mais expressivos.
A definição de uma função afeta dois escopos: o escopo de definição que contém a função e o escopo de execução contido na função. Embora seja bastante claro como os blocos são mapeados para os escopos, a questão é onde
def <name>(<args=defaults>):
pertence:A
def name
parte deve ser avaliada no escopo definido -name
afinal, queremos estar disponíveis lá. Avaliar a função apenas dentro de si mesma a tornaria inacessível.Como
parameter
é um nome constante, podemos "avaliá-lo" ao mesmo tempo quedef name
. Isso também tem a vantagem de produzir a função com uma assinatura conhecida comoname(parameter=...):
, em vez de vazianame(...):
.Agora, quando avaliar
default
?A consistência já diz "na definição": todo o resto
def <name>(<args=defaults>):
é melhor avaliado também na definição. Atrasar partes dela seria a escolha surpreendente.As duas opções também não são equivalentes: Se
default
for avaliada no momento da definição, ainda poderá afetar o tempo de execução. Sedefault
for avaliado no tempo de execução, não poderá afetar o tempo de definição. Escolher "na definição" permite expressar os dois casos, enquanto escolher "na execução" pode expressar apenas um:fonte
def <name>(<args=defaults>):
é melhor avaliado também na definição." Eu não acho que a conclusão se segue da premissa. Só porque duas coisas estão na mesma linha não significa que elas devam ser avaliadas no mesmo escopo.default
é uma coisa diferente do resto da linha: é uma expressão. Avaliar uma expressão é um processo muito diferente da definição de uma função.def
) ou expressão (lambda
) não altera que a criação de uma função significa avaliação - especialmente de sua assinatura. E os padrões fazem parte da assinatura de uma função. Isso não defaults médios têm de ser avaliados imediatamente - dicas tipo não pode, por exemplo. Mas certamente sugere que deveriam, a menos que haja uma boa razão para não fazê-lo.Todas as outras respostas explicam por que esse é realmente um comportamento agradável e desejado ou por que você não deveria estar precisando disso. O meu é para aqueles teimosos que querem exercer seu direito de dobrar o idioma à sua vontade, e não o contrário.
"Corrigiremos" esse comportamento com um decorador que copiará o valor padrão em vez de reutilizar a mesma instância para cada argumento posicional deixado em seu valor padrão.
Agora vamos redefinir nossa função usando este decorador:
Isso é particularmente interessante para funções que recebem vários argumentos. Comparar:
com
É importante observar que a solução acima é interrompida se você tentar usar a palavra-chave args, assim:
O decorador pode ser ajustado para permitir isso, mas deixamos isso como um exercício para o leitor;)
fonte