IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

Question théorico-pratique sur les méthodes d’instances


Sujet :

Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    Profil pro
    Inscrit en
    Janvier 2007
    Messages
    1 418
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2007
    Messages : 1 418
    Par défaut Question théorico-pratique sur les méthodes d’instances
    Bonjour,


    Je cherche à éclaircir un point concernant les objets méthode dans le cadre des classes et instances de classes.

    Voici des extraits de la doc

    class MyClass:
    ...."""A simple example class"""
    ....i = 12345
    ....def f(self):
    ........return 'hello world'

    x = MyClass() creates a new instance of the class and assigns this object to the local variable x.

    A method is a function that “belongs to” an object.

    (....)

    Valid method names of an instance object depend on its class.
    By definition, all attributes of a class that are function objects define corresponding methods of its instances.
    So in our example, x.f is a valid method reference, since MyClass.f is a function, but x.i is not, since MyClass.i is not.
    But x.f is not the same thing as MyClass.f - it is a method object, not a function object.

    Usually, a method is called right after it is bound: x.f()
    In the MyClass example, this will return the string 'hello world'.

    However, it is not necessary to call a method right away: x.f is a method object, and can be stored away and called at a later time.
    For example:
    xf = x.f
    while True:
    ....print xf()
    will continue to print hello world until the end of time.



    What exactly happens when a method is called?

    You may have noticed that x.f() was called without an argument above, even though the function definition for f() specified an argument. What happened to the argument?
    (....)
    the special thing about methods is that the object is passed as the first argument of the function.
    In our example, the call x.f() is exactly equivalent to MyClass.f(x).

    In general, calling a method with a list of n arguments is equivalent to calling the corresponding function with an argument list that is created by inserting the method’s object before the first argument.

    If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters.
    When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object.
    When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.



    Ce qui m’intéresse là dedans c’est la notion d’ ’objet méthode’.




    * La doc décrit ce qui se passe quand on appelle une méthode d’une instance pour une exécution.
    Si on fait par exempleprint x.f() , le programme fait en réalité
    print MyClass.f(x)
    Je qualifierai ce genre d’appel de complet, c’est à dire provoquant une exécution.

    * Mais la doc explique bien qu’on n’est pas obligé d’appeler complétement une méthode:
    it is not necessary to call a method right away: x.f is a method object
    Dans ce cas, il n’y a pas d’exécution immédiate. Je qualifierai ce genre d’appel d’incomplet.



    Je comprends de ceci que dans les faits un appel de fonction commence toujours par la création d’un ’objet méthode’, même quand on fait un appel de méthode pour exécution immédiate.

    En termes d’implémentation, je pense que cela doit vouloir dire qu’il y a un constructeur d’ ’objet méthode’ qui est mis en branle. À ce niveau, un objet méthode est un packing de pointeurs vers l’instance et vers la fonction de sa classe.

    Ensuite tout dépend de la présence ou non de parenthèses (seules ou avec des paramètres à l’intérieur).
    • S’il n’y a pas de parenthèses, les choses en restent là, on peut décrire ça comme un appel partiel. On n’a fait que créer un objet avec un nom dessus comme référence directe.
    • S’il y a des parenthèses, il y a bien création d’un ’objet méthode’ en coulisses, qui est immédiatement sollicité pour fournir son résultat dans la foulée. Mais dans ce cas, on n’y fait pas attention puisque ce qui importe c’est l’exécution
    .




    J’ai donc des questions sur la durée de vie de cet ’objet méthode’ qui concrétise aussi bien les appels de méthode complets que les appels partiels.

    - quand l’objet méthode d’une méthode est-il créé ?
    Dans le passage où est décrite l’implémentation des appels de méthode, la création d’un objet méthode est décrite pour « When an instance attribute is referenced that isn’t a data attribute »

    il me semble évident qu’il faut comprendre référence à un attribut méthode comme étant aussi bien le x.f auquel se limite un appel partiel que le même x.f qui constitue la première partie d’un appel complet x.f() .
    De plus une telle référence est immédiatement suivie de «its class is searched», ce qui signifie que l’instance ne dispose pas de ce qu’il faut.

    Ce que je comprends donc de ceci, c’est que l’objet méthode d’une méthode n’est pas créé au moment de l’instanciation, mais seulement donc après, quand un appel partiel ou complet a lieu sur cette méthode par une référence explicite.
    Il n’y a pas lieu de penser qu’une instanciation constitue une référence implicite aux fonctions de la classe qui déclencherait la création d’un objet méthode pour chacune de ces fonctions.
    Ainsi je ne crois pas que des objets méthodes appartiennent aux instances, c’est à dire qu’ils seraient inclus dans l’objet instance lui-même, comme la phrase « A method is a function that “belongs to” an object.
    » tend à en suggérer l’idée. Je pense au contraire que ces objets méthodes, quand ils sont créés, le sont à l’extérieur de toute instance.




    - combien de temps un objet méthode vit-il, c’est à dire existe-t-il en mémoire vive ?

    Je pense pour ma part que lorsqu’un appel partiel du type xf = x.f est fait, un objet méthode est créé et perdure tant que l’espace de nom dans lequel il a été créé ne disparait pas.
    L’objet méthode reste “vif“ en mémoire vive.

    Par contre, pour un appel complet, une fois l’exécution terminée, aucun nom d’étiquette n’ayant été associé à l’objet méthode, il connait à mon avis le destin des objets qui ne sont plus référencés dans un espace de nom: il flotte dans la mémoire vive sans pouvoir être resollicité et la mémoire qui lui a été allouée lors de l’appel sera libérée dès que le garbage collector fera un tour de nettoyage.
    Dans ce cas, il s’agit d’un objet méthode créé à la volée et s'évanouissant dès que son action est terminée.



    Motif de ce questionnement:

    J’envisage créer dans un programme une liste d’instances de classe [instance 1, instance 2....instance 350....].

    Je me demande quelle consommation de mémoire va faire cette liste d’instances.

    Cette consommation est suceptible d’être notablement plus importante dans le cas où chaque instance dans la liste comporterait dans son enveloppe des objets méthodes avant même que les méthodes aient été appelées, et ce d’autant plus si les objets méthodes étaient créés au moment de l’instanciation de chaque méthode. Car il est clair qu’il n’y a pas d’objet méthode commun à toutes les instances d’une même classe et qu'il y aurait donc autant d'objets méthodes que d'instances (plus même) dès la création des instances.

    Je voudrais savoir à quoi m’en tenir. Je pense personnellement que les objets méthodes sont créés à la volée, et en dehors d’une classe d’ailleurs, quand il y a appel de la méthode, mais j’aimerais une confirmation sur ce point fondamental.

    Qu’en pensez vous ?


    PS

    Pour rendre la question un peu plus concrète:

    si on a à définir et traiter 350 instances et que chaque instance est alourdie de tous les objets méthodes correspondant aux fonctions de sa classe,
    on peut avoir intérêt, pour économiser la mémoire, de traiter tout de suite après sa définition toute instance,
    au lieu de créer d’abord la collecion des 350 instances et de les traiter ensuite.

  2. #2
    Membre émérite
    Avatar de Antoine_935
    Profil pro
    Développeur web/mobile
    Inscrit en
    Juillet 2006
    Messages
    883
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur web/mobile

    Informations forums :
    Inscription : Juillet 2006
    Messages : 883
    Par défaut
    En voilà une question intéressante !

    Pour connaître la réalité, j'ai un peu joué avec le désassemblage et les identités. Voici, en vrac, ce qui en ressort:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    >>> from dis import dis
    >>> class MyClass:
    ...     def f(self):
    ...         print("Hello World !")
    ...
    >>> def test1():
    ...     x = MyClass()
    ...     x.f()
    ...
    >>> def test2():
    ...     x = MyClass()
    ...     xf = x.f
    ...     xf()
    ...
    >>> test1()
    Hello World !
    >>> test2()
    Hello World !
    >>>
    >>>
    >>>
    >>> dis(test1)
      2           0 LOAD_GLOBAL              0 (MyClass)
                  3 CALL_FUNCTION            0
                  6 STORE_FAST               0 (x)
     
      3           9 LOAD_FAST                0 (x)
                 12 LOAD_ATTR                1 (f)
                 15 CALL_FUNCTION            0
                 18 POP_TOP
                 19 LOAD_CONST               0 (None)
                 22 RETURN_VALUE
    >>> dis(test2)
      2           0 LOAD_GLOBAL              0 (MyClass)
                  3 CALL_FUNCTION            0
                  6 STORE_FAST               0 (x)
     
      3           9 LOAD_FAST                0 (x)
                 12 LOAD_ATTR                1 (f)
                 15 STORE_FAST               1 (xf)
     
      4          18 LOAD_FAST                1 (xf)
                 21 CALL_FUNCTION            0
                 24 POP_TOP
                 25 LOAD_CONST               0 (None)
                 28 RETURN_VALUE
    >>> x = MyClass()
    >>> x.f
    <bound method MyClass.f of <__main__.MyClass instance at 0x0238D620>>
    >>> MyClass.f
    <unbound method MyClass.f>
    >>> x.f is MyClass.f
    False
    >>> x.f is x.f
    False
    >>> id(x.f)
    37258224
    >>> id(x.f)
    37258224
    >>> x.f is x.f
    False
    >>> # M'enfin...
    ...
    >>> id(x.f)
    37258144
    >>> h = "hello"
    >>> id(x.f)
    37258144
    >>> id(x.f)
    37258144
    >>> id(x.f)
    37258144
    >>> id(x.f)
    37258144
    >>> import gc
    >>> gc.collect()
    0
    >>> del h
    >>> gc.collect()
    0
    >>> id(x.f)
    37262840
    Le test x.f is x.f retourne False, et montre donc bien que ce ne sont pas les mêmes objets. Et comme le montre le désassemblage, le bytecode est exactement le même pour l'appel comme pour l'assignation, j'aurais tendance à penser que Python crée bien un objet bound method pour chaque appel.
    Hélas il nous manque une information pour être certain de cela... Python prend peut-être un raccourci quand il ne doit pas stocker la méthode.

    J'ai bien essayé de fouiller dans les frames, mais je n'ai rien trouvé qui puisse confirmer quoi que ce soit.
    Je pense que le seul moyen d'en être certain est de mettre les mains dans le code source de Python.

    Cela dit, il y a de très fortes chances qu'il crée une bound method par appel, vu que ça simplifierait l'implémentation.

    En réponse à ta question, tu as de toute façon intérêt à traiter les instances une par une si tu veux surcharger le moins possible la mémoire. Mais bon, 350 objets, ce n'est pas non plus un grand danger


    Belle question

  3. #3
    Membre expérimenté
    Profil pro
    Inscrit en
    Mai 2008
    Messages
    141
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mai 2008
    Messages : 141
    Par défaut
    Bonjour,

    Effectivement, les méthodes définies dans une classe n'appartiennent pas aux instances de cette classe.
    Quand on appelle une méthode sur une instance, Python va parcourir la classe correspondante, ainsi que toutes les classes parentes (voir le bien nommé __mro__ ("method resolution order") pour savoir dans quel ordre).
    Pour s'en convaincre, un petit test (fait sous Python 2.6.2) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    In [1]: class Spam(object):
       ...:     def __init__(self):
       ...:         self.food = 'spam'
       ...:     def spam(self):
       ...:         print self.food * 4
       ...:
       ...:
    
    In [2]: s = Spam()
    
    <snip!>
    
    In [7]: s.__dict__
    Out[7]: {'food': 'spam'}
    
    In [8]: for k, v in Spam.__dict__.items():
       ...:     print k, ':', v
       ...:
       ...:
    __module__ : __main__
    spam : <function spam at 0x00C25CF0>
    __dict__ : <attribute '__dict__' of 'Spam' objects>
    __weakref__ : <attribute '__weakref__' of 'Spam' objects>
    __doc__ : None
    __init__ : <function __init__ at 0x00F16AB0>
    A mon sens, la "liste" fournit par object.__dict__ indique quels sont les attributs réellement lié à l'objet considéré.

    On voit donc que l'instance s de Spam ne possède pas de méthode.
    M'est avis que tu peux créer tes 350 instances tranquille.

    Si on va + loin, on peut voir qu'une méthode liée ("bound method") contient une référence à l'instance :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    In [30]: boundmethod = s.spam
     
    In [31]: boundmethod.__self__
    Out[31]: <__main__.Spam object at 0x0104CB30>
     
    In [32]: hex(id(s))
    Out[32]: '0x104cb30'
     
    In [33]: boundmethod.__self__ is s
    Out[33]: True
    Maintenant, est-ce que ça prend de l'espace mémoire d'avoir plusieurs méthodes liées ? Je dirais non (enfin, une quantité négligeable), à la vue du test suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     
    In [72]: s2 = Spam()
     
    In [73]: b2 = s2.spam
     
    In [74]: b2.__func__
    Out[74]: <function spam at 0x00C25CF0>
     
    In [75]: boundmethod.__func__
    Out[75]: <function spam at 0x00C25CF0>
     
    In [76]: Spam.spam.__func__
    Out[76]: <function spam at 0x00C25CF0>
     
    In [77]: b2.__func__ is boundmethod.__func__
    Out[77]: True
     
    In [78]: b2.__func__ is Spam.spam.__func__
    Out[78]: True
    On peut même voir que l'adresse mémoire des fonctions spam des méthodes liées est exactement la même que celle de la méthode dans la classe Spam.

    Donc tu peux même faire une liste d'objets méthode, ça ne prendra pas beaucoup + de place mémoire qu'une liste d'instances.

    Un petit dernier pour la route :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    In [79]: for x in (s, s2, boundmethod, b2, Spam.spam, Spam.spam.__func__):
       ....:     print x, ':', x.__sizeof__()
       ....:
       ....:
    <__main__.Spam object at 0x0104CB30> : 16
    <__main__.Spam object at 0x010B1570> : 16
    <bound method Spam.spam of <__main__.Spam object at 0x0104CB30>> : 24
    <bound method Spam.spam of <__main__.Spam object at 0x010B1570>> : 24
    <unbound method Spam.spam> : 24
    <function spam at 0x00C25CF0> : 44
    Sinon, je confirme, à la lecture de "Learning Python", 4° éd, de Mark Lutz, que ce que tu nommes l'appel complet passe d'abord par la création d'un objet méthode avant "d'attaquer les parenthèses" (chapitre 30, section "Methods Are Objects: Bound or Unbound").

    Concernant la fin de vie des objets méthodes, ton raisonnement me parait cohérent (je veux dire que j'ai le même ), mais Python doit utiliser un cache mémoire pour les objets, même s'il me semble qu'il n'utilise ce cache que pour les objets immutables (strings de petite taille (hou !), nombres, tuples(?)). A confirmer.

  4. #4
    Membre expérimenté
    Profil pro
    Inscrit en
    Août 2007
    Messages
    190
    Détails du profil
    Informations personnelles :
    Localisation : France, Maine et Loire (Pays de la Loire)

    Informations forums :
    Inscription : Août 2007
    Messages : 190
    Par défaut
    Salut,

    Si tu veux avoir une vision claire de ce qui se passe lorsque l'on accède à un attribut (d'une classe ou d'une instance) de type "function object", je te conseille de lire la documentation Language Reference dans laquelle peut trouve ceci (3.2 User-defined Methods):
    User-defined methods

    A user-defined method object combines a class, a class instance (or None) and any callable object (normally a user-defined function).

    Special read-only attributes: im_self is the class instance object, im_func is the function object; im_class is the class of im_self for bound methods or the class that asked for the method for unbound methods; __doc__ is the method’s documentation (same as im_func.__doc__); __name__ is the method name (same as im_func.__name__); __module__ is the name of the module the method was defined in, or None if unavailable.

    Changed in version 2.2: im_self used to refer to the class that defined the method.

    Changed in version 2.6: For 3.0 forward-compatibility, im_func is also available as __func__, and im_self as __self__.

    Methods also support accessing (but not setting) the arbitrary function attributes on the underlying function object.

    User-defined method objects may be created when getting an attribute of a class (perhaps via an instance of that class), if that attribute is a user-defined function object, an unbound user-defined method object, or a class method object. When the attribute is a user-defined method object, a new method object is only created if the class from which it is being retrieved is the same as, or a derived class of, the class stored in the original method object; otherwise, the original method object is used as it is.

    When a user-defined method object is created by retrieving a user-defined function object from a class, its im_self attribute is None and the method object is said to be unbound. When one is created by retrieving a user-defined function object from a class via one of its instances, its im_self attribute is the instance, and the method object is said to be bound. In either case, the new method’s im_class attribute is the class from which the retrieval takes place, and its im_func attribute is the original function object.

    When a user-defined method object is created by retrieving another method object from a class or instance, the behaviour is the same as for a function object, except that the im_func attribute of the new instance is not the original method object but its im_func attribute.

    When a user-defined method object is created by retrieving a class method object from a class or instance, its im_self attribute is the class itself (the same as the im_class attribute), and its im_func attribute is the function object underlying the class method.

    When an unbound user-defined method object is called, the underlying function (im_func) is called, with the restriction that the first argument must be an instance of the proper class (im_class) or of a derived class thereof.

    When a bound user-defined method object is called, the underlying function (im_func) is called, inserting the class instance (im_self) in front of the argument list. For instance, when C is a class which contains a definition for a function f(), and x is an instance of C, calling x.f(1) is equivalent to calling C.f(x, 1).

    When a user-defined method object is derived from a class method object, the “class instance” stored in im_self will actually be the class itself, so that calling either x.f(1) or C.f(1) is equivalent to calling f(C,1) where f is the underlying function.

    Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance. In some cases, a fruitful optimization is to assign the attribute to a local variable and call that local variable. Also notice that this transformation only happens for user-defined functions; other callable objects (and all non-callable objects) are retrieved without transformation. It is also important to note that user-defined functions which are attributes of a class instance are not converted to bound methods; this only happens when the function is an attribute of the class.

Discussions similaires

  1. Réponses: 3
    Dernier message: 17/09/2008, 13h52
  2. Question Pratique sur les mises a jour
    Par geof dans le forum MS SQL Server
    Réponses: 7
    Dernier message: 26/11/2007, 17h09
  3. question pratique sur les fonctions 'inutiles'
    Par Plomeg dans le forum C++
    Réponses: 13
    Dernier message: 20/11/2007, 19h58
  4. Question sur les méthodes abstraites
    Par nmathon dans le forum Delphi
    Réponses: 3
    Dernier message: 15/06/2006, 20h30
  5. Réponses: 5
    Dernier message: 24/04/2005, 04h09

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo