19

我对 Python 比较陌生,并且努力使语言的特性与我从 C++ 和 Java 背景中养成的习惯相协调。

我遇到的最新问题与封装有关,特别是 Meyer 的“ Effective C++ ”的第 23 条最好地总结了一个想法:

首选非成员非朋友函数而不是成员函数

暂时忽略缺乏friend机制,非成员函数是否也被认为比 Python 中的成员函数更可取

一个强制性的,愚蠢的例子:

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)将向量的大小加倍。

考虑到我们在这种情况下使用属性这一事实,这两个选项中的哪一个(如果有的话)更好,为什么?

4

5 回答 5

18

有趣的问题。

与大多数来自 Java 程序员的问题不同,您从不同的地方开始,Java 程序员倾向于假设您需要类,而实际上您并不需要。通常,在 Python 中,除非您专门进行数据封装,否则拥有类是没有意义的。

当然,在您的示例中,您实际上正在这样做,因此使用类是合理的。就个人而言,我会说,既然你确实有一个类,那么成员函数是最好的方法:你专门对那个特定的向量实例进行操作,所以函数作为一个方法是有意义的向量。

您可能想让它成为一个独立函数(我们实际上并没有使用“成员”或“非成员”这个词)是如果您需要使其与多个不一定相互继承的类一起使用一个共同的基地。多亏了鸭子类型,这样做是相当普遍的做法:指定您的函数需要一个具有一组特定属性或方法的对象,然后对它们做一些事情。

于 2012-04-09T11:05:03.553 回答
4

一个自由函数也让您可以灵活地为第一个参数使用鸭子类型。

成员函数为您提供了将功能与类相关联的表现力。

相应地选择。通常,函数的创建是平等的,因此它们都应该对类的接口有相同的假设。一旦您发布了一个免费功能scale,您实际上就是在宣传该功能.dX并且.dYVector. 那可能不是你想要的。您这样做是为了换取与其他具有.dX和的对象重用相同功能的能力.dY。这可能对你没有价值。所以在这种情况下,我当然更喜欢成员函数。

对于喜欢自由函数的好例子,我们只需要看看标准库:sorted是一个自由函数,而不是 的成员函数list,因为从概念上讲,您应该能够创建对任何可迭代序列进行排序的列表。

于 2012-04-09T12:00:32.160 回答
3

更喜欢非成员非朋友函数而不是成员函数

这是一种设计理念,可以而且应该扩展到所有 OOP Paradigm 编程语言。如果你理解了这个的本质,这个概念就很清楚了

如果您不需要对类的成员进行私有/受保护的访问,则您的设计没有理由包含该类的成员函数。反过来想,在设计一个类时,在枚举所有属性之后,您需要确定足以构成该类的最小行为集。您可以使用任何可用的公共方法/成员函数编写的任何成员函数都应该公开。

这在 Python 中适用多少

在某种程度上,如果你小心的话。与其他 OOP 语言(如 Java/C++)相比,Python 支持较弱的封装,这主要是因为没有私有成员。(有一种叫做私有变量的东西,程序员可以通过在变量名称前加上一个“_”前缀来轻松编写它。通过名称修饰功能,这成为类私有。)。因此,如果我们从字面上完全采用 Scott Meyer 的话,完全考虑到应该从 Class 访问的内容与应该从外部访问的内容之间存在细微差别。最好留给设计者/程序员来决定一个函数是否应该是类的一个组成部分。我们可以轻松采用的一种设计原则,"Unless your function required to access any of the properties of the class you can make it a non-member function".

于 2012-04-09T12:21:24.747 回答
2

看看你自己的例子——非成员函数必须访问 Vector 类的数据成员。这不是封装的胜利。尤其是当它更改传入对象的数据成员时。在这种情况下,最好返回一个缩放的向量,并保持原始向量不变。

此外,您不会意识到使用非成员函数的类多态性的任何好处。例如,在这种情况下,它仍然只能处理两个分量的向量。如果它利用向量乘法功能,或者使用一种方法来迭代组件,那就更好了。

总之:

  1. 使用成员函数对您控制的类的对象进行操作;
  2. 使用非成员函数来执行纯粹的通用操作,这些操作是根据本身是多态的方法和运算符实现的。
  3. 在方法中保留对象变异可能会更好。
于 2012-04-09T11:04:47.173 回答
2

由于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

因此,类接口丰富且流线型,同时保持封装性。

于 2014-05-07T18:59:50.427 回答