问题:在Python中,什么时候应该使用函数而不是方法?
Python的Zen指出,只有一种方法可以做事情-但我经常遇到决定何时使用函数以及何时使用方法的问题。
让我们举一个简单的例子-ChessBoard对象。假设我们需要某种方式使董事会上所有合法的King举动均可用。我们是否编写ChessBoard.get_king_moves()或get_king_moves(chess_board)?
这是我看过的一些相关问题:
我得到的答案基本上没有定论:
为什么Python使用方法来实现某些功能(例如list.index()),却使用其他方法(例如len(list))呢?
主要原因是历史。函数用于那些对一组类型通用的操作,即使对于根本没有方法的对象(例如元组),这些操作也可以使用。使用Python的功能特性(map(),apply()等)时,具有可以轻松应用于对象的不定形集合的函数也很方便。
实际上,将len(),max(),min()实现为内置函数实际上比将它们实现为每种类型的方法要少。人们可能会质疑个别情况,但这是Python的一部分,现在进行这样的基本更改为时已晚。必须保留功能以避免大量代码损坏。
尽管很有趣,但是上面并没有真正说明采用哪种策略。
这是原因之一-使用自定义方法,开发人员可以自由选择其他方法名称,例如getLength(),length(),getlength()或其他名称。Python强制执行严格的命名,以便可以使用通用函数len()。
稍微有趣一点。我认为函数在某种意义上是接口的Pythonic版本。
最后,来自Guido本人:
谈论能力/接口使我想到了一些“流氓”特殊方法名称。在《语言参考》中,它说:“类可以通过定义具有特殊名称的方法来实现某些由特殊语法调用的操作(例如算术运算或下标和切片)。” 但是,所有这些带有特殊名称的方法(例如
__len__
或)__unicode__
似乎都是为内置函数的利益提供的,而不是为了支持语法。大概在基于接口的Python中,这些方法将在ABC上变成常规命名的方法,因此__len__
将成为class container: ... def len(self): raise NotImplemented
虽然,再想一想,我不明白为什么所有的句法运算都不会仅仅在特定的ABC上调用适当的通常命名的方法。“
<
”举例来说,大概会调用“object.lessthan
”(或者是“comparable.lessthan
“)。因此,另一个好处是能够使Python摆脱这种乱七八糟的名字,对我而言这似乎是HCI的改进。嗯 我不确定我是否同意(图:-)。
我首先要解释“ Python基本原理”的两个方面。
首先,出于HCI的原因,我选择了len(x)而不是x.len()(
def __len__()
后来出现了)。实际上,两个HCI相互交织在一起:(a)对于某些运算,前缀表示法比后缀读得更好-前缀(和infix!)操作在数学中具有悠久的传统,喜欢在视觉上帮助数学家思考问题的表示法。比较与我们改写像公式简单
x*(a+b)
到x*a + x*b
使用原始OO符号做同样的事情的笨拙。(b)当我读到说的代码时,
len(x)
我知道那是在问某物的长度。这告诉我两件事:结果是整数,参数是某种容器。相反,当我阅读本文时x.len()
,我必须已经知道这x
是一种实现接口或从具有standard的类继承的容器len()
。当未实现映射的类具有get()
或keys()
方法,或者不是文件的某些具有方法时,我们有时会感到困惑write()
。用另一种方式说同样的事情,我将’len’视为内置 操作。我不想失去那个。我不能肯定地说出您是否是那样的意思,但是“ def len(self):…”当然听起来像您想将其降级为普通方法。我对此坚决为-1。
我答应解释的Python基本原理的第二点是为什么我选择了特殊的外观
__special__
而不是仅仅 选择外观的原因special
。我期待类可能要覆盖的许多操作,一些标准(例如__add__
或__getitem__
),某些不是那么标准(例如,泡菜__reduce__
很长一段时间都不支持C代码)。我不希望这些特殊操作使用普通的方法名称,因为那样的话,预先存在的类或用户没有为所有特殊方法存储百科全书的用户编写的类可能会意外地定义它们并非要实现的操作,可能会造成灾难性的后果。伊万·科斯蒂奇(IvanKrstić)在他的信息中对此进行了更为简洁的解释,在我将所有这些内容写完之后,这些信息才得以体现。—Guido van Rossum(主页:http ://www.python.org/~guido/ )
我对此的理解是,在某些情况下,前缀表示法更有意义(即,从语言的角度来看,Duck.quack比quack(Duck)更有意义。)而且,该函数还允许使用“接口”。
在这种情况下,我的猜测是仅基于Guido的第一点实现get_king_moves。但这仍然存在很多悬而未决的问题,例如使用类似的push和pop方法实现堆栈和队列类-它们应该是函数还是方法?(在这里我会猜测功能,因为我真的很想发信号通知推送界面)
TLDR:有人可以解释决定何时使用函数还是方法的策略是什么?
回答 0
我的一般规则是- 是在对象上执行还是由对象执行操作?
如果是由对象完成的,则应该是成员操作。如果它也可以应用于其他事物,或者由对象的其他事物完成,那么它应该是一个函数(或其他事物的成员)。
引入编程时,传统上(尽管实现不正确)以现实世界中的对象(例如汽车)来描述对象。您提到了一只鸭子,所以让我们开始吧。
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."
远离这种类比,为什么我们要使用方法和类?因为我们要包含数据并希望以某种方式构造我们的代码,以便将来可以重用和扩展它。这使我们想到了面向对象设计非常重要的封装概念。
封装原理实际上就是它的含义:作为设计人员,您应该隐藏有关实现和类内部的所有内容,对于任何用户或其他开发人员而言,都不一定要访问它。因为我们处理类的实例,所以这简化为“ 对该实例至关重要的操作”。如果操作不是实例特定的,则它不应是成员函数。
TL; DR:@Bryan说了什么。如果它在实例上运行并且需要访问类实例内部的数据,则它应该是成员函数。
回答 1
在需要以下情况时,请使用类:
2)当您想替代其他对象时-利用多态性。
3)当您想为相似的对象重用代码时-利用继承。
将函数用于对许多不同的对象类型有意义的调用-例如,内置的len和repr函数适用于多种对象。
话虽如此,选择有时取决于口味。考虑一下最适合常规通话的方式和可读性。例如,这将是更好的(x.sin()**2 + y.cos()**2).sqrt()
还是sqrt(sin(x)**2 + cos(y)**2)
?
回答 2
这是一条简单的经验法则:如果代码作用于对象的单个实例,请使用一种方法。甚至更好:除非有充分的理由将其编写为函数,否则请使用一种方法。
在您的特定示例中,您希望它看起来像这样:
chessboard = Chessboard()
...
chessboard.get_king_moves()
不要过度考虑。始终使用方法,直到您对自己说“将此方法定义为没有意义”为止,在这种情况下,您可以创建函数。
回答 3
我通常认为一个物体像一个人。
属性是人物的姓名,身高,鞋子的大小等。
方法和功能是人可以执行的操作。
如果该操作只能由任何其他人完成,而又不需要该特定人独有的任何东西(并且无需更改该特定人的任何东西),那么它就是一个函数,应该这样编写。
如果某项操作正在对该人进行操作(例如进餐,散步等),或者需要该人独特的操作(例如跳舞,写书等),则应采用一种方法。
当然,将其转换为您正在使用的特定对象并不总是一件容易的事,但是我发现这是思考它的好方法。
回答 4
通常,我使用类来为某件事实现一组逻辑功能,以便在程序的其余部分中,我可以对事物进行推理,而不必担心构成其实现的所有小问题。
凡是是那核心抽象的一部分“你可以用做什么事情 ”通常应该是一个方法。这通常包括可以改变一切的事情,作为内部数据状态通常被认为是私人,而不是“你可以用做什么逻辑思想的一部分的事情 ”。
当您进行更高级别的操作时,特别是如果它们涉及多个事物,我发现它们通常最自然地表示为函数,前提是它们可以从事物的公共抽象中构建而无需特殊的内部访问(除非它们re方法)。这具有很大的优势,当我决定完全重写我的工作方式的内部结构(无需更改接口)时,我只有一小部分核心方法可以重写,然后使用这些方法编写所有外部函数将工作。我发现坚持认为与类X有关的所有操作都是类X上的方法会导致类过于复杂。
这取决于我正在编写的代码。对于某些程序,我将它们建模为对象的集合,这些对象的相互作用引起了程序的行为。在这里,最重要的功能紧密耦合到单个对象,因此是在方法中实现的,其中包含实用功能。对于其他程序,最重要的东西是一组操作数据的函数,而类仅用于实现由这些函数操纵的自然“鸭子类型”。
回答 5
您可能会说,“面对模棱两可,拒绝猜测的诱惑”。
但是,这甚至不是猜测。您绝对可以确保两种方法的结果相同,因为它们可以解决您的问题。
我相信,采用多种方式实现目标只是一件好事。与其他用户一样,我要谦虚地告诉您,就语言而言,采用“味道更好” /感觉更直观的方法。