如何在整个程序的生命周期中唯一地标识Python中的类?我对内置类型和用户定义类型都感兴趣。 id
不适合,因为:
[{
id
]返回对象的“身份”。这是一个整数(或长整数),在该对象的生存期内,此整数保证是唯一且恒定的。具有不重叠生存期的两个对象可能具有相同的id()值。
有可能吗?
我有一本字典,其中的字符串作为键,Python的类型/类作为值。例如:
{'tid': int, 'name': str, 'color': str}
问题是唯一地标识字符串和类型的组合,并将其用作比较的基础。因此,字典{'tid': int, 'name': str, 'color': str}
和字典{'tid': float, 'name': str, 'color': str}
应该被裁定为不相等。同样,{'tid': int, 'name': str, 'color': str}
和{'tid': int, 'name': str, 'colour': str}
。
此外,如果对名称和类型进行散列并存储结果,则比较应该可以进行(因为比较整数比比较字典快得多)。例如:
def hash_heading(dct):
s = ''
for k, v in OrderedDict(dct).items():
s += k + str(id(v))
return hash(s)
这带来了这样的风险:两个生命周期不相交的A类和B类被分配相同的id
(以防A在声明B之前被销毁),因此检查通过。
答案 0 :(得分:2)
首先:内置类型和自定义Python类型都只是Python对象。出于id()
的目的,您无需担心任何差异。
您确实确实想使用id()
来跟踪课程。因为如果id()
已更改,则意味着您不再具有相同的类。反之亦然,因为id()
值可以用于具有非重叠生命周期的对象,所以具有相同的id()
值并不意味着您仍然具有相同的对象。
类应为单例。很少重新定义类,因为大多数代码都会在模块级别定义它们,并且模块也是单例的(它们位于sys.modules
中,并且仅加载一次)。
此外,具有不同ID 的课程不是同一课程。它可能具有相同的名称,也可能位于相同的模块中,但是您不能使用其中一个来断言另一个实例的类型相同。您不应尝试在Python的整个生命周期内跟踪类,而忽略它们的id()
值。
一个简短的演示来说明为什么不能:
>>> import types
>>> module.Foo = type('Foo', (), {'__module__': module.__name__})
>>> module.Foo
<class 'foo.Foo'>
>>> id(module.Foo)
140646171069480
>>> foo_instance = module.Foo()
>>> isinstance(foo_instance, module.Foo)
True
>>> module.Foo = type('Foo', (), {'__module__': module.__name__}) # new class, same name, same location!
>>> id(module.Foo)
140646170684440
>>> isinstance(foo_instance, module.Foo)
False
>>> type(foo_instance)
<class 'foo.Foo'>
>>> type(foo_instance) is not module.Foo
True
foo_instance
实例是类Foo
的实例,但它是 first Foo
类的实例。通过创建新的Foo
类,id()
随之发生了变化,但它告诉您现在我们有了一个不同的新类对象,它是一个不同的对象。
如果仅跟踪id()
,则如果要删除对原始Foo
的所有引用,则从内存中删除该对象,然后创建一个新的Foo
,它可能具有相同的id()
值,因为可以重新使用id()
值。因此,您可以使用同一个Foo
的 new id()
。这无关紧要,因为如果没有更多引用,则只能从内存中删除旧的Foo
。实例拥有对其类的引用,因此,如果有一个类的实例,则该类将继续保留在内存中。
如果您所做的只是比较字典中包含的类,那么您根本就不必id()
测试 ,因为类相等性测试只对对象返回true完全一样reference_to_class == other_reference_to_class
仅在两个引用都指向同一个类对象时才是True
;测试基本上与reference_to_class is other_reference_to_class
相同。字典会保留对其所有内容的引用,使这些内容保持活动状态。通过将类型存储在字典中,您确保它们的id()
值也不会更改。
您链接到的帖子不是在询问类id()
。他们正在讨论 instances 的id()
值;它讨论了诸如具有不同id()
值的多个实例之类的事情,并且快速连续创建和丢弃实例并不能保证第一个实例的id()
被第二个实例重用。
答案 1 :(得分:1)
我假设您的用例是这样的(但不是那么琐碎和毫无意义的):您要保留自启动以来所见过的每种类型的集合,因此您可以执行以下操作:< / p>
def seen_before(types):
if ???:
print("I've seen those types before")
>>> seen_before({'name': str, 'value': int})
>>> seen_before({'name': str, 'value': int})
I've seen those types before!
问题是,您不能将字典映射字符串粘贴到集合中的类型。
因此,您的想法是通过存储(name, id(typ))
对的frozenset或sorted tuple将其平整为可哈希的内容。
但是问题是:
>>> class C: pass
>>> seen_before({'name': str, 'value': C})
>>> del C
>>> class D: pass
>>> seen_before({'name': str, 'value': D})
I've seen those types before!
虽然这种情况不太可能发生(您的用户不太可能破坏这样的类),但这不是不可能的。 C
和D
的生命周期不相交,因此它们可能以相同的id
结尾。
但是您在这里不需要id
。类型已经可以进行哈希处理,因此您可以只存储它们而不是它们的id
:
def seen_before(types, *, _cache=set()):
tup = tuple(sorted(map(tuple, types.items()))
if tup in _cache:
print("I've seen those types before")
_cache.add(tup)
现在,因为您将类型存储在集合中,所以任何人传递给您的任何类型都将永远存在,因此不会出现生命周期不相交的整个类型问题。
如果您想要使用id代替类型本身,则出于某些其他原因,只要您将类型存储在某个地方,它仍然可以工作……
def seen_before(types, *, _cache=set(), _typestash=set()):
tup = tuple(sorted((name, id(typ)) for name, typ in types.items()))
if tup in _cache:
print("I've seen those types before")
else:
_cache.add(tup)
_typestash.update(types.values())
…或:
def seen_before(types, *, _cache={}):
tup = tuple(sorted((name, id(typ)) for name, typ in types.items()))
if tup in _cache:
print("I've seen those types before")
else:
_cache[tup] = list(types.values())
在惯用的Python代码中,类型通常总是永远存在,并且类型只有几百个,所以很好。
但是,如果有人做一些奇怪的事情,例如在循环中创建和销毁大型类型,该怎么办?存储一百万个任意大小的类型可能比存储一百万个64位整数要昂贵得多。
或者,如果某人确实做某件事真的很奇怪,例如使用具有重要作用的__del__
方法创建带有元类的类型,该怎么办?然后存储您要处理的每种类型都会进行一次重要的语义更改(并且这种更改已经很好地隐藏了,很难调试)。
您可以通过将弱引用存储到类型而不是类型本身来缓解这两个问题(然后使用类型和弱引用的ID对作为键,而不仅仅是类型的ID)。即使该类型消失,其已失效的weakref也不会。即使一个新类型具有相同的ID,也不会匹配死的weakref,因此它将获得一个新的,因此其ID对将有所不同。
当然,存储weakrefs仍然不是免费的,甚至与存储int一样便宜。但是至少它们是静态的,而且尺寸很小,无论它们所指的东西有多大。而且,它们不会像存储id
那样干扰目标的破坏。