2012-08-09 63 views
1

我有任務創建一個可迭代的類上ITER返回已經從這個類創建的實例列表上的迭代器,例如:如何爲我的任務創建適當的元類?

x = SomeClass() 
y = SomeClass() 
for obj in SomeClass: 
    print obj 
>><__main__.SomeClass object at .......> and etc 

我做它通過元類和全局()。它看起來很糟糕,但它起作用。我想找到一種更優雅和pythonic的方式;起初我想通過weakref做到這一點,但我不明白我如何從我的元類獲得工作類的一些變量,並且當我嘗試在我的元類中存儲對象的ref時,它正在創建一個ref給我類,所以這是我的實現:

class ObjectsInMemory(object): 

    class __metaclass__(type): 
     def __init__(self, *args, **kwargs): 
      self.name = args[0] 
     def __iter__(self): 
      glob = globals().copy() 
      cls_inst = [] 
      for _, v in glob.iteritems(): 
       try: 
        if self.name in v.__repr__(): 
         cls_inst.append(v) 
       except TypeError: 
        pass 
      yield cls_inst 

回答

3

如果你想跟蹤一些類的實例,我建議使用弱引用容器來做這個,比如weakref.WeakSet。這樣的容器類的代碼素描:

import weakref 

class InstanceTracker(object): 
    def __init__(self): 
     self.instances = {} 
    def new(self, cls, *args, **kwargs): 
     instance = cls(*args, **kwargs) 
     self.instances.setdefault(cls, weakref.WeakSet()).add(instance) 
     return instance 
    def iter_instances(cls): 
     return iter(self.instances[cls]) 

現在,您可以爲InstanceTracker例如tracker創建的SomeClasstracker.new(SomeClass)新的實例。

這樣,你不必依賴全局狀態,你的函數沒有奇怪的副作用,而且你不需要修改一個類來跟蹤它的實例。

+0

+1爲InstanceTracker對象的想法 - 即使需要更多的管理來保持它,它比元類或全局變量更清潔。 – Pyrce 2012-08-09 17:20:43

+0

它只需要額外增加兩行來編寫元類,而且一旦理解了元類的工作原理,它就很簡單。 – 2012-08-10 13:46:36

+0

@NoctisSkytower:我知道如何用元類來做到這一點。無論如何,由於其隱藏的全局狀態和反直覺的類迭代行爲,我不喜歡該解決方案。這可能是一個有趣的練習,但在實踐中,我更喜歡更簡單和更明確的解決方案。 – 2012-08-10 14:24:19

2

老實說,我認爲你有你正在尋找的功能錯誤的方法。

首先,全局變量是不好的 - 如果你真的可以,就避免它們。其次,你實際上不需要創建一個元類來創建一個全局迭代器。請嘗試以下操作:

class SomeClass(): 
    ALL_INSTANCES = set() 
    def __init__(self): 
     self.ALL_INSTANCES.add(self) 

for obj in SomeClass.ALL_INSTANCES: 
    print obj 

這給了您相同的迭代功能,沒有可怕的元類混亂。它也隱藏了SomeClass範圍內的全局定義,所以你不用擔心全局的問題(儘管不是全部,因爲它仍然保持全局狀態)。

請注意,您不能輕鬆刪除ALL_INSTANCES中的對象,因爲__del__不會自動調用,因爲只要存在Class類型,就會固有地引用該對象。定義的手動del調用__del__將從全局集合中刪除對象(一旦最後一個引用消失 - 除非有循環引用),但這依賴於用戶記住這樣做。這就是說,這個結構,你應該假設所有的對象無限期地生活。

或者您可以在WeakSet中使用弱引用,並確保__del__實際上從工作集中刪除。但由於循環引用(至少)仍不能保證被調用,所以'死'對象可能仍然存在於全局實例中。

+0

我建議使用'WeakSet'而不是'set'以防止情況從永遠呆在身邊。還要注意,儘管'ALL_INSTANCES'不在全局範圍*中,它仍然是全局*狀態*。這意味着像單元測試這樣的東西仍然是混亂的。鑑於解決方案的簡單性,無論如何,這可能會在某些情況下完成工作+1。 – 2012-08-09 17:42:27

+0

還要注意,即使定義了'del'也不會自動調用__del __()'。它只會刪除一個引用。如果有更多的引用,就像'ALL_INSTANCES'中的引用那樣,對象將保持活動狀態。 – 2012-08-09 17:44:57

+0

@SvenMarnach WeakSet總體上可能會更好,但我不清楚他是否希望對象持續存在或不存在。我會更新我提到的弱引用部分。雖然我認爲如果我想在自己的代碼中使用對象跟蹤,我會實際使用您的實現,因爲您指出了全局狀態問題。 – Pyrce 2012-08-09 17:54:56

1

這是一個有趣的元類需求,但在Python 3中編寫起來相當簡單(花了幾分鐘)。


代碼:

import weakref 

class InstanceIter(type): 

    def __new__(cls, name, bases, class_dict): 
     new_class = super().__new__(cls, name, bases, class_dict) 
     new_class.__instances = weakref.WeakSet() 
     return new_class 

    def __call__(self, *args, **kwargs): 
     instance = super().__call__(*args, **kwargs) 
     self.__instances.add(instance) 
     return instance 

    def __iter__(self): 
     return iter(self.__instances) 

class SomeClass(metaclass=InstanceIter): 

    def __init__(self, name): 
     self.name = name 

    def __repr__(self): 
     return '{!s}({!r})'.format(self.__class__.__name__, self.name) 

測試以確保代碼工作竟然是簡單的爲好。弱引用有助於記憶。


測試:

+0

請注意,OP使用的是Python 2.x,而不是3.x. – 2012-08-10 14:20:56

相關問題