Accesso all'indirizzo memoria oggetto


168

Quando chiami il object.__repr__()metodo in Python ottieni qualcosa del genere:

<__main__.Test object at 0x2aba1c0cf890> 

Esiste un modo per mettere in attesa l'indirizzo di memoria in caso di sovraccarico __repr__(), oltre a chiamarlo super(Class, obj).__repr__()e riaccenderlo?

Risposte:


208

Il manuale di Python ha questo da dire su id():

Restituisce l '"identità" di un oggetto. Si tratta di un numero intero (o intero lungo) che è garantito essere unico e costante per questo oggetto durante la sua vita. Due oggetti con durata non sovrapposta possono avere lo stesso valore id (). (Nota di implementazione: questo è l'indirizzo dell'oggetto.)

Quindi in CPython, questo sarà l'indirizzo dell'oggetto. Nessuna garanzia del genere per nessun altro interprete Python, comunque.

Nota che se stai scrivendo un'estensione C, hai pieno accesso agli interni dell'interprete Python, incluso l'accesso diretto agli indirizzi degli oggetti.


7
Questa non è una risposta universale alla domanda; si applica solo a CPython.
DilithiumMatrix,

5
Nota per sé: la garanzia non si applica al multiprocessing
Rufus

1
Alcuni modi per usarlo (per confrontare il valore che contiene): forum.freecodecamp.com/t/python-id-object/19207
J. Fa

Cosa si riferisce a un oggetto lifetime(e cosa significa per tutta la vita overlap/not overlap) in questo contesto?
Minh Tran,

4
@MinhTran perché l'id è l'indirizzo di memoria dell'oggetto, è garantito univoco all'interno del processo e mentre l'oggetto esiste. Qualche tempo dopo la raccolta dei rifiuti, la memoria potrebbe essere riutilizzata. Una durata non sovrapposta significherebbe che l'oggetto originale non esiste più quando viene creato il nuovo oggetto. Quindi questa limitazione significa che non puoi usare id () in modo sicuro per creare un hash di un oggetto da archiviare, liberarlo e successivamente ripristinarlo.
Joshua Clayton,

71

È possibile reimplementare il repr predefinito in questo modo:

def __repr__(self):
    return '<%s.%s object at %s>' % (
        self.__class__.__module__,
        self.__class__.__name__,
        hex(id(self))
    )

1
So che questo è vecchio, ma puoi semplicemente fare return object.__repr__(self)o anche solo object.__repr__(obj)quando ne hai bisogno, invece di fare una nuova lezione
Artyer,

2
@Artyer: cosa c'entra questo commento con la domanda originale? La risposta pubblicata qui sta ricreando l'indirizzo come richiesto dalla domanda originale. Non dovresti stringere il mangle se lo facessi come suggerisci?
Rafe,

1
Questa mi sembra la migliore risposta. Prova a creare un oggetto (), stampalo, quindi stampa hex (id (object)) e i risultati corrispondono
Rafe

@Rafe La tua risposta è un modo lungimirante di fare __repr__ = object.__repr__, e non è altrettanto infallibile, in quanto ci sono una varietà di situazioni in cui questo non funziona, ad esempio __getattribute__un'implementazione sovrascritta o non CPython dove l'id non è la posizione della memoria. Inoltre non riempie z, quindi dovresti capire se il sistema è a 64 bit e aggiungere gli zeri se necessario.
Artyer,

@Artyer: il mio esempio mostra come costruire una repr. Aggiungiamo spesso informazioni personalizzate (e direi che questa è una buona pratica di codifica in quanto aiuta nel debug). Usiamo questo stile pesantemente e non ho mai incontrato i tuoi casi limite. Grazie per averli condivisi!
Rafe,


24

Ci sono alcuni problemi qui che non sono coperti da nessuna delle altre risposte.

Innanzitutto, idrestituisce solo:

l '"identità" di un oggetto. Questo è un numero intero (o intero lungo) che è garantito essere unico e costante per questo oggetto durante la sua vita. Due oggetti con durata non sovrapposta possono avere lo stesso id()valore.


In CPython, questo sembra essere il puntatore a PyObjectche rappresenta l'oggetto nell'interprete, che è la stessa cosa che object.__repr__viene visualizzata. Ma questo è solo un dettaglio dell'implementazione di CPython, non qualcosa che è vero per Python in generale. Jython non si occupa di puntatori, si occupa di riferimenti Java (che la JVM ovviamente rappresenta come puntatori, ma non è possibile visualizzarli - e non vorrebbe, perché il GC è autorizzato a spostarli). PyPy consente a diversi tipi di avere diversi tipi di id, ma il più generale è solo un indice in una tabella di oggetti che hai chiamatoidon, che ovviamente non sarà un puntatore. Non sono sicuro di IronPython, ma sospetto che sia più simile a Jython che a CPython in questo senso. Quindi, nella maggior parte delle implementazioni di Python, non c'è modo di ottenere ciò che viene mostrato in questo repr, e inutile se lo facessi.


E se ti interessasse solo CPython? Questo è un caso abbastanza comune, dopo tutto.

Bene, per prima cosa potresti notare che idè un numero intero; * se vuoi quella 0x2aba1c0cf890stringa invece del numero 46978822895760, dovrai formattarla tu stesso. Sotto le copertine, credo che alla object.__repr__fine stia usando printfil %pformato, che non hai da Python ... ma puoi sempre farlo:

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

* In 3.x, è un int. In 2.x, è un intif abbastanza grande da contenere un puntatore, il che potrebbe non essere dovuto a problemi di numeri firmati su alcune piattaforme e un longaltro.

C'è qualcosa che puoi fare con questi puntatori oltre a stamparli? Sicuro (di nuovo, supponendo che ti interessi solo di CPython).

Tutte le funzioni dell'API C portano un puntatore a uno PyObjecto un tipo correlato. Per quei tipi correlati, puoi semplicemente chiamare PyFoo_Checkper assicurarti che sia davvero un Foooggetto, quindi eseguire il cast (PyFoo *)p. Quindi, se stai scrivendo un'estensione C, idè esattamente quello che ti serve.

E se stai scrivendo puro codice Python? Puoi chiamare le stesse identiche funzioni con pythonapida ctypes.


Infine, alcune delle altre risposte hanno sollevato ctypes.addressof. Questo non è rilevante qui. Funziona solo con ctypesoggetti come c_int32(e forse alcuni oggetti simili a buffer di memoria, come quelli forniti da numpy). E, anche lì, non ti sta dando l'indirizzo del c_int32valore, ti sta dando l'indirizzo del livello C int32che c_int32racchiude.

Detto questo, il più delle volte, se davvero pensi di aver bisogno dell'indirizzo di qualcosa, in primo luogo non volevi un ctypesoggetto nativo di Python, ma volevi un oggetto.


bene, questo è l'unico modo per conservare oggetti mutabili in mappe / set quando l'identità è importante ...
Enerccio

@Enerccio Gli altri usi di id—includere l'uso di questi per contenere valori mutabili in un seenset o in un cachedict — non dipendono in alcun modo iddall'essere un puntatore o in alcun modo correlato al repr. Questo è esattamente il motivo per cui tale codice funziona in tutte le implementazioni di Python, invece di funzionare solo in CPython.
Abarnert,

sì, l'ho usato idper questo, ma voglio dire che anche in Java puoi ottenere l'indirizzo dell'oggetto, sembra strano che non ci sia modo in (C) Python poiché quello ha effettivamente un gc stabile che non sposta gli oggetti, quindi l'indirizzo rimane lo stesso
Enerccio,

@Enerccio Ma non vuoi usare l'indirizzo di un oggetto per un valore memorizzabile nella cache: vuoi usare il idfo un oggetto, che sia o meno un indirizzo. Ad esempio, in PyPy, idè ancora utile quanto una chiave in CPython, anche se di solito è solo un indice in una tabella nascosta nell'implementazione, ma un puntatore sarebbe inutile, perché (come Java) l'oggetto può essere spostato in memoria.
abarnert

@Enerccio In ogni caso, c'è un modo per ottenere un puntatore a CPython. Come spiegato nella risposta, CPython documenta esplicitamente, come dettaglio specifico dell'implementazione, che idof of a object è il puntatore alla posizione dell'oggetto in memoria. Quindi, se hai qualche utilità per il valore del puntatore (cosa che non fai quasi mai, come spiegato anche nella risposta) nel codice specifico di CPython, c'è un modo per farlo documentato e garantito che funzioni.
Abarnert,

13

Proprio in risposta a Torsten, non sono stato in grado di chiamare addressof()un normale oggetto Python. Inoltre, id(a) != addressof(a). Questo è in CPython, non so nient'altro.

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

4

Con i tipi , puoi ottenere la stessa cosa con

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

Documentazione:

addressof(C instance) -> integer
Restituisce l'indirizzo del buffer interno dell'istanza C.

Nota che in CPython, attualmente id(a) == ctypes.addressof(a), ma ctypes.addressofdovrebbe restituire l'indirizzo reale per ogni implementazione di Python, se

  • ctypes è supportato
  • i puntatori di memoria sono una nozione valida.

Modifica : aggiunte informazioni sull'indipendenza dell'interprete dei tipi


13
>>> import ctypes >>> a = (1,2,3) >>> ctypes.addressof (a) Traceback (ultima chiamata più recente): File "<input>", riga 1, in <module> TypeError: tipo non valido >>> id (a) 4493268872 >>>

5
Concordo con Barry: il codice sopra riportato risulta TypeError: invalid typequando lo provo con Python 3.4.
Brandon Rhodes,


1

So che questa è una vecchia domanda, ma se stai ancora programmando, in Python 3 in questi giorni ... Ho effettivamente scoperto che se è una stringa, allora c'è un modo davvero semplice per farlo:

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

la conversione di stringhe non influisce sulla posizione in memoria:

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

0

Mentre è vero che id(object)ottiene l'indirizzo dell'oggetto nell'implementazione predefinita di CPython, questo è generalmente inutile ... non puoi fare nulla con l'indirizzo dal puro codice Python.

L'unica volta in cui potresti effettivamente utilizzare l'indirizzo proviene da una libreria di estensioni C ... nel qual caso è banale ottenere l'indirizzo dell'oggetto poiché gli oggetti Python vengono sempre passati come puntatori C.


1
A meno che non si usi il ctypestoolkit integrato nella Libreria standard. Nel qual caso puoi fare ogni sorta di cose con l'indirizzo :)
Brandon Rhodes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.