代码之家  ›  专栏  ›  技术社区  ›  David Webb

类方法的目的是什么?

  •  229
  • David Webb  · 技术社区  · 16 年前

    我在自学巨蟒,最近的课是 Python is not Java 所以我花了一段时间把我所有的类方法变成函数。

    我现在意识到,我不需要使用类方法来完成我将要完成的工作。 static 方法在Java中,但现在我不确定什么时候使用它们。我能找到的关于Python类方法的所有建议都是沿着像我这样的新手的路线,应该避开它们,并且在讨论它们时,标准文档是最不透明的。

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

    15 回复  |  直到 6 年前
        1
  •  167
  •   John Millikin    16 年前

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

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

        2
  •  62
  •   user114245 Wayne Werner    12 年前

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

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

    举个例子,在优秀的 unipath 模块:

    当前目录

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

    由于当前目录是进程范围的,因此 cwd 方法没有与之关联的特定实例。但是,改变 随钻测井 到给定目录 Path 实例确实应该是实例方法。

    六羟甲基三聚氰胺六甲醚。。。作为 路径CWD() 确实返回 路径 例如,我想它可以被认为是一种工厂方法…

        3
  •  46
  •   Brandon Rhodes    9 年前

    这样想:普通方法对于隐藏分派的细节很有用:您可以键入 myobj.foo() 不用担心 foo() 方法由 myobj 对象的类或其父类之一。类方法与此完全类似,但使用类对象:它们允许您调用 MyClass.foo() 不用担心 英尺() 是由 MyClass 因为它需要自己的专用版本,或者是否允许其父类处理调用。

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

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

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

        4
  •  27
  •   Marvo HugoPoi    14 年前

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

    所以我用类变量和 @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()
    
        5
  •  23
  •   Aaron Maenpaa    16 年前

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

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

    上面的过程代码没有很好的组织,正如您在仅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()时,可以消除此问题,并且两个复制函数都可以并排使用。

        7
  •  9
  •   Rusty Rob    12 年前

    当用户登录到我的网站时,会根据用户名和密码实例化一个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()
    
        8
  •  6
  •   Jason Baker    16 年前

    说真的?我从来没有找到StaticMethod或ClassMethod的用法。我还没有看到使用全局函数或实例方法无法完成的操作。

    如果Python使用的是私有的和受保护的成员,就更像Java了。在爪哇中,我需要一个静态方法来访问一个实例的私有成员来做事情。在Python中,这是很少必要的。

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

        9
  •  6
  •   Paul Navin Israni    8 年前

    它允许您编写可用于任何兼容类的通用类方法。

    例如:

    @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
    
        10
  •  5
  •   Drachenfels    15 年前

    我以前用过PHP,最近我问自己,这个类方法怎么样了?python手册是非常技术性的,而且非常简短,所以它不会帮助理解这个特性。我在google和google中找到了答案--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 decorator,因此可以分别输出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()
    

    聪明,不是吗?

        11
  •  4
  •   Tshilidzi Mudau Simon Müller    8 年前

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

    示例:您得到了一组表示对象的类。您可能需要类方法 all() find() User.all() User.find(firstname='Guido') . 当然,这可以通过使用模块级函数来实现…

        12
  •  3
  •   starfry    7 年前

    我刚从Ruby那里得到的消息是 方法和所谓的 实例 方法只是对其第一个参数应用语义意义的函数,当 功能 作为 对象 (即 obj.meth() )

    通常情况下 对象 必须是实例,但 @classmethod method decorator 更改规则以通过类。您可以在实例上调用类方法(它只是一个函数),第一个参数就是它的类。

    因为它只是一个 功能 ,它只能在任何给定范围内声明一次(即 class 定义。因此,对于一个红头发的人来说,这是一个惊喜。 不能有同名的类方法和实例方法 .

    考虑一下:

    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)
    

    现在添加 @类方法 :

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

    对实例的调用现在传递其类:

    Foo().foo()
    __main__.Foo
    

    就像调用类一样:

    Foo.foo()
    __main__.Foo
    

    只有惯例规定我们使用 self 对于实例方法和 cls 在类方法上。我也没有用这来说明这只是一个论点。红宝石, 自己 是一个 关键字。

    与红宝石形成对比:

    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 . 一个修饰的方法包装了真实的方法(在 @类方法 它传递附加的类参数)。基础方法仍然存在,隐藏 but still accessible .


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

        13
  •  2
  •   Peter Moore    12 年前

    这是一个有趣的话题。我的看法是,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
    
        14
  •  2
  •   Dee'Kej    10 年前

    我也问过自己同样的问题几次。尽管这里的人努力解释,但我发现最好(也是最简单)的答案是 description python文档中的类方法。

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

    关于这个主题的进一步深入的阐述也可以在文档中找到: The standard type hierarchy (向下滚动到 实例方法 部分)

        15
  •  0
  •   Ben Hyde    8 年前

    当然,类定义了一组实例。类的方法在单个实例上工作。类方法(和变量)是挂起与实例集相关的其他信息的地方。

    例如,如果您的班级定义了一组学生,您可能需要班级变量或方法,这些变量或方法定义了学生可以成为其成员的一组分数。

    还可以使用类方法定义用于处理整个集合的工具。例如student.all_em()可能会返回所有已知的学生。显然,如果您的实例集的结构多于一个集,那么您可以提供类方法来了解该结构。学生们,所有的学生们

    这样的技术往往会导致将实例集的成员存储到基于类变量的数据结构中。那么,您需要注意避免使垃圾收集受到阻碍。