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_bullet2
ou add_bullet_with_really_long_name
.
Para abordar algumas respostas:
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.
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
default value + if + else
para fazer o mesmo que C ++. Esta é uma das poucas coisas que o C ++ tem melhor legibilidade do que o Python ...script, curve
sã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, éScript
compatí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, speed
estart, headto, spead, acceleration
ambos descrevem os tipos de trajetórias, mas novamente é até você para projetar a classe recebendo saber como descompactá-los e processá-los.Respostas:
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.
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:
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 :
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:
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.
fonte
speed
pode mudar no meio da função quando outro thread definir seu próprio valorspeed
) !!! Levei muito tempo para perceber que era a biblioteca que era a culpada.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:
No código acima,
default
é um valor padrão plausível para esses argumentos, ouNone
. 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:
Outra alternativa é conectar diretamente a função desejada diretamente à classe ou instância:
Outra maneira é usar um padrão abstrato de fábrica:
fonte
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.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):
O uso seria
As limitações mais restritivas no momento são:
fonte
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:
Você consegue fazer isso:
Com o uso resultante:
fonte
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:
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:
fonte
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:
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.
fonte
pos+v*t
e depois comparar com os limites da telaif x > 800
e 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.add_bullet
e extraia todos os campos necessários. Vou editar minha resposta.Ao passar a palavra-chave args .
fonte
Use vários argumentos de palavra-chave na definição ou crie uma
Bullet
hierarquia cujas instâncias sejam passadas para a função.fonte
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):
E pode ser usado simplesmente assim:
Resultado:
fonte
O
@overload
decorador 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
fonte
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
BulletMetaBase
instância daBulletMeta
classe é criada chamando explicitamente a metaclasse ao criar aBullet
classe básica (em vez de usar o__metaclass__=
atributo class ou por meio de ummetaclass
argumento de palavra - chave, dependendo da versão do Python).Resultado:
fonte
Bullet
subclasses 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).Python 3.8 adicionado functools.singledispatchmethod
Resultado
Resultado:
fonte
Use argumentos de palavra-chave com padrões. Por exemplo
No caso de uma bala direta versus uma bala curva, eu adicionaria duas funções:
add_bullet_straight
eadd_bullet_curved
.fonte
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.
transmita os argumentos da classe remota:
OU
Portanto, o manuseio está sendo alcançado para lista, dicionário ou variáveis primitivas da sobrecarga de método.
experimente seus códigos.
fonte
Apenas um decorador simples
Você pode usá-lo assim
Modifique-o para adaptá-lo ao seu caso de uso.
self/this
argumento. No entanto, a maioria dos idiomas faz isso apenas para othis
argumento. O decorador acima estende a ideia a vários parâmetros.Para esclarecer, assuma uma linguagem estática e defina as funções
Com o envio estático (sobrecarga), você verá "número chamado" duas vezes, porque
x
foi declarado comoNumber
e isso é tudo sobre sobrecarga. Com o despacho dinâmico, você verá "número inteiro chamado, chamada flutuante", porque esses são os tipos reaisx
no momento em que a função é chamada.fonte
x
para 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 paraprint('number called for Integer')
etc.