subplot python
¿Cuál es la diferencia entre una función, un método independiente y un método vinculado? (5)
es bastante difícil de entender
Bueno, es un tema bastante difícil, y tiene que ver con los descriptores.
Comencemos con la función. Todo está claro aquí; simplemente lo llama, todos los argumentos suministrados se pasan mientras se ejecuta:
>>> f = A.__dict__[''f1'']
>>> f(1)
1
Regular TypeError
se plantea en caso de cualquier problema con el número de parámetros:
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f1() takes exactly 1 argument (0 given)
Ahora, métodos. Los métodos son funciones con un poco de especias. Los descriptores vienen en juego aquí. Como se describe en el Modelo de datos , A.f1
y A().f1
se traducen en A.__dict__[''f1''].__get__(None, A)
y type(a).__dict__[''f1''].__get__(a, type(a))
respectivamente. Y los resultados de estos __get__
''difieren de la función raw f1
. Estos objetos son envoltorios alrededor del f1
original y contienen alguna lógica adicional.
En el caso del unbound method
independiente, esta lógica incluye una comprobación de si el primer argumento es una instancia de A
:
>>> f = A.f1
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got nothing instead)
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method f1() must be called with A instance as first argument (got int instance instead)
Si esta comprobación tiene éxito, ejecuta el f1
original con esa instancia como primer argumento:
>>> f(A())
<__main__.A object at 0x800f238d0>
Tenga en cuenta que im_self
atributo im_self
es None
:
>>> f.im_self is None
True
En el caso de un bound method
esta lógica proporciona inmediatamente a f1
original una instancia de A
que se creó (esta instancia se almacena en el atributo im_self
):
>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>
Entonces, bound
significa que la función subyacente está vinculada a alguna instancia. unbound
significa que todavía está vinculado, pero solo a una clase.
Estoy haciendo esta pregunta debido a una discusión sobre el hilo de comentario de esta respuesta . Estoy al 90% del camino para entenderlo.
In [1]: class A(object): # class named ''A''
...: def f1(self): pass
...:
In [2]: a = A() # an instance
f1
existe en tres formas diferentes:
In [3]: a.f1 # a bound method
Out[3]: <bound method a.f1 of <__main__.A object at 0x039BE870>>
In [4]: A.f1 # an unbound method
Out[4]: <unbound method A.f1>
In [5]: a.__dict__[''f1''] # doesn''t exist
KeyError: ''f1''
In [6]: A.__dict__[''f1''] # a function
Out[6]: <function __main__.f1>
¿Cuál es la diferencia entre el método vinculado , el método de unión libre y los objetos de función , todos los cuales están descritos por f1? ¿Cómo se llaman estos tres objetos? ¿Cómo pueden transformarse el uno en el otro? La documentation sobre esto es bastante difícil de entender.
Consulte la documentación de Python 2 y Python 3 para obtener más detalles.
Mi interpretación es la siguiente.
Fragmentos de Function
clase:
Python 3:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)
Python 2:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
- Si se llama a una función sin clase o instancia, es una función simple.
Si se llama a una función desde una clase o una instancia, se llama a su
__get__
para recuperar la función envuelta:
a.Bx
es lo mismo queB.__dict__[''x''].__get__(None, B)
. En Python 3, esto devuelve una función simple. En Python 2, esto devuelve una función independiente.segundo.
bx
es igual que eltype(b).__dict__[''x''].__get__(b, type(b)
. Esto devolverá un método enlazado tanto en Python 2 como en Python 3, lo que significa queself
pasará implícitamente como primer argumento.
Objeto de función es un objeto invocable creado por una definición de función. Tanto los métodos vinculados como los no vinculados son objetos invocables creados por un Descriptor llamado por el operador binario de puntos.
Los objetos de método enlazados y no im_func
tienen 3 propiedades principales: im_func
es el objeto de función definido en la clase, im_class
es la clase e im_self
es la instancia de clase. Para los métodos im_self
, im_self
es None
.
Cuando se llama a un método enlazado, llama a im_func
con im_self
ya que el primer parámetro siguió sus parámetros de llamada. los métodos unbound llaman a la función subyacente solo con sus parámetros de llamada.
Una función es creada por la declaración def
, o por lambda
. Bajo Python 2, cuando una función aparece dentro del cuerpo de una declaración de class
(o se pasa a una llamada de construcción de clase de type
), se transforma en un método independiente . (Python 3 no tiene métodos independientes, ver más abajo.) Cuando se accede a una función en una instancia de clase, se transforma en un método enlazado , que automáticamente suministra la instancia al método como el primer parámetro self
.
def f1(self):
pass
Aquí f1
es una función .
class C(object):
f1 = f1
Ahora C.f1
es un método C.f1
.
>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True
También podemos usar el constructor de clase de type
:
>>> C2 = type(''C2'', (object,), {''f1'': f1})
>>> C2.f1
<unbound method C2.f1>
Podemos convertir f1
a un método independiente de forma manual:
>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>
Los métodos no vinculados están vinculados por el acceso en una instancia de clase:
>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>
El acceso se traduce en llamadas a través del protocolo de descripción:
>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
Combinando estos:
>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>
O directamente:
>>> types.MethodType(f1, C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
La principal diferencia entre una función y un método independiente es que este último sabe a qué clase está destinada; llamar o vincular un método independiente requiere una instancia de su tipo de clase:
>>> f1(None)
>>> C.f1(None)
TypeError: unbound method f1() must be called with C instance as first argument (got NoneType instance instead)
>>> class D(object): pass
>>> f1.__get__(D(), D)
<bound method D.f1 of <__main__.D object at 0x7f6c98cfe290>>
>>> C.f1.__get__(D(), D)
<unbound method C.f1>
Dado que la diferencia entre una función y un método independiente es bastante mínima, Python 3 se deshace de la distinción; en Python 3 el acceso a una función en una instancia de clase solo le da la función en sí:
>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True
En Python 2 y Python 3, entonces, estos tres son equivalentes:
f1(C())
C.f1(C())
C().f1()
La vinculación de una función a una instancia tiene el efecto de fijar su primer parámetro (convencionalmente llamado self
) a la instancia. Por lo tanto, el método vinculado C().f1
es equivalente a cualquiera de:
(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())
Una cosa interesante que vi hoy es que, cuando asigno una función a un miembro de la clase, se convierte en un método independiente. Como:
class Test(object):
@classmethod
def initialize_class(cls):
def print_string(self, str):
print(str)
# Here if I do print(print_string), I see a function
cls.print_proc = print_string
# Here if I do print(cls.print_proc), I see an unbound method; so if I
# get a Test object o, I can call o.print_proc("Hello")