python:模块函数vs静态方法vs类方法vs无装饰器:哪种习惯用法更符合python风格?

我是一名Java开发人员,曾经断断续续地接触过Python。最近我偶然发现了这篇文章,其中提到了Java程序员在学习Python时经常犯的错误。第一个引起了我的注意:

A static method in Java does not translate to a Python classmethod. Oh sure, it results in more or less the same effect, but the goal of a classmethod is actually to do something that's usually not even possible in Java (like inheriting a non-default constructor). The idiomatic translation of a Java static method is usually a module-level function, not a classmethod or staticmethod. (And static final fields should translate to module-level constants.)

This isn't much of a performance issue, but a Python programmer who has to work with Java-idiom code like this will be rather irritated by typing Foo.Foo.someMethod when it should just be Foo.someFunction. But do note that calling a classmethod involves an additional memory allocation that calling a staticmethod or function does not.

Oh, and all those Foo.Bar.Baz attribute chains don't come for free, either. In Java, those dotted names are looked up by the compiler, so at runtime it really doesn't matter how many of them you have. In Python, the lookups occur at runtime, so each dot counts. (Remember that in Python,"Flat is better than nested", although it's more related to"Readability counts" and"Simple is better than complex," than to being about performance.)

我发现这有点奇怪,因为staticmethod的文档说:

Static methods in Python are similar to those found in Java or C++. Also see classmethod() for a variant that is useful for creating alternate class constructors.

更令人困惑的是这段代码:

1
2
3
4
class A:
    def foo(x):
        print(x)
A.foo(5)

在Python 2.7.3中预期会失败,但在3.2.3中可以正常工作(尽管不能在A的实例上调用该方法,但只能在类上调用)。

因此,有三种方法实现静态方法(如果使用classmethod计算,有四种方法),每种方法都有细微的差别,其中一种似乎没有文档说明。这似乎与Python的口头禅不一致,Python的口头禅是应该有一种(最好只有一种)显而易见的方法来实现它。哪一个成语最像蟒蛇?每种方法的优缺点是什么?

以下是我目前为止的理解:

模块功能:

避免了Foo.Foo.f()问题对模块名称空间的污染比其他选项更严重没有继承

staticmethod:

将与类相关的函数保存在类中,并将其保存在模块命名空间之外。允许在类的实例上调用函数。子类可以覆盖该方法。

classmethod:

与staticmethod相同,但也将类作为第一个参数传递。

常规方法(只适用于python3):

与staticmethod相同,但不能对类的实例调用该方法。

我是不是想太多了?这不是问题吗?请帮助!


最直接的方法是考虑方法需要什么类型的对象来完成它的工作。如果您的方法需要访问实例,请将其设置为常规方法。如果它需要访问类,则将其设置为classmethod。如果它不需要访问类或实例,则将其设置为函数。很少需要将某个函数设置为staticmethod,但是如果您发现希望将一个函数与一个类"分组"(例如,这样它就可以被覆盖),即使它不需要访问该类,我猜您也可以将其设置为staticmethod。

我要补充一点,将函数放在模块级别不会"污染"名称空间。如果要使用这些函数,它们不会污染名称空间,而是按照应该使用的方式使用名称空间。函数是模块中的合法对象,就像类或其他任何东西一样。如果类中没有隐藏函数的理由,那么就没有理由在类中隐藏函数。


BrenBarn的回答很好,但是如果它不需要访问类或实例,我将把它变成一个函数:

如果它不需要访问类或实例……但是在主题上与类相关(典型的例子:其他类方法使用的helper函数和转换函数,或者其他构造函数使用的转换函数),然后使用staticmethod

否则,使它成为一个模块函数


这并不是一个真正的答案,而是一个很长的评论:

Even more puzzling is that this code:

1
2
3
4
        class A:
            def foo(x):
                print(x)
        A.foo(5)

Fails as expected in Python 2.7.3 but works fine in 3.2.3 (although
you can't call the method on an instance of A, only on the class.)

我会试着解释这里发生了什么。

严格地说,这是对"正常"实例方法协议的滥用。

这里定义的是一个方法,但是第一个(也是唯一一个)参数不是self,而是x。当然你可以在一个A的实例中调用这个方法,但是你必须这样调用它:

1
A().foo()

1
2
a = A()
a.foo()

所以这个实例作为第一个参数给了这个函数。

通过类调用常规方法的可能性一直存在,并且是通过

1
2
a = A()
A.foo(a)

在这里,当您调用类的方法而不是实例时,它不会自动获得给定的第一个参数,但是您必须提供它。

只要这是A的一个实例,就没有问题。在我看来,给它一些别的东西是对协议的滥用,因此Py2和Py3之间的区别:

在Py2中,A.foo被转换为一个unbound方法,因此要求它的第一个参数是它"居住"的类的一个实例。用其他东西调用它会失败。

在Py3中,这个检查已经被删除,A.foo只是原始函数对象。你可以把所有东西都作为第一个参数来调用,但我不会这么做。方法的第一个参数应该总是命名为self,并且具有self的语义。


最佳答案取决于函数将如何使用。在我的情况下,我编写的应用程序包将用于木星笔记本。我的主要目标是为用户提供方便。

函数定义的主要优点是,用户可以使用"as"关键字导入定义文件。这允许用户以与调用numpy或matplotlib中的函数相同的方式调用函数。

Python的缺点之一是无法保护名称不受进一步赋值的影响。然而,如果"import numpy as np"出现在笔记本的顶部,则强烈暗示"np"不应该用作通用变量名。显然,您可以使用类名完成相同的工作,但是用户熟悉程度非常重要。

然而,在包内部,我更喜欢使用静态方法。我的软件架构是面向对象的,我使用Eclipse编写,我使用Eclipse编写多种目标语言。打开源文件并查看顶层的类定义、缩进一级的方法定义等等都很方便。此级别代码的受众主要是其他分析人员和开发人员,因此最好避免使用特定于语言的习惯用法。

我对Python名称空间管理不是很有信心,尤其是在使用设计模式时(比如),一个对象将一个引用传递给它自己,以便被调用的对象可以调用在调用者上定义的方法。所以我尽量不强迫它走得太远。我使用了许多完全限定名和显式实例变量(使用self),在其他语言中,我可以指望解释器或编译器更紧密地管理范围。使用类和静态方法更容易做到这一点,这就是为什么我认为它们是抽象和信息隐藏最有用的复杂包的更好选择。