Sobrecarga de la función Python

213

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_bullet2o add_bullet_with_really_long_name.

Para abordar algunas respuestas:

  1. 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.

  2. 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

Balas
fuente
55
Solo funciona para un parámetro, pero aquí (para las personas que vienen desde un motor de búsqueda): docs.python.org/3/library/…
leewz
1
Este parece ser un buen lugar para los valores predeterminados. puede establecer algunos en Ninguno y simplemente verificarlos. el impacto booleano extra parece insignificante
Andrew Scott Evans
Tiene que usar default value + if + elsepara hacer lo mismo que C ++. Esta es una de las pocas cosas que C ++ tiene mejor legibilidad que Python ...
Deqing
Estoy confundido sobre por qué kwargs no es una respuesta válida. Dices que no quieres usar muchos argumentos de palabras clave porque se vuelve feo rápido ... bueno, esa es solo la naturaleza del problema. Si tiene muchos argumentos y es complicado porque tiene muchos argumentos, ¿qué esperaba? ¿Quieres usar muchos argumentos sin especificarlos en ningún lado? Python no es un lector de mente.
Cálculo
No sabemos qué tipo de objetos script, curveson, 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. Presumiblemente Scriptadmite 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?). Presumiblemente start, direction, speedy start, headto, spead, accelerationambos describen tipos de trayectorias, pero nuevamente depende de usted diseñar la clase receptora para saber cómo desempaquetarlos y procesarlos.
smci

Respuestas:

144

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.

Cuando se trabaja con idiomas que pueden discriminar los tipos de datos en tiempo de compilación, la selección entre las alternativas puede ocurrir en tiempo de compilación. El acto de crear tales funciones alternativas para la selección en tiempo de compilación generalmente se conoce como sobrecargar una función. ( Wikipedia )

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:

En los lenguajes de programación que difieren la identificación del tipo de datos hasta el tiempo de ejecución, la selección entre funciones alternativas debe ocurrir en tiempo de ejecución, en función de los tipos de argumentos de función determinados dinámicamente. Las funciones cuyas implementaciones alternativas se seleccionan de esta manera generalmente se denominan métodos múltiples . ( Wikipedia )

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 :

El despacho múltiple o los métodos múltiples es la característica de algunos lenguajes de programación orientados a objetos en los que una función o método se puede distribuir dinámicamente en función del tipo de tiempo de ejecución (dinámico) de más de uno de sus argumentos. ( Wikipedia )

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:

>>> from multipledispatch import dispatch
>>> from collections import namedtuple  
>>> from types import *  # we can test for lambda type, e.g.:
>>> type(lambda a: 1) == LambdaType
True

>>> Sprite = namedtuple('Sprite', ['name'])
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Curve = namedtuple('Curve', ['x', 'y', 'z'])
>>> Vector = namedtuple('Vector', ['x','y','z'])

>>> @dispatch(Sprite, Point, Vector, int)
... def add_bullet(sprite, start, direction, speed):
...     print("Called Version 1")
...
>>> @dispatch(Sprite, Point, Point, int, float)
... def add_bullet(sprite, start, headto, speed, acceleration):
...     print("Called version 2")
...
>>> @dispatch(Sprite, LambdaType)
... def add_bullet(sprite, script):
...     print("Called version 3")
...
>>> @dispatch(Sprite, Curve, int)
... def add_bullet(sprite, curve, speed):
...     print("Called version 4")
...

>>> sprite = Sprite('Turtle')
>>> start = Point(1,2)
>>> direction = Vector(1,1,1)
>>> speed = 100 #km/h
>>> acceleration = 5.0 #m/s
>>> script = lambda sprite: sprite.x * 2
>>> curve = Curve(3, 1, 4)
>>> headto = Point(100, 100) # somewhere far away

>>> add_bullet(sprite, start, direction, speed)
Called Version 1

>>> add_bullet(sprite, start, headto, speed, acceleration)
Called version 2

>>> add_bullet(sprite, script)
Called version 3

>>> add_bullet(sprite, curve, speed)
Called version 4

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.

Andriy Drozdyuk
fuente
66
¿Cuál es el problema con 'multipledispatch' en un entorno multiproceso? ¡Dado que el código del lado del servidor generalmente está en un entorno de subprocesos múltiples! ¡Solo trato de desenterrarlo!
danzeer
77
@danzeer No era seguro para subprocesos. ¡Vi que el argumento se modificaba por dos hilos diferentes (es decir, el valor de speedpodría cambiar en el medio de la función cuando otro hilo establece su propio valor speed)! Me llevó mucho tiempo darme cuenta de que era la biblioteca la culpable.
Andriy Drozdyuk
108

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:

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, sprite=default, start=default, 
                 direction=default, speed=default, accel=default, 
                  curve=default):
        # do stuff with your arguments

En el código anterior, defaultes 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:

class Character(object):
    # your character __init__ and other methods go here

    def add_bullet(self, **kwargs):
        # here you can unpack kwargs as (key, values) and
        # do stuff with them, and use some global dictionary
        # to provide default values and ensure that ``key``
        # is a valid argument...

        # do stuff with your arguments

Otra alternativa es conectar directamente la función deseada directamente a la clase o instancia:

def some_implementation(self, arg1, arg2, arg3):
  # implementation
my_class.add_bullet = some_implementation_of_add_bullet

Otra forma más es utilizar un patrón abstracto de fábrica:

class Character(object):
   def __init__(self, bfactory, *args, **kwargs):
       self.bfactory = bfactory
   def add_bullet(self):
       sprite = self.bfactory.sprite()
       speed = self.bfactory.speed()
       # do stuff with your sprite and speed

class pretty_and_fast_factory(object):
    def sprite(self):
       return pretty_sprite
    def speed(self):
       return 10000000000.0

my_character = Character(pretty_and_fast_factory(), a1, a2, kw1=v1, kw2=v2)
my_character.add_bullet() # uses pretty_and_fast_factory

# now, if you have another factory called "ugly_and_slow_factory" 
# you can change it at runtime in python by issuing
my_character.bfactory = ugly_and_slow_factory()

# In the last example you can see abstract factory and "method
# overloading" (as you call it) in action 
Escualo
fuente
107
Todos estos se ven como ejemplos de argumentos variables, en lugar de sobrecargarlos. Dado que la sobrecarga le permite tener la misma función, para diferentes tipos como argumentos. por ejemplo: sum (real_num1, real_num2) y sum (imaginary_num1, imaginary_num2) Ambos tendrán la misma sintaxis de llamada, pero en realidad esperan 2 tipos diferentes como entrada, y la implementación también debe cambiar internamente
Efren
17
Usando la respuesta con la que iría, ¿cómo le presentaría a la persona que llama qué argumentos tienen sentido juntos? Simplemente poner un montón de argumentos, cada uno con un valor predeterminado, puede proporcionar la misma funcionalidad, pero en términos de una API es mucho menos elegante
Greg Ennis
66
Nada de lo anterior es sobrecargado, la implementación tendrá que verificar todas las combinaciones de entradas de parámetros (o ignorar parámetros) como: 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.
Roee Gavirel
55
Es muy molesto cuando la gente dice que Python admite la sobrecarga de métodos. No es asi. El hecho de que ponga "sobrecarga de método" en las comillas indica que está al tanto de este hecho. Puede obtener una funcionalidad similar con varias técnicas, como la que se menciona aquí. Pero la sobrecarga de métodos tiene una definición muy específica.
Howard Swope
Creo que el objetivo es que, si bien la sobrecarga de métodos no es una característica de Python, los mecanismos anteriores se pueden utilizar para lograr el efecto equivalente.
Rawr llamó el
93

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):

registry = {}

class MultiMethod(object):
    def __init__(self, name):
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function


def multimethod(*types):
    def register(function):
        name = function.__name__
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

El uso sería

from multimethods import multimethod
import unittest

# 'overload' makes more sense in this case
overload = multimethod

class Sprite(object):
    pass

class Point(object):
    pass

class Curve(object):
    pass

@overload(Sprite, Point, Direction, int)
def add_bullet(sprite, start, direction, speed):
    # ...

@overload(Sprite, Point, Point, int, int)
def add_bullet(sprite, start, headto, speed, acceleration):
    # ...

@overload(Sprite, str)
def add_bullet(sprite, script):
    # ...

@overload(Sprite, Curve, speed)
def add_bullet(sprite, curve, speed):
    # ...

Las limitaciones más restrictivas en este momento son:

  • los métodos no son compatibles, solo las funciones que no son miembros de la clase;
  • la herencia no se maneja;
  • los kwargs no son compatibles;
  • el registro de nuevas funciones se debe hacer en el momento de la importación, lo que no es seguro para subprocesos
Alexander Poluektov
fuente
66
+1 para decoradores para ampliar el idioma en este caso de uso.
Eloims
1
+1 porque esta es una gran idea (y probablemente con lo que debería ir el OP) --- Nunca había visto una implementación de métodos múltiples en Python.
Escualo
39

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:

def add(self, other):
    if isinstance(other, Foo):
        ...
    elif isinstance(other, Bar):
        ...
    else:
        raise NotImplementedError()

Puedes hacerlo:

from multipledispatch import dispatch
@dispatch(int, int)
def add(x, y):
    return x + y    

@dispatch(object, object)
def add(x, y):
    return "%s + %s" % (x, y)

Con el uso resultante:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'
Dave C
fuente
44
¿Por qué esto no obtiene más votos? Supongo que debido a la falta de ejemplos ... He creado una respuesta con un ejemplo de cómo implementar una solución al problema de OP con el paquete de múltiples paquetes.
Andriy Drozdyuk
19

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:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say,", end=" ")
    print(arg)

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:

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)

@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)
SKhalymon
fuente
11

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:

class Bullet(object):
    def __init__(self):
        self.curve = None
        self.speed = None
        self.acceleration = None
        self.sprite_image = None

class RegularBullet(Bullet):
    def __init__(self):
        super(RegularBullet, self).__init__()
        self.speed = 10

class Grenade(Bullet):
    def __init__(self):
        super(Grenade, self).__init__()
        self.speed = 4
        self.curve = 3.5

add_bullet(Grendade())

def add_bullet(bullet):
    c_function(bullet.speed, bullet.curve, bullet.acceleration, bullet.sprite, bullet.x, bullet.y) 


void c_function(double speed, double curve, double accel, char[] sprite, ...) {
    if (speed != null && ...) regular_bullet(...)
    else if (...) curved_bullet(...)
    //..etc..
}

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.

Josh Smeaton
fuente
1
Esa fue mi primera aproximación, pero por razones de rendimiento que tuve que reescribir ese código en C.
Las balas
@Bullets, sugeriría que puede haber una serie de opciones diferentes disponibles para mejorar el rendimiento en lugar de escribir una gran cantidad de funciones c que probablemente no harán mucho. Por ejemplo: crear una instancia puede ser costoso, así que mantenga un grupo de objetos. Aunque digo esto sin saber lo que encontraste demasiado lento. Por interés, ¿qué fue exactamente lento sobre este enfoque? A menos que se pase mucho tiempo en el lado C del límite, no puedo pensar que Python (en sí mismo) sea el verdadero problema.
Josh Smeaton
Quizás haya otras formas de mejorar el rendimiento, pero soy mucho mejor con C que con Python. El problema era calcular los movimientos de las balas y detectar cuándo salían de los límites de la pantalla. Tenía un método para calcular la posición de la bala pos+v*ty luego compararlo con los límites de la pantalla, if x > 800etc. 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.
balas
@Bullets, bastante justo entonces. Todavía usaría el enfoque que utilicé para encapsular datos. Pase una instancia de viñeta add_bullety extraiga todos los campos que necesita. Editaré mi respuesta.
Josh Smeaton
@Bullets: puede combinar sus funciones C y el enfoque OOP sugerido por Josh usando Cython . Permite la unión anticipada, por lo que no debe haber una penalización de velocidad.
jfs
4

Utilice múltiples argumentos de palabras clave en la definición o cree una Bulletjerarquía cuyas instancias se pasen a la función.

Ignacio Vazquez-Abrams
fuente
Iba a sugerir el segundo enfoque: hacer algunos BulletParams ... clases para especificar los detalles de la viñeta.
John Zwinck
¿Puedes dar más detalles sobre esto? Traté de crear una jerarquía de clases con diferentes viñetas, pero esto no funciona, porque Python es demasiado lento. No puede calcular los movimientos de la cantidad requerida de viñetas lo suficientemente rápido, así que tuve que escribir esa parte en C. Todas las variantes de add_bullet simplemente llaman a la función C correspondiente.
Balas
4

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):

class TestOverloading(object):
    def overloaded_function(self, *args, **kwargs):
        # Call the function that has the same number of non-keyword arguments.  
        getattr(self, "_overloaded_function_impl_" + str(len(args)))(*args, **kwargs)
    
    def _overloaded_function_impl_3(self, sprite, start, direction, **kwargs):
        print "This is overload 3"
        print "Sprite: %s" % str(sprite)
        print "Start: %s" % str(start)
        print "Direction: %s" % str(direction)
        
    def _overloaded_function_impl_2(self, sprite, script):
        print "This is overload 2"
        print "Sprite: %s" % str(sprite)
        print "Script: "
        print script

Y se puede usar simplemente así:

test = TestOverloading()

test.overloaded_function("I'm a Sprite", 0, "Right")
print
test.overloaded_function("I'm another Sprite", "while x == True: print 'hi'")

Salida:

Esto es sobrecarga 3
Sprite: Soy un Sprite
Inicio: 0
Dirección: Derecha

Esto es sobrecarga 2
Sprite: soy otro Sprite
Script:
mientras x == Verdadero: imprime 'hola'

Tim Ludwinski
fuente
4

El @overloaddecorador 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

Richard Whitehead
fuente
¿Puedes agregar algunos ejemplos?
Gerrit
3

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 BulletMetaBaseinstancia de la BulletMetaclase llamando explícitamente a la metaclase cuando se crea la Bulletclase base (en lugar de usar el __metaclass__=atributo de clase o mediante un metaclassargumento de palabra clave según la versión de Python).

class BulletMeta(type):
    def __new__(cls, classname, bases, classdict):
        """ Create Bullet class or a subclass of it. """
        classobj = type.__new__(cls, classname, bases, classdict)
        if classname != 'BulletMetaBase':
            if classname == 'Bullet':  # Base class definition?
                classobj.registry = {}  # Initialize subclass registry.
            else:
                try:
                    alias = classdict['alias']
                except KeyError:
                    raise TypeError("Bullet subclass %s has no 'alias'" %
                                    classname)
                if alias in Bullet.registry: # unique?
                    raise TypeError("Bullet subclass %s's alias attribute "
                                    "%r already in use" % (classname, alias))
                # Register subclass under the specified alias.
                classobj.registry[alias] = classobj

        return classobj

    def __call__(cls, alias, *args, **kwargs):
        """ Bullet subclasses instance factory.

            Subclasses should only be instantiated by calls to the base
            class with their subclass' alias as the first arg.
        """
        if cls != Bullet:
            raise TypeError("Bullet subclass %r objects should not to "
                            "be explicitly constructed." % cls.__name__)
        elif alias not in cls.registry: # Bullet subclass?
            raise NotImplementedError("Unknown Bullet subclass %r" %
                                      str(alias))
        # Create designated subclass object (call its __init__ method).
        subclass = cls.registry[alias]
        return type.__call__(subclass, *args, **kwargs)


class Bullet(BulletMeta('BulletMetaBase', (object,), {})):
    # Presumably you'd define some abstract methods that all here
    # that would be supported by all subclasses.
    # These definitions could just raise NotImplementedError() or
    # implement the functionality is some sub-optimal generic way.
    # For example:
    def fire(self, *args, **kwargs):
        raise NotImplementedError(self.__class__.__name__ + ".fire() method")

    # Abstract base class's __init__ should never be called.
    # If subclasses need to call super class's __init__() for some
    # reason then it would need to be implemented.
    def __init__(self, *args, **kwargs):
        raise NotImplementedError("Bullet is an abstract base class")


# Subclass definitions.
class Bullet1(Bullet):
    alias = 'B1'
    def __init__(self, sprite, start, direction, speed):
        print('creating %s object' % self.__class__.__name__)
    def fire(self, trajectory):
        print('Bullet1 object fired with %s trajectory' % trajectory)


class Bullet2(Bullet):
    alias = 'B2'
    def __init__(self, sprite, start, headto, spead, acceleration):
        print('creating %s object' % self.__class__.__name__)


class Bullet3(Bullet):
    alias = 'B3'
    def __init__(self, sprite, script): # script controlled bullets
        print('creating %s object' % self.__class__.__name__)


class Bullet4(Bullet):
    alias = 'B4'
    def __init__(self, sprite, curve, speed): # for bullets with curved paths
        print('creating %s object' % self.__class__.__name__)


class Sprite: pass
class Curve: pass

b1 = Bullet('B1', Sprite(), (10,20,30), 90, 600)
b2 = Bullet('B2', Sprite(), (-30,17,94), (1,-1,-1), 600, 10)
b3 = Bullet('B3', Sprite(), 'bullet42.script')
b4 = Bullet('B4', Sprite(), Curve(), 720)
b1.fire('uniform gravity')
b2.fire('uniform gravity')

Salida:

creating Bullet1 object
creating Bullet2 object
creating Bullet3 object
creating Bullet4 object
Bullet1 object fired with uniform gravity trajectory
Traceback (most recent call last):
  File "python-function-overloading.py", line 93, in <module>
    b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method
  File "python-function-overloading.py", line 49, in fire
    raise NotImplementedError(self.__class__.__name__ + ".fire() method")
NotImplementedError: Bullet2.fire() method
Martineau
fuente
Hmm, esta sigue siendo una forma elegante de nombrar las funciones como add_bullet1, add_bullet2, etc.
Balas
1
@Bullets: Tal vez lo sea, o tal vez sea solo una forma ligeramente elaborada de crear una función de fábrica. Lo bueno de esto es que admite una jerarquía de Bulletsubclases 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).
Martineau
1
Esta idea de herencia también sería mi primera opción.
Daniel Möller
3

Python 3.8 agregó functools.singledispatchmethod

Transforme un método en una función genérica de despacho único.

Para definir un método genérico, decórelo con el decorador @singledispatchmethod. Tenga en cuenta que el envío se realiza en el tipo del primer argumento que no es propio o que no es cls, cree su función en consecuencia:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg


negator = Negator()
for v in [42, True, "Overloading"]:
    neg = negator.neg(v)
    print(f"{v=}, {neg=}")

Salida

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

@singledispatchmethod admite el anidamiento con otros decoradores como @classmethod. Tenga en cuenta que para permitir dispatcher.register, el método singledispatch debe ser el decorador más externo. Aquí está la clase Negator con los métodos neg vinculados a la clase:

from functools import singledispatchmethod


class Negator:
    @singledispatchmethod
    @staticmethod
    def neg(arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(arg: int) -> int:
        return -arg

    @neg.register
    def _(arg: bool) -> bool:
        return not arg


for v in [42, True, "Overloading"]:
    neg = Negator.neg(v)
    print(f"{v=}, {neg=}")

Salida:

v=42, neg=-42
v=True, neg=False
NotImplementedError: Cannot negate a

Se puede usar el mismo patrón para otros decoradores similares: método estático, método abstracto y otros.

Vlad Bezden
fuente
2

Utilice argumentos de palabras clave con valores predeterminados. P.ej

def add_bullet(sprite, start=default, direction=default, script=default, speed=default):

En el caso de una viñeta recta versus una viñeta curva, agregaría dos funciones: add_bullet_straighty add_bullet_curved.

Rafe Kettler
fuente
2

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.

def add_bullet(sprite=None, start=None, headto=None, spead=None, acceleration=None):

pasar los argumentos de la clase remota:

add_bullet(sprite = 'test', start=Yes,headto={'lat':10.6666,'long':10.6666},accelaration=10.6}

O

add_bullet(sprite = 'test', start=Yes, headto={'lat':10.6666,'long':10.6666},speed=['10','20,'30']}

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.

shashankS
fuente
2

Solo un simple decorador

class overload:
    def __init__(self, f):
        self.cases = {}

    def args(self, *args):
        def store_function(f):
            self.cases[tuple(args)] = f
            return self
        return store_function

    def __call__(self, *args):
        function = self.cases[tuple(type(arg) for arg in args)]
        return function(*args)

Puedes usarlo así

@overload
def f():
    pass

@f.args(int, int)
def f(x, y):
    print('two integers')

@f.args(float)
def f(x):
    print('one float')


f(5.5)
f(1, 2)

Modifíquelo para adaptarlo a su caso de uso.

Una aclaración de conceptos.

  • Despacho de funciones : hay múltiples funciones con el mismo nombre. ¿Cuál debería llamarse? dos estrategias
  • envío estático / en tiempo de compilación ( también conocido como "sobrecarga" ). decida qué función llamar en función del tipo de tiempo de compilación de los argumentos. En todos los lenguajes dinámicos, no existe un tipo de tiempo de compilación, por lo que la sobrecarga es imposible por definición
  • despacho dinámico / tiempo de ejecución : decida a qué función llamar en función del tipo de tiempo de ejecución de los argumentos. Esto es lo que hacen todos los lenguajes OOP: varias clases tienen los mismos métodos, y el lenguaje decide a cuál llamar según el tipo de self/thisargumento. Sin embargo, la mayoría de los idiomas solo lo hacen por el thisargumento solamente. El decorador anterior extiende la idea a múltiples parámetros.

Para aclarar, asuma un lenguaje estático y defina las funciones.

void f(Integer x):
    print('integer called')

void f(Float x):
    print('float called')

void f(Number x):
    print('number called')


Number x = new Integer('5')
f(x)
x = new Number('3.14')
f(x)

Con el despacho estático (sobrecarga) verá "número llamado" dos veces, porque xse ha declarado como Number, 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 reales xen el momento en que se llama a la función.

nota azul
fuente
Este ejemplo no ilustra de manera crucial qué método se solicitó xpara 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 a print('number called for Integer')etc.
smci