python encapsulation member-functions non-member-functions

title python



No miembro vs funciones miembro en Python (5)

Como la scale basa en la multiplicación de un vector por parte de los miembros, consideraría que implementar la multiplicación como método y definir la scale es más general:

class Vector(object): def __init__(self, dX, dY): self._dX = dX self._dY = dY def __str__(self): return "->(" + str(self._dX) + ", " + str(self._dY) + ")" def __imul__(self, other): if other is Vector: self._dX *= other._dX self._dY *= other._dY else: self._dX *= other self._dY *= other return self def scale(vector, scalar): vector *= scalar

Por lo tanto, la interfaz de clase es rica y simplificada mientras se mantiene la encapsulación.

Soy relativamente nuevo en Python y estoy luchando por conciliar las características del lenguaje con los hábitos que he aprendido de mi experiencia en C ++ y Java.

El último número que tengo que ver tiene que ver con la encapsulación, específicamente una idea resumida mejor por el ítem 23 de " E + C ++ efectivo " de Meyer:

Prefiere las funciones que no sean miembros que no sean amigas a las funciones miembro .

Ignorando la falta de un mecanismo de friend por un momento, ¿ se considera que las funciones que no son miembros son preferibles a las funciones miembro en Python ?

Un ejemplo obligatorio, asinino:

class Vector(object): def __init__(self, dX, dY): self.dX = dX self.dY = dY def __str__(self): return "->(" + str(self.dX) + ", " + str(self.dY) + ")" def scale(self, scalar): self.dX *= scalar self.dY *= scalar def scale(vector, scalar): vector.dX *= scalar vector.dY *= scalar

Dado v = Vector(10, 20) , ahora podemos llamar v.scale(2) o scale(v, 2) para duplicar la magnitud del vector.

Teniendo en cuenta el hecho de que estamos usando propiedades en este caso, ¿ cuál de las dos opciones es mejor, si es que existe, y por qué?


Interesante pregunta.

Está comenzando desde un lugar diferente al de la mayoría de las preguntas de los programadores de Java, que tienden a asumir que necesita clases cuando en su mayoría no las necesita. En general, en Python no tiene sentido tener clases a menos que esté haciendo específicamente encapsulación de datos.

Por supuesto, aquí en tu ejemplo estás haciendo eso, por lo que el uso de clases está justificado. Personalmente, diría que ya que tienes una clase, entonces la función miembro es la mejor manera de hacerlo: estás haciendo una operación específica en esa instancia de vector en particular, por lo que tiene sentido que la función sea un método en Vector.

Si desea que sea una función independiente (realmente no usamos la palabra "miembro" o "no miembro") es si necesita hacer que funcione con varias clases que no necesariamente se heredan entre sí o una base comun Gracias a la escritura de pato, es una práctica bastante común hacer esto: especifique que su función espera un objeto con un conjunto particular de atributos o métodos, y haga algo con ellos.


Mire su propio ejemplo: la función que no es miembro tiene que acceder a los miembros de datos de la clase Vector. Esto no es una victoria para la encapsulación. Esto es especialmente así porque cambia los miembros de datos del objeto pasado. En este caso, podría ser mejor devolver un vector escalado y dejar el original sin cambios.

Además, no obtendrá ningún beneficio del polimorfismo de clase con la función de no miembro. Por ejemplo, en este caso, todavía puede hacer frente a los vectores de dos componentes. Sería mejor si usara una capacidad de multiplicación de vectores, o usara un método para iterar sobre los componentes.

En resumen:

  1. usa las funciones miembro para operar en los objetos de las clases que controlas;
  2. usar funciones no miembros para realizar operaciones puramente genéricas que se implementan en términos de métodos y operadores que son en sí mismos polimórficos.
  3. Probablemente es mejor mantener la mutación de objetos en los métodos.

Una función gratuita le brinda la flexibilidad de usar también el tipeo de pato para ese primer parámetro.

Una función miembro le brinda la expresividad de asociar la funcionalidad con la clase.

Elija en consecuencia. En general, las funciones se crean igual, por lo que todas deben tener las mismas suposiciones sobre la interfaz de una clase. Una vez que publica una scale función gratuita, efectivamente está anunciando que .dX y .dY son parte de la interfaz pública de Vector . Eso probablemente no es lo que quieres. Está haciendo esto a cambio de la capacidad de reutilizar la misma función con otros objetos que tienen un .dX y .dY . Eso probablemente no sea valioso para ti. Así que en este caso, sin duda preferiría la función miembro.

Para obtener buenos ejemplos de cómo preferir una función libre, no necesitamos buscar más que la biblioteca estándar: sorted es una función libre, y no una función miembro de la list , porque conceptualmente debería ser capaz de crear la lista que resulta de clasificar cualquier iterable. secuencia.


Prefiere las funciones que no son miembros que no son amigos a las funciones miembro

Esta es una filosofía de diseño y puede y debe extenderse a todos los lenguajes de programación OOP Paradigm. Si entiendes la esencia de esto, el concepto es claro.

Si puede hacerlo sin requerir acceso privado / protegido a los miembros de una Clase, su diseño no tiene una razón para incluir la función, un miembro de la Clase. Para pensar esto de otra manera, cuando diseñe una Clase, después de que haya enumerado todas las propiedades, debe determinar el conjunto mínimo de comportamientos que serían suficientes para hacer la Clase. Cualquier función miembro que pueda escribir utilizando cualquiera de los métodos públicos disponibles / funciones miembro debe hacerse pública.

¿Cuánto es esto aplicable en Python

Hasta cierto punto si eres cuidadoso. Python admite una encapsulación más débil en comparación con otros lenguajes OOP (como Java / C ++), especialmente porque no hay miembros privados. (Hay algo que se llama variables privadas que un programador puede escribir fácilmente con el prefijo ''_'' antes del nombre de la variable. Esto se convierte en una clase privada a través de una función de denominación de nombres). Entonces, si adoptamos literalmente la palabra de Scott Meyer teniendo en cuenta que existe una ligera diferencia entre lo que debería accederse desde Clase y lo que debería ser desde fuera. Es mejor dejar que el diseñador / programador decida si una función debe ser una parte integral de la Clase o No. Podemos adoptar fácilmente un principio de diseño: "Unless your function required to access any of the properties of the class you can make it a non-member function".