Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improving __init__ where args are assigned directly to members

Tags:

python

I'm finding myself writing a lot of classes with constructors like this:

class MyClass(object):
    def __init__(self, foo, bar, foobar=1, anotherfoo=None):
        self.foo = foo
        self.bar = bar
        self.foobar = foobar
        self.anotherfoo = anotherfoo

Is this a bad code smell? Does Python offer a more elegant way of handling this?

My classes and even some of the constructors are more than just what I've shown, but I usually have a list of args passed to the constructor which just end up being assigned to similarly named members. I made some of the arguments optional to point out the problem with doing something like:

class MyClass(object):
    def __init__(self, arg_dict):
        self.__dict__ = arg_dict
like image 695
Colin Avatar asked Sep 10 '10 03:09

Colin


People also ask

What are the arguments of init () method?

The init () method arguments are optional. We can define a constructor with any number of arguments. Let’s look at some examples of the constructor function in different scenarios.

Which function is used to initialize the instance of the class?

Python class constructor function job is to initialize the instance of the class. Python __init__ () is the constructor function for the classes in Python. 1. Class with No Constructor

What is the use of init () in Python?

It binds the instance to the init () method. It’s usually named “self” to follow the naming convention. You can read more about it at Python self variable. The init () method arguments are optional. We can define a constructor with any number of arguments.

How to handle ARGs in Python?

3 Ways to Handle Args in Python. A Straight to the Point Guide | by Dardan Xhymshiti | Towards Data Science The sys module in Python has the argv functionality. This functionality returns a list of all command-line arguments provided to the main.py when triggering an execution of it through terminal.


3 Answers

If they're kwargs, you could do something like this:

def __init__(self, **kwargs):
    for kw,arg in kwargs.iteritems():
        setattr(self, kw, arg)

posargs are a bit trickier since you don't get naming information in a nice way.

If you want to provide default values, you can do it like this:

def __init__(self, **kwargs):
    arg_vals = {
        'param1': 'default1',
        # ...
    }
    arg_vals.update(kwargs)
    for kw,arg in arg_vals.iteritems():
        setattr(self, kw, arg)
like image 60
Amber Avatar answered Nov 15 '22 10:11

Amber


Personally, I'd stick with the way you're currently doing it as it's far less brittle.

Consider the following code with a typo:

myobject = MyClass(foo=1,bar=2,fobar=3)

If you use your original approach you'll get the following desirable behaviour when you try to create the object:

TypeError: __init__() got an unexpected keyword argument 'fobar'

With the kwargs approach this happens:

>>> myobject.fobar
3

This seems to me the source of the kind of bugs that are very difficult to find.

You could validate the kwargs list to ensure it only has expected values, but by the time you've done that and the work to add default values I think it'll be more complex than your original approach.

like image 39
Dave Webb Avatar answered Nov 15 '22 11:11

Dave Webb


you could do something like this:

def Struct(name):
    def __init__(self, **fields):
        self.__dict__.update(fields)
    cls = type(name, (object, ), {'__init__', __init__})
    return cls

You would use it like:

MyClass = Struct('MyClass')
t = MyClass(a=1, b=2)

If you want positional argumentsas well, then use this:

def Struct(name, fields):
    fields = fields.split()
    def __init__(self, *args, **kwargs):
        for field, value in zip(fields, args):     
             self.__dict__[field] = value
        self.__dict__.update(kwargs)
    cls = type(name, (object, ), {'__init__': __init__})
    return cls

It's then used like

MyClass = Struct('MyClass', 'foo bar foobar anotherfoo')
a = MyClass(1, 2, foobar=3, anotherfoo=4)

This is similar to the namedtuple from collections This saves you a lot more typing than defining essentially the same __init__ method over and over again and doesn't require you to muddy up your inheritance tree just to get that same method without retyping it.

If you need to add additional methods, then you can just create a base

MyClassBase = Struct('MyClassBase', 'foo bar')
class MyClass(MyClassBase):
    def other_method(self):
        pass
like image 27
aaronasterling Avatar answered Nov 15 '22 09:11

aaronasterling