web-dev-qa-db-fra.com

Quelle est la différence entre une fonction, une méthode non liée et une méthode liée?

Je pose cette question à cause d'une discussion sur le fil de commentaires de cette réponse . Je suis à 90% du chemin pour contourner la tête.

In [1]: class A(object):  # class named 'A'
   ...:     def f1(self): pass
   ...:
In [2]: a = A()  # an instance

f1 existe sous trois formes différentes:

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>

Quelle est la différence entre la méthode liée , la méthode non liée et fonction objets, tous décrits par f1? Comment appelle-t-on ces trois objets? Comment peuvent-ils se transformer les uns les autres? Le documentation sur ce genre de choses est assez difficile à comprendre.

56
Benjamin Hodgson

Une fonction est créée par l'instruction def, ou par lambda. Sous Python 2, lorsqu'une fonction apparaît dans le corps d'une instruction class (ou est passée à un appel de construction de classe type), elle est transformée en an méthode non liée. (Python 3 n'a pas de méthodes non liées; voir ci-dessous.) Lorsqu'une fonction est accédée sur une instance de classe, elle est transformée en méthode liée, qui fournit automatiquement l'instance à la méthode en tant que premier paramètre self.

def f1(self):
    pass

Ici f1 Est une fonction.

class C(object):
    f1 = f1

Maintenant C.f1 Est une méthode non liée.

>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True

Nous pouvons également utiliser le constructeur de classe type:

>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>

Nous pouvons convertir f1 En une méthode non liée manuellement:

>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>

Les méthodes non liées sont liées par l'accès à une instance de classe:

>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>

L'accès se traduit par un appel via le protocole descripteur:

>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

La combinaison de ces:

>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>

Ou directement:

>>> types.MethodType(f1, C(), C)                
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>

La principale différence entre une fonction et une méthode non liée est que cette dernière sait à quelle classe elle est liée; l'appel ou la liaison d'une méthode non liée nécessite une instance de son type de classe:

>>> 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>

Étant donné que la différence entre une fonction et une méthode indépendante est assez minime, Python 3 supprime la distinction; sous Python 3 accède à une fonction sur une instance de classe) vous donne juste la fonction elle-même:

>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True

Dans les deux Python 2 et Python 3, alors, ces trois sont équivalents:

f1(C())
C.f1(C())
C().f1()

La liaison d'une fonction à une instance a pour effet de fixer son premier paramètre (conventionnellement appelé self) à l'instance. Ainsi, la méthode liée C().f1 est équivalente à:

(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())
64
ecatmur

est assez difficile à comprendre

Eh bien, c'est un sujet assez difficile, et cela a à voir avec les descripteurs.

Commençons par la fonction. Tout est clair ici - vous l'appelez simplement, tous les arguments fournis sont passés lors de son exécution:

>>> f = A.__dict__['f1']
>>> f(1)
1

Un TypeError régulier est levé en cas de problème avec le nombre de paramètres:

>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f1() takes exactly 1 argument (0 given)

Maintenant, les méthodes. Les méthodes sont des fonctions avec un peu d'épices. Les descripteurs entrent en jeu ici. Comme décrit dans Modèle de données , A.f1 Et A().f1 sont traduits respectivement en A.__dict__['f1'].__get__(None, A) et type(a).__dict__['f1'].__get__(a, type(a)). Et les résultats de ces __get__ Diffèrent de la fonction brute f1. Ces objets sont des wrappers autour du f1 D'origine et contiennent une logique supplémentaire.

Dans le cas de unbound method Cette logique inclut une vérification si le premier argument est une instance 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 cette vérification réussit, elle exécute le f1 Original avec cette instance comme premier argument:

>>> f(A())
<__main__.A object at 0x800f238d0>

Notez que l'attribut im_self Est None:

>>> f.im_self is None
True

Dans le cas de bound method, Cette logique fournit immédiatement f1 D'origine avec une instance de A dont elle a été créée (cette instance est en fait stockée dans l'attribut im_self):

>>> f = A().f1
>>> f.im_self
<__main__.A object at 0x800f23950>
>>> f()
<__main__.A object at 0x800f23950>

Ainsi, bound signifie que la fonction sous-jacente est liée à une instance. unbound signifie qu'il est toujours lié, mais uniquement à une classe.

8
Roman Bodnarchuk

L'objet fonction est un objet appelable créé par une définition de fonction. Les méthodes liées et non liées sont des objets appelables créés par un descripteur appelé par l'opérateur binaire point.

Les objets de méthode liés et non liés ont 3 propriétés principales: im_func est l'objet fonction défini dans la classe, im_class est la classe et im_self est l'instance de classe. Pour les méthodes non liées, im_self est None.

Lorsqu'une méthode liée est appelée, elle appelle im_func avec im_self car le premier paramètre a suivi ses paramètres d'appel. les méthodes non liées appellent la fonction sous-jacente avec seulement ses paramètres d'appel.

3
Mohammad Alhashash

Une chose intéressante que j'ai vue aujourd'hui est que, lorsque j'affecte une fonction à un membre de la classe, elle devient une méthode non liée. Tel que:

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")
2
robbie fan

Veuillez consulter la documentation Python 2 et Python pour plus de détails.

Mon interprétation est la suivante.

Extraits de classe Function:

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)
  1. Si une fonction est appelée sans classe ni instance, il s'agit d'une fonction simple.
  2. Si une fonction est appelée à partir d'une classe ou d'une instance, son __get__ Est appelé pour récupérer la fonction encapsulée:
    une. B.x Est identique à B.__dict__['x'].__get__(None, B). En Python 3, cela renvoie une fonction simple. En Python 2, cela renvoie une fonction non liée.

    b. b.x Est identique à type(b).__dict__['x'].__get__(b, type(b). Cela renverra une méthode liée dans les deux Python 2 et Python 3, ce qui signifie que self sera implicitement passé comme premier argument).

2
lyu.l

Quelle est la différence entre une fonction, une méthode non liée et une méthode liée?

Du point de vue révolutionnaire ce qui est une fonction il n'y a pas de différence. Python les fonctionnalités orientées objet sont construites sur un environnement basé sur les fonctions.

Être lié est égal à:

La fonction prendra-t-elle le classe ( cls ) ou le instance d'objet ​​( self ) comme premier paramètre ou non?

Voici l'exemple:

class C:

    #instance method 
    def m1(self, x):
        print(f"Excellent m1 self {self} {x}")

    @classmethod
    def m2(cls, x):
        print(f"Excellent m2 cls {cls} {x}")

    @staticmethod
    def m3(x):
        print(f"Excellent m3 static {x}")    

ci=C()
ci.m1(1)
ci.m2(2)
ci.m3(3)

print(ci.m1)
print(ci.m2)
print(ci.m3)
print(C.m1)
print(C.m2)
print(C.m3)

Les sorties:

Excellent m1 self <__main__.C object at 0x000001AF40319160> 1
Excellent m2 cls <class '__main__.C'> 2
Excellent m3 static 3
<bound method C.m1 of <__main__.C object at 0x000001AF40319160>>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>
<function C.m1 at 0x000001AF402FBB70>
<bound method C.m2 of <class '__main__.C'>>
<function C.m3 at 0x000001AF4023CBF8>

La sortie affiche la fonction statique m3 ne sera jamais appelé lié. C.m2 est lié à la classe C car nous avons envoyé le paramètre cls qui est le pointeur de classe.

ci.m1 et ci.m2 sont tous deux liés; ci.m1 car nous avons envoyé self qui est un pointeur vers l'instance, et ci.m2 car l'instance sait que la classe est liée;).

Pour conclure, vous pouvez lier la méthode à une classe ou à un objet de classe, en fonction du premier paramètre utilisé par la méthode. Si la méthode n'est pas liée, elle peut être appelée non liée.


Notez que la méthode peut ne pas faire partie à l'origine de la classe. Vérifiez cette réponse d'Alex Martelli pour plus de détails.

1
prosti