类方法的目的是什么?

Dav*_*ebb 244 python class-method

我正在教自己Python,我最近的教训是Python不是Java,因此我花了一些时间将所有的Class方法转换为函数.

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

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

Joh*_*kin 174

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

如果你有一个类MyClass,以及一个在MyClass(工厂,依赖注入存根等)上运行的模块级函数,那就把它做成一个classmethod.然后它将可用于子类.


Way*_*ner 62

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

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

例如,在优秀的单路模块中:

当前目录

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

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

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

  • 在python中,那些是静态方法,而不是类方法. (3认同)

Bra*_*des 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()数据库后端用于导入按需需要的特定于供应商的数据库库的方法是一种类方法,因为它需要特定数据库连接的实例开始创建之前运行- 但它不能是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法,这些方法可能同样需要在子类中比在父类中更具体地编写.如果你发送到函数或静态类,那么你"忘记"并且失去了关于哪个类正在进行初始化的知识.


Mar*_*rvo 27

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

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

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

Logger._level = Logger.DEBUG
Run Code Online (Sandbox Code Playgroud)

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

Logger.debug( "this is some annoying message I only want to see while debugging" )
Run Code Online (Sandbox Code Playgroud)

可能会出现错误

Logger.error( "Wow, something really awful happened." )
Run Code Online (Sandbox Code Playgroud)

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

Logger._level = Logger.ERROR
Run Code Online (Sandbox Code Playgroud)

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

这是我的班级:

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
Run Code Online (Sandbox Code Playgroud)

还有一些代码可以测试它:

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()
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说似乎并不像好的示例类方法那样好,因为它可以通过简单地创建所有类方法模块级函数并完全取消类来完成所有操作. (9认同)
  • 哦,更重要的是,Python提供了一个非常简单易用的日志类.比创建一个不太理想的解决方案更糟糕的是,我重新发明了轮子.双手拍打.但它至少提供了一个工作的例子.但是,不确定我是否同意在概念层面上摆脱课程.有时您希望封装代码以便于重复使用,并且日志记录方法是重用的重要目标. (9认同)
  • 为什么不使用静态方法? (3认同)

Aar*_*paa 25

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


fir*_*hil 9

我认为最明确的答案是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()
Run Code Online (Sandbox Code Playgroud)

上面的程序代码组织得不好,你可以看到只有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")
Run Code Online (Sandbox Code Playgroud)

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


rob*_*ing 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()
Run Code Online (Sandbox Code Playgroud)


Jas*_*ker 6

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

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

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


yet*_*yet 6

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

例如:

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

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()
Run Code Online (Sandbox Code Playgroud)

如果您不使用@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
Run Code Online (Sandbox Code Playgroud)


小智 6

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

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

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

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

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

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

class A3C_Worker(threading.Thread):
   global_shared_total_episodes_across_all_workers = 0
Run Code Online (Sandbox Code Playgroud)

现在,当工作人员完成游戏时,每完成一场游戏,计数就会增加 1

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

修复我指定的问题:

class A3C_Worker(threading.Thread):
    @classmethod
    def reset(cls):
        A3C_Worker.global_shared_total_episodes_across_all_workers = 0
Run Code Online (Sandbox Code Playgroud)

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

A3C_Worker.reset()
Run Code Online (Sandbox Code Playgroud)

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

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


Dra*_*els 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();
Run Code Online (Sandbox Code Playgroud)

输出将在两种情况下都是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()
Run Code Online (Sandbox Code Playgroud)

聪明,不是吗?


Pie*_*rre 5

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

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


sta*_*fry 5

来自 Ruby 的让我震惊的是,所谓的方法和所谓的实例方法只是一个将语义应用于其第一个参数的函数,当该函数作为 的方法被调用时,它会默默地传递。一个对象(即obj.meth())。

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

因为它只是一个函数,所以它只能在任何给定范围(即class定义)中声明一次。因此,让 Rubyist 感到惊讶的是,您不能拥有同名的类方法和实例方法

考虑一下:

class Foo():
  def foo(x):
    print(x)
Run Code Online (Sandbox Code Playgroud)

您可以调用foo实例

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>
Run Code Online (Sandbox Code Playgroud)

但不是在课堂上:

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)
Run Code Online (Sandbox Code Playgroud)

现在添加@classmethod

class Foo():
  @classmethod
  def foo(x):
    print(x)
Run Code Online (Sandbox Code Playgroud)

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

Foo().foo()
__main__.Foo
Run Code Online (Sandbox Code Playgroud)

就像调用一个类一样:

Foo.foo()
__main__.Foo
Run Code Online (Sandbox Code Playgroud)

这只是约定,要求我们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>
Run Code Online (Sandbox Code Playgroud)

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


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