Pergunta

Qualquer pessoa mexer com Python tempo suficiente foi mordido (ou 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 é, em vez muito diferente, e muito surpreendente (para um novato):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

Um gerente da mina uma vez teve seu primeiro encontro com esse recurso, e chamou-lhe "uma falha de design dramática" da língua. I respondeu que o comportamento teve uma explicação subjacente, e é realmente muito intrigante e inesperado se você não entender os internos. No entanto, não foi capaz de responder (para mim) a seguinte pergunta: qual é a razão para a ligação o argumento padrão na definição de função, e não na execução da função? Eu duvido que o comportamento experiente tem um uso prático (que realmente utilizado variáveis ??estáticas em C, sem criação de bugs?)

Editar :

Baczek feito um exemplo interessante. Juntamente com a maioria de seus comentários e Utaal do em particular, eu mais elaborada:

>>> 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 em relação a onde colocar o escopo de parâmetros:? Dentro da função ou "juntos" com ele

Fazer a ligação dentro da função significaria que x é efetivamente ligado ao padrão especificado quando a função é chamada, não definido, algo que iria apresentar uma falha profunda: a linha def seria "híbrido" no sentido de que parte da ligação (do objeto de função) que aconteceria na definição, e uma parte (atribuição de parâmetros padrão) em tempo de função de invocação.

O comportamento real é mais consistente:. Tudo dessa linha fica avaliada quando essa linha é executada, ou seja, a definição de função

Foi útil?

Solução

Na verdade, isso não é uma falha de projeto, e não é por causa de internos, ou desempenho.
Ele vem simplesmente do fato de que as funções em Python são objetos de primeira classe, e não apenas um pedaço de código.

Assim que você começa a pensar neste caminho, então faz totalmente sentido: a função é um objeto a ser avaliado na sua definição; parâmetros padrão são uma espécie de "dados membro" e, portanto, seu estado pode mudar de uma chamada para a outra - exatamente como em qualquer outro objeto

.

Em qualquer caso, Effbot tem uma ótima explicação das razões para esse comportamento em Padrão Parâmetro Valores em Python .
Achei muito clara, e eu realmente sugerem lê-lo para um melhor conhecimento de como função de objetos de trabalho.

Outras dicas

Suponha que você tenha o seguinte código

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

Quando vejo a declaração de comer, a coisa menos surpreendente é pensar que, se o primeiro parâmetro não é dado, que será igual ao tupla ("apples", "bananas", "loganberries")

No entanto, supostamente mais tarde no código, eu fazer algo assim

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

, em seguida, se os parâmetros padrão foram obrigados a execução da função, em vez de declaração de função, então eu ficaria surpreso (em uma maneira muito ruim) a descobrir que as frutas tinha sido mudado. Isso seria mais surpreendente IMO do que descobrir que sua função foo acima foi mutação da lista.

As mentiras verdadeiro problema com variáveis ??mutáveis, e todas as línguas têm este problema até certo ponto. Aqui está uma pergunta: suponha que em Java Eu tenho o seguinte código:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

Agora, o meu mapa usar o valor da chave StringBuffer quando foi colocado no mapa, ou ele armazenar a chave por referência? De qualquer forma, alguém está surpreso; Ou a pessoa que tentou obter o objeto fora do Map usando um valor idêntico ao que eles colocá-lo em com, ou a pessoa que não consegue recuperar o seu objeto, mesmo que a chave que eles estão usando é literalmente o mesmo objeto que foi usado para colocá-lo no mapa (este é realmente por que Python não permite a sua mutável built-in tipos de dados para ser usado como chaves de dicionário).

O seu exemplo é um bom de um caso em que os recém-chegados Python será surpreendido e mordido. Mas eu diria que, se "fixo" isto, então que só iria criar uma situação diferente, onde eles estariam mordido em vez disso, e que um seria ainda menos intuitiva. Além disso, este é sempre o caso quando se lida com variáveis ??mutáveis; você sempre correr em casos em que alguém poderia intuitivamente esperar um ou o comportamento oposto, dependendo do que o código que está escrito.

Eu pessoalmente gosto atual abordagem do Python: argumentos de função padrão são avaliados quando a função é definida e esse objeto é sempre o padrão. Suponho que podiam caso especial usando uma lista vazia, mas esse tipo de embalagem especial causaria ainda mais espanto, para não mencionar ser para trás incompatíveis.

AFAICS ninguém ainda postou a parte relevante do documentação :

Os valores de parâmetros por defeito são avaliados quando a definição da função é executada. Isto significa que a expressão é avaliada uma vez, quando a função é definida, e que o mesmo valor “pré-calculado” é usado para cada chamada. Isto é especialmente importante para compreender quando um parâmetro padrão é um objeto mutável, como uma lista ou um dicionário: se as modifica função do objeto (por exemplo, acrescentando um item para uma lista), o valor padrão está em vigor modificado. Isso geralmente não é o que se pretendia. Uma maneira de contornar isso é usar None como padrão, e explicitamente teste para ele no corpo da função [...]

Eu não sei nada sobre o Python intérprete funcionamento interno (e eu não sou um especialista em compiladores e intérpretes, quer), então não me culpe se eu proponho nada Insensível ou impossível.

Desde que objetos python são mutáveis ?? Eu acho que isso deve ser levado em conta na concepção do material argumentos padrão. Quando você criar uma lista:

a = []

você espera obter um new lista referenciada por a.

Por que a a=[] em

def x(a=[]):

instanciar uma nova lista na definição da função e não no momento da invocação? É como você está perguntando "se o usuário não fornecer o argumento, em seguida, instanciar uma nova lista e usá-lo como se fosse produzido pelo chamador". Eu acho que isso é ambíguo em vez disso:

def x(a=datetime.datetime.now()):

user, você quer a como padrão para a data e hora correspondente a quando você está definindo ou executar x? Neste caso, como no anterior, eu vou manter o mesmo comportamento como se o argumento default "atribuição", foi a primeira instrução da função (datetime.now() chamada em chamada de função). Por outro lado, se o usuário queria que o mapeamento em tempo definição que ele poderia escrever:

b = datetime.datetime.now()
def x(a=b):

Eu sei, eu sei: isso é um fechamento. Alternativamente Python pode fornecer uma palavra-chave para a força definição de tempo de ligação:

def x(static a=b):

Bem, a razão é simplesmente que as ligações são feitas quando o código é executado, e a definição da função é executado, bem ... quando as funções é definido.

Comparar isto:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

Este código sofre com a exata mesma acaso inesperado. bananas é um atributo de classe e, portanto, quando você adicionar coisas a ele, ele é adicionado a todas as instâncias dessa classe. A razão é exatamente o mesmo.

É apenas "Como Funciona", e fazê-lo funcionar de forma diferente no caso função provavelmente seria complicado, e no caso da classe provavelmente impossível, ou pelo menos lento instanciação baixo objeto muito, como você teria que manter o código de classe ao redor e executá-lo quando os objetos são criados.

Sim, é inesperado. Mas uma vez que a moeda cai, ele se encaixa perfeitamente com a forma como Python funciona em geral. Na verdade, é um bom auxiliar de ensino, e uma vez que você entender por que isso acontece, você vai grok python muito melhor.

Dito isto, deve figurar com destaque em qualquer boa Python tutorial. Porque, como você mencionou, todo mundo corre para este problema mais cedo ou mais tarde.

Eu costumava pensar que a criação de objetos em tempo de execução seria a melhor abordagem. Estou menos certo agora, desde que você perder alguns recursos úteis, embora possa valer a pena, independentemente simplesmente para evitar confusão novato. As desvantagens de fazer isso são:

1. Desempenho

def foo(arg=something_expensive_to_compute())):
    ...

Se a avaliação em tempo de chamada é usado, então a função caro é chamado toda vez que a função é usado sem um argumento. Você gostaria quer pagar um preço caro em cada chamada, ou necessidade de armazenar em cache manualmente o valor externamente, poluindo o seu namespace e adicionando verbosidade.

2. Forçando parâmetros vinculados

Um truque útil é a de vincular parâmetros de um lambda para o atual ligação de uma variável quando o lambda é criado. Por exemplo:

funcs = [ lambda i=i: i for i in range(10)]

Este retorna uma lista de funções que retornam 0,1,2,3 ... respectivamente. Se o comportamento for alterado, eles vão i vez se ligam à Chamada em tempo valor de i, então você teria uma lista de funções que todos 9 retornado.

A única maneira de implementar isso de outra forma seria a criação de um outro fecho, com o i vinculada, ou seja:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. Introspecção

Considere o código:

def foo(a='test', b=100, c=[]):
   print a,b,c

Podemos obter informações sobre os argumentos e padrões usando o módulo inspect, que

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

Esta informação é muito útil para coisas como a geração de documentos, metaprogramming, decoradores etc.

Agora, suponha que o comportamento da inadimplência poderia ser alterado para que este é o equivalente a:

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

No entanto, perdemos a capacidade de introspecção, e ver o que os argumentos padrão são . Porque os objetos não foram construídos, não podemos nunca obtê-los sem realmente chamar a função. O melhor que podemos fazer é armazenar fora do código-fonte e retorno que como uma string.

Por que você não introspect?

Eu sou realmente não surpreendeu ninguém tem realizado a introspecção perspicaz oferecido pelo Python (2 e 3 extra) em chamáveis.

Dado um pouco simples função func definido como:

>>> def func(a = []):
...    a.append(5)

Quando Python encontra-lo, a primeira coisa que vai fazer é compilá-lo, a fim de criar um objeto code para esta função. Embora esta etapa de compilação é feito, Python avalia * e, em seguida, lojas os argumentos padrão (uma lista [] vazio aqui) no próprio objeto de função . Como a resposta superior mencionado:. A a lista agora pode ser considerado um membro do do func função

Então, vamos fazer alguma introspecção, um antes e depois para examinar como a lista é expandida dentro o objeto função. Estou usando Python 3.x para isso, para Python 2 o mesmo se aplica (__defaults__ uso ou func_defaults em Python 2; sim, dois nomes para a mesma coisa).

Função antes da execução:

>>> def func(a = []):
...     a.append(5)
...     

Depois de Python executa essa definição levará quaisquer parâmetros padrão especificados (a = [] aqui) e Cram-los no atributo __defaults__ para a função de objeto (seção relevante: Callables):

>>> func.__defaults__
([],)

O.k, então uma lista vazia como a única entrada na __defaults__, apenas como esperado.

Função Depois de Execução:

Vamos agora executar esta função:

>>> func()

Agora, vamos ver aqueles __defaults__ novamente:

>>> func.__defaults__
([5],)

Espantado? O valor dentro do objeto mudanças! chamadas consecutivas para a função irá agora simplesmente acrescentar a esse objeto list incorporado:

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

Então, você tem isso, a razão pela qual este 'falha' acontece, é porque os argumentos padrão são parte do objeto função. Não há nada de estranho acontecendo aqui, é tudo apenas um pouco surpreendente.

A solução comum para combater isso é usar None como padrão e, em seguida, inicializar no corpo da função:

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

Uma vez que o corpo da função é executada de novo cada vez, você sempre terá uma nova nova lista vazia se nenhum argumento foi passado para a.


Para verificar, ainda, que a lista em __defaults__ é a mesma que a utilizada na func função você pode apenas mudar a sua função para retornar o id do a lista usada dentro do corpo da função. Em seguida, compará-lo com a lista em __defaults__ ([0] posição na __defaults__) e você vai ver como estes são de fato se referindo à mesma instância lista:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

Tudo isso com o poder de introspecção!


* Para verificar se o Python avalia os argumentos padrão durante a compilação da função, tente executar o seguinte:

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

Como você vai notar, input() é chamado antes do processo de construção da função e vinculando-o ao nome bar é feita.

5 pontos em defesa do Python

  1. Simplicidade : O comportamento é simples no seguinte sentido: A maioria das pessoas cair nessa armadilha apenas uma vez, e não várias vezes.

  2. Consistência : Python sempre passa objetos, não nomes. O parâmetro padrão é, obviamente, parte da função título (não o corpo da função). É, por conseguinte, deve ser avaliada no momento do carregamento do módulo (e apenas no momento do carregamento do módulo, a menos que aninhado), não em tempo de chamada de função.

  3. Utilidade : Como Frederik Lundh aponta em sua explicação de "Default Parâmetro Valores em Python" , o comportamento atual pode ser bastante útil para programação avançada. (Use com moderação.)

  4. Documentação suficiente : Na documentação mais básico Python, o tutorial, a questão é bem alto anunciado como um "aviso importante" na início subseção da Seção "Mais sobre a definição de funções" . O aviso ainda usa negrito, que é raramente fora das posições aplicada. RTFM:. Leia o manual fino

  5. Meta-aprendizagem : Queda na armadilha é realmente um muito momento útil (pelo menos se você é um aprendiz reflexivo), porque você vai, posteriormente, entender melhor o ponto "Consistência" acima e que a vontade te ensinar muito sobre Python.

Este comportamento é fácil explicado por:

  1. Função (classe etc.) declaração é executado apenas uma vez, criando todo o valor padrão objetos
  2. tudo é passado por referência

Assim:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a não muda - cada chamada atribuição cria novo objeto int - novo objeto é impresso
  2. b não muda - nova matriz é de construção de valor padrão e impresso
  3. c muda - operação é executada no mesmo objeto - e ele é impresso

O que você está pedindo é porque este:

def func(a=[], b = 2):
    pass

não é internamente equivalente a esta:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

exceto para o caso de chamar explicitamente func (Nada, Nada), que vamos ignorar.

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?

Uma resposta é provavelmente lá - seria efetivamente transformar cada função com parâmetros predefinidos em um fechamento. Mesmo se tudo está escondido no intérprete e não um encerramento de full-blown, tem que ser armazenado em algum lugar dos dados. Seria mais lento e usar mais memória.

1) O chamado problema do "argumento padrão Mutante" é, em geral, um exemplo especial demonstrando que:
"Todas as funções com este problema sofrem também do problema efeito colateral similar sobre o parâmetro real ,"
Isso é contra as regras da programação funcional, geralmente indesejável e deve ser corrigido os dois juntos.

Exemplo:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

Solução : cópia
Uma solução absolutamente segura é copy ou deepcopy o objeto de entrada em primeiro lugar e, em seguida, fazer o que quer com a cópia.

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

Muitos embutidas tipos mutáveis ??têm um método de cópia como some_dict.copy() ou some_set.copy() ou pode ser copiado fácil como somelist[:] ou list(some_list). Cada objeto também pode ser copiado por copy.copy(any_object) ou mais minuciosa por copy.deepcopy() (este último útil se o objeto mutável é composto de objetos mutáveis). Alguns objetos são fundamentalmente baseado em efeitos colaterais como objeto "arquivo" e não pode ser significativamente reproduzida por cópia. copiar

Exemplo problema para uma questão de SO semelhante

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

Ele não deve ser nem salvos em qualquer público atributo de uma instância retornado por esta função. (Assumindo que privada atributos de exemplo não deve ser modificado a partir do lado de fora desta classe ou subclasses por convenção. _var1 isto é um atributo particular)

Conclusão:
parâmetros de entrada objetos não devem ser modificados no lugar (mutante), nem eles não devem ser binded em um objeto retornado pela função. (Se prefere programar sem efeitos colaterais que é fortemente recomendado. Consulte Wiki sobre o "efeito colateral" (Os dois primeiros parágrafos são relevent neste contexto.) .)

2)
Só se o efeito colateral sobre o parâmetro real é necessária, mas não desejada no parâmetro padrão, em seguida, a solução útil é def ...(var1=None): if var1 is None: var1 = [] Mais ..

3) Em alguns casos é o comportamento mutável da parâmetros padrão útil.

Isso realmente não tem nada a ver com valores padrão, diferente de que muitas vezes surge como um comportamento inesperado quando você escrever funções com valores padrão mutáveis.

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

Não há valores padrão à vista neste código, mas você obter exatamente o mesmo problema.

O problema é que foo é modificar uma variável mutável passado do chamador, quando o chamador não espera isso. Código como este seria bom se a função foi chamado algo como append_5; em seguida, o chamador seria chamar a função, a fim de modificar o valor que eles passam, e o comportamento seria esperado. Mas tal função seria muito improvável a tomar um argumento padrão, e provavelmente não voltar a lista (uma vez que o chamador já tem uma referência a essa lista, aquele que acabou de passar em).

Seu foo original, com um argumento padrão, não deve ser modificando a se foi passado explicitamente ou tem o valor padrão. Seu código deve deixar argumentos mutáveis ??sozinho a menos é claro a partir do contexto / nome / documentação que os argumentos devem ser modificados. Usando valores mutáveis ??passados ??como argumentos como temporários locais é uma péssima idéia, se estamos em Python ou não e se existem argumentos padrão envolvidos ou não.

Se você precisa manipular destrutivamente um local temporário no curso de computação alguma coisa, e você precisa para iniciar a sua manipulação de um valor de argumento, você precisa fazer uma cópia.

tópico já ocupado, mas pelo que eu li aqui, o seguinte me ajudou a perceber como ele está trabalhando internamente:

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232

É uma otimização de desempenho. Como resultado desta funcionalidade, qual destas duas chamadas de função que você acha que é mais rápido?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

Eu vou te dar uma dica. Aqui está a desmontagem (ver http://docs.python.org/library/dis.html):

#1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

#2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Eu duvido que o comportamento experiente tem um uso prático (que realmente utilizado variáveis ??estáticas em C, sem criação de bugs?)

Como você pode ver, há é um benefício de desempenho ao utilizar argumentos padrão imutáveis. Isso pode fazer a diferença se é uma função frequentemente chamado ou o argumento padrão leva muito tempo para construir. Além disso, tenha em mente que Python não é C. Em C você tem constantes que são bastante livre muito. Em Python você não tem esse benefício.

Python: O Mutante argumento padrão

argumentos padrão obter avaliados no momento da função é compilado em um objeto de função. Quando usado pela função, várias vezes por essa função, são e permanecem o mesmo objeto.

Quando eles são mutáveis, quando mutado (por exemplo, adicionando um elemento a ele) eles permanecem transformado em chamadas consecutivas.

Eles ficam mutação, porque eles são o mesmo objeto cada vez.

código equivalente:

Uma vez que a lista é obrigado a função quando o objeto de função é compilado e instanciado, o seguinte:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

é quase exatamente equivalente a esta:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

Demonstração

Aqui está uma demonstração - você pode verificar se eles são o mesmo objeto cada vez que eles são referenciados por

  • vendo que a lista é criada antes que a função tem compilação acabado para um objeto de função,
  • observando que a ID é o mesmo cada vez que a lista é referenciado,
  • observando que as estadias lista alterada quando a função que usa é chamado uma segunda vez,
  • respeite a ordem em que a saída é impresso a partir da fonte (que eu convenientemente numerada para você):

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

e executá-lo com python example.py:

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

Será que isso violaria o princípio de "menos Espanto"?

Esta ordem de execução é frequentemente confuso para novos usuários do Python. Se você entender o modelo de execução Python, então torna-se bastante esperado.

A instrução usual para os novos usuários do Python:

Mas é por isso que a instrução habitual para novos usuários é criar os seus argumentos padrão como este em vez disso:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

Este usa o Nenhum singleton como um objeto de sentinela para dizer a função ou não temos obtido um argumento diferente do padrão. Se tivermos nenhum argumento, então nós realmente quiser usar uma nova lista vazia, [], como o padrão.

Como o tutorial seção sobre controle de fluxo diz:

Se você não quer que o padrão a ser compartilhado entre chamadas subsequentes, você pode escrever a função como esta em vez disso:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

A resposta mais curta provavelmente seria "definição é a execução", portanto, todo o argumento não faz sentido estrito. Como um exemplo mais artificial, você pode citar este:

def a(): return []

def b(x=a()):
    print x

Esperamos que é suficiente para mostrar que não executar as expressões argumento padrão no tempo de execução da declaração def não é fácil ou não faz sentido, ou ambos.

Eu concordo que é uma pegadinha quando você tentar usar construtores padrão, no entanto.

Uma simples solução alternativa usando Nenhum

>>> def bar(b, data=None):
...     data = data or []
...     data.append(b)
...     return data
... 
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]

Este comportamento não é surpreendente se você levar em consideração o seguinte:

  1. O comportamento da classe somente leitura atributos mediante tentativas de atribuição, e que
  2. As funções são objetos (explicado bem na resposta aceite).

O papel de (2) foi coberto extensivamente neste segmento. (1) é provável que o espanto causando fator, como este comportamento não é "intuitivo" quando proveniente de outras línguas.

(1) é descrito na Python tutorial em classes . Em uma tentativa de atribuir um valor a um atributo de classe só de leitura:

... todas as variáveis ??encontradas fora do escopo interno são read-only ( uma tentativa de escrita para tal variável a irá simplesmente criar um nova variável local no escopo interno, deixando o identicamente chamado variável externa inalterada ).

olhar para trás ao exemplo original e considerar os pontos acima:

def foo(a=[]):
    a.append(5)
    return a

Aqui foo é um objeto e a é um atributo de foo (disponível em foo.func_defs[0]). Desde a é uma lista, a é mutável e é, portanto, um atributo de leitura e escrita de foo. Ele é inicializado com a lista vazia, conforme especificado pela assinatura quando a função é instanciado, e está disponível para leitura e escrita enquanto existir o objeto função.

Chamando foo sem substituir um padrão usa valor que do padrão de foo.func_defs. Neste caso, foo.func_defs[0] é usado para a dentro do escopo código de função de objeto. Mudanças a a foo.func_defs[0] mudança, que faz parte do objeto foo e persiste entre a execução do código em foo.

Agora, compare isso com o exemplo da documentação sobre emulando o comportamento argumento padrão de outras línguas , de modo que os padrões de assinatura de função são usados ??cada vez que a função é executada:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

Tomando (1) e (2) em consideração, pode-se ver por esta realiza o o comportamento desejado:

  • Quando o objeto de função foo é instanciado, foo.func_defs[0] está definido para None, um objeto imutável.
  • Quando a função é executada com padrões (com nenhum parâmetro especificado para L na chamada de função), foo.func_defs[0] (None) está disponível no âmbito local como L.
  • Ao L = [], a atribuição pode não ter sucesso em foo.func_defs[0], porque esse atributo é somente leitura.
  • Por (1) , uma nova variável local também L nomeado é criado no âmbito local e usado para o restante da função ligar. foo.func_defs[0] permanece assim inalterada para futuras invocações de foo.

As soluções aqui são:

  1. Use None como seu valor padrão (ou um object nonce), eo interruptor de que para criar os seus valores em tempo de execução; ou
  2. Use um lambda como seu parâmetro padrão, e chamá-lo dentro de um bloco try para obter o valor padrão (este é o tipo de coisa que lambda abstração é para).

A segunda opção é bom porque os usuários da função pode passar em um exigível, que pode já ser existente (como um type)

Vou demonstrar uma estrutura alternativa para passar um valor lista padrão para uma função (ele funciona igualmente bem com dicionários).

Como já foi amplamente comentado, o parâmetro lista é ligada à função quando ela é definida ao contrário de quando ele é executado. Como as listas e dicionários são mutáveis, qualquer alteração a este parâmetro afetará outras chamadas para esta função. Como resultado, as chamadas subseqüentes para a função receberá esta lista compartilhada que podem ter sido alteradas por quaisquer outras chamadas para a função. Pior ainda, dois parâmetros são o parâmetro compartilhado desta função, ao mesmo tempo ignorando as alterações feitas pelo outro.

método errado (provavelmente ...) :

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

Você pode verificar se eles são um eo mesmo objeto usando id:

>>> id(a)
5347866528

>>> id(b)
5347866528

O Per Brett Slatkin "Eficaz Python: 59 maneiras específicas para Escrever Melhor Python", Item 20: Use None e docstrings para especificar argumentos padrão dinâmico (. P 48)

A convenção para alcançar o resultado desejado em Python é fornecer um valor padrão de None e documentar o comportamento real na docstring.

Esta implementação garante que cada chamada para a função tanto recebe a lista padrão ou então a lista passado para a função.

Método Preferido :

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

Pode haver casos de uso legítimo para o 'método errado' pelo qual o programador destina o parâmetro lista padrão para ser compartilhado, mas isso é mais provável a exceção do que a regra.

Quando fazemos isso:

def foo(a=[]):
    ...

... vamos atribuir o a argumento para um sem nome lista, se o chamador não passar o valor de um.

Para tornar as coisas mais simples para essa discussão, vamos dar temporariamente a lista sem nome um nome. Como cerca de pavlo?

def foo(a=pavlo):
   ...

A qualquer momento, se o chamador não nos diz o que a é, nós reutilizar pavlo.

Se pavlo é mutável (modificável), e termina foo-se modificá-lo, um efeito notamos a foo próxima vez que é chamado sem especificar a.

Então é isso que você vê (Lembre-se, pavlo é inicializado para []):

 >>> foo()
 [5]

Agora, pavlo é [5].

Chamando foo() novamente modifica pavlo novamente:

>>> foo()
[5, 5]

Especificando a ao chamar garante foo() pavlo não é tocado.

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

Assim, pavlo ainda é [5, 5].

>>> foo()
[5, 5, 5]

Às vezes eu explorar este comportamento como uma alternativa para o seguinte padrão:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

Se singleton é utilizado apenas por use_singleton, eu como o padrão a seguir como uma substituição:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

Eu usei isso para instanciar classes de cliente que acessar recursos externos, e também para a criação de dicts ou listas para memoization.

Desde que eu não acho que esse padrão é bem conhecido, eu colocar um breve comentário para proteger contra futuros mal-entendidos.

Você pode contornar isso, substituindo o objeto (e, portanto, o empate com o escopo):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

feio, mas funciona.

Pode ser verdade que:

  1. Alguém está usando todos os recursos de linguagem / biblioteca, e
  2. Mudar o comportamento aqui seria mal aconselhado, mas

é inteiramente consistente para manter a ambas as características acima e ainda fazer um outro ponto:

  1. É uma característica confuso e é lamentável em Python.

As outras respostas, ou pelo menos alguns deles ou aponta fazer 1 e 2, mas não 3, ou fazer ponto 3 e pontos minimizar 1 e 2. Mas todos os três são verdadeiras.

Pode ser verdade que a mudança cavalos no meio do caminho aqui estaria pedindo quebra significativa, e que poderia haver mais problemas criados pela mudança Python para lidar intuitivamente trecho de abertura de Stefano. E isso pode ser verdade que alguém que sabia internos Python bem poderia explicar um campo minado de consequências. No entanto,

O comportamento existente não é Pythonic e Python é bem sucedido porque muito pouco sobre a linguagem viola o princípio da menor em qualquer lugar espanto próximo este mal. É um problema real, se é ou não seria sensato para arrancá-lo. É uma falha de projeto. Se você entender a língua muito melhor, tentando traçar o comportamento, posso dizer que C ++ faz tudo isso e muito mais; você aprende muito ao navegar, por exemplo, erros de ponteiro sutis. Mas este não é Pythonic: as pessoas que se preocupam com Python suficiente para perseverar em face de este comportamento são as pessoas que são atraídos para a linguagem Python porque tem muito menos surpresas do que outra língua. Curiosos e curiosos se tornar Pythonistas quando eles são surpreendidos com o pouco tempo que leva para obter algo de trabalho - não por causa de um fl design - Quero dizer, quebra-cabeça de lógica oculta - que os cortes contra as intuições de programadores que são atraídos para Python porque Apenas Works .

Isto não é uma falha de projeto . Qualquer um que tropeça isso está fazendo algo errado.

Existem 3 casos eu ver onde você pode executar para esse problema:

  1. Você pretende modificar o argumento como um efeito colateral da função. Neste caso, não faz sentido para ter um argumento padrão. A única exceção é quando você está abusando da lista de argumentos para ter atributos de função, por exemplo, cache={}, e você não pode esperar para chamar a função com um argumento real em tudo.
  2. Você pretende deixar o argumento não modificada, mas você acidentalmente se modificá-lo. Isso é um erro, corrigi-lo.
  3. Você pretende modificar o argumento para o uso dentro da função, mas não esperava que a modificação de estar fora visível da função. Nesse caso, você precisa fazer um cópia do argumento, se era o padrão ou não! Python não é uma linguagem chamada por valor, por isso não faz a cópia para você, você precisa ser explícito sobre isso.

O exemplo na questão poderia cair na categoria 1 ou 3. É estranho que as duas modifica a lista e retorna passou; você deve escolher um ou outro.

Esta "bug" me deu um monte de horas de trabalho de horas extras! Mas estou começando a ver um potencial de uso (mas eu teria gostado de estar no tempo de execução, ainda)

Eu vou te dar o que eu vejo como um exemplo útil.

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

imprime a seguinte

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way

Apenas alterar a função a ser:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a

Eu acho que a resposta a esta questão reside na forma como os dados passagem python para o parâmetro (passagem por valor ou por referência), não mutabilidade ou como python pega o "def" declaração.

A introdução breve. Primeiro, existem dois tipos de tipos de dados em python, um é tipo de dados elementar simples, como números, e outro tipo de dados é objetos. Em segundo lugar, ao passar dados para parâmetros, python passar tipo de dados elementar por valor, ou seja, fazer uma cópia local do valor de uma variável local, mas passar objeto por referência, ou seja, os ponteiros para o objeto.

Admitindo os dois pontos acima, vamos explicar o que aconteceu com o código python. É só por causa da passagem 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 "def" instrução é executada apenas uma vez quando ele é definido.

[] É um objectivo, por isso pitão passar a referência de [] a a, isto é, a apenas um ponteiro para [] que reside na memória como um objecto. Há apenas uma cópia de [] com, no entanto, muitas referências a ele. Para o primeiro foo (), a lista [] é alterado para 1 pelo método de acréscimo. Mas note que há apenas uma cópia do objeto lista e este objeto agora se torna 1 . Ao executar a segunda foo (), o que webpage effbot diz (itens não é avaliado mais) está errado. a é avaliado para ser o objeto da lista, embora agora o conteúdo do objeto é 1 . Este é o efeito da passagem por referência! O resultado de foo (3) pode ser facilmente derivada da mesma maneira.

Para mais validar a minha resposta, vamos dar uma olhada em dois códigos adicionais.

====== No. 2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

[] é um objeto, então é None (o primeiro é mutável, enquanto o último é 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 ele está lá e há apenas uma cópia de nenhum lá. Assim, cada foo tempo é invocado, itens é avaliada (em oposição a alguma resposta que só é avaliada uma vez) para ser None, para ser claro, a referência (ou o endereço) de None. Em seguida, no foo, item é alterado para [], isto é, aponta para um outro objecto, que tem um endereço diferente.

====== ======= No. 3

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

A invocação do foo (1) Os artigos fazem a apontar 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 for alterado, ainda 11111111. foo Então (2, []) está chegando. Embora o [] em foo (2, []) tem o mesmo conteúdo que o parâmetro padrão [] ao chamar foo (1), o seu endereço são diferentes! Desde nós fornecemos o parâmetro explicitamente, items tem de ter o endereço do novo [], dizem 2222222, e devolvê-lo depois de fazer alguma mudança. Agora foo (3) é executado. uma vez que apenas x é fornecido, itens tem que tomar seu valor padrão novamente. Qual é o valor padrão? Ele é definido ao definir a função foo: o objeto da lista localizado no 11111111. Assim, os itens é avaliada para ser o endereço 11111111 ter um elemento 1. A lista localizado na 2222222 também contém um elemento 2, mas não é apontado por itens qualquer Mais. Por conseguinte, um anexar de 3 vai fazer items [1,3].

A partir das explicações acima, podemos ver que a effbot webpage recomendado no aceitado resposta não conseguiu dar uma resposta relevante para esta pergunta. O que é mais, eu acho que um ponto na webpage effbot está errado. Eu acho que o código em relação ao UI.Button está correto:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

Cada botão pode realizar uma função de retorno distinto que irá exibir valor diferente de i. Posso dar um exemplo para mostrar isto:

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

Se nós executarmos x[7]() nós vamos chegar 7 como esperado, e x[9]() vontade dá 9, um outro valor de i.

TLDR:. Definir tempo padrões são consistentes e estritamente mais expressiva


A definição de uma função afeta dois escopos: o escopo definindo contendo a função e o escopo de execução contido por a função. Embora seja muito claro como blocos de mapear para escopos, a questão é onde def <name>(<args=defaults>): pertence a:

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

A parte def name deve avaliar no âmbito definindo - queremos name estar disponível lá, depois de tudo. Avaliando a função só dentro de si tornaria inacessível.

Desde parameter é um nome constante, podemos "avaliar" ao mesmo tempo que def name. Isto também tem a vantagem que produz a função com uma assinatura conhecida como name(parameter=...):, em vez de um name(...): nua.

Agora, quando avaliar default?

Consistência já diz "na definição": todo o resto de def <name>(<args=defaults>): é melhor avaliada na definição também. Atrasar partes seria a escolha surpreendente.

As duas opções não são equivalentes, ou: Se default é avaliada em tempo de definição, pode ainda afetar o tempo de execução. Se default é avaliada em tempo de execução, ele não pode afetar o tempo de definição. Escolhendo "na definição" permite expressar ambos os casos, ao escolher "em execução" pode expressar apenas uma:

def name(parameter=defined):  # set default at definition time
    ...

def name(parameter=default):     # delay default until execution time
    parameter = default if parameter is None else parameter
    ...

Cada outra resposta explica porque este é realmente um bom e comportamento desejado, ou porque você não deve precisar disto de qualquer maneira. O meu é para aqueles teimosos que querem exercer o seu direito de dobrar a língua à sua vontade, e não o contrário.

Nós "correção" este comportamento com um decorador que irá copiar o valor padrão em vez de reutilizar a mesma instância para cada argumento posicional à esquerda no seu valor padrão.

import inspect
from copy import copy

def sanify(function):
    def wrapper(*a, **kw):
        # store the default values
        defaults = inspect.getargspec(function).defaults # for python2
        # construct a new argument list
        new_args = []
        for i, arg in enumerate(defaults):
            # allow passing positional arguments
            if i in range(len(a)):
                new_args.append(a[i])
            else:
                # copy the value
                new_args.append(copy(arg))
        return function(*new_args, **kw)
    return wrapper

Agora vamos redefinir a nossa função usando este decorador:

@sanify
def foo(a=[]):
    a.append(5)
    return a

foo() # '[5]'
foo() # '[5]' -- as desired

Isto é particularmente puro para funções que recebem vários argumentos. Compare:

# the 'correct' approach
def bar(a=None, b=None, c=None):
    if a is None:
        a = []
    if b is None:
        b = []
    if c is None:
        c = []
    # finally do the actual work

com

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

É importante notar que as quebras de solução acima se você tentar usar argumentos de palavra-chave, assim:

foo(a=[4])

O decorador pode ser modificada para permitir isso, mas vamos deixar isso como um exercício para o leitor;)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top