我正在自学Python,而我最近的经验是Python不是Java,所以我花了一些时间将所有的类方法转换成函数。
我现在意识到,我不需要使用类方法来完成我在Java中使用
有没有人有在Python中使用类方法的好例子,或者至少有人能告诉我什么时候可以合理地使用类方法?
类方法用于当您需要不特定于任何特定实例,但仍然以某种方式涉及类的方法时。它们最有趣的地方是可以被子类覆盖,这在Java的静态方法或Python的模块级函数中是不可能的。
如果您有一个类
工厂方法(可选构造函数)确实是类方法的一个经典例子。
基本上,只要您希望有一个方法自然地适合于类的名称空间,但又不与类的特定实例关联,那么类方法就非常适合。
例如,在优秀的unipath模块中:
当前目录由于当前目录是进程范围的,
嗯…由于
这样考虑:普通方法对于隐藏分派的细节很有用:您可以输入
在创建实际实例之前进行设置或计算时,类方法是必不可少的,因为在实例存在之前,显然不能将实例用作方法调用的分派点。一个很好的例子可以在SQLAlchemy源代码中查看;请看下面链接中的
https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47
我最近想要一个非常轻量级的日志类,它可以根据可编程设置的日志级别输出不同数量的输出。但我还想封装这个日志工具的功能,使其无需声明任何全局变量即可重用。
因此,我使用类变量和
使用我的简单日志类,我可以做以下事情:
1 | Logger._level = Logger.DEBUG |
然后,在我的代码中,如果我想输出一堆调试信息,我只需编写代码
1 | Logger.debug("this is some annoying message I only want to see while debugging" ) |
错误可以被排除
1 | Logger.error("Wow, something really awful happened." ) |
在"生产"环境中,我可以指定
1 | Logger._level = Logger.ERROR |
现在,只输出错误消息。调试消息将不会打印。
这是我的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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 |
还有一些测试它的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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() |
替代构造函数就是一个经典的例子。
我认为最明确的答案是AmanKow的一个。归结起来就是你想要如何组织你的代码。你可以把所有的东西都写成模块级的函数,这些函数被封装在模块的命名空间中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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中),但是您的代码很快就会变得不可管理。
面向对象的方法是将代码分解为可管理的块i。e类,对象和函数可以与对象实例或类关联。
与模块级函数相比,使用类函数可以在代码中获得另一个级别的除法。因此,可以在类中对相关函数进行分组,使它们更特定于分配给该类的任务。例如,你可以创建一个文件工具类:
1 2 3 4 5 6 7 8 9 | 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()时,可以删除问题,并且可以同时使用这两个复制函数。
当用户登录到我的网站时,user()对象由用户名和密码实例化。
如果我需要一个没有用户登录的用户对象(例如,管理员用户可能想删除另一个用户帐户,所以我需要实例化该用户并调用其删除方法):
我有获取user对象的类方法。
1 2 3 4 5 6 7 8 9 10 11 12 | 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() |
它允许您编写可以与任何兼容类一起使用的泛型类方法。
例如:
1 2 3 4 5 6 7 8 9 10 11 | @classmethod def get_name(cls): print cls.name class C: name ="tester" C.get_name = get_name #call it: C.get_name() |
如果你不使用
1 2 3 4 5 6 7 8 9 10 | 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 |
诚实?我从来没有发现静态方法或类方法的用法。我还没有见过不能使用全局函数或实例方法来完成的操作。
如果python像Java那样使用私有和受保护的成员,情况就会有所不同。在Java中,我需要一个静态方法来访问实例的私有成员。在Python中,这几乎没有必要。
通常,当人们真正需要做的只是更好地使用python的模块级名称空间时,我看到他们使用静态方法和类方法。
我以前使用PHP,最近我问自己,这个类方法是怎么回事?Python手册是非常技术性的,而且非常简短,所以它对理解该特性没有帮助。我在google上搜索了很久,终于找到了答案——> http://code.anjanesh.net/2007/12/pythonclassmethods.html。
如果你懒得点击它。我的解释更简短。:)
在PHP中(也许不是所有人都知道PHP,但是这种语言非常直接,每个人都应该理解我在说什么),我们有这样的静态变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class A { static protected $inner_var = null; static public function echoInnerVar() { echo self::$inner_var." "; } 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。例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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() |
聪明,不是吗?
类方法提供了一个"语义糖"(不知道这个术语是否被广泛使用)——或者"语义便利性"。
示例:您得到了一组表示对象的类。您可能希望使用类方法
让我印象深刻的是,来自Ruby的一个所谓的类方法和一个所谓的实例方法只是一个将语义应用于其第一个参数的函数,当函数作为一个对象的方法(即
通常该对象必须是一个实例,但是
因为它只是一个函数,它只能在任何给定的范围内声明一次(例如
考虑一下:
1 2 3 | class Foo(): def foo(x): print(x) |
您可以对一个实例调用
1 2 | Foo().foo() <__main__.Foo instance at 0x7f4dd3e3bc20> |
但不是在课堂上:
1 2 3 4 | 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) |
现在添加
1 2 3 4 | class Foo(): @classmethod def foo(x): print(x) |
调用一个实例现在传递它的类:
1 2 | Foo().foo() __main__.Foo |
就像调用一个类一样:
1 2 | Foo.foo() __main__.Foo |
只有惯例规定我们对实例方法的第一个参数使用
与Ruby:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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类方法只是一个修饰函数,您可以使用相同的技术来创建自己的修饰器。修饰后的方法包装真实的方法(对于
脚注:在类和实例方法之间的名称冲突激起我的好奇心之后,我写了这篇文章。我不是一个Python专家,如果这些都是错误的,我希望得到您的评论。
我好几次问自己同样的问题。尽管这里的人努力地解释它,我发现最好的答案(和最简单的)是Python文档中类方法的描述。
还引用了静态方法。如果有人已经知道实例方法(我假设是这样),那么这个答案可能是将它们组合在一起的最后一部分……
关于这个主题的进一步和深入的阐述也可在文件中找到:标准类型层次结构(向下滚动到实例方法部分)
这是一个有趣的话题。我的观点是,python classmethod的操作方式类似于单例,而不是工厂(工厂返回生成的类实例)。它是单例的原因是有一个公共对象,它只为类生成一次,但由所有实例共享。
这里有一个例子来说明这一点。注意,所有实例都有对单个字典的引用。这不是我所理解的工厂模式。这可能是python所特有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
当然,类定义了一组实例。类的方法处理单个实例。类方法(和变量)是挂起与所有实例集相关的其他信息的位置。
例如,如果您的类定义了一组学生,您可能想要类变量或方法来定义学生可以属于的年级集之类的东西。
您还可以使用类方法来定义处理整个集合的工具。例如Student.all_of_em()可能返回所有已知的学生。显然,如果实例集的结构不止一组,则可以提供类方法来了解该结构。Students.all_of_em(等级="下属")
这样的技术倾向于将实例集的成员存储到植根于类变量的数据结构中。您需要小心避免破坏垃圾收集。