Python中的非成员与成员函数

时间:2012-04-09 10:58:10

标签: python encapsulation member-functions non-member-functions

我对Python比较陌生,并努力将语言的功能与我从C ++和Java背景中学到的习惯相协调。

我与封装有关的最新一期,特别是Meyer的“ Effective C ++ ”第23项总结的一个想法:

  

Prefer non-member non-friend functions to member functions

暂时忽略缺少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)来将向量的大小加倍。

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

5 个答案:

答案 0 :(得分:16)

有趣的问题。

你从一个不同的地方开始,而不是来自Java程序员的大多数问题,这些问题往往假设你需要课程,而你大多数时候都不需要。通常,在Python中除非你专门进行数据封装,否则没有必要使用类。

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

你可能想让它成为一个独立的函数(我们实际上并没有使用“成员”或“非成员”这个词),如果你需要让它适用于多个类,这些类不一定是继承自彼此或共同的基础。感谢duck-typing,这是很常见的做法:指定你的函数需要一个具有特定属性或方法集的对象,并对它们做些什么。

答案 1 :(得分:4)

免费功能使您可以灵活地为第一个参数使用duck-typing。

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

相应地选择。通常,函数创建相等,因此它们都应该对类的接口有相同的假设。发布免费功能scale后,您有效地宣传.dX.dYVector公共界面的一部分。这可能不是你想要的。您正在这样做,以换取与具有.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类的数据成员。这不是封装的胜利。这尤其是因为它更改了传入的对象的数据成员。在这种情况下,返回缩放的向量可能会更好,并保持原始状态不变。

此外,您将不会发现使用非成员函数的类多态的任何好处。例如,在这种情况下,它仍然只能处理两个组件的向量。如果它使用向量乘法功能或使用方法迭代组件会更好。

总结:

  1. 使用成员函数来操作您控制的类的对象;
  2. 使用非成员函数执行纯泛型操作,这些操作是根据方法和操作符实现的,这些操作本身就是多态的。
  3. 在方法中保持对象变异可能更好。

答案 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

因此,类接口丰富且简化,同时保持封装。