16

Running this code:

import weakref

class A(object):
    _instances = []
    def __init__(self):
        self._instances.append(weakref.ref(self))

    @property
    @classmethod
    def instances(cls):
        for inst_ref in cls._instances:
            inst = inst_ref()
            if inst is not None:
                yield inst

foo = A()
bar = A()
for inst in A.instances:
    print inst

I get this error:

Traceback (most recent call last):
  File "test.py", line 18, in <module>
    for inst in A.instances:
TypeError: 'property' object is not iterable

I can't figure out how having a class method behave like a property (no parentheses).

  • Can anyone explain me why I get this error?
  • Can anyone explain me how I could have a class method behaving like a property?
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
Narann
  • 819
  • 2
  • 8
  • 20

3 Answers3

15

Here is one way to use descriptors with a class:

import weakref

class classproperty(object):
    def __init__(self, fget):
        self.fget = fget
    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class A(object):
    _instances = []
    def __init__(self):
        self._instances.append(weakref.ref(self))

    @classproperty
    def instances(cls):
        for inst_ref in cls._instances:
            inst = inst_ref()
            if inst is not None:
                yield inst

foo = A()
bar = A()
for inst in A.instances:
    print inst

References:

Community
  • 1
  • 1
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • 2
    I will go with it as it look the most elegant to me. Thanks a lot! (PS I can't upvote because I'm less than 15, sorry for this...). – Narann May 01 '15 at 22:20
  • Does this make `@instances.setter` work as well? [related @property python docs](https://docs.python.org/3/library/functions.html?highlight=setter#property) – luckydonald Nov 13 '19 at 09:36
11

Properties always apply to instances, not classes.

The way to do this would be to define a metaclass that defines the property on its own instance method, since a class is an instance of its metaclass:

class AMeta(type):
    def __init__(self,name,bases,dict):
        self._instances = []

    @property
    def instances(self):
        for inst_ref in self._instances:
             inst = inst_ref()
             if inst is not None:
                 yield inst

class A(object):
     __metaclass__ = AMeta

     def __init__(self):
         self._instances.append(weakref.ref(self))

This now works as expected:

>>> foo=A()
>>> bar = A()

>>> for inst in A.instances:
...     print inst
<__main__.A object at 0x1065d7290>
<__main__.A object at 0x1065d7990>
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • _Properties always apply to instances, not classes._ Thanks a lot I though that too but I can't find any info in the official doc. Any source for this? – Narann May 01 '15 at 20:40
  • I can't find the exact place right now, but a good place to look would be in Raymond Hettinger's [descriptor howto](https://docs.python.org/2/howto/descriptor.html) in the Python docs. It's worth a read in any case. – Daniel Roseman May 01 '15 at 20:50
  • "Properties always apply to instances, not classes." Daniel Roseman with some gold! – lukeaus Jun 07 '17 at 22:27
1

One solution would be, to use a WeakKeyDictionary, which is iterable, like you want:

import weakref

class A(object):
    instances = weakref.WeakKeyDictionary()
    def __init__(self):
        self.instances[self] = True

foo = A()
bar = A()
for inst in A.instances:
    print inst
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • It's technically iterable, but [as noted in the docs](https://docs.python.org/2/library/weakref.html#weakref.WeakKeyDictionary), you need to make sure that none of the keys disappear while you're iterating over it. That can be extremely problematic. – user2357112 May 01 '15 at 20:55
  • Wait... the [source](https://hg.python.org/cpython/file/2.7/Lib/weakref.py) actually has specific guards to prevent the GC from removing entries from the dict while an iteration is in progress, as long as nothing else modifies the dict. The docs might need updating. – user2357112 May 01 '15 at 21:11