Qual é a maneira mais pitônica e correta de criar aliases de composição?
Aqui está um cenário hipotético:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
# do something
class Person:
def __init__(self, house):
self.house = house
# aliases house.cleanup
# 1.
self.cleanup_house = self.house.cleanup
# 2.
def cleanup_house(self, arg1, arg2, kwarg1=False):
return self.house.cleanup(arg1=arg1, arg2=arg2, kwarg1=kwarg1)
AFAIK com # 1 meus editores testados entendem isso tão bem quanto # 2 - conclusão automática, sequências de documentos etc.
Existem desvantagens na abordagem nº 1? Qual o caminho mais correto do ponto de vista do python?
Para expandir o método # 1, a variante insinuada e com tipo de sugestão seria imune a todos os problemas apontados nos comentários:
class House:
def cleanup(self, arg1, arg2, kwarg1=False):
"""clean house is nice to live in!"""
pass
class Person:
def __init__(self, house: House):
self._house = house
# aliases
self.cleanup_house = self.house.cleanup
@property
def house(self):
return self._house
Person.cleanup_house
fazer. Ele pega a casa associada a essa pessoa e a limpa. O nº 1 limpará uma casa e, em algum momento (inicialização), essa casa será a correta. Não lida bem com a pessoa que muda de casa, ou se uma pessoa não tem casaRespostas:
Há vários problemas com o primeiro método:
house
umproperty
com um levantador, mas esse é um trabalho não trivial para algo que não deveria exigir. Veja o final desta resposta para uma implementação de amostra.cleanup_house
não será herdável. Um objeto de função definido em uma classe é um descritor que não é de dados que pode ser herdado e substituído, além de ser vinculado a uma instância. Um atributo de instância como na primeira abordagem não está presente na classe. O fato de ser um método vinculado é incidental. Uma classe filho não poderá acessarsuper().cleanup_house
, por exemplo concreto.person.cleanup_house.__name__ != 'cleanup_house'
. Isso não é algo que você verifica com frequência, mas quando o faz, espera-se que o nome da função sejacleanup
.A boa notícia é que você não precisa repetir as assinaturas várias vezes para usar a abordagem nº 2. O Python oferece a notação splat (
*
) / splatty-splat (**
) muito conveniente para delegar toda a verificação de argumentos ao método que está sendo envolvido:E é isso. Todos os argumentos regulares e padrão são passados como estão.
Esta é a razão pela qual o nº 2 é, de longe, a abordagem mais pitônica. Não tenho idéia de como ele irá interagir com editores que suportam dicas de tipo, a menos que você copie a assinatura do método.
Uma coisa que pode ser um problema é que
cleanup_house.__doc__
não é o mesmo quehouse.cleanup.__doc__
. Isso poderia merecer uma conversão dehouse
para aproperty
, cujo setter atribuicleanup_house.__doc__
.Para resolver o problema 1. (mas não o 2. ou 3.), você pode implementar
house
como uma propriedade com um setter. A idéia é atualizar os aliases sempre que ohouse
atributo for alterado. Esta não é uma boa ideia em geral, mas aqui está uma implementação alternativa ao que você tem na pergunta que provavelmente funcionará um pouco melhor:fonte
*args, **kwargs
, os recursos de conclusão automática de interrupção e, em geral, são difíceis de trabalhar. Você também precisa definir duas vezes as doutrinas parahouse.cleanup
e /person.cleanup_house
ou hacká-las de alguma forma. Portanto, com essa abordagem, é necessário manter constantemente as assinaturas e as doutrinas manualmente, o que pode dar muito trabalho em grandes programas.*args
e**kwargs
sou estranho. Eles estão literalmente lá, para que você não precise manter assinaturas.__doc__
pode ser mantido por atribuição direta.functool.wraps(<parent_func>)
decorador, no entanto, que introduz uma série de inconsistências também.Eu só queria adicionar mais uma abordagem aqui, que, se você quiser
house
ser público e configurável (geralmente trataria algo como imutável), você pode criarcleanup_house
a propriedade da seguinte maneira:Pelo menos em um Ipython REPL, a conclusão do código e a docstring parecem estar funcionando como você esperaria. Observe como ele interage com as anotações de tipo ...
EDIT: então, mypy 0.740 pelo menos não pode inferir a assinatura de tipo
person.cleanup_house
, então isso não é ótimo, embora não seja surpreendente:Eu ainda continuaria com o # 2.
fonte