Dostęp do adresu pamięci obiektu

168

Kiedy wywołujesz object.__repr__()metodę w Pythonie, otrzymujesz z powrotem coś takiego:

<__main__.Test object at 0x2aba1c0cf890> 

Czy jest jakiś sposób na przechwycenie adresu pamięci, jeśli przeładujesz __repr__(), inny niż wywołanie super(Class, obj).__repr__()i regexing go?

thr
źródło

Odpowiedzi:

208

Podręcznik Pythona mówi o tym id():

Zwróć „tożsamość” obiektu. Jest to liczba całkowita (lub długa liczba całkowita), która na pewno będzie unikalna i stała dla tego obiektu podczas jego życia. Dwa obiekty z nienakładającymi się okresami istnienia mogą mieć tę samą wartość id (). (Uwaga implementacyjna: to jest adres obiektu).

Więc w CPythonie będzie to adres obiektu. Nie ma takiej gwarancji dla żadnego innego interpretera Pythona.

Zauważ, że jeśli piszesz rozszerzenie C, masz pełny dostęp do wewnętrznych elementów interpretera Pythona, w tym bezpośredni dostęp do adresów obiektów.

Nick Johnson
źródło
7
To nie jest uniwersalna odpowiedź na to pytanie; dotyczy tylko CPythona.
DilithiumMatrix
5
Uwaga dla siebie: gwarancja nie dotyczy przetwarzania wieloprocesowego
Rufus
1
Kilka sposobów korzystania z niego (aby porównać wartość, którą zawiera): forum.freecodecamp.com/t/python-id-object/19207
J. Czy
Do czego odnosi się przedmiot lifetime(i co oznacza przez całe życie overlap/not overlap) w tym kontekście?
Minh Tran
4
@MinhTran, ponieważ id jest adresem pamięci obiektu, jest gwarantowany jako unikalny w ramach procesu i dopóki obiekt istnieje. Po jakimś czasie po usunięciu obiektów pamięć może zostać ponownie wykorzystana. Nienachodzący czas życia oznaczałby, że oryginalny obiekt nie istnieje już w momencie tworzenia nowego obiektu. Zatem to ograniczenie oznacza, że ​​nie można bezpiecznie używać id () do utworzenia skrótu obiektu do przechowywania, zwolnienia go, a później przywrócenia.
Joshua Clayton
71

Możesz ponownie zaimplementować domyślną repr w ten sposób:

def __repr__(self):
    return '<%s.%s object at %s>' % (
        self.__class__.__module__,
        self.__class__.__name__,
        hex(id(self))
    )
Armin Ronacher
źródło
1
Wiem, że to jest stare, ale możesz to zrobić, return object.__repr__(self)a nawet zrobić, object.__repr__(obj)kiedy tylko potrzebujesz, zamiast tworzyć nowe zajęcia
Artyer
2
@Artyer: Co ten komentarz ma wspólnego z pierwotnym pytaniem? Opublikowana tutaj odpowiedź polega na odtworzeniu adresu zgodnie z pierwotnym pytaniem. Czy nie musiałbyś majstrować, gdybyś zrobił to w sposób, który sugerujesz?
Rafe
1
To wydaje mi się najlepszą odpowiedzią. Po prostu spróbuj utworzyć obiekt (), wydrukuj go, a następnie wypisz hex (id (obiekt)), a wyniki będą zgodne
Rafe
@Rafe Twoja odpowiedź to rozwlekły sposób działania __repr__ = object.__repr__i nie jest aż tak głupi dowód, ponieważ istnieje wiele sytuacji, w których to nie działa, np. __getattribute__Implementacja z przesłonięciem lub inna niż CPython, gdzie id nie jest lokalizacja pamięci. Nie wypełnia również w Z, więc musisz sprawdzić, czy system jest 64-bitowy i dodać zera w razie potrzeby.
Artyer
@Artyer: Mój przykład pokazuje, jak utworzyć repr. Często dodajemy niestandardowe informacje (i powiedziałbym, że jest to dobra praktyka kodowania, ponieważ pomaga w debugowaniu). Używamy tego stylu intensywnie i nigdy nie trafiłem na twoje skrajne przypadki. Dzięki za udostępnienie ich!
Rafe,
52

Po prostu użyj

id(object)
Ben Hoffstein
źródło
6
co daje liczbę. ... Co dalej? Czy mogę uzyskać dostęp do obiektu o tym numerze?
JLT,
Możesz sprawdzić to id()@JLT
Billal Begueradj
24

Jest tutaj kilka problemów, które nie zostały uwzględnione w żadnej z innych odpowiedzi.

Pierwszy, id zwraca tylko:

„tożsamość” przedmiotu. Jest to liczba całkowita (lub długa liczba całkowita), która jest unikalna i stała dla tego obiektu w czasie jego życia. Dwa obiekty z nienakładającymi się okresami istnienia mogą mieć tę samą id()wartość.


W CPythonie jest to wskaźnik do tego, PyObjectktóry reprezentuje obiekt w interprecie, co jest tym samym, coobject.__repr__ wyświetla. Ale to tylko szczegół implementacji CPythona, a nie coś, co jest prawdą w Pythonie w ogóle. Jython nie zajmuje się wskaźnikami, zajmuje się odwołaniami do języka Java (które JVM oczywiście prawdopodobnie reprezentuje jako wskaźniki, ale nie możesz ich zobaczyć - i nie chcesz, ponieważ GC może je przenosić). PyPy pozwala różnym typom mieć różne rodzaje id, ale najbardziej ogólny jest po prostu indeksem do tabeli obiektów, które wywołałeśidon, który oczywiście nie będzie wskaźnikiem. Nie jestem pewien co do IronPythona, ale podejrzewam, że pod tym względem bardziej przypomina Jython niż CPython. Tak więc w większości implementacji Pythona nie ma sposobu, aby uzyskać to, co się w nim pojawiło repri nie ma sensu, jeśli to zrobisz.


Ale co, jeśli zależy ci tylko na CPythonie? W końcu to dość powszechny przypadek.

Cóż, po pierwsze, możesz zauważyć, że idjest to liczba całkowita; * jeśli chcesz ten 0x2aba1c0cf890ciąg zamiast liczby 46978822895760, będziesz musiał sam go sformatować. Pod kołdrą, wierzę object.__repr__ostatecznie używając printf„s %pformatu, które nie mają z Pythona ... ale zawsze można to zrobić:

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

* W wersji 3.x jest to plik int. W wersji 2.x jest to, intczy jest wystarczająco duży, aby pomieścić wskaźnik - co może nie wynikać z problemów z liczbą ze znakiem na niektórych platformach - a longpoza tym.

Czy jest coś, co możesz zrobić z tymi wskazówkami oprócz ich wydrukowania? Jasne (ponownie, zakładając, że obchodzi Cię tylko CPython).

Wszystkie funkcje C API pobierają wskaźnik do PyObjectlub pokrewnego typu. W przypadku tych pokrewnych typów możesz po prostu wywołać, PyFoo_Checkaby upewnić się, że to naprawdę jest Fooobiekt, a następnie wykonać rzut za pomocą (PyFoo *)p. Tak więc, jeśli piszesz rozszerzenie C, idjest dokładnie tym, czego potrzebujesz.

A jeśli piszesz czysty kod w Pythonie? Możesz wywołać dokładnie te same funkcje za pomocą pythonapifrom ctypes.


Wreszcie pojawiło się kilka innych odpowiedzi ctypes.addressof. To nie ma tutaj znaczenia. Działa to tylko dla ctypesobiektów takich jak c_int32(i może kilku obiektów podobnych do bufora pamięci, takich jak te dostarczane przez numpy). I nawet tam nie podaje adresu c_int32wartości, ale adres poziomu C, int32który c_int32zawija.

Biorąc to pod uwagę, najczęściej, jeśli naprawdę myślisz, że potrzebujesz adresu czegoś, nie chciałeś natywnego obiektu Pythona na pierwszym miejscu, chciałeś ctypesobiektu.

abarnert
źródło
cóż, to jedyny sposób na przechowywanie zmiennych obiektów na mapach / zestawach, gdy ważna jest tożsamość ...
Enerccio
@Enerccio Inne zastosowania id- w tym używanie ich do przechowywania zmiennych wartości w seenzestawie lub cachedyktandzie - nie zależą w żaden sposób od idbycia wskaźnikiem ani w żaden sposób powiązane z repr. Właśnie dlatego taki kod działa we wszystkich implementacjach Pythona, zamiast działać tylko w CPythonie.
abarnert
tak, użyłem iddo tego, ale mam na myśli, że nadal nawet w Javie można uzyskać adres obiektu, wydaje się dziwne, że w (C) Pythonie nie ma sposobu, ponieważ ten ma faktycznie stabilne gc, które nie przenosi obiektów, więc adres pozostaje taki sam
Enerccio
@Enerccio Ale nie chcesz używać adresu obiektu jako wartości, którą można zapisać w pamięci podręcznej - chcesz użyć obiektu idfo, niezależnie od tego, czy jest to adres, czy nie. Na przykład w PyPy idjest nadal tak samo przydatny jak klucz w CPythonie, mimo że zwykle jest to tylko indeks do jakiejś ukrytej tabeli w implementacji, ale wskaźnik byłby bezużyteczny, ponieważ (podobnie jak Java) obiekt można przenieść pamięć.
abarnert
@Enerccio W każdym razie, nie jest to sposób, aby uzyskać wskaźnik w CPython. Jak wyjaśniono w odpowiedzi, CPython wyraźnie dokumentuje, jako szczegół specyficzny dla implementacji, że idobiekt jest wskaźnikiem do lokalizacji obiektu w pamięci. Tak więc, jeśli masz jakiekolwiek zastosowanie dla wartości wskaźnika (czego prawie nigdy nie robisz, jak również wyjaśniono w odpowiedzi) w kodzie specyficznym dla CPythona, istnieje sposób, aby uzyskać udokumentowaną i gwarantowaną pracę.
abarnert
13

W odpowiedzi na Torstena nie mogłem wywołać addressof()zwykłego obiektu Pythona. Ponadto id(a) != addressof(a). To jest w CPythonie, nie wiem nic więcej.

>>> 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
źródło
4

Dzięki ctypes możesz osiągnąć to samo dzięki

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

Dokumentacja:

addressof(C instance) -> integer
Zwróć adres wewnętrznego bufora instancji C.

Zauważ, że w CPythonie obecnie id(a) == ctypes.addressof(a), ale ctypes.addressofpowinien zwrócić rzeczywisty adres dla każdej implementacji Pythona, jeśli

  • ctypes jest obsługiwany
  • wskaźniki pamięci są poprawnym pojęciem.

Edycja : dodano informacje o niezależności ctypów od tłumacza

Torsten Marek
źródło
13
>>> import ctypes >>> a = (1,2,3) >>> ctypes.addressof (a) Traceback (ostatnie wywołanie ostatnie): Plik "<input>", linia 1, w <module> TypeError: nieprawidłowy typ >>> id (a) 4493268872 >>>
5
Zgadzam się z Barrym: powyższy kod TypeError: invalid typepojawia się, gdy spróbuję go z Pythonem 3.4.
Brandon Rhodes
2

Możesz dostać coś odpowiedniego do tego celu dzięki:

id(self)
Thomas Wouters
źródło
1

Wiem, że to stare pytanie, ale jeśli nadal programujesz, w dzisiejszych czasach w Pythonie 3 ... Odkryłem, że jeśli jest to ciąg znaków, jest na to naprawdę łatwy sposób:

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

konwersja ciągów nie wpływa również na lokalizację w pamięci:

>>> spam = {437 : 'passphrase'}
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'
>>> str(spam)
"{437: 'passphrase'}"
>>> object.__repr__(spam)
'<dict object at 0x1043313f0>'
dowódca bashera
źródło
0

Chociaż prawdą jest, że id(object)pobiera adres obiektu w domyślnej implementacji CPythona, jest to generalnie bezużyteczne ... nie można nic zrobić z adresem z czystego kodu Pythona.

Jedyny przypadek, w którym faktycznie mógłbyś użyć adresu, pochodzi z biblioteki rozszerzeń C ... w takim przypadku uzyskanie adresu obiektu jest trywialne, ponieważ obiekty Pythona są zawsze przekazywane jako wskaźniki C.

Dan Lenski
źródło
1
Chyba że używasz wbudowanego ctypeszestawu narzędzi z Biblioteki standardowej. W takim przypadku możesz robić różne rzeczy z adresem :)
Brandon Rhodes