python function coding-style methods

title in python plot



En Python, ¿cuándo debería usar una función en lugar de un método? (6)

Aquí hay una regla general: si el código actúa sobre una sola instancia de un objeto, use un método. Mejor aún: 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í:

chessboard = Chessboard() ... chessboard.get_king_moves()

No lo pienses más. 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 hacer una función.

El Zen de Python establece que solo debería haber una forma de hacer las cosas, pero 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 Rey 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 cual las cadenas de Python no tengan un método de longitud de cadena?

Las respuestas que obtuve no fueron en gran medida concluyentes:

¿Por qué Python utiliza métodos para algunas funcionalidades (por ejemplo, list.index ()) pero funciones para otras (por ej. Len (list))?

La razón principal es la historia. Las funciones se usaron 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 (p. Ej., Tuplas). También es conveniente tener una función que se pueda aplicar fácilmente a una colección amorfa de objetos cuando se usan las características funcionales de Python (map (), apply () y otros).

De hecho, implementar len (), max (), min () como función incorporada es en realidad menos código que implementarlos como métodos para cada tipo. Uno puede objetar sobre casos individuales pero es parte de Python, y es demasiado tarde para hacer cambios tan fundamentales ahora. Las funciones deben permanecer para evitar la rotura masiva de código.

Si bien es interesante, lo anterior no dice mucho sobre qué estrategia adoptar.

Esta es una de las razones: con los métodos personalizados, los desarrolladores serían libres de elegir un nombre de método diferente, como getLength (), length (), getlength () o lo que sea. Python impone el uso de nombres estrictos 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 de Pythonic de las interfaces.

Por último, del propio Guido :

Hablar de las Habilidades / Interfaces me hizo pensar en algunos de nuestros nombres de métodos especiales "pícaros". En la Referencia del lenguaje, dice: "Una clase puede implementar ciertas operaciones que se invocan mediante una sintaxis especial (como operaciones aritméticas o de subíndices y segmentación) al definir 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 incorporadas, en lugar de ser compatibles con la sintaxis. Presumiblemente en una Python basada en interfaz, estos métodos se convertirían en métodos de nombre regular en un ABC, de modo que __len__ se convertiría en

class container: ... def len(self): raise NotImplemented

Sin embargo, al pensarlo un poco más, no veo por qué todas las operaciones sintácticas no invocarían el método apropiado normalmente nombrado en un ABC específico. " < ", por ejemplo, presumiblemente invocaría " object.lessthan " (o quizás " comparable.lessthan "). Así que otro beneficio sería la capacidad de alejar a Python de esta rareza de nombre mutilado, lo que me parece una mejora de HCI .

Hm. No estoy seguro de estar de acuerdo (imagine eso :-).

Hay dos partes del "razonamiento de Python" que me gustaría explicar primero.

Antes que nada, elegí len (x) sobre x.len () para razones de HCI ( def __len__() llegó mucho más tarde). En realidad, hay dos razones entrelazadas, ambas HCI:

(a) Para algunas operaciones, la notación de prefijo simplemente lee mejor que postfix - las operaciones de prefijo (¡e infijo!) tienen una larga tradición en matemáticas a las que les gustan las notaciones donde los elementos visuales ayudan al matemático a pensar en un problema. Compare lo fácil con lo que reescribimos una fórmula como x*(a+b) en x*a + x*b a la torpeza de hacer lo mismo usando una notación OO cruda.

(b) Cuando leo un código que dice len(x) que está pidiendo la longitud de algo. Esto me dice dos cosas: el resultado es un número entero, y el argumento es algún tipo de contenedor. Por el contrario, cuando leo x.len() , tengo que saber que x es un tipo de contenedor que implementa una interfaz o hereda de una clase que tiene un len() estándar. Sea testigo de la confusión que ocasionalmente tenemos cuando una clase que no implementa una asignación tiene un método get() o keys() , o algo que no es un archivo tiene un método write() .

Al decir lo mismo de otra manera, veo ''len'' como una operación incorporada. Odiaría perder eso. No puedo decir con seguridad si lo dijiste o no, pero ''def len (self): ...'' ciertamente parece que quieres degradarlo a un método común. Estoy fuertemente -1 en eso.

La segunda parte del razonamiento de Python que prometí explicar es la razón por la que elegí métodos especiales para buscar __special__ y no meramente special . Estaba anticipando muchas operaciones que las clases podrían querer anular, algunas estándar (por ejemplo, __add__ o __getitem__ ), otras no tan estándar (por ejemplo, la __reduce__ de __reduce__ durante mucho tiempo no tenía soporte para el código C). No quería que estas operaciones especiales utilizaran 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 pretendían implementar. , con consecuencias posiblemente desastrosas Ivan Krstić explicó esto con más precisión en su mensaje, que llegó después de que había escrito 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 simplemente tiene más sentido (es decir, Duck.quack tiene más sentido que quack (Duck) desde un punto de vista lingüístico.) Y de nuevo, 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 aún deja muchas preguntas abiertas con respecto a, digamos, la implementación de una clase de pila y cola con métodos push y pop similares, ¿deberían ser funciones o métodos? (Aquí supongo funciones, porque realmente quiero señalar una interfaz push-pop)

TLDR: ¿Puede alguien explicar cuál debería ser la estrategia para decidir cuándo usar las funciones frente a los métodos?


Generalmente utilizo las clases para implementar un conjunto lógico de capacidades para algo , de modo que en el resto de mi programa pueda razonar sobre el tema, sin tener que preocuparme por todas las pequeñas preocupaciones que conforman su implementación.

Cualquier cosa que sea parte de esa abstracción central de "lo que puedes hacer con una cosa " debería ser un método. Por lo general, esto 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 llegas a operaciones de nivel superior, especialmente si involucran muchas cosas , creo que normalmente se expresan de forma más natural como funciones, si se pueden construir a partir de la abstracción pública de una cosa sin necesidad de un acceso especial a las partes internas (a menos que re métodos de algún otro objeto). Esto tiene la gran ventaja de que cuando decido reescribir completamente las partes internas 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. solo funcionará Me parece 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 modelé como una colección de objetos cuyas interacciones dan lugar al comportamiento del programa; Aquí, la funcionalidad más importante está estrechamente relacionada con un solo objeto, y así 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 están en uso solo para implementar los "tipos de pato" naturales que son manipulados por las funciones.


Mi regla general es esta: ¿la operación se realiza en el objeto o por el objeto?

si lo hace el objeto, debe ser una operación miembro. Si también se puede aplicar a otras cosas, o al objeto lo hace otra cosa, entonces debería ser una función (o tal vez un miembro de otra cosa).

Al introducir la programación, es tradicional (aunque implementación incorrecta) describir los objetos en términos de objetos del mundo real como los automóviles. Mencionas un pato, así que vamos con eso.

class duck: def __init__(self):pass def eat(self, o): pass def crap(self) : pass def die(self) ....

En el contexto de la analogía de "los objetos son cosas reales", es "correcto" agregar un método de clase para cualquier cosa que el objeto pueda hacer. Entonces, si quiero matar a un pato, ¿agrego un .kill () al pato? No ... por lo que sé, los animales no se suicidan. Por lo tanto, si quiero matar a un pato, debería hacer esto:

def kill(o): if isinstance(o, duck): o.die() elif isinstance(o, dog): print "WHY????" o.die() elif isinstance(o, nyancat): raise Exception("NYAN "*9001) else: print "can''t kill it."

Al alejarnos de esta analogía, ¿por qué usamos métodos y clases? Porque queremos contener datos y, con un poco de suerte, estructurar nuestro código de manera tal 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 principal de la encapsulación es en realidad a lo que se reduce todo esto: como diseñador, debe ocultar todo lo relacionado con la implementación y las funciones internas de la clase, lo cual no es necesariamente necesario para cualquier usuario u otro desarrollador. 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 la instancia, entonces 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 clase, debe ser una función miembro.


Normalmente pienso en un objeto como una persona.

Los atributos son el nombre de la persona, la altura, el tamaño del zapato, etc.

Los métodos y las funciones son operaciones que la persona puede realizar.

Si la operación puede ser realizada por cualquier persona, sin requerir nada exclusivo de 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 actúa sobre la persona (por ejemplo, comer, caminar, ...) o requiere que se involucre a alguien (como bailar, escribir un libro, ...), entonces debe ser un método .

Por supuesto, no siempre es trivial traducir esto al objeto específico con el que está trabajando, pero creo que es una buena manera de pensarlo.


Puede decir que, "frente a 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 ya que resuelven su problema.

Creo que es bueno tener múltiples formas de lograr los objetivos. Humildemente les digo, como otros usuarios ya lo hicieron, que empleen lo que "sabe mejor" / se siente más intuitivo , en términos de lenguaje.


Usa una clase cuando quieras:

1) Aislar el código de llamada de los detalles de implementación, aprovechando la abstraction y el encapsulation .

2) Cuando quieres ser sustituible por otros objetos, aprovechando el polymorphism .

3) Cuando desee reutilizar código para objetos similares, aprovechando la inheritance .

Use una función para llamadas que tengan sentido en muchos tipos de objetos diferentes; por ejemplo, las funciones integrales len y repr aplican a muchos tipos de objetos.

Dicho esto, la elección a veces se reduce a una cuestión de gusto. Piense en términos de lo que es más conveniente y legible para las llamadas típicas. Por ejemplo, ¿cuál sería mejor (x.sin()**2 + y.cos()**2).sqrt() o sqrt(sin(x)**2 + cos(y)**2) ?