模块函数vs静态方法vs类方法vs无装饰器:哪个习语更pythonic?

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

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

这不是一个性能问题,但是一个Python程序员必须像这样使用Java-idiom代码,当inputFoo.Foo.someMethod的时候,应该只是Foo.someFunction而非常恼火。 但是请注意,调用一个classmethod涉及一个额外的内存分配,调用静态方法或函数不会。

哦,所有那些Foo.Bar.Baz属性链也不是免费的。 在Java中,这些虚线名称是由编译器查找的,所以在运行时,它们有多less个并不重要。 在Python中,查找在运行时发生,所以每个点都是重要的。 (请记住,在Python中,“Flat比嵌套更好”,但它与“Readability counts”和“Simple比复杂”更相关,而不是关于性能。

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

Python中的静态方法类似于Java或C ++中的方法。 另请参见classmethod(),该类对于创build备用类构造函数非常有用。

更令人费解的是,这个代码:

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

在Python 2.7.3中失败,但在3.2.3中正常工作(虽然你不能在A的实例上调用方法,只能在类上调用)。

所以有三种方法来实现静态方法(如果使用classmethod方法,则有四种方法),每种方法都有细微的差别,其中一种看似没有logging。 这似乎与Python的口头禅不一致。 应该有一个 – 最好只有一个 – 明显的方法来做到这一点。 哪个成语是Pythonic最? 各有什么优点和缺点?

以下是我目前所了解的内容:

模块function:

  • 避免Foo.Foo.f()问题
  • 污染模块的名称空间比替代品多
  • 没有inheritance

静态方法:

  • 在类中保持与类有关的function,并在模块名称空间之外。
  • 允许在类的实例上调用该函数。
  • 子类可以覆盖该方法。

类方法:

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

常规方法(仅限Python 3)

  • 与静态方法相同,但不能调用该类的实例的方法。

我是否在意这个? 这是一个非问题? 请帮忙!

思考这个问题的最直接的方法就是考虑方法需要什么types的对象来完成它的工作。 如果您的方法需要访问实例,请将其作为常规方法。 如果需要访问课堂,则将其作为课堂教学。 如果不需要访问类或实例,则将其作为函数。 很less有需要做一些静态的方法,但是如果你发现你想要一个函数与一个类“分组”(比如,所以它可以被覆盖),即使它不需要访问类,我猜你可以把它变成一个静态的方法。

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

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

'如果它不需要访问类或实例…但是主题相关(典型示例:其他类方法使用的辅助函数和转换函数,或者由其他构造函数使用),则使用staticmethod

否则使其成为模块function

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

更令人费解的是,这个代码:

  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一个实例,一切都可以。 给它别的东西是国际海事组织滥用协议,因此Py2和Py3的区别:

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

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