16

Soy relativamente nuevo en Python y tengo dificultades para conciliar las características del lenguaje con los hábitos que he adquirido de mis antecedentes en C++ y Java.Funciones de miembro no miembro frente a miembro en Python

El último número que estoy teniendo tiene que ver con la encapsulación, en concreto una idea mejor resumen del artículo 23 de Meyer "efectiva C++":

Prefer non-member non-friend functions to member functions.

Haciendo caso omiso de la falta de un mecanismo friend por un momento, se consideran preferible funciones miembro funciones no miembros en Python, también?

Una obligatorio, asnal ejemplo:

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 ya sea v.scale(2) o scale(v, 2) al doble de la magnitud del vector.

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

+2

Creo que esto simplemente no es cierto en Python. Los argumentos realmente no se sientan con Python, donde puedes modificar clases tan fácilmente. Python también se enfoca en la legibilidad, y creo que '' v.scale (2) '' es mucho más claro que '' scale (v, 2) ''. Si busca en la biblioteca estándar, todas las funciones, excepto las más generales, se guardarán como miembros en lugar de integradas. –

Respuesta

15

Interesante pregunta.

Estás empezando desde un lugar diferente que la mayoría de las preguntas que provienen de los programadores de Java, que tienden a suponer que necesitas clases cuando en su mayoría no las necesitas. En general, en Python no tiene sentido tener clases a menos que específicamente estés encapsulando datos.

Por supuesto, aquí en su ejemplo lo está haciendo en realidad, por lo que el uso de clases está justificado. Personalmente, yo diría que dado que tiene una clase, la función de miembro es la mejor manera de hacerlo: específicamente está haciendo una operación en esa instancia de vector particular, por lo que tiene sentido que la función sea una método en Vector.

Donde quiera que sea una función independiente (realmente no usamos la palabra "miembro" o "no miembro") es si necesita hacerlo funcionar con múltiples clases que no necesariamente heredan uno del otro o una base común. Gracias al tipado de patos, es una práctica bastante común hacer esto: especificar que su función espera un objeto con un conjunto particular de atributos o métodos, y hacer algo con eso.

+3

Mientras estamos aquí, una nota algo relacionada: una diferencia con C++ es que Python no tiene ningún concepto de 'public' o' private'. La sintaxis '_underscore' es la forma convencional de proporcionar una * sugerencia * de que algo es privado, pero no está * implementado *. –

2

Mire su propio ejemplo: la función no miembro debe acceder a los miembros de datos de la clase Vector. Esto no es una victoria para la encapsulación. Esto es especialmente así ya que cambia los datos de los miembros del objeto que se pasa. 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 al usar la función de no miembro. Por ejemplo, en este caso, solo puede lidiar con vectores de dos componentes. Sería mejor si hiciera uso de una capacidad de multiplicación vectorial, o si usara un método para iterar sobre los componentes.

En resumen:

  1. funciones miembro para operar el uso de objetos de clases a controlar;
  2. usan funciones no miembro para realizar operaciones puramente genéricas que se implementan en términos de métodos y operadores que son a su vez polimórficos.
  3. Probablemente sea mejor mantener la mutación del objeto en los métodos.
+0

Los miembros a los que se accede (componente xey) tienen que ser públicos para que el objeto sea útil. Teniendo en cuenta los vectores con diferentes dimensiones, sin embargo, tiene un punto sobre el polimorfismo. – delnan

+1

@delnan No es que considere la función independiente una violación de la encapsulación, sino que obviamente no mejora la encapsulación de ninguna manera. Desapruebo su falta de genericidad, solo puede lidiar con vectores de dos elementos. Tampoco estoy de acuerdo con el hecho de que cambie los datos del vector. – Marcin

+1

@Marcin: los nombres de los objetos del ejemplo son irrelevantes, y de hecho, en mi defensa, * se * etiquetaron como asíncronos. La pregunta solicita comentarios sobre los méritos relativos de las funciones miembro/no miembro y * no * de sugerencias sobre cómo hacer que los ejemplos arbitrarios sean más genéricos. – JimmidyJoo

4

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

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

Elija en consecuencia. En general, las funciones se crean iguales, por lo que todas deben tener las mismas suposiciones sobre la interfaz de una clase. Una vez que publique una función gratuita scale, estará anunciando efectivamente que .dX y .dY son parte de la interfaz pública de Vector. Eso probablemente no es lo que quieres. Usted 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 va a ser valioso para ti. Entonces, en este caso, ciertamente preferiría la función miembro.

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

3

Prefiero funciones no amigo que no son miembros de las funciones miembro

Ésta es una filosofía de diseño y puede y debe extenderse a todos los lenguajes de programación orientada a objetos paradigma. Si comprende 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 un motivo para incluir la función, un miembro de la Clase . Para pensar de otra manera, al diseñar una clase, después de haber enumerado todas las propiedades, debe determinar el conjunto mínimo de comportamientos que sería suficiente para formar la clase. Cualquier función de miembro que pueda escribir utilizando cualquiera de los métodos/funciones de miembros públicos disponibles debe hacerse pública.

¿Cuánto es esto aplicable en Python

Hasta cierto punto, si se tiene cuidado. Python admite una encapsulación más débil en comparación con los otros lenguajes OOP (como Java/C++), especialmente porque no hay miembros privados. (Hay algo llamado Variables privadas que un programador puede escribir fácilmente prefijando un '_' antes del nombre de la variable. Esto se convierte en clase privada a través de una función de creación de nombres). Entonces, si adoptamos literalmente la palabra de Scott Meyer, tenemos en cuenta que hay una delgada diferencia entre lo que se debe acceder desde la clase y lo que debería ser desde afuera. Lo mejor es dejar que el diseñador/programador decida si una función debe ser una parte integral de la clase o no. Uno de los principios de diseño que podemos adoptar fácilmente, "Unless your function required to access any of the properties of the class you can make it a non-member function".

1

Como scale se basa en la multiplicación miembro-racional de un vector, consideraría la aplicación de la multiplicación como un método y definir scale a ser 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.

+0

Creo que este es definitivamente un enfoque interesante para mi ejemplo específico. – JimmidyJoo

Cuestiones relacionadas