2011-08-24 10 views
5

Digamos que tiene una función que necesita mantener algún tipo de estado y comportarse de manera diferente según ese estado. Soy consciente de dos formas de implementar este donde el estado se almacena en su totalidad por la función:Python: atributos de función o valores predeterminados mutables

  1. Usando un atributo de la función
  2. El uso de un valor predeterminado mutable

Utilizando una versión ligeramente modificada de Felix Klings answer to another question , aquí es una función de ejemplo que se puede utilizar en re.sub() de modo que sólo el tercer partido a una expresión regular será reemplazado:

atributo función:

def replace(match): 
    replace.c = getattr(replace, "c", 0) + 1 
    return repl if replace.c == 3 else match.group(0) 

mutable valor predeterminado:

def replace(match, c=[0]): 
    c[0] += 1 
    return repl if c[0] == 3 else match.group(0) 

Para mí la primera parece más limpio, pero he visto la segunda más comúnmente. ¿Cuál es preferible y por qué?

+4

La gente simplemente no piensan en función de los atributos, ya que no son tan ampliamente utilizados por los principiantes, pero piensa _everyone_ de utilizar el valor por defecto mutable porque _every_ principiante es mordido por ellos al menos una vez. – agf

Respuesta

4

Utilizo el cierre en su lugar, sin efectos secundarios.

Aquí está el ejemplo (acabo he modificado el ejemplo original de Felix Klings answer):

def replaceNthWith(n, replacement): 
    c = [0] 
    def replace(match): 
     c[0] += 1 
     return replacement if c[0] == n else match.group(0) 
    return replace 

Y el uso:

# reset state (in our case count, c=0) for each string manipulation 
re.sub(pattern, replaceNthWith(n, replacement), str1) 
re.sub(pattern, replaceNthWith(n, replacement), str2) 
#or persist state between calls 
replace = replaceNthWith(n, replacement) 
re.sub(pattern, replace, str1) 
re.sub(pattern, replace, str2) 

Para mutable lo que debería suceder si alguien llama a reemplazar (partido , c = [])?

Para atributo que se rompió la encapsulación (sí sé que pitón no implementado en las clases de razones diff ...)

+0

+1 De acuerdo: no me di cuenta de que el contexto de la pregunta * original * es una función anidada. – Seth

+0

@ JasonR.Coombs He añadido un ejemplo –

+0

¡ah! confundiendo 'c = [0]' para 'c [0]' ... – n611x007

1

¿Qué tal:

  1. utilizar una clase
  2. Utilice una variable global

Es cierto que estos no se almacenan en su totalidad dentro de la función. Probablemente me volvería a utilizar una clase:

class Replacer(object): 
    c = 0 
    @staticmethod # if you like 
    def replace(match): 
     replace.c += 1 
     ... 

Para responder a su pregunta real, utilice getattr. Es una forma muy clara y legible de almacenar datos para más adelante. Debería ser bastante obvio para alguien que lo lee lo que intenta hacer.

La versión del argumento predeterminado mutable es un ejemplo de un error de programación común (suponiendo que obtendrá una nueva lista cada vez). Por esa razón solo lo evitaría. Alguien que lo lea más tarde puede decidir que es una buena idea sin entender completamente las consecuencias. E incluso en este caso, parece que su función solo funcionará una vez (su valor c nunca se restablece a cero).

1

Para mí, estos dos enfoques parecen poco fiables. El problema es clamar por una instancia de clase. Normalmente no pensamos en funciones como mantenimiento de estado entre llamadas; eso es lo que las clases son para.

Dicho esto, he utilizado antes los atributos de función para este tipo de cosas. Particularmente si se trata de una función de un solo uso definida dentro de otro código (es decir, no es posible utilizarla desde ningún otro lugar), basta con tachuelas en los atributos para que sea más conciso que definir una clase completamente nueva y crear una instancia de la misma.

Nunca abusaría de los valores predeterminados para esto. Hay una gran barrera para entender porque el propósito natural de los valores predeterminados es proporcionar valores predeterminados de argumentos, no para mantener el estado entre llamadas. Además, un argumento predeterminado lo invita a proporcionar un valor no predeterminado, y normalmente obtiene un comportamiento muy extraño si lo hace con una función que está abusando de los valores predeterminados para mantener el estado.

2

Ambas formas me resultan extrañas. El primero, sin embargo, es mucho mejor. Pero cuando lo piensas de esta manera: "Algo con un estado que puede hacer operaciones con ese estado y una entrada adicional", realmente suena como un objeto normal. Y cuando algo suena como un objeto, éste debe ser un objeto ... SO, mi solución sería utilizar un objeto simple con un método __call__:

class StatefulReplace(object): 
    def __init__(self, initial_c=0): 
     self.c = initial_c 
    def __call__(self, match): 
     self.c += 1 
     return repl if self.c == 3 else match.group(0) 

y luego se puede escribir en el espacio global o init módulo:

replace = StatefulReplace(0) 
Cuestiones relacionadas