在 Python 中,我什么时候应该使用函数而不是方法? [英] In Python, when should I use a function instead of a method?

查看:21
本文介绍了在 Python 中,我什么时候应该使用函数而不是方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Python 之禅(The Zen of Python)指出,做事应该只有一种方法——但我经常遇到决定何时使用函数与何时使用方法的问题.

让我们举一个简单的例子 - ChessBoard 对象.假设我们需要某种方法来让所有合法的国王移动都出现在棋盘上.我们是写 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 本人::><块引用><块引用>

谈论能力/接口让我想到了我们的一些流氓"特殊方法名称.在语言参考中,它说,A类可以实现由特殊调用的某些操作语法(例如算术运算或下标和切片)由定义具有特殊名称的方法." 但是有所有这些方法使用诸如 __len____unicode__ 之类的特殊名称,它们似乎是提供给内置函数的好处,而不是为了语法支持.大概在基于接口的 Python 中,这些方法将变成 ABC 上的常规命名方法,因此__len__ 会变成

类容器:...def len(自我):提高未实施

虽然,再想一想,我不明白为什么所有的句法操作不会只是调用适当的通常命名的方法在特定的 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): ...' 听起来肯定像你想降级为普通方法.我对此强烈-1.

我承诺要解释的第二个 Python 原理是原因为什么我选择特殊的方法来查看 __special__ 而不仅仅是特殊.我期待着许多类可能想要的操作覆盖一些标准(例如 __add____getitem__),有些则不然标准(例如,pickle 的 __reduce__ 长期以来在 C 中不支持代码).不想这些特殊操作用普通的方法名称,因为然后预先存在的类,或由编写的类没有所有特殊方法的百科全书的用户,可能会意外地定义他们无意定义的操作实施,可能带来灾难性后果.伊万·科斯蒂奇在他的消息中更简洁地解释了这一点,这是在我之后到达的把这些都写好了.

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

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

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

TLDR:谁能解释一下决定何时使用函数和方法的策略应该是什么?

解决方案

我的一般规则是这样 - 操作是在对象上执行的还是由对象执行的?

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

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

类鸭子:def __init__(self):passdef 吃(自我,o):通过def废话(自我):通过def die(self)....

在对象是真实的事物"类比的上下文中,为对象可以做的任何事情添加类方法是正确的".所以说我想杀死一只鸭子,我要加一个.kill() 到鸭子?不……据我所知,动物不会自杀.因此,如果我想杀死一只鸭子,我应该这样做:

def kill(o):if isinstance(o,duck):o.死()elif isinstance(o, dog):打印为什么????"o.死()elif isinstance(o, nyancat):引发异常(NYAN"* 9001)别的:打印不能杀死它."

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

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

TL;DR:@Bryan 所说的.如果对实例进行操作,需要访问类实例内部的数据,则应该是成员函数.

The Zen of Python states that there should only be one way to do things- yet frequently I run into the problem of deciding when to use a function versus when to use a method.

Let's take a trivial example- a ChessBoard object. Let's say we need some way to get all the legal King moves available on the board. Do we write ChessBoard.get_king_moves() or get_king_moves(chess_board)?

Here are some related questions I looked at:

The answers I got were largely inconclusive:

Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

The major reason is history. Functions were used for those operations that were generic for a group of types and which were intended to work even for objects that didn’t have methods at all (e.g. tuples). It is also convenient to have a function that can readily be applied to an amorphous collection of objects when you use the functional features of Python (map(), apply() et al).

In fact, implementing len(), max(), min() as a built-in function is actually less code than implementing them as methods for each type. One can quibble about individual cases but it’s a part of Python, and it’s too late to make such fundamental changes now. The functions have to remain to avoid massive code breakage.

While interesting, the above doesn't really say much as to what strategy to adopt.

This is one of the reasons - with custom methods, developers would be free to choose a different method name, like getLength(), length(), getlength() or whatsoever. Python enforces strict naming so that the common function len() can be used.

Slightly more interesting. My take is that functions are in a sense, the Pythonic version of interfaces.

Lastly, from Guido himself:

Talking about the Abilities/Interfaces made me think about some of our "rogue" special method names. In the Language Reference, it says, "A class can implement certain operations that are invoked by special syntax (such as arithmetic operations or subscripting and slicing) by defining methods with special names." But there are all these methods with special names like __len__ or __unicode__ which seem to be provided for the benefit of built-in functions, rather than for support of syntax. Presumably in an interface-based Python, these methods would turn into regularly-named methods on an ABC, so that __len__ would become

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

Though, thinking about it some more, I don't see why all syntactic operations wouldn't just invoke the appropriate normally-named method on a specific ABC. "<", for instance, would presumably invoke "object.lessthan" (or perhaps "comparable.lessthan"). So another benefit would be the ability to wean Python away from this mangled-name oddness, which seems to me an HCI improvement.

Hm. I'm not sure I agree (figure that :-).

There are two bits of "Python rationale" that I'd like to explain first.

First of all, I chose len(x) over x.len() for HCI reasons (def __len__() came much later). There are two intertwined reasons actually, both HCI:

(a) For some operations, prefix notation just reads better than postfix -- prefix (and infix!) operations have a long tradition in mathematics which likes notations where the visuals help the mathematician thinking about a problem. Compare the easy with which we rewrite a formula like x*(a+b) into x*a + x*b to the clumsiness of doing the same thing using a raw OO notation.

(b) When I read code that says len(x) I know that it is asking for the length of something. This tells me two things: the result is an integer, and the argument is some kind of container. To the contrary, when I read x.len(), I have to already know that x is some kind of container implementing an interface or inheriting from a class that has a standard len(). Witness the confusion we occasionally have when a class that is not implementing a mapping has a get() or keys() method, or something that isn't a file has a write() method.

Saying the same thing in another way, I see 'len' as a built-in operation. I'd hate to lose that. I can't say for sure whether you meant that or not, but 'def len(self): ...' certainly sounds like you want to demote it to an ordinary method. I'm strongly -1 on that.

The second bit of Python rationale I promised to explain is the reason why I chose special methods to look __special__ and not merely special. I was anticipating lots of operations that classes might want to override, some standard (e.g. __add__ or __getitem__), some not so standard (e.g. pickle's __reduce__ for a long time had no support in C code at all). I didn't want these special operations to use ordinary method names, because then pre-existing classes, or classes written by users without an encyclopedic memory for all the special methods, would be liable to accidentally define operations they didn't mean to implement, with possibly disastrous consequences. Ivan Krstić explained this more concise in his message, which arrived after I'd written all this up.

-- --Guido van Rossum (home page: http://www.python.org/~guido/)

My understanding of this is that in certain cases, prefix notation just makes more sense (ie, Duck.quack makes more sense than quack(Duck) from a linguistic standpoint.) and again, the functions allow for "interfaces".

In such a case, my guess would be to implement get_king_moves based solely on Guido's first point. But that still leaves a lot of open questions regarding say, implementing a stack and queue class with similar push and pop methods- should they be functions or methods? (here I would guess functions, because I really want to signal a push-pop interface)

TLDR: Can someone explain what the strategy for deciding when to use functions vs. methods should be?

解决方案

My general rule is this - is the operation performed on the object or by the object?

if it is done by the object, it should be a member operation. If it could apply to other things too, or is done by something else to the object then it should be a function (or perhaps a member of something else).

When introducing programming, it is traditional (albeit implementation incorrect) to describe objects in terms of real-world objects such as cars. You mention a duck, so let's go with that.

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

In the context of the "objects are real things" analogy, it is "correct" to add a class method for anything which the object can do. So say I want to kill off a duck, do I add a .kill() to the duck? No... as far as I know animals do not commit suicide. Therefore if I want to kill a duck I should do this:

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."

Moving away from this analogy, why do we use methods and classes? Because we want to contain data and hopefully structure our code in a manner such that it will be reusable and extensible in the future. This brings us to the notion of encapsulation which is so dear to OO design.

The encapsulation principal is really what this comes down to: as a designer you should hide everything about the implementation and class internals which it is not absolutely necessarily for any user or other developer to access. Because we deal with instances of classes, this reduces to "what operations are crucial on this instance". If an operation is not instance specific, then it should not be a member function.

TL;DR: what @Bryan said. If it operates on an instance and needs to access data which is internal to the class instance, it should be a member function.

这篇关于在 Python 中,我什么时候应该使用函数而不是方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆