Функція перевантаження Python


213

Я знаю, що Python не підтримує перевантаження методом, але я зіткнувся з проблемою, яку я, здається, не можу вирішити приємно пітонічно.

Я роблю гру, де персонаж повинен стріляти з різних куль, але як я можу писати різні функції для створення цих куль? Наприклад, припустимо, у мене є функція, яка створює кулю, яка рухається від точки А до В із заданою швидкістю. Я б написав таку функцію:

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

Але я хочу написати інші функції для створення куль, як-от:

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

І так з багатьма варіаціями. Чи є кращий спосіб зробити це без використання стількох аргументів ключових слів, що спричиняють його потворність швидко. Перейменування кожної функції дуже погано теж , тому що ви отримуєте або add_bullet1, add_bullet2або add_bullet_with_really_long_name.

Щоб вирішити деякі відповіді:

  1. Ні, я не можу створити ієрархію класу Bullet, тому що це занадто повільно. Фактичний код управління кулями знаходиться в C, а мої функції - обгортки навколо C API.

  2. Я знаю про аргументи ключових слів, але перевірка наявності всіляких комбінацій параметрів стає дратівливою, але аргументи за замовчуванням допомагають виділити як acceleration=0


5
Працює лише для одного параметра, але тут (для людей, які приходять сюди з пошукової системи): docs.python.org/3/library/…
leewz

1
це здається хорошим місцем для значень за замовчуванням. ви можете встановити деякі на None і просто перевірити їх. додатковий бульний вплив здається незначним
Ендрю Скотт Еванс

Доводиться використовувати default value + if + elseте саме, що і C ++. Це одна з небагатьох речей, у яких C ++ має кращу читаність, ніж Python ...
Deqing

Мене бентежить питання, чому кварги - неправдива відповідь. Ви говорите, що не хочете використовувати багато аргументів ключових слів, тому що це стає некрасиво швидко ... ну це лише суть проблеми. Якщо у вас багато аргументів, і це безладно, тому що у вас є багато аргументів, ніж ви очікували? Ви хочете використовувати багато аргументів, не вказуючи їх ніде ??? Python - не читач розуму.
Обчислення

Ми не знаємо, що це за об'єкти script, curve, чи є у них спільний предок, які методи вони підтримують. Завдяки набору тексту качок вирішувати, які методи вони повинні підтримувати. Імовірно, Scriptпідтримується якийсь зворотний виклик на основі часового кроку (але який об’єкт він повинен повертати? Позиція в цьому часовому кроці? Траєкторія на цьому етапі?). Імовірно , start, direction, speedі start, headto, spead, accelerationобидва описують типи траєкторій, але знову - таки це до вас , щоб розробити отримує клас , щоб знати , як їх розпакувати й обробляти їх.
smci

Відповіді:


144

Те, що ви просите, називається багаторазовим відправленням . Дивіться приклади мови Джулії, де демонструються різні типи розсилок.

Однак, перш ніж розглянути це, ми спочатку розберемося, чому перевантаження не є справді тим, що потрібно в python.

Чому б не перевантажувати?

По-перше, потрібно зрозуміти поняття перевантаження і чому це не застосовується до python.

Під час роботи з мовами, які можуть розрізняти типи даних під час компіляції, вибір серед альтернатив може відбуватися під час компіляції. Акт створення таких альтернативних функцій для вибору часу компіляції зазвичай називають перевантаженням функції. ( Вікіпедія )

Python - це динамічно набрана мова, тому поняття перевантаження просто не стосується її. Однак все не втрачено, оскільки ми можемо створювати такі альтернативні функції під час виконання:

У мовах програмування, які відкладають ідентифікацію типу даних до моменту виконання, вибір серед альтернативних функцій повинен відбуватися під час виконання на основі динамічно визначених типів аргументів функції. Функції, альтернативні варіанти реалізації яких обрані таким чином, найчастіше називають мультиметодами . ( Вікіпедія )

Таким чином, ми повинні мати можливість робити багатометоди в python - або, як це ще називається: багаторазова диспетчеризація .

Багаторазова відправка

Мультиметоди також називаються множинними диспетчерами :

Багаторазова диспетчеризація або мультиметоди є особливістю деяких об'єктно-орієнтованих мов програмування, в яких функцію або метод можна динамічно розсилати на основі типу часу виконання (динамічного) більш ніж одного з його аргументів. ( Вікіпедія )

Python не підтримує це з вікна 1 , але, як це буває, є чудовий пакет python під назвою multipledispatch, який робить саме це.

Рішення

Ось як ми можемо використовувати пакет multipledispatch 2 для реалізації ваших методів:

>>> 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 в даний час підтримує єдиний відправка
2. догляду Take не використовувати multipledispatch в багатопотоковому середовищі або ви отримаєте дивну поведінку.


6
Яка проблема із "кратним диспетчером" у багатопотоковому середовищі? Оскільки код на стороні сервера зазвичай знаходиться у багатопотоковому середовищі! Просто намагаюся викопати це!
danzeer

7
@danzeer Це не було безпечно для потоків. Я бачив, як аргумент змінюється двома різними потоками (тобто значення speedможе змінюватися в середині функції, коли інший потік встановлює власне значення speed) !!! Мені знадобилося багато часу, щоб зрозуміти, що саме винуватцем була саме бібліотека.
Андрій Дроздюк

108

Python підтримує "метод перевантаження", коли ви його представляєте. Насправді, те, що ви тільки що описуєте, тривіально реалізувати в Python у так багато різних способів, але я б пішов із:

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

У наведеному вище коді defaultє правдоподібним значенням за замовчуванням для цих аргументів, абоNone . Потім ви можете зателефонувати методу лише з аргументів, які вас цікавлять, і Python буде використовувати значення за замовчуванням.

Ви також можете зробити щось подібне:

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

Інша альтернатива - безпосередньо підключити потрібну функцію безпосередньо до класу чи екземпляра:

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

Ще один спосіб - використовувати абстрактний заводський візерунок:

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
Все це виглядає як приклади змінних аргументів, а не перевантаження. Оскільки перевантаження дозволяє мати одну і ту ж функцію для різних типів, як аргументи. наприклад: sum (real_num1, real_num2) та sum (imaginary_num1, imaginary_num2) Обидва матимуть один і той же синтаксис виклику, але насправді очікують на введення 2 різні типи, і реалізація має змінюватися також внутрішньо
Efren

17
Використовуючи відповідь, з яким ви б пішли, як би ви подали абоненту, які аргументи мають сенс разом? Просто додавання аргументів, кожен з яких має значення за замовчуванням, може забезпечити однакову функціональність, але з точки зору API це набагато менш елегантно
Грег Енніс

6
Якщо вищезазначене не є перевантаженим, реалізація повинна буде перевірити всі комбінації входів параметрів (або ігнорувати параметри) на кшталт: if sprite and script and not start and not direction and not speed...просто щоб знати, що він знаходиться в конкретній дії. тому що абонент може зателефонувати до функції, яка забезпечує всі доступні параметри. Під час перевантаження визначайте для вас точні набори відповідних параметрів.
Roee Gavirel

5
Це дуже засмучує, коли люди кажуть, що python підтримує метод перевантаження. Це не. Той факт, що ви ставите «перевантаження методом» у цитатах, свідчить про те, що вам відомо про цей факт. Ви можете отримати подібний функціонал за допомогою декількох прийомів, як той, що згадується тут. Але метод перевантаження має дуже конкретне визначення.
Говард

Я думаю, що задуманий момент полягає в тому, що перевантаження методу не є особливістю пітона, вищезгадані механізми можна використовувати для досягнення еквівалентного ефекту.
rawr задзвонив

93

Ви можете використовувати рішення "roll-your-own" для перевантаження функцій. Це скопійовано із статті Гвідо ван Россума про багатометоди (оскільки різниця між мм і перевантаженням у пітон є невеликою):

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

Використання було б

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

Більшість обмежувальних обмежень на даний момент є:

  • методи не підтримуються, лише функції, які не є членами класу;
  • спадкування не обробляється;
  • kwargs не підтримуються;
  • реєстрація нових функцій повинна здійснюватися під час імпорту, річ не є безпечною для потоків

6
+1 для декораторів для розширення мови в цьому випадку використання.
Елоїмс

1
+1, тому що це відмінна ідея (і, мабуть, з чим повинна йти ОП) --- Я ніколи не бачив багатометодної реалізації в Python.
Ескуало

39

Можливим варіантом є використання модуля мультидисплей, як детально описано тут: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Замість цього:

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

Ви можете зробити це:

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)

З отриманим результатом:

>>> add(1, 2)
3

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

4
Чому це не отримує більше голосів? Я здогадуюсь через брак прикладів ... Я створив відповідь на прикладі, як реалізувати рішення проблеми ОП з пакетом multipledispatch .
Андрій Дроздюк

19

У Python 3.4 був доданий PEP-0443. Загальні функції одноразового відправлення .

Ось короткий опис API від PEP.

Щоб визначити загальну функцію, прикрасьте її декоратором @singledispatch. Зауважте, що відправка відбувається за типом першого аргументу. Створіть свою функцію відповідно:

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

Щоб додати перевантажені реалізації до функції, використовуйте атрибут register () загальної функції. Це декоратор, який приймає параметр типу та декорує функцію, що реалізує операцію для цього типу:

@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

Цей тип поведінки, як правило, вирішується (мовами ООП) за допомогою поліморфізму. Кожен тип кулі несе відповідальність за те, як він подорожує. Наприклад:

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

Передайте стільки аргументів існуючій c_function, потім виконайте завдання визначення функції c, яку потрібно викликати, виходячи зі значень у початковій функції c. Отже, python повинен коли-небудь викликати лише функцію c. Ця одна функція c переглядає аргументи, а потім може належним чином делегувати інші функції c.

Ви по суті просто використовуєте кожен підклас як інший контейнер даних, але, визначаючи всі потенційні аргументи базового класу, підкласи можуть ігнорувати ті, з якими вони нічого не роблять.

Коли з'являється новий тип кулі, ви можете просто визначити ще одне властивість на базі, змінити одну функцію python, щоб вона передала додаткове властивість, і одну c_function, яка вивчає аргументи та делегує належним чином. Думаю, це не дуже погано.


1
Це був мій початковий підхід, але з міркувань продуктивності мені довелося переписати цей код у C.
Bullets

@Bullets, я б припустив, що може бути кілька різних варіантів для підвищення продуктивності, а не написання цілого ряду c функцій, які, ймовірно, не будуть робити багато. Наприклад: створення екземпляра може бути дорогим, тому підтримуйте пул об’єктів. Хоча я кажу це, не знаючи, що ви виявилися занадто повільними. З інтересу, що саме повільно ставилося до цього підходу? Якщо значну кількість часу не буде проведено на стороні кордону, я не можу подумати, що справжньою проблемою є саме Python.
Джош Смітон

Можливо, є й інші способи покращити продуктивність, але я набагато кращий з C, ніж з Python. Проблема полягала в обчисленні рухів куль і виявленні, коли вони виходять за межі екрана. У мене були методи обчислення положення кулі, pos+v*tа потім порівняння меж екрана if x > 800тощо. Виклик цих функцій кілька сотень разів на кадр виявився неприпустимо повільним. Це було щось на кшталт 40 кадрів в секунду при 100% процесора з чистим пітоном до 60 кадрів в секунду при 5% -10% при виконанні в C.
Кулі

@Bullets, досить справедливо тоді. Я все одно використовую підхід, з яким я застосував, для інкапсуляції даних. Передайте екземпляр кулі add_bulletта витягніть усі потрібні поля. Я відредагую свою відповідь.
Джош Смітон

@Bullets: Ви можете комбінувати свої функції C та підхід OOP, запропонований Джошем за допомогою Cython . Це дозволяє достроково прив’язувати, тому не повинно бути швидкого покарання.
jfs


4

Або використовуйте декілька аргументів ключового слова у визначенні, або створіть Bulletієрархію, екземпляри якої передаються функції.


Я збирався запропонувати другий підхід: зробіть кілька класів BulletParams ..., щоб вказати деталі кулі.
Джон Цвінк

Чи можете ви детальніше зупинитися на цьому? Я намагався створити ієрархію класів з різними кулями, але це не працює, тому що Python занадто повільний. Він не може досить швидко обчислити рухи потрібної кількості куль, тому мені довелося записати цю частину в C. Усі варіанти add_bullet просто викликають відповідну функцію C.
Кулі

4

Я думаю, що ваша основна вимога - мати синтаксис типу C / C ++ у python з найменшим можливим головним болем. Хоча мені сподобалась відповідь Олександра Полуектова, вона не працює для занять.

Для занять повинні працювати наступні. Він працює, розрізняючи кількість аргументів, що не належать до ключових слів (але не підтримує розрізнення за типом):

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

І його можна використовувати просто так:

test = TestOverloading()

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

Вихід:

Це перевантаження 3
Sprite: Я Sprite
Start: 0
Напрям: Вправо

Це перевантаження 2
Sprite: Я ще один Sprite
Script:
while x == True: надрукувати "привіт"


4

До @overloadдекоратора додали підказки типу (PEP 484). Хоча це не змінює поведінку python, це полегшує розуміння того, що відбувається, і для mypy виявляє помилки.
Див .: Тип підказки та PEP 484


Чи можете ви додати кілька прикладів?
Герріт

3

Я думаю, що Bulletієрархія класів із пов'язаним з ним поліморфізмом - це шлях. Ви можете ефективно перевантажувати конструктор базового класу, використовуючи метаклас, так що виклик базового класу призводить до створення відповідного об'єкта підкласу. Нижче наводиться зразок коду, який ілюструє суть того, що я маю на увазі.

Оновлено

Код був модифікований для запуску під Python 2 і 3, щоб він був актуальним. Це було зроблено таким чином, щоб уникнути використання явного синтаксису метакласу Python, який змінюється між двома версіями.

Для досягнення цієї мети BulletMetaBaseекземпляр BulletMetaкласу створюється шляхом явного виклику метакласу під час створення Bulletбазового класу (замість використання __metaclass__=атрибута класу або через metaclassаргумент ключового слова залежно від версії 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')

Вихід:

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

Хм, це все ще просто фантазійний спосіб називати функції як add_bullet1, add_bullet2 тощо.
Кулі

1
@Bullets: Можливо, це є, а може, це лише трохи складний спосіб створити заводську функцію. Приємно в тому, що він підтримує ієрархію Bulletпідкласів, не змінюючи базовий клас або заводську функцію щоразу, коли ви додаєте інший підтип. (Звичайно, якщо ви використовуєте C, а не C ++, я маю на увазі, що у вас немає класів.) Ви також можете зробити розумніший метаклас, який розібрався самостійно, який підклас створити на основі типу та / або числа аргументів, що передаються (як C ++ робить для підтримки перевантаження).
мартіно

1
Ця ідея успадкування була б і моїм першим варіантом.
Даніель Мьоллер

3

Python 3.8 додав метод functools.singledispatchmethod

Перетворіть метод у загальну функцію одноразового відправлення.

Щоб визначити загальний метод, прикрасьте його декоратором @singledispatchmethod. Зауважте, що відправка відбувається за типом першого аргументу non-self або non-cls, створіть свою функцію відповідно:

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

Вихідні дані

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

@singledispatchmethod підтримує вкладення з іншими декораторами, такими як @classmethod. Зауважте, що для того, щоб дозволити discher.register, singledispatchmethod повинен бути зовнішнім декоратором. Ось клас Negator з методами neg, які пов'язані класом:

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

Вихід:

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

Таку ж схему можна використовувати і для інших подібних декораторів: статичного методу, абстрактного методу та інших.


2

Використовуйте аргументи ключових слів за замовчуванням. Напр

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

У випадку прямої кулі проти вигнутої кулі я б додав дві функції: add_bullet_straightі add_bullet_curved.


2

Методи перенавантаження складні в python. Однак може бути застосовано проходження dict, list або примітивних змінних.

Я спробував щось для моїх випадків використання, це могло б допомогти зрозуміти людей, щоб перевантажити методи.

Візьмемо ваш приклад:

метод перевантаження класу з методами виклику з іншого класу.

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

передайте аргументи з віддаленого класу:

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

АБО

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

Отже, обробка досягається для списку, словника або примітивних змінних від перевантаження методом.

спробуйте це для своїх кодів.


2

Просто простий декоратор

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)

Ви можете використовувати його так

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

Змініть його, щоб адаптувати його до вашого випадку використання.

Уточнення понять

  • функція диспетчеризації : є кілька функцій з тим самим іменем. Кого слід назвати? дві стратегії
  • статична відправка / час компіляції ( ака. "перевантаження" ). вирішуйте, яку функцію викликати на основі типу аргументів часу компіляції . У всіх динамічних мовах відсутній тип часу компіляції, тому перевантаження неможливо за визначенням
  • динамічний / виконання доставки : вирішити , яку функцію для виклику на основі виконання типу аргументів. Це те, що роблять усі мови OOP: декілька класів мають однакові методи, і мова визначає, який з них викликати на основі типу self/thisаргументу. Однак більшість мов роблять це лише для thisаргументу. Вищеописаний декоратор розширює ідею на кілька параметрів.

Щоб очистити, припустіть статичну мову та визначте функції

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)

За допомогою статичної диспетчеризації (перевантаження) ви побачите "номер, який називається" двічі, тому xщо був оголошений як Number, і це все перевантаження. При динамічній розсилці ви побачите "ціле число, яке називається, поплавок викликається", тому що це фактичні типи xна момент виклику функції.


Цей приклад вирішально не ілюструє, який метод закликався xдо динамічної диспетчеризації, ні в якому порядку обидва методи були запрошені для статичної відправки. Рекомендую вам відредагувати друковані виписки print('number called for Integer')тощо
smci
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.