El Zen de Python afirma que solo debería haber una forma de hacer las cosas; sin embargo, con frecuencia me encuentro con el problema de decidir cuándo usar una función y cuándo usar un método.
Tomemos un ejemplo trivial: un objeto ChessBoard. Digamos que necesitamos alguna forma de obtener todos los movimientos legales de King disponibles en el tablero. ¿Escribimos ChessBoard.get_king_moves () o get_king_moves (chess_board)?
Aquí hay algunas preguntas relacionadas que miré:
- ¿Por qué Python usa 'métodos mágicos'?
- ¿Hay alguna razón por la que las cadenas de Python no tienen un método de longitud de cadena?
Las respuestas que obtuve fueron en gran parte inconclusas:
¿Por qué Python usa métodos para algunas funciones (por ejemplo, list.index ()) pero funciones para otras (por ejemplo, len (lista))?
La principal razón es la historia. Las funciones se utilizaron para aquellas operaciones que eran genéricas para un grupo de tipos y que estaban destinadas a funcionar incluso para objetos que no tenían ningún método (por ejemplo, tuplas). También es conveniente tener una función que se pueda aplicar fácilmente a una colección amorfa de objetos cuando se utilizan las características funcionales de Python (map (), apply () et al).
De hecho, implementar len (), max (), min () como una función incorporada es en realidad menos código que implementarlos como métodos para cada tipo. Se pueden objetar casos individuales, pero es parte de Python, y es demasiado tarde para hacer cambios tan fundamentales ahora. Las funciones deben permanecer para evitar una rotura masiva del código.
Si bien es interesante, lo anterior no dice mucho sobre qué estrategia adoptar.
Esta es una de las razones: con métodos personalizados, los desarrolladores podrían elegir un nombre de método diferente, como getLength (), length (), getlength () o lo que sea. Python impone un nombre estricto para que se pueda usar la función común len ().
Un poco más interesante. Mi opinión es que las funciones son, en cierto sentido, la versión Pythonic de las interfaces.
Por último, del propio Guido :
Hablar sobre las habilidades / interfaces me hizo pensar en algunos de nuestros nombres de métodos especiales "deshonestos". En la Referencia del lenguaje, dice, "Una clase puede implementar ciertas operaciones que son invocadas por una sintaxis especial (como operaciones aritméticas o subíndices y segmentaciones) definiendo métodos con nombres especiales". Pero existen todos estos métodos con nombres especiales como
__len__
o__unicode__
que parecen proporcionarse para el beneficio de las funciones integradas, más que para dar soporte a la sintaxis. Presumiblemente en un Python basado en interfaz, estos métodos se convertirían en métodos con nombres regulares en un ABC, por lo que__len__
se convertiría enclass container: ... def len(self): raise NotImplemented
Aunque, pensándolo un poco más, no veo por qué todas las operaciones sintácticas no invocarían el método apropiado con nombre normal en un ABC específico. "
<
", por ejemplo, supuestamente invocaría "object.lessthan
" (o quizás "comparable.lessthan
"). Entonces, otro beneficio sería la capacidad de alejar a Python de esta rareza del nombre destrozado, que me parece una mejora de HCI .Hm. No estoy seguro de estar de acuerdo (imagino que :-).
Hay dos partes del "fundamento de Python" que me gustaría explicar primero.
En primer lugar, elegí len (x) sobre x.len () por razones de HCI (
def __len__()
vino mucho más tarde). En realidad, hay dos razones entrelazadas, ambas HCI:(a) Para algunas operaciones, la notación de prefijo se lee mejor que el sufijo - las operaciones de prefijo (¡e infijo!) tienen una larga tradición en las matemáticas a las que les gustan las notaciones donde las imágenes ayudan al matemático a pensar en un problema. Comparar la facilidad con la que volvemos a escribir una fórmula como
x*(a+b)
enx*a + x*b
la torpeza de hacer lo mismo usando una notación OO prima.(b) Cuando leo un código que dice
len(x)
, sé que pide la longitud de algo. Esto me dice dos cosas: el resultado es un número entero y el argumento es una especie de contenedor. Al contrario, cuando leox.len()
, ya tengo que saber quex
es una especie de contenedor que implementa una interfaz o hereda de una clase que tiene un estándarlen()
. Sea testigo de la confusión que de vez en cuando tienen una clase que no está llevando a cabo un mapeo tiene unaget()
okeys()
método, o algo que no es un archivo tiene unwrite()
método.Al decir lo mismo de otra manera, veo 'len' como una operación incorporada . Odiaría perder eso. No puedo decir con certeza si quiso decir eso o no, pero 'def len (self): ...' ciertamente suena como si quisiera degradarlo a un método ordinario. Estoy fuertemente -1 en eso.
La segunda parte del fundamento de Python que prometí explicar es la razón por la que elegí métodos especiales para buscar
__special__
y no simplementespecial
. Estaba anticipando muchas operaciones que las clases querrían anular, algunas estándar (por ejemplo,__add__
o__getitem__
), algunas no tan estándar (por ejemplo, pickle's__reduce__
durante mucho tiempo no tuvo soporte en el código C). No quería que estas operaciones especiales usaran nombres de métodos ordinarios, porque entonces las clases preexistentes, o las clases escritas por usuarios sin una memoria enciclopédica para todos los métodos especiales, podrían definir accidentalmente operaciones que no tenían la intención de implementar. , con consecuencias posiblemente desastrosas. Ivan Krstić explicó esto de manera más concisa en su mensaje, que llegó después de que yo escribiera todo esto.- --Guido van Rossum (página de inicio: http://www.python.org/~guido/ )
Mi comprensión de esto es que en ciertos casos, la notación de prefijo tiene más sentido (es decir, Duck.quack tiene más sentido que quack (Duck) desde un punto de vista lingüístico) y, nuevamente, las funciones permiten "interfaces".
En tal caso, mi suposición sería implementar get_king_moves basándose únicamente en el primer punto de Guido. Pero eso todavía deja muchas preguntas abiertas con respecto a, por ejemplo, implementar una clase de pila y cola con métodos push y pop similares: ¿deberían ser funciones o métodos? (aquí supongo que funciones, porque realmente quiero señalar una interfaz push-pop)
TLDR: ¿Alguien puede explicar cuál debería ser la estrategia para decidir cuándo usar funciones o métodos?
X.frob
oX.__frob__
es independientefrob
.