Acessando o Endereço da Memória do Objeto

168

Quando você chama o object.__repr__()método em Python, você recebe algo assim:

<__main__.Test object at 0x2aba1c0cf890> 

Existe alguma maneira de obter o endereço de memória se você sobrecarregar __repr__() , além de chamá super(Class, obj).__repr__()-lo e recuperá-lo?

thr
fonte

Respostas:

208

O manual do Python tem a dizer sobre id():

Retorne a "identidade" de um objeto. Este é um número inteiro (ou inteiro longo) que é garantido como único e constante para esse objeto durante sua vida útil. Dois objetos com vida útil não sobreposta podem ter o mesmo valor id (). (Nota de implementação: este é o endereço do objeto.)

Portanto, no CPython, este será o endereço do objeto. Porém, não existe essa garantia para qualquer outro intérprete Python.

Observe que, se você estiver escrevendo uma extensão C, terá acesso total aos componentes internos do interpretador Python, incluindo acesso diretamente aos endereços dos objetos.

Nick Johnson
fonte
7
Esta não é uma resposta universal para a pergunta; isso se aplica apenas ao CPython.
precisa
5
Nota to self: A garantia não se aplica a multiprocessamento
Rufus
1
Algumas maneiras de usá-lo (para comparar o valor que ele contém): forum.freecodecamp.com/t/python-id-object/19207
J. Será que
O que um objeto lifetime(e o que significa para a vida toda overlap/not overlap) se refere nesse contexto?
Minh Tran
4
@MinhTran, porque o ID é o endereço de memória do objeto, ele é garantido exclusivo dentro do processo e enquanto o objeto existe. Algum tempo depois que o objeto é coletado como lixo, a memória pode ser reutilizada. Uma vida útil sem sobreposição significaria que o objeto original não existe mais quando o novo objeto é criado. Portanto, essa limitação significa que você não pode usar o id () com segurança para criar um hash de um objeto para armazenar, liberá-lo e posteriormente restaurá-lo.
Joshua Clayton
71

Você pode reimplementar o repr padrão da seguinte maneira:

def __repr__(self):
    return '<%s.%s object at %s>' % (
        self.__class__.__module__,
        self.__class__.__name__,
        hex(id(self))
    )
Armin Ronacher
fonte
1
Eu sei que isto é antiga, mas você pode apenas fazer return object.__repr__(self)ou até mesmo fazer object.__repr__(obj)sempre que você precisar desta vez de fazer uma nova classe
Artyer
2
@Artyer: O que esse comentário tem a ver com a pergunta original? A resposta postada aqui está recriando o endereço conforme solicitado pela pergunta original. Você não teria que usar o mangle se o fizesse da maneira que sugere?
Rafe
1
Esta parece ser a melhor resposta para mim. Basta tentar fazer um objeto (), imprimi-lo, em seguida, imprimir hex (id (objeto)) e os resultados corresponder
Rafe
@ Rafe Sua resposta é uma maneira longa de fazer __repr__ = object.__repr__, e não é tão à prova de idiotas, pois há uma variedade de situações em que isso não funciona, por exemplo, uma __getattribute__implementação substituída ou não CPython onde o ID não é a localização da memória. Ele também não é preenchido com z, então você teria que descobrir se o sistema é de 64 bits e adicionar os zeros conforme necessário.
Artyer
@Artyer: Meu exemplo mostra como construir uma repr. Geralmente, adicionamos informações personalizadas (e eu diria que essa é uma boa prática de codificação, pois ajuda na depuração). Usamos esse estilo intensamente e nunca me deparei com seus casos extremos. Obrigado por compartilhá-los!
Rafe
52

Apenas use

id(object)
Ben Hoffstein
fonte
6
que dá um número. ... Qual é o próximo? Posso acessar o objeto com esse número?
JLT
Você pode verificar isso id()@JLT
Billal Begueradj
24

Existem alguns problemas aqui que não são cobertos por nenhuma das outras respostas.

Primeiro, idapenas retorna:

a "identidade" de um objeto. Esse é um número inteiro (ou inteiro longo) que é garantido como único e constante para esse objeto durante sua vida útil. Dois objetos com vida útil sem sobreposição podem ter o mesmo id()valor.


No CPython, este é o ponteiro para o PyObjectque representa o objeto no intérprete, que é a mesma coisa que no, que obviamente não será um ponteiro. Não tenho certeza sobre o IronPython, mas eu suspeitaria que é mais como o Jython do que com o CPython a esse respeito. Portanto, na maioria das implementações do Python, não há como obter o que foi mostrado nisso , e de nada serve se você o fez.object.__repr__ exibida. Mas este é apenas um detalhe de implementação do CPython, não algo que é verdadeiro no Python em geral. O Jython não lida com ponteiros, lida com referências a Java (que a JVM provavelmente representa como ponteiros, mas você não pode vê-las - e não gostaria, porque o GC pode movê-las). O PyPy permite que tipos diferentes tenham tipos diferentes id, mas o mais geral é apenas um índice em uma tabela de objetos que você chamouidrepr


Mas e se você se importa apenas com o CPython? Afinal, esse é um caso bastante comum.

Bem, primeiro, você pode perceber que idé um número inteiro; * se você quiser essa 0x2aba1c0cf890sequência em vez do número 46978822895760, precisará formatá-la. Nos bastidores, acredito que, em object.__repr__última análise, esteja usando printfo %pformato s , que você não possui do Python ... mas você sempre pode fazer isso:

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

* No 3.x, é um int. Na 2.x, é um inttamanho suficiente para armazenar um ponteiro - o que pode não ser por causa de problemas com números assinados em algumas plataformas - e de longoutra forma.

Há algo que você possa fazer com esses ponteiros além de imprimi-los? Claro (novamente, supondo que você se preocupe apenas com o CPython).

Todas as funções da API C levam um ponteiro para um PyObjectou um tipo relacionado. Para esses tipos relacionados, você pode simplesmente ligar PyFoo_Checkpara garantir que realmente seja um Fooobjeto e, em seguida, lançar com (PyFoo *)p. Portanto, se você estiver escrevendo uma extensão C, idé exatamente isso que você precisa.

E se você estiver escrevendo código Python puro? Você pode chamar exatamente as mesmas funções com pythonapifrom ctypes.


Finalmente, algumas das outras respostas foram levantadas ctypes.addressof. Isso não é relevante aqui. Isso funciona apenas para ctypesobjetos como c_int32(e talvez alguns objetos semelhantes a buffer de memória, como os fornecidos por numpy). E, mesmo lá, não está fornecendo o endereço do c_int32valor, está fornecendo o endereço do nível C int32que c_int32envolve.

Dito isto, na maioria das vezes, se você realmente acha que precisa do endereço de algo, não queria um objeto Python nativo em primeiro lugar, queria um ctypesobjeto.

abarnert
fonte
bem esta é a única maneira de armazenar objetos mutáveis em mapas / conjuntos quando a identidade é importante ...
Enerccio
@Enerccio Os outros usos id- incluindo usá-los para manter valores mutáveis ​​em um seenconjunto ou em um cacheditado - não dependem de maneira alguma de idser um ponteiro ou de qualquer maneira relacionados ao repr. É exatamente por isso que esse código funciona em todas as implementações do Python, em vez de apenas trabalhar no CPython.
abarnert
Sim, eu usei idpara isso, mas quero dizer que, mesmo em java, você pode obter o endereço do objeto, parece estranho que não há como (C) Python, pois esse tem um gc realmente estável que não move objetos, portanto, o endereço permanece o mesmo
Enerccio
@Enerccio Mas você não deseja usar o endereço de um objeto para obter um valor armazenável em cache - você deseja usar o idobjeto para um objeto, seja ele um endereço ou não. Por exemplo, no PyPy, idainda é tão útil quanto uma chave no CPython, apesar de geralmente ser apenas um índice em alguma tabela oculta na implementação, mas um ponteiro seria inútil, porque (como Java) o objeto pode ser movido memória.
abarnert
@Enerccio De qualquer forma, não é uma forma de obter um ponteiro em CPython. Conforme explicado na resposta, o CPython documenta explicitamente, como um detalhe específico da implementação, que o idde um objeto é o ponteiro para o local do objeto na memória. Portanto, se você tiver alguma utilidade para o valor do ponteiro (o que você quase nunca faz, como também explicado na resposta) no código específico do CPython, existe uma maneira de obtê-lo documentado e garantido que funcione.
abarnert
13

Em resposta a Torsten, não consegui chamar addressof()um objeto python comum. Além disso id(a) != addressof(a),. Isso está no CPython, não sei de mais nada.

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

Com ctypes , você pode conseguir a mesma coisa com

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

Documentação:

addressof(C instance) -> integer
Retornar o endereço do buffer interno da instância C

Observe que no CPython, atualmente id(a) == ctypes.addressof(a), mas ctypes.addressofdeve retornar o endereço real para cada implementação do Python, se

  • ctypes é suportado
  • ponteiros de memória são uma noção válida.

Editar : informações adicionadas sobre a independência de intérpretes de ctypes

Torsten Marek
fonte
13
>>> import ctypes >>> a = (1,2,3) >>> ctypes.addressof (a) Traceback (última chamada mais recente): Arquivo "<input>", linha 1, no <module> TypeError: Tipo inválido >>> id (a) 4493268872 >>>
5
Concordo com Barry: o código acima resulta TypeError: invalid typequando o tento com o Python 3.4.
Brandon Rhodes
2

Você pode obter algo adequado para esse fim com:

id(self)
Thomas Wouters
fonte
1

Eu sei que essa é uma pergunta antiga, mas se você ainda estiver programando, no python 3 hoje em dia ... Na verdade, descobri que se for uma string, existe uma maneira muito fácil de fazer isso:

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

A conversão de cadeia de caracteres não afeta o local na memória:

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

Embora seja verdade que id(object)obtém o endereço do objeto na implementação padrão do CPython, isso geralmente é inútil ... você não pode fazer nada com o endereço do código Python puro.

A única vez em que você realmente poderia usar o endereço é de uma biblioteca de extensão C ... nesse caso, é trivial obter o endereço do objeto, pois os objetos Python sempre são passados ​​como ponteiros C.

Dan Lenski
fonte
1
A menos que você use o ctypeskit de ferramentas interno na Biblioteca Padrão. Caso em que você pode fazer todos os tipos de coisas com o endereço :)
Brandon Rhodes