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?
fuente
X.frob
oX.__frob__
es independientefrob
.Respuestas:
Mi regla general es esta: ¿la operación se realiza en el objeto o por el objeto?
si lo hace el objeto, debería ser una operación miembro. Si también pudiera aplicarse a otras cosas, o si algo más lo hace en el objeto, entonces debería ser una función (o quizás un miembro de otra cosa).
Al introducir la programación, es tradicional (aunque la implementación es incorrecta) describir los objetos en términos de objetos del mundo real, como los automóviles. Mencionaste un pato, así que vamos con eso.
En el contexto de la analogía "los objetos son cosas reales", es "correcto" agregar un método de clase para cualquier cosa que el objeto pueda hacer. Entonces, digamos que quiero matar un pato, ¿agrego un .kill () al pato? No ... que yo sepa, los animales no se suicidan. Por lo tanto, si quiero matar un pato, debería hacer esto:
Alejándonos de esta analogía, ¿por qué usamos métodos y clases? Porque queremos contener datos y, con suerte, estructurar nuestro código de manera que sea reutilizable y extensible en el futuro. Esto nos lleva a la noción de encapsulación que es tan apreciada por el diseño OO.
El principio de encapsulación es realmente a lo que se reduce: como diseñador, debe ocultar todo lo relacionado con la implementación y los componentes internos de la clase a los que no es absolutamente necesario que ningún usuario u otro desarrollador acceda. Debido a que tratamos con instancias de clases, esto se reduce a "qué operaciones son cruciales en esta instancia ". Si una operación no es específica de una instancia, no debería ser una función miembro.
TL; DR : lo que dijo @Bryan. Si opera en una instancia y necesita acceder a datos que son internos a la instancia de la clase, debería ser una función miembro.
fuente
Usa una clase cuando quieras:
1) Aísle el código de llamada de los detalles de implementación, aprovechando la abstracción y la encapsulación .
2) Cuando desee ser sustituible por otros objetos, aproveche el polimorfismo .
3) Cuando desee reutilizar código para objetos similares, aproveche la herencia .
Utilice una función para llamadas que tengan sentido en muchos tipos de objetos diferentes; por ejemplo, las funciones integradas len y repr se aplican a muchos tipos de objetos.
Dicho esto, la elección a veces se reduce a una cuestión de gustos. Piense en lo que es más conveniente y legible para llamadas típicas. Por ejemplo, ¿cuál sería mejor
(x.sin()**2 + y.cos()**2).sqrt()
osqrt(sin(x)**2 + cos(y)**2)
?fuente
Aquí hay una regla general simple: si el código actúa sobre una sola instancia de un objeto, use un método. Aún mejor: use un método a menos que haya una razón convincente para escribirlo como una función.
En su ejemplo específico, quiere que se vea así:
No lo pienses demasiado. Utilice siempre métodos hasta que llegue el momento en que se diga a sí mismo "no tiene sentido hacer de esto un método", en cuyo caso puede crear una función.
fuente
len()
, también se puede argumentar que el diseño tiene sentido, aunque personalmente creo que las funciones tampoco serían tan malas allí; simplemente tendríamos otra convención quelen()
siempre tiene que devolver un entero (pero con todos los problemas adicionales de backcomp, tampoco recomendaría eso para Python)Normalmente pienso en un objeto como una persona.
Los atributos son el nombre de la persona, la altura, la talla del zapato, etc.
Los métodos y funciones son operaciones que la persona puede realizar.
Si la operación puede ser realizada por cualquier persona, sin requerir nada exclusivo para esta persona específica (y sin cambiar nada en esta persona específica), entonces es una función y debe escribirse como tal.
Si una operación está actuando sobre la persona (por ejemplo, comer, caminar, ...) o requiere algo exclusivo para que esta persona se involucre (como bailar, escribir un libro, ...), entonces debería ser un método .
Por supuesto, no siempre es trivial traducir esto en el objeto específico con el que estás trabajando, pero creo que es una buena manera de pensarlo.
fuente
height(person)
,person.height
¿ no ?are_married(person1, person2)
? Esta consulta es muy general y, por tanto, debería ser una función y no un método.Generalmente utilizo clases para implementar un conjunto lógico de capacidades para alguna cosa , de modo que en el resto de mi programa que puedo razonar acerca de la cosa , no tener que preocuparse por todas las pequeñas preocupaciones que conforman su aplicación.
Cualquier cosa que forme parte de esa abstracción central de "lo que puedes hacer con una cosa " debería ser normalmente un método. Esto generalmente incluye todo lo que puede alterar una cosa , ya que el estado de los datos internos generalmente se considera privado y no forma parte de la idea lógica de "lo que se puede hacer con una cosa ".
Cuando se trata de operaciones de nivel superior, especialmente si involucran múltiples cosas , encuentro que generalmente se expresan de manera más natural como funciones, si se pueden construir a partir de la abstracción pública de una cosa sin necesidad de acceso especial a los internos (a menos que ' re métodos de algún otro objeto). Esto tiene la gran ventaja de que cuando decido reescribir completamente los aspectos internos de cómo funciona mi cosa (sin cambiar la interfaz), solo tengo un pequeño conjunto básico de métodos para reescribir, y luego todas las funciones externas escritas en términos de esos métodos. simplemente funcionará. Encuentro que insistir en que todas las operaciones relacionadas con la clase X son métodos en la clase X conduce a clases demasiado complicadas.
Sin embargo, depende del código que estoy escribiendo. Para algunos programas, los modelo como una colección de objetos cuyas interacciones dan lugar al comportamiento del programa; aquí la funcionalidad más importante está estrechamente acoplada a un solo objeto y, por lo tanto, se implementa en métodos, con una dispersión de funciones de utilidad. Para otros programas, lo más importante es un conjunto de funciones que manipulan datos, y las clases se utilizan sólo para implementar los "tipos pato" naturales que son manipulados por las funciones.
fuente
Se puede decir que "ante la ambigüedad, rechace la tentación de adivinar".
Sin embargo, ni siquiera es una suposición. Está absolutamente seguro de que los resultados de ambos enfoques son los mismos en el sentido de que resuelven su problema.
Creo que es bueno tener múltiples formas de lograr los objetivos. Le diría humildemente, como ya hicieron otros usuarios, que emplee lo que "sepa mejor" / se sienta más intuitivo , en términos de lenguaje.
fuente