Sovraccarico della funzione Python


213

So che Python non supporta il sovraccarico del metodo, ma ho riscontrato un problema che non riesco a risolvere in un buon modo Pythonic.

Sto realizzando un gioco in cui un personaggio deve sparare una varietà di proiettili, ma come faccio a scrivere diverse funzioni per creare questi proiettili? Ad esempio supponiamo che io abbia una funzione che crea un proiettile che viaggia dal punto A a B con una data velocità. Vorrei scrivere una funzione come questa:

    def add_bullet(sprite, start, headto, speed):
        ... Code ...

Ma voglio scrivere altre funzioni per la creazione di proiettili come:

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

E così via con molte varianti. C'è un modo migliore per farlo senza usare così tanti argomenti di parole chiave che lo fanno diventare piuttosto brutto in fretta. Rinominare ogni funzione è piuttosto male anche perché si ottiene uno add_bullet1, add_bullet2o add_bullet_with_really_long_name.

Per rispondere ad alcune risposte:

  1. No, non riesco a creare una gerarchia di classi Bullet perché è troppo lenta. Il codice effettivo per la gestione dei proiettili è in C e le mie funzioni sono involucri attorno all'API C.

  2. Conosco gli argomenti delle parole chiave, ma verificare ogni tipo di combinazione di parametri sta diventando fastidioso, ma gli argomenti predefiniti aiutano a assegnare simili acceleration=0


5
Funziona con un solo parametro, ma qui (per le persone che arrivano qui da un motore di ricerca): docs.python.org/3/library/…
leewz

1
questo sembra un buon posto per i valori predefiniti. puoi impostarne alcuni su Nessuno e controllarli. l'impatto extra booleano sembra trascurabile
Andrew Scott Evans

Devono usare default value + if + elseper fare lo stesso di C ++. Questa è una delle poche cose in cui C ++ ha una migliore leggibilità di Python ...
Deqing

Sono confuso sul perché kwargs non sia una risposta valida. Dici che non vuoi usare molti argomenti di parole chiave perché diventa brutto veloce ... beh, questa è solo la natura del problema. Se hai molti argomenti ed è disordinato perché hai molti argomenti di quello che ti aspettavi? Vuoi usare molti argomenti senza specificarli ovunque ??? Python non è un lettore di mente.
Calcolo

Non sappiamo che tipo di oggetti script, curvesiano, hanno un antenato comune, quali metodi supportano. Con la tipizzazione anatra, spetta a te per il design di classe capire quali metodi devono supportare. Presumibilmente Scriptsupporta una sorta di callback basato su timestep (ma quale oggetto dovrebbe restituire? La posizione in quel timestep? La traiettoria in quel timestep?). Presumibilmente start, direction, speeded start, headto, spead, accelerationentrambi descrivono i tipi di traiettorie, ma di nuovo tocca a te progettare la classe ricevente per sapere come decomprimerli ed elaborarli.
smci,

Risposte:


144

Quello che stai chiedendo si chiama spedizione multipla . Consulta gli esempi di lingua Julia che mostrano diversi tipi di invii.

Tuttavia, prima di guardarlo, affronteremo innanzitutto perché il sovraccarico non è proprio quello che vuoi in Python.

Perché non sovraccaricare?

Innanzitutto, è necessario comprendere il concetto di sovraccarico e perché non è applicabile a Python.

Quando si lavora con lingue che possono discriminare i tipi di dati in fase di compilazione, la selezione tra le alternative può avvenire in fase di compilazione. L'atto di creare tali funzioni alternative per la selezione del tempo di compilazione viene di solito definito sovraccarico di una funzione. ( Wikipedia )

Python è una dinamica linguaggio tipizzato in , quindi il concetto di sovraccarico semplicemente non si applica ad esso. Tuttavia, non tutto è perduto, poiché possiamo creare tali funzioni alternative in fase di esecuzione:

Nei linguaggi di programmazione che differiscono l'identificazione del tipo di dati fino al runtime, la selezione tra funzioni alternative deve avvenire al runtime, in base ai tipi di argomenti di funzione determinati dinamicamente. Le funzioni le cui implementazioni alternative sono selezionate in questo modo vengono generalmente definite multimetodi . ( Wikipedia )

Quindi dovremmo essere in grado di fare multimetodi in Python o, come viene chiamato in alternativa: invio multiplo .

Invio multiplo

I multimetodi sono anche chiamati invio multiplo :

Spedizione multipla o multimetodi è la caratteristica di alcuni linguaggi di programmazione orientati agli oggetti in cui una funzione o un metodo possono essere inviati in modo dinamico in base al tipo di runtime (dinamico) di più di uno dei suoi argomenti. ( Wikipedia )

Pitone non supporta questo fuori dalla scatola 1 , ma, come accade, v'è un eccellente pacchetto python chiamato multipledispatch che fa esattamente questo.

Soluzione

Ecco come potremmo usare il pacchetto multipledispatch 2 per implementare i tuoi metodi:

>>> 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 attualmente supporta l' invio singolo
2. Fare attenzione a non utilizzare il multipledispatch in un ambiente multi-thread o si otterranno comportamenti strani.


6
Qual è il problema con "multipledispatch" in un ambiente multi-thread? Dato che il codice lato server è generalmente in un ambiente multi-thread! Sto solo cercando di scavare!
danzeer,

7
@danzeer Non era thread-safe. Ho visto l'argomento essere modificato da due thread diversi (ovvero il valore di speedpotrebbe cambiare nel mezzo della funzione quando un altro thread imposta il proprio valore di speed) !!! Mi ci è voluto molto tempo per capire che era la biblioteca il colpevole.
Andriy Drozdyuk,

108

Python supporta il "metodo di overload" mentre lo presenti. In effetti, ciò che hai appena descritto è banale da implementare in Python, in molti modi diversi, ma vorrei andare con:

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

Nel codice precedente, defaultè un valore predefinito plausibile per tali argomenti, oppure None. Puoi quindi chiamare il metodo con solo gli argomenti che ti interessano e Python utilizzerà i valori predefiniti.

Potresti anche fare qualcosa del genere:

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

Un'altra alternativa è quella di collegare direttamente la funzione desiderata direttamente alla classe o all'istanza:

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

Ancora un altro modo è quello di utilizzare un modello di fabbrica astratto:

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 

107
Tutti questi sembrano esempi di argomenti variabili, piuttosto che un sovraccarico. Poiché il sovraccarico consente di avere la stessa funzione, per diversi tipi di argomenti. es: sum (real_num1, real_num2) e sum (imaginary_num1, imaginary_num2) avranno entrambi la stessa sintassi di chiamata, ma in realtà si aspettano 2 tipi diversi come input e l'implementazione deve cambiare anche internamente
Efren,

17
Usando la risposta con cui andresti, come presenteresti al chiamante quali argomenti hanno senso insieme? Basta mettere un sacco di argomenti ciascuno con un valore predefinito può fornire la stessa funzionalità ma in termini di API è molto meno elegante
Greg Ennis,

6
Non di quanto sopra è sovraccarico, l'implementazione dovrà controllare tutte le combinazioni di input di parametri (o ignorare i parametri) come: if sprite and script and not start and not direction and not speed...solo per sapere che è in un'azione specifica. perché un chiamante può chiamare la funzione fornendo tutti i parametri disponibili. Mentre il sovraccarico definisce per te gli esatti set di parametri rilevanti.
Roee Gavirel,

5
È molto sconvolgente quando le persone affermano che Python supporta il sovraccarico del metodo. Non è così. Il fatto che tu abbia inserito il "metodo di sovraccarico" tra virgolette indica che sei a conoscenza di questo fatto. Puoi ottenere funzionalità simili con diverse tecniche, come quella menzionata qui. Ma il sovraccarico del metodo ha una definizione molto specifica.
Howard Swope,

Penso che il punto previsto sia che il sovraccarico del metodo non è una caratteristica di Python, i meccanismi di cui sopra possono essere utilizzati per ottenere l'effetto equivalente.
Rawr suonò il

93

È possibile utilizzare la soluzione "roll-your-own" per il sovraccarico delle funzioni. Questo è copiato dall'articolo di Guido van Rossum sui multimetodi (perché c'è poca differenza tra mm e sovraccarico in pitone):

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

L'uso sarebbe

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

Le limitazioni più restrittive al momento sono:

  • i metodi non sono supportati, solo le funzioni che non sono membri della classe;
  • l'eredità non viene gestita;
  • i kwarg non sono supportati;
  • la registrazione di nuove funzioni dovrebbe essere eseguita al momento dell'importazione, cosa che non è thread-safe

6
+1 per i decoratori per l'estensione della lingua in questo caso d'uso.
Eloims

1
+1 perché questa è una grande idea (e probabilmente con cosa dovrebbe funzionare l'OP) --- Non avevo mai visto un'implementazione multimetodo in Python.
Escualo,

39

Un'opzione possibile è utilizzare il modulo multipledispatch come dettagliato qui: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Invece di fare questo:

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

Puoi farlo:

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 l'utilizzo risultante:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'

4
Perché questo non ottiene più voti? Sto indovinando a causa della mancanza di esempi ... Ho creato una risposta con un esempio di come implementare una soluzione al problema di OP con il pacchetto multipledispatch .
Andriy Drozdyuk,

19

In Python 3.4 è stato aggiunto PEP-0443. Funzioni generiche a spedizione singola .

Ecco una breve descrizione dell'API di PEP.

Per definire una funzione generica, decorala con il decoratore @singledispatch. Si noti che l'invio avviene sul tipo del primo argomento. Crea la tua funzione di conseguenza:

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

Per aggiungere implementazioni sovraccariche alla funzione, utilizzare l'attributo register () della funzione generica. Questo è un decoratore, prendendo un parametro di tipo e decorando una funzione che implementa l'operazione per quel 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)

11

Questo tipo di comportamento è in genere risolto (nei linguaggi OOP) usando il polimorfismo. Ogni tipo di proiettile sarebbe responsabile di sapere come viaggia. Per esempio:

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

Passa quanti più argomenti alla funzione c esistente, quindi fai il lavoro per determinare quale funzione c chiamare in base ai valori nella funzione c iniziale. Quindi, Python dovrebbe sempre e solo chiamare la funzione one c. Quella funzione c esamina gli argomenti e quindi può delegare ad altre funzioni c in modo appropriato.

In pratica stai solo usando ciascuna sottoclasse come un contenitore di dati diverso, ma definendo tutti i potenziali argomenti sulla classe base, le sottoclassi sono libere di ignorare quelle con cui non fanno nulla.

Quando arriva un nuovo tipo di proiettile, puoi semplicemente definire un'altra proprietà sulla base, cambiare la funzione one python in modo che passi la proprietà extra e quella c_funzione che esamina gli argomenti e i delegati in modo appropriato. Non suona troppo male, immagino.


1
Quello era il mio approccio iniziale, ma per motivi di prestazioni ho dovuto riscrivere quel codice in C.
Bullets,

@Bullets, suggerirei che potrebbero esserci diverse opzioni disponibili per migliorare le prestazioni piuttosto che scrivere un sacco di funzioni c che probabilmente non faranno molto. Ad esempio: la creazione di un'istanza può essere costosa, quindi mantenere un pool di oggetti. Anche se lo dico senza sapere cosa hai trovato troppo lento. Per interesse, cosa è stato esattamente lento in questo approccio? A meno che non si trascorra tempo significativo nella parte C del confine, non posso pensare che Python (stesso) sia il vero problema.
Josh Smeaton,

Forse ci sono altri modi per migliorare le prestazioni, ma sto molto meglio con C che con Python. Il problema era calcolare i movimenti dei proiettili e rilevare quando uscivano dai limiti dello schermo. Avevo un metodo per calcolare la posizione del proiettile pos+v*te quindi confrontarlo con i confini dello schermo if x > 800e così via. Chiamare queste funzioni diverse centinaia di volte per frame si è rivelato inaccettabilmente lento. Era qualcosa come 40 fps al 100% cpu con pitone puro a 60 fps con il 5% -10% quando fatto in C.
Bullets

@Bullets, abbastanza giusto allora. Utilizzerei ancora l'approccio seguito per l'incapsulamento dei dati. Passa un'istanza di bullet add_bulleted estrai tutti i campi di cui hai bisogno. Modificherò la mia risposta.
Josh Smeaton,

@Bullets: puoi combinare le tue funzioni C e l'approccio OOP suggerito da Josh usando Cython . Consente l'associazione anticipata, quindi non dovrebbe esserci una penalità.
jfs,


4

Utilizzare più argomenti di parole chiave nella definizione o creare una Bulletgerarchia le cui istanze vengono passate alla funzione.


Stavo per suggerire il secondo approccio: creare alcune classi BulletParams ... per specificare i dettagli del proiettile.
John Zwinck,

Puoi approfondire questo? Ho provato a creare una gerarchia di classi con proiettili diversi ma questo non funziona, perché Python è troppo lento. Non è in grado di calcolare i movimenti del numero richiesto di proiettili abbastanza velocemente, quindi ho dovuto scrivere quella parte in C. Tutte le varianti add_bullet chiamano semplicemente la funzione C corrispondente.
Proiettili

4

Penso che il tuo requisito di base sia avere una sintassi come C / C ++ in Python con il minor mal di testa possibile. Sebbene mi sia piaciuta la risposta di Alexander Poluektov, non funziona per le lezioni.

Quanto segue dovrebbe funzionare per le lezioni. Funziona distinguendo per il numero di argomenti non parole chiave (ma non supporta la distinzione per 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

E può essere usato semplicemente in questo modo:

test = TestOverloading()

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

Produzione:

Questo è sovraccarico 3
Sprite: I'm a Sprite
Start: 0
Direzione: Right

Questo è sovraccarico 2
Sprite: sono un altro Sprite
Script:
mentre x == True: stampa 'ciao'


4

Il @overloaddecoratore è stato aggiunto con suggerimenti sul tipo (PEP 484). Anche se questo non cambia il comportamento di Python, rende più facile capire cosa sta succedendo e per mypy rilevare errori.
Vedere: Suggerimenti sul tipo e PEP 484


Puoi aggiungere qualche esempio?
Gerrit,

3

Penso che una Bulletgerarchia di classe con il polimorfismo associato sia la strada da percorrere. È possibile sovraccaricare efficacemente il costruttore della classe base utilizzando una metaclasse in modo che la chiamata alla classe base comporti la creazione dell'oggetto della sottoclasse appropriata. Di seguito è riportato un codice di esempio per illustrare l'essenza di ciò che intendo.

aggiornato

Il codice è stato modificato per essere eseguito con Python 2 e 3 per mantenerlo rilevante. Ciò è stato fatto in modo da evitare l'uso della sintassi di metaclasse esplicita di Python, che varia tra le due versioni.

Per raggiungere questo obiettivo, viene creata BulletMetaBaseun'istanza della BulletMetaclasse chiamando esplicitamente la metaclasse durante la creazione della Bulletbaseclass (anziché utilizzare l' __metaclass__=attributo class o tramite un metaclassargomento della parola chiave a seconda della versione di 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')

Produzione:

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

Hmm, questo è ancora solo un modo elegante per nominare le funzioni come add_bullet1, add_bullet2 e così via.
Proiettili

1
@Bullets: Forse lo è, o forse è solo un modo leggermente elaborato per creare una funzione di fabbrica. Una cosa interessante è che supporta una gerarchia di Bulletsottoclassi senza dover modificare la classe di base o la funzione di fabbrica ogni volta che aggiungi un altro sottotipo. (Ovviamente, se stai usando C piuttosto che C ++, suppongo che tu non abbia classi.) Potresti anche creare una metaclasse più intelligente che ha capito da sola quale sottoclasse creare in base al tipo e / o numero degli argomenti passati (come fa C ++ per supportare il sovraccarico).
martineau,

1
Questa idea di eredità sarebbe anche la mia prima opzione.
Daniel Möller,

3

Python 3.8 ha aggiunto functools.singledispatchmethod

Trasforma un metodo in una funzione generica a spedizione singola.

Per definire un metodo generico, decoralo con il decoratore @singledispatchmethod. Nota che l'invio avviene sul tipo del primo argomento non-self o non-cls, crea la tua funzione di conseguenza:

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=}")

Produzione

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

@singledispatchmethod supporta la nidificazione con altri decoratori come @classmethod. Tieni presente che per consentire dispatcher.register, il metodo singledispatch deve essere il decoratore più esterno. Ecco la classe Negator con i metodi neg associati alla classe:

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=}")

Produzione:

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

Lo stesso motivo può essere utilizzato per altri decoratori simili: metodo statico, metodo astratto e altri.


2

Usa argomenti di parole chiave con valori predefiniti. Per esempio

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

Nel caso di un proiettile dritto contro un proiettile curvo, aggiungerei due funzioni: add_bullet_straighte add_bullet_curved.


2

i metodi di sovraccarico sono complicati in Python. Tuttavia, potrebbe essere utile passare le variabili dict, list o primitive.

Ho provato qualcosa per i miei casi d'uso, questo potrebbe aiutare qui a capire le persone a sovraccaricare i metodi.

Facciamo il tuo esempio:

un metodo di sovraccarico di classe con chiamata a metodi di classe diversa.

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

passare gli argomenti dalla classe 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']}

Pertanto, la gestione viene ottenuta per elenco, dizionario o variabili primitive dal sovraccarico del metodo.

provalo per i tuoi codici.


2

Solo un semplice decoratore

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)

Puoi usarlo in questo modo

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

Modificalo per adattarlo al tuo caso d'uso.

Un chiarimento di concetti

  • invio funzioni : esistono più funzioni con lo stesso nome. Quale dovrebbe essere chiamato? due strategie
  • invio in tempo statico / di compilazione ( alias "sovraccarico" ). decidere quale funzione chiamare in base al tipo di argomenti in fase di compilazione . In tutti i linguaggi dinamici, non esiste un tipo di tempo di compilazione, quindi il sovraccarico è impossibile per definizione
  • dispatch dinamico / runtime : decide quale funzione chiamare in base al tipo di runtime degli argomenti. Questo è ciò che fanno tutte le lingue OOP: più classi hanno gli stessi metodi e la lingua decide quale chiamare in base al tipo di self/thisargomento. Tuttavia, la maggior parte delle lingue lo fa solo per l' thisargomento. Il decoratore di cui sopra estende l'idea a più parametri.

Per chiarire, assumere un linguaggio statico e definire le funzioni

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 l'invio statico (sovraccarico) vedrai il "numero chiamato" due volte, perché xè stato dichiarato come Number, e questo è tutto ciò che preoccupa il sovraccarico. Con il dispacciamento dinamico vedrai "intero chiamato, float chiamato", perché quelli sono i tipi effettivi di xquando viene chiamata la funzione.


Questo esempio cruciale non illustra quale metodo è stato richiesto xper l'invio dinamico, né in quale ordine sono stati chiamati entrambi i metodi per l'invio statico. Consiglia di modificare le dichiarazioni di stampa su print('number called for Integer')ecc.
smci
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.