À quoi sert la fonction id ()?

99

J'ai lu la documentation Python 2 et j'ai remarqué la id()fonction:

Renvoie «l'identité» d'un objet. Il s'agit d'un entier (ou entier long) qui est garanti unique et constant pour cet objet pendant sa durée de vie. Deux objets dont les durées de vie ne se chevauchent pas peuvent avoir la même valeur id ().

Détail de l'implémentation CPython: il s'agit de l'adresse de l'objet en mémoire.

Donc, j'ai expérimenté en utilisant id()avec une liste:

>>> list = [1,2,3]
>>> id(list[0])
31186196
>>> id(list[1])
31907092 // increased by 896
>>> id(list[2])
31907080 // decreased by 12

Quel est l'entier renvoyé par la fonction? Est-ce aussi des adresses mémoire en C? Si tel est le cas, pourquoi l'entier ne correspond-il pas à la taille du type de données?

Quand est-il id()utilisé dans la pratique?

Thanakron Tandavas
la source
4
Ce n'est pas parce que vous stockez (disons) un int 32 bits dans une structure de données dans un langage de script que vous utiliserez 32 bits de mémoire en plus. il y a TOUJOURS des métadonnées attachées à TOUTES les données que vous stockez. type, taille, longueur, bla bla bla.
Marc B
cpython alloue à partir d'un tas qui est brouillé au fur et à mesure que les objets sont malléables et libres.
tdelaney
Les nombres Python ne sont pas de simples éléments de données. Ce sont des objets qui utilisent des longs en interne pour commencer, puis qui sont automatiquement promus en une représentation de style BigNumber si la valeur devient trop grande.
Gareth Latty

Réponses:

156

Votre message pose plusieurs questions:

Quel est le nombre renvoyé par la fonction?

C'est " un entier (ou entier long) qui est garanti pour être unique et constant pour cet objet pendant sa durée de vie. " (Python Standard Library - Built-in Functions) Un numéro unique. Rien de plus et rien de moins. Considérez-le comme un numéro de sécurité sociale ou un numéro d'identification d'employé pour les objets Python.

Est-ce la même chose avec les adresses mémoire en C?

Conceptuellement, oui, en ce sens qu'ils sont tous les deux garantis d'être uniques dans leur univers au cours de leur vie. Et dans une implémentation particulière de Python, il s'agit en fait de l'adresse mémoire de l'objet C correspondant.

Si oui, pourquoi le nombre n'augmente-t-il pas instantanément de la taille du type de données (je suppose que ce serait int)?

Parce qu'une liste n'est pas un tableau et qu'un élément de liste est une référence, pas un objet.

Quand utilisons-nous vraiment la id( )fonction?

Presque jamais. id()(ou son équivalent) est utilisé dans l' isopérateur.

Robᵩ
la source
Nous utilisons généralement id () à des fins de démonstration comme ici stackoverflow.com/questions/17246693/…
Nabin
49

C'est l'identité de l' emplacement de l'objet en mémoire ...

Cet exemple peut vous aider à comprendre un peu plus le concept.

foo = 1
bar = foo
baz = bar
fii = 1

print id(foo)
print id(bar)
print id(baz)
print id(fii)

> 1532352
> 1532352
> 1532352
> 1532352

Ceux-ci pointent tous vers le même emplacement dans la mémoire, c'est pourquoi leurs valeurs sont les mêmes. Dans l'exemple, 1n'est stocké qu'une seule fois, et tout ce qui pointe vers 1fera référence à cet emplacement mémoire.

brbcoding
la source
9
Mais si vous utilisez des nombres au-delà de la plage de -5 à 256, vous n'obtiendrez pas le même identifiant pour la variable fii.
saurav
c'est extrêmement intéressant. Pouvez-vous partager davantage?
jouell
3
Je pense que cette réponse est trompeuse car ce n'est pas vrai pour la plupart des chiffres; voir l' opérateur «est» se comporte de manière inattendue avec des entiers .
kevinji
8

id()renvoie l'adresse de l'objet référencé (en CPython), mais votre confusion vient du fait que les listes python sont très différentes des tableaux C. Dans une liste python, chaque élément est une référence . Donc, ce que vous faites est beaucoup plus similaire à ce code C:

int *arr[3];
arr[0] = malloc(sizeof(int));
*arr[0] = 1;
arr[1] = malloc(sizeof(int));
*arr[1] = 2;
arr[2] = malloc(sizeof(int));
*arr[2] = 3;
printf("%p %p %p", arr[0], arr[1], arr[2]);

En d'autres termes, vous imprimez l'adresse à partir de la référence et non une adresse relative à l'endroit où votre liste est stockée.

Dans mon cas, j'ai trouvé la id()fonction pratique pour créer des poignées opaques pour revenir au code C lors d'un appel à pythonpartir de C.Pour cela, vous pouvez facilement utiliser un dictionnaire pour rechercher l'objet à partir de sa poignée et il est garanti d'être unique.

Erreur fatale
la source
7

La réponse de Rob (la plus votée ci-dessus) est correcte. Je voudrais ajouter que dans certaines situations, l'utilisation d'identifiants est utile car elle permet de comparer des objets et de trouver quels objets font référence à vos objets.

Ce dernier vous aide généralement, par exemple, à déboguer des bogues étranges où des objets mutables sont passés en paramètre pour dire des classes et sont assignés à des variables locales dans une classe. La mutation de ces objets fera muter les variables vars dans une classe. Cela se manifeste par un comportement étrange où plusieurs choses changent en même temps.

Récemment, j'ai eu ce problème avec une application Python / Tkinter où l'édition de texte dans un champ de saisie de texte changeait le texte dans un autre au fur et à mesure que je tapais :)

Voici un exemple sur la façon dont vous pouvez utiliser la fonction id () pour tracer où se trouvent ces références. Ce n'est certainement pas une solution couvrant tous les cas possibles, mais vous voyez l'idée. Encore une fois, les ID sont utilisés en arrière-plan et l'utilisateur ne les voit pas:

class democlass:
    classvar = 24

    def __init__(self, var):
        self.instancevar1 = var
        self.instancevar2 = 42

    def whoreferencesmylocalvars(self, fromwhere):
        return {__l__: {__g__
                    for __g__ in fromwhere
                        if not callable(__g__) and id(eval(__g__)) == id(getattr(self,__l__))
                    }
                for __l__ in dir(self)
                    if not callable(getattr(self, __l__)) and __l__[-1] != '_'
                }

    def whoreferencesthisclassinstance(self, fromwhere):
        return {__g__
                    for __g__ in fromwhere
                        if not callable(__g__) and id(eval(__g__)) == id(self)
                }

a = [1,2,3,4]
b = a
c = b
democlassinstance = democlass(a)
d = democlassinstance
e = d
f = democlassinstance.classvar
g = democlassinstance.instancevar2

print( 'My class instance is of', type(democlassinstance), 'type.')
print( 'My instance vars are referenced by:', democlassinstance.whoreferencesmylocalvars(globals()) )
print( 'My class instance is referenced by:', democlassinstance.whoreferencesthisclassinstance(globals()) )

PRODUCTION:

My class instance is of <class '__main__.democlass'> type.
My instance vars are referenced by: {'instancevar2': {'g'}, 'classvar': {'f'}, 'instancevar1': {'a', 'c', 'b'}}
My class instance is referenced by: {'e', 'd', 'democlassinstance'}

Les traits de soulignement dans les noms de variables sont utilisés pour éviter les colisions de noms. Les fonctions utilisent l'argument "fromwhere" pour que vous puissiez leur indiquer par où commencer la recherche de références. Cet argument est rempli par une fonction qui répertorie tous les noms dans un espace de noms donné. Globals () est l'une de ces fonctions.

Arijan
la source
Excellente explication!
Neeraj Verma
5

Je commence avec python et j'utilise id lorsque j'utilise le shell interactif pour voir si mes variables sont affectées à la même chose ou si elles se ressemblent simplement.

Chaque valeur est un identifiant, qui est un numéro unique lié à l'endroit où elle est stockée dans la mémoire de l'ordinateur.

lontgomjr
la source
5

Si vous utilisez python 3.4.1, vous obtenez une réponse différente à votre question.

list = [1,2,3]
id(list[0])
id(list[1])
id(list[2])

Retour:

1705950792   
1705950808  # increased by 16   
1705950824  # increased by 16

Les entiers -5pour 256avoir un identifiant constant, et en le trouvant plusieurs fois, son identifiant ne change pas, contrairement à tous les autres nombres avant ou après qui ont des identifiants différents à chaque fois que vous le trouvez. Les nombres de -5à 256ont des identifiants dans l'ordre croissant et diffèrent par 16.

Le nombre retourné par la id()fonction est un identifiant unique donné à chaque élément stocké en mémoire et il est par analogie le même que l'emplacement mémoire en C.

Dhvanan Shah
la source
2

La réponse est pratiquement jamais. Les identifiants sont principalement utilisés en interne avec Python.

Le programmeur Python moyen n'aura probablement jamais besoin d'utiliser id()dans son code.

Gareth Latty
la source
3
Peut-être que je ne suis pas moyen, mais j'utilise id()pas mal. Deux cas d'utilisation qui me viennent à l'esprit: un dict d'identité (roulé à la main, ad hoc) et une coutume repr()pour un objet où l'identité compte mais la valeur par défaut reprn'est pas appropriée.
5
@delnan Je ne dirais pas que ce sont des cas courants.
Gareth Latty
2

L' isopérateur l'utilise pour vérifier si deux objets sont identiques (par opposition à égaux). La valeur réelle renvoyée id()n'est quasiment jamais utilisée pour quoi que ce soit car elle n'a pas vraiment de sens et dépend de la plate-forme.

omz
la source
2

C'est l'adresse de l'objet en mémoire, exactement comme le dit le doc. Cependant, des métadonnées y sont attachées, les propriétés de l'objet et l'emplacement dans la mémoire sont nécessaires pour stocker les métadonnées. Ainsi, lorsque vous créez votre variable appelée liste, vous créez également des métadonnées pour la liste et ses éléments.

Donc, à moins que vous ne soyez un gourou absolu dans la langue, vous ne pouvez pas déterminer l'identifiant de l'élément suivant de votre liste en fonction de l'élément précédent, car vous ne savez pas ce que la langue alloue avec les éléments.

Lajos Arpad
la source
1
En fait, être un gourou Python ne sera pas d'une grande utilité pour prédire n'importe quel objet id(). Vous devez être intimement familier avec les gestionnaires de mémoire (pluriel!) En question, connaître leur état exact à un moment donné et connaître l'ordre exact dans lequel les objets ont été alloués. En d'autres termes: ça n'arrivera pas.
Vous êtes un gourou absolu de Python si vous connaissez absolument toutes ses nuances, y compris les éventuels gestionnaires de mémoire.
Lajos Arpad
1
Connaître Python et connaître une implémentation particulière (par exemple CPython) sont deux choses totalement différentes. Et même connaître CPython à fond n'aidera pas, car il existe plusieurs gestionnaires de mémoire auxquels CPython fait appel qui ne font pas partie de CPython mais font partie du système d'exploitation respectif. Et comme je l'ai déjà dit, même connaître toutes ces choses non Python ne vous aidera pas, car les adresses réelles dépendent de l'état d'exécution des gestionnaires de mémoire et sont parfois même aléatoires.
1
Sans parler, il serait inutile de le savoir, car s'appuyer sur une telle chose reviendrait à s'appuyer sur un détail de mise en œuvre et serait donc fragile et inflexible.
Gareth Latty
2

J'ai une idée à utiliser id()dans la journalisation.
C'est bon marché et assez court.
Dans mon cas, j'utilise tornado et id()j'aimerais avoir une ancre pour regrouper les messages dispersés et mixés sur fichier par socket Web.

Daneel S. Yaitskov
la source
0

À partir de python 3, l'id est affecté à une valeur et non à une variable. Cela signifie que si vous créez deux fonctions comme ci-dessous, les trois identifiants sont identiques.

>>> def xyz():
...     q=123
...     print(id(q))
...
>>> def iop():
...     w=123
...     print(id(w))
>>> xyz()
1650376736
>>> iop()
1650376736
>>> id(123)
1650376736
Asish Kumar
la source
Même si cela contredit les explications précédentes, cela semble se produire et je ne sais pas pourquoi. Une explication? Par exemple x [0] = "abc" x [1] = "def" q = ["abc", "def"]. Id (x) == Id (q) est vrai. Coïncidence?
VMMF
0

Je suis un peu en retard et je vais parler de Python3. Pour comprendre ce qu'est id () et comment il (et Python) fonctionne, considérons l'exemple suivant:

>>> x=1000
>>> y=1000
>>> id(x)==id(y)
False
>>> id(x)
4312240944
>>> id(y)
4312240912
>>> id(1000)
4312241104
>>> x=1000
>>> id(x)
4312241104
>>> y=1000
>>> id(y)
4312241200

Vous devez penser à tout sur le côté droit comme des objets. Chaque fois que vous effectuez une affectation, vous créez un nouvel objet et cela signifie un nouvel identifiant. Au milieu, vous pouvez voir un objet "sauvage" qui est créé uniquement pour la fonction - id (1000). Donc, sa durée de vie n'est que pour cette ligne de code. Si vous cochez la ligne suivante - vous voyez que lorsque nous créons une nouvelle variable x, elle a le même identifiant que cet objet sauvage. À peu près cela fonctionne comme une adresse mémoire.

Tomas
la source
0

Attention (concernant la réponse juste en dessous) ... Ce n'est vrai que parce que 123 est entre -5 et 256 ...

In [111]: q = 257                                                         

In [112]: id(q)                                                            
Out[112]: 140020248465168

In [113]: w = 257                                                         

In [114]: id(w)                                                           
Out[114]: 140020274622544

In [115]: id(257)                                                         
Out[115]: 140020274622768
gee
la source