如何在整个程序的生命周期中唯一地标识Python中的类

时间:2018-07-24 16:32:07

标签: python python-3.x

问题

如何在整个程序的生命周期中唯一地标识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之前被销毁),因此检查通过。



[1] https://docs.python.org/3/library/functions.html#id

2 个答案:

答案 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!

虽然这种情况不太可能发生(您的用户不太可能破坏这样的类),但这不是不可能的。 CD的生命周期不相交,因此它们可能以相同的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那样干扰目标的破坏。