改进__init__,其中参数直接分配给成员

6

我发现自己写了很多像这样的构造函数类:

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

这是一种糟糕的代码习惯吗?Python是否提供了更优雅的处理方式?

我的类甚至一些构造函数不仅仅是我展示的内容,但通常我会有一个参数列表传递给构造函数,最终只是被赋值给同名的成员变量。我将其中一些参数设置为可选项,以指出像下面这样做的问题:

class MyClass(object):
    def __init__(self, arg_dict):
        self.__dict__ = arg_dict

除非你预见到参数列表会比现在长很多,或者你需要将任意一组参数始终转换为属性(这种情况下我会强制使用kwargs),否则我认为这样做没有任何问题。 在大多数情况下,我自己也是这样做的,这样阅读起来很顺畅,而且这本来就是__init__方法的一个本意。我认为这一点都不可疑。 - user349594
另请参见https://dev59.com/q3A65IYBdhLWcg3wvxaE。 - FMc
5个回答

8
如果它们是kwargs,你可以像这样做:

如果它们是kwargs,你可以像这样做:

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

因为你不能很好地获取命名信息,所以posargs有点棘手。

如果您想提供默认值,则可以像这样执行:

def __init__(self, **kwargs):
    arg_vals = {
        'param1': 'default1',
        # ...
    }
    arg_vals.update(kwargs)
    for kw,arg in arg_vals.iteritems():
        setattr(self, kw, arg)

2

个人而言,我建议你继续采用现有的方式,因为它更加灵活。

考虑以下带有拼写错误的代码:

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

如果您使用原始方法创建对象,当您尝试创建对象时,您将获得以下理想的行为:
TypeError: __init__() got an unexpected keyword argument 'fobar'

使用kwargs方法会发生以下情况:
>>> myobject.fobar
3

我认为这是很难发现的错误源。

你可以验证 kwargs 列表,确保它只有预期值,但在你完成这个工作并添加默认值之前,我认为它比你最初的方法更加复杂。


1
你可以像这样做:
def Struct(name):
    def __init__(self, **fields):
        self.__dict__.update(fields)
    cls = type(name, (object, ), {'__init__', __init__})
    return cls

你可以像这样使用它:

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

如果您想要使用位置参数,可以使用以下代码:
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

然后它就像这样被使用

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

这类似于collections中的namedtuple。这比一遍又一遍地定义基本相同的__init__方法节省了大量的打字时间,而且不需要你为了获得相同的方法而弄乱你的继承树。

如果您需要添加其他方法,那么您只需创建一个基础类即可。

MyClassBase = Struct('MyClassBase', 'foo bar')
class MyClass(MyClassBase):
    def other_method(self):
        pass

0

这个模式没有问题。它易于阅读和理解(比其他答案要容易得多)。

我发现自己写了很多像这样的构造函数

如果你已经厌倦了输入这样的构造函数,那么解决方案就是让计算机为你完成。例如,如果你正在使用pydev编码,你可以按下Ctrl+1A让编辑器为你完成

这比花时间编写和调试魔法代码要好得多,因为魔法代码会混淆你真正想做的事情,即给一些实例变量赋值。


a) 代码并不是那么神奇 b) 复制粘贴编程很糟糕(几乎值得-1分) c) 如果解决这个问题的代码需要调试,那么这样做是一个好的练习(也就是说,你应该能够第一次就把它搞对)。 - aaronasterling

0

这是糟糕的代码。

class MyClass(object):
    def __init__(self, foo, bar, spam, eggs):
        for arg in self.__init__.func_code.co_varnames:
            setattr(self, arg, locals()[arg])

然后,你可以做类似这样的事情:

myobj = MyClass(1, 0, "hello", "world")
myletter = myobj.spam[myobj.bar]

4
请注意,对于这种情况,使用kwargs可能更易读,以便稍后维护代码的人 - 在阅读代码时看到MyClass(1, 4, 6, 2)之类的东西真的很烦人。 - Amber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接