开发者

Improving __init__ where args are assigned directly to members

开发者 https://www.devze.com 2023-01-15 19:45 出处:网络
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):

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消