我正在自学Python,我最近的一课是Python不是Java,所以我刚刚花了一段时间把我所有的Class方法变成了函数。
我现在意识到,我不需要使用Class方法来做我在Java中使用静态方法所做的事情,但现在我不确定什么时候我会使用它们。我能找到的所有关于Python类方法的建议都是,像我这样的新手应该避开它们,而标准文档在讨论它们时是最不透明的。
谁有一个在Python中使用类方法的好例子,或者至少有人能告诉我什么时候可以合理地使用类方法吗?
我正在自学Python,我最近的一课是Python不是Java,所以我刚刚花了一段时间把我所有的Class方法变成了函数。
我现在意识到,我不需要使用Class方法来做我在Java中使用静态方法所做的事情,但现在我不确定什么时候我会使用它们。我能找到的所有关于Python类方法的建议都是,像我这样的新手应该避开它们,而标准文档在讨论它们时是最不透明的。
谁有一个在Python中使用类方法的好例子,或者至少有人能告诉我什么时候可以合理地使用类方法吗?
当前回答
类方法用于当您需要不特定于任何特定实例,但仍以某种方式涉及类的方法时。最有趣的是,它们可以被子类覆盖,这在Java的静态方法或Python的模块级函数中是不可能的。
如果你有一个类MyClass,和一个模块级的函数,它操作MyClass(工厂,依赖注入存根等),让它成为一个类方法。然后它将可用于子类。
其他回答
我认为最明确的答案是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()时,您可以消除这个问题,并且两个复制函数可以并排使用。
我最近想要一个非常轻量级的日志类,它可以根据可编程设置的日志级别输出不同数量的输出。但我不想每次输出调试消息、错误或警告时都实例化这个类。但是我还想封装这个日志记录工具的功能,并使其在不声明任何全局变量的情况下可重用。
所以我使用类变量和@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 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()
Think about it this way: normal methods are useful to hide the details of dispatch: you can type myobj.foo() without worrying about whether the foo() method is implemented by the myobj object's class or one of its parent classes. Class methods are exactly analogous to this, but with the class object instead: they let you call MyClass.foo() without having to worry about whether foo() is implemented specially by MyClass because it needed its own specialized version, or whether it is letting its parent class handle the call.
当您在创建实际实例之前进行设置或计算时,类方法是必不可少的,因为在实例存在之前,您显然不能将实例用作方法调用的调度点。在SQLAlchemy源代码中可以看到一个很好的例子;在下面的链接中查看dbapi()类方法:
https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47
You can see that the dbapi() method, which a database backend uses to import the vendor-specific database library it needs on-demand, is a class method because it needs to run before instances of a particular database connection start getting created — but that it cannot be a simple function or static function, because they want it to be able to call other, supporting methods that might similarly need to be written more specifically in subclasses than in their parent class. And if you dispatch to a function or static class, then you "forget" and lose the knowledge about which class is doing the initializing.
工厂方法(替代构造函数)确实是类方法的经典例子。
基本上,类方法适用于任何您希望有一个方法自然地适合类的名称空间,但不与类的特定实例相关联的时候。
例如,在excellent unipath模块中:
当前目录
Path.cwd () 返回实际的当前目录;例如,路径(“/ tmp / my_temp_dir”)。这是一个类方法。 .chdir () 使self为当前目录。
由于当前目录是进程范围的,cwd方法没有应该与之关联的特定实例。但是,将cwd更改为给定Path实例的目录确实应该是一个实例方法。
嗯…因为Path.cwd()确实返回了一个Path实例,我猜它可以被认为是一个工厂方法…
它允许您编写可与任何兼容类一起使用的泛型类方法。
例如:
@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