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

Dov*_*val 65 python static-methods

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

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

这不是一个性能问题,但是一个必须使用Java-idiom代码的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)
Run Code Online (Sandbox Code Playgroud)

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

因此,有三种方法可以实现静态方法(如果使用classmethod计算,则有四种方法),每种方法都有细微差别,其中一种看似无法记录.这似乎与Python的口头禅是不一致的应该有一个 - 最好只有一个 - 明显的方式来做到这一点.哪个成语是最Pythonic?各自的优点和缺点是什么?

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

模块功能:

  • 避免Foo.Foo.f()问题
  • 污染模块的命名空间比替代方案更多
  • 没有继承

静态方法:

  • 保持类中与类相关的函数以及模块命名空间之外的函数.
  • 允许在类的实例上调用该函数.
  • 子类可以覆盖该方法.

类方法:

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

常规方法(仅限Python 3):

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

我是否想过这个?这不是问题吗?请帮忙!

Bre*_*arn 83

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

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

  • 这就是为什么不鼓励来自Foo import*`的原因.如果函数是模块的内部函数而不是公共API的一部分,则可以使用前导下划线命名它们,当您从Foo import*`执行时,它们将不会被导入. (13认同)
  • 对于`如果要使用这些函数,它们没有污染命名空间,它们正在使用它就像它应该被使用一样.如果那是污染,那么为什么如果你只是一个命名空间要避免使用它吗? (4认同)

smc*_*mci 28

BrenBarn给出了很好的答案,但我会改变'如果它不需要访问类或实例,那就让它成为一个函数':

"如果它不需要访问类或实例...但主题相关的类(典型的例子:通过其它类的方法中使用或由交替的构造方法的辅助函数和转换函数),然后使用静态方法

否则使它成为模块功能


glg*_*lgl 15

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

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

        class A:
            def foo(x):
                print(x)
        A.foo(5)
Run Code Online (Sandbox Code Playgroud)

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

我会试着解释一下这里发生了什么.

严格来说,这是滥用"正常"实例方法协议.

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

A().foo()
Run Code Online (Sandbox Code Playgroud)

要么

a = A()
a.foo()
Run Code Online (Sandbox Code Playgroud)

所以实例作为第一个参数赋予函数.

通过课程调用常规方法的可能性一直存在并且可以工作

a = A()
A.foo(a)
Run Code Online (Sandbox Code Playgroud)

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

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

在Py2中,A.foo变换为未绑定的方法,因此要求其第一个参数是它"生活"的类的实例.用其他东西调用它将失败.

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