是否有一种普遍接受的最佳实践来创建一个实例将具有许多(不可违约)变量的类?
例如,通过显式参数:
class Circle(object):
def __init__(self,x,y,radius):
self.x = x
self.y = y
self.radius = radius
使用** kwargs:
class Circle(object):
def __init__(self, **kwargs):
if 'x' in kwargs:
self.x = kwargs['x']
if 'y' in kwargs:
self.y = kwargs['y']
if 'radius' in kwargs:
self.radius = kwargs['radius']
或使用属性:
class Circle(object):
def __init__(self):
pass
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
self._radius = value
对于实现少量实例变量的类(如上例所示),似乎自然的解决方案是使用显式参数,但随着变量数量的增加,这种方法很快变得不可靠。当实例变量的数量增长很长时,是否有一种首选方法?
答案 0 :(得分:9)
我确信在这方面有许多不同的思想流派,在这里我通常会想到它:
这应该是你的第一次攻击方法。但是,如果您发现您传递的内容列表过长,则可能表明代码存在更多结构性问题。你传递的这些东西中的一些是否存在共同点?你能把它封装在一个单独的对象中吗?有时候我已经使用了配置对象,然后你会从一个非常多的args传递到传递1或2
e.g。
def do_it(a, b, thing=None, zip=2, zap=100, zimmer='okay', zammer=True):
# do some stuff with a and b
# ...
get_er_done(abcombo, thing=thing, zip=zip, zap=zap, zimmer=zimmer, zammer=zammer)
取而代之的是:
def do_it(a, b, **kwargs):
# do some stuff with a and b
# ...
get_er_done(abcombo, **kwargs)
在这种情况下更加清晰,并且可以看到get_er_done
的完整签名,虽然好的文档字符串也可以列出所有参数,好像它们是do_it
接受的真实参数
* args和** kwargs语法非常有用,但也可能超级危险且难以维护,因为你失去了可传入的参数的显式特性。我通常喜欢在我拥有的情况下使用它们一个基本上只是另一个方法或系统的包装器的方法,你想要在不重新定义所有内容的情况下传递信息,或者在需要预先过滤或更加动态化的参数的有趣情况下等等。如果你只是使用它来隐藏你有大量参数和关键字参数这一事实,** kwargs可能会让你的代码变得更加笨拙和神秘,从而加剧了这个问题。
__init__
我实际上非常喜欢利用getter和setter属性,特别是当我使用那些我不想公开的属性的私有版本做一些棘手的事情时。它也可以用于配置对象和其他东西,并且很好,很明确,我喜欢。但是,如果我正在初始化一个对象,我不想让半成形的对象四处走动并且它们没有任何用处,那么最好只使用显式的参数和关键字参数。
** kwargs和属性具有很好的特定用例,但只要在实际/可能的情况下坚持使用显式关键字参数。如果实例变量太多,请考虑将您的类拆分为分层容器对象。
答案 1 :(得分:5)
如果没有真正了解你的具体情况,那么经典的答案是这样的:如果你的类初始化程序需要一大堆参数,那么可能做得太多了,它应该被考虑在内几节课。
定义一个Car
类:
class Car:
def __init__(self, tire_size, tire_tread, tire_age, paint_color,
paint_condition, engine_size, engine_horsepower):
self.tire_size = tire_size
self.tire_tread = tire_tread
# ...
self.engine_horsepower = engine_horsepower
显然,更好的方法是定义Engine
,Tire
和Paint
类(或namedtuple
s)并将这些的实例传递给Car()
:
class Car:
def __init__(self, tire, paint, engine):
self.tire = tire
self.paint = paint
self.engine = engine
如果需要来创建类的实例,例如,radius
类中的Circle
,它应该是{{1}的必需参数(或者被分解为一个较小的类,它传递给__init__
,或者由另一个构造函数设置)。原因是:IDE,自动文档生成器,代码自动完成器,短接器等可以读取方法的参数列表。如果它只是__init__
,则那里没有任何信息。但如果它具有您期望的参数的名称,那么这些工具可以完成它们的工作。
现在,属性非常酷,但在必要时我会毫不犹豫地使用它们(并且您知道何时需要它们)。保留您的属性,让人们直接访问它们。如果不应该设置或更改它们,请记录下来。
最后,如果确实必须有一大堆参数,但又不想在**kwargs
中写一堆作业,那么您可能会对Alex感兴趣马尔泰利answer提到相关问题。
答案 2 :(得分:2)
将参数传递给__init__
通常是最好的做法,就像任何面向对象的编程语言一样。在你的例子中,setter / getters允许对象处于这种奇怪的状态,它还没有任何属性。
指定参数或使用**kwargs
取决于具体情况。这是一个很好的经验法则:
**kwargs
是一个很好的解决方案,因为它避免了这样的代码:def __init__(first, second, third, fourth, fifth, sixth, seventh,
ninth, tenth, eleventh, twelfth, thirteenth, fourteenth,
...
)
**kwargs
是最佳解决方案:class Parent:
def __init__(self, many, arguments, here):
self.many = many
self.arguments = arguments
self.here = here
class Child(Parent):
def __init__(self, **kwargs):
self.extra = kwargs.pop('extra')
super().__init__(**kwargs)
避免写作:
class Child:
def __init__(self, many, arguments, here, extra):
self.extra = extra
super().__init__(many, arguments, here)
对于所有其他情况,指定参数更好,因为它允许开发人员使用位置参数和命名参数,如下所示:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
可以Point(1, 2)
或Point(x=1, y=2)
实例化。
一般知识,you can see how namedtuple
does it并使用它。
答案 3 :(得分:0)
您的第二种方法可以用更优雅的方式编写:
class A:
def __init__(self, **kwargs):
self.__dict__ = {**self.__dict__, **kwargs}
a = A(x=1, y=2, verbose=False)
b = A(x=5, y=6, z=7, comment='bar')
print(a.x + b.x)
但是所有已经提到的缺点仍然存在...