11

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!

mirind4
  • 1,423
  • 4
  • 21
  • 29
  • 2
    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.) – Two-Bit Alchemist Sep 02 '15 at 20:06
  • @Two-BitAlchemist -- I think that OP wants it to yield `C - A - R` ... Interesting concept making an iterable _class_... – mgilson Sep 02 '15 at 20:09
  • @mgilson 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. – Two-Bit Alchemist Sep 02 '15 at 20:09
  • 1
    In Python 3, you define a metaclass with `class Car(metaclass=IterableCar)`, not with the `__metaclass__` attribute. – chepner Sep 02 '15 at 20:13
  • @Two-BitAlchemist It's possible, in the sense that you just need to maintain such a list yourself with the class's `__new__` method. – chepner Sep 02 '15 at 20:19
  • @chepner Would that catch all the subclasses and mixins and such? – Two-Bit Alchemist Sep 02 '15 at 20:22
  • 1
    Assuming the metaclass is providing the definition of `__new__`, I believe so. – chepner Sep 02 '15 at 20:27
  • Two-BitAlchemist is right, I want it to return every instance so far created. Sorry for my unclear explanation... – mirind4 Sep 02 '15 at 20:49
  • Thank you very much, it works fine with the solution below. I appreciate your all help and comments! ;) – mirind4 Sep 02 '15 at 20:53

3 Answers3

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.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • The `weakref` module can be used to fix the memory leak. – Dan D. Sep 02 '15 at 20:26
  • @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...) – mgilson Sep 02 '15 at 20:28
  • @DanD. -- Updated with a `WeakSet` – mgilson Sep 02 '15 at 20:30
  • @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! – mirind4 Sep 04 '15 at 15:24
  • 2
    @mirind4 -- 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. – mgilson Sep 04 '15 at 15:27
  • @mgilson 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! ;) – mirind4 Sep 04 '15 at 15:33
  • @mgilson 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... – mirind4 Sep 07 '15 at 09:04
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)
chepner
  • 497,756
  • 71
  • 530
  • 681
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.