Accès à l'adresse de la mémoire d'objets

168

Lorsque vous appelez la object.__repr__()méthode en Python, vous obtenez quelque chose comme ceci:

<__main__.Test object at 0x2aba1c0cf890> 

Existe-t-il un moyen de récupérer l'adresse mémoire en cas de surcharge __repr__(), à part l'appel super(Class, obj).__repr__()et la regexing?

thr
la source

Réponses:

208

Le manuel Python a ceci à dire à propos de id():

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 la durée de vie ne se chevauche pas peuvent avoir la même valeur id (). (Note d'implémentation: c'est l'adresse de l'objet.)

Donc en CPython, ce sera l'adresse de l'objet. Aucune garantie de ce type pour aucun autre interpréteur Python, cependant.

Notez que si vous écrivez une extension C, vous avez un accès complet aux internes de l'interpréteur Python, y compris l'accès aux adresses des objets directement.

Nick Johnson
la source
7
Ce n'est pas une réponse universelle à la question; cela ne s'applique qu'à CPython.
DilithiumMatrix
5
Note à moi-même: la garantie ne s'applique pas au multitraitement
Rufus
1
Quelques façons de l'utiliser (pour comparer la valeur qu'il contient): forum.freecodecamp.com/t/python-id-object/19207
J. fait
À quoi fait référence un objet lifetime(et que signifie la durée de vie overlap/not overlap) dans ce contexte?
Minh Tran
4
@MinhTran car l'id est l'adresse mémoire de l'objet, il est garanti unique dans le processus, et tant que l'objet existe. Quelque temps après la récupération de la mémoire de l'objet, la mémoire peut être réutilisée. Une durée de vie sans chevauchement signifierait que l'objet d'origine n'existe plus lorsque le nouvel objet est créé. Donc, cette limitation signifie que vous ne pouvez pas utiliser en toute sécurité id () pour créer un hachage d'un objet à stocker, le libérer et le réactiver plus tard.
Joshua Clayton
71

Vous pouvez réimplémenter la repr par défaut de cette façon:

def __repr__(self):
    return '<%s.%s object at %s>' % (
        self.__class__.__module__,
        self.__class__.__name__,
        hex(id(self))
    )
Armin Ronacher
la source
1
Je sais que c'est vieux, mais vous pouvez simplement le faire return object.__repr__(self)ou même le faire object.__repr__(obj)chaque fois que vous en avez besoin au lieu de créer une nouvelle classe
Artyer
2
@Artyer: Qu'est-ce que ce commentaire a à voir avec la question d'origine? La réponse affichée ici recrée l'adresse comme demandé par la question d'origine. N'auriez-vous pas à ficeler mangle si vous le faisiez comme vous le suggérez?
Rafe
1
Cela me semble être la meilleure réponse. Essayez simplement de créer un objet (), imprimez-le, puis imprimez hex (id (object)) et les résultats correspondent
Rafe
@Rafe Votre réponse est une longue façon de faire __repr__ = object.__repr__, et n'est pas aussi infaillible, car il existe une variété de situations où cela ne fonctionne pas, par exemple une __getattribute__implémentation remplacée ou non CPython où l'id n'est pas l'emplacement de la mémoire. Il ne se remplit pas non plus en z, vous devrez donc déterminer si le système est 64 bits et ajouter les zéros si nécessaire.
Artyer
@Artyer: Mon exemple montre comment construire une repr. Nous ajoutons souvent des informations personnalisées (et je dirais que c'est une bonne pratique de codage car elle facilite le débogage). Nous utilisons beaucoup ce style et je n'ai jamais couru dans vos cas extrêmes. Merci de les partager!
Rafe
52

Juste utiliser

id(object)
Ben Hoffstein
la source
6
ce qui donne un nombre. ... Et après? Puis-je accéder à l'objet avec ce numéro?
JLT
Vous pouvez vérifier ceci id()@JLT
Billal Begueradj
24

Il y a quelques problèmes ici qui ne sont couverts par aucune des autres réponses.

Tout d'abord, idne renvoie que:

«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 id()valeur.


En CPython, il s'agit du pointeur vers le PyObjectqui représente l'objet dans l'interpréteur, qui est la même chose qui object.__repr__s'affiche. Mais ce n'est qu'un détail d'implémentation de CPython, pas quelque chose qui est vrai de Python en général. Jython ne s'occupe pas des pointeurs, il traite des références Java (que la JVM représente bien sûr probablement comme des pointeurs, mais vous ne pouvez pas les voir - et vous ne voudriez pas, car le GC est autorisé à les déplacer). PyPy permet à différents types d'avoir différents types de id, mais le plus général est juste un index dans une table d'objets que vous avez appelésidsur, ce qui ne sera évidemment pas un indicateur. Je ne suis pas sûr d'IronPython, mais je suppose que cela ressemble plus à Jython qu'à CPython à cet égard. Donc, dans la plupart des implémentations Python, il n'y a aucun moyen d'obtenir ce qui s'y trouve repr, et cela ne sert à rien si vous l'avez fait.


Mais que faire si vous ne vous souciez que de CPython? C'est un cas assez courant, après tout.

Eh bien, tout d'abord, vous remarquerez peut-être que idc'est un entier; * si vous voulez cette 0x2aba1c0cf890chaîne au lieu du nombre 46978822895760, vous devrez la formater vous-même. Sous les couvertures, je pense object.__repr__qu'en fin de compte, il utilise printfle %pformat de, que vous n'avez pas de Python ... mais vous pouvez toujours le faire:

format(id(spam), '#010x' if sys.maxsize.bit_length() <= 32 else '#18x')

* Dans 3.x, c'est un int. Dans 2.x, c'est un intsi c'est assez grand pour contenir un pointeur - ce qui peut ne pas être dû à des problèmes de nombres signés sur certaines plates-formes - et un longautre.

Y a-t-il quelque chose que vous pouvez faire avec ces pointeurs en plus de les imprimer? Bien sûr (encore une fois, en supposant que vous ne vous souciez que de CPython).

Toutes les fonctions de l' API C prennent un pointeur vers un PyObjectou un type associé. Pour ces types associés, vous pouvez simplement appeler PyFoo_Checkpour vous assurer qu'il s'agit bien d'un Fooobjet, puis lancer avec (PyFoo *)p. Donc, si vous écrivez une extension C, idc'est exactement ce dont vous avez besoin.

Et si vous écrivez du code Python pur? Vous pouvez appeler exactement les mêmes fonctions avec pythonapide ctypes.


Enfin, quelques-unes des autres réponses ont été soulevées ctypes.addressof. Ce n'est pas pertinent ici. Cela ne fonctionne que pour des ctypesobjets comme c_int32(et peut-être quelques objets de type tampon mémoire, comme ceux fournis par numpy). Et, même là, cela ne vous donne pas l'adresse de la c_int32valeur, c'est l'adresse du niveau C int32qui se c_int32termine.

Cela étant dit, le plus souvent, si vous pensez vraiment avoir besoin de l'adresse de quelque chose, vous ne vouliez pas d'un objet Python natif en premier lieu, vous vouliez un ctypesobjet.

Abarnert
la source
Eh
@Enerccio Les autres utilisations de id- y compris leur utilisation pour contenir des valeurs mutables dans un seenensemble ou un cachedict - ne dépendent d'aucune façon du fait d' idêtre un pointeur, ou liées de quelque manière que ce soit au repr. C'est exactement pourquoi ce code fonctionne dans toutes les implémentations Python, au lieu de fonctionner uniquement en CPython.
abarnert
oui, je l'ai utilisé idpour cela, mais je veux dire que même en java, vous pouvez obtenir l'adresse de l'objet, cela semble étrange, il n'y a aucun moyen dans (C) Python car celui-ci a en fait un gc stable qui ne déplace pas les objets, donc l'adresse reste la même
Enerccio
@Enerccio Mais vous ne voulez pas utiliser l'adresse d'un objet pour une valeur pouvant être mise en cache - vous voulez utiliser le idfo un objet, que ce soit une adresse ou non. Par exemple, dans PyPy, idest toujours aussi utile qu'une clé en CPython, même s'il ne s'agit généralement que d'un index dans une table cachée de l'implémentation, mais un pointeur serait inutile, car (comme Java) l'objet peut être déplacé dans Mémoire.
abarnert
@Enerccio Quoi qu'il en soit, il existe un moyen d'obtenir un pointeur en CPython. Comme expliqué dans la réponse, CPython documente explicitement, en tant que détail spécifique à l'implémentation, que le idd'un objet est le pointeur vers l'emplacement de l'objet en mémoire. Donc, si vous avez une utilisation de la valeur du pointeur (ce que vous ne faites presque jamais, comme expliqué également dans la réponse) dans du code spécifique à CPython, il existe un moyen de l'obtenir qui est documenté et garanti de fonctionner.
abarnert
13

Juste en réponse à Torsten, je n'ai pas pu appeler addressof()un objet Python normal. De plus, id(a) != addressof(a). C'est en CPython, je ne sais rien d'autre.

>>> from ctypes import c_int, addressof
>>> a = 69
>>> addressof(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: invalid type
>>> b = c_int(69)
>>> addressof(b)
4300673472
>>> id(b)
4300673392
Peter Le Bek
la source
4

Avec ctypes , vous pouvez réaliser la même chose avec

>>> import ctypes
>>> a = (1,2,3)
>>> ctypes.addressof(a)
3077760748L

Documentation:

addressof(C instance) -> integer
Renvoie l'adresse du tampon interne de l'instance C

Notez que dans CPython, actuellement id(a) == ctypes.addressof(a), mais ctypes.addressofdevrait renvoyer l'adresse réelle pour chaque implémentation Python, si

  • ctypes est pris en charge
  • les pointeurs de mémoire sont une notion valide.

Edit : ajout d'informations sur l'indépendance de l'interpréteur des ctypes

Torsten Marek
la source
13
>>> import ctypes >>> a = (1,2,3) >>> ctypes.addressof (a) Traceback (dernier appel en dernier): Fichier "<input>", ligne 1, dans <module> TypeError: type invalide >>> id (a) 4493268872 >>>
5
Je suis d'accord avec Barry: le code ci-dessus se produit TypeError: invalid typelorsque je l'essaye avec Python 3.4.
Brandon Rhodes
2

Vous pouvez obtenir quelque chose qui convient à cet effet avec:

id(self)
Thomas Wouters
la source
1

Je sais que c'est une vieille question, mais si vous êtes encore en train de programmer, en python 3 ces jours-ci ... j'ai en fait trouvé que s'il s'agit d'une chaîne, il existe un moyen très simple de le faire:

>>> spam.upper
<built-in method upper of str object at 0x1042e4830>
>>> spam.upper()
'YO I NEED HELP!'
>>> id(spam)
4365109296

la conversion de chaîne n'affecte pas non plus l'emplacement en mémoire:

>>> spam = {437 : 'passphrase'}
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'
>>> str(spam)
"{437: 'passphrase'}"
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'
commanderbasher
la source
0

S'il est vrai qu'il id(object)obtient l'adresse de l'objet dans l'implémentation CPython par défaut, c'est généralement inutile ... vous ne pouvez rien faire avec l'adresse du code Python pur.

Le seul moment où vous pourriez réellement utiliser l'adresse est à partir d'une bibliothèque d'extensions C ... auquel cas il est trivial d'obtenir l'adresse de l'objet puisque les objets Python sont toujours transmis comme des pointeurs C.

Dan Lenski
la source
1
Sauf si vous utilisez la ctypesboîte à outils intégrée dans la bibliothèque standard. Dans ce cas, vous pouvez faire toutes sortes de choses avec l'adresse :)
Brandon Rhodes