Улучшение __init__, где args назначаются непосредственно членам

Я нахожу, что пишу много классов с такими конструкторами:

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 

Если они кварты, вы можете сделать что-то вроде этого:

 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) 

Лично я придерживаюсь того, как вы сейчас это делаете, так как он гораздо менее хрупкий.

Рассмотрим следующий код с опечаткой:

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

Если вы используете свой оригинальный подход, при попытке создать объект вы получите следующее желаемое поведение:

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

С приближением kwargs это происходит:

 >>> myobject.fobar 3 

Это кажется мне источником ошибок, которые очень трудно найти.

Вы можете проверить список kwargs чтобы убедиться, что он имеет только ожидаемые значения, но к тому времени, когда вы это сделали, и работа по добавлению значений по умолчанию, я думаю, что это будет сложнее, чем ваш оригинальный подход.

вы можете сделать что-то вроде этого:

 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) 

Это похоже на namedtuple из collections Это значительно namedtuple ввод текста, чем определение по существу одного и того же метода __init__ снова и снова и не требует, чтобы вы namedtuple дерево наследования только для того, чтобы получить тот же метод, не перепечатывая его.

Если вам нужно добавить дополнительные методы, то вы можете просто создать базу

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

Это ужасный код.

 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] 

В шаблоне нет ничего плохого. Его легко читать и легко понять (гораздо больше, чем многие другие ответы здесь).

Я нахожу, что пишу много классов с такими конструкторами, как это

Если вам становится скучно печатать такие конструкторы, тогда решение должно сделать компьютер для вас. Например, если вы используете кодирование с pydev, вы можете нажать Ctrl + 1 , A, чтобы редактор сделал это за вас .

Это гораздо лучшее решение, чем тратить время на создание и отладку магического кода, который запутывает то, что вы на самом деле пытаетесь сделать, а именно присвоить значения некоторым переменным экземпляра.