访问对象存储器地址


168

当您object.__repr__()在Python中调用该方法时,您会得到类似以下的信息:

<__main__.Test object at 0x2aba1c0cf890> 

如果您过载了__repr__(),还有什么方法可以保留该内存地址,然后调用super(Class, obj).__repr__()并重新分配它呢?

Answers:


208

Python的手册已经这样说id()

返回一个对象的“身份”,这是一个整数(或长整数),在该对象的生存期内保证是唯一且恒定的。两个不重叠生存期的对象可能具有相同的id()值。 (实施说明:这是对象的地址。)

因此,在CPython中,这将是对象的地址。但是,没有任何其他Python解释器的此类保证。

请注意,如果您正在编写C扩展名,则可以完全访问Python解释器的内部,包括直接访问对象的地址。


7
不是这个问题的普遍答案;它仅适用于CPython。
DilithiumMatrix 2014年

5
自我注意:保证不适用于多重处理
Rufus

1
一些方法来使用它(到它包含的值进行比较):forum.freecodecamp.com/t/python-id-object/19207
J.确实

在这种情况下,对象lifetime指的是什么(对生命而言意味着什么overlap/not overlap)?
Minh Tran

4
@MinhTran因为id是对象的内存地址,所以可以保证它在进程中以及对象存在时是唯一的。对象被垃圾回收一段时间后,内存可能会被重用。非重叠生存期将意味着在创建新对象时原始对象不再存在。因此,此限制意味着您不能安全地使用id()创建对象的哈希值以进行存储,释放它,然后再将其恢复。
约书亚·克莱顿

71

您可以通过以下方式重新实现默认代表:

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

1
我知道这很古老,但是您只要有需要return object.__repr__(self)就可以做,甚至可以做,object.__repr__(obj)而不
用上新课

2
@Artyer:此评论与原始问题有什么关系?此处发布的答案是按照原始问题的要求重新创建地址。如果按照建议的方式进行操作,您是否不必打乱字符串?
Rafe

1
这似乎是我的最佳答案。只需尝试制作一个object(),将其打印,然后打印hex(id(object)),结果匹配
Rafe

@Rafe您的答案是一个漫长的尝试__repr__ = object.__repr__,并且并不是那么简单,因为在许多情况下这都不起作用,例如__getattribute__,id无效的覆盖或非CPython实现内存位置。它也不会z填充,因此您必须确定系统是否为64位,并根据需要添加零。
Artyer

@Artyer:我的示例显示了如何构建一个代表。我们经常添加自定义信息(我会说这是很好的编码习惯,因为它有助于调试)。我们大量使用这种样式,而我从未遇到过这种情况。多谢分享!
拉菲


24

这里有一些其他答案未涵盖的问题。

首先,id仅返回:

对象的“身份”。这是一个整数(或长整数),在该对象的生存期内,此整数保证是唯一且恒定的。具有非重叠生存期的两个对象可能具有相同的id()值。


在CPython中,这恰好是指向PyObject解释器中代表对象的指针,这与on上的东西相同,显然不会成为指针。我不确定IronPython,但我怀疑在这方面,它更像是Jython,而不是CPython。因此,在大多数Python实现中,无法获得显示在其中的任何内容,如果您这样做了,则毫无用处。object.__repr__显示。但这只是CPython的实现细节,而不是一般Python的真实情况。Jython不处理指针,它处理Java引用(JVM当然可以将其表示为指针,但是您看不到它们,并且也不想这样做,因为允许GC来回移动它们)。PyPy让不同类型的对象具有不同的种类id,但最一般的只是对您已调用的对象表的索引idrepr


但是,如果您只关心CPython怎么办?毕竟,这是一个很普通的情况。

好吧,首先,您可能会注意到这id是一个整数; *如果您想要该0x2aba1c0cf890字符串而不是数字46978822895760,则必须自己设置其格式。在幕后,我相信object.__repr__最终使用printf%p格式,你没有从Python的有......但你总是可以做到这一点:

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

*在3.x中,它是一个int。在2.x中,int如果它足够大以容纳一个指针(可能不是由于某些平台上的有符号数问题而引起的),long否则是一个错误。

除了将它们打印出来,您还能使用这些指针做什么?当然(再次假设您只关心CPython)。

所有C API函数均采用指向PyObject或相关类型的指针。对于那些相关的类型,您可以调用PyFoo_Check以确保它确实是一个Foo对象,然后使用进行强制转换(PyFoo *)p。因此,如果您要编写C扩展名,id则正是您所需要的。

如果您正在编写纯Python代码怎么办?您可以使用pythonapifrom 调用完全相同的函数ctypes


最后,提出了其他一些答案ctypes.addressof。这与这里无关。这仅适用于ctypes类似的对象c_int32(可能还有一些类似内存缓冲区的对象,如所提供的对象numpy)。而且,即使在那儿,它也没有为您提供c_int32值的地址,而是为您提供int32c_int32包装的C级地址。

话虽这么说,但通常情况下,如果您确实认为自己需要某个东西的地址,那么首先就不需要原生Python对象,而是想要一个ctypes对象。


好,这是在身份很重要时将可变对象存储在地图/集中的唯一方法……
Enerccio

@Enerccio的其他用法(id包括使用它们来保存seen集合或cachedict中的可变值)不依赖于id成为指针或以任何方式与关联repr。这就是为什么这样的代码可以在所有Python实现中工作而不仅仅是在CPython中工作的原因。
abarnert

是的,我用过id,但是我的意思是即使在java中也可以获取对象的地址,这似乎很奇怪,因为(C)Python中没有办法,因为实际上有一个稳定的gc不会移动对象,因此地址保持不变
Enerccio

@Enerccio但是,您不想使用对象的地址作为可缓存的值,而是想使用id对象,无论它是不是地址。例如,在PyPy中,id它通常仍然与CPython中的键一样有用,尽管它通常只是实现中一些隐藏表的索引,但是指针将无用,因为(例如Java)可以将对象移入记忆。
阿巴内特

@Enerccio无论如何,有一种方式来获得在CPython的一个指针。正如答案中所解释的那样,CPython明确记录了作为实现特定细节id的对象的对象是指向对象在内存中位置的指针。因此,如果您在CPython专用代码中对指针值有任何用处(您几乎从未做过,正如答案中所解释),有一种方法得到记录并保证能正常使用。
abarnert

13

仅作为对Torsten的回应,我无法调用addressof()常规的python对象。此外,id(a) != addressof(a)。这是在CPython中,对其他什么都不知道。

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

使用ctypes,您可以使用

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

说明文件:

addressof(C instance) -> integer
返回C实例内部缓冲区的地址

请注意,在CPython中,当前是id(a) == ctypes.addressof(a),但是ctypes.addressof应返回每个Python实现的真实地址,如果

  • 支持ctypes
  • 内存指针是一个有效的概念。

编辑:添加了有关ctypes解释器独立性的信息


13
>>>导入ctypes >>> a =(1,2,3)>>> ctypes.addressof(a)回溯(最近一次调用):文件“ <input>”,<module> TypeError中的第1行:无效的类型>>> id(a)4493268872 >>>

5
我同意Barry的意见:上面的代码是TypeError: invalid type在我使用Python 3.4尝试时得出的。
Brandon Rhodes


1

我知道这是一个老问题,但是如果您现在仍在使用python 3编程,我实际上发现如果它是字符串,那么有一种非常简单的方法可以做到这一点:

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

字符串转换也不影响内存中的位置:

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

0

虽然确实id(object)可以在默认的CPython实现中获取对象的地址,但这通常是无用的……您无法纯Python代码中的地址进行任何操作。

实际上,唯一可以使用该地址的时间是来自C扩展库...在这种情况下,获取对象的地址很简单,因为Python对象始终作为C指针传递。


1
除非您使用ctypes标准库中的内置工具包。在这种情况下,您可以使用地址进行各种操作:)
Brandon Rhodes
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.