¿Puede una clase base abstracta de Python imponer firmas de funciones?

Supongamos que defino una clase base abstracta como esta:

from abc import abstractmethod, ABCMeta class Quacker(object): __metaclass__ = ABCMeta @abstractmethod def quack(self): return "Quack!" 

Esto garantiza que cualquier clase derivada de Quacker debe implementar el método quack . Pero si defino lo siguiente:

 class PoliteDuck(Quacker): def quack(self, name): return "Quack quack %s!" % name d = PoliteDuck() # no error 

Se me permite crear una instancia de la clase porque proporcioné el método quack , pero las firmas de la función no coinciden. Puedo ver cómo esto puede ser útil en algunas situaciones, pero estoy interesado en asegurar que definitivamente pueda llamar a los métodos abstractos. ¡Esto podría fallar si la firma de la función es diferente!

Entonces, ¿cómo puedo hacer cumplir una firma de función coincidente? Esperaría un error al crear el objeto si las firmas no coinciden, como si no lo hubiera definido en absoluto.

que esto no es idiomático, y que Python es el lenguaje incorrecto que se usa si quiero este tipo de garantías, pero eso no viene al caso, ¿es posible?

    Es peor de lo que piensas. Los métodos abstractos se rastrean solo por su nombre, por lo que ni siquiera tiene que hacer un método de quack para crear una instancia de la clase secundaria.

     class SurrealDuck(Quacker): quack = 3 d = SurrealDuck() print d.quack # Shows 3 

    No hay nada en el sistema que haga que quack sea ​​un objeto que se pueda llamar, y mucho menos uno cuyos argumentos coincidan con el original del método abstracto. En el mejor de los casos, podría ABCMeta subclase ABCMeta y agregar el código usted mismo para comparar las firmas de tipo en el hijo con los originales en el padre, pero esto no sería trivial de implementar.

    (Actualmente, marcar algo como “abstracto” esencialmente solo agrega el nombre a un atributo de conjunto congelado en el padre ( Quacker.__abstractmethods__ ). Hacer que una clase sea instanciable es tan simple como establecer este atributo en un iterable vacío, lo cual es útil para realizar pruebas). )

    Te recomiendo que mires a pylint. Ejecuté este código a través de su análisis estático, y en la línea donde definió el método quack (), informó:

     Argument number differs from overridden method (arguments-differ) 

    ( https://en.wikipedia.org/wiki/Pylint )

    No creo que esto haya cambiado en el lenguaje base de python , pero encontré una solución que podría ser útil. El paquete mypy parece imponer la conformidad de la firma en las clases base abstractas y su implementación concreta. Básicamente, si define una firma en la clase base abstracta, todas las clases concretas tienen que seguir la misma firma exacta.

    Aquí hay un ejemplo que se romperá en mypy . El código se tomó del mypy web de mypy , pero lo mypy para esta respuesta.

    El primer ejemplo es el código que pasará. Tenga en cuenta que las firmas para el método de eat son las mismas y mypy no se queja.

     from abc import ABCMeta, abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def eat(self, food: str) -> None: pass @property @abstractmethod def can_walk(self) -> bool: pass class Cat(Animal): def eat(self, food: str) -> None: pass # Body omitted @property def can_walk(self) -> bool: return True y = Cat() # OK 

    Pero mypy este código un poco y ahora mypy lanza un error:

     from abc import ABCMeta, abstractmethod class Animal(metaclass=ABCMeta): @abstractmethod def eat(self, food: str) -> None: pass @property @abstractmethod def can_walk(self) -> bool: pass class Cat(Animal): def eat(self, food: str, drink: str) -> None: pass # Body omitted @property def can_walk(self) -> bool: return True y = Cat() # Error 

    Mypy sigue siendo un trabajo en progreso, pero funciona en este caso. Hay algunos casos de esquina en los que algunas variantes de firma pueden no ser atrapadas, pero de lo contrario parece funcionar para la mayoría de las aplicaciones prácticas.