84

我是一名 Java 开发人员,经常玩弄 Python。我最近偶然发现了这篇文章,其中提到了 Java 程序员在使用 Python 时常犯的错误。第一个引起了我的注意:

Java 中的静态方法不会转换为 Python 类方法。哦,当然,它会产生或多或少相同的效果,但类方法的目标实际上是做一些在 Java 中通常甚至不可能的事情(比如继承非默认构造函数)。Java 静态方法的惯用翻译通常是模块级函数,而不是类方法或静态方法。(静态最终字段应转换为模块级常量。)

这并不是什么性能问题,但是对于必须使用此类 Java 惯用代码的 Python 程序员来说,当它应该只是 Foo.someFunction 时,键入 Foo.Foo.someMethod 会很恼火。但请注意,调用类方法涉及额外的内存分配,而调用静态方法或函数则不会。

哦,所有这些 Foo.Bar.Baz 属性链也不是免费的。在 Java 中,这些带点的名称由编译器查找,因此在运行时,您拥有多少实际上并不重要。在 Python 中,查找发生在运行时,因此每个点都很重要。(请记住,在 Python 中,“平面比嵌套更好”,尽管它与“可读性计数”和“简单胜于复杂”更相关,而不是与性能有关。)

我觉得这有点奇怪,因为staticmethod的文档说:

Python 中的静态方法类似于 Java 或 C++ 中的静态方法。另请参阅 classmethod() 以了解可用于创建替代类构造函数的变体。

更令人费解的是这段代码:

class A:
    def foo(x):
        print(x)
A.foo(5)

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

所以有三种方法可以实现静态方法(如果你用 classmethod 算的话,有四种方法),每一种都有细微的差别,其中一种似乎没有记录。这似乎与 Python 的“应该有一种——最好只有一种——明显的方式”的口号不一致。哪个成语是最 Pythonic 的?各自的优缺点是什么?

到目前为止,这是我的理解:

模块功能:

  • 避免 Foo.Foo.f() 问题
  • 比替代品更污染模块的命名空间
  • 无继承

静态方法:

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

上课方式:

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

常规方法(仅限 Python 3)

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

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

4

4 回答 4

113

考虑它的最直接的方法是考虑该方法需要什么类型的对象才能完成其工作。如果您的方法需要访问实例,请将其设为常规方法。如果它需要访问该类,请将其设为类方法。如果它不需要访问类或实例,请将其设为函数。很少需要将某些东西设为静态方法,但如果你发现你希望一个函数与一个类“分组”(例如,这样它可以被覆盖),即使它不需要访问该类,我猜你可以把它变成一个静态方法。

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

于 2012-08-03T02:01:32.300 回答
37

BrenBarn的回答很好,但我会将“如果它不需要访问类或实例,使其成为函数”更改为:

'如果它不需要访问类或实例...但与类主题上相关(典型示例:其他类方法使用的辅助函数和转换函数或备用构造函数使用),则使用静态方法

否则使其成为模块功能

于 2013-04-18T10:02:31.863 回答
16

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

更令人费解的是这段代码:

        class A:
            def foo(x):
                print(x)
        A.foo(5)

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

我将尝试解释这里发生了什么。

严格来说,这是对“正常”实例方法协议的滥用。

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

A().foo()

或者

a = A()
a.foo()

所以实例作为第一个参数提供给函数。

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

a = A()
A.foo(a)

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

只要这是 的一个实例A,一切都很好。给它其他东西是IMO滥用协议,因此Py2和Py3之间的区别:

在 Py2 中,A.foo被转换为一个未绑定的方法,因此需要它的第一个参数是它“生活”在其中的类的实例。用其他东西调用它会失败。

在 Py3 中,此检查已被删除,A.foo它只是原始函数对象。所以你可以用一切作为第一个参数来调用它,但我不会这样做。方法的第一个参数应始终命名self并具有self.

于 2013-05-04T20:40:50.083 回答
0

最佳答案取决于将如何使用该功能。就我而言,我编写了将在 Jupyter 笔记本中使用的应用程序包。我的主要目标是让用户更轻松。

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

Python 的缺点之一是无法保护名称免受进一步分配。但是,如果笔记本顶部出现“import numpy as np”,则强烈暗示不应将“np”用作通用变量名。很明显,你可以用类名来完成同样的事情,但用户熟悉度很重要。

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

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

于 2018-01-06T00:56:33.230 回答