在Python中,什么时候应该使用函数而不是方法?

Python的禅说,应该只有一种方法来做事情 – 但我经常遇到决定何时使用函数与何时使用方法的问题。

我们来看一个简单的例子 – 一个ChessBoard对象。 比方说,我们需要一些方法来获得董事会提供的所有合法的国王移动。 我们写ChessBoard.get_king_moves()或get_king_moves(chess_board)吗?

以下是我看到的一些相关问题:

  • 为什么python使用“魔术方法”?
  • 是否有一个原因Pythonstring没有string长度的方法?

我得到的答案很大程度上是不确定的:

为什么Python使用一些function的方法(如list.index()),但其他function(例如len(列表))?

主要原因是历史。 函数被用于那些对于一组types是通用的操作,并且即使对于根本没有方法的对象(例如,元组)也可以工作。 当你使用Python的function特性(map(),apply()等)时,有一个函数可以很容易地应用到非对象集合中。

实际上,实现len(),max(),min()作为一个内置的函数,实际上比实现它们的方法更less。 人们可以对个别案例进行质疑,但是它是Python的一部分,现在就做出这种根本性的改变为时已晚。 函数必须保持以避免大量的代码破坏。

有趣的是,上面的内容并没有太多说明采取什么策略。

这是原因之一 – 使用自定义方法,开发人员可以自由select不同的方法名称,如getLength(),length(),getlength()或任何其他方法。 Python强制严格命名,以便可以使用常用函数len()。

稍微有趣一点。 我认为function是某种意义上的Pythonic版本的接口。

最后, 从圭多本人 :

谈到能力/接口让我想起了一些“stream氓”特殊的方法名称。 在“语言参考”中,它指出:“一个类可以通过定义具有特殊名称的方法来实现特定语法(如算术运算或者下标和切片)所调用的某些操作。 但是,所有这些方法都有一些特殊的名字,比如__len____unicode__ ,它们似乎是为了内置函数而提供的,而不是为了支持语法。 据推测,在基于接口的Python中,这些方法会变成ABC上的定期命名方法,这样__len__会变成

 class container: ... def len(self): raise NotImplemented 

虽然再想一想,但我不明白为什么所有的句法操作都不会在特定的ABC上调用适当的通常命名的方法。 例如,“ < ”大概会调用“ object.lessthan ”(或者可能是“ comparable.lessthan object.lessthan ”)。 所以另外一个好处就是能够让Python远离这个蹩脚的怪名,这在我看来是一个HCI的改进

嗯。 我不确定我是否同意(数字:-)。

有两个“Python基本原理”,我想先解释一下。

首先,我select了len(x)over x.len()作为HCI原因( def __len__()来得晚)。 实际上有两个相互交织的原因,HCI:

(a)对于一些操作,前缀表示法比postfix – 前缀(和中缀法)操作在math上有着悠久的传统,它们喜欢用视觉帮助math家思考问题的符号。 比较我们用x*(a+b)将公式重写成x*a + x*b以便使用原始的OO符号来执行相同的操作。

(b)当我读取len(x)代码时,我知道它是在询问一些东西的长度。 这告诉我两件事情:结果是一个整数,参数是某种容器。 相反,当我读x.len() ,我必须已经知道x是某种实现接口的容器,或者是从具有标准len()的类inheritance而来的。 当一个没有实现映射的类有一个get()keys()方法,或者不是一个文件的东西有一个write()方法时,见证我们偶尔会遇到的困惑。

用另一种方式说同样的事情,我认为“len”是一种内置操作 。 我不想失去那个。 我不能确定你是否意味着这个问题,但是“def len(self):…”当然听起来像你想把它降级到一个普通的方法。 我强烈-1。

我答应解释的Python理论的第二点是我select特殊方法来看看__special__而不仅仅是special 。 我期待着许多类可能要覆盖的操作,一些标准(例如__add____getitem__ ),有些不那么标准(例如pickle的__reduce__很长一段时间根本不支持C代码)。 我不希望这些特殊操作使用普通的方法名称,因为那些预先存在的类或用户编写的类没有用于所有特殊方法的百科全书式的内存,将会被意外地定义它们并不意味着要执行的操作,可能带来灾难性的后果。 伊万·克尔斯蒂奇(IvanKrstić)在他的信息中解释了这个更加简洁的信息。

– – Guido van Rossum(主页: http ://www.python.org/~guido/)

我的理解是,在某些情况下,前缀符号更有意义(即从语言的angular度来说,Duck.quack比庸医(Duck)更有意义),并且函数允许“接口”。

在这种情况下,我的猜测是仅仅基于Guido的第一点来实现get_king_moves。 但是这仍然留下了很多关于如何使用类似的push和pop方法来实现栈和队列的问题 – 它们应该是函数还是方法? (在这里我会猜测函数,因为我真的想要发信号给一个push-pop接口)

TLDR:有人可以解释什么时候决定何时使用函数与方法的策略应该是什么?

我的一般规则是 – 是在对象上还是在对象上执行的操作?

如果是由对象完成,则应该是成员操作。 如果它也可以适用于其他的东西,或者是由其他东西来完成,那么它应该是一个函数(或者也许是其他东西的成员)。

在介绍编程时,用真实世界的对象(如汽车)来描述对象是传统的(尽pipe是不正确的)。 你提到一只鸭子,让我们一起去。

 class duck: def __init__(self):pass def eat(self, o): pass def crap(self) : pass def die(self) .... 

在“对象是真实的东西”的情况下,类比来说,为对象所能做的任何事情添加一个类方法是“正确的”。 所以说我想杀鸭子,给鸭子加一个.kill() 不,据我所知,动物不会自杀。 因此,如果我想杀死一只鸭子,我应该这样做:

 def kill(o): if isinstance(o, duck): o.die() elif isinstance(o, dog): print "WHY????" o.die() elif isinstance(o, nyancat): raise Exception("NYAN "*9001) else: print "can't kill it." 

摆脱这种类比,为什么我们要使用方法和类? 因为我们想要包含数据,并希望以一种可以在将来重用和扩展的方式来构build我们的代码。 这给我们带来了OOdevise非常珍贵的封装概念。

封装主体实际上是这样的:作为devise师,你应该隐藏关于实现和类内部的一切,对于任何用户或其他开发人员来说,访问都不一定是必须的。 因为我们处理类的实例,这减less到“什么操作对这个实例至关重要”。 如果一个操作不是实例特定的,那么它不应该是一个成员函数。

TL; DR :布莱恩说的是什么。 如果它在一个实例上运行,并且需要访问类实例内部的数据,它应该是一个成员函数。

如果您想要:

1)从实现细节中隔离调用代码 – 利用抽象和封装 。

2)当你想要替代其他对象 – 利用多态性 。

3)当你想为类似的对象重用代码 – 利用inheritance 。

在许多不同的对象types中使用一个有意义的调用函数 – 例如,内置lenrepr函数适用于多种对象。

这就是说,select有时归结为一个品味问题。 对于典型的呼叫来说,想一想什么是最方便和可读的。 例如,哪个更好(x.sin()**2 + y.cos()**2).sqrt()sqrt(sin(x)**2 + cos(y)**2)

下面是一个简单的经验法则:如果代码作用于对象的单个实例,请使用方法。 更好的方法是使用一种方法,除非有一个令人信服的理由把它写成函数。

在您的具体示例中,您希望它看起来像这样:

 chessboard = Chessboard() ... chessboard.get_king_moves() 

不要过度思考。 总是使用方法,直到你对自己说“把它作为一种方法是没有意义的”,在这种情况下你可以做一个function。

我通常会想像一个人的对象。

属性是人的名字,身高,鞋子的大小等等

方法function是人可以执行的操作。

如果操作只能由任何一个人完成,而不需要这个特定人的任何特定的东西(并且不对这个特定的人做任何改变),那么这是一个function ,应该这样写。

如果一个行动是对这个人采取行动 (例如,吃饭,散步……)或者要求这个人独特的东西参与(如跳舞,写书……),那么这应该是一种方法

当然,把它翻译成你正在使用的特定对象并不总是微不足道的,但我觉得这是一个很好的思考方式。

一般来说,我使用类来实现一些function的逻辑集合,所以在我的程序的其余部分中,我可以推论这个事情 ,而不必担心构成它的所有小问题。

任何事物的核心抽象的一部分,通常应该是一种方法。 这通常包括所有可以改变事物的东西 ,因为内部数据状态通常被认为是私人的,而不是“你能用一件事物做什么”的逻辑思想的一部分。

当你来到更高层次的操作,特别是如果涉及到多个事物时 ,我发现它们通常最自然地expression为function,如果它们可以在不需要特别访问内部构件的情况下从公共抽象构build出来(除非它们“一些其他对象的方法)。 这有很大的好处,当我决定完全重写我的东西的工作原理(不改变接口)时,我只是有一小组核心的方法来重写,然后用这些方法编写的所有外部函数将工作。 我发现坚持所有与X类相关的操作都是类X上的方法导致过度复杂的类。

这取决于我正在写的代码。 对于某些程序,我将它们build模为一组对象,这些对象的交互引起程序的行为; 这里最重要的function是紧密结合到一个单一的对象,所以在方法中实现,具有实用函数的散射。 对于其他程序来说,最重要的东西是一组操作数据的函数,而类只用于实现由函数操纵的自然“鸭子types”。