我对Python比较陌生,并努力将语言的功能与我从C ++和Java背景中学到的习惯相协调。
我与封装有关的最新一期,特别是Meyer的“ Effective C ++ ”第23项总结的一个想法:
暂时忽略缺少friend
机制,是非成员函数,被认为优于Python中的成员函数吗?
强制性的,asinine例子:
class Vector(object):
def __init__(self, dX, dY):
self.dX = dX
self.dY = dY
def __str__(self):
return "->(" + str(self.dX) + ", " + str(self.dY) + ")"
def scale(self, scalar):
self.dX *= scalar
self.dY *= scalar
def scale(vector, scalar):
vector.dX *= scalar
vector.dY *= scalar
鉴于v = Vector(10, 20)
,我们现在可以调用v.scale(2)
或scale(v, 2)
来将向量的大小加倍。
考虑到我们在这种情况下使用属性的事实,两个选项中的哪一个 - ,如果有的话 - 更好,为什么?
答案 0 :(得分:16)
有趣的问题。
你从一个不同的地方开始,而不是来自Java程序员的大多数问题,这些问题往往假设你需要课程,而你大多数时候都不需要。通常,在Python中除非你专门进行数据封装,否则没有必要使用类。
当然,在你的例子中,你实际上是这样做的,因此使用类是合理的。就个人而言,我会说,因为你做有一个类,那么成员函数是最好的方法:你专门对那个特定的矢量实例进行操作,所以它是有意义的该函数是Vector的一种方法。
你可能想让它成为一个独立的函数(我们实际上并没有使用“成员”或“非成员”这个词),如果你需要让它适用于多个类,这些类不一定是继承自彼此或共同的基础。感谢duck-typing,这是很常见的做法:指定你的函数需要一个具有特定属性或方法集的对象,并对它们做些什么。
答案 1 :(得分:4)
免费功能使您可以灵活地为第一个参数使用duck-typing。
成员函数为您提供将功能与类相关联的表现力。
相应地选择。通常,函数创建相等,因此它们都应该对类的接口有相同的假设。发布免费功能scale
后,您有效地宣传.dX
和.dY
是Vector
公共界面的一部分。这可能不是你想要的。您正在这样做,以换取与具有.dX
和.dY
的其他对象重用相同功能的能力。这可能不会对你有价值。所以在这种情况下我肯定更喜欢成员函数。
对于喜欢自由函数的好例子,我们需要看看标准库:sorted
是一个自由函数,而不是list
的成员函数,因为从概念上你应该是能够创建排序任何可迭代序列的结果列表。
答案 2 :(得分:3)
首选非会员非朋友功能到会员功能
这是一种设计理念,可以而且应该扩展到所有OOP Paradigm编程语言。如果您了解这一点的本质,那么这个概念就很明确了
如果您不需要对类成员进行私有/受保护访问,则您的设计没有理由包含该类函数,该类成员。以其他方式来思考,在设计类时,在枚举所有属性之后,需要确定足以构成类的最小行为集。您可以使用任何可用的公共方法/成员函数编写的任何成员函数都应公开。
这在Python中适用多少
如果你小心的话,在某种程度上。与其他OOP语言(如Java / C ++)相比,Python支持更弱的封装,特别是因为没有私有成员。 (有一种称为私有变量的东西,程序员可以通过在变量名之前加上'_'来轻松编写。这通过名称修改功能变为私有类。)。因此,如果我们完全采用Scott Meyer的话,那么应该从Class访问什么以及应该从外部访问什么之间存在着薄弱的一面。最好由设计人员/程序员决定一个函数是否应该成为Class或Not的一个组成部分。我们可以轻松采用的一个设计原则"Unless your function required to access any of the properties of the class you can make it a non-member function".
答案 3 :(得分:2)
查看您自己的示例 - 非成员函数必须访问Vector类的数据成员。这不是封装的胜利。这尤其是因为它更改了传入的对象的数据成员。在这种情况下,返回缩放的向量可能会更好,并保持原始状态不变。
此外,您将不会发现使用非成员函数的类多态的任何好处。例如,在这种情况下,它仍然只能处理两个组件的向量。如果它使用向量乘法功能或使用方法迭代组件会更好。
总结:
答案 4 :(得分:1)
由于scale
依赖于向量的成员乘法,我会考虑将乘法作为一种方法,并将scale
定义为更通用:
class Vector(object):
def __init__(self, dX, dY):
self._dX = dX
self._dY = dY
def __str__(self):
return "->(" + str(self._dX) + ", " + str(self._dY) + ")"
def __imul__(self, other):
if other is Vector:
self._dX *= other._dX
self._dY *= other._dY
else:
self._dX *= other
self._dY *= other
return self
def scale(vector, scalar):
vector *= scalar
因此,类接口丰富且简化,同时保持封装。