为什么同一个对象的不同方法具有相同的`id`?

时间:2016-02-03 09:47:20

标签: python

我认为is运算符会检查对象id的相等性。但它似乎并非如此:

>>> class A(object):
...   def f(): return 1
...   def g(): return 2
...
>>> a = A()
>>> a.f is a.g
False
>>> id(a.f) == id(a.g)
True

4 个答案:

答案 0 :(得分:15)

Python重用相同的内存位置而不保留对对象的其他引用,一旦id(a.f)被评估,对对象有更多的引用,因此它是gc'然后python可以自由地重用它a.g的内存位置。如果将方法分配给名称,您将看到不同的行为:

# creates a reference to the method f
In [190]: f = a.f
# creates a reference to the method g
In [191]: g = a.g
# cannot reuse the memory location of f as it is still referenced
In [192]: id(f) == id(g)
Out[192]: False

实际上你真的只需要存储对f的引用来查看与上面相同的行为。

In [201]: f = a.f

In [202]: id(f) == id(a.g)
Out[202]: False

您可以使用sys.getrefcountgc.gc.get_referrers

查看引荐计数
In [2]: import gc

In [3]: f = a.f

In [4]: len(gc.get_referrers(a.g)),len(gc.get_referrers(f))
Out[4]: (0, 1)

In [5]: sys.getrefcount(a.g),sys.getrefcount(f)
Out[5]: (1, 2)

你为a.g看到1的唯一原因是因为返回的计数通常比你预期的高一个,因为它包含(临时)引用作为getrefcount()的参数。 它类似于您自己的示例,在评估方法后,您仍然会引用fa.g引用计数为0,因此立即进行垃圾回收并且python可以自由使用其他任何事物的记忆位置。

值得注意的是,行为不仅限于方法,而且它只是一个cpython实现细节,而不是你应该依赖的东西:

In [67]: id([]), id([])
Out[67]: (139746946179848, 139746946179848)

In [73]: id(tuple()),id([]),id([])
Out[73]: (139747414818888, 139746946217544, 139746946217544)

In [74]: id([]),id([]),id([])
Out[74]: (139746946182024, 139746946182024, 139746946182024)

In [75]: id([]),id(tuple()),id([])
Out[75]: (139746946186888, 139747414818888, 139746946186888)

In [76]: id(tuple()),id([]),id(tuple())
Out[76]: (139747414818888, 139746946217736, 139747414818888)

答案 1 :(得分:4)

Python正在使用相同的内存位置来处理方法a.fa.g,它们是**两个具有非重叠生命周期*的对象,因此{{1 }返回两者的相同标识。请参阅下面的更详细解释。

来自is operator的文档:

  

运算符是和不是对象标识的测试:x是y是真的   当且仅当x和y是同一个对象时。

来自id

的文档
  

返回对象的“标识”。这是一个整数(或长整数)   整数)保证对于该对象是唯一的和常量的   在其一生中。 具有非重叠生命期的两个对象可以   具有相同的id()值。

<强>说明

每当您通过idclass.name查找方法时,方法对象都会创建为a-new。 Python每次都使用descriptor protocol将函数包装在方法对象中。

因此,当您查找instance.nameid(a.f)时,会创建一个新的方法对象。

  1. 当你对id(a.g)的id进行grubbing时,会在内存中创建它的副本。此内存位置由a.f返回。
  2. 由于没有对新创建的方法的引用,它由GC回收(现在的内存地址再次可用)。
  3. 获取id的ID后,会在同一内存地址创建一份副本,您可以使用a.g再次检索该地址。
  4. 你有 truthy id的比较。
  5. 祝你好运!

答案 2 :(得分:2)

a.f和a.g是不同的对象。 is运算符仅在有一个对象时才返回true。

但是两个生命周期不重叠的对象可能具有相同的id()值。

有关id运算符,请参阅here

答案 3 :(得分:2)

运算符is检查对象标识,而不是值。在这种情况下,您有两个单独的函数(对象);因此他们有不同的身份。

关于以下部分:

>>> id(a.f) == id(a.g)
True

由于 Python在运行时创建对象,Python第一次尝试获取a.f的ID时,a.g尚未定义并基于在Python wiki上两个具有非重叠生命周期的对象可能具有相同的id()值。

因此,在这种情况下,具有非重叠生命周期的对象a.fa.g具有相同的ID。

  

返回对象的“标识”。这是一个整数(或长整数)   整数)保证对于该对象是唯一的和常量的   在其一生中。两个具有非重叠寿命的对象可以   具有相同的id()值。

关于is运算符的一些额外说明:

正如我在前面提到的行中所说的,is运算符将检查对象的身份,并且将在运行时创建Python中的对象。但是对于像整数和字符串这样的小类型来说,情况并非如此,因为它们是单例而不是Python对象。因此,它们将像C类型一样立即位于内存中。

为了更好地演示,您可以看到以下示例:

>>> 100 is 10*10
True
>>>
>>> 1000 is 10*100
False
>>>

对于字符串:

>>> 'aaaa'*5 is 'a'*20
True
>>> 'aaaa'*50 is 'a'*200
False