模块函数vs staticmethod vs classmethod vs no decorators:哪个成语更pythonic?

时间:2012-08-03 01:50:00

标签: python static-methods

我是一名Java开发人员,他开玩笑地使用Python。我最近偶然发现了this article,它提到了Java程序员在拿起Python时常犯的错误。第一个引起了我的注意:

  

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

     

这不是一个性能问题,但是一个必须使用像这样的Java习惯代码的Python程序员会因为输入Foo.Foo.someMethod而感到烦恼,因为它应该只是Foo.someFunction。但请注意,调用classmethod涉及额外的内存分配,调用static方法或函数不会。

     

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

我发现这有点奇怪,因为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 个答案:

答案 0 :(得分:81)

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

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

答案 1 :(得分:28)

BrenBarn给出了很好的答案,但我会更改'如果它不需要访问该类或实例,请将其作为函数'

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

否则将其设为模块功能

答案 2 :(得分:15)

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

  

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

        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

答案 3 :(得分:0)

最佳答案取决于函数的使用方式。就我而言,我编写将在Jupyter笔记本中使用的应用程序包。我的主要目标是为用户提供便利。

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

Python的一个缺点是无法保护名称不被进一步分配。但是,如果"将numpy导入为np"出现在笔记本的顶部,它强烈暗示" np"不应该用作公共变量名。显然,你可以用类名来完成同样的事情,但是用户的熟悉程度非常重要。

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

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