类方法的目的是什么?

时间:2008-09-01 18:16:41

标签: python class-method

我在教自己Python,我最近的课程是Python is not Java,所以我花了一些时间把我所有的Class方法转化为函数。

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

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

18 个答案:

答案 0 :(得分:171)

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

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

答案 1 :(得分:63)

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

基本上,只要你想拥有一个自然适合类的命名空间但与类的特定实例无关的方法,类方法就适用。

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

当前目录

  • Path.cwd()
    • 返回实际的当前目录;例如,Path("/tmp/my_temp_dir")。这是一种类方法。
  • .chdir()
    • 将自己设为当前目录。

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

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

答案 2 :(得分:46)

以这种方式思考:普通方法可用于隐藏调度的详细信息:您可以键入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()方法(数据库后端用于导入按需需要的特定于供应商的数据库库)是一种类方法,因为它需要在之前运行 >特定数据库连接的实例开始被创建 - 但它不能是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法,这些方法可能同样需要在子类中更具体地编写,而不是在他们的子类中编写。父母班。如果你发送到函数或静态类,那么你“忘记”并且失去了关于哪个类正在进行初始化的知识。

答案 3 :(得分:27)

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

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

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

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()

答案 4 :(得分:23)

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

答案 5 :(得分: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中),但仍然可以非常快速地使代码无法管理。

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

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

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()时,您可以删除问题并同时复制两个函数可以并排使用。

答案 6 :(得分:9)

当用户登录我的网站时,会从用户名和密码中实例化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()

答案 7 :(得分:6)

诚实?我从未找到过使用static方法或classmethod的用法。我还没有看到使用全局函数或实例方法无法完成的操作。

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

通常,我看到人们使用staticmethods和classmethods时他们真正需要做的就是更好地使用python的模块级命名空间。

答案 8 :(得分:6)

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

例如:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

如果您不使用@classmethod,可以使用self关键字,但需要一个Class实例:

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

答案 9 :(得分:5)

我曾经使用PHP,最近我问自己,这个类方法有什么用? Python手册非常技术性,而且文字很短,因此它无助于理解该功能。我在谷歌搜索和谷歌搜索,我找到了答案 - &gt; 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()

聪明,不是吗?

答案 10 :(得分:4)

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

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

答案 11 :(得分:3)

来自Ruby的最让我感动的是,所谓的方法和所谓的实例方法只是一个应用了语义含义的函数它的第一个参数,当函数作为对象的方法(即obj.meth())调用时,它会以静默方式传递。

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

因为它只是一个函数,所以它只能在任何给定的范围内声明一次(即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
和p上课一样:

Foo.foo()
__main__.Foo

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

与Ruby对比:

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类方法只是一个修饰函数,您可以使用相同的技术create your own decorators。一个修饰的方法包装了真正的方法(在@classmethod的情况下,它传递了额外的类参数)。基础方法仍然存在,隐藏but still accessible

脚注:我在类和实例方法之间的名称冲突引起了我的好奇之后写了这个。我远非Python专家,如果有任何错误,我想要评论。

答案 12 :(得分: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

答案 13 :(得分:2)

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

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

在文档中还可以找到有关此主题的更深入的详细说明: The standard type hierarchy(向下滚动到实例方法部分)

答案 14 :(得分:0)

当然,类定义了一组实例。并且类的方法适用于各个实例。类方法(和变量)可以挂起与所有实例集相关的其他信息。

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

您还可以使用类方法来定义用于处理整个集合的工具。例如,Student.all_of_em()可能会返回所有已知学生。显然,如果您的实例集具有的结构多于集合,那么您可以提供类方法来了解该结构。 Students.all_of_em(等级=&#39;大三&#39)

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

答案 15 :(得分:0)

@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的值将与设置['x']相同。

答案 16 :(得分:0)

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

仅考虑一个方案,即建立一个学生数据库系统来维护学生的详细信息。 您需要提供有关学生,老师和员工的详细信息。您需要构建函数来计算费用,薪水,分数等。费用和分数仅适用于学生,薪水仅适用于教职员工。因此,如果您为每种类型的人创建单独的类,则代码将井井有条。

答案 17 :(得分:0)

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

我认为我已经理解了网络上方和其他地方的技术说明,但是始终遇到一个问题:“很好,但是为什么我需要它?什么是实际的用例?”。现在生活给了我一个很好的例子,澄清了一切:

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

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

a3c_workers = [A3C_Worker(self.master_model,self.optimizer,i,self.env_name,self.model_dir)for range(multiprocessing.cpu_count())中的我

  • 它创建的播放器数量与我的comp上具有处理器内核的数量一样多 A3C_Worker-是定义代理的类 a3c_workers-是该类实例的列表(即每个实例是一个玩家/代理)

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

A3C_Worker类(threading.Thread): global_shared_total_episodes_across_all_workers = 0

现在,当工人完成比赛时,他们每完成一场比赛就将计数增加1

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

解决我指定的问题:

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

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

A3C_Worker.reset()

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

使用通常的方法定义“ def play(self):”,将需要我们分别为每个实例重置该计数器,这将在计算上更加繁琐且难以跟踪。