Diferencia entre métodos y funciones, en Python comparado con C ++

Estoy haciendo los tutoriales de Code Academy en Python, y estoy un poco confundido acerca de la definición de método y función. Del tutorial:

Ya conoce algunas de las funciones incorporadas que hemos utilizado en (o para crear) cadenas, como .upper() , .lower() , str() y len() .

Viniendo de C ++, creo que .upper() y .lower() se llamarían métodos y funciones len() y str() . En el tutorial, los términos parecen usarse indistintamente.

¿Python distingue entre métodos y funciones en la forma en que lo hace C ++?

A diferencia de Diferencia entre un método y una función , estoy preguntando sobre los detalles de Python. Los términos ‘método’ y ‘función’ no parecen seguir siempre la definición dada en la respuesta aceptada de la pregunta vinculada.

Una función es un objeto invocable en Python, es decir, se puede llamar utilizando el operador de llamada (aunque otros objetos pueden emular una función implementando __call__ ). Por ejemplo:

 >>> def a(): pass >>> a  >>> type(a)  

Un método es una clase especial de función, una que puede vincularse o no .

 >>> class A: ... def a(self): pass >>> Aa  >>> type(Aa)  >>> A().a > >>> type(A().a)  

Por supuesto, no se puede llamar a un método independiente (al menos no directamente sin pasar una instancia como argumento):

 >>> Aa() Traceback (most recent call last): File "", line 1, in  TypeError: unbound method a() must be called with A instance as first argument (got nothing instead) 

En Python, en la mayoría de los casos, no notará la diferencia entre un método vinculado, una función o un objeto invocable (es decir, un objeto que implementa __call__ ) o un constructor de clase. Todos tienen el mismo aspecto, solo tienen diferentes convenciones de nomenclatura. Sin embargo, bajo el capó, los objetos pueden parecer muy diferentes.

Esto significa que un método vinculado puede usarse como una función, esta es una de las muchas cosas pequeñas que hacen que Python sea tan poderoso

 >>> b = A().a >>> b() 

También significa que, aunque hay una diferencia fundamental entre len(...) y str(...) (este último es un constructor de tipos), no notarás la diferencia hasta que profundices un poco más:

 >>> len  >>> str  

Si aún no entiende cómo funcionan los métodos, una mirada a la implementación quizás pueda aclarar las cosas. Cuando se hace referencia a un atributo de instancia que no es un atributo de datos, se busca su clase. Si el nombre denota un atributo de clase válido que es un objeto de función, se crea un objeto de método empacando (punteros) el objeto de instancia y el objeto de función que se encuentran juntos en un objeto abstracto: este es el objeto de método. Cuando se llama al objeto de método con una lista de argumentos, se construye una nueva lista de argumentos a partir del objeto de instancia y la lista de argumentos, y se llama al objeto de función con esta nueva lista de argumentos.

http://docs.python.org/2/tutorial/classes.html#method-objects

Lee atentamente este extracto.

Significa :

1) Una instancia realmente no contiene un objeto que sea un método que sería su atributo.
De hecho, no hay ningún atributo de “método” en el __dict__ de una instancia ( __dict__ es el espacio de nombres de un objeto)

2) El hecho de que una instancia parezca tener un “método” cuando se llama un atributo “método” se debe a un proceso, no a la presencia de un objeto método dentro del espacio de nombres de una instancia

3) Además, no existe realmente un objeto método en el espacio de nombres de una clase.

Pero hay una diferencia con una instancia, porque debe haber algo en alguna parte que conduzca a un objeto de método real cuando se realiza una llamada, ¿no es así?

Lo que se llama un atributo de “método” de una clase, por la facilidad de la redacción, es en realidad un objeto de función que es un atributo en el espacio de nombres de la clase.
Es decir, un par (identificador de la función, función) es un miembro del __dict__ de una clase, y este atributo permite al intepreter construir un objeto de método cuando se realiza una llamada a un método.

4) De nuevo, el hecho de que una clase parezca tener un “método” cuando se llama un atributo “método” se debe a un proceso, no a la presencia de un objeto método dentro del espacio de nombres de una clase

EDITAR No estoy más seguro de eso; ver al final

5) Un objeto de método (no un objeto de “método”; me refiero a que el objeto real es realmente un método “, lo que se describe en el extracto) se crea en el momento de la llamada, no existe antes.
Es una especie de envoltorio: contiene punteros al objeto instancia y al objeto función en el que se basa el método.

Entonces, un método se basa en una función. Esta función es para mí el atributo real de la clase que contiene dicho “método”, porque esta función realmente pertenece al espacio de nombres ( __dict__ ) de la clase: esta función se describe como una cuando el __dict__ está impreso.
Se puede llegar a esta función desde el objeto de método utilizando el alias im_func o __func__ (ver el código a continuación)

.

Creo que estas nociones no son muy conocidas y entendidas. Pero el siguiente código prueba lo que dije.

 class A(object): def __init__(self,b=0): self.b = b print 'The __init__ object :\n',__init__ def addu(self): self.b = self.b + 10 print '\nThe addu object :\n',addu print '\nThe A.__dict__ items :\n', print '\n'.join(' {0:{align}11} : {1}'.format(*it,align='^') for it in A.__dict__.items()) a1 = A(101) a2 = A(2002) print '\nThe a1.__dict__ items:' print '\n'.join(' {0:{align}11} : {1}'.format(*it,align='^') for it in a1.__dict__.items()) print '\nThe a2.__dict__ items:' print '\n'.join(' {0:{align}11} : {1}'.format(*it,align='^') for it in a2.__dict__.items()) print '\nA.addu.__func__ :',A.addu.__func__ print id(A.addu.__func__),'==',hex(id(A.addu.__func__)) print print 'A.addu :\n ', print A.addu,'\n ',id(A.addu),'==',hex(id(A.addu)) print 'a1.addu :\n ', print a1.addu,'\n ',id(a1.addu),'==',hex(id(a1.addu)) print 'a2.addu :\n ', print a2.addu,'\n ',id(a2.addu),'==',hex(id(a2.addu)) a2.addu() print '\na2.b ==',a2.b print '\nThe A.__dict__ items :\n', print '\n'.join(' {0:{align}11} : {1}'.format(*it,align='^') for it in A.__dict__.items()) 

resultado

 The __init__ object :  The addu object :  The A.__dict__ items : __module__ : __main__ addu :  __dict__ :  __weakref__ :  __doc__ : None __init__ :  The a1.__dict__ items: b : 101 The a2.__dict__ items: b : 2002 A.addu.__func__ :  18765040 == 0x11e54f0 A.addu :  18668040 == 0x11cda08 a1.addu : > 18668040 == 0x11cda08 a2.addu : > 18668040 == 0x11cda08 a2.b == 2012 The A.__dict__ items : __module__ : __main__ addu :  __dict__ :  __weakref__ :  __doc__ : None __init__ :  

.

EDITAR

Algo me preocupa y no conozco las profundas entrañas del tema:

El código anterior muestra que A.addu , a1.addu y a2.addu son todos el mismo objeto de método, con una identidad única.
Sin embargo, se dice que A.addu es un método A.addu porque no tiene información sobre una instancia en particular,
y a1.addu y a2.addu son métodos enlazados porque cada uno tiene información que designa la instancia que debe estar relacionada con las operaciones del método.
Lógicamente, para mí, eso significaría que el método debería ser diferente para cada uno de estos 3 casos.

PERO la identidad es la misma para los tres, y además esta identidad es diferente de la identidad de la función en la que se basa el método.
Lleva a la conclusión de que el método es realmente un objeto que vive en la memoria, y que no cambia de una llamada de una instancia a otra de otra instancia.

SIN EMBARGO , al imprimir el espacio de nombres __dict__ de la clase, incluso después de la creación de instancias y la llamada del “método” addu() , este espacio de nombres no expone un nuevo objeto que podría identificarse al objeto de método diferente de la función addu .

Qué significa eso ?
Me da la impresión de que tan pronto como se crea un objeto método, no se destruye, sino que se almacena en la memoria (RAM).
Pero vive escondido y solo los procesos que forman el funcionamiento del interperador saben cómo y dónde encontrarlo.
Este objeto oculto, el objeto de método real, debe tener la capacidad de cambiar la referencia a la instancia a la que se debe aplicar la función, o hacer referencia a None si se llama como un método independiente. Eso es lo que me parece, pero es solo una hipótesis sobre el brain-storming.

¿Alguien sabe algo sobre este interrogatorio?


Para responder a la pregunta, se puede considerar correcto llamar a las funciones .upper y .lower , ya que en realidad se basan en funciones como cada método de una clase.

Sin embargo, el siguiente resultado es especial, probablemente porque son métodos / funciones integrados, no son métodos / funciones del usuario como en mi código.

 x = 'hello' print x.upper.__func__ 

resultado

  print x.upper.__func__ AttributeError: 'builtin_function_or_method' object has no attribute '__func__' 

Básicamente, sí, Python sí los distingue, pero en Python es común ver los métodos como un subconjunto de funciones. Los métodos están asociados con una clase o instancia, y las “funciones independientes” no lo son. Algo que es un método también es una función, pero puede haber funciones que no son métodos.

Sin embargo, como Jon Clements mencionó en su comentario, la distinción no es tan rígida como en C ++. Las funciones autónomas se pueden “convertir” en métodos en tiempo de ejecución, y los métodos se pueden asignar a las variables de tal forma que se comporten efectivamente de manera diferente que las funciones independientes. Entonces el límite entre métodos y funciones es permeable.

En la siguiente definición de clase:

 class MyClass: """A simple example class""" def f(self): return 'hello world' 
  • Clase : MyClass
  • Función : f ()
  • Método : Ninguno (En realidad, no aplicable)

Permite crear una instancia de la clase anterior. Lo haremos asignando class object, ie MyClass() a var x

  x = MyClass() 

Aquí,

  • Función : ninguna
  • Método : xf ()

Y no olvidemos que el function object MyClass.f se usó para definir (internamente) el method object xf cuando asignamos x a MyClass ()