Making Python's __init__ method magical
blog.lerner.co.il
blog.lerner.co.il
from pprint import pprint
def default_init(init_func):
arg_count = init_func.func_code.co_argcount
arg_vars = init_func.func_code.co_varnames[1:1+arg_count]
def wrap_init(self, *args, **kwargs):
idx = 0
for arg in arg_vars:
if arg in kwargs:
setattr(self, arg, kwargs[arg])
else:
setattr(self, arg, args[idx])
idx += 1
init_func(self, *args, **kwargs)
return wrap_init
class testing(object):
@default_init
def __init__(self, a, b, c):
pass
x = testing(1, 2, 3)
pprint(x.a)
pprint(x.b)
pprint(x.c)
y = testing(1, 2, c=3)
pprint(y.a)
pprint(y.b)
pprint(y.c)
[note: I'm aware this does not do even remotely a complete job as a decorator. Use at your own risk, it is a demonstration only.]It's an interesting balance between one being more magical in its use of the system (inspecting live stack frames) and the other being more magical in appearance (having a function with just pass in it actually do something).
import inspect
def autoinit(decorated_init):
def _wrap(*args, **kwargs):
obj, nargs= args[0], args[1:]
names = decorated_init.func_code.co_varnames[1:len(nargs)+1]
nargs = {k: nargs[names.index(k)] for k in names}
kwa_keys = decorated_init.func_code.co_varnames[len(nargs)+1:]
kwa_defaults = inspect.getargspec(decorated_init).defaults
for k in kwa_keys:
nargs[k] = kwargs[k] if (k in kwargs) else kwa_defaults[kwa_keys.index(k)]
for k, v in nargs.iteritems():
setattr(obj, k, v)
return decorated_init(*args, **kwargs)
return _wrap
if __name__ == '__main__':
class Foo():
@autoinit
def __init__(self, a, b, x=0, y=0):
self.p = 'Hello, I am set inside the __init__'
self.s = self.a + self.b + self.x + self.y
f = Foo(1, 2, x=120, y=5)
for param in 'abxyps':
print param, '=>', getattr(f, param)
f = Foo(31, 33, x=64)
for param in 'abxyps':
print param, '=>', getattr(f, param)
f = Foo(7, 121)
for param in 'abxyps':
print param, '=>', getattr(f, param)> While many people think of __init__ as a constructor, that’s not really the case. Rather, __init__ is invoked on our new object after it has been created
I'm not aware of a language (though I don't claim to know them all!) in which this is not exactly the definition of a constructor. In C++, `operator new` does the allocation, the constructor is called after. In java it's similar. In ruby it's Class#alloc (which is called by Class#new as roughly `ClassName.alloc.tap{|o| o.initialize(*args); o}`)
So the reader is correct to interpret it as a constructor. Constructors initialize the state of an uninitialized object.
Edited to add: Reading around, it appears that smalltalk has no separation between allocation and construction, having only the equivalent to `operator new`/`Class#new`/`obj.__new`, with initialization also happening in overloads of that. So some confusion may stem from that. I do think the distinction is meaningful and important in languages that separate these two things out, though.
That said, I'm totally willing to believe that neither they nor I understood the difference between an "allocator" and a "constructor." I'll check into this further, both for myself and for the sake of future writing and lectures!
In C++ the heap object initialization goes like this:
Obj *obj = new Obj;
This calls either `Obj::operator new` or the globally scoped `::operator new`, which somehow obtains an area of memory at least sizeof(obj) bytes long and returns it. After that calls to the object's constructors are generated and called.`operator new` is an allocator and the constructor initializes the raw bytes returned from it.
You can actually do the process explicitly in its entirety like so using placement new, which is a special form that calls the constructors on an arbitrary location:
Obj *obj = ::operator new(sizeof(Obj);
new(obj) Obj;
You can't call constructors directly (except with a C++0x feature that lets you call them from a same-level constructor) because of the issue of how they're ordered based on the object graph. They have a precondition of their parent class having been fully constructed.[1] Note: I don't really mean this as a judgement, understanding of C++ internals are pretty poor in a lot of programmers and for pretty good reasons a lot of the time.
I am not trying to argue (on the contrary, I would defer to your expertise), but rather I mean to add a small data point of what perspective developers with my background may have.
I really should do something in smalltalk. I only know details about it from reading about it, really, I've never had an opportunity to use it directly, though I go out of my way to understand its influences on more contemporary languages.
That said, it seems to me that many Ruby developers (and to a more limited degree, Python developers) are learning Smalltalk nowadays, for the same reason as English speakers learn Latin: to understand the origins of the language, and thus get a deeper understanding of how it works.
class Autoinit(object):
def __call__(self, cls):
orig_init = cls.__init__
arg_count = orig_init.func_code.co_argcount
arg_vars = orig_init.func_code.co_varnames[1:1 + arg_count]
class Wrapped(cls):
def __init__(self, *args, **kwargs):
for pos, var in enumerate(arg_vars[:len(args)]):
setattr(self, var, args[pos])
for var in kwargs:
setattr(self, var, kwargs[var])
return orig_init(self, *args, **kwargs)
return Wrapped
if __name__ == '__main__':
@Autoinit()
class Test(object):
def __init__(self, a, b):
pass
t = Test(1, b=2)
print t.a, t.b
[Disclaimer: This is my first class decorator. Please correct me if I'm wrong.] class Base(object):
def __init__(self, *args, **kws):
self.__dict__.update(kws)
class Foo(Base):
def__init__("foo", age=30):
self.name = "foo"
super(Foo, self).__init__("foo", age=30)
or you can do the same using a decorator for the init. def update(init_meth):
def wrapped(self, *args, **kws):
init_meth(self, *args, **kws)
self.__dict__.update(kws)
return wrapped
class Foo(object):
@update
def__init__("foo", age=30):
self.name = "foo"Then, I learned some Scala & Java. That's when I realized how for granted I took this thing called "reflection". Especially when it came to ORMs. Holy mackerel. I got over it but it is definitely a huge thing -- for better and worse -- that is so easy in Python that really spoils beginners.
You do see it used for mocking, ORMs, dependency injection, etc.
class Foo
constructor: (@x, @y) ->
@z = @x + @y
setFoo: (@foo) ->
Which compiles to: http://coffeescript.org/#try:class%20Foo%0A%20%20constructor... def __init__(self, self.x, self.y): # fail
pass
Did you mention this because there is an existing PEP that proposes this functionality?[1] https://mail.python.org/pipermail/python-dev/2005-July/05456...
[2] http://bytes.com/topic/python/answers/101594-__autoinit__-pr...
class A(object):
def __init__(self, x, y):
self.__dict__.update((k,v) for k,v in locals().items() if k != 'self')(see the examples at the bottom of the file)
This could also be tweaked to use __dict__ instead of __slots__ so it can act as an expando class which I believe is more similar to what the OP achieves.
def __init__(self, x, y):
self.x = x
self.y = y
is easy to read, easy to maintain, and not even that hard to write.I'm not sure you and I have the same meaning for that word.