propias procedimientos por parámetros parametros opcionales omision linea funciones comando argumentos python inheritance method-signature

python - procedimientos - La mejor práctica de herencia:*args,** kwargs o especificación explícita de parámetros



python class kwargs (5)

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 desde *args y **kwargs y mantiene las cosas seguras si la firma de Parent:save changes (por ejemplo, agregar un nuevo argumento predeterminado).

Actualización : en este caso, tener los * argumentos puede causar problemas si se agrega un nuevo argumento posicional al padre. Mantendré solo **kwargs y administraré solo nuevos argumentos con valores predeterminados. Evitaría la propagación de errores.

A menudo me encuentro sobrescribiendo los métodos de una clase padre, y nunca puedo decidir si debería enumerar explícitamente los parámetros dados o simplemente usar una manta *args, **kwargs construcción *args, **kwargs . ¿Una versión es mejor que la otra? ¿Hay una mejor práctica? ¿Qué (des) 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

  • Más explícito (Zen de Python)
  • más fácil de entender
  • parámetros de función de fácil acceso

Beneficios percibidos de la variante general

  • más seco
  • la clase de padres es fácilmente intercambiable
  • el cambio de los valores predeterminados en el método principal se propaga sin tocar otro código

No es realmente una respuesta, sino más bien una nota al margen: si realmente desea asegurarse de que los valores predeterminados para 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, tengo que admitir que esto se ve bastante feo y solo lo usaría si siento que realmente lo necesito.


Prefiero argumentos explícitos porque autocompletar le permite ver la firma del método de la función al hacer la llamada a la función.


Si está seguro de que Child mantendrá la firma, seguramente el enfoque explícito es preferible, pero cuando Child 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 forma, 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 unas cuantas veces es bastante desagradable cuando no tienes args y kwargs.

Por 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 debe basar sus elecciones en tales cosas, simplemente estoy implicando que este enfoque es bastante generalizado.


Principio de sustitución de Liskov

En general, no desea que la firma del método varíe en los tipos derivados. Esto puede causar problemas si desea cambiar el uso de los tipos derivados. Esto a menudo se conoce como el Principio de Sustitución de Liskov .

Beneficios de 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:

  • ayuda a documentar el método a través de buenos nombres de argumentos
  • ayuda a documentar el método especificando qué argumentos son necesarios y cuáles tienen valores predeterminados
  • proporcionar validación implícita (args necesarios faltantes arrojar excepciones obvias)

Argumentos y longitud de longitud variable

No confundas los argumentos de longitud variable para una buena práctica de acoplamiento. Debe haber una cierta cantidad de cohesión entre una clase principal y las clases derivadas, de lo contrario no estarían relacionadas entre sí. Es normal que el código relacionado produzca un acoplamiento que refleje el nivel de cohesión.

Lugares para usar argumentos de longitud variable

El uso de argumentos de longitud variable no debe ser su primera opción. Debe usarse cuando tiene una buena razón, como:

  • Definición de un contenedor de funciones (es decir, un decorador).
  • Definición de una función paramétrica polimórfica.
  • Cuando los argumentos que puede tomar realmente son completamente variables (por ejemplo, una función de conexión DB generalizada). Por lo general, las funciones de conexión de DB toman una cadena de conexión en muchas formas diferentes, tanto en forma de arg único como en forma de múltiples arcos. También hay diferentes conjuntos de opciones para diferentes bases de datos.
  • ...

¿Estás haciendo algo mal?

Si encuentra que a menudo está creando métodos que toman muchos argumentos o métodos derivados con firmas diferentes, es posible que tenga un problema mayor en la forma en que está organizando su código.