A menudo me encuentro sobreescribiendo los métodos de una clase padre, y nunca puedo decidir si debo enumerar explícitamente los parámetros dados o simplemente usar una manta *args, **kwargs
construct. ¿Una versión es mejor que la otra? ¿Hay una mejor práctica? ¿Qué (dis-) ventajas me estoy perdiendo?
class Parent(object): def save(self, commit=True): # ... class Explicit(Parent): def save(self, commit=True): super(Explicit, self).save(commit=commit) # more logic class Blanket(Parent): def save(self, *args, **kwargs): super(Blanket, self).save(*args, **kwargs) # more logic
Beneficios percibidos de la variante explícita.
Beneficios percibidos de la variante de manta.
Principio de Sustitución de Liskov
En general, no desea que la firma de su método varíe en los tipos derivados. Esto puede causar problemas si desea intercambiar el uso de tipos derivados. Esto se refiere a menudo como el principio de sustitución de Liskov .
Beneficios de las firmas explícitas
Al mismo tiempo, no creo que sea correcto que todos sus métodos tengan una firma de *args
, **kwargs
. Firmas explícitas:
Argumentos de longitud variable y acoplamiento
No confunda los argumentos de longitud variable con una buena práctica de acoplamiento. Debe haber una cierta cantidad de cohesión entre una clase padre y las clases derivadas, de lo contrario no estarían relacionadas entre sí. Es normal que el código relacionado dé como resultado un acoplamiento que refleje el nivel de cohesión.
Lugares para usar argumentos de longitud variable
El uso de argumentos de longitud variable no debería ser su primera opción. Se debe usar cuando tengas una buena razón como:
¿Estás haciendo algo mal?
Si encuentra que a menudo está creando métodos que toman muchos argumentos o métodos derivados con diferentes firmas, puede tener un problema mayor en la forma en que organiza su código.
Mi elección sería:
class Child(Parent): def save(self, commit=True, **kwargs): super(Child, self).save(commit, **kwargs) # more logic
Evita el acceso al argumento de confirmación de *args
y **kwargs
y mantiene las cosas seguras si cambia la firma de Parent:save
(por ejemplo, agregar un nuevo argumento predeterminado).
Actualización : en este caso, tener * args puede causar problemas si se agrega un nuevo argumento posicional al padre. Mantendría solo **kwargs
y manejaría solo los nuevos argumentos con valores predeterminados. Evitaría errores de propagación.
Si está seguro de que el niño mantendrá la firma, seguramente el enfoque explícito es preferible, pero cuando el niño cambie la firma, yo personalmente prefiero usar ambos enfoques:
class Parent(object): def do_stuff(self, a, b): # some logic class Child(Parent): def do_stuff(self, c, *args, **kwargs): super(Child, self).do_stuff(*args, **kwargs) # some logic with c
De esta manera, los cambios en la firma son bastante legibles en Child, mientras que la firma original es bastante legible en Parent.
En mi opinión, esta es también la mejor manera cuando tienes herencia múltiple, porque llamar super
algunas veces es bastante desagradable cuando no tienes args y kwargs.
Para lo que vale, esta es también la forma preferida en bastantes libs y frameworks de Python (Django, Tornado, Requests, Markdown, por nombrar algunos). Aunque uno no debería basar sus elecciones en tales cosas, simplemente estoy insinuando que este enfoque está bastante extendido.
No es realmente una respuesta, sino más bien una nota al margen: si realmente quiere asegurarse de que los valores predeterminados de la clase principal se propaguen a las clases secundarias, puede hacer algo como:
class Parent(object): default_save_commit=True def save(self, commit=default_save_commit): # ... class Derived(Parent): def save(self, commit=Parent.default_save_commit): super(Derived, self).save(commit=commit)
Sin embargo, debo admitir que esto parece bastante feo y solo lo usaría si siento que realmente lo necesito.
Prefiero los argumentos explícitos porque la función de autocompletar te permite ver la firma del método de la función mientras realizas la llamada de la función.
Además de las otras respuestas:
Tener argumentos variables puede “desacoplar” al padre del hijo, pero crea un acoplamiento entre el objeto creado y el padre, lo que creo que es peor, porque ahora creó una pareja de “larga distancia” (más difícil de detectar, más difícil de identificar). mantener, porque puede crear varios objetos en su aplicación)
Si está buscando un desacoplamiento, eche un vistazo a la composición sobre la herencia