Python常见面试题017: Python中是否可以获取类的所有实例

发布时间 2023-04-12 17:00:18作者: 松勤吴老师

017. Python中是否可以获取类的所有实例

转载请注明出处,https://www.cnblogs.com/wuxianfeng023

出处

 https://docs.python.org/zh-cn/3.9/faq/programming.html#faq-multidimensional-list

  • 官方回答:Python 不会记录类(或内置类型)的实例。可以在类的构造函数中编写代码,通过保留每个实例的弱引用列表来跟踪所有实例

  • 所以答案是不可以?可以?

  • 准确的说是python不提供这样的接口(没做好)给你,但你要自己实现是么有问题的。

实现代码

  • 方式一

    class A:
        instances = []
        def __init__(self,name):
            self.name = name
            self.__class__.instances.append(self)
    class B:
        instances = {}
        def __init__(self,name):
            self.__class__.instances[self] = name
    
    a1 = A('a1')
    a2 = A('a2')
    print(A.instances) # [<__main__.A object at 0x00000250F285FAC0>, <__main__.A object at 0x00000250F285F7F0>]
    print(A.instances[0].name) # a1
    
    b1 = B('b1')
    b2 = B('b2')
    print(B.instances) # {<__main__.B object at 0x00000250F285F0A0>: 'b1', <__main__.B object at 0x00000250F285FEE0>: 'b2'}
    

  • 方式一有点问题

  • 比如你来个c = A('c1'),你处理的是实例化传递的c1,如果 你要获取c这个变量名是做不到的

  • 方式二

    from inspect import stack
    class A:
        instances = []
    
        def __init__(self):
            name = stack()[1].code_context[0].split('=')[0].strip()
            self.instances.append(name)
    
    a = A()
    b = A()
    print(A.instances)  # ['a','b']
    
  • 你会发现关键是stack(),而这是inspect中的

  • 详细你可以参考:https://docs.python.org/zh-cn/3.9/library/inspect.html?highlight=inspect

  • inspect.stack()

    inspect.stack(context=1)
    返回调用者的栈的帧记录列表。第一个记录代表调用者,最后一个记录代表了栈上最外层的调用。
    
    在 3.5 版更改: 返回一个 具名元组 FrameInfo(frame, filename, lineno, function, code_context, index) 的列表。
    
    具名元组:named tuple 可以参考 https://docs.python.org/zh-cn/3.9/glossary.html#term-named-tuple
    
    

  • 方式二一样有问题

  • 比如你对实例进行了del操作(这可能是显式的,也可能是隐式的),那你的处理是有问题的

    from inspect import stack
    class A:
        instances = []
    
        def __init__(self):
            name = stack()[1].code_context[0].split('=')[0].strip()
            self.instances.append(name)
    
    a = A()
    del a
    print(A.instances)  # ['a']  # 没错你仍然能得到这个a
    
  • 回到最开始的官方回答:通过保留每个实例的弱引用列表来跟踪所有实例,弱引用是啥?

  • 你可能要去看下官网

    https://docs.python.org/zh-cn/3.9/library/weakref.html
    
  • 简而言之是:对对象的弱引用不能保证对象存活:当对象的引用只剩弱引用时, garbage collection 可以销毁引用并将其内存重用于其他内容。但是,在实际销毁对象之前,即使没有强引用,弱引用也一直能返回该对象。术语 referent 表示由弱引用引用的对象。

  • 实现弱引用的模块是weakref

  • weakref返回一个类似其他语言指针的东西,在不影响python内建gc垃圾收集的情况下,创建一个指向该instance的弱引用。你可以理解python的gc机制类似于检测当前有没有任何引用该实例的对象,其中weakref就是创建一个新的引用,但这个引用在gc机制看来是“不存在”的,当只剩下weakref的时候gc就可以回收这块内存了

  • 方式三

    
    from inspect import stack
    from weakref import proxy
    
    class A:
        instances = []
    
        def __init__(self):
            self.name = stack()[1].code_context[0].split('=')[0].strip()
            self.instances.append((self.name,proxy(self)))
        def __del__(self):
            try:
                for name_instance in self.instances:
                    if name_instance[1] == self:
                        A.instances.remove(name_instance)
            except ReferenceError:
                print('引用被删了')
            #     for name_instance in self.instances:
            #         if name_instance[1] == self:
            #             A.instances.remove(name_instance)
            # ReferenceError: weakly-referenced object no longer exists
    a1 = A()
    del a1 # 显式的删除 ,但del是不一定会触发__del__的,因为可能还有别的引用
    def func():
        a2 = A()  # a2 也是一个实例,但它出了这个函数也不会存在
    func()
    a3 = A()
    print(A.instances)  # 只有a3
    # [('a3', <weakproxy at 0x0000013C5A7A43B0 to A at 0x0000013C5A5FF760>)]
    
    
    
    

拓展

  • 在stackoverflow上很早就有人问过类似的问题

    https://stackoverflow.com/questions/328851/printing-all-instances-of-a-class
    https://stackoverflow.com/questions/54000173/how-to-get-all-instances-of-a-class
    
  • 有一些有趣的代码你可以看下(稍作更改)

    def get_obj_instance_nums(dest_obj):
        '''
        获取对象的个数
        '''
        import gc
        obj_instance = []
        for obj in gc.get_objects():
            if isinstance(obj, dest_obj):
                obj_instance.append(obj)
        return len(obj_instance)
    
    class A:
        pass
    a1 = A()
    a2 = A()
    del a1  # 你如果删了,那返回1,不删就返回2
    print(get_obj_instance_nums(A)) # 1
    
  • 弱引用的另外一个示例

    from collections import defaultdict
    import weakref
    
    class KeepRefs(object):
        __refs__ = defaultdict(list)
        def __init__(self):
            self.__refs__[self.__class__].append(weakref.ref(self))
    
        @classmethod
        def get_instances(cls):
            for inst_ref in cls.__refs__[cls]:
                inst = inst_ref()
                if inst is not None:
                    yield inst
    
    class X(KeepRefs):
        def __init__(self, name):
            super(X, self).__init__()
            self.name = name
    
    x = X("x")
    y = X("y")
    for r in X.get_instances():
        print r.name
    del y
    for r in X.get_instances():
        print r.name