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?
X.frob
ouX.__frob__
e autônomofrob
.