4

Many times I have member functions that copy parameters into object's fields. For Example:

class NouveauRiches(object):
  def __init__(self, car, mansion, jet, bling):
    self.car = car
    self.mansion = mansion
    self.jet = jet
    self.bling = bling

Is there a python language construct that would make the above code less tedious? One could use *args:

def __init__(self, *args):
  self.car, self.mansion, self.jet, self.bling = args

+: less tedious

-: function signature not revealing enough. need to dive into function code to know how to use function

-: does not raise a TypeError on call with wrong # of parameters (but does raise a ValueError)

Any other ideas? (Whatever your suggestion, make sure the code calling the function does stays simple)

bandana
  • 3,422
  • 6
  • 26
  • 30
  • 1
    I personally don't find the first *too* tedious. As a matter of fact, I kind of prefer that type of code as it offers more clarity. You can pretty much be a dummy and still be able to understand exactly what's going on there. But maybe that's an extreme view of "Explicit is better than implicit". Although I do agree that it would be nice to have a more beautiful, yet still explicit way of assigning the parameters. Maybe you should write a PEP! – Wayne Werner May 26 '10 at 12:18
  • @bandana: function signature isn't supposed to reveal how to use a function, why don't you provide a `__doc__` string for your function? You could raise `TypeError` yourself if it is an issue. You could also use keyword arguments, to be more explicit – SilentGhost May 26 '10 at 12:21
  • possible duplicate of [Python: Automatically initialize instance variables?](http://stackoverflow.com/questions/1389180/python-automatically-initialize-instance-variables) – SilentGhost May 26 '10 at 12:24
  • @SilentGhost: it's a duplicate alright. it's a duplicate alright. i donno how to close my own question – bandana May 26 '10 at 12:34
  • @bandana: do you see `close` link under the tags? – SilentGhost May 26 '10 at 12:36
  • @SilentGhost: nope. maybe its cause i didnt fully register – bandana May 26 '10 at 12:37
  • ah, you don't have enough rep – SilentGhost May 26 '10 at 12:40
  • You should use Nadia's code from a very similar question I've asked: http://stackoverflow.com/questions/1389180/python-automatically-initialize-instance-variables/1389216#1389216 – Adam Matan Jul 08 '10 at 12:48

7 Answers7

2

I would go for this, also you could override already defined properties.

class D:
  def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

But i personally would just go the long way. Think of those:

- Explicit is better than implicit.
- Flat is better than nested.
(The Zen of Python)
evilpie
  • 2,718
  • 20
  • 21
  • I don't like this because it won't work with properties or other descriptors -- `self.__dict__.update()` skips all these aspects of objects which you only get with `setattr()`. – Ian Bicking May 26 '10 at 20:11
2

You could do this with a helper method, something like this:

import inspect

def setargs(func):
    f = inspect.currentframe(1)
    argspec = inspect.getargspec(func)
    for arg in argspec.args:
        setattr(f.f_locals["self"], arg, f.f_locals[arg])

Usage:

class Foo(object):

    def __init__(self, bar, baz=4711):
        setargs(self.__init__)

        print self.bar # Now defined
        print self.baz # Now defined

This is not pretty, and it should probably only be used when prototyping. Please use explicit assignment if you plan to have others read it.

It could probably be improved not to need to take the function as an argument, but that would require even more ugly hacks and trickery :)

mthurlin
  • 26,247
  • 4
  • 39
  • 46
  • Seems unnecessarily convoluted, and also sets self.self. The only advantage I see over `self.__dict__.update(locals())` is that it might be less likely to break if Python ever adds `__foo__` to the output of. On the other hand, it "relies on Python stack frame support in the interpreter, which isn’t guaranteed to exist in all implementations of Python" and makes a reference cycle between stack frames, which is warned against in the docs. – tc. Jun 02 '10 at 12:03
1

Try something like

d = dict(locals())
del d['self']
self.__dict__.update(d)

Of course, it returns all local variables, not just function arguments.

tc.
  • 33,468
  • 5
  • 78
  • 96
1

I am not sure this is such a good idea, but it can be done:

import inspect
class NouveauRiches(object):
    def __init__(self, car, mansion, jet, bling):
        arguments = inspect.getargvalues(frame)[0]
        values = inspect.getargvalues(frame)[3];
        for name in arguments:
            self.__dict__[name] = values[name]

It does not read great either, though I suppose you could put this in a utility method that is reused.

Tendayi Mawushe
  • 25,562
  • 6
  • 51
  • 57
0

You could try something like this:

class C(object):
    def __init__(self, **kwargs):
        for k in kwargs:
            d = {k: kwargs[k]}
            self.__dict__.update(d)

Or using setattr you can do:

class D(object):
    def __init__(self, **kwargs):
        for k in kwargs:
            setattr(self, k, kwargs[k])

Both can then be called like:

myclass = C(test=1, test2=2)

So you have to use **kwargs, rather than *args.

Peter Farmer
  • 9,020
  • 5
  • 27
  • 29
0

I sometimes do this for classes that act "bunch-like", that is, they have a bunch of customizable attributes:

class SuperClass(object):
    def __init__(self, **kw):
        for name, value in kw.iteritems():
            if not hasattr(self, name):
                raise TypeError('Unexpected argument: %s' % name)
            setattr(self, name, value)

class SubClass(SuperClass):
    instance_var = None # default value

class SubClass2(SubClass):
    other_instance_var = True

    @property
    def something_dynamic(self):
        return self._internal_var

    @something_dynamic.setter # new Python 2.6 feature of properties
    def something_dynamic(self, value):
        assert value is None or isinstance(value, str)
        self._internal_var = value

Then you can call SubClass2(instance_var=[], other_instance_var=False) and it'll work without defining __init__ in either of them. You can use any property as well. Though this allows you to overwrite methods, which you probably wouldn't intend (as they return True for hasattr() just like an instance variable).

If you add any property or other other descriptor it will work fine. You can use that to do type checking; unlike type checking in __init__ it'll be applied any time that value is updated. Note you can't use any positional arguments for these unless you override __init__, so sometimes what would be a natural positional argument won't work. formencode.declarative covers this and other issues, probably with a thoroughness I would not suggest you attempt (in retrospect I don't think it's worth it).

Note that any recipe that uses self.__dict__ won't respect properties and descriptors, and if you use those together you'll just get weird and unexpected results. I only recommend using setattr() to set attributes, never self.__dict__.

Also this recipe doesn't give a very helpful signature, while some of the ones that do frame and function introspection do. With some work it is possible to dynamically generate a __doc__ that clarifies the arguments... but again I'm not sure the payoff is worth the addition of more moving parts.

Ian Bicking
  • 9,762
  • 6
  • 33
  • 32
0

I am a fan of the following

import inspect

def args_to_attrs(otherself):
    frame = inspect.currentframe(1)
    argvalues = inspect.getargvalues(frame)

    for arg in argvalues.args:
        if arg == 'self':
            continue
        value = argvalues.locals[arg]
        setattr(otherself, arg, value)

class MyClass:
    def __init__(self, arga="baf", argb="lek", argc=None):
        args_to_attrs(self)

Arguments to __init__ are explicitly named, so it is clear what attributes are being set. Additionally, it is a little bit streamlined over the currently accepted answer.

user7610
  • 25,267
  • 15
  • 124
  • 150