Quá tải hàm Python


212

Tôi biết rằng Python không hỗ trợ quá tải phương thức, nhưng tôi đã gặp phải một vấn đề mà dường như tôi không thể giải quyết theo cách tốt đẹp của Pythonic.

Tôi đang làm một trò chơi trong đó một nhân vật cần phải bắn nhiều loại đạn khác nhau, nhưng làm thế nào để tôi viết các chức năng khác nhau để tạo ra những viên đạn này? Ví dụ, giả sử tôi có một chức năng tạo ra một viên đạn đi từ điểm A đến B với một tốc độ nhất định. Tôi sẽ viết một chức năng như thế này:

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

Nhưng tôi muốn viết các chức năng khác để tạo đạn như:

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

Và như vậy với nhiều biến thể. Có cách nào tốt hơn để làm điều đó mà không sử dụng quá nhiều đối số từ khóa khiến nó trở nên xấu đi nhanh chóng. Đổi tên mỗi chức năng là xấu đẹp quá bởi vì bạn sẽ có được một trong hai add_bullet1, add_bullet2hoặc add_bullet_with_really_long_name.

Để giải quyết một số câu trả lời:

  1. Không, tôi không thể tạo cấu trúc phân cấp lớp Bullet vì quá chậm. Mã thực tế để quản lý đạn nằm trong C và các chức năng của tôi là các hàm bao quanh C API.

  2. Tôi biết về các đối số từ khóa nhưng việc kiểm tra tất cả các loại kết hợp tham số đang gây khó chịu, nhưng các đối số mặc định giúp phân bổ như acceleration=0


5
Công trình cho chỉ có một tham số, nhưng ở đây (cho những người đến đây từ một công cụ tìm kiếm): docs.python.org/3/library/...
leewz

1
đây có vẻ là một nơi tốt cho các giá trị mặc định bạn có thể đặt một số thành Không và chỉ cần kiểm tra chúng. tác động boolean thêm có vẻ không đáng kể
Andrew Scott Evans

Phải dùng default value + if + elseđể làm giống như C ++. Đây là một trong số rất ít thứ mà C ++ có khả năng đọc tốt hơn Python ...
De Khánh

Tôi bối rối về lý do tại sao kwargs không phải là một câu trả lời hợp lệ. Bạn nói rằng bạn không muốn sử dụng nhiều đối số từ khóa vì nó trở nên xấu nhanh ... đó chỉ là bản chất của vấn đề. Nếu bạn có nhiều đối số và nó lộn xộn vì bạn có nhiều đối số hơn những gì bạn mong đợi? Bạn có muốn sử dụng nhiều đối số mà không chỉ định chúng ở bất cứ đâu ??? Python không phải là một người đọc tâm trí.
Giải tích

Chúng ta không biết loại vật thể script, curvenày là gì, chúng có tổ tiên chung không, chúng hỗ trợ phương pháp nào. Với kiểu gõ vịt, tùy thuộc vào thiết kế lớp của bạn để tìm ra phương pháp nào họ cần hỗ trợ. Có lẽ Scripthỗ trợ một số loại gọi lại dựa trên dấu thời gian (nhưng đối tượng nào sẽ trả về? Vị trí tại dấu thời gian đó? Quỹ đạo tại dấu thời gian đó?). Có lẽ start, direction, speedstart, headto, spead, accelerationcả hai đều mô tả các loại quỹ đạo, nhưng một lần nữa, tùy thuộc vào bạn để thiết kế lớp nhận để biết cách giải nén chúng và xử lý chúng.
smci

Câu trả lời:


143

Những gì bạn đang yêu cầu được gọi là nhiều công văn . Xem các ví dụ ngôn ngữ Julia thể hiện các loại công văn khác nhau.

Tuy nhiên, trước khi xem xét điều đó, trước tiên chúng ta sẽ giải quyết tại sao quá tải không thực sự là điều bạn muốn ở python.

Tại sao không quá tải?

Đầu tiên, người ta cần hiểu khái niệm quá tải và tại sao nó không áp dụng được với trăn.

Khi làm việc với các ngôn ngữ có thể phân biệt các loại dữ liệu tại thời gian biên dịch, việc chọn trong số các lựa chọn thay thế có thể xảy ra tại thời gian biên dịch. Hành động tạo các hàm thay thế như vậy để lựa chọn thời gian biên dịch thường được gọi là quá tải một hàm. ( Wikipedia )

Python là một ngôn ngữ được gõ động , vì vậy khái niệm quá tải đơn giản là không áp dụng cho nó. Tuy nhiên, tất cả không bị mất, vì chúng ta có thể tạo các chức năng thay thế như vậy vào thời gian chạy:

Trong các ngôn ngữ lập trình trì hoãn nhận dạng kiểu dữ liệu cho đến thời gian chạy, việc lựa chọn giữa các hàm thay thế phải xảy ra vào thời gian chạy, dựa trên các loại đối số hàm được xác định động. Các hàm có triển khai thay thế được chọn theo cách này được gọi chung là đa phương thức . ( Wikipedia )

Vì vậy, chúng ta sẽ có thể làm multimethods trong python-hay, vì nó là cách khác gọi là: nhiều công văn .

Nhiều công văn

Các multimethod cũng được gọi là nhiều công văn :

Nhiều công văn hoặc đa phương thức là tính năng của một số ngôn ngữ lập trình hướng đối tượng, trong đó một chức năng hoặc phương thức có thể được gửi động dựa trên loại thời gian chạy (động) của nhiều hơn một đối số của nó. ( Wikipedia )

Python không hỗ trợ điều này trong hộp 1 , nhưng, như nó xảy ra, có một gói python tuyệt vời được gọi là Multipledispatch thực hiện chính xác điều đó.

Giải pháp

Dưới đây là cách chúng tôi có thể sử dụng gói đa nhân 2 để thực hiện các phương thức của bạn:

>>> 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 hiện hỗ trợ công văn đơn
2. Cẩn thận không sử dụng đa nhân trong môi trường đa luồng nếu không bạn sẽ có hành vi kỳ lạ.


6
Vấn đề với 'bội số' trong môi trường đa luồng là gì? Vì mã máy chủ-mã thường ở trong môi trường đa luồng! Chỉ cần cố gắng để đào nó ra!
danzeer

7
@danzeer Nó không an toàn cho chủ đề. Tôi thấy đối số được sửa đổi bởi hai luồng khác nhau (nghĩa là giá trị của speedcó thể thay đổi ở giữa hàm khi một luồng khác đặt giá trị riêng của nó speed) !!! Phải mất một thời gian dài tôi mới nhận ra rằng chính thư viện là thủ phạm.
Andriy Drozdyuk

108

Python không hỗ trợ "nạp chồng phương thức" như bạn trình bày. Trong thực tế, những gì bạn vừa mô tả là tầm thường để thực hiện trong Python, theo nhiều cách khác nhau, nhưng tôi sẽ đi theo:

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

Trong đoạn mã trên, defaultlà một giá trị mặc định hợp lý cho các đối số đó, hoặc None. Sau đó, bạn có thể gọi phương thức chỉ với các đối số bạn quan tâm và Python sẽ sử dụng các giá trị mặc định.

Bạn cũng có thể làm một cái gì đó như thế này:

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

Một cách khác là nối trực tiếp hàm mong muốn trực tiếp vào lớp hoặc thể hiện:

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

Một cách khác là sử dụng một mô hình nhà máy trừu tượng:

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
Tất cả những thứ này trông giống như các ví dụ về các đối số biến, thay vì quá tải. Vì quá tải cho phép bạn có cùng chức năng, cho các loại khác nhau làm đối số. ví dụ: sum (real_num1, real_num2) và sum (fantinary_num1, fantinary_num2) Cả hai sẽ có cùng một cú pháp gọi, nhưng thực sự đang mong đợi 2 loại khác nhau làm đầu vào và việc triển khai cũng phải thay đổi trong nội bộ
Efren

17
Sử dụng câu trả lời bạn sẽ đi cùng, làm thế nào bạn sẽ trình bày cho người gọi mà các đối số có ý nghĩa với nhau? Chỉ cần đặt một loạt các đối số với mỗi giá trị mặc định có thể cung cấp cùng chức năng nhưng về mặt API thì nó kém thanh lịch hơn nhiều
Greg Enni

6
Không phải ở trên là quá tải, việc thực hiện sẽ phải kiểm tra tất cả các kết hợp của các tham số đầu vào (hoặc bỏ qua các tham số) như: if sprite and script and not start and not direction and not speed...chỉ để biết nó đang ở trong một hành động cụ thể. bởi vì một người gọi có thể gọi hàm cung cấp tất cả các tham số có sẵn. Trong khi quá tải xác định cho bạn các bộ chính xác của các tham số có liên quan.
Roee Gavirel

5
Thật khó chịu khi mọi người nói rằng python hỗ trợ quá tải phương thức. Nó không. Việc bạn đặt "quá tải phương thức" trong trích dẫn cho thấy rằng bạn biết về thực tế này. Bạn có thể có được chức năng tương tự với một số kỹ thuật, như một kỹ thuật được đề cập ở đây. Nhưng quá tải phương pháp có một định nghĩa rất cụ thể.
Howard Swope

Tôi nghĩ rằng điểm dự định là trong khi quá tải phương thức không phải là một tính năng của python, các cơ chế trên có thể được sử dụng để đạt được hiệu quả tương đương.
rawr reo

93

Bạn có thể sử dụng giải pháp "roll-your-own" cho chức năng nạp chồng. Cái này được sao chép từ bài viết của Guido van Rossum về multimethods (vì có rất ít sự khác biệt giữa mm và quá tải trong 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

Việc sử dụng sẽ là

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

Hầu hết các hạn chế hạn chế tại thời điểm này là:

  • các phương thức không được hỗ trợ, chỉ các hàm không phải là thành viên lớp;
  • thừa kế không được xử lý;
  • kwargs không được hỗ trợ;
  • Đăng ký các chức năng mới nên được thực hiện tại thời điểm nhập khẩu không phải là chủ đề an toàn

6
+1 cho các nhà trang trí để mở rộng ngôn ngữ trong trường hợp sử dụng này.
Eloims

1
+1 vì đây là một ý tưởng tuyệt vời (và có lẽ là những gì OP nên làm với) --- Tôi chưa bao giờ thấy một triển khai đa phương thức trong Python.
Escualo

39

Một tùy chọn có thể là sử dụng mô-đun đa nhân như chi tiết tại đây: http://matthewrocklin.com/blog/work/2014/02/25/Multipl-Dispatch

Thay vì làm điều này:

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

Bạn có thể làm được việc này:

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)

Với việc sử dụng kết quả:

>>> add(1, 2)
3

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

4
Tại sao điều này không nhận được nhiều phiếu bầu? Tôi đoán là do thiếu ví dụ ... Tôi đã tạo một câu trả lời với một ví dụ về cách triển khai giải pháp cho vấn đề của OP với gói đa nhân .
Andriy Drozdyuk 17/03/2015

19

Trong Python 3,4 đã được thêm PEP-0443. Chức năng chung đơn gửi .

Đây là mô tả API ngắn từ PEP.

Để xác định một hàm chung, hãy trang trí nó bằng trình trang trí @singledispatch. Lưu ý rằng công văn xảy ra trên loại đối số đầu tiên. Tạo chức năng của bạn phù hợp:

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

Để thêm các cài đặt quá tải vào hàm, hãy sử dụng thuộc tính register () của hàm chung. Đây là một trình trang trí, lấy tham số kiểu và trang trí một hàm thực hiện thao tác cho kiểu đó:

@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

Loại hành vi này thường được giải quyết (bằng ngôn ngữ OOP) bằng cách sử dụng Đa hình. Mỗi loại đạn sẽ chịu trách nhiệm cho việc nó di chuyển như thế nào. Ví dụ:

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

Truyền càng nhiều đối số cho c_factor tồn tại, sau đó thực hiện công việc xác định hàm c nào sẽ gọi dựa trên các giá trị trong hàm c ban đầu. Vì vậy, python chỉ nên gọi hàm c. Hàm một c đó xem xét các đối số và sau đó có thể ủy quyền cho các hàm c khác một cách thích hợp.

Về cơ bản, bạn chỉ sử dụng mỗi lớp con như một thùng chứa dữ liệu khác nhau, nhưng bằng cách xác định tất cả các đối số tiềm năng trên lớp cơ sở, các lớp con có thể tự do bỏ qua những lớp mà chúng không làm gì với chúng.

Khi một loại đạn mới xuất hiện, bạn có thể chỉ cần xác định thêm một thuộc tính trên cơ sở, thay đổi một hàm python để nó chuyển thuộc tính bổ sung và một c_feft kiểm tra các đối số và ủy nhiệm một cách thích hợp. Tôi đoán không tệ lắm.


1
Đó là cách tiếp cận ban đầu của tôi, nhưng vì lý do hiệu suất, tôi đã phải viết lại mã đó trong C.
Bullets

@Bullets, tôi sẽ đề xuất rằng có thể có một số tùy chọn khác nhau có sẵn để cải thiện hiệu suất thay vì viết toàn bộ nhiều chức năng c mà có lẽ sẽ không thực hiện được nhiều. Ví dụ: tạo một thể hiện có thể tốn kém, vì vậy hãy duy trì một nhóm đối tượng. Mặc dù tôi nói điều này mà không biết những gì bạn thấy là quá chậm. Không quan tâm, chính xác thì điều gì đã chậm về cách tiếp cận này? Trừ khi thời gian đáng kể sẽ được dành ở phía C của ranh giới, tôi không thể nghĩ rằng Python (chính nó) là vấn đề thực sự.
Josh Smeaton

Có thể có nhiều cách khác để cải thiện hiệu suất, nhưng tôi tốt hơn với C so với Python. Vấn đề là tính toán chuyển động của viên đạn và phát hiện khi chúng đi ra khỏi giới hạn màn hình. Tôi đã có một phương pháp tính toán vị trí của viên đạn pos+v*tvà sau đó so sánh với các ranh giới màn hình if x > 800, v.v. Gọi các chức năng này vài trăm lần trên mỗi khung hình hóa ra là chậm không thể chấp nhận được. Nó giống như 40 khung hình / giây ở cpu 100% với con trăn thuần đến 60 khung hình / giây với 5% -10% khi được thực hiện trong C.
Đạn

@Bullets, đủ công bằng rồi. Tôi vẫn sẽ sử dụng phương pháp tôi đã thực hiện để đóng gói dữ liệu. Truyền một ví dụ về dấu đầu dòng add_bulletvà trích xuất tất cả các trường mà bạn cần. Tôi sẽ chỉnh sửa câu trả lời của tôi.
Josh Smeaton

@Bullets: Bạn có thể kết hợp các chức năng C của mình và phương pháp OOP được đề xuất bởi Josh bằng Cython . Nó cho phép ràng buộc sớm vì vậy không nên có một hình phạt tốc độ.
jfs


4

Hoặc sử dụng nhiều đối số từ khóa trong định nghĩa hoặc tạo một Bullethệ thống phân cấp có phiên bản được truyền cho hàm.


Tôi sẽ đề xuất cách tiếp cận thứ hai: tạo một số lớp BulletParams ... để xác định chi tiết viên đạn.
John Zwinck

bạn có thể xây dựng trên này? Tôi đã cố gắng tạo một hệ thống phân cấp lớp với các loại đạn khác nhau nhưng điều này không hoạt động, vì Python quá chậm. Nó không thể tính toán chuyển động của số lượng đạn cần thiết đủ nhanh, vì vậy tôi đã phải viết phần đó trong C. Tất cả các biến thể add_bONS chỉ cần gọi hàm C tương ứng.
Đạn

4

Tôi nghĩ rằng yêu cầu cơ bản của bạn là có cú pháp giống C / C ++ trong python với ít đau đầu nhất có thể. Mặc dù tôi thích câu trả lời của Alexander Poluektov nhưng nó không hoạt động cho các lớp học.

Sau đây nên làm việc cho các lớp học. Nó hoạt động bằng cách phân biệt theo số lượng đối số không phải từ khóa (nhưng không hỗ trợ phân biệt theo loại):

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

Và nó có thể được sử dụng đơn giản như thế này:

test = TestOverloading()

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

Đầu ra:

Đây là quá tải 3
Sprite: Tôi là Sprite
Bắt đầu: 0
Hướng: Phải

Đây là quá tải 2
Sprite: Tôi là một Sprite
Script khác:
while x == True: print 'hi'


4

Trình @overloadtrang trí đã được thêm vào với các gợi ý loại (PEP 484). Mặc dù điều này không thay đổi hành vi của python, nhưng nó giúp dễ hiểu hơn những gì đang xảy ra và để mypy phát hiện lỗi.
Xem: Gợi ý nhậpPEP 484


Bạn có thể thêm một số ví dụ?
gerrit

3

Tôi nghĩ rằng một Bullethệ thống phân cấp lớp với đa hình liên quan là cách để đi. Bạn có thể quá tải hiệu quả trình xây dựng lớp cơ sở bằng cách sử dụng siêu dữ liệu để gọi lớp cơ sở dẫn đến việc tạo đối tượng lớp con thích hợp. Dưới đây là một số mã mẫu để minh họa bản chất của những gì tôi muốn nói.

Đã cập nhật

Mã này đã được sửa đổi để chạy theo cả Python 2 và 3 để giữ cho nó phù hợp. Điều này được thực hiện theo cách tránh sử dụng cú pháp siêu dữ liệu rõ ràng của Python, thay đổi giữa hai phiên bản.

Để thực hiện mục tiêu đó, một BulletMetaBasethể hiện của BulletMetalớp được tạo bằng cách gọi rõ ràng siêu dữ liệu khi tạo lớp cơ sở Bullet(thay vì sử dụng __metaclass__=thuộc tính lớp hoặc thông qua một metaclassđối số từ khóa tùy thuộc vào phiên bản 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')

Đầu ra:

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 đây vẫn chỉ là một cách ưa thích để đặt tên cho các hàm là add_bONS1, add_bONS2, v.v.
Đạn

1
@Bullets: Có lẽ, hoặc có thể đó chỉ là một cách hơi phức tạp để tạo ra một chức năng của nhà máy. Một điều tốt đẹp về nó là nó hỗ trợ một hệ thống phân cấp của các Bulletlớp con mà không phải sửa đổi lớp cơ sở hoặc hàm xuất xưởng mỗi khi bạn thêm một kiểu con khác. (Tất nhiên, nếu bạn đang sử dụng C thay vì C ++, tôi đoán bạn không có các lớp.) Bạn cũng có thể tạo một siêu dữ liệu thông minh hơn để tìm ra lớp con nào để tạo dựa trên loại và / hoặc số của các đối số được thông qua (như C ++ không hỗ trợ quá tải).
martineau

1
Ý tưởng kế thừa này cũng sẽ là lựa chọn đầu tiên của tôi.
Daniel Möller

3

Python 3,8 đã thêm funcools.singledispatchmethod

Chuyển đổi một phương thức thành một hàm chung gửi đơn.

Để xác định một phương thức chung, hãy trang trí nó bằng công cụ trang trí @singledispatchmethod. Lưu ý rằng công văn xảy ra trên loại đối số không tự hoặc không cls đầu tiên, tạo hàm của bạn tương ứng:

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

Đầu ra

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

@singledispatchmethod hỗ trợ lồng với các trình trang trí khác như @ classmethod. Lưu ý rằng để cho phép distatcher.register, singledispatchmethod phải là trang trí bên ngoài nhất. Đây là lớp Negator với các phương thức neg bị ràng buộc bởi lớp:

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

Đầu ra:

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

Mô hình tương tự có thể được sử dụng cho các nhà trang trí tương tự khác: staticmethod, trừu tượng, và những người khác.


2

Sử dụng đối số từ khóa với mặc định. Ví dụ

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

Trong trường hợp viên đạn thẳng so với viên đạn cong, tôi sẽ thêm hai chức năng: add_bullet_straightadd_bullet_curved.


2

phương pháp quá tải là khó khăn trong python. Tuy nhiên, có thể có việc sử dụng chuyển các chính tả, danh sách hoặc các biến nguyên thủy.

Tôi đã thử một cái gì đó cho các trường hợp sử dụng của mình, điều này có thể giúp ở đây để hiểu mọi người làm quá tải các phương thức.

Hãy lấy ví dụ của bạn:

một phương thức nạp chồng lớp với gọi các phương thức từ các lớp khác nhau.

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

vượt qua các đối số từ lớp từ xa:

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

HOẶC LÀ

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

Vì vậy, việc xử lý đang đạt được cho danh sách, Từ điển hoặc các biến nguyên thủy từ quá tải phương thức.

thử nó cho mã của bạn.


2

Chỉ là một trang trí đơn giản

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)

Bạn có thể sử dụng nó như thế này

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

Sửa đổi nó để thích ứng với trường hợp sử dụng của bạn.

Làm rõ các khái niệm

  • chức năng công văn : có nhiều chức năng có cùng tên. Cái nào nên được gọi là? hai chiến lược
  • công văn tĩnh / biên dịch thời gian ( hay còn gọi là "quá tải" ). quyết định hàm nào sẽ gọi dựa trên kiểu thời gian biên dịch của các đối số. Trong tất cả các ngôn ngữ động, không có loại thời gian biên dịch, do đó, quá tải là không thể theo định nghĩa
  • công văn động / thời gian chạy : quyết định gọi hàm nào dựa trên kiểu thời gian chạy của các đối số. Đây là điều mà tất cả các ngôn ngữ OOP làm: nhiều lớp có cùng phương thức và ngôn ngữ quyết định nên gọi ngôn ngữ nào dựa trên loại self/thisđối số. Tuy nhiên, hầu hết các ngôn ngữ chỉ làm điều đó cho các thisđối số mà thôi. Các trang trí ở trên mở rộng ý tưởng cho nhiều tham số.

Để xóa, giả sử một ngôn ngữ tĩnh và xác định các chức năng

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)

Với công văn tĩnh (quá tải), bạn sẽ thấy "số được gọi" hai lần, bởi vì xđã được khai báo là Number, và đó là tất cả sự quá tải quan tâm. Với công văn động, bạn sẽ thấy "số nguyên được gọi, float được gọi", bởi vì đó là các loại thực tế xtại thời điểm hàm được gọi.


Ví dụ này điều quan trọng là không minh họa phương pháp đã kêu gọi xcho cử động, cũng không phải trong đó ra lệnh cho cả hai phương pháp đã gọi cho văn tĩnh. Đề nghị bạn chỉnh sửa các báo cáo in thành print('number called for Integer')vv
smci
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.