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

Cea*_*sta 107 python methods coding-style function

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
Run Code Online (Sandbox Code Playgroud)

虽然,再考虑一下,我不明白为什么所有语法操作都不会在特定的ABC上调用适当的常用命名方法." <"举例来说,大概会调用" object.lessthan"(或者是" comparable.lessthan").所以另一个好处是能够让Python远离这个错位名称奇怪,这在我看来是HCI的改进.

嗯.我不确定我同意(数字:-).

有两点"Python基本原理"我想首先解释一下.

首先,由于HCI的原因,我选择len(x)而不是x.len()(def __len__()后来发布).实际上有两个相互交织的原因,都是人机交互:

(a)对于某些操作,前缀表示法只比读后缀更好 - 前缀(和中缀!)操作在数学中有悠久的传统,它喜欢视觉效果帮助数学家思考问题的符号.比较与我们改写像公式简单x*(a+b)x*a + x*b使用原始OO符号做同样的事情的笨拙.

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

用另一种方式说同样的事情,我认为'len'是一种内置 操作.我不想失去那个.我不能肯定地说你是否意味着,但是'def len(self):......'听起来好像你想把它降级为普通的方法.我非常喜欢这个.

我承诺解释的Python理论的第二部分是我选择特殊方法__special__而不仅仅是 选择特殊方法的原因special.我期待类可能想要覆盖的许多操作,一些标准(例如__add__或者__getitem__),一些不那么标准(例如__reduce__长时间的pickle 在C代码中根本没有支持).我不希望这些特殊操作使用普通的方法名称,因为那些预先存在的类,或者没有用于所有特殊方法的百科全书内存的用户编写的类,可能会意外地定义它们并不意味着要实现的操作,可能带来灾难性的后果.IvanKrstić在他的信息中解释了这一点,这些信息是在我写完所有信息之后到达的.

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

我对此的理解是,在某些情况下,前缀表示法更有意义(即,从语言学的角度来看,Duck.quack比嘎嘎(Duck)更有意义.)而且,这些函数允许"接口".

在这种情况下,我的猜测是基于Guido的第一点来实现get_king_moves.但是,这仍然会留下很多未解决的问题,比如使用类似的推送和弹出方法实现堆栈和队列类 - 它们应该是函数还是方法?(这里我猜功能,因为我真的想发信号推送pop界面)

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

arr*_*dem 73

我的一般规则是 - 是对象还是对象执行的操作?

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

在介绍编程时,传统(尽管实现不正确)用汽车等现实世界的对象来描述对象.你提到一只鸭子,让我们继续吧.

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....
Run Code Online (Sandbox Code Playgroud)

在"对象是真实的东西"类比的上下文中,为对象可以做的任何事情添加类方法是"正确的".所以说我要杀掉一只鸭子,我会给鸭子加一个.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."
Run Code Online (Sandbox Code Playgroud)

远离这个类比,我们为什么要使用方法和类?因为我们希望包含数据,并希望以一种可以在将来重用和扩展的方式构建我们的代码.这使我们了解封装的概念,这对于OO设计来说是如此珍贵.

封装原则实际上归结为:作为设计者,您应该隐藏有关实现和类内部的所有内容,这对任何用户或其他开发人员来说都不是绝对必需的.因为我们处理类的实例,所以这简化为" 在这个实例上哪些操作至关重要".如果操作不是特定于实例的,那么它不应该是成员函数.

TL; DR:@Bryan说的话.如果它在一个实例上运行并且需要访问类实例内部的数据,那么它应该是一个成员函数.


Ray*_*ger 26

如果您想要使用课程:

1)从实现细节中隔离调用代码 - 利用抽象封装.

2)当你想要替代其他对象时 - 利用多态性.

3)当你想重用类似对象的代码时 - 利用继承.

对于在许多不同对象类型中有意义的调用使用函数 - 例如,内置lenrepr函数适用于多种对象.

话虽如此,选择有时归结为品味问题.考虑典型呼叫最方便和可读的方面.例如,哪个更好(x.sin()**2 + y.cos()**2).sqrt()sqrt(sin(x)**2 + cos(y)**2)


Bry*_*ley 7

这是一个简单的经验法则:如果代码作用于对象的单个实例,请使用方法.更好的是:使用方法,除非有令人信服的理由将其作为函数编写.

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

chessboard = Chessboard()
...
chessboard.get_king_moves()
Run Code Online (Sandbox Code Playgroud)

不要过度思考.总是使用方法直到你对自己说"你把这个方法变成没有意义",在这种情况下你可以创建一个函数.

  • 这种经验法则没有意义.标准库本身可以作为何时使用类与函数的指南.*heapq*和*math*是函数,因为它们在普通的python对象(浮点数和列表)上运行,因为它们不需要像*random*模块那样维护复杂的状态. (11认同)
  • 你能解释为什么你默认使用方法而不是函数吗?(你可以解释一下这个规则在堆栈和队列/弹出和推送方法的情况下是否仍然有意义?) (2认同)

Thr*_*eth 7

我通常会想到一个像人一样的物体.

属性是人的名字,身高,鞋码等.

方法功能是人可以执行的操作.

如果操作可以由任何一个人完成,而不需要任何特定于这个特定人的任何东西(并且不改变这个特定人的任何东西),那么它就是一个功能,应该这样写.

如果一个手术对这个人起作用(例如吃饭,走路......)或需要这个人独有的东西参与(比如跳舞,写书,......),那么它应该是一种方法.

当然,将它转换为您正在使用的特定对象并不总是微不足道的,但我发现这是一种思考它的好方法.


Ben*_*Ben 5

通常,我使用类来为某些事物实现一组逻辑功能,以便在程序的其余部分中我可以对事物进行推理,而不必担心构成其实现的所有小问题。

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

当您进行更高级别的操作时,尤其是当它们涉及多个事物时,我发现它们通常最自然地表示为函数,如果它们可以从事物的公共抽象中构建出来而无需对内部进行特殊访问(除非它们re 其他对象的方法)。这有一个很大的优势,当我决定完全重写我的东西如何工作的内部结构(不改变接口)时,我只需要重写一小部分核心方法,然后根据这些方法编写所有外部函数将只是工作。我发现坚持认为与 X 类有关的所有操作都是 X 类上的方法会导致类过于复杂。

不过,这取决于我正在编写的代码。对于某些程序,我将它们建模为一组对象,这些对象的交互导致程序的行为;这里最重要的功能与单个对象紧密耦合,因此在方法中实现,具有分散的效用函数。对于其他程序,最重要的东西是一组操作数据的函数,而类仅用于实现由函数操作的自然“鸭子类型”。