¿Cómo debo declarar valores predeterminados para variables de instancia en Python?

90

¿Debo dar a los miembros de mi clase valores predeterminados como este:

class Foo:
    num = 1

¿o así?

class Foo:
    def __init__(self):
        self.num = 1

En esta pregunta descubrí que en ambos casos,

bar = Foo()
bar.num += 1

es una operación bien definida.

Entiendo que el primer método me dará una variable de clase, mientras que el segundo no. Sin embargo, si no necesito una variable de clase, pero solo necesito establecer un valor predeterminado para mis variables de instancia, ¿son ambos métodos igualmente buenos? ¿O uno de ellos más 'pitónico' que el otro?

Una cosa que he notado es que en el tutorial de Django, usan el segundo método para declarar modelos. Personalmente, creo que el segundo método es más elegante, pero me gustaría saber cuál es la forma "estándar".

int3
fuente

Respuestas:

132

Ampliando la respuesta de bp, quería mostrarle lo que quería decir con tipos inmutables.

Primero, esto está bien:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Sin embargo, esto solo funciona para tipos inmutables (invariables). Si el valor predeterminado fuera mutable (lo que significa que se puede reemplazar), esto sucedería en su lugar:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Tenga en cuenta que ambos ay btienen un atributo compartido. A menudo esto no es deseado.

Esta es la forma Pythonic de definir valores predeterminados para variables de ejemplo, cuando el tipo es mutable:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

La razón por la que mi primer fragmento de código funciona es porque, con tipos inmutables, Python crea una nueva instancia de él siempre que lo desee. Si necesita agregar 1 a 1, Python crea un nuevo 2 para usted, porque el antiguo 1 no se puede cambiar. La razón es principalmente por hash, creo.

Xavier Ho
fuente
2
Recientemente me encontré con esta forma mucho más simple de implementar valores predeterminados para atributos mutables. Solo escribe self.attr = attr or []dentro del __init__método. Logra el mismo resultado (creo) y sigue siendo claro y legible.
Bill
4
Lo siento amigos. Investigué un poco más sobre la sugerencia en mi comentario anterior y aparentemente no es lo mismo y no se recomienda .
Proyecto de ley
¿Por qué la clase TestC tiene paréntesis vacíos? ¿Es eso un legado de Python 2?
ChrisG
55

Los dos fragmentos hacen cosas diferentes, por lo que no es una cuestión de gustos, sino de cuál es el comportamiento correcto en su contexto. La documentación de Python explica la diferencia, pero aquí hay algunos ejemplos:

Anexo A

class Foo:
  def __init__(self):
    self.num = 1

Esto se une numa las instancias de Foo . El cambio a este campo no se propaga a otras instancias.

Así:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Anexo B

class Bar:
  num = 1

Esto se une numa la clase Bar . ¡Los cambios se propagan!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Respuesta real

Si no necesito una variable de clase, pero solo necesito establecer un valor predeterminado para mis variables de instancia, ¿son ambos métodos igualmente buenos? ¿O uno de ellos más 'pitónico' que el otro?

El código en la exhibición B es completamente incorrecto para esto: ¿por qué querría vincular un atributo de clase (valor predeterminado en la creación de la instancia) a la instancia única?

El código de la exhibición A está bien.

Si desea dar valores predeterminados, por ejemplo, variables en su constructor, sin embargo, haría esto:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...o incluso:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... o incluso: (preferible, ¡pero si y solo si se trata de tipos inmutables!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

De esta forma puedes hacer:

foo1 = Foo(4)
foo2 = Foo() #use default
badp
fuente
1
¿Es el método de inicio utilizado en este ejemplo diferente del guión bajo___init__undercore para la inicialización @badp
Eliethesaiyan
¿No debería ser el primer ejemplodef __init__(self, num = None)
PyWalker2797
6

El uso de miembros de clase para dar valores predeterminados funciona muy bien siempre que tenga cuidado de hacerlo solo con valores inmutables. Si intenta hacerlo con una lista o un dict, sería bastante mortal. También funciona cuando el atributo de instancia es una referencia a una clase siempre que el valor predeterminado sea Ninguno.

He visto esta técnica utilizada con mucho éxito en reposicionamiento, que es un marco que se ejecuta sobre Zope. La ventaja aquí no es solo que cuando su clase persiste en la base de datos, solo deben guardarse los atributos no predeterminados, sino también cuando necesita agregar un nuevo campo al esquema, todos los objetos existentes ven el nuevo campo con su valor predeterminado. valor sin necesidad de cambiar realmente los datos almacenados.

También encuentro que funciona bien en la codificación más general, pero es una cuestión de estilo. Usa lo que sea con lo que estés más feliz.

Duncan
fuente
3

Usar miembros de clase para valores predeterminados de variables de instancia no es una buena idea, y es la primera vez que veo que se menciona esta idea. Funciona en su ejemplo, pero puede fallar en muchos casos. Por ejemplo, si el valor es mutable, mutarlo en una instancia sin modificar alterará el valor predeterminado:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
Max Shawabkeh
fuente
excepto que solo el primero []se clonó allí; x.ly y.lson tanto nombres malvados como valores inmediatos diferentes vinculados al mismo referente. (Términos de abogado de idiomas inventados)
Phlip
1

También puede declarar variables de clase como Ninguna, lo que evitará la propagación. Esto es útil cuando necesita una clase bien definida y desea evitar AttributeErrors. Por ejemplo:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Además, si necesita valores predeterminados:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Por supuesto, siga la información en las otras respuestas sobre el uso de tipos mutables frente a inmutables como predeterminado en __init__.

Realista
fuente
0

Con las clases de datos , una característica agregada en Python 3.7, ahora hay otra forma (bastante conveniente) de lograr establecer valores predeterminados en instancias de clases. El decorador dataclassgenerará automáticamente algunos métodos en su clase, como el constructor. Como señala la documentación vinculada anteriormente, "[l] as variables miembro a utilizar en estos métodos generados se definen mediante anotaciones de tipo PEP 526 ".

Considerando el ejemplo de OP, podríamos implementarlo así:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Al construir un objeto del tipo de esta clase, opcionalmente podríamos sobrescribir el valor.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Johannes Gehrs
fuente