首页 文章

类方法的目的是什么?

提问于
浏览
225

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

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

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

15 回答

  • 2

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

    例如:

    @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
    
  • 2

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

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

  • 62

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

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

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

    当前目录

    • Path.cwd()

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

    • .chdir()

    • 将self设为当前目录 .

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

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

  • 6

    通过这种方式考虑:正常的方法对于隐藏调度的细节非常有用:您可以键入 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() 方法(数据库后端用于导入按需需要的特定于供应商的数据库库)是一种类方法,因为它需要在特定数据库连接的实例开始创建之前运行 - 但它不能是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法,这些方法可能同样需要在子类中比在其父类中更具体地编写 . 如果你派遣到一个函数或静态类,那么你就会失去关于哪个类正在进行初始化的知识 .

  • 0

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

    所以我使用了类变量和 @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 :

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

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

  • 23

    我认为最明确的答案是 AmanKow's 一个 . 它归结为您希望如何组织代码 . 您可以将所有内容编写为模块级函数,这些函数包含在模块的命名空间中,即

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

  • 5

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

    说实话?我从未发现静态方法或类方法的用途 . 我还没有看到使用全局函数或实例方法无法完成的操作 .

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

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

  • 165

    我曾经使用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()
    

    聪明,不是吗?

  • 10

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

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

  • 4

    刚从Ruby那里得到的是,一个所谓的类方法和一个所谓的实例方法只是一个具有语义含义的函数应用于它的第一个参数,当函数被调用为一个方法时,它会默默地传递 . 一个对象(即 obj.meth() ) .

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

    因为它只是一个函数,所以它只能在任何给定的范围内声明一次(即 class 定义) . 如果遵循这一点,作为Rubyist的一个惊喜,那 you can't have a class method and an instance method with the same name .

    考虑一下:

    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
    

    和一个 class 一样:

    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专家,如果有任何错误,我想要评论 .

  • 9

    这是一个有趣的话题 . 我对它的看法是python classmethod就像一个单例而不是一个单元factory(返回生成的类的实例) . 它是单例的原因是有一个共同的对象(字典),但只有一次为类,但由所有实例共享 .

    为了说明这一点,这是一个例子 . 请注意,所有实例都引用了单个字典 . 据我所知,这不是工厂模式 . 这可能是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
    
  • 3

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

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

    有关此主题的更多详细信息,请参阅文档:The standard type hierarchy(向下滚动到“实例方法”部分)

  • 45

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

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

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

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

相关问题