TypeError: 'type' object is not iterable - Iterating over object instances
Asked Answered
C

3

12

I am working on a project and I would like to make one of my classes iterable. To the best of my knowledge I can do that with using metaclass.

First of all I would like to understand how metaclass works. Therefore I would like to present my own practicing example where I made a Car class. So here I would like to make my Car class objects iterable then I would like to print the names of them in the main function.

The code example is the following:

__author__ = 'mirind4'

class IterableCar(type):
    def __iter__(self):
        return iter(self.__name__)

class Car(object):
    __metaclass__ = IterableCar

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


if __name__=='__main__':

    car1 = Car('Mercedes')
    car2 = Car('Toyota')
    for cars in Car:
        print (cars.name)

But unfortunately I got an TypeError:

TypeError: 'type' object is not iterable

Would you be so kind as to tell me where I do the mistake in my code? So far I have checked similar problem-questions over this site and internet but I do not know what the problem is. I am using python 3.4. Thanks in advance!

Celom answered 2/9, 2015 at 20:5 Comment(9)
You have for cars in Car. Car is a class definition. What do you expect that to do? Did you mean to put car1 and car2 into a list and iterate over that? (I see that you have something at the top of your question about "making a class iterable" but it's not clear what you want. Car doesn't seem to represent any kind of collection.)Tenderloin
@Two-BitAlchemist -- I think that OP wants it to yield C - A - R ... Interesting concept making an iterable class...Gurtner
@Gurtner I would actually guess that the OP wants it to return every instance so far created, which I doubt is possible, but we shall see.Tenderloin
In Python 3, you define a metaclass with class Car(metaclass=IterableCar), not with the __metaclass__ attribute.Avisavitaminosis
@Two-BitAlchemist It's possible, in the sense that you just need to maintain such a list yourself with the class's __new__ method.Avisavitaminosis
@Avisavitaminosis Would that catch all the subclasses and mixins and such?Tenderloin
Assuming the metaclass is providing the definition of __new__, I believe so.Avisavitaminosis
Two-BitAlchemist is right, I want it to return every instance so far created. Sorry for my unclear explanation...Celom
Thank you very much, it works fine with the solution below. I appreciate your all help and comments! ;)Celom
G
4

As far as I can tell, making a class object iterable by using a metaclass works just fine:

from __future__ import print_function

class IterableCar(type):
    def __iter__(cls):
        return iter(cls.__name__)

class Car(object):
    __metaclass__ = IterableCar

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


if __name__=='__main__':

    car1 = Car('Mercedes')
    car2 = Car('Toyota')
    for cars in Car:
        print (cars)

Results in:

mgilson$ python ~/sandbox/test.py 
C
a
r

Here's an example where I actually track the cars generated:

from __future__ import print_function
import weakref

class IterableCar(type):

    _cars = weakref.WeakSet()

    def __iter__(cls):
        return iter(cls._cars)

    def add_car(cls, car):
        cls._cars.add(car)


class Car(object):
    __metaclass__ = IterableCar

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


if __name__=='__main__':

    car1 = Car('Mercedes')
    car2 = Car('Toyota')
    for cars in Car:
        print (cars.name)

Note that if you're using python3.x, to use a metaclass you do:

class Car(metaclass=IterableCar):
    ...

Rather than:

class Car(object):
    __metaclass__ = IterableCar

which likely explains the problem that you're experiencing.

Gurtner answered 2/9, 2015 at 20:12 Comment(7)
The weakref module can be used to fix the memory leak.Denotation
@DanD. -- Yep weakref.WeakSet is probably the easiest to use object in there that could help here, but that's out of the scope of this question I think ... (since we don't really even know what OP is trying to do...)Gurtner
@DanD. -- Updated with a WeakSetGurtner
@DanD. Unfortunately I do not understand why the program had memory-leak problem without the weakref module. Is Garbage Collector not working in this situation? To be honest I am still a bit beginner at this topic, sorry for that. Would you be so kind as to give me a short explanation of it? (I know what memory-leak is, but I do not know where is (was) it in mgilson's code). Many Thanks!Celom
@Celom -- If you keep a list of instances at the class level, there will always be a reference to every instance every created. Because of this, the reference count can never drop to 0 and so the object will never be able to be garbage collected. It's possible that is your desired behavior. With the WeakSet, when an object goes out of scope, it will be garbage collected and the class will stop tracking it.Gurtner
@Gurtner Thank you again for the explanation, it helped my thinking of this a lot! I am of the opinion that I should look for some articles and information about this "reference count" thing! There is always something to learn! Have a nice day! ;)Celom
@Gurtner I am still wondering this example, and unfortunately I am not completely understand it, sorry for it! :\ If you have a little time could you help me, please? In order to make the class iterable I have to make a metaclass. Till this point I got it. But when I iterate over them in the main function I use the "Car" class as the iterable instead of IterableCar class. What's exactly happening here? I've read articles of this metaclass thing, but it's still a bit vague for me :\ And again, sorry for my nooby questions...Celom
A
3

To track instances of the class that are created, we'll start by adding a _cars attribute to each the class created by the metaclass. This will be set of weak references, so that the class itself does not prevent unused instances from being garbage-collected.

class IterableCar(type):
    def __new__(meta, name, bases, attrs):
        attrs['_cars'] = weaker.WeakSet()
        return type.__new__(meta, name, bases, attrs)

To add the instances, we'll override __call__. Essentially, this is where you put code that you would ordinarily put in __new__ or __init__ when defining the class itself.

    def __call__(cls, *args, **kwargs):
        rv = type.__call__(cls, *args, **kwargs)
        cls._cars.add(rv)
        return rv

And to make the class iterable by iterating over its set of instances,

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

Any class using IterableCar will automatically track its instances.

class Car(metaclass=IterableCar):
    def __init__(self, name):
        self.name = name

car1 = Car('Mercedes')
car2 = Car('Toyota')
for cars in Car:
    print(cars.name)
Avisavitaminosis answered 2/9, 2015 at 20:57 Comment(3)
Nice. Now if you just make it a WeakSet to fix the "memory-leak", you'll be all set :-).Gurtner
And, I suppose for completeness, you'll want to add the __iter__ as well (which would iterate return iter(cls._cars))Gurtner
@Avisavitaminosis thank you for your detailed answer, I appreciate it! :)Celom
E
0

I had this error appear when a decorator was expecting a Tuple and I passed in a single element without a trailing comma.

@api_view(["GET"])
@authentication_classes((CustomAuthentication,))
@permission_classes((AdminPermission))
def order_attached_documents_from_order_uuid(request):
    # ...
    return Response(status=status.HTTP_200_OK)

After I changed @permission_classes((AdminPermission)) to @permission_classes((AdminPermission,)) everything started working just fine.

Endoparasite answered 11/8, 2021 at 23:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.