Generic constructors and duplication of internal Python logic

Peter Otten __peter__ at web.de
Thu Apr 29 12:32:12 EDT 2004


Michele Simionato wrote:

> sdementen at hotmail.com (Sebastien de Menten) wrote in message
> news:<8dad5312.0404280217.21c2cfd1 at posting.google.com>...
>> Here is a metaclass for uber-lazy user :-)
>> 
>> Concretely, at the creation of the class it takes the source of the
>> __init__ function and add, at the first line of __init__, the line
>> that sets the attributes :
>> >         self.foo, self.bar, self.baz, self.optional1, self.optional2 =
>> >                    foo, bar, baz, optional1, optional2
>> 
> 
> Unfortunately this approach does not work if the source is not available
> (this happens when you are in the interpreter, or when you only have a
> .pyc file). I was playing this kind of tricks some time ago, then I
> decided that it was best to switch to Lisp/Scheme for this kind of stuff
> ;)

Here's a variant that operates on the byte code:

import opcode

class List(list):
    def ensure(self, value):
        try:
            return self.index(value)
        except ValueError:
            self.append(value)
            return len(self)-1

class Recorder(object):
    def __init__(self, code):
        self.func_code = code
        self._code = map(ord, code.co_code)[:-4]
        self._names = List(code.co_names)

    def __getattr__(self, name):
        opc = opcode.opmap[name.upper()]
        def record(self, arg=None):
            # XXX limit name resolution/addition to the proper opcodes
            if isinstance(arg, str):
                arg = self._names.ensure(arg)
            self._code.append(opc)
            if arg is not None:
                self._code.append(arg & 0xff)
                self._code.append(arg >> 8)
        setattr(self.__class__, name, record)
        return getattr(self, name)

    def code(self):
        return ''.join(map(chr, self._code))
    def names(self):
        return tuple(self._names)

def autoinit(f):
    co = f.func_code

    r = Recorder(co)
    for i in range(1, co.co_argcount):
        r.load_fast(i)
        r.load_fast(0) # self
        r.store_attr(co.co_varnames[i])
    r.load_const(0) # None
    r.return_value()

    new_names = r.names()
    new_code = r.code()

    codeobj = type(co)(co.co_argcount, co.co_nlocals, co.co_stacksize,
                    co.co_flags, new_code, co.co_consts, new_names,
                    co.co_varnames, co.co_filename, co.co_name,
                    co.co_firstlineno, co.co_lnotab, co.co_freevars,
                    co.co_cellvars)
    return type(f)(codeobj, f.func_globals, f.func_name, f.func_defaults,
                    f.func_closure)

class AutoInit(type):
    def __new__(cls, classname, bases, classdict):
        classdict["__init__"] = autoinit(classdict["__init__"])
        return type.__new__(cls, classname, bases, classdict)

class Demo(object):
    __metaclass__ = AutoInit
    def __init__(self, baz, top, foo=3, r=None):
        if r is None:
            r = ["default"]
        foo *= 2
        baz *= 3
        helper = 42 #ignored

    def __str__(self):
        return ("Demo(baz=%(baz)r, top=%(top)r, foo=%(foo)r, r=%(r)r)"
            % self.__dict__)


if __name__ == "__main__":
    print Demo(1, 2)
    print Demo(10, 20, 30, r=["other"])
    print Demo(100, foo="other", top=200)

I guess that was just a complicated way to fail the sanity check :)

Peter




More information about the Python-list mailing list