Supraîncărcarea funcțiilor Python (Programare, Python, Clasa, Oop, Supraîncărcare, Dispecerizare Multiplă)

Gloanțe a intrebat.

Știu că Python nu suportă supraîncărcarea metodelor, dar am dat peste o problemă pe care nu reușesc să o rezolv într-un mod frumos pythonic.

Fac un joc în care un personaj trebuie să tragă o varietate de gloanțe, dar cum pot scrie diferite funcții pentru a crea aceste gloanțe? De exemplu, să presupunem că am o funcție care creează un glonț care se deplasează din punctul A în punctul B cu o viteză dată. Aș scrie o funcție ca aceasta:

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

Dar vreau să scriu și alte funcții pentru a crea gloanțe, cum ar fi::

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

Și așa mai departe, cu multe variante. Există o modalitate mai bună de a face acest lucru fără a folosi atât de multe argumente de cuvinte cheie, deoarece devine cam urât rapid. Redenumirea fiecărei funcții este de asemenea destul de proastă pentru că veți obține fie add_bullet1, add_bullet2, sau add_bullet_with_really_long_name.

Pentru a aborda câteva răspunsuri:

  1. Nu, nu pot crea o ierarhie a claselor Bullet pentru că este prea lent. Codul real pentru gestionarea gloanțelor este în C, iar funcțiile mele sunt învelișuri în jurul API-ului C.

  2. Știu despre argumentele cu cuvinte cheie, dar verificarea pentru tot felul de combinații de parametri devine enervantă, dar argumentele implicite ajută la alocare, cum ar fi acceleration=0

Comentarii

  • Funcționează pentru un singur parametru, dar aici (pentru cei care vin aici de la un motor de căutare): docs.python.org/3/library/… –  > Por leewz.
  • acesta pare un loc bun pentru valorile implicite. puteți seta unele la None și doar să le verificați. impactul boolean suplimentar pare neglijabil –  > Por Andrew Scott Evans.
  • Trebuie să folosiți default value + if + else pentru a face același lucru ca în C++. Acesta este unul dintre foarte puținele lucruri pentru care C++ are o lizibilitate mai bună decât Python… –  > Por Deqing.
  • Nu înțeleg de ce kwargs nu este un răspuns valid. Spui că nu vrei să folosești multe argumente de cuvinte cheie pentru că devine urât repede… ei bine, asta este doar natura problemei. Dacă aveți multe argumente și este dezordonat pentru că aveți multe argumente, atunci la ce vă așteptați? Vreți să folosiți multe argumente fără să le specificați nicăieri???? Python nu este un cititor de gânduri. –  > Por Calcul.
  • Nu știm ce fel de obiecte script, curve sunt, dacă au un strămoș comun, ce metode suportă. Cu tipărirea de tip rață, depinde de dumneavoastră pentru proiectarea claselor să vă dați seama ce metode trebuie să suporte. Probabil că Script suportă un fel de callback bazat pe timestep (dar ce obiect ar trebui să returneze? poziția la acel timestep? traiectoria la acel timestep?). Se presupune că start, direction, speed și start, headto, spead, acceleration descriu ambele tipuri de traiectorii, dar, din nou, depinde de dumneavoastră să proiectați clasa de recepție pentru a ști cum să le descompuneți și să le procesați. –  > Por smci.
15 răspunsuri
Andriy Drozdyuk

Ceea ce cereți se numește expediere multiplă. Vedeți Julia care demonstrează diferite tipuri de dispecerizare.

Cu toate acestea, înainte de a analiza acest aspect, vom aborda mai întâi motivul pentru care supraîncărcarea nu este ceea ce vă doriți în Python.

De ce nu supraîncărcarea?

În primul rând, trebuie să înțelegem conceptul de supraîncărcare și de ce nu este aplicabil în Python.

Atunci când se lucrează cu limbaje care pot discrimina tipurile de date la momentul compilării, selecția dintre alternative poate avea loc la momentul compilării. Acțiunea de a crea astfel de funcții alternative pentru selectarea în timp de compilare este denumită de obicei supraîncărcare a unei funcții. (Wikipedia)

Python este un dinamic tipizat dinamic, astfel încât conceptul de supraîncărcare pur și simplu nu i se aplică. Cu toate acestea, nu totul este pierdut, deoarece putem crea astfel de funcții alternative în timpul execuției:

În limbajele de programare care amână identificarea tipului de date până la execuție, selecția între funcțiile alternative trebuie să aibă loc în timpul execuției, pe baza tipurilor determinate dinamic ale argumentelor funcției. Funcțiile ale căror implementări alternative sunt selectate în acest mod sunt denumite, în general, funcții alternative. multimethods. (Wikipedia)

Deci, ar trebui să putem face multimethods în Python – sau, așa cum se numește alternativ: multiple dispatch.

Expediere multiplă

Multimethods se mai numesc și multiple dispatch:

Dispecerizarea multiplă sau multimethods este o caracteristică a unor limbaje de programare orientate pe obiecte, în care o funcție sau o metodă poate fi distribuită dinamic pe baza tipului de execuție (dinamic) a mai mult de unul dintre argumentele sale. (Wikipedia)

Python nu suportă această funcție din start1, dar, din întâmplare, există un pachet Python excelent numit multipledispatch care face exact acest lucru.

Soluția

Iată cum am putea folosi multipledispatch2 pentru a vă implementa metodele:

>>> 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 suportă în prezent dispecerizarea unică2. Aveți grijă să nu folosiți multipledispatch într-un mediu cu mai multe fire de execuție, altfel veți obține un comportament ciudat.

Comentarii

  • Care este problema cu „multipledispatch” într-un mediu multi-threaded? Deoarece codul din partea serverului este de obicei în mediu multi-threaded! Încerc doar să aflu! –  > Por danzeer.
  • @danzeer Nu a fost thread-safe. Am văzut că argumentul era modificat de două fire diferite (adică valoarea lui speed s-ar putea schimba în mijlocul funcției, când un alt fir își stabilește propria valoare de speed)!!! Mi-a luat mult timp să îmi dau seama că biblioteca era cea vinovată. –  > Por Andriy Drozdyuk.
  • Avantajul multipledispatch în comparație cu single_dispatch este că funcționează și cu metode de clasă în python<3.8. –  > Por Dr_Zaszuś.
Escualo

Python acceptă „supraîncărcarea metodelor” așa cum o prezentați. De fapt, ceea ce tocmai ați descris este trivial de implementat în Python, în atât de multe moduri diferite, dar aș merge cu:

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

În codul de mai sus, default este o valoare implicită plauzibilă pentru aceste argumente, sau None. Apoi puteți apela metoda doar cu argumentele care vă interesează, iar Python va folosi valorile implicite.

De asemenea, ați putea face ceva de genul următor:

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

O altă alternativă este să agățați direct funcția dorită direct la clasă sau instanță:

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

O altă modalitate este de a utiliza un model de fabrică abstractă:

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 

Comentarii

    114

  • Toate acestea par a fi exemple de argumente variabile, mai degrabă decât de supraîncărcare. Deoarece supraîncărcarea vă permite să aveți aceeași funcție pentru diferite tipuri de argumente. de exemplu: sum(real_num1, real_num2) și sum(imaginary_num1, imaginary_num2) vor avea aceeași sintaxă de apelare, dar așteaptă de fapt 2 tipuri diferite ca intrări, iar implementarea trebuie să se schimbe și ea la nivel intern.  > Por Efren.
  • 18

  • Folosind răspunsul pe care l-ați alege, cum ați prezenta apelantului care argumente au sens împreună? Doar punând o grămadă de argumente, fiecare cu o valoare implicită, ar putea oferi aceeași funcționalitate, dar din punct de vedere al unei API este mult mai puțin elegant –  > Por Greg Ennis.
  • Niciuna dintre variantele de mai sus nu reprezintă o supraîncărcare, implementarea va trebui să verifice toate combinațiile de intrări de parametri (sau să ignore parametrii), cum ar fi: if sprite and script and not start and not direction and not speed... doar pentru a ști că se află într-o acțiune specifică. deoarece un apelant poate apela funcția furnizând toți parametrii disponibili. În timp ce supraîncărcarea definește pentru dvs. seturile exacte de parametri relevanți. –  > Por Roee Gavirel.
  • Este foarte supărător când oamenii spun că python suportă supraîncărcarea metodelor. Nu este așa. Faptul că ați pus „supraîncărcarea metodelor” între ghilimele indică faptul că sunteți conștient de acest fapt. Puteți obține o funcționalitate similară cu mai multe tehnici, cum ar fi cea menționată aici. Dar supraîncărcarea metodelor are o definiție foarte specifică. –  > Por Howard Swope.
  • Cred că ideea este că, deși supraîncărcarea metodelor nu este o caracteristică a programului python, mecanismele de mai sus pot fi utilizate pentru a obține un efect echivalent. –  > Por rawr rang.
Alexander Poluektov

Puteți utiliza soluția „roll-your-own” pentru supraîncărcarea funcțiilor. Aceasta este copiată din articolul lui Guido van Rossum despre multimethods (pentru că există o mică diferență între multimethods și supraîncărcarea în 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

Utilizarea ar fi

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

Limitările cele mai restrictive în acest moment sunt:

  • metodele nu sunt acceptate, ci doar funcțiile care nu sunt membri ai clasei;
  • moștenirea nu este gestionată;
  • kwargs nu sunt acceptate;
  • înregistrarea de noi funcții trebuie să se facă la momentul importului; lucrul nu este sigur pentru fire de execuție.

Comentarii

  • +1 pentru decoratori pentru extinderea limbajului în acest caz de utilizare. –  > Por Eloims.
  • +1 pentru că aceasta este o idee excelentă (și probabil ceea ce ar trebui să aleagă OP) — Nu am văzut niciodată o implementare multimethod în Python. –  > Por Escualo.
  • Cum se compară acest lucru cu biblioteca multipledispatch? –  > Por Make42.
Dave C

O opțiune posibilă este să folosiți modulul multipledispatch, așa cum este detaliat aici:http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

În loc de a face acest lucru:

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

Puteți face acest lucru:

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)

Cu utilizarea rezultată:

>>> add(1, 2)
3

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

Comentarii

  • De ce nu primește mai multe voturi? Presupun că din cauza lipsei de exemple… Am creat un răspuns cu un exemplu de implementare a unei soluții la problema lui OP cu multipledispatch pachet. –  > Por Andriy Drozdyuk.
skhalymon

În Python 3.4 PEP-0443. Funcții generice cu dispecerizare unică a fost adăugată.

Iată o scurtă descriere API din PEP.

Pentru a defini o funcție generică, decorați-o cu atributul @singledispatch decorator. Rețineți că expedierea se face în funcție de tipul primului argument. Creați-vă funcția în consecință:

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

Pentru a adăuga implementări supraîncărcate la funcție, utilizați atributul register() al funcției generice. Acesta este un decorator care ia un parametru de tip și decorează o funcție care implementează operația pentru tipul respectiv:

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

Comentarii

  • +1, dar un exemplu care să folosească dispecerizarea unică pentru a implementa cazul de utilizare din întrebare (cu alte cuvinte, cum să implementezi dispecerizarea multiplă pe lângă dispecerizarea unică) ar face acest răspuns mult mai bun. Dacă cineva nu se gândește cum să rezolve dispecerizarea multiplă cu dispecerizarea unică, acest răspuns ar putea părea irelevant sau inutil pentru persoanele care analizează probleme precum cea din întrebare. –  > Por mtraceur.
Josh Smeaton

Acest tip de comportament este rezolvat de obicei (în limbajele OOP) folosind polimorfism. Fiecare tip de glonț ar fi responsabil pentru a ști cum se deplasează. De exemplu:

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

Treceți cât mai multe argumente pentru c_function care există, iar apoi determină ce funcție c trebuie apelată pe baza valorilor din funcția c inițială. Așadar, Python ar trebui să apeleze întotdeauna doar o singură funcție c. Acea funcție c se uită la argumente și apoi poate delega la alte funcții c în mod corespunzător.

În esență, utilizați fiecare subclasă ca un container de date diferit, dar prin definirea tuturor argumentelor potențiale în clasa de bază, subclasele sunt libere să le ignore pe cele cu care nu fac nimic.

Atunci când apare un nou tip de glonț, puteți defini pur și simplu încă o proprietate în clasa de bază, puteți modifica o funcție python astfel încât să transmită proprietatea suplimentară și o funcție c_ care examinează argumentele și deleagă în mod corespunzător. Cred că nu sună prea rău.

Comentarii

  • Aceasta a fost abordarea mea inițială, dar din motive de performanță a trebuit să rescriu codul în C. –  > Por Bullets.
  • @Bullets, aș sugera că ar putea exista o serie de opțiuni diferite disponibile pentru a îmbunătăți performanța, mai degrabă decât să scrie o mulțime de funcții c care probabil nu vor face prea multe. De exemplu: crearea unei instanțe poate fi costisitoare, așa că mențineți un grup de obiecte. Deși spun acest lucru fără să știu ce vi s-a părut că este prea lent. Din interes, ce anume a fost lent la această abordare? Cu excepția cazului în care timp semnificativ va fi petrecut în partea C a graniței, nu pot să cred că Python (în sine) este adevărata problemă. –  > Por Josh Smeaton.
  • Poate că există și alte modalități de a îmbunătăți performanța, dar eu mă descurc mult mai bine cu C decât cu Python. Problema a fost calcularea mișcărilor gloanțelor și detectarea momentului în care acestea ies din limitele ecranului. Aveam o metodă de calcul al poziției glonțului pos+v*t și apoi compararea cu limitele ecranului if x > 800 și așa mai departe. Apelarea acestor funcții de câteva sute de ori pe cadru s-a dovedit a fi inacceptabil de lentă. A fost ceva de genul 40 fps la 100% cpu cu python pur la 60 fps cu 5%-10% când a fost făcut în C. –  > Por Gloanțe.
  • @Bullets, destul de corect atunci. Eu aș folosi în continuare abordarea pe care am ales-o pentru încapsularea datelor. Treceți o instanță de bullet la add_bulletși extrageți toate câmpurile de care aveți nevoie. Voi edita răspunsul meu. –  > Por Josh Smeaton.
  • @Bullets: Puteți combina funcțiile C și abordarea OOP sugerată de Josh folosind Cython. Acesta permite legarea timpurie, astfel încât nu ar trebui să existe o penalizare a vitezei. –  > Por jfs.
blue_note

Este imposibil prin definiție de a supraîncărca o funcție în python (citiți mai departe pentru detalii), dar puteți obține ceva similar cu un decorator simplu

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)

Îl puteți utiliza astfel

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

Modificați-l pentru a-l adapta la cazul dvs. de utilizare.

O clarificare a conceptelor

  • dispecerizarea funcțiilor: există mai multe funcții cu același nume. Care dintre ele ar trebui să fie apelată? două strategii
  • dispecerizare statică/în timp de compilare (aka. „supraîncărcare”). decideți ce funcție să apelați în funcție de timp de compilare tipului argumentelor. În toate limbajele dinamice, nu există un tip la compilare, deci supraîncărcarea este imposibilă prin definiție.
  • dispecerizare dinamică/în timp de execuție: decide ce funcție să apeleze pe baza timpul de execuție tipului de execuție al argumentelor. Acest lucru este ceea ce fac toate limbajele OOP: mai multe clase au aceleași metode, iar limbajul decide pe care dintre ele să o apeleze pe baza tipului de self/this argumentului. Cu toate acestea, majoritatea limbajelor fac acest lucru doar pentru this argument. Decoratorul de mai sus extinde ideea la mai mulți parametri.

Pentru a clarifica, să presupunem că definim, într-un limbaj static ipotetic, funcțiile

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)

Cu dispecerizarea statică (supraîncărcare), veți vedea „număr apelat” de două ori, deoarece x a fost declarat ca Number, iar asta este tot ceea ce interesează pe supraîncărcarea. În cazul dispecerizării dinamice, veți vedea „integer called, float called”, deoarece acestea sunt tipurile reale ale funcției x în momentul în care funcția este apelată.

Comentarii

  • Acest exemplu nu ilustrează în mod esențial care a fost apelată pe x în cazul dispecerizării dinamice, nici în ce ordine ambele metode au fost apelate pentru dispecerizarea statică. Vă recomandăm să modificați instrucțiunile de imprimare astfel print('number called for Integer') etc. –  > Por smci.
Nick Radford

De către trecerea de argetele cuvintelor cheie.

def add_bullet(**kwargs):
    #check for the arguments listed above and do the proper things

Comentarii

  • Re „trecând cuvântul cheie args”: Nu vrei să spui „a trece cuvântul cheie kwargs”? –  > Por Peter Mortensen.
Tim Ludwinski

Cred că cerința dvs. de bază este de a avea o sintaxă asemănătoare cu C/C++ în Python, cu cât mai puține bătăi de cap posibile. Deși mi-a plăcut răspunsul lui Alexander Poluektov, acesta nu funcționează pentru clase.

Următoarele ar trebui să funcționeze pentru clase. Funcționează făcând distincția în funcție de numărul de argumente care nu sunt cuvinte cheie (dar nu acceptă distincția în funcție de tip):

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

Și poate fi folosit pur și simplu astfel:

test = TestOverloading()

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

Ieșire:

Aceasta este supraîncărcarea 3
Sprite: Sunt un Sprite
Start: 0
Direcție: 0 Dreapta

Aceasta este supraîncărcarea 2
Sprite: Sunt un alt Sprite.
Script:
while x == True: print ‘hi’

Comentarii

  • Îmi place acest răspuns. Ar putea fi folosit și pentru a detecta tipurile și apoi pentru a face supraîncărcări pe baza numărului de arg și a tipurilor –  > Por Andrew.
  • Cel mai mare dezavantaj este că parserul nu mai poate „vedea” sau indica numele parametrilor și nici tipurile pe care metoda le acceptă. Aceasta face necesară utilizarea unui docstring, altfel cineva care utilizează doar codul dvs. va trebui să îl citească o singură dată. –  > Por Conrad B.
Richard Whitehead

The @overload a fost adăugat cu indicii de tip (PEP 484).

Deși acest lucru nu schimbă comportamentul lui Python, facilitează înțelegerea a ceea ce se întâmplă și detectarea erorilor de către mypy.

A se vedea: Indicații de tip și PEP 484

Comentarii

  • Puteți adăuga câteva exemple? –  > Por gerrit.
Ignacio Vazquez-Abrams

Fie utilizați mai multe argumente de cuvinte cheie în definiție, fie creați un Bullet ierarhie ale cărei instanțe sunt transmise funcției.

Comentarii

  • Aveam de gând să sugerez a doua abordare: faceți câteva clase BulletParams… pentru a specifica detaliile glonțului. –  > Por John Zwinck.
  • Puteți să detaliați acest lucru? Am încercat să creez o ierarhie de clase cu gloanțe diferite, dar acest lucru nu funcționează, deoarece Python este prea lent. Nu poate calcula mișcările numărului necesar de gloanțe suficient de repede, așa că a trebuit să scriu acea parte în C. Toate variantele add_bullet nu fac decât să apeleze funcția C corespunzătoare. –  > Por Bullets.
martineau

Cred că o Bullet ierarhie de clase cu polimorfismul asociat este calea de urmat. Puteți supraîncărca în mod eficient constructorul clasei de bază prin utilizarea unei metaclase, astfel încât apelarea clasei de bază să aibă ca rezultat crearea obiectului subclasei corespunzătoare. Mai jos sunt câteva exemple de cod pentru a ilustra esența a ceea ce vreau să spun.

Actualizat

Codul a fost modificat pentru a funcționa atât în Python 2, cât și în Python 3, pentru a-l păstra relevant. Acest lucru a fost făcut într-un mod care evită utilizarea sintaxei explicite a metaclasei Python, care variază între cele două versiuni.

Pentru a realiza acest obiectiv, a fost adăugat un BulletMetaBase instanță a clasei BulletMeta este creată prin apelarea explicită a metaclasei la crearea clasei Bullet baseclass (mai degrabă decât să se utilizeze __metaclass__= class sau prin intermediul unui atribut metaclass în funcție de versiunea 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')

Ieșire:

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

Comentarii

  • Hmm, aceasta este încă doar o modalitate sofisticată de a numi funcțiile ca add_bullet1, add_bullet2 și așa mai departe. –  > Por Bullets.
  • @Bullets: Poate că este, sau poate că este doar un mod ușor elaborat de a crea o funcție fabrică. Un lucru plăcut este că suportă o ierarhie de Bullet subclase fără a fi nevoie să modificați clasa de bază sau funcția fabrică de fiecare dată când adăugați un alt subtip. (Desigur, dacă folosiți C în loc de C++, cred că nu aveți clase.) Ați putea, de asemenea, să creați o metaclasă mai inteligentă care să își dea seama singură ce subclasă să creeze în funcție de tipul și/sau numărul de argumente transmise (așa cum face C++ pentru a sprijini supraîncărcarea). –  > Por martineau.
  • Această idee de moștenire ar fi și prima mea opțiune. –  > Por Daniel Möller.
Vlad Bezden

A fost adăugat Python 3.8 functools.singledispatchmethod

Transformă o metodă într-o funcție generică single-dispatch.

Pentru a defini o metodă generică, decorați-o cu decoratorul @singledispatchmethod. Rețineți că expedierea are loc în funcție de tipul primului argument non-self sau non-cls, creați-vă funcția în consecință:

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

Ieșire

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

@singledispatchmethod suportă înglobarea cu alți decoratori, cum ar fi @classmethod. Rețineți că, pentru a permite dispatcher.register, singledispatchmethod trebuie să fie cel mai exterior decorator. Iată clasa Negator, cu metodele neg legate de clasă:

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

Output:

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

Același model poate fi utilizat pentru alți decoratori similari: staticmethod, abstractmethod și alții.

Rafe Kettler

Utilizați argumente de tip cuvânt cheie cu valori implicite. De ex.

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

În cazul unui glonț drept față de un glonț curbat, aș adăuga două funcții: add_bullet_straight și add_bullet_curved.

shashankS

Supraîncărcarea metodelor este dificilă în Python. Cu toate acestea, ar putea exista utilizarea de a trece dict, listă sau variabile primitive.

Am încercat ceva pentru cazurile mele de utilizare, iar acest lucru ar putea ajuta aici pentru a înțelege oamenii să supraîncarce metodele.

Să luăm exemplul tău:

O metodă de supraîncărcare a clasei care apelează metodele din clase diferite.

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

Trece argumentele din clasa la distanță:

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

Sau

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

Astfel, manipularea se realizează pentru liste, dicționare sau variabile primitive prin supraîncărcarea metodelor.

Încercați să vă încercați codul.