ฟังก์ชัน Python โอเวอร์โหลด


213

ฉันรู้ว่า Python ไม่รองรับการบรรทุกเกินพิกัด แต่ฉันพบปัญหาที่ฉันไม่สามารถแก้ไขได้ด้วยวิธี Pythonic ที่ดี

ฉันกำลังสร้างเกมที่ตัวละครต้องยิงกระสุนหลากหลายแบบ แต่ฉันจะเขียนฟังก์ชั่นต่าง ๆ เพื่อสร้างกระสุนได้อย่างไร ตัวอย่างเช่นสมมติว่าฉันมีฟังก์ชั่นที่สร้างสัญลักษณ์แสดงหัวข้อย่อยเดินทางจากจุด A ถึง B ด้วยความเร็วที่กำหนด ฉันจะเขียนฟังก์ชั่นเช่นนี้:

    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_bullet2add_bullet_with_really_long_name

วิธีตอบคำถาม:

  1. ไม่ฉันไม่สามารถสร้างลำดับชั้นของ Bullet เพราะนั่นช้าเกินไป รหัสจริงสำหรับการจัดการสัญลักษณ์แสดงหัวข้อย่อยอยู่ใน C และฟังก์ชั่นของฉันห่อรอบ C API

  2. ฉันรู้เกี่ยวกับอาร์กิวเมนต์ของคำหลัก แต่การตรวจสอบการรวมกันของพารามิเตอร์ทุกประเภทกำลังเริ่มน่ารำคาญ แต่อาร์กิวเมนต์เริ่มต้นช่วยได้เช่นกัน acceleration=0


5
ใช้งานได้สำหรับพารามิเตอร์เดียวเท่านั้น แต่ที่นี่ (สำหรับผู้ที่มาที่นี่จากเครื่องมือค้นหา): docs.python.org/3/library/…
leewz

1
ดูเหมือนว่าเป็นสถานที่ที่ดีสำหรับค่าเริ่มต้น คุณสามารถตั้งค่าเป็นไม่มีและเพียงตรวจสอบพวกเขา ผลกระทบทางบูลพิเศษดูเหมือนว่าจะไม่อาจเพิกเฉยได้
Andrew Scott Evans

ต้องใช้default value + if + elseเพื่อทำเช่นเดียวกับ C ++ ทำ นี้เป็นหนึ่งในไม่กี่สิ่งที่ C ++ มีการอ่านดีกว่างูหลาม ...
Deqing

ฉันสับสนว่าทำไม kwargs ไม่ใช่คำตอบที่ถูกต้อง คุณบอกว่าคุณไม่ต้องการใช้อาร์กิวเมนต์คำหลักหลายคำเพราะมันน่าเกลียดเร็ว ... และนั่นเป็นเพียงลักษณะของปัญหา หากคุณมีข้อโต้แย้งมากมายและมันยุ่งเพราะคุณมีข้อโต้แย้งมากมายกว่าที่คุณคาดไว้? คุณต้องการที่จะใช้ข้อโต้แย้งมากมายโดยไม่ต้องระบุที่ใดก็ได้ ??? Python ไม่ใช่ผู้อ่านใจ
แคลคูลัส

เราไม่รู้ว่าวัตถุประเภทscript, curveใดพวกเขามีบรรพบุรุษร่วมกันพวกเขาสนับสนุนวิธีใด ด้วยการพิมพ์เป็ดมันขึ้นอยู่กับคุณสำหรับการออกแบบชั้นเรียนเพื่อหาวิธีที่พวกเขาต้องการการสนับสนุน สันนิษฐานว่าScriptสนับสนุนการโทรกลับตามการประทับเวลาบางประเภท (แต่วัตถุใดที่ควรส่งคืนตำแหน่งที่การประทับเวลานั้นหรือเส้นทางการเคลื่อนที่ในการประทับเวลานั้น) สันนิษฐานstart, direction, speedและstart, headto, spead, accelerationทั้งสองอธิบายประเภทของวิถี แต่อีกครั้งก็ขึ้นอยู่กับคุณในการออกแบบชั้นรับที่จะรู้วิธีที่จะแกะพวกเขาและประมวลผลพวกเขา
smci

คำตอบ:


144

สิ่งที่คุณจะขอเรียกว่าหลายจัดส่ง เห็นจูเลียตัวอย่างภาษาที่แสดงให้เห็นประเภทของการแจกจ่ายต่าง ๆ

อย่างไรก็ตามก่อนที่จะดูอย่างนั้นเราจะแก้ไขปัญหาก่อนที่จะเกิดการโอเวอร์โหลดไม่ใช่สิ่งที่คุณต้องการในงูหลาม

ทำไมไม่บรรทุกเกินพิกัด?

ก่อนอื่นต้องเข้าใจแนวคิดของการโอเวอร์โหลดและทำไมมันจึงไม่สามารถใช้กับงูหลามได้

เมื่อทำงานกับภาษาที่สามารถแยกแยะชนิดข้อมูลในเวลารวบรวมการเลือกระหว่างทางเลือกสามารถเกิดขึ้นได้ในเวลารวบรวม การกระทำของการสร้างฟังก์ชั่นทางเลือกดังกล่าวสำหรับการเลือกเวลารวบรวมมักจะเรียกว่าการทำงานมากเกินไป ( Wikipedia )

Python เป็นภาษาที่พิมพ์แบบไดนามิกดังนั้นแนวคิดของการโอเวอร์โหลดก็ไม่สามารถใช้ได้กับมัน อย่างไรก็ตามทั้งหมดจะไม่สูญหายไปเนื่องจากเราสามารถสร้างฟังก์ชั่นทางเลือกดังกล่าวได้ในเวลาทำงาน:

ในภาษาการเขียนโปรแกรมที่เลื่อนการระบุชนิดข้อมูลไปจนถึงเวลาทำการการเลือกระหว่างฟังก์ชั่นทางเลือกจะต้องเกิดขึ้นในเวลาทำงานตามชนิดของอาร์กิวเมนต์ฟังก์ชันที่พิจารณาแบบไดนามิก ฟังก์ชั่นที่มีการเลือกการนำไปใช้ทางเลือกในลักษณะนี้จะถูกอ้างถึงโดยทั่วไปว่าเป็นมัลติวิธี ( Wikipedia )

ดังนั้นเราควรจะสามารถที่จะทำMultimethodsในหลามหรือที่เรียกว่าผลัด: หลายจัดส่งหลายจัดส่ง

หลายการจัดส่ง

วิธีการนี้เรียกว่าการกระจายหลายแบบ :

การจัดส่งหลายครั้งหรือหลายวิธีเป็นคุณสมบัติของภาษาการเขียนโปรแกรมเชิงวัตถุบางอย่างซึ่งฟังก์ชันหรือวิธีการสามารถจัดส่งแบบไดนามิกตามชนิดเวลารัน (ไดนามิก) ของอาร์กิวเมนต์มากกว่าหนึ่งรายการ ( Wikipedia )

Python ไม่รองรับสิ่งนี้จากกล่องที่1แต่อย่างที่มันเกิดขึ้นมีแพ็คเกจของหลามที่เรียกว่า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. ระวังอย่าใช้ multipledispatchในสภาพแวดล้อมแบบมัลติเธรดไม่เช่นนั้นคุณจะได้รับพฤติกรรมแปลก ๆ


6
ปัญหา 'multipledispatch' ในสภาพแวดล้อมแบบมัลติเธรดคืออะไร เนื่องจากรหัสของฝั่งเซิร์ฟเวอร์มักอยู่ในสภาพแวดล้อมแบบมัลติเธรด! แค่พยายามขุดออกมา!
danzeer

7
@danzeer มันไม่ปลอดภัยสำหรับเธรด ฉันเห็นการโต้แย้งที่ถูกแก้ไขโดยสองกระทู้ที่แตกต่างกัน (เช่นค่าของspeedอาจเปลี่ยนแปลงตรงกลางของฟังก์ชันเมื่อเธรดอื่นตั้งค่าเป็นค่าของspeed ) !!! ฉันใช้เวลานานในการรู้ว่ามันเป็นห้องสมุดที่เป็นตัวการ
Andriy Drozdyuk

108

Python สนับสนุน "method overloading" ตามที่คุณเสนอ ในความเป็นจริงสิ่งที่คุณอธิบายเพียงเล็กน้อยที่จะนำไปใช้ใน 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 มันไม่ค่อยสง่างามเท่าไหร่
Greg Ennis

6
สิ่งที่ไม่ได้กล่าวถึงข้างต้นคือการใช้งานมากเกินไปการใช้งานจะต้องตรวจสอบการรวมกันทั้งหมดของอินพุตพารามิเตอร์ (หรือละเว้นพารามิเตอร์) เช่น: if sprite and script and not start and not direction and not speed...เพียงแค่รู้ว่ามันเป็นการกระทำที่เฉพาะเจาะจง เนื่องจากผู้เรียกสามารถเรียกใช้ฟังก์ชันที่จัดเตรียมพารามิเตอร์ทั้งหมดที่มี ในขณะที่มากไปกำหนดสำหรับคุณชุดที่แน่นอนของพารามิเตอร์ที่เกี่ยวข้อง
Roee Gavirel

5
มันเป็นเรื่องที่น่าเสียใจมากเมื่อมีคนบอกว่าหลามสนับสนุนวิธีการโอเวอร์โหลด มันไม่ใช่. ความจริงที่ว่าคุณใส่ "วิธีการมากไป" ในใบเสนอราคาบ่งชี้ว่าคุณตระหนักถึงความจริงนี้ คุณสามารถใช้ฟังก์ชั่นที่คล้ายกันได้หลายเทคนิคเช่นเดียวกับที่กล่าวถึงในที่นี้ แต่วิธีการบรรทุกเกินพิกัดมีคำจำกัดความที่เฉพาะเจาะจงมาก
Howard Swope

ฉันคิดว่าจุดมุ่งหมายคือในขณะที่วิธีการบรรทุกเกินพิกัดไม่ใช่คุณสมบัติของหลาม แต่กลไกดังกล่าวสามารถใช้เพื่อให้ได้ผลที่เท่าเทียมกัน
rawr rang

93

คุณสามารถใช้วิธีการแก้ปัญหา "ม้วนของคุณเอง" สำหรับฟังก์ชั่นการโหลดมากเกินไป อันนี้ถูกคัดลอกมาจากบทความของ Guido van Rossumเกี่ยวกับมัลติวิธี (เนื่องจากมีความแตกต่างเล็กน้อยระหว่างมม. และการโอเวอร์โหลดในไพ ธ อน):

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 สำหรับผู้ตกแต่งเพื่อขยายภาษาในกรณีใช้งานนี้
Eloims

1
+1 เพราะนี่เป็นความคิดที่ดี (และอาจเป็นสิ่งที่ OP ควรทำ) --- ฉันไม่เคยเห็นการใช้งานแบบหลายวิธีใน Python
Escualo

39

ตัวเลือกที่เป็นไปได้คือการใช้โมดูล multipledispatch ตามรายละเอียดที่นี่: 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
ทำไมสิ่งนี้ถึงไม่ได้รับการโหวตมากกว่านี้? ฉันคาดเดาเนื่องจากไม่มีตัวอย่าง ... ฉันได้สร้างคำตอบพร้อมตัวอย่างวิธีใช้โซลูชันของปัญหา OP ด้วยแพ็คเกจหลายระดับ
Andriy Drozdyuk

19

ใน Python 3.4 ถูกเพิ่มPEP-0443 ฟังก์ชั่นทั่วไปการจัดส่งเดี่ยวฟังก์ชั่นทั่วไปเดี่ยวจัดส่ง

นี่คือคำอธิบาย API แบบย่อจาก PEP

ในการกำหนดฟังก์ชั่นทั่วไปให้ตกแต่งด้วย @singledispatch decorator โปรดทราบว่าการจัดส่งเกิดขึ้นกับประเภทของอาร์กิวเมนต์แรก สร้างฟังก์ชั่นของคุณตามนั้น:

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

โดยทั่วไปพฤติกรรมประเภทนี้จะได้รับการแก้ไข (ในภาษา OOP) โดยใช้ Polymorphism สัญลักษณ์แสดงหัวข้อย่อยแต่ละประเภทจะต้องรับผิดชอบในการทราบวิธีการเดินทาง ตัวอย่างเช่น

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 เริ่มต้น ดังนั้นไพ ธ อนควรจะเรียกใช้ฟังก์ชัน c ตัวเดียวเท่านั้น ฟังก์ชัน c หนึ่งนั้นพิจารณาอาร์กิวเมนต์แล้วสามารถมอบหมายฟังก์ชัน c อื่นได้อย่างเหมาะสม

คุณเพียงแค่ใช้คลาสย่อยแต่ละคลาสเป็นที่เก็บข้อมูลที่แตกต่างกัน แต่โดยการกำหนดอาร์กิวเมนต์ที่เป็นไปได้ทั้งหมดในคลาสพื้นฐานคลาสย่อยนั้นมีอิสระที่จะเพิกเฉยต่อสิ่งที่พวกเขาไม่ได้ทำ

เมื่อสัญลักษณ์แสดงหัวข้อย่อยชนิดใหม่เข้ามาคุณสามารถกำหนดคุณสมบัติเพิ่มเติมหนึ่งรายการบนฐานเปลี่ยนฟังก์ชันหลามหนึ่งเพื่อให้ผ่านคุณสมบัติพิเศษและหนึ่ง c_function ที่ตรวจสอบข้อโต้แย้งและผู้ได้รับมอบหมายอย่างเหมาะสม ไม่ได้ฟังดูแย่เกินไปฉันเดา


1
นั่นเป็นวิธีการเริ่มต้นของฉัน แต่ด้วยเหตุผลด้านประสิทธิภาพฉันต้องเขียนโค้ดนั้นใหม่ใน C
Bullets

@Bullets ฉันอยากจะแนะนำว่าอาจมีตัวเลือกต่าง ๆ มากมายเพื่อปรับปรุงประสิทธิภาพแทนที่จะเขียนฟังก์ชั่น c จำนวนมากซึ่งอาจจะไม่ได้ทำอะไรมากมายเลย ตัวอย่างเช่น: การสร้างอินสแตนซ์อาจมีราคาแพงดังนั้นควรดูแลกลุ่มวัตถุ แม้ว่าฉันจะพูดแบบนี้โดยที่ไม่รู้ว่าคุณคิดว่าอะไรช้าเกินไป จากความสนใจสิ่งที่ช้าเกี่ยวกับวิธีการนี เว้นแต่จะใช้เวลาอย่างมีนัยสำคัญในฝั่ง C ของขอบเขตฉันไม่สามารถคิดได้ว่า Python (ตัวเอง) เป็นปัญหาจริง
Josh Smeaton

อาจมีวิธีอื่นในการปรับปรุงประสิทธิภาพ แต่ฉันดีกว่า C มากกว่ากับ Python ปัญหาคือการคำนวณการเคลื่อนไหวของกระสุนและการตรวจสอบเมื่อพวกเขาออกไปนอกขอบเขต ฉันมีวิธีคำนวณตำแหน่งของสัญลักษณ์แสดงหัวข้อย่อยpos+v*tแล้วเปรียบเทียบกับขอบเขตหน้าจอif x > 800และอื่น ๆ การเรียกใช้ฟังก์ชั่นเหล่านี้หลายร้อยครั้งต่อเฟรมปรากฏว่าช้าจนไม่อาจยอมรับได้ มันเป็นอะไรบางอย่างที่ 40 fps ที่ 100% cpu กับ python บริสุทธิ์ถึง 60 fps กับ 5% -10% เมื่อทำใน C
กระสุน

@Bullets ยุติธรรมเพียงพอแล้ว ฉันยังคงใช้วิธีการที่ฉันใช้ในการห่อหุ้มข้อมูล ส่งตัวอย่างของสัญลักษณ์แสดงหัวข้อย่อยadd_bulletและแยกเขตข้อมูลทั้งหมดที่คุณต้องการ ฉันจะแก้ไขคำตอบของฉัน
Josh Smeaton

@Bullets: คุณสามารถรวมฟังก์ชั่น C ของคุณและวิธี OOP ที่แนะนำโดยจอชโดยใช้Cython อนุญาตให้ใช้การรวมก่อนหน้าดังนั้นจึงไม่ควรถูกปรับความเร็ว
jfs


4

ใช้อาร์กิวเมนต์คำหลักหลายคำในนิยามหรือสร้างBulletลำดับชั้นที่อินสแตนซ์ถูกส่งผ่านไปยังฟังก์ชัน


ฉันจะแนะนำวิธีที่สอง: ทำ BulletParams ... คลาสเพื่อระบุรายละเอียดของ bullet
John Zwinck

คุณสามารถทำอย่างละเอียดเกี่ยวกับเรื่องนี้? ฉันพยายามสร้างลำดับชั้นของคลาสที่มีสัญลักษณ์แสดงหัวข้อย่อยต่างกัน แต่วิธีนี้ไม่ได้ผลเนื่องจาก Python ช้าเกินไป มันไม่สามารถคำนวณการเคลื่อนที่ของจำนวนกระสุนที่ต้องการเร็วพอฉันจึงต้องเขียนส่วนนั้นใน C ตัวแปรทั้งหมดของ add_bullet เพียงแค่เรียกฟังก์ชัน C ที่สอดคล้องกัน
กระสุน

4

ฉันคิดว่าความต้องการขั้นพื้นฐานของคุณคือมี C / C ++ เหมือนซินแท็กซ์ในไพ ธ อนและปวดหัวน้อยที่สุด แม้ว่าฉันชอบคำตอบของ Alexander Poluektov แต่ก็ไม่ได้ผลกับชั้นเรียน

ต่อไปนี้ควรใช้กับคลาส ทำงานโดยแยกความแตกต่างด้วยจำนวนอาร์กิวเมนต์ที่ไม่ใช่คำหลัก (แต่ไม่รองรับแยกตามประเภท):

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
เริ่ม: 0
ทิศทาง: ใช่

นี่คือ overload 2
Sprite: ฉันเป็นอีกหนึ่ง Sprite
Script:
ในขณะที่ x == จริง: พิมพ์ 'hi'


4

มีการ@overloadเพิ่มเครื่องมือตกแต่งด้วยคำแนะนำประเภท (PEP 484) แม้ว่าสิ่งนี้จะไม่เปลี่ยนพฤติกรรมของงูหลาม แต่ก็ช่วยให้เข้าใจได้ง่ายขึ้นว่าเกิดอะไรขึ้นและสำหรับ mypy เพื่อตรวจหาข้อผิดพลาด
ดู: พิมพ์คำใบ้และPEP 484


คุณสามารถเพิ่มตัวอย่างได้ไหม?
gerrit

3

ฉันคิดว่าBulletลำดับชั้นของชั้นเรียนที่มีความหลากหลายเป็นวิธีที่จะไป คุณสามารถโอเวอร์โหลดคลาสคลาสพื้นฐานได้อย่างมีประสิทธิภาพโดยใช้เมตาคลาสเพื่อให้การเรียกคลาสพื้นฐานส่งผลให้เกิดการสร้างออบเจ็กต์คลาสย่อยที่เหมาะสม ด้านล่างนี้คือตัวอย่างรหัสบางส่วนเพื่อแสดงให้เห็นถึงสาระสำคัญของสิ่งที่ฉันหมายถึง

Updated

รหัสได้รับการแก้ไขให้ทำงานภายใต้ทั้ง Python 2 และ 3 เพื่อให้มันมีความเกี่ยวข้อง สิ่งนี้ทำในลักษณะที่หลีกเลี่ยงการใช้ไวยากรณ์เมตาคลาสอย่างชัดเจนของ Python ซึ่งแตกต่างกันระหว่างสองเวอร์ชัน

เพื่อให้บรรลุวัตถุประสงค์นั้นBulletMetaBaseอินสแตนซ์ของBulletMetaคลาสนั้นถูกสร้างขึ้นโดยการเรียกเมตาคลาสอย่างชัดเจนเมื่อสร้างBulletbaseclass (แทนที่จะใช้แอ__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 ++ ฉันคิดว่าคุณไม่มีคลาส) คุณสามารถสร้าง metaclass ที่ชาญฉลาดซึ่งคิดได้เองว่าจะสร้างซับคลาสอะไรขึ้นอยู่กับประเภทและ / หรือตัวเลข ของอาร์กิวเมนต์ที่ส่งผ่าน (เช่น C ++ ทำเพื่อสนับสนุนการโหลดมากเกินไป)
martineau

1
แนวคิดการสืบทอดนี้จะเป็นตัวเลือกแรกของฉันเช่นกัน
Daniel Möller

3

Python 3.8 เพิ่มfunctools.singledispatchmethod

เปลี่ยนวิธีการให้เป็นฟังก์ชั่นทั่วไปแบบกระจายเดียว

ในการกำหนดวิธีการทั่วไปให้ตกแต่งด้วยเครื่องมือตกแต่ง @singledispatchmethod โปรดทราบว่าการจัดส่งเกิดขึ้นกับประเภทของอาร์กิวเมนต์ที่ไม่ใช่ตัวเองหรือไม่ใช่ 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 โปรดทราบว่าการอนุญาตสำหรับ dispatcher.register, singledispatchmethod ต้องเป็น decorator ส่วนใหญ่ภายนอก นี่คือคลาส 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

รูปแบบเดียวกันสามารถใช้สำหรับผู้ตกแต่งที่คล้ายกันอื่น ๆ : staticmethod, abstractmethod และอื่น ๆ


2

ใช้อาร์กิวเมนต์คำหลักที่มีค่าเริ่มต้น เช่น

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

ในกรณีของกระสุนตรงกับกระสุนโค้งผมเพิ่มสองฟังก์ชั่น: และadd_bullet_straightadd_bullet_curved


2

วิธีการมากไปเป็นเรื่องยุ่งยากในหลาม อย่างไรก็ตามอาจมีการใช้งานผ่านการเขียนตามคำบอกรายการหรือตัวแปรดั้งเดิม

ฉันได้ลองทำบางสิ่งสำหรับกรณีการใช้งานของฉันนี่อาจช่วยให้ผู้คนเข้าใจวิธีการใช้งานเกินพิกัดได้

ลองยกตัวอย่างของคุณ:

เมธอด overload คลาสพร้อมเรียกเมธอดจากคลาสอื่น

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)

ปรับเปลี่ยนเพื่อปรับให้เข้ากับกรณีการใช้งานของคุณ

การอธิบายแนวคิด

  • ฟังก์ชั่นการจัดส่ง : มีหลายฟังก์ชั่นที่มีชื่อเดียวกัน ควรเลือกแบบไหน สองกลยุทธ์
  • การส่งแบบสแตติก / เวลาคอมไพล์ ( aka. "overloading" ) ตัดสินใจว่าจะเรียกฟังก์ชันใดตามชนิดเวลารวบรวมของอาร์กิวเมนต์ ในภาษาแบบไดนามิกทั้งหมดไม่มีประเภทเวลาคอมไพล์ดังนั้นจึงไม่สามารถทำการโอเวอร์โหลดได้ตามคำจำกัดความ
  • การจัดส่งแบบไดนามิก / รันไทม์ : ตัดสินใจว่าจะเรียกใช้ฟังก์ชันใดตามชนิดรันไทม์ของอาร์กิวเมนต์ นี่คือสิ่งที่ทุกภาษา 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.