Python-functie Overbelasting

Ik weet dat Python geen ondersteuning biedt voor de methode, maar ik ben een probleem tegengekomen dat ik niet op een mooie pythonische manier kan oplossen.

Ik maak een spel waarbij een personage een verscheidenheid aan kogels nodig heeft, maar hoe schrijf ik verschillende functies voor het maken van deze kogels? Stel dat ik bijvoorbeeld een functie heb die een kogel reist van punt A naar B met een bepaalde snelheid. Ik zou een functie als volgt schrijven:

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

Maar ik wil andere functies schrijven voor het maken van kogels zoals:

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

enzovoort met vele variaties. Is er een betere manier om het te doen zonder zoveel zoekwoordargumenten te gebruiken, want het wordt een beetje lelijk snel. Het hernoemen van elke functie is ook behoorlijk slecht, omdat je add_bullet1, add_bullet2of add_bullet_with_really_long_name.

\

om enkele antwoorden aan te pakken:

  1. Nee, ik kan geen kogelklasse hiërarchie maken omdat dat te traag is. De eigenlijke code voor het beheren van kogels is in C en mijn functies zijn wrappers rond C API.

  2. Ik weet over de trefwoordargumenten, maar controleren op allerlei combinaties van parameters wordt vervelend, maar standaardargumenten helpen toe als acceleration=0


1, Autoriteit 100%

Wat u vraagt, wordt meervoudige verzendinggenoemd. Bekijk de Juliataalvoorbeelden die verschillende soorten verzendingen demonstreren.

Voordat we daar echter naar kijken, zullen we eerst bespreken waarom overbelastingniet echt is wat je wilt in Python.

Waarom niet overladen?

Eerst moet men het concept van overbelasting begrijpen en waarom het niet van toepassing is op Python.

Bij het werken met talen die gegevenstypen kunnen onderscheiden op
compileren, kan het selecteren van een van de alternatieven plaatsvinden op:
compileertijd. De handeling van het creëren van dergelijke alternatieve functies voor
compile-time selectie wordt meestal aangeduid als overbelasting a
functie. (Wikipedia)

Python is een dynamischgetypte taal, dus het concept van overbelasting is er gewoon niet op van toepassing. Alles is echter niet verloren, aangezien we tijdens runtime dergelijke alternatieve functieskunnen maken:

In programmeertalen die identificatie van gegevenstypes uitstellen tot
runtime de selectie uit alternatieve
functies moeten tijdens runtime plaatsvinden, op basis van de dynamisch bepaalde
soorten functieargumenten. Functies waarvan alternatief
implementaties die op deze manier worden geselecteerd, worden het meest genoemd
algemeen als multimethoden. (Wikipedia)

Dus we zouden multimethodenin Python moeten kunnen doen, of, zoals het ook wel wordt genoemd: multiple dispatch.

Meerdere verzending

De multimethoden worden ook wel multiple dispatchgenoemd:

Meerdere verzending of meerdere methoden is het kenmerk van sommige
objectgeoriënteerde programmeertalen waarin een functie of methode
kan dynamisch worden verzonden op basis van het runtime (dynamische) type van
meer dan één van zijn argumenten. (Wikipedia)

Python ondersteunt dit niet standaard1, maar toevallig is er een uitstekend Python-pakket genaamd multipledispatchdie precies dat doet.

Oplossing

Hier is hoe we het multipledispatch2-pakket kunnen gebruiken om implementeer uw methoden:

>>> 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**2
>>> 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 ondersteunt momenteel enkele verzending
2. Zorg ervoor dat u multipledispatchniet gebruikt in een omgeving met meerdere threads, anders krijgt u raar gedrag.


Antwoord 2, autoriteit 59%

Python ondersteunt “method overloading” zoals je het presenteert. In feite is wat je zojuist beschrijft triviaal om in Python te implementeren, op zoveel verschillende manieren, maar ik zou kiezen voor:

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

In de bovenstaande code is defaulteen plausibele standaardwaarde voor die argumenten, of None. Je kunt dan de methode aanroepen met alleen de argumenten waarin je geïnteresseerd bent, en Python zal de standaardwaarden gebruiken.

Je zou ook zoiets als dit kunnen doen:

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

Een ander alternatief is om de gewenste functie direct aan de klasse of instantie te haken:

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

Nog een andere manier is om een abstract fabriekspatroon te gebruiken:

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 

Antwoord 3, autoriteit 52%

U kunt de “roll-your-own”-oplossing gebruiken voor overbelasting van functies. Deze is gekopieerd uit Guido van Rossum’s artikelover multimethods (omdat er is weinig verschil tussen multimethoden en overbelasting in 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

Het gebruik zou zijn

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

De meeste restrictieve beperkingen op dit moment zijn:

  • methoden worden niet ondersteund, alleen functies die niet klasse leden zijn;
  • de erfenis wordt niet behandeld;
  • kwargs worden niet ondersteund;
  • het registreren van nieuwe functies moeten worden gedaan op invoer eenmalige zaak is niet thread-safe

4, Autoriteit 26%

Een mogelijke optie is om de multipledispatch module te gebruiken zoals hier beschreven:
http://matthewrocklin.com/blog/work/2014/02/25 / Multiple-Dispatch

In plaats van dit te doen:

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

U kunt dit doen:

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)

Met het verkregen gebruik:

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

5, Autoriteit 14%

3.4 Python PEP-0443. Single-verzending generieke functies werd toegevoegd.

Hier volgt een korte API beschrijving van PEP.

Om een algemene functie te definiëren, versier deze met de @singledispatchdecorateur. Merk op dat de verzending gebeurt op het type van het eerste argument. Creëer uw functie dienovereenkomstig:

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

Gebruik het kenmerk register() van de generieke functie om overbelaste implementaties aan de functie toe te voegen. Dit is een decorateur, die een typeparameter neemt en een functie versiert die de bewerking voor dat type implementeert:

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

Antwoord 6, autoriteit 6%

Dit soort gedrag wordt meestal opgelost (in OOP-talen) met polymorfisme. Elk type kogel zou verantwoordelijk zijn om te weten hoe het reist. Bijvoorbeeld:

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

Geef zoveel argumenten door aan de c_functiondie er zijn, en bepaal vervolgens welke c-functie moet worden aangeroepen op basis van de waarden in de initiële c-functie. Python zou dus alleen de ene c-functie moeten aanroepen. Die ene c-functie kijkt naar de argumenten en kan vervolgens op gepaste wijze delegeren aan andere c-functies.

Je gebruikt in wezen elke subklasse als een andere gegevenscontainer, maar door alle mogelijke argumenten voor de basisklasse te definiëren, zijn de subklassen vrij om degene te negeren waarmee ze niets doen.

Als er een nieuw type bullet komt, kun je eenvoudig nog een eigenschap op de basis definiëren, de ene python-functie wijzigen zodat deze de extra eigenschap doorgeeft, en de ene c_function die de argumenten onderzoekt en op de juiste manier delegeert. Het klinkt niet slecht, denk ik.


Antwoord 7, autoriteit 5%

Het is per definitie onmogelijkom een functie in python te overbelasten (lees verder voor details), maar je kunt iets soortgelijks bereiken met een eenvoudige decorateur

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)

Je kunt het zo gebruiken

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

Pas het aan om het aan uw gebruik aan te passen.

Een verduidelijking van concepten

  • functieverzending: er zijn meerdere functies met dezelfde naam. Welke moet heten? twee strategieën
  • statische verzending/compilatietijd(ook bekend als “overloading”). bepaal welke functie moet worden aangeroepen op basis van het compileertijd-type van de argumenten. In alle dynamische talen is er geen compileertype, dus overbelasting is per definitie onmogelijk
  • dynamische/runtime verzending: bepaal welke functie moet worden aangeroepen op basis van het runtime-type van de argumenten. Dit is wat alle OOP-talen doen: meerdere klassen hebben dezelfde methoden en de taal beslist welke wordt aangeroepen op basis van het type self/this-argument. De meeste talen doen het echter alleen voor het argument this. De bovenstaande decorateur breidt het idee uit naar meerdere parameters.

Om op te helderen, neem aan dat we, in een hypothetische statische taal, de functies definiëren

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)

Met statische verzending (overbelasting) ziet u “Nummer genaamd” tweemaal, omdat xis gedeclareerd als Number, en dat is alle overbelasting om. Met Dynamic Dispatch ziet u “Integer genaamd, float genaamd”, omdat dat de werkelijke typen xzijn op het moment dat de functie wordt genoemd.


8, Autoriteit 4%

De @overloadDecorator is toegevoegd met Type-hints (PEP 484).

Hoewel dit het gedrag van Python niet verandert, maakt het het wel gemakkelijker om te begrijpen wat er aan de hand is, en voor MyPy om fouten te detecteren.

Zie: Type hints en PEP 484


9, Autoriteit 4%

door Trefwoord doorgeven args .

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

10, Autoriteit 3%

Ik denk dat uw basisvereiste is om een ​​C / C++ – zoals syntaxis in Python te hebben met de minste hoofdpijn mogelijk. Hoewel ik Alexander Poluebtov’s antwoord niet werkt voor de lessen.

Het volgende zou voor klassen moeten werken. Het werkt door te onderscheiden door het aantal niet-trefwoordargumenten (maar het ondersteunt geen onderscheidend type):

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

En het kan eenvoudig worden gebruikt:

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

Uitgang:

Dit is overbelasting 3
Sprite: Ik ben een sprite
Begin: 0
Richting: Rechts

Dit is overbelasting 2
Sprite: Ik ben weer een sprite
Script:
terwijl x == true: print ‘hi’


11, Autoriteit 2%

Gebruik ofwel gebruik van meerdere trefwoordargumenten in de definitie of maak een Bullethiërarchie wiens instanties naar de functie worden doorgegeven.


Antwoord 12, autoriteit 2%

Python 3.8 heeft functools.singledispatchmethod

Zet een methode om in een generieke functie met één verzending.

Om een generieke methode te definiëren, versier deze met de @singledispatchmethode
decorateur. Merk op dat de verzending gebeurt op het type van de eerste
non-self of non-cls argument, creëer je functie dienovereenkomstig:

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

Uitvoer

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

@singledispatchmethod ondersteunt nesten met andere decorateurs zoals
@klasmethode. Merk op dat om dispatcher.register toe te staan,
singledispatchmethode moet de buitenste decorateur zijn. Hier is de
Negator-klasse waarbij de neg-methoden klassegebonden zijn:

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

Uitvoer:

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

Hetzelfde patroon kan worden gebruikt voor andere soortgelijke decorateurs:
staticmethod, abstractmethod en anderen.


Antwoord 13

Gebruik trefwoordargumenten met standaardwaarden. Bijv.

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

In het geval van een rechte kogel versus een gebogen kogel, zou ik twee functies toevoegen: add_bullet_straighten add_bullet_curved.


Antwoord 14

Het overbelasten van methoden is lastig in Python. Er kan echter gebruik worden gemaakt van het doorgeven van het dictaat, de lijst of primitieve variabelen.

Ik heb iets geprobeerd voor mijn gebruiksscenario’s, en dit zou hier kunnen helpen om te begrijpen dat mensen de methoden overbelasten.

Laten we uw voorbeeld nemen:

Een klasse-overbelastingsmethode waarbij de methoden van een andere klasse worden aangeroepen.

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

Geef de argumenten van de klasse op afstand door:

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

Of

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

Er wordt dus verwerking bereikt voor lijst-, woordenboek- of primitieve variabelen door overbelasting van de methode.

Probeer het uit voor je code.


Antwoord 15

Deze bibliotheekondersteunt het op een eenvoudige pythonische manier. Een voorbeeld kopiëren uit de onderstaande README.

from plum import dispatch
@dispatch
def f(x: str):
    return "This is a string!"
@dispatch
def f(x: int):
    return "This is an integer!"
>>> f("1")
'This is a string!'
>>> f(1)
'This is an integer!'

Antwoord 16

Je kunt functie-overbelasting heel gemakkelijk implementeren in Python. Hier is een voorbeeld met floatsen integers:

class OverloadedFunction:
    def __init__(self):
        self.D = {int: self.f_int, float: self.f_float}
    def __call__(self, x):
        return self.D[type(x)](x)
    def f_int(self, x):
        print('Integer Function')
        return x**2
    def f_float(self, x):
        print('Float Function (Overloaded)')
        return x**3
# f is our overloaded function
f = OverloadedFunction()
print(f(3 ))
print(f(3.))
# Output:
# Integer Function
# 9
# Float Function (Overloaded)
# 27.0

Het hoofdidee achter de code is dat een klasse de verschillende mogelijke functies bevat die u wilt implementeren, en een woordenboek werkt als een router, waarbij u uw code naar de juiste functie richt, afhankelijk van de invoer type(x).

PS1. In het geval van aangepaste klassen, zoals Bullet1, kunt u het interne woordenboek initialiseren na een vergelijkbaar patroon, zoals self.D = {Bullet1: self.f_Bullet1, ...}. De rest van de code is hetzelfde.

PS2. De tijd / ruimtescomplexiteit van de voorgestelde oplossing is ook redelijk goed, met een gemiddelde kosten van O(1)per bewerking.

Other episodes