Python işlevi aşırı yüklenmesi


213

Python'un yöntem aşırı yüklemesini desteklemediğini biliyorum, ama güzel bir Pythonic şekilde çözemediğim bir sorunla karşılaştım.

Bir karakterin çeşitli mermileri vurması gereken bir oyun yapıyorum, ancak bu mermileri oluşturmak için nasıl farklı işlevler yazarım? Örneğin, A noktasından B noktasına belirli bir hızda hareket eden bir mermi oluşturan bir fonksiyonum olduğunu varsayalım. Ben böyle bir fonksiyon yazmak istiyorum:

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

Ama mermi oluşturmak için diğer işlevleri yazmak istiyorum:

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

Ve birçok varyasyonda böyle devam eder. Bu kadar çok anahtar kelime argümanı kullanmadan yapmanın daha iyi bir yolu var mı? Her işlev yeniden adlandırma ya olsun, çünkü çok güzel feci olduğu add_bullet1, add_bullet2ya da add_bullet_with_really_long_name.

Bazı yanıtları ele almak için:

  1. Hayır Bullet sınıfı hiyerarşisi oluşturamıyorum çünkü bu çok yavaş. Madde işaretlerini yönetmek için gerçek kod C'dir ve işlevlerim C API etrafındaki paketleyicilerdir.

  2. Anahtar kelime argümanlarını biliyorum ama her türlü parametre kombinasyonunu kontrol etmek can sıkıcı oluyor, ancak varsayılan argümanlar acceleration=0


5
Sadece bir parametre için çalışır, ancak burada (buraya bir arama motorundan gelen insanlar için): docs.python.org/3/library/…
leewz

1
Bu, varsayılan değerler için iyi bir yer gibi görünüyor. bazılarını Yok olarak ayarlayabilir ve kontrol edebilirsiniz. ekstra boolean etkisi ihmal edilebilir görünüyor
Andrew Scott Evans

Kullanmak zorunda default value + if + elseC do ++ ile aynı yapmak. Bu C ++ 'nın Python'dan daha iyi okunabilirliğe sahip olduğu çok az şeyden biri ...
Deqing

Kwargs'ın neden geçerli bir cevap olmadığı konusunda kafam karıştı. Çok fazla anahtar kelime argümanı kullanmak istemediğinizi söylüyorsunuz çünkü bu çok hızlı bir şekilde hızlanıyor ... bu sadece sorunun doğası. Çok fazla argümanınız varsa ve dağınıksa, beklediğinizden daha fazla argümanınız var mı? Birçok argümanı herhangi bir yerde belirtmeden kullanmak ister misiniz ??? Python bir zihin okuyucu değildir.
Matematik

Ne tür nesneler olduğunu bilmiyoruz script, curve, ortak bir ataları var mı, hangi yöntemleri destekliyorlar. Ördek yazmayla, sınıf tasarımını hangi yöntemleri desteklemeleri gerektiğini bulmak size kalmış. Muhtemelen Scriptbir tür timestep tabanlı geri çağrıyı destekler (ancak hangi nesneye geri dönmeli? O zaman çizelgesindeki konum? O zaman çizelgesindeki yörünge?). Muhtemelen start, direction, speedve start, headto, spead, accelerationher ikisi de yörünge türlerini açıklar, ancak yine de alıcı sınıfın nasıl paketleneceğini ve işleneceğini bilmesi size bağlıdır.
smci

Yanıtlar:


144

İstediğinize çoklu gönderim denir . Farklı gönderi türlerini gösteren Julia dil örneklerine bakın .

Bununla birlikte, buna bakmadan önce, aşırı yüklemenin neden python'da gerçekten istediğiniz şeyle mücadele edeceğiz .

Neden Aşırı Yüklenmiyor?

İlk olarak, aşırı yükleme kavramını ve bunun neden python için geçerli olmadığını anlaması gerekir.

Veri türlerini derleme zamanında ayırt edebilen dillerle çalışırken, derleme zamanında alternatifler arasından seçim yapılabilir. Derleme zamanı seçimi için bu gibi alternatif fonksiyonlar yaratma eylemine genellikle bir fonksiyonun aşırı yüklenmesi denir. ( Wikipedia )

Python dinamik olarak yazılan bir dildir, bu nedenle aşırı yükleme kavramı bu dil için geçerli değildir. Ancak, çalışma zamanında bu tür alternatif işlevler oluşturabildiğimiz için hepsi kaybolmaz :

Çalışma zamanına kadar veri türü tanımlamasını erteleyen programlama dillerinde, dinamik olarak belirlenen işlev argümanı türlerine dayanarak, alternatif işlevler arasında seçim çalışma zamanında gerçekleşmelidir. Alternatif uygulamaları bu şekilde seçilen işlevlere en genel olarak çok- metotlu denir . ( Wikipedia )

Bu nedenle, python'da multimethods yapabilmeliyiz - veya alternatif olarak adlandırıldığı gibi: çoklu gönderim .

Çoklu gönderim

Çoklu yöntemlere çoklu gönderim de denir :

Çoklu gönderim veya multimethods, bir işlev veya yöntemin bağımsız değişkenlerinin birden fazlasının çalışma süresi (dinamik) türüne göre dinamik olarak gönderilebildiği bazı nesne yönelimli programlama dillerinin özelliğidir. ( Wikipedia )

Python bunu kutudan 1 desteklemiyor , ancak olduğu gibi, multipledispatch adı verilen ve tam olarak bunu yapan mükemmel bir python paketi var.

Çözüm

Yöntemlerinizi uygulamak için multipledispatch 2 paketini şu şekilde kullanabiliriz :

>>> 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 şu anda destekleyen tek sevk
kullanmamayı 2. özen gösterin multipledispatch çok dişli bir ortamda ya da garip davranışlar alacak.


6
Çok iş parçacıklı bir ortamda 'multipledispatch' ile ilgili sorun nedir? Sunucu tarafı kodu genellikle çok iş parçacıklı bir ortamda olduğu için! Sadece kazmaya çalışıyor!
danzeer

7
@danzeer İş parçacığı için güvenli değildi. İki farklı iş parçacığı tarafından değiştirilen argüman gördüm ( speedbaşka bir iş parçacığı kendi değerini ayarlarken yani işlevinin ortasında değişebilir değeri speed) !!! Suçlu olan kütüphane olduğunu fark etmem uzun zaman aldı.
Andriy Drozdyuk

108

Python, sunduğunuz gibi "yöntem aşırı yüklemesini" destekliyor. Aslında, sadece açıkladığınız şey Python'da uygulamak için çok farklı şekillerde önemsizdir, ancak ben:

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

Yukarıdaki kodda, defaultbu bağımsız değişkenler için makul bir varsayılan değer veya None. Ardından yöntemi yalnızca ilgilendiğiniz bağımsız değişkenlerle çağırabilirsiniz ve Python varsayılan değerleri kullanır.

Bunun gibi bir şey de yapabilirsiniz:

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

Başka bir alternatif, istenen işlevi doğrudan sınıfa veya örneğe doğrudan bağlamaktır:

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

Yine başka bir yol da soyut bir fabrika modeli kullanmaktır:

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
Bütün bunlar aşırı yüklenmeden ziyade değişken argümanlarına örnek olarak görülmektedir. Aşırı yükleme, bağımsız değişkenlerle farklı türler için aynı işleve sahip olmanızı sağlar. örneğin: sum (real_num1, real_num2) ve sum (imaginary_num1, imaginary_num2) Her ikisi de aynı çağrı sözdizimine sahip olacak, ancak aslında giriş olarak 2 farklı tür bekliyor ve uygulamanın dahili olarak da değişmesi gerekiyor
Efren

17
Gittiğiniz yanıtı kullanarak, hangi argümanların birlikte anlamlı olduğunu arayan kişiye nasıl sunabilirsiniz? Her biri varsayılan bir değere sahip bir dizi argüman koymak aynı işlevselliği sağlayabilir, ancak API açısından çok daha az zariftir
Greg Ennis

6
Yukarıdakilerin hiçbiri aşırı yüklenmiyorsa, uygulamanın aşağıdaki gibi tüm parametre giriş kombinasyonlarını kontrol etmesi (veya parametreleri yoksayması) if sprite and script and not start and not direction and not speed...gerekecektir : sadece belirli bir eylemde olduğunu bilmek. çünkü bir arayan mevcut tüm parametreleri sağlayan fonksiyonu çağırabilir. Aşırı yüklenirken sizin için ilgili parametrelerin tam setlerini tanımlayın.
Roee Gavirel

5
İnsanlar python'un yöntem aşırı yüklemesini desteklediğini söylediğinde çok üzücü. O değil. Alıntılara "yöntem aşırı yüklemesi" koymanız, bu gerçeğin farkında olduğunuzu gösterir. Burada bahsedilen gibi birçok teknikle benzer işlevsellik elde edebilirsiniz. Ancak yöntem aşırı yüklemesinin çok spesifik bir tanımı vardır.
Howard Swope

Bence amaç yöntem aşırı yük python bir özelliği değil, yukarıdaki mekanizmalar eşdeğer etki elde etmek için kullanılabilir olduğunu düşünüyorum.
rawr

93

İşlev aşırı yüklemesi için "kendinize ait rulo" çözümünü kullanabilirsiniz. Bu Guido van Rossum'un multimethods hakkındaki yazısından kopyalanmıştır (çünkü python'da mm ve aşırı yükleme arasında çok az fark vardır):

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

Kullanımı olurdu

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

Şu anda en kısıtlayıcı sınırlamalar :

  • yöntemler desteklenmez, yalnızca sınıf üyesi olmayan işlevler;
  • kalıtım ele alınmaz;
  • kwarg'lar desteklenmez;
  • yeni işlevlerin kaydedilmesi, içe aktarma zamanında yapılmalıdır, iş parçacığı için güvenli değildir

6
Bu kullanım durumunda dili genişletmek için dekoratörler için +1.
Eloims

1
+1 çünkü bu harika bir fikir (ve muhtemelen OP'nin ne yapması gerektiği) --- Python'da hiç bir çok metrik uygulama görmemiştim.
Escualo

39

Olası bir seçenek, burada ayrıntılı olarak açıklandığı gibi, çoklu gönderim modülünü kullanmaktır: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Bunu yapmak yerine:

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

Bunu yapabilirsiniz:

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)

Ortaya çıkan kullanımla:

>>> add(1, 2)
3

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

4
Bu neden daha fazla oy almıyor? Örnek eksikliğinden dolayı tahmin ediyorum ... OP'nin multipledispatch paketiyle ilgili sorunun çözümünün nasıl uygulanacağına dair bir örnekle bir cevap oluşturdum .
Andriy Drozdyuk

19

Python 3.4'e PEP-0443 eklendi . Tek dağıtımlı genel işlevler .

İşte PEP'in kısa API açıklaması.

Genel bir işlev tanımlamak için, @singledispatch dekoratörü ile süsleyin. Gönderinin ilk argümanın türünde gerçekleştiğini unutmayın. İşlevinizi buna göre oluşturun:

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

İşleve aşırı yüklenmiş uygulamalar eklemek için genel işlevin register () özelliğini kullanın. Bu, bir tür parametresi alan ve bu tür için işlemi uygulayan bir işlevi süsleyen bir dekoratördür:

@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

Bu tür davranışlar tipik olarak (OOP dillerinde) Polimorfizm kullanılarak çözülür. Her mermi türü nasıl hareket ettiğini bilmekle yükümlüdür. Örneğin:

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

Varolan c_function'a çok sayıda argüman iletin, ardından ilk c işlevindeki değerlere göre hangi c işlevinin çağrılacağını belirleme işini yapın. Yani, python hiç bir zaman c fonksiyonunu çağırmalıdır. Bu bir c işlevi bağımsız değişkenlere bakar ve sonra diğer c işlevlerine uygun şekilde yetki verebilir.

Temelde her bir alt sınıfı farklı bir veri taşıyıcısı olarak kullanıyorsunuz, ancak temel sınıftaki tüm olası argümanları tanımlayarak, alt sınıflar hiçbir şey yapmadıklarını göz ardı etmekte serbesttirler.

Yeni bir madde işareti türü geldiğinde, temelde bir özellik daha tanımlayabilir, bir python işlevini ekstra özelliği geçecek şekilde ve bağımsız değişkenleri ve delegeleri uygun şekilde inceleyen bir c_function öğesini değiştirebilirsiniz. Çok kötü gelmiyor sanırım.


1
Bu benim ilk yaklaşımımdı, ancak performans nedenleriyle bu kodu C'de yeniden yazmak zorunda kaldım
Bullets

@Bullets, ben muhtemelen bir sürü yapmayacak c fonksiyonları bir sürü yazmak yerine performansı artırmak için kullanılabilir bir dizi farklı seçenek olabilir öneririz. Örneğin: bir örnek oluşturmak pahalı olabilir, bu nedenle bir nesne havuzunu koruyun. Ben bunu çok yavaş bulduğunu bilmeden söylememe rağmen. İlgi çekici olmayan bu yaklaşımla ilgili yavaş olan neydi? Sınırın C tarafında önemli bir zaman harcanmayacaksa, Python'un (kendisinin) gerçek sorun olduğunu düşünemiyorum.
Josh Smeaton

Belki performansı arttırmanın başka yolları da vardır, ancak C ile Python'dan çok daha iyiyim. Sorun, mermilerin hareketlerini hesaplamak ve ekran sınırlarının dışına çıktıklarını tespit etmekti. Merminin konumunu hesaplamak pos+v*tve sonra ekran sınırlarını karşılaştırmak için bir yöntemim if x > 800vardı. Bu işlevlerin kare başına birkaç yüz kez çağrılması kabul edilemez derecede yavaştı. C. yapılan zaman% 5 ile 60 fps% 10 saf piton% 100 işlemci 40 fps gibi bir şey
Mermiler

@Bullets, o zaman yeterince adil. Verileri kapsüllemek için hala kullandığım yaklaşımı kullanırdım. Bir madde işareti örneğini iletin add_bulletve ihtiyacınız olan tüm alanları çıkarın. Cevabımı düzenleyeceğim.
Josh Smeaton

@Bullets: C işlevlerinizi ve Josh tarafından önerilen OOP yaklaşımını Cython'u kullanarak birleştirebilirsiniz . Erken bağlanmaya izin verir, bu nedenle bir hız cezası olmamalıdır.
jfs


4

Tanımda birden fazla anahtar kelime bağımsız değişkeni kullanın veya Bulletörnekleri işleve iletilen bir hiyerarşi oluşturun .


İkinci yaklaşımı önerecektim: madde işareti ayrıntılarını belirtmek için bazı BulletParams ... sınıfları yap.
John Zwinck

Bu konuyu biraz açıklayabilir misiniz? Farklı madde işaretleri ile bir sınıf hiyerarşisi oluşturmaya çalıştım, ancak bu çalışmıyor, çünkü Python çok yavaş. Gerekli mermi sayısının hareketlerini yeterince hızlı hesaplayamıyor, bu yüzden bu kısmı C'ye yazmak zorunda kaldım. Tüm add_bullet varyantları sadece karşılık gelen C işlevini çağırır.
Bullets

4

Bence temel gereksiniminiz Python'da C / C ++ benzeri bir sözdizimine sahip olmak ve mümkün olan en az baş ağrısına sahip olmak. Alexander Poluektov'un cevabını sevmeme rağmen dersler için işe yaramıyor.

Sınıflar için aşağıdakiler çalışmalıdır. Anahtar kelime olmayan bağımsız değişkenlerin sayısına göre ayırt ederek çalışır (ancak türe göre ayırmayı desteklemez):

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

Ve sadece şöyle kullanılabilir:

test = TestOverloading()

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

Çıktı:

Bu aşırı yük 3
Sprite: Ben bir Sprite
Başlangıcı: 0
Yön: Sağ

Bu aşırı yük 2
Sprite: Ben başka bir Sprite
Script'im:
x == True: print 'hi'


4

@overloadDekoratör türü tavsiyeleri (PEP 484) ilave edildi. Bu, python'un davranışını değiştirmese de, neler olup bittiğini anlamayı ve mypy'nin hataları algılamasını kolaylaştırır.
Bakınız: Tip ipuçları ve PEP 484


Bazı örnekler ekleyebilir misiniz?
gerrit

3

Bence Bullet, ilişkili polimorfizm ile bir sınıf hiyerarşisi, gidilecek yol. Temel sınıfı çağırmak, uygun alt sınıf nesnesinin oluşturulmasına neden olacak şekilde bir metasınıf kullanarak temel sınıf yapıcısını etkili bir şekilde aşırı yükleyebilirsiniz. Aşağıda, ne demek istediğimin özünü gösteren bazı örnek kodlar verilmiştir.

Güncellenmiş

Kod, ilgili kalması için hem Python 2 hem de 3 altında çalışacak şekilde değiştirildi. Bu, Python'un iki sürüm arasında değişen açık metasınıf sözdiziminin kullanılmasını önleyecek şekilde yapıldı.

Bu hedefi gerçekleştirmek için BulletMetaBase, BulletMetasınıfın bir örneği, temel sınıfı oluştururken Bulletmetasınıfı açıkça çağırarak oluşturulur ( __metaclass__=class niteliğini kullanmak yerine veya metaclassPython sürümüne bağlı olarak bir anahtar kelime argümanı aracılığıyla ).

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

Çıktı:

creating Bullet1 object
creating Bullet2 object
creating Bullet3 object
creating Bullet4 object
Bullet1 object fired with uniform gravity trajectory
Traceback (most recent call last):
  File "python-function-overloading.py", line 93, in <module>
    b2.fire('uniform gravity') # NotImplementedError: Bullet2.fire() method
  File "python-function-overloading.py", line 49, in fire
    raise NotImplementedError(self.__class__.__name__ + ".fire() method")
NotImplementedError: Bullet2.fire() method

Hmm, işlevleri add_bullet1, add_bullet2 ve benzeri olarak adlandırmanın sadece süslü bir yoludur.
Bullets

1
@Bullets: Belki de, belki de bir fabrika işlevi oluşturmak için biraz ayrıntılı bir yol olabilir. Bununla ilgili güzel bir şey, bir hiyerarşisini desteklemesiBullet her bir alt tip eklediğinizde temel sınıfı veya fabrika işlevini değiştirmek zorunda kalmadan bir alt sınıf . (Elbette, C ++ yerine C kullanıyorsanız, sanırım sınıfınız yok.) Ayrıca tür ve / veya sayıya göre hangi alt sınıfı oluşturacağınızı kendi başınıza çözen daha akıllı bir metasınıf da yapabilirsiniz. (C ++ 'nın aşırı yüklemeyi desteklemek için yaptığı gibi).
martineau

1
Bu kalıtım fikri benim de ilk seçeneğim olacaktı.
Daniel Möller

3

Python 3.8 functools.singledispatchmethod ekledi

Bir yöntemi tek bir dağıtım genel işlevine dönüştürün.

Genel bir yöntem tanımlamak için, @singledispatchmethod dekoratörü ile süsleyin. Gönderimin ilk self olmayan veya cls olmayan argümanın türünde gerçekleştiğini unutmayın, işlevinizi buna göre oluşturun:

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

Çıktı

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

@singledispatchmethod @classmethod gibi diğer dekoratörler ile iç içe yerleştirmeyi destekler. Dispatcher.register öğesine izin vermek için, singledispatchmethod'un en dekoratör olması gerektiğini unutmayın. Neg metodları sınıfa bağlı olan Negator sınıfı:

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

Çıktı:

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

Aynı model diğer benzer dekoratörler için kullanılabilir: statik yöntem, soyut yöntem ve diğerleri.


2

Varsayılanlarla anahtar kelime bağımsız değişkenleri kullanın. Örneğin

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

Kavisli bir mermiye karşı düz bir mermi durumunda, iki işlev ekleyeceğim: add_bullet_straightve add_bullet_curved.


2

aşırı yükleme yöntemleri python zor. Ancak, diktenin, listenin veya ilkel değişkenlerin geçirilmesinin kullanımı olabilir.

Kullanım durumlarım için bir şey denedim, bu burada insanların yöntemleri aşırı yüklemelerini anlamaya yardımcı olabilir.

Örneğinizi ele alalım:

farklı sınıftan yöntemleri çağıran bir sınıf aşırı yük yöntemi.

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

uzak sınıftan bağımsız değişkenleri iletme:

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

VEYA

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

Böylece, yöntem aşırı yüklemesinden liste, Sözlük veya ilkel değişkenler için işlem gerçekleştirilmektedir.

kodlarınız için deneyin.


2

Sadece basit bir dekoratör

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)

Bu şekilde kullanabilirsiniz

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

Kullanım durumunuza uyarlamak için değiştirin.

Kavramların açıklığa kavuşturulması

  • işlev dağıtımı : aynı ada sahip birden çok işlev vardır. Hangisi çağrılmalıdır? iki strateji
  • statik / derleme zamanı gönderimi ( diğer adıyla "aşırı yükleme" ). bağımsız değişkenlerin derleme zamanı türüne göre hangi işlevi çağıracağınıza karar verin . Tüm dinamik dillerde, derleme zamanı türü yoktur, bu nedenle tanım gereği aşırı yükleme mümkün değildir
  • dinamik / çalışma zamanı gönderimi : bağımsız değişkenlerin çalışma zamanı türüne göre hangi işlevi çağıracağınıza karar verir . Tüm OOP dillerinin yaptığı budur: birden fazla sınıf aynı yöntemlere sahiptir ve dil, self/thisargümanın türüne göre hangisinin çağrılacağına karar verir . Ancak, çoğu dil bunu thisyalnızca argüman için yapar. Yukarıdaki dekoratör fikri birden fazla parametreye genişletir.

Temizlemek, statik bir dil kullanmak ve işlevleri tanımlamak için

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)

Statik sevkıyat (aşırı yükleme) ile, iki kez "olarak adlandırılan" ifadesini görürsünüz, çünkü xolarak bildirilmiştir Numberve bu aşırı yüklenmenin önemini gösterir. Dinamik dağıtımda "tamsayı çağrıldı, float çağrıldı" ifadesini göreceksiniz, çünkü bunlar xişlevin çağrıldığı andaki gerçek türlerdir .


Bu örnek , dinamik dağıtım için hangi yöntemin çağrıldığını xveya statik dağıtım için her iki yöntemin hangi sırayla çağrıldığını önemli ölçüde göstermez . Eğer yazdırma ifadeleri düzenlemek tavsiye print('number called for Integer')vs.
SMCI
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.