Kelebihan fungsi Python

213

Saya tahu bahwa Python tidak mendukung metode overloading, tapi saya mengalami masalah yang sepertinya tidak bisa saya selesaikan dengan cara Pythonic yang bagus.

Saya membuat game di mana karakter perlu menembak berbagai peluru, tetapi bagaimana cara saya menulis fungsi yang berbeda untuk membuat peluru ini? Sebagai contoh misalkan saya memiliki fungsi yang membuat peluru bergerak dari titik A ke B dengan kecepatan yang diberikan. Saya akan menulis fungsi seperti ini:

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

Tapi saya ingin menulis fungsi lain untuk membuat peluru seperti:

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

Demikian seterusnya dengan banyak variasi. Apakah ada cara yang lebih baik untuk melakukannya tanpa menggunakan begitu banyak argumen kata kunci menyebabkan semakin cepat jelek. Mengganti nama masing-masing fungsi sangat buruk juga karena Anda mendapatkan baik add_bullet1, add_bullet2atau add_bullet_with_really_long_name.

Untuk menjawab beberapa jawaban:

  1. Tidak, saya tidak dapat membuat hierarki kelas Bullet karena terlalu lambat. Kode aktual untuk mengelola peluru ada di C dan fungsi saya adalah pembungkus di sekitar C API.

  2. Saya tahu tentang argumen kata kunci tetapi memeriksa segala macam kombinasi parameter semakin mengganggu, tetapi argumen default membantu membagikan seperti acceleration=0

Peluru
sumber
5
Hanya berfungsi untuk satu parameter, tetapi di sini (untuk orang-orang yang datang ke sini dari mesin pencari): docs.python.org/3/library/…
leewz
1
ini sepertinya tempat yang bagus untuk nilai default. Anda dapat mengatur beberapa untuk Tidak Ada dan cukup periksa untuk mereka. dampak boolean ekstra tampaknya dapat diabaikan
Andrew Scott Evans
Harus digunakan default value + if + elseuntuk melakukan hal yang sama seperti C ++ do. Ini adalah salah satu dari sedikit hal yang dapat dibaca oleh C ++ daripada Python ...
Deqing
Saya bingung mengapa kwargs bukan jawaban yang valid. Anda mengatakan bahwa Anda tidak ingin menggunakan banyak argumen kata kunci karena terlalu cepat buruk ... yah, itulah sifat masalahnya. Jika Anda memiliki banyak argumen dan berantakan karena Anda memiliki banyak argumen daripada apa yang Anda harapkan? Apakah Anda ingin menggunakan banyak argumen tanpa menentukannya di mana saja ??? Python bukan pembaca pikiran.
Kalkulus
Kita tidak tahu benda apa script, curveitu, apakah mereka memiliki leluhur yang sama, metode apa yang mereka dukung. Dengan bebek-mengetik, terserah Anda untuk desain kelas untuk mencari tahu metode apa yang mereka butuhkan untuk mendukung. Agaknya Scriptmendukung semacam callback berbasis timestep (tapi objek apa yang harus dikembalikan? Posisi di timestep itu? Lintasan di timestep itu?). Agaknya start, direction, speeddan start, headto, spead, accelerationkeduanya menggambarkan jenis lintasan, tetapi sekali lagi terserah Anda untuk merancang kelas penerima untuk mengetahui cara membongkar mereka dan memprosesnya.
smci

Jawaban:

144

Apa yang Anda minta disebut pengiriman ganda . Lihat contoh-contoh bahasa Julia yang menunjukkan berbagai jenis pengiriman.

Namun, sebelum melihat itu, pertama-tama kita akan membahas mengapa kelebihan tidak benar-benar seperti yang Anda inginkan dengan python.

Kenapa Tidak Berlebihan?

Pertama, kita perlu memahami konsep overloading dan mengapa itu tidak berlaku untuk python.

Ketika bekerja dengan bahasa yang dapat membedakan tipe data pada waktu kompilasi, memilih di antara alternatif dapat terjadi pada waktu kompilasi. Tindakan menciptakan fungsi-fungsi alternatif tersebut untuk pemilihan waktu kompilasi biasanya disebut sebagai fungsi yang berlebihan. ( Wikipedia )

Python adalah bahasa yang diketik secara dinamis , sehingga konsep overload tidak bisa diterapkan. Namun, semua tidak hilang, karena kita dapat membuat fungsi alternatif seperti itu pada saat run-time:

Dalam bahasa pemrograman yang menunda identifikasi tipe data sampai run-time, pemilihan di antara fungsi-fungsi alternatif harus terjadi pada run-time, berdasarkan pada tipe argumen fungsi yang ditentukan secara dinamis. Fungsi yang implementasi alternatifnya dipilih dengan cara ini disebut paling umum sebagai metode multimetode . ( Wikipedia )

Jadi kita harus dapat melakukan multimethod dengan python — atau, seperti yang disebut sebagai: pengiriman ganda .

Pengiriman ganda

Metode multimetode juga disebut pengiriman ganda :

Multiple dispatch atau multimethods adalah fitur dari beberapa bahasa pemrograman berorientasi objek di mana fungsi atau metode dapat secara dinamis dikirim berdasarkan jenis run time (dinamis) lebih dari satu argumennya. ( Wikipedia )

Python tidak mendukung ini di luar kotak 1 , tetapi, seperti yang terjadi, ada paket python yang sangat baik yang disebut multipledispatch yang melakukan hal itu.

Larutan

Inilah cara kami menggunakan paket multipledispatch 2 untuk mengimplementasikan metode Anda:

>>> 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 saat ini mendukung pengiriman tunggal
2. Berhati-hatilah untuk tidak menggunakan multipledispatch dalam lingkungan multi-utas atau Anda akan mendapatkan perilaku aneh.

Andriy Drozdyuk
sumber
6
Apa masalah dengan 'multipledispatch' di lingkungan multi-utas? Karena kode sisi server biasanya dalam lingkungan multi-utas! Hanya berusaha menggali!
danzeer
7
@danzeer Itu bukan thread-safe. Saya melihat argumen yang dimodifikasi oleh dua utas berbeda (yaitu nilai speedmungkin berubah di tengah fungsi ketika utas lain menetapkan nilai sendiri speed) !!! Butuh waktu lama bagi saya untuk menyadari bahwa perpustakaanlah yang menjadi biang keladinya.
Andriy Drozdyuk
108

Python mendukung "metode overloading" saat Anda mempresentasikannya. Sebenarnya, apa yang baru saja Anda gambarkan adalah sepele untuk diimplementasikan dengan Python, dalam banyak cara yang berbeda, tetapi saya akan menjelaskan:

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

Dalam kode di atas, defaultadalah nilai default yang masuk akal untuk argumen tersebut, atau None. Anda kemudian dapat memanggil metode hanya dengan argumen yang Anda minati, dan Python akan menggunakan nilai default.

Anda juga dapat melakukan sesuatu seperti ini:

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

Alternatif lain adalah langsung menghubungkan fungsi yang diinginkan langsung ke kelas atau instance:

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

Namun cara lain adalah dengan menggunakan pola pabrik abstrak:

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

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

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

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

# In the last example you can see abstract factory and "method
# overloading" (as you call it) in action 
Escualo
sumber
107
Semua ini terlihat sebagai contoh argumen variabel, bukan overloading. Karena overloading memungkinkan Anda untuk memiliki fungsi yang sama, untuk tipe yang berbeda sebagai argumen. misalnya: sum (real_num1, real_num2) dan sum (imaginary_num1, imaginary_num2) Keduanya akan memiliki sintaks panggilan yang sama, tetapi sebenarnya mengharapkan 2 jenis yang berbeda sebagai input, dan implementasinya harus berubah juga secara internal
Efren
17
Dengan menggunakan jawaban yang akan Anda ajukan, bagaimana Anda akan hadir kepada penelepon yang argumennya masuk akal bersama? Hanya dengan menempatkan beberapa argumen, masing-masing dengan nilai default dapat memberikan fungsionalitas yang sama tetapi dalam hal API itu jauh kurang elegan
Greg Ennis
6
Selain hal-hal di atas adalah kelebihan beban, implementasi harus memeriksa semua kombinasi input parameter (atau mengabaikan parameter) seperti: if sprite and script and not start and not direction and not speed...hanya untuk mengetahui itu dalam tindakan tertentu. karena seorang penelepon dapat memanggil fungsi yang menyediakan semua parameter yang tersedia. Sementara overloading menentukan untuk Anda set yang tepat dari parameter yang relevan.
Roee Gavirel
5
Sangat mengecewakan ketika orang mengatakan bahwa python mendukung metode overloading. Itu tidak. Fakta bahwa Anda memasukkan "kelebihan metode" dalam kutipan menunjukkan bahwa Anda mengetahui fakta ini. Anda bisa mendapatkan fungsionalitas serupa dengan beberapa teknik, seperti yang disebutkan di sini. Tetapi metode overloading memiliki definisi yang sangat spesifik.
Howard Swope
Saya pikir titik yang dimaksud adalah sementara metode overloading bukan fitur python, mekanisme di atas dapat digunakan untuk mencapai efek yang setara.
Rawr berdering
93

Anda dapat menggunakan solusi "roll-your-own" untuk fungsi yang berlebihan. Yang ini disalin dari artikel Guido van Rossum tentang multimethods (karena ada sedikit perbedaan antara mm dan overloading dengan 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

Penggunaannya akan

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

Batasan yang paling membatasi saat ini adalah:

  • metode tidak didukung, hanya fungsi yang bukan anggota kelas;
  • warisan tidak ditangani;
  • kwarg tidak didukung;
  • mendaftarkan fungsi-fungsi baru harus dilakukan pada waktu impor hal itu tidak aman thread
Alexander Poluektov
sumber
6
+1 untuk dekorator untuk memperluas bahasa dalam kasus penggunaan ini.
Eloims
1
1 karena ini adalah ide bagus (dan mungkin apa yang harus OP lakukan) --- Saya belum pernah melihat implementasi multimethod dengan Python.
Escualo
39

Opsi yang memungkinkan adalah menggunakan modul multipledispatch seperti yang dijelaskan di sini: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Alih-alih melakukan ini:

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

Kamu bisa melakukan ini:

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)

Dengan penggunaan yang dihasilkan:

>>> add(1, 2)
3

>>> add(1, 'hello')
'1 + hello'
Dave C
sumber
4
Mengapa ini tidak mendapatkan lebih banyak suara? Saya menduga karena kurangnya contoh ... Saya telah membuat jawaban dengan contoh tentang bagaimana menerapkan solusi untuk masalah OP dengan paket multipledispatch .
Andriy Drozdyuk
19

Dalam Python 3.4 ditambahkan PEP-0443. Fungsi generik single-dispatch .

Berikut ini deskripsi API pendek dari PEP.

Untuk mendefinisikan fungsi generik, hiasi dengan dekorator @singledispatch. Perhatikan bahwa pengiriman terjadi pada jenis argumen pertama. Buat fungsi Anda sesuai:

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

Untuk menambahkan implementasi kelebihan fungsi, gunakan atribut register () dari fungsi generik. Ini adalah dekorator, mengambil parameter tipe dan mendekorasi fungsi yang mengimplementasikan operasi untuk tipe itu:

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

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

Jenis perilaku ini biasanya diselesaikan (dalam bahasa OOP) menggunakan Polimorfisme. Setiap jenis peluru akan bertanggung jawab untuk mengetahui bagaimana perjalanannya. Misalnya:

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

Lewati sebanyak mungkin argumen ke fungsi c_ yang ada, kemudian lakukan pekerjaan menentukan fungsi c mana yang akan dipanggil berdasarkan nilai-nilai pada fungsi c awal. Jadi, python seharusnya hanya memanggil fungsi satu c. Bahwa satu fungsi c melihat argumen, dan kemudian dapat mendelegasikan ke fungsi c lainnya dengan tepat.

Anda pada dasarnya hanya menggunakan setiap subclass sebagai wadah data yang berbeda, tetapi dengan mendefinisikan semua argumen potensial pada kelas dasar, subclass bebas untuk mengabaikan yang mereka tidak melakukan apa-apa.

Ketika jenis peluru baru datang, Anda dapat dengan mudah mendefinisikan satu properti lagi di pangkalan, mengubah satu fungsi python sehingga melewati properti tambahan, dan satu fungsi c_ yang memeriksa argumen dan mendelegasikan dengan tepat. Sepertinya tidak terlalu buruk.

Josh Smeaton
sumber
1
Itu adalah pendekatan awal saya, tetapi untuk alasan kinerja saya harus menulis ulang kode itu dalam C.
Bullets
@Bullets, saya akan menyarankan bahwa mungkin ada sejumlah opsi berbeda yang tersedia untuk meningkatkan kinerja daripada menulis banyak fungsi c yang mungkin tidak akan melakukan banyak hal. Misalnya: membuat instance mungkin mahal, jadi pertahankan kumpulan objek. Meskipun saya mengatakan ini tanpa mengetahui apa yang Anda temukan terlalu lambat. Karena ketertarikan, apa yang sebenarnya lambat dari pendekatan ini? Kecuali waktu yang signifikan akan dihabiskan di sisi C dari batas, saya tidak bisa berpikir bahwa Python (itu sendiri) adalah masalah sebenarnya.
Josh Smeaton
Mungkin ada cara lain untuk meningkatkan kinerja, tapi saya jauh lebih baik dengan C daripada dengan Python. Masalahnya adalah menghitung gerakan peluru dan mendeteksi ketika mereka keluar dari batas layar. Saya memiliki metode untuk menghitung posisi peluru pos+v*tdan kemudian membandingkannya dengan batas layar if x > 800dan sebagainya. Memanggil fungsi ini beberapa ratus kali per frame ternyata sangat lambat. Itu adalah sesuatu seperti 40 fps pada 100% cpu dengan python murni hingga 60 fps dengan 5% -10% bila dilakukan dalam C.
Bullets
@Bullets, cukup adil. Saya masih menggunakan pendekatan yang saya gunakan untuk mengenkapsulasi data. Berikan contoh peluru ke add_bullet, dan ekstrak semua bidang yang Anda butuhkan. Saya akan mengedit jawaban saya.
Josh Smeaton
@Bullets: Anda dapat menggabungkan fungsi C Anda dan pendekatan OOP yang disarankan oleh Josh menggunakan Cython . Ini memungkinkan pengikatan awal sehingga seharusnya tidak ada penalti kecepatan.
jfs
4

Baik menggunakan beberapa argumen kata kunci dalam definisi, atau membuat Bullethierarki yang instansnya diteruskan ke fungsi.

Ignacio Vazquez-Abrams
sumber
Saya akan menyarankan pendekatan kedua: buat beberapa BulletParams ... kelas untuk menentukan detail peluru.
John Zwinck
Bisakah Anda menguraikan ini? Saya mencoba membuat hierarki kelas dengan peluru yang berbeda tetapi ini tidak berhasil, karena Python terlalu lambat. Itu tidak bisa menghitung gerakan jumlah peluru yang dibutuhkan cukup cepat, jadi saya harus menulis bagian itu di C. Semua varian add_bullet hanya memanggil fungsi C yang sesuai.
Peluru
4

Saya pikir persyaratan dasar Anda adalah memiliki sintaks seperti C / C ++ di python dengan sakit kepala sesedikit mungkin. Meskipun saya menyukai jawaban Alexander Poluektov itu tidak bekerja untuk kelas.

Berikut ini harus bekerja untuk kelas. Ia berfungsi dengan membedakan dengan jumlah argumen bukan kata kunci (tetapi tidak mendukung pembedaan berdasarkan jenis):

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

Dan itu dapat digunakan secara sederhana seperti ini:

test = TestOverloading()

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

Keluaran:

Ini kelebihan 3
Sprite: Saya seorang Sprite
Start: 0
Direction: Right

Ini kelebihan 2
Sprite: Saya adalah
Skrip Sprite lain :
sementara x == Benar: cetak 'hai'

Tim Ludwinski
sumber
4

The @overloaddekorator ditambahkan dengan jenis petunjuk (PEP 484). Meskipun ini tidak mengubah perilaku python, itu membuatnya lebih mudah untuk memahami apa yang sedang terjadi, dan bagi mypy untuk mendeteksi kesalahan.
Lihat: Ketik petunjuk dan PEP 484

Richard Whitehead
sumber
Bisakah Anda menambahkan beberapa contoh?
gerrit
3

Saya pikir Bullethierarki kelas dengan polimorfisme terkait adalah cara untuk pergi. Anda dapat secara efektif membebani konstruktor kelas dasar dengan menggunakan metaclass sehingga memanggil kelas dasar menghasilkan penciptaan objek subclass yang sesuai. Di bawah ini adalah beberapa kode contoh untuk menggambarkan esensi dari apa yang saya maksud.

Diperbarui

Kode telah dimodifikasi untuk dijalankan di bawah Python 2 dan 3 agar tetap relevan. Ini dilakukan dengan cara yang menghindari penggunaan sintaks metaclass eksplisit Python, yang bervariasi antara dua versi.

Untuk mencapai tujuan itu, BulletMetaBaseinstance BulletMetakelas dibuat dengan secara eksplisit memanggil metaclass saat membuat Bulletbaseclass (daripada menggunakan __metaclass__=atribut kelas atau melalui metaclassargumen kata kunci tergantung pada versi 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')

Keluaran:

creating Bullet1 object
creating Bullet2 object
creating Bullet3 object
creating Bullet4 object
Bullet1 object fired with uniform gravity trajectory
Traceback (most recent call last):
  File "python-function-overloading.py", line 93, in <module>
    b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method
  File "python-function-overloading.py", line 49, in fire
    raise NotImplementedError(self.__class__.__name__ + ".fire() method")
NotImplementedError: Bullet2.fire() method
martineau
sumber
Hmm ini masih cara mewah untuk memberi nama fungsi sebagai add_bullet1, add_bullet2 dan sebagainya.
Peluru
1
@Bullets: Mungkin begitu, atau mungkin itu hanya cara yang agak rumit untuk membuat fungsi pabrik. Yang menyenangkan tentang hal itu adalah mendukung hierarki Bulletsubclass tanpa harus memodifikasi kelas dasar atau fungsi pabrik setiap kali Anda menambahkan subtipe lain. (Tentu saja, jika Anda menggunakan C daripada C ++, saya kira Anda tidak memiliki kelas.) Anda juga bisa membuat metaclass yang lebih cerdas yang mengetahui sendiri subkelas apa yang akan dibuat berdasarkan jenis dan / atau nomor argumen yang diteruskan (seperti yang dilakukan C ++ untuk mendukung overloading).
martineau
1
Ide pewarisan ini akan menjadi pilihan pertama saya juga.
Daniel Möller
3

Python 3.8 menambahkan functools.singledispatchmethod

Ubah metode menjadi fungsi umum pengiriman tunggal.

Untuk mendefinisikan metode generik, hiasi dengan dekorator @singledispatchmethod. Perhatikan bahwa pengiriman terjadi pada jenis argumen non-diri atau non-cls pertama, buat fungsi Anda sesuai:

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

Keluaran

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

@singledispatchmethod mendukung bersarang dengan dekorator lain seperti @classmethod. Perhatikan bahwa untuk memungkinkan dispatcher.register, metode khusus harus menjadi dekorator terluar. Berikut adalah kelas Negator dengan metode neg yang terikat kelas:

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

Keluaran:

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

Pola yang sama dapat digunakan untuk dekorator serupa lainnya: metode statis, metode abstrak, dan lainnya.

Vlad Bezden
sumber
2

Gunakan argumen kata kunci dengan default. Misalnya

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

Dalam kasus peluru lurus versus peluru melengkung, saya akan menambahkan dua fungsi: add_bullet_straightdan add_bullet_curved.

Rafe Kettler
sumber
2

metode overloading rumit di python. Namun, mungkin ada penggunaan melewati dikt, daftar atau variabel primitif.

Saya telah mencoba sesuatu untuk kasus penggunaan saya, ini bisa membantu di sini untuk memahami orang-orang untuk membebani metode.

Mari kita ambil contoh Anda:

metode overload kelas dengan memanggil metode dari kelas yang berbeda.

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

lewati argumen dari kelas jarak jauh:

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

ATAU

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

Jadi, penanganan sedang dilakukan untuk daftar, Kamus atau variabel primitif dari metode overloading.

coba untuk kode Anda.

shashankS
sumber
2

Hanya dekorator yang sederhana

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)

Anda bisa menggunakannya seperti ini

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

Ubah untuk menyesuaikannya dengan use case Anda.

Klarifikasi konsep

  • function dispatch : ada beberapa fungsi dengan nama yang sama. Yang mana yang harus dipanggil? dua strategi
  • pengiriman statis / kompilasi-waktu ( alias. "overloading" ). memutuskan fungsi yang akan dipanggil berdasarkan tipe waktu kompilasi argumen. Dalam semua bahasa dinamis, tidak ada tipe waktu kompilasi, jadi overload tidak mungkin secara definisi
  • dynamic / run-time dispatch : memutuskan fungsi yang akan dipanggil berdasarkan tipe runtime dari argumen. Inilah yang dilakukan oleh semua bahasa OOP: beberapa kelas memiliki metode yang sama, dan bahasa memutuskan yang akan dipanggil berdasarkan jenis self/thisargumen. Namun, sebagian besar bahasa hanya melakukannya untuk thisargumen saja. Dekorator di atas memperluas ide ke beberapa parameter.

Untuk menghapus, asumsikan bahasa statis, dan tentukan fungsinya

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)

Dengan pengiriman statis (overloading) Anda akan melihat "nomor dipanggil" dua kali, karena xtelah dinyatakan sebagai Number, dan itu semua overloading peduli. Dengan pengiriman dinamis Anda akan melihat "integer disebut, float disebut", karena mereka adalah tipe aktual xpada saat fungsi dipanggil.

blue_note
sumber
Contoh ini sangat tidak menggambarkan metode mana yang dipanggil xuntuk pengiriman dinamis, atau dalam urutan mana kedua metode dipanggil untuk pengiriman statis. Rekomendasikan Anda mengedit pernyataan cetak ke print('number called for Integer')dll.
smci