267

我正在自学 Python,我最近的一课是Python 不是 Java,所以我花了一段时间将我所有的 Class 方法都变成了函数。

我现在意识到我不需要使用 Class 方法来处理我static在 Java 中使用方法所做的事情,但现在我不确定何时会使用它们。我能找到的关于 Python 类方法的所有建议都是像我这样的新手应该避开它们,标准文档在讨论它们时是最不透明的。

有没有人有一个在 Python 中使用 Class 方法的好例子,或者至少有人可以告诉我什么时候可以合理使用 Class 方法?

4

18 回答 18

197

类方法适用于当您需要具有不特定于任何特定实例但仍以某种方式涉及类的方法时。关于它们最有趣的是它们可以被子类覆盖,这在 Java 的静态方法或 Python 的模块级函数中根本不可能。

如果您有一个 classMyClass和一个在 MyClass 上运行的模块级函数(工厂、依赖注入存根等),请将其设为classmethod. 然后它将可用于子类。

于 2008-09-01T18:45:56.807 回答
66

工厂方法(替代构造函数)确实是类方法的经典示例。

基本上,类方法适用于任何时候您希望有一个自然适合类名称空间但不与类的特定实例相关联的方法。

例如,在优秀的unipath模块中:

当前目录

  • Path.cwd()
    • 返回实际的当前目录;例如,Path("/tmp/my_temp_dir")。这是一个类方法。
  • .chdir()
    • 使 self 成为当前目录。

由于当前目录是进程范围的,因此该cwd方法没有应该与之关联的特定实例。但是,将 更改为cwd给定Path实例的目录确实应该是一个实例方法。

嗯......Path.cwd()确实返回了一个Path实例,我想它可以被认为是一个工厂方法......

于 2008-09-01T19:08:25.660 回答
49

这样想:普通方法对于隐藏调度细节很有用:您可以输入myobj.foo()而不必担心该foo()方法是由myobj对象的类还是由其父类之一实现的。类方法与此完全类似,但使用类对象:它们让您调用MyClass.foo()而不必担心是否因为它需要自己的专用版本而专门foo()实现MyClass,或者是否让其父类处理调用。

当您在创建实际实例之前进行设置或计算时,类方法是必不可少的,因为在实例存在之前,您显然不能将实例用作方法调用的调度点。一个很好的例子可以在 SQLAlchemy 源代码中查看;dbapi()在以下链接中查看类方法:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

您可以看到,dbapi()数据库后端用来按需导入特定于供应商的数据库库的方法是一个类方法,因为它需要在开始创建特定数据库连接的实例之前运行——但它不能是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法,这些方法可能同样需要在子类中比在其父类中更具体地编写。如果您分派给一个函数或静态类,那么您“忘记”并失去关于哪个类正在执行初始化的知识。

于 2010-08-19T12:51:27.397 回答
30

我最近想要一个非常轻量级的日志记录类,它可以根据可以以编程方式设置的日志记录级别输出不同数量的输出。但是我不想每次想输出调试消息或错误或警告时都实例化该类。但我还想封装这个日志记录工具的功能,并使其可重用,而无需声明任何全局变量。

所以我使用类变量和@classmethod装饰器来实现这一点。

使用我的简单 Logging 类,我可以执行以下操作:

Logger._level = Logger.DEBUG

然后,在我的代码中,如果我想吐出一堆调试信息,我只需要编写代码

Logger.debug( "this is some annoying message I only want to see while debugging" )

错误可能会与

Logger.error( "Wow, something really awful happened." )

在“生产”环境中,我可以指定

Logger._level = Logger.ERROR

现在,只会输出错误消息。不会打印调试消息。

这是我的课:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

还有一些测试它的代码:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()
于 2010-05-11T01:48:33.627 回答
29

替代构造函数是典型的例子。

于 2008-09-01T18:27:27.523 回答
11

当用户登录我的网站时,会从用户名和密码实例化一个 User() 对象。

如果我需要一个用户对象而没有用户登录(例如,管理员用户可能想要删除另一个用户帐户,所以我需要实例化该用户并调用其删除方法):

我有类方法来获取用户对象。

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()
于 2012-03-26T06:36:52.593 回答
10

我认为最明确的答案是AmanKow 的答案。它归结为你想如何组织你的代码。您可以将所有内容编写为模块级函数,这些函数包装在模块的命名空间中,即

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

上面的程序代码没有很好的组织,你可以看到只有 3 个模块后它变得混乱,每个方法是做什么的?您可以为函数使用长描述性名称(如在 java 中),但您的代码仍然很快变得难以管理。

面向对象的方式是将代码分解为可管理的块,即类和对象以及函数可以与对象实例或类相关联。

与模块级函数相比,使用类函数可以在代码中获得另一个级别的划分。因此,您可以在一个类中对相关功能进行分组,以使它们更具体地用于您分配给该类的任务。例如,您可以创建一个文件实用程序类:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

这种方式更灵活,更易于维护,您将功能组合在一起,并且每个功能的作用更加明显。您还可以防止名称冲突,例如,函数副本可能存在于您在代码中使用的另一个导入模块(例如网络副本)中,因此当您使用全名 FileUtil.copy() 时,您可以消除问题并且两个复制函数可以并排使用。

于 2011-05-14T10:24:32.273 回答
10

它允许您编写可以与任何兼容类一起使用的通用类方法。

例如:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

如果你不使用@classmethod你可以用 self 关键字来做,但它需要一个类的实例:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C
于 2012-12-26T15:11:20.733 回答
7

诚实地?我从来没有发现静态方法或类方法的用途。我还没有看到使用全局函数或实例方法无法完成的操作。

如果 python 更像 Java 那样使用私有成员和受保护成员,情况会有所不同。在 Java 中,我需要一个静态方法来访问实例的私有成员来做事。在 Python 中,这很少需要。

通常,当人们真正需要做的就是更好地使用 python 的模块级命名空间时,我看到人们使用静态方法和类方法。

于 2008-09-01T18:54:29.470 回答
5

我曾经使用 PHP,最近我在问自己,这个类方法是怎么回事?Python 手册技术性很强,而且用词很短,因此对理解该功能没有帮助。我在谷歌搜索和谷歌搜索,我找到了答案 - > http://code.anjanesh.net/2007/12/python-classmethods.html

如果你懒惰点击它。我的解释更短,如下。:)

在 PHP 中(也许不是所有人都知道 PHP,但这种语言非常简单,每个人都应该明白我在说什么)我们有这样的静态变量:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

在这两种情况下,输出都是 20。

然而,在 python 中,我们可以添加 @classmethod 装饰器,因此可以分别输出 10 和 20。例子:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

聪明,不是吗?

于 2010-02-22T13:27:12.047 回答
5

类方法提供了一种“语义糖”(不知道这个术语是否被广泛使用)——或“语义便利”。

示例:您有一组表示对象的类。您可能希望拥有类方法all()find()编写User.all()or User.find(firstname='Guido')。这当然可以使用模块级功能来完成......

于 2010-08-17T15:53:14.477 回答
4

让我印象深刻的是,来自 Ruby,所谓的方法和所谓的实例方法只是一个函数,它的第一个参数具有语义含义,当函数作为一个对象(即obj.meth())。

通常,该对象必须是一个实例,但@classmethod 方法装饰器会更改规则以传递一个类。您可以在实例上调用类方法(它只是一个函数) - 第一个参数将是它的类。

因为它只是一个函数,它只能在任何给定范围(即class定义)中声明一次。因此,如果遵循这样的话,对于 Rubyist 来说,您不能拥有同名的类方法和实例方法

考虑一下:

class Foo():
  def foo(x):
    print(x)

您可以调用foo实例

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

但不是在课堂上:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

现在添加@classmethod

class Foo():
  @classmethod
  def foo(x):
    print(x)

现在调用一个实例会传递它的类:

Foo().foo()
__main__.Foo

和上课一样:

Foo.foo()
__main__.Foo

只有约定规定我们self在实例方法和cls类方法上使用第一个参数。我在这里都没有使用来说明这只是一个论点。在 Ruby 中,self是一个关键字。

与红宝石对比:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Python 类方法只是一个装饰函数,您可以使用相同的技术创建自己的装饰器。装饰方法包装了真实方法(在@classmethod它传递附加类参数的情况下)。底层方法仍然存在,隐藏但仍可访问


脚注:在类和实例方法之间的名称冲突激起了我的好奇心之后,我写了这篇文章。我远非 Python 专家,如果其中有任何错误,我想发表评论。

于 2017-03-24T20:45:09.847 回答
4

如果您不是“受过培训的程序员”,这应该会有所帮助:

我想我已经理解了上面和网上其他地方的技术解释,但我总是被一个问题留下“很好,但我为什么需要它?什么是实用的用例?”。现在生活给了我一个很好的例子,澄清了一切:

我正在使用它来控制在由多线程模块实例化的类的实例之间共享的全局共享变量。在人性化的语言中,我正在运行多个代理,它们为并行的深度学习创建示例。(想象多个玩家同时玩 ATARI 游戏,每个玩家都将游戏结果保存到一个公共存储库(共享变量))

我使用以下代码(在主/执行代码中)实例化玩家/代理:

a3c_workers = [A3C_Worker(self.master_model, self.optimizer, i, self.env_name, self.model_dir) for i in range(multiprocessing.cpu_count())]
  • 它创建与我的 comp A3C_Worker 上的处理器内核一样多的玩家 - 是一个定义代理 a3c_workers 的类 - 是该类的实例列表(即每个实例都是一个玩家/代理)

现在我想知道所有玩家/代理已经玩了多少游戏,因此在 A3C_Worker 定义中我定义了要在所有实例之间共享的变量:

class A3C_Worker(threading.Thread):
   global_shared_total_episodes_across_all_workers = 0

现在,当工人完成他们的游戏时,他们每完成一场游戏,就会将计数增加 1

在我的示例生成结束时,我正在关闭实例,但共享变量分配了所玩游戏的总数。所以当我再次重新运行它时,我最初的总集数是之前的总集数。但我需要该计数来代表每次运行的值

解决我指定的问题:

class A3C_Worker(threading.Thread):
    @classmethod
    def reset(cls):
        A3C_Worker.global_shared_total_episodes_across_all_workers = 0

比我刚刚调用的执行代码:

A3C_Worker.reset()

请注意,这是对整个 CLASS 的调用,而不是单独调用它的任何实例。因此,从现在开始,对于我启动的每个新代理,它都会将我的计数器设置为 0。

使用通常的方法定义def play(self):,将需要我们为每个实例单独重置该计数器,这对计算的要求更高且难以跟踪。

于 2020-10-20T12:37:36.480 回答
2

这是一个有趣的话题。我的看法是,python classmethod 像单例而不是工厂一样运行(它返回一个生成的类的实例)。它是单例的原因是产生了一个公共对象(字典),但该类只生成一次,但由所有实例共享。

为了说明这一点,这里举一个例子。请注意,所有实例都引用了单个字典。这不是我理解的工厂模式。这对于python来说可能是非常独特的。

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5
于 2012-09-07T05:06:16.973 回答
2

我多次问自己同样的问题。即使这里的人努力解释它,恕我直言,我发现的最佳答案(也是最简单的)答案是 Python 文档中对 Class 方法的描述

还有对静态方法的引用。如果有人已经知道实例方法(我假设),这个答案可能是将它们放在一起的最后一部分......

也可以在文档中找到有关此主题的更深入和更深入的阐述: 标准类型层次结构(向下滚动到实例方法部分)

于 2014-10-14T07:07:55.110 回答
1

@classmethod可以用于从外部资源轻松实例化该类的对象。考虑以下:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

然后在另一个文件中:

from some_package import SomeClass

inst = SomeClass.from_settings()

访问 inst.x 将给出与 settings['x'] 相同的值。

于 2019-11-15T14:19:20.170 回答
0

当然,一个类定义了一组实例。并且类的方法适用于单个实例。类方法(和变量)是一个放置与所有实例集相关的其他信息的地方。

例如,如果您的班级定义了一组学生,您可能需要班级变量或方法来定义学生可以成为其中成员的年级集等内容。

您还可以使用类方法来定义用于处理整个集合的工具。例如 Student.all_of_em() 可能会返回所有已知的学生。显然,如果您的一组实例具有比一组更多的结构,您可以提供类方法来了解该结构。Students.all_of_em(grade='juniors')

像这样的技术往往会导致将实例集的成员存储到以类变量为根的数据结构中。您需要注意避免破坏垃圾收集。

于 2016-09-08T01:29:19.767 回答
0

类和对象概念在组织事物时非常有用。确实,一个方法可以完成的所有操作,也可以使用一个静态函数来完成。

想象一个场景,建立一个学生数据库系统来维护学生的详细信息。您需要了解有关学生、教师和工作人员的详细信息。您需要构建计算费用、薪水、分数等的函数。费用和分数仅适用于学生,薪水仅适用于教职员工和教师。因此,如果您为每种类型的人创建单独的类,代码就会被组织起来。

于 2020-08-19T06:42:04.767 回答