Python中的类方法是什么?

我自学Python,最近的一个教训是Python不是Java ,所以我花了一段时间把所有的Class方法变成了函数。

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

有没有人有一个在Python中使用类方法的好例子,或者至less有人能告诉我何时可以合理使用类方法?

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

如果你有一个MyClass类和一个在MyClass(工厂,dependency injection桩等)上运行的模块级函数,那么把它作为一个classmethod 。 然后它将可用于子类。

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

基本上,只要你希望有一种自然而然地适应类的名字空间的方法,但是不与类的特定实例相关联,类方法就是合适的。

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

当前目录

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

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

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

用这种方式考虑一下:普通的方法可以隐藏调度的细节:你可以inputmyobj.foo()而不用担心foo()方法是由myobj对象的类还是由它的父类之一实现的。 类方法与此类似,但用类对象代替:它们让你调用MyClass.foo()而不必担心foo()是否由MyClass特别实现,因为它需要它自己的专用版本,或者它是否让它的父类处理这个调用。

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

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

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

我最近想要一个非常轻量级的日志logging类,它可以输出不同的输出量,具体取决于可以通过编程设置的日志logging级别。 但是我不想每次想要输出一个debugging消息或者错误或者警告的时候就实例化这个类。 但是我也想封装这个日志工具的function,并且不需要声明任何全局variables就可以重用。

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

用我简单的Logging类,我可以做到以下几点:

 Logger._level = Logger.DEBUG 

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

 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 

现在只有错误信息会被输出。 debugging信息将不会被打印。

这是我的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 

还有一些testing它的代码:

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

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

我认为最明确的答案是阿曼科的答案。 这归结于你想如何组织你的代码。 你可以把所有的东西都写成模块级别的函数,这些函数被封装在模块的命名空间里

 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中),但是你的代码很快就会变得难以pipe理。

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

通过类函数,与模块级函数相比,您可以在代码中获得另一级别的划分。 所以你可以将一个类中的相关函数分组,使它们更专用于你分配给这个类的任务。 例如你可以创build一个文件工具类:

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

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

当用户在我的网站上login时,User()对象将从用户名和密码实例化。

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

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

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

我曾经和PHP一起工作,最近我问自己,这个类的方法是怎么回事? Python手册是非常技术性和非常短的文字,所以它不会帮助理解该function。 我是谷歌search和谷歌search,我发现答案 – > http://code.anjanesh.net/2007/12/python-classmethods.html

如果你懒得点击它。 我的解释更短,更低。 🙂

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

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

聪明,不是吗?

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

例如:

 @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 

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

如果python更像Java一样使用私有和受保护的成员,情况就会不同。 在Java中,我需要一个静态方法来访问一个实例的私有成员来完成任务。 在Python中,这是很less有必要的。

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

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

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

这是一个有趣的话题。 我的意思是python classmethod像一个singleton而不是一个工厂(它返回一个生成的类的实例)。 它是一个单身的原因是有一个共同的对象是生产(字典),但只有一次的类,但由所有实例共享。

这里举例说明一下。 请注意,所有实例都具有对单个字典的引用。 我不明白这是工厂模式。 这可能是非常独特的python。

 class M(): @classmethod def m(cls, arg): print "arg was", getattr(cls, "arg" , None), cls.arg = arg print "arg is" , cls.arg Mm(1) # prints arg was None arg is 1 Mm(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. Mm(5) # prints arg was 4 arg is 5 

刚刚碰到Ruby的是,一个所谓的方法和一个所谓的实例方法只是一个具有语义意义的函数,它的第一个参数被静静地传递,当这个函数被调用的时候,一个对象 (即obj.meth() )。

通常情况下, 对象必须是一个实例,但@classmethod 方法装饰器更改规则传递一个类。 你可以调用一个实例的类方法(这只是一个函数) – 第一个方法是它的类。

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

考虑一下:

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

你可以在实例上调用foo

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

但不是在一个class上:

 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 ,并在类方法上使用self 我在这里既没有用来说明这只是一个说法。 在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类方法只是一个装饰函数 ,你可以使用相同的技术来创build你自己的装饰器 。 装饰的方法包装真正的方法(在@classmethod的情况下它传递额外的类参数)。 底层方法仍然存在,隐藏但仍然可以访问 。


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

我几次问自己同样的问题。 即使这里的人试图很难解释它,恕我直言,最好的答案(和最简单)的答案我已经find了Python文档中的类方法的描述 。

还有对Static方法的引用。 如果有人已经知道实例方法(我认为),这个答案可能是最后一块把它放在一起…

有关此主题的更深入的阐述,请参见文档: 标准types层次结构 (向下滚动到“ 实例方法”部分)

当然,一个类定义了一组实例。 而一个类的方法在单个实例上工作。 类方法(和variables)是一个地方挂起其他信息,是有关的一组实例的所有。

例如,如果你的class级定义了一组学生,你可能需要类variables或方法来定义类似学生可以成为的成绩集。

您也可以使用类方法来定义在整个集合上工作的工具。 例如Student.all_of_em()可能会返回所有已知的学生。 显然,如果你的一组实例具有更多的结构而不仅仅是一组,你可以提供类方法来了解这个结构。 Students.all_of_em(等级= '大三')

像这样的技术往往会导致将实例集中的成员存储到根variables中的数据结构中。 你需要小心避免挫败垃圾收集。