O Zen do Python afirma que deve haver apenas uma maneira de fazer as coisas - mas frequentemente me deparo com o problema de decidir quando usar uma função versus quando usar um método.
Vamos dar um exemplo trivial - um objeto ChessBoard. Digamos que precisamos de alguma forma de obter todos os lances legais do Rei disponíveis no tabuleiro. Escrevemos ChessBoard.get_king_moves () ou get_king_moves (chess_board)?
Aqui estão algumas questões relacionadas que examinei:
- Por que o python usa 'métodos mágicos'?
- Existe uma razão para as strings do Python não terem um método de comprimento de string?
As respostas que obtive foram em grande parte inconclusivas:
Por que o Python usa métodos para algumas funcionalidades (por exemplo, list.index ()), mas funções para outras (por exemplo, len (lista))?
O principal motivo é a história. Funções foram usadas para aquelas operações que eram genéricas para um grupo de tipos e que deveriam funcionar até mesmo para objetos que não tinham métodos (por exemplo, tuplas). Também é conveniente ter uma função que pode ser prontamente aplicada a uma coleção amorfa de objetos quando você usa os recursos funcionais do Python (map (), apply () et al).
Na verdade, implementar len (), max (), min () como uma função interna é, na verdade, menos código do que implementá-los como métodos para cada tipo. Pode-se discutir sobre casos individuais, mas isso faz parte do Python e é tarde demais para fazer essas mudanças fundamentais agora. As funções devem permanecer para evitar a quebra massiva do código.
Embora interessante, o que foi dito acima não diz muito sobre qual estratégia adotar.
Esse é um dos motivos - com métodos personalizados, os desenvolvedores seriam livres para escolher um nome de método diferente, como getLength (), length (), getlength () ou qualquer outro. Python impõe nomenclatura estrita para que a função comum len () possa ser usada.
Um pouco mais interessante. Minha opinião é que as funções são, em certo sentido, a versão pitônica das interfaces.
Por último, do próprio Guido :
Falar sobre as Habilidades / Interfaces me fez pensar sobre alguns de nossos nomes de métodos especiais "desonestos". Na Referência de Linguagem, ele diz: "Uma classe pode implementar certas operações que são chamadas por sintaxe especial (como operações aritméticas ou subscrito e fracionamento) definindo métodos com nomes especiais." Mas existem todos esses métodos com nomes especiais como
__len__
ou__unicode__
que parecem ser fornecidos para o benefício de funções integradas, em vez de para suporte de sintaxe. Presumivelmente em um Python baseado em interface, esses métodos se transformariam em métodos nomeados regularmente em um ABC, de modo que__len__
se tornariaclass container: ... def len(self): raise NotImplemented
Porém, pensando mais um pouco, não vejo por que todas as operações sintáticas não invocariam apenas o método de nome normal apropriado em um ABC específico. "
<
", por exemplo, presumivelmente invocaria "object.lessthan
" (ou talvez "comparable.lessthan
"). Portanto, outro benefício seria a capacidade de afastar o Python dessa estranheza de nome mutilado, o que me parece uma melhoria de HCI .Hm. Não tenho certeza se concordo (imagine que :-).
Existem duas partes da "lógica do Python" que gostaria de explicar primeiro.
Em primeiro lugar, escolhi len (x) em vez de x.len () por motivos de HCI (
def __len__()
veio muito depois). Na verdade, existem duas razões interligadas, ambas HCI:(a) Para algumas operações, a notação com prefixo apenas lê melhor do que pós-fixada - as operações com prefixo (e infixo!) têm uma longa tradição em matemática que gosta de notações onde os visuais ajudam o matemático a pensar sobre um problema. Compare o fácil com que reescrever uma fórmula como
x*(a+b)
parax*a + x*b
a falta de jeito de fazer a mesma coisa usando uma notação OO cru.(b) Quando leio um código que diz
len(x)
que sei que está pedindo o comprimento de algo. Isso me diz duas coisas: o resultado é um número inteiro e o argumento é algum tipo de contêiner. Ao contrário, quando lix.len()
, já devo saber quex
é algum tipo de container implementando uma interface ou herdando de uma classe que possui um padrãolen()
. Testemunha a confusão que ocasionalmente têm quando uma classe que não está a implementar um mapeamento tem umget()
oukeys()
método, ou algo que não é um arquivo tem umwrite()
método.Dizendo a mesma coisa de outra maneira, vejo 'len' como uma operação embutida . Eu odiaria perder isso. Não posso dizer com certeza se você quis dizer isso ou não, mas 'def len (self): ...' certamente parece que você deseja rebaixá-lo a um método comum. Estou fortemente -1 nisso.
A segunda parte da lógica do Python que prometi explicar é a razão pela qual escolhi métodos especiais para olhar,
__special__
e não apenasspecial
. Eu estava antecipando muitas operações que as classes podem querer substituir, alguns padrões (por exemplo,__add__
or__getitem__
), alguns não tão padrão (por exemplo, pickles__reduce__
por um longo tempo não tiveram suporte em código C). Eu não queria que essas operações especiais usassem nomes de métodos comuns, porque então as classes pré-existentes, ou classes escritas por usuários sem uma memória enciclopédica para todos os métodos especiais, estariam propensas a definir acidentalmente operações que eles não pretendiam implementar , com consequências possivelmente desastrosas. Ivan Krstić explicou isso de forma mais concisa em sua mensagem, que chegou depois de eu ter escrito tudo isso.- --Guido van Rossum (página inicial: http://www.python.org/~guido/ )
Meu entendimento disso é que, em certos casos, a notação de prefixo faz mais sentido (ou seja, Duck.quack faz mais sentido do que quack (Duck) de um ponto de vista linguístico) e, novamente, as funções permitem "interfaces".
Nesse caso, meu palpite seria implementar get_king_moves baseado exclusivamente no primeiro ponto de Guido. Mas isso ainda deixa muitas questões em aberto sobre, digamos, implementar uma classe de pilha e fila com métodos push e pop semelhantes - devem ser funções ou métodos? (aqui eu acho que funciona, porque eu realmente quero sinalizar uma interface push-pop)
TLDR: Alguém pode explicar qual deve ser a estratégia para decidir quando usar funções versus métodos?
fonte
X.frob
ouX.__frob__
e autônomofrob
.Respostas:
Minha regra geral é esta - a operação é realizada no objeto ou pelo objeto?
se for feito pelo objeto, deve ser uma operação de membro. Se pudesse se aplicar a outras coisas também, ou se fosse feito por outra coisa no objeto, então deveria ser uma função (ou talvez um membro de outra coisa).
Ao introduzir a programação, é tradicional (embora a implementação seja incorreta) descrever objetos em termos de objetos do mundo real, como carros. Você mencionou um pato, então vamos em frente.
No contexto da analogia "objetos são coisas reais", é "correto" adicionar um método de classe para qualquer coisa que o objeto possa fazer. Então, digamos que eu queira matar um pato, devo adicionar um .kill () ao pato? Não ... até onde eu sei, os animais não se suicidam. Portanto, se eu quiser matar um pato, devo fazer o seguinte:
Afastando-se dessa analogia, por que usamos métodos e classes? Porque queremos conter dados e, com sorte, estruturar nosso código de forma que ele seja reutilizável e extensível no futuro. Isso nos leva à noção de encapsulamento tão cara ao design OO.
O encapsulamento principal é realmente o que isso significa: como um designer, você deve ocultar tudo sobre a implementação e as partes internas da classe, que não é absolutamente necessário para qualquer usuário ou outro desenvolvedor acessar. Como lidamos com instâncias de classes, isso se reduz a "quais operações são cruciais nesta instância ". Se uma operação não for específica da instância, não deve ser uma função de membro.
TL; DR : o que @Bryan disse. Se ele opera em uma instância e precisa acessar dados internos à instância da classe, deve ser uma função de membro.
fonte
Use uma aula quando quiser:
1) Isole o código de chamada dos detalhes de implementação - aproveitando a abstração e o encapsulamento .
2) Quando você deseja ser substituível por outros objetos - aproveitando o polimorfismo .
3) Quando você deseja reutilizar código para objetos semelhantes - aproveitando a herança .
Use uma função para chamadas que façam sentido em muitos tipos de objetos diferentes - por exemplo, as funções internas len e repr se aplicam a muitos tipos de objetos.
Dito isso, a escolha às vezes se resume a uma questão de gosto. Pense no que é mais conveniente e legível para ligações típicas. Por exemplo, o que seria melhor
(x.sin()**2 + y.cos()**2).sqrt()
ousqrt(sin(x)**2 + cos(y)**2)
?fonte
Esta é uma regra simples: se o código atua em uma única instância de um objeto, use um método. Melhor ainda: use um método, a menos que haja uma razão convincente para escrevê-lo como uma função.
Em seu exemplo específico, você deseja que seja assim:
Não pense demais. Sempre use métodos até chegar ao ponto em que você diga a si mesmo "não faz sentido fazer disso um método", caso em que você pode fazer uma função.
fonte
len()
, também pode-se argumentar que o design faz sentido, embora eu pessoalmente ache que as funções não seriam tão ruins lá - teríamos apenas outra convenção quelen()
sempre deve retornar um inteiro (mas com todos os problemas adicionais de backcomp, eu também não recomendaria isso para python)Normalmente penso em um objeto como uma pessoa.
Atributos são o nome da pessoa, altura, tamanho do calçado, etc.
Métodos e funções são operações que a pessoa pode realizar.
Se a operação pudesse ser feita por qualquer pessoa, sem exigir nada exclusivo dessa pessoa específica (e sem alterar nada nessa pessoa específica), então é uma função e deve ser escrita como tal.
Se uma operação está agindo sobre a pessoa (por exemplo, comer, caminhar, ...) ou requer algo exclusivo dessa pessoa para se envolver (como dançar, escrever um livro, ...), então deve ser um método .
Claro, nem sempre é trivial traduzir isso para o objeto específico com o qual você está trabalhando, mas acho que é uma boa maneira de pensar nisso.
fonte
height(person)
, nãoperson.height
?are_married(person1, person2)
? Essa consulta é muito geral e, portanto, deve ser uma função e não um método.Geralmente eu uso classes para implementar um conjunto lógico de recursos para alguma coisa , de modo que no resto do meu programa eu possa raciocinar sobre a coisa , não tendo que me preocupar com todas as pequenas preocupações que compõem sua implementação.
Qualquer coisa que faça parte dessa abstração central de "o que você pode fazer com uma coisa " geralmente deve ser um método. Isso geralmente inclui tudo que pode alterar algo , já que o estado dos dados internos é geralmente considerado privado e não faz parte da ideia lógica de "o que você pode fazer com uma coisa ".
Quando você chega a operações de nível superior, especialmente se envolvem várias coisas , acho que geralmente são mais naturalmente expressas como funções, se puderem ser construídas a partir da abstração pública de uma coisa sem a necessidade de acesso especial aos internos (a menos que ' re métodos de algum outro objeto). Isso tem a grande vantagem de que, quando eu decidir reescrever completamente a parte interna de como a minha coisa obras (sem alterar a interface), eu só tenho um pequeno conjunto básico de métodos para reescrever, e, em seguida, todas as funções externas escritas em termos desses métodos vai apenas funcionar. Acho que insistir em que todas as operações da classe X são métodos da classe X leva a classes excessivamente complicadas.
No entanto, depende do código que estou escrevendo. Para alguns programas, eu os modelo como uma coleção de objetos cujas interações dão origem ao comportamento do programa; aqui, a funcionalidade mais importante está intimamente ligada a um único objeto e, portanto, é implementada em métodos, com uma dispersão de funções utilitárias. Para outros programas, a coisa mais importante é um conjunto de funções que manipulam dados, e as classes são usadas apenas para implementar os "tipos de pato" naturais que são manipulados pelas funções.
fonte
Você pode dizer que, "em face da ambigüidade, recuse a tentação de adivinhar".
No entanto, não é nem mesmo um palpite. Você está absolutamente certo de que os resultados de ambas as abordagens são os mesmos, pois resolvem o seu problema.
Acredito que seja bom ter várias maneiras de cumprir metas. Eu humildemente diria a você, como outros usuários já fizeram, para empregar qualquer "gosto melhor" / pareça mais intuitivo , em termos de linguagem.
fonte