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
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.
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
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.
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.
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)
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With