Improving __init__ where args are assigned directly to members
I'm finding myself writing a lot of classes with constructors like this:
class MyClass(object):
def __init__(self, foo, bar, foobar=1, an开发者_开发问答otherfoo=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
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
This is horrible code.
class MyClass(object):
def __init__(self, foo, bar, spam, eggs):
for arg in self.__init__.func_code.co_varnames:
setattr(self, arg, locals()[arg])
Then, you can do something like:
myobj = MyClass(1, 0, "hello", "world")
myletter = myobj.spam[myobj.bar]
There's nothing wrong with the pattern. It's easy to read and easy to understand (much more so than many of the other answers here).
I'm finding myself writing a lot of classes with constructors like this
If you are getting bored typing up such constructors, then the solution is to make the computer do it for you. For example, if you happen to be coding with pydev, you can press Ctrl+1, A to make the editor do it for you.
This is a much better solution than spending time writing and debugging magic code that obfuscates what you're really trying to do, which is to assign values to some instance variables.
精彩评论