Sé que Python no admite la sobrecarga de métodos, pero me he encontrado con un problema que parece que no puedo resolver de una manera agradable Pythonic.
Estoy haciendo un juego donde un personaje necesita disparar una variedad de balas, pero ¿cómo escribo diferentes funciones para crear estas balas? Por ejemplo, supongamos que tengo una función que crea una bala que viaja del punto A al B con una velocidad determinada. Escribiría una función como esta:
def add_bullet(sprite, start, headto, speed):
... Code ...
Pero quiero escribir otras funciones para crear viñetas como:
def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
... And so on ...
Y así sucesivamente con muchas variaciones. ¿Hay una mejor manera de hacerlo sin usar tantos argumentos de palabras clave porque se vuelve un poco feo rápido? Cambiar el nombre de cada función es bastante mala debido a que obtiene ya sea add_bullet1
, add_bullet2
o add_bullet_with_really_long_name
.
Para abordar algunas respuestas:
No, no puedo crear una jerarquía de clase Bullet porque eso es demasiado lento. El código real para administrar viñetas está en C y mis funciones son envoltorios alrededor de la API de C.
Sé acerca de los argumentos de las palabras clave, pero verificar todo tipo de combinaciones de parámetros se está volviendo molesto, pero los argumentos predeterminados ayudan a
acceleration=0
default value + if + else
para hacer lo mismo que C ++. Esta es una de las pocas cosas que C ++ tiene mejor legibilidad que Python ...script, curve
son, si tienen un antepasado común, qué métodos admiten. Con el tipeo de pato, depende de usted el diseño de la clase para descubrir qué métodos necesitan admitir. PresumiblementeScript
admite algún tipo de devolución de llamada basada en el paso de tiempo (pero ¿qué objeto debería devolver? ¿La posición en ese paso de tiempo? ¿La trayectoria en ese paso de tiempo?). Presumiblementestart, direction, speed
ystart, headto, spead, acceleration
ambos describen tipos de trayectorias, pero nuevamente depende de usted diseñar la clase receptora para saber cómo desempaquetarlos y procesarlos.Respuestas:
Lo que está pidiendo se llama envío múltiple . Ver julia ejemplos de lenguaje que demuestran diferentes tipos de despachos.
Sin embargo, antes de ver eso, primero abordaremos por qué sobrecargar no es realmente lo que quieres en Python.
¿Por qué no sobrecargar?
Primero, uno necesita comprender el concepto de sobrecarga y por qué no es aplicable a Python.
Python es un lenguaje de tipo dinámico , por lo que el concepto de sobrecarga simplemente no se aplica a él. Sin embargo, no todo está perdido, ya que podemos crear tales funciones alternativas en tiempo de ejecución:
Por lo tanto, deberíamos poder hacer métodos múltiples en Python o, como se le llama alternativamente: despacho múltiple .
Despacho múltiple
Los métodos múltiples también se denominan despacho múltiple :
Python no admite esto fuera de la caja 1 , pero, como sucede, hay un excelente paquete de Python llamado multipledispatch que hace exactamente eso.
Solución
Así es como podemos usar el paquete multipledispatch 2 para implementar sus métodos:
1. Python 3 actualmente admite el envío único 2. Tenga cuidado de no usar el envío múltiple en un entorno de subprocesos múltiples o obtendrá un comportamiento extraño.
fuente
speed
podría cambiar en el medio de la función cuando otro hilo establece su propio valorspeed
)! Me llevó mucho tiempo darme cuenta de que era la biblioteca la culpable.Python admite "sobrecarga de métodos" tal como lo presenta. De hecho, lo que acaba de describir es trivial de implementar en Python, de muchas maneras diferentes, pero yo diría que:
En el código anterior,
default
es un valor predeterminado plausible para esos argumentos, oNone
. Luego puede llamar al método solo con los argumentos que le interesan, y Python usará los valores predeterminados.También podrías hacer algo como esto:
Otra alternativa es conectar directamente la función deseada directamente a la clase o instancia:
Otra forma más es utilizar un patrón abstracto de fábrica:
fuente
if sprite and script and not start and not direction and not speed...
solo para saber que está en una acción específica. porque una persona que llama puede llamar a la función proporcionando todos los parámetros disponibles. Mientras se sobrecarga, defina los conjuntos exactos de parámetros relevantes.Puede utilizar la solución "roll-your-own" para la sobrecarga de funciones. Este se copia del artículo de Guido van Rossum sobre métodos múltiples (porque hay poca diferencia entre mm y sobrecarga en python):
El uso sería
Las limitaciones más restrictivas en este momento son:
fuente
Una opción posible es usar el módulo multipledispatch como se detalla aquí: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch
En lugar de hacer esto:
Puedes hacerlo:
Con el uso resultante:
fuente
En Python 3.4 se agregó PEP-0443. Funciones genéricas de despacho único .
Aquí hay una breve descripción de la API de PEP.
Para definir una función genérica, decorarla con el decorador @singledispatch. Tenga en cuenta que el envío ocurre en el tipo del primer argumento. Cree su función en consecuencia:
Para agregar implementaciones sobrecargadas a la función, use el atributo register () de la función genérica. Este es un decorador, toma un parámetro de tipo y decora una función que implementa la operación para ese tipo:
fuente
Este tipo de comportamiento generalmente se resuelve (en lenguajes OOP) usando el polimorfismo. Cada tipo de bala sería responsable de saber cómo viaja. Por ejemplo:
Pase tantos argumentos a la función c_ que existan, luego haga el trabajo de determinar a qué función c llamar en función de los valores en la función c inicial. Entonces, python solo debería estar llamando a la función one c. Esa función c mira los argumentos y luego puede delegar a otras funciones c de manera apropiada.
Básicamente, está utilizando cada subclase como un contenedor de datos diferente, pero al definir todos los argumentos potenciales en la clase base, las subclases son libres de ignorar aquellos con los que no hacen nada.
Cuando aparece un nuevo tipo de viñeta, simplemente puede definir una propiedad más en la base, cambiar la función de una pitón para que pase la propiedad adicional y la función c_ que examina los argumentos y delega adecuadamente. No suena tan mal, supongo.
fuente
pos+v*t
y luego compararlo con los límites de la pantalla,if x > 800
etc. Llamar a estas funciones varios cientos de veces por cuadro resultó ser inaceptablemente lento. Era algo como 40 fps a 100% de la CPU con el pitón puro a 60 fps con 5% -10% cuando se hace en C.add_bullet
y extraiga todos los campos que necesita. Editaré mi respuesta.Al pasar la palabra clave args .
fuente
Utilice múltiples argumentos de palabras clave en la definición o cree una
Bullet
jerarquía cuyas instancias se pasen a la función.fuente
Creo que su requisito básico es tener una sintaxis similar a C / C ++ en Python con el menor dolor de cabeza posible. Aunque me gustó la respuesta de Alexander Poluektov, no funciona para las clases.
Lo siguiente debería funcionar para las clases. Funciona al distinguir por la cantidad de argumentos que no son palabras clave (pero no admite la distinción por tipo):
Y se puede usar simplemente así:
Salida:
fuente
El
@overload
decorador se agregó con sugerencias de tipo (PEP 484). Si bien esto no cambia el comportamiento de Python, facilita la comprensión de lo que está sucediendo y mypy detecta errores.Ver: sugerencias de tipo y PEP 484
fuente
Creo que un
Bullet
jerarquía de clases con el polimorfismo asociado es el camino a seguir. Puede sobrecargar efectivamente el constructor de la clase base usando una metaclase para que llamar a la clase base resulte en la creación del objeto de subclase apropiado. A continuación se muestra un código de ejemplo para ilustrar la esencia de lo que quiero decir.Actualizado
El código ha sido modificado para ejecutarse tanto en Python 2 como en 3 para mantenerlo relevante. Esto se hizo de una manera que evita el uso de la sintaxis explícita de metaclase de Python, que varía entre las dos versiones.
Para lograr ese objetivo, se crea una
BulletMetaBase
instancia de laBulletMeta
clase llamando explícitamente a la metaclase cuando se crea laBullet
clase base (en lugar de usar el__metaclass__=
atributo de clase o mediante unmetaclass
argumento de palabra clave según la versión de Python).Salida:
fuente
Bullet
subclases sin tener que modificar la clase base o la función de fábrica cada vez que agrega otro subtipo. (Por supuesto, si está utilizando C en lugar de C ++, supongo que no tiene clases). También podría hacer una metaclase más inteligente que descubriera por sí misma qué subclase crear en función del tipo y / o número de argumentos pasados (como lo hace C ++ para soportar la sobrecarga).Python 3.8 agregó functools.singledispatchmethod
Salida
Salida:
fuente
Utilice argumentos de palabras clave con valores predeterminados. P.ej
En el caso de una viñeta recta versus una viñeta curva, agregaría dos funciones:
add_bullet_straight
yadd_bullet_curved
.fuente
Los métodos de sobrecarga son difíciles en Python. Sin embargo, podría usarse pasar las variables dict, list o primitivas.
He intentado algo para mis casos de uso, esto podría ayudar aquí a comprender que las personas sobrecarguen los métodos.
Tomemos su ejemplo:
un método de sobrecarga de clase con llamada a los métodos de diferentes clases.
pasar los argumentos de la clase remota:
O
Por lo tanto, se está logrando el manejo de la lista, el diccionario o las variables primitivas de la sobrecarga de métodos.
pruébalo para tus códigos.
fuente
Solo un simple decorador
Puedes usarlo así
Modifíquelo para adaptarlo a su caso de uso.
self/this
argumento. Sin embargo, la mayoría de los idiomas solo lo hacen por elthis
argumento solamente. El decorador anterior extiende la idea a múltiples parámetros.Para aclarar, asuma un lenguaje estático y defina las funciones.
Con el despacho estático (sobrecarga) verá "número llamado" dos veces, porque
x
se ha declarado comoNumber
, y eso es lo único que le importa a la sobrecarga. Con el despacho dinámico, verá "entero llamado, flotante llamado", porque esos son los tipos realesx
en el momento en que se llama a la función.fuente
x
para el envío dinámico, ni en qué orden se solicitó a ambos métodos para el envío estático. Recomiendo editar las declaraciones impresas aprint('number called for Integer')
etc.