2

I want to store all the instances of a class in a list and want to iterate over it.

I have tried the following two approaches :

1 Using MetaClasses

class ClassIter(type):
    def __iter__(cls):
        return iter(cls._ClassRegistry)
    def __len__(cls):
        return len(cls._ClassRegistry)         

class MyClass(object):
    __metaclass__ = ClassIter
    _ClassRegistry = []
    def __init__(self,objname):
        self._ClassRegistry.append(self)
        self.name = objname

for x in ["first", "second", "third"]:
    MyClass(x)

for y in MyClass:
    print y.name

2 : Using iter in Class itself

class MyClass1(object):
    _ClassRegistry = []
    def __init__(self,objname):
        self._ClassRegistry.append(self)
        self.name = objname
    def __iter__(self):
        return iter(self._ClassRegistry)

for x in ["first", "second", "third"]:
    MyClass1(x)

for y in MyClass1:
    print y.name

Out of both the solutions, former approach works perfectly while the later solution gives an error TypeError: 'type' object is not iterable.

Can some one please explain me (in detail) why the second approach is not working and why there is a need to use metaclass to make another class iterable?

sarbjit
  • 3,786
  • 9
  • 38
  • 60
  • 1
    Directly related, same principles apply here: [Overriding special methods on an instance](http://stackoverflow.com/q/10376604) – Martijn Pieters Feb 23 '15 at 15:01
  • I don't understand why people keep on downvoting the Questions without mentioning any reason. I believe, I have asked a valid question here ... – sarbjit Feb 23 '15 at 15:07
  • I agree that the downvotes on your post appear harsh. You did show your attempt, and your expected outcome. The subject is a little esoteric. – Martijn Pieters Feb 23 '15 at 15:08
  • Well, I personally downvoted because I can't even begin to understand why you think the second version would work. You're defining an instance method (you didn't even attempt to use a class method), of course that only works on the _instance_ and not on the class itself. This should be basic python knowledge for anybody working with classes; that you don't seem to know this means you did way too little research. – l4mpi Feb 23 '15 at 15:10
  • @l4mpi: there is always more research that could be done, and more experience gained. But that doesn't mean this question doesn't show effort to understand the issue. I don't think the bar needs to be *that* high. – Martijn Pieters Feb 23 '15 at 15:18
  • @MartijnPieters I can't see any effort expended for finding out why it's not working. I mean, even the most basic case, defining any method on a class and then attempting to call it on the class leads to a pretty straightforward exception - you're calling a bound method but don't pass an instance to it. If OP had used the extensively documented `@classmethod` or `@staticmethod` I would not have downvoted, but not even arriving at the knowledge that you can't simply call a method on the class itself just feels like a lack of research and experimentation for me. – l4mpi Feb 23 '15 at 15:24
  • Well, I must say that I'm a learner and I mostly learn the language by reading on net and by asking these stupid questions. I don't have any problem with down voting but my point is that it would be more helpful if you would have pointed me to some reference where I can study about them. Otherwise I would have learnt nothing but just demotivation about asking any question. – sarbjit Feb 23 '15 at 15:26

2 Answers2

3

Special methods like __iter__ are not looked up directly on the object, only on the type of the object. Python calls type(object).__iter__(object), not object.__iter__(), effectively bypassing the normal object-first-then-type lookups (so from instance to class and from class to metaclass).

This is done to make hashing of types possible; if hash(object) used the __hash__ method found directly on the object, you could never define a custom class with a __hash__ method for the instances.

The meta class is the type of the class; just like the class is the type of an instance, so iter(class) looks for type(class).__iter__() rather than at class.__iter__ directly.

If it did work that way, you could never define a __iter__ method for your class instances, because class.__iter__ would be a method and require the instance to be bound to, while there is no such instance if you are iterating over the class.

See the Special method lookup section of the Python datamodel documentation:

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I suspect you must have meant something like "Python calls `type(object).__iter__()`, not `object.__iter__()`, effectively bypassing the normal instance-first-then-class lookups" — because what you have doesn't quite make sense regarding how methods are looked up on an object. – martineau Feb 23 '15 at 15:36
  • @martineau: Perhaps, but I suspect it is the second half of the sentence that is the confusing part here. – Martijn Pieters Feb 23 '15 at 15:39
  • Don't think so. Seems like "only on the _type_ of the object" should translate to `type(object)` not `type(MyClass)`. – martineau Feb 23 '15 at 15:44
  • @martineau: ah, you are right; in the context of the rest of the paragraph that makes more sense. – Martijn Pieters Feb 23 '15 at 15:45
0

Special method names are always invoked like this:

iter(foo) <--> type(foo).__iter__(foo)

Your first solution is compatible with this call (type(MyClass) is the metaclass). Your second is not (type(MyClass1) is type, which has no __iter__() method).

Kevin
  • 28,963
  • 9
  • 62
  • 81