Sobrecarga de função Python


213

Eu sei que o Python não suporta sobrecarga de método, mas encontrei um problema que não consigo resolver de uma maneira Pythonic agradável.

Estou fazendo um jogo em que um personagem precisa atirar em várias balas, mas como escrevo funções diferentes para criar essas balas? Por exemplo, suponha que eu tenha uma função que crie uma bala viajando do ponto A para B com uma determinada velocidade. Eu escreveria uma função como esta:

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

Mas eu quero escrever outras funções para criar marcadores como:

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

E assim por diante com muitas variações. Existe uma maneira melhor de fazê-lo sem usar tantos argumentos de palavra-chave, pois isso fica feio e rápido. Mudar o nome de cada função é muito ruim também, porque você quer receber add_bullet1, add_bullet2ou add_bullet_with_really_long_name.

Para abordar algumas respostas:

  1. Não, não posso criar uma hierarquia de classes Bullet, porque isso é muito lento. O código real para gerenciar marcadores está em C e minhas funções são wrappers em torno da API C.

  2. Eu sei sobre os argumentos da palavra-chave, mas verificar todos os tipos de combinações de parâmetros está ficando irritante, mas argumentos padrão ajudam a colocar como acceleration=0


5
Obras de apenas um parâmetro, mas aqui (para as pessoas que vêm aqui a partir de um motor de busca): docs.python.org/3/library/...
leewz

1
esse parece ser um bom lugar para os valores padrão. você pode definir alguns como Nenhum e apenas verificá-los. o impacto boolean adicional parece insignificante
Andrew Scott Evans

Tem que usar default value + if + elsepara fazer o mesmo que C ++. Esta é uma das poucas coisas que o C ++ tem melhor legibilidade do que o Python ...
Deqing

Estou confuso sobre o motivo pelo qual kwargs não é uma resposta válida. Você diz que não deseja usar muitos argumentos de palavra-chave porque fica muito rápido ... bem, essa é apenas a natureza do problema. Se você tem muitos argumentos e é confuso, porque você tem muitos argumentos do que você esperava? Deseja usar muitos argumentos sem especificá-los em qualquer lugar ??? Python não é um leitor de mentes.
Cálculo

Não sabemos que tipo de objetos script, curvesão, eles têm um ancestral comum, quais métodos eles suportam. Com a digitação de pato, você decide que o design da classe deve descobrir quais métodos eles precisam oferecer suporte. Presumivelmente, é Scriptcompatível com algum tipo de retorno de chamada baseado em timestep (mas que objeto ele deve retornar? A posição nesse timestep? A trajetória nesse timestep?). Presumivelmente, start, direction, speede start, headto, spead, accelerationambos descrevem os tipos de trajetórias, mas novamente é até você para projetar a classe recebendo saber como descompactá-los e processá-los.
smci 5/01

Respostas:


144

O que você está pedindo é chamado de despacho múltiplo . Veja exemplos de linguagem Julia que demonstram diferentes tipos de despachos.

No entanto, antes de analisarmos isso, primeiro abordaremos por que sobrecarregar não é realmente o que você deseja em python.

Por que não sobrecarregar?

Primeiro, é preciso entender o conceito de sobrecarga e por que não é aplicável ao python.

Ao trabalhar com idiomas que podem discriminar tipos de dados em tempo de compilação, a seleção entre as alternativas pode ocorrer em tempo de compilação. O ato de criar essas funções alternativas para a seleção em tempo de compilação é geralmente chamado de sobrecarga de uma função. ( Wikipedia )

Python é uma linguagem de tipo dinâmico , portanto o conceito de sobrecarga simplesmente não se aplica a ele. No entanto, nem tudo está perdido, pois podemos criar essas funções alternativas em tempo de execução:

Nas linguagens de programação que adiam a identificação do tipo de dados até o tempo de execução, a seleção entre funções alternativas deve ocorrer no tempo de execução, com base nos tipos determinados dinamicamente de argumentos de função. As funções cujas implementações alternativas são selecionadas dessa maneira são geralmente denominadas multimétodos . ( Wikipedia )

Portanto, devemos ser capazes de executar vários métodos em python - ou, como é chamado alternativamente: despacho múltiplo .

Expedição múltipla

Os multimétodos também são chamados de despacho múltiplo :

Múltiplos despachos ou métodos múltiplos é o recurso de algumas linguagens de programação orientadas a objetos, nas quais uma função ou método pode ser despachado dinamicamente com base no tipo de tempo de execução (dinâmico) de mais de um de seus argumentos. ( Wikipedia )

Python não suporta esta fora da caixa 1 , mas, como acontece, há um pacote de python excelente chamado multipledispatch que faz exatamente isso.

Solução

Aqui está como podemos usar o pacote multipledispatch 2 para implementar seus métodos:

>>> 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 suporta atualmente única expedição
2. Tome cuidado para não usar multipledispatch em um ambiente multi-threaded ou você vai ter um comportamento estranho.


6
Qual é o problema do 'multipledispatch' em um ambiente multithread? Como o código do lado do servidor geralmente está em ambiente multiencadeado! Apenas tentando desenterrá-lo!
Danzeer

7
@danzeer Não era seguro para threads. Eu vi o argumento sendo modificado por dois threads diferentes (ou seja, o valor de speedpode mudar no meio da função quando outro thread definir seu próprio valor speed) !!! Levei muito tempo para perceber que era a biblioteca que era a culpada.
Andriy Drozdyuk 22/01

108

O Python suporta "sobrecarga de método" conforme você o apresenta. De fato, o que você acabou de descrever é trivial para implementar em Python, de muitas maneiras diferentes, mas eu iria com:

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

No código acima, defaulté um valor padrão plausível para esses argumentos, ou None. Você pode chamar o método apenas com os argumentos de seu interesse e o Python usará os valores padrão.

Você também pode fazer algo assim:

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

Outra alternativa é conectar diretamente a função desejada diretamente à classe ou instância:

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

Outra maneira é usar um padrão abstrato de fábrica:

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
Todos estes parecem exemplos de argumentos variáveis, em vez de sobrecarregar. Como a sobrecarga permite que você tenha a mesma função, para diferentes tipos de argumentos. por exemplo: sum (real_num1, real_num2) e sum (imaginary_num1, imaginary_num2) Será que ambos têm a mesma sintaxe de chamada, mas estão realmente esperando 2 tipos diferentes como entrada, ea implementação tem de mudar também internamente
Efren

17
Usando a resposta que você escolheria, como você apresentaria ao chamador quais argumentos fazem sentido juntos? Basta colocar um monte de argumentos cada um com um valor padrão pode fornecer a mesma funcionalidade, mas em termos de uma API é muito menos elegante
Greg Ennis

6
Além disso, a implementação terá que verificar todas as combinações de entradas de parâmetros (ou ignorar parâmetros) como: if sprite and script and not start and not direction and not speed...apenas para saber que está em uma ação específica. porque um chamador pode chamar a função fornecendo todos os parâmetros disponíveis. Durante a sobrecarga, defina para você os conjuntos exatos de parâmetros relevantes.
Roee Gavirel

5
É muito perturbador quando as pessoas dizem que o python suporta a sobrecarga de métodos. Isso não. O fato de você colocar "sobrecarga de método" entre aspas indica que você está ciente desse fato. Você pode obter funcionalidades semelhantes com várias técnicas, como a mencionada aqui. Mas a sobrecarga de método tem uma definição muito específica.
Howard Swope

Eu acho que o ponto pretendido é que, enquanto a sobrecarga de método não é um recurso do python, os mecanismos acima podem ser usados ​​para obter o efeito equivalente.
rawr tocou

93

Você pode usar a solução "faça você mesmo" para sobrecarga de funções. Este é copiado do artigo de Guido van Rossum sobre multimétodos (porque há pouca diferença entre mm e sobrecarga em 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

O uso seria

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

As limitações mais restritivas no momento são:

  • métodos não são suportados, apenas funções que não são membros da classe;
  • herança não é tratada;
  • kwargs não são suportados;
  • o registro de novas funções deve ser feito no momento da importação, caso não seja seguro para threads

6
+1 para decoradores por estender o idioma neste caso de uso.
Eloims

1
+1 porque essa é uma ótima idéia (e provavelmente o que o OP deve seguir) --- eu nunca tinha visto uma implementação de vários métodos no Python.
Escualo 27/02

39

Uma opção possível é usar o módulo multipledispatch conforme detalhado aqui: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch

Em vez de fazer isso:

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

Você consegue fazer isso:

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)

Com o uso resultante:

>>> add(1, 2)
3

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

4
Por que isso não recebe mais votos? Estou supondo que, devido à falta de exemplos ... Criei uma resposta com um exemplo de como implementar uma solução para o problema do OP com o pacote multipledispatch .
Andriy Drozdyuk 17/03/2015

19

No Python 3.4 foi adicionado o PEP-0443. Funções genéricas de despacho único .

Aqui está uma breve descrição da API do PEP.

Para definir uma função genérica, decore-a com o decorador @singledispatch. Observe que o despacho acontece no tipo do primeiro argumento. Crie sua função de acordo:

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

Para adicionar implementações sobrecarregadas à função, use o atributo register () da função genérica. Este é um decorador, usando um parâmetro de tipo e decorando uma função implementando a operação para esse tipo:

@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

Esse tipo de comportamento geralmente é resolvido (em idiomas OOP) usando o polimorfismo. Cada tipo de bala seria responsável por saber como viaja. Por exemplo:

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

Passe o máximo de argumentos para a função c_ que existe e faça o trabalho de determinar qual função c chamar com base nos valores da função c inicial. Portanto, o python deve sempre chamar a função one c. Essa função c examina os argumentos e, em seguida, pode delegar para outras funções c adequadamente.

Você está basicamente apenas usando cada subclasse como um contêiner de dados diferente, mas, ao definir todos os argumentos em potencial na classe base, as subclasses são livres para ignorar aquelas com as quais não fazem nada.

Quando um novo tipo de marcador aparece, você pode simplesmente definir mais uma propriedade na base, alterar a função python para que ela passe a propriedade extra e a função c_ que examina os argumentos e delega adequadamente. Não parece tão ruim, eu acho.


1
Essa foi a minha abordagem inicial, mas por motivos de desempenho que tive de reescrever esse código em C.
Bullets

@Bullets, eu sugeriria que pode haver várias opções disponíveis para melhorar o desempenho, em vez de escrever várias funções c que provavelmente não farão muito. Por exemplo: criar uma instância pode ser caro, portanto, mantenha um pool de objetos. Embora eu diga isso sem saber o que você achou muito lento. Por interesse, o que exatamente foi lento nessa abordagem? A menos que um tempo significativo seja gasto no lado C da fronteira, não consigo pensar que o Python (ele mesmo) seja o problema real.
Josh Smeaton

Talvez haja outras maneiras de melhorar o desempenho, mas sou muito melhor com C do que com Python. O problema era calcular os movimentos das balas e detectar quando elas saíam dos limites da tela. Eu tinha um método para calcular a posição do marcador pos+v*te depois comparar com os limites da tela if x > 800e assim por diante. Chamar essas funções várias centenas de vezes por quadro acabou sendo inaceitavelmente lento. Foi algo como 40 fps em 100% da CPU com o pitão puro para 60 fps com 5% -10%, quando feito em C.
Marcadores

@ Boletins, justo o suficiente então. Eu ainda usaria a abordagem adotada para encapsular dados. Passe uma instância de marcador para add_bullete extraia todos os campos necessários. Vou editar minha resposta.
Josh Smeaton

@Bullets: você pode combinar suas funções C e a abordagem OOP sugerida por Josh usando o Cython . Permite ligação antecipada, portanto não deve haver uma penalidade de velocidade.
jfs


4

Use vários argumentos de palavra-chave na definição ou crie uma Bullethierarquia cujas instâncias sejam passadas para a função.


Eu sugeriria a segunda abordagem: faça algumas classes BulletParams ... para especificar os detalhes do marcador.
John Zwinck

Você pode elaborar sobre isso? Tentei criar uma hierarquia de classes com marcadores diferentes, mas isso não funciona, porque o Python é muito lento. Ele não pode calcular os movimentos do número necessário de marcadores com rapidez suficiente, então tive que escrever essa parte em C. Todas as variantes add_bullet chamam a função C correspondente.
Bullets

4

Eu acho que seu requisito básico é ter uma sintaxe semelhante a C / C ++ em python com o mínimo de dor de cabeça possível. Embora eu tenha gostado da resposta de Alexander Poluektov, isso não funciona para as aulas.

O seguinte deve funcionar para as aulas. Ele funciona distinguindo pelo número de argumentos que não são de palavra-chave (mas não suporta distinção por tipo):

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

E pode ser usado simplesmente assim:

test = TestOverloading()

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

Resultado:

Isso é sobrecarga 3
Sprite: Sou um Sprite
Início: 0
Direção: Direita

Isso está sobrecarregando 2
Sprite: eu sou outro
Script Sprite :
enquanto x == True: print 'hi'


4

O @overloaddecorador foi adicionado com dicas de tipo (PEP 484). Embora isso não mude o comportamento do python, facilita a compreensão do que está acontecendo e o mypy detecta erros.
Consulte: Dicas de tipo e PEP 484


Você pode adicionar alguns exemplos?
gerrit 14/02

3

Eu acho que Bullet hierarquia de classes com o polimorfismo associado é o caminho a percorrer. Você pode sobrecarregar efetivamente o construtor da classe base usando uma metaclasse para que a chamada da classe base resulte na criação do objeto de subclasse apropriado. Abaixo está um código de exemplo para ilustrar a essência do que quero dizer.

Atualizada

O código foi modificado para ser executado no Python 2 e 3 para mantê-lo relevante. Isso foi feito de uma maneira que evita o uso da sintaxe explícita da metaclasse do Python, que varia entre as duas versões.

Para atingir esse objetivo, uma BulletMetaBaseinstância da BulletMetaclasse é criada chamando explicitamente a metaclasse ao criar a Bulletclasse básica (em vez de usar o __metaclass__=atributo class ou por meio de um metaclassargumento de palavra - chave, dependendo da versão do 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')

Resultado:

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, ainda é apenas uma maneira elegante de nomear as funções como add_bullet1, add_bullet2 e assim por diante.
Bullets

1
Boletins @: Talvez seja, ou talvez seja apenas uma maneira um pouco elaborada para criar uma função de fábrica. Uma coisa boa é que ele suporta uma hierarquia de Bulletsubclasses sem precisar modificar a classe base ou a função de fábrica toda vez que você adiciona outro subtipo. (Obviamente, se você estiver usando C em vez de C ++, acho que não tem aulas.) Você também pode criar uma metaclasse mais inteligente que descubra por si mesma qual subclasse criar com base no tipo e / ou número dos argumentos passados ​​(como o C ++ faz para suportar sobrecarga).
22611 martineau

1
Essa idéia de herança seria minha primeira opção também.
Daniel Möller

3

Python 3.8 adicionado functools.singledispatchmethod

Transforme um método em uma função genérica de despacho único.

Para definir um método genérico, decore-o com o decorador @singledispatchmethod. Observe que o despacho acontece no tipo do primeiro argumento não-próprio ou não-cls, crie sua função adequadamente:

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

Resultado

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

@singledispatchmethod suporta aninhamento com outros decoradores, como @classmethod. Observe que, para permitir o dispatcher.register, o método singlepispatch deve ser o decorador mais externo. Aqui está a classe Negator com os métodos neg sendo vinculados à classe:

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

Resultado:

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

O mesmo padrão pode ser usado para outros decoradores semelhantes: staticmethod, abstractmethod e outros.


2

Use argumentos de palavra-chave com padrões. Por exemplo

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

No caso de uma bala direta versus uma bala curva, eu adicionaria duas funções: add_bullet_straighte add_bullet_curved.


2

métodos de sobrecarga são complicados em python. No entanto, pode haver o uso de passar o dict, list ou variáveis ​​primitivas.

Eu tentei algo para meus casos de uso, isso poderia ajudar aqui a entender as pessoas que sobrecarregam os métodos.

Vamos dar o seu exemplo:

um método de sobrecarga de classe com chame os métodos de classe diferente.

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

transmita os argumentos da classe remota:

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

OU

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

Portanto, o manuseio está sendo alcançado para lista, dicionário ou variáveis ​​primitivas da sobrecarga de método.

experimente seus códigos.


2

Apenas um decorador simples

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)

Você pode usá-lo assim

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

Modifique-o para adaptá-lo ao seu caso de uso.

Um esclarecimento de conceitos

  • despacho de função : existem várias funções com o mesmo nome. Qual deve ser chamado? duas estratégias
  • expedição estática / em tempo de compilação ( também conhecida como "sobrecarga" ). decida qual função chamar com base no tipo de tempo de compilação dos argumentos. Em todas as linguagens dinâmicas, não existe um tipo de tempo de compilação; portanto, a sobrecarga é impossível por definição
  • despacho dinâmico / de tempo de execução : decida qual função chamar com base no tipo de tempo de execução dos argumentos. É o que todas as linguagens OOP fazem: várias classes têm os mesmos métodos, e a linguagem decide qual chamar com base no tipo de self/thisargumento. No entanto, a maioria dos idiomas faz isso apenas para o thisargumento. O decorador acima estende a ideia a vários parâmetros.

Para esclarecer, assuma uma linguagem estática e defina as funções

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)

Com o envio estático (sobrecarga), você verá "número chamado" duas vezes, porque xfoi declarado como Numbere isso é tudo sobre sobrecarga. Com o despacho dinâmico, você verá "número inteiro chamado, chamada flutuante", porque esses são os tipos reais xno momento em que a função é chamada.


Este exemplo crucialmente não ilustra qual método foi chamado xpara envio dinâmico, nem em qual ordem os dois métodos foram chamados para envio estático. Recomendo que você edite as instruções de impressão para print('number called for Integer')etc.
smci 5/01
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.