3

This is an unusual question, but I'd like to dynamically generate the __slots__ attribute of the class based on whatever attributes I happened to have added to the class.

For example, if I have a class:

class A(object):
    one = 1
    two = 2

    __slots__ = ['one', 'two']

I'd like to do this dynamically rather than specifying the arguments by hand, how would I do this?

8
  • This smells of premature optimisation. Can you discuss your use case a bit more? Commented Mar 20, 2009 at 17:35
  • This doesn't make a lot of sense -- attributes are already defined dynamically, at run time. Why mess with slots? What are you planning to do with slots? Commented Mar 20, 2009 at 18:01
  • I've done a load of profiling and there's room for improving on the design I think. For 100,000 class instances, I want to remove the ability to dynamically defined attributes so the classes are more lightweight. Commented Mar 20, 2009 at 18:11
  • Also, the 'attributes' are a little more complicated than this example and contain objects that can be the same for every instance of the class (rather than instantiating one for each instance). Commented Mar 20, 2009 at 18:12
  • For high performance with 100,000 class instances, you might consider using Flyweight design pattern -- the attributes aren't in the class instance, they're simple tuples and the class is basically all static methods. Commented Mar 20, 2009 at 18:17

2 Answers 2

3

At the point you're trying to define slots, the class hasn't been built yet, so you cannot define it dynamically from within the A class.

To get the behaviour you want, use a metaclass to introspect the definition of A and add a slots attribute.

class MakeSlots(type):

    def __new__(cls, name, bases, attrs):
        attrs['__slots__'] = attrs.keys()

        return super(MakeSlots, cls).__new__(cls, name, bases, attrs)

class A(object):
    one = 1
    two = 2

    __metaclass__ = MakeSlots
Sign up to request clarification or add additional context in comments.

3 Comments

Two problems with this answer: 1) it doesn't work; 2) even if it did work, it will also add methods, classes, everything that is defined in the class body into __slots__. It doesn't work because the names, besides being in the newly created __slots__, are also in the class overriding the __slots__ memberdescriptors, and what we end up with is AttributeError: 'A' object has no attribute 'one'
Argh -- above should be AttributeError: 'A' object attribute 'one' is read-only
While it's true that the class hasn't been built yet, you can still do it during class creation: use vars(), and do it last.
1

One very important thing to be aware of -- if those attributes stay in the class, the __slots__ generation will be useless... okay, maybe not useless -- it will make the class attributes read-only; probably not what you want.

The easy way is to say, "Okay, I'll initialize them to None, then let them disappear." Excellent! Here's one way to do that:

class B(object):
    three = None
    four = None

    temp = vars()                    # get the local namespace as a dict()
    __slots__ = temp.keys()          # put their names into __slots__
    __slots__.remove('temp')         # remove non-__slots__ names
    __slots__.remove('__module__')   # now remove the names from the local
    for name in __slots__:           # namespace so we don't get read-only
        del temp[name]               # class attributes
    del temp                         # and get rid of temp

If you want to keep those initial values it takes a bit more work... here's one possible solution:

class B(object):
    three = 3
    four = 4

    def __init__(self):
        for key, value in self.__init__.defaults.items():
            setattr(self, key, value)

    temp = vars()
    __slots__ = temp.keys()
    __slots__.remove('temp')
    __slots__.remove('__module__')
    __slots__.remove('__init__')
    __init__.defaults = dict()
    for name in __slots__:
        __init__.defaults[name] = temp[name]
        del temp[name]
    del temp

As you can see, it is possible to do this without a metaclass -- but who wants all that boilerplate? A metaclass could definitely help us clean this up:

class MakeSlots(type):
    def __new__(cls, name, bases, attrs):
        new_attrs = {}
        new_attrs['__slots__'] = slots = attrs.keys()
        slots.remove('__module__')
        slots.remove('__metaclass__')
        new_attrs['__weakref__'] = None
        new_attrs['__init__'] = init = new_init
        init.defaults = dict()
        for name in slots:
            init.defaults[name] = attrs[name]
        return super(MakeSlots, cls).__new__(cls, name, bases, new_attrs)

def new_init(self):
    for key, value in self.__init__.defaults.items():
        setattr(self, key, value)

class A(object):
    __metaclass__ = MakeSlots

    one = 1
    two = 2


class B(object):
    __metaclass__ = MakeSlots

    three = 3
    four = 4

Now all the tediousness is kept in the metaclass, and the actual class is easy to read and (hopefully!) understand.

If you need to have anything else in these classes besides attributes I strongly suggest you put whatever it is in a mixin class -- having them directly in the final class would complicate the metaclass even more.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.