首页 文章

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

提问于
浏览
56

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

Java中的静态方法不能转换为Python类方法 . 哦,当然,它会产生或多或少相同的效果,但是类方法的目标实际上是做一些在Java中通常甚至不可能的事情(比如继承非默认的构造函数) . Java静态方法的惯用翻译通常是模块级函数,而不是classmethod或static方法 . (并且静态最终字段应该转换为模块级常量 . )这不是一个性能问题,但是必须使用像这样的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的实例上调用该方法,只能在类上调用 . )

因此,应该有一个 - 最好只有一个 - 明显的方式来做到这一点 . 哪个成语是最Pythonic?各自的优点和缺点是什么?

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

Module function:

  • 避免Foo.Foo.f()问题

  • 污染模块的命名空间比替代方案更多

  • 没有继承

staticmethod:

  • 保持类中与类相关的函数以及模块命名空间之外的函数 .

  • 允许在类的实例上调用该函数 .

  • 子类可以覆盖该方法 .

classmethod:

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

Regular method (Python 3 only)

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

我是否过度思考这个?这不是问题吗?请帮忙!

4 回答

  • 25

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

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

  • 71

    BrenBarn给出了很好的答案,但我会改变'If it doesn' t需要访问类或实例,使其成为一个函数':

    'If it doesn' t需要访问类或实例...但是 is 主题与类相关(典型示例:辅助函数和其他类方法使用的转换函数或由备用构造函数使用),然后使用 staticmethod

    否则让它成为 module function

  • 14

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

    更令人费解的是这段代码:A类:
    def foo(x):
    打印(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 的语义 .

  • 0

    最佳答案取决于函数的使用方式 . 就我而言,我编写将在Jupyter笔记本中使用的应用程序包 . 我的主要目标是让用户轻松上手 .

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

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

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

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

相关问题