是否有一个普遍接受的最佳实践来创建一个实例将具有许多(不可违约)变量的类?
例如,通过显式参数:
class Circle(object):
def __init__(self,x,y,radius):
self.x = x
self.y = y
self.radius = radius
Run Code Online (Sandbox Code Playgroud)
使用**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']
Run Code Online (Sandbox Code Playgroud)
或使用属性:
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
Run Code Online (Sandbox Code Playgroud)
对于实现少量实例变量的类(如上面的示例),似乎自然的解决方案是使用显式参数,但随着变量数量的增加,这种方法很快变得不可靠.当实例变量的数量变得冗长时,是否有一种首选方法?
lem*_*ead 10
我相信在这方面有许多不同的思想流派,通过这里我通常会想到它:
这应该是你的第一次攻击方法.但是,如果您发现您传递的内容列表过长,则可能表明代码存在更多结构性问题.你传递的这些东西中的一些是否存在共同点?你能把它封装在一个单独的对象中吗?有时我会使用配置对象,然后你会从一个巨大的args传递到传递1或2
例如
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)
Run Code Online (Sandbox Code Playgroud)
取而代之的是
def do_it(a, b, **kwargs):
# do some stuff with a and b
# ...
get_er_done(abcombo, **kwargs)
Run Code Online (Sandbox Code Playgroud)
在这样的情况下更清洁,并且可以看到get_er_done完整的签名,虽然好的文档字符串也可以只列出所有的参数,就像它们是被接受的真实参数一样do_it
*args和**kwargs语法非常有用,但也可能超级危险且难以维护,因为你失去了可以传入的参数的显式特性.我通常喜欢在我有一个方法的情况下使用它们基本上只是另一个方法或系统的包装器,你只想在不重新定义所有内容的情况下通过,或者在需要预先过滤或使其更加动态的有趣情况下等等.如果你只是用它来隐藏一个事实,你有大量的论点和关键字参数,**kwargs可能会通过使你的代码更加笨拙和晦涩来加剧问题.
__init__的我实际上非常喜欢利用getter和setter属性,特别是当我使用那些我不想公开的属性的私有版本做一些棘手的事情时.它也可以用于配置对象和其他东西,并且很好,很明确,我喜欢.但是,如果我正在初始化一个对象,我不想让半成形的对象四处走动并且它们没有任何用处,那么最好只使用显式的参数和关键字参数.
**kwargs和属性具有很好的特定用例,但只要在实际/可能的情况下坚持使用显式关键字参数.如果实例变量太多,请考虑将您的类拆分为分层容器对象.
与任何面向对象的编程语言一样,向 传递参数__init__通常是最佳实践。在您的示例中,setter/getters 将允许对象处于这种奇怪的状态,其中它还没有任何属性。
指定参数或使用**kwargs取决于具体情况。这是一个很好的经验法则:
**kwargs这是一个很好的解决方案,因为它避免了如下代码:def __init__(first, second, third, fourth, fifth, sixth, seventh,
ninth, tenth, eleventh, twelfth, thirteenth, fourteenth,
...
)
Run Code Online (Sandbox Code Playgroud)
**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)
Run Code Online (Sandbox Code Playgroud)
避免写:
class Child:
def __init__(self, many, arguments, here, extra):
self.extra = extra
super().__init__(many, arguments, here)
Run Code Online (Sandbox Code Playgroud)
对于所有其他情况,指定参数更好,因为它允许开发人员使用位置参数和命名参数,如下所示:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
Run Code Online (Sandbox Code Playgroud)
可以通过Point(1, 2)或实例化Point(x=1, y=2)。
对于一般知识,您可以查看namedtuple它是如何使用的。
在没有真正了解你的具体情况的情况下,经典的答案就是这样:如果你的类初始化程序需要一大堆参数,那么它可能做得太多了,它应该被考虑到几个类中.
拿一个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
Run Code Online (Sandbox Code Playgroud)
显然,更好的方法是定义Engine,Tire和Paint
类(或namedtuples)并将这些实例传递到Car():
class Car:
def __init__(self, tire, paint, engine):
self.tire = tire
self.paint = paint
self.engine = engine
Run Code Online (Sandbox Code Playgroud)
如果需要某些东西来创建一个类的实例,例如,radius在你的Circle类中,它应该是一个必需的参数__init__(或者被分解成一个较小的类,它被传入__init__或由另一个构造函数设置).原因是:IDE,自动文档生成器,代码自动完成器,linters等可以读取方法的参数列表.如果只是**kwargs,那里没有任何信息.但如果它具有您期望的参数的名称,那么这些工具可以完成它们的工作.
现在,属性很酷,但我会毫不犹豫地使用它们直到必要时(你会知道它们何时是必要的).保留您的属性,让人们直接访问它们.如果不应该设置或更改它们,请记录它.
最后,如果你真的必须拥有一大堆论点,但又不想写一堆作业__init__,你可能会对Alex Martelli 对相关问题的回答感兴趣.