Welcome, guest | Sign In | My Account | Store | Cart

There was recently a short discussion about some way to make certain initialization of instances easier (less typing). This was then followed by an expression of a desire for the automatic generation of the __slots__ attribute for classes (which is generally painful to do by hand for any nontrivial class). This recipe defines a metaclass and a function. The 'AutoSlots' metaclass automatically generates a __slots__ attribute during compilation, and the 'InitAttrs' function initializes instance variables during runtime.

A variant which only requires the metaclass would be convenient and would be graciously accepted.

Python, 52 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class AutoSlots(type):
    def __new__(cls, name, bases, dct):
        slots = dict.fromkeys(dct.get('__slots__', []))
        if '__init__' in dct:
            init = dct['__init__']
            ifvn = init.func_code.co_varnames
            for i in xrange(init.func_code.co_argcount):
                x = ifvn[i]
                if x[:1] == '_':
                    slots[x[1:]] = None
        dct['__slots__'] = slots.keys()
        return type.__new__(cls, name, bases, dct)

def InitAttrs(ob, args):
    for k, v in args.iteritems():
        if k[:1] == '_':
            setattr(ob,k[1:],v)

'''
If you would like to automatically assign arguments to methods
to the attributes of an object, you can do so by prefixing those
local variable names with a a single leading underscore '_',
which will be stripped from the instance variable (_x becomes x).


class Foo(object):
    def __init__(self, _x, _y, _z):
        InitAttrs(self, locals())
        #...


AutoSlots further allows you to automatically generate __slots__
for your classes, pulling the variables which need to be slots
from the arguments to the __init__ method on your class.

class Goo(object):
    __metaclass__ = AutoSlots
    def __init__(self, _x, _y, _z):
        InitAttrs(self, locals())
        #...


>>> x = Goo(1,2,3)
>>> [i for i in dir(x) if i[:1] != '_']
['x', 'y', 'z']
>>> x.a = 7
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'Goo' object has no attribute 'a'
>>>

'''

This is a fixed version of the AutoSlots metaclass that I originally posted on python-dev.

2 comments

Bernhard Mulder 16 years, 8 months ago  # | flag

Using assignment statements to construct __slot__. Here is a variant of the idea which looks at the assignment statements in the __init__ function.

To use this metaclass, inherit from the class Autoslots.

import inspect, _ast
class Autoslots_meta(type):
    """
    Looks for assignments in __init__
    and creates a __slot__ variable for all the
    instance attributes in the assignment.
    Assumes that all assignments in __init__ are of
    the form:
        self.attr =
    """
    def __new__(cls, name, bases, dct):
    slots = dct.get('__slots__', [])
    orig_slots = []
    for base in bases:
        if hasattr(base, "__slots__"):
        orig_slots += base.__slots__
    if '__init__' in dct:
        init = dct['__init__']
        initproc = type.__new__(cls, name, bases, dct)
        initproc_source = inspect.getsource(initproc)
        ast = compile(initproc_source, "dont_care", 'exec', _ast.PyCF_ONLY_AST)
        classdef = ast.body[0]
        stmts = classdef.body
        for declaration in stmts:
        if isinstance(declaration, _ast.FunctionDef):
            name = declaration.name
            if name == '__init__':
            initbody = declaration.body
            for statement in initbody:
                if isinstance(statement, _ast.Assign):
                for target in statement.targets:
                    name = target.attr
                    if name not in orig_slots:
                    slots.append(name)
        dct['__slots__'] = slots
    return type.__new__(cls, name, bases, dct)

class Autoslots(object):
    __metaclass__ = Autoslots_meta

class TestClass(Autoslots):
    def __init__(self):
    self.a = 1
    self.b = 2

    def t(self):
    pass
Bernhard Mulder 16 years, 8 months ago  # | flag

Correction needed for previous comment. Oops. Accidentally changed the name of the class. One should replace name by something else in the loops, since name is already an input parameter.

Created by Josiah Carlson on Sat, 2 Jul 2005 (PSF)
Python recipes (4591)
Josiah Carlson's recipes (9)

Required Modules

  • (none specified)

Other Information and Tasks