类方法的用途是什么?
- 2025-02-12 10:03:00
- admin 原创
- 69
问题描述:
我正在自学 Python,最近的教训是Python 不是 Java,因此我花了一段时间将所有 Class 方法转换为函数。
我现在意识到我不需要使用类方法来做我static
在 Java 中用方法做的事情,但现在我不确定什么时候会用到它们。我能找到的关于 Python 类方法的所有建议都是,像我这样的新手应该避开它们,而且标准文档在讨论它们时是最不透明的。
是否有人有在 Python 中使用 Class 方法的好例子,或者至少有人可以告诉我何时可以合理地使用 Class 方法?
解决方案 1:
类方法适用于需要不特定于任何特定实例但仍以某种方式涉及类的方法的情况。它们最有趣的地方在于,它们可以被子类覆盖,而 Java 的静态方法或 Python 的模块级函数根本不可能做到这一点。
如果你有一个类MyClass
,以及一个在 MyClass 上运行的模块级函数(工厂、依赖注入存根等),请将其设为classmethod
。然后它将可供子类使用。
解决方案 2:
工厂方法(替代构造函数)确实是类方法的经典示例。
基本上,当您希望拥有一种自然适合类的命名空间但不与类的特定实例关联的方法时,类方法就适用。
举个例子,在优秀的unipath模块中:
当前目录
Path.cwd()
返回实际的当前目录;例如
Path("/tmp/my_temp_dir")
。这是一个类方法。
.chdir()
将自身设为当前目录。
由于当前目录是进程范围的,因此该cwd
方法没有与之关联的特定实例。但是,将 更改cwd
为给定Path
实例的目录确实应该是一个实例方法。
嗯...因为Path.cwd()
确实返回了一个Path
实例,我想它可以被认为是一种工厂方法......
解决方案 3:
可以这样想:普通方法有助于隐藏调度细节:您可以键入而myobj.foo()
不必担心该foo()
方法是由myobj
对象的类还是其父类之一实现的。类方法与此完全类似,但使用的是类对象:它们让您调用而MyClass.foo()
不必担心是否foo()
由专门实现(MyClass
因为它需要自己的专门版本),或者是否让其父类处理调用。
在创建实际实例之前进行设置或计算时,类方法是必不可少的,因为在实例存在之前,您显然无法使用该实例作为方法调用的调度点。可以在 SQLAlchemy 源代码中查看一个很好的示例;请查看dbapi()
以下链接中的类方法:
您可以看到dbapi()
,数据库后端用于按需导入特定于供应商的数据库库的方法是一种类方法,因为它需要在开始创建特定数据库连接的实例之前运行- 但它不能是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法,这些方法可能同样需要在子类中而不是父类中更具体地编写。如果您分派到函数或静态类,那么您就会“忘记”并失去有关哪个类正在执行初始化的知识。
解决方案 4:
我最近想要一个非常轻量级的日志类,它可以根据可以通过编程设置的日志级别输出不同数量的输出。但我不想每次想要输出调试消息、错误或警告时都实例化该类。但我还想封装此日志工具的功能并使其可重复使用而无需声明任何全局变量。
所以我使用类变量和@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()
解决方案 5:
替代构造函数是典型的例子。
解决方案 6:
当用户登录我的网站时,将根据用户名和密码实例化一个 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()
解决方案 7:
它允许您编写可与任何兼容类一起使用的通用类方法。
例如:
@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
解决方案 8:
我认为最清楚的答案是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")
这种方式更加灵活,更易于维护,您可以将函数组合在一起,每个函数的作用也更加明显。您还可以避免名称冲突,例如函数 copy 可能存在于您在代码中使用的另一个导入模块(例如网络 copy)中,因此当您使用全名 FileUtil.copy() 时,可以消除此问题,并且两个复制函数可以并排使用。
解决方案 9:
说实话?我从未发现过静态方法或类方法的用途。我还没有看到无法使用全局函数或实例方法完成的操作。
如果 Python 像 Java 那样使用私有和受保护成员,情况就会有所不同。在 Java 中,我需要一个静态方法才能访问实例的私有成员来执行操作。在 Python 中,这很少是必要的。
通常,我看到人们使用静态方法和类方法,而他们真正需要做的是更好地使用 python 的模块级命名空间。
解决方案 10:
类方法提供了“语义糖”(不知道这个术语是否被广泛使用) - 或者“语义便利”。
例如:您有一组表示对象的类。您可能希望使用类方法all()
或find()
编写User.all()
或User.find(firstname='Guido')
。当然,这可以使用模块级函数来完成...
解决方案 11:
如果你不是“受过训练的程序员”,这应该会有所帮助:
我想我已经理解了上述以及网络上其他地方的技术解释,但我总是留下一个问题“很好,但我为什么需要它?什么是实际的用例?”。现在生活给了我一个很好的例子,澄清了所有:
我用它来控制由多线程模块实例化的类的实例之间共享的全局共享变量。用人性化的语言来说,我正在运行多个代理,它们并行创建深度学习的示例。(想象多个玩家同时玩 ATARI 游戏,每个玩家都将他们的游戏结果保存到一个公共存储库(共享变量)中)
我使用以下代码(在主/执行代码中)实例化玩家/代理:
a3c_workers = [A3C_Worker(self.master_model, self.optimizer, i, self.env_name, self.model_dir) for i in range(multiprocessing.cpu_count())]
它创建的玩家数量与我的电脑上的处理器核心数量一样多。A3C_Worker - 是定义代理的类。a3c_workers - 是该类的实例列表(即每个实例都是一个玩家/代理)
现在我想知道所有玩家/代理玩了多少场游戏,因此在 A3C_Worker 定义中我定义了要在所有实例之间共享的变量:
class A3C_Worker(threading.Thread):
global_shared_total_episodes_across_all_workers = 0
现在,当工人完成他们的游戏时,他们每完成一个游戏就会将计数增加 1
在我的示例生成结束时,我关闭了实例,但共享变量已分配了玩过的游戏总数。所以当我再次重新运行它时,我的初始总集数是之前的总数。但我需要该计数来表示每次运行的单独值
为了修复我指定的问题:
class A3C_Worker(threading.Thread):
@classmethod
def reset(cls):
A3C_Worker.global_shared_total_episodes_across_all_workers = 0
比在执行代码中我只需调用:
A3C_Worker.reset()
请注意,这是一个对 CLASS 整体的调用,而不是对它的任何单独实例的调用。因此,从现在开始,它将把我启动的每个新代理的计数器设置为 0。
使用通常的方法定义def play(self):
,需要我们为每个实例单独重置该计数器,这将需要更多的计算并且难以跟踪。
解决方案 12:
我以前用过 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."
";
}
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()
很聪明,不是吗?
解决方案 13:
来自 Ruby 的让我震惊的是,所谓的类方法和所谓的实例方法只是一个将语义含义应用于其第一个参数的函数,当该函数作为对象的方法调用时,该参数会被默默传递(即obj.meth()
)。
通常,该对象必须是一个实例,但@classmethod
方法装饰器会更改规则以传递类。您可以在实例上调用类方法(它只是一个函数) - 第一个参数将是它的类。
因为它只是一个函数,所以它只能在任何给定范围(即class
定义)中声明一次。因此,对于 Rubyist 来说,令人惊讶的是,你不能拥有同名的类方法和实例方法。
考虑一下:
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
就像调用一个类一样:
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 类方法只是一个修饰函数,您可以使用相同的技术来创建自己的修饰器。修饰方法包装真实方法(在传递附加类参数的情况下@classmethod
)。底层方法仍然存在,隐藏但仍可访问。
脚注:我在类和实例方法之间的名称冲突引起了我的好奇心后写了这篇文章。我远非 Python 专家,如果本文有任何错误,请提出意见。
解决方案 14:
这是一个有趣的话题。我的看法是,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
解决方案 15:
我多次问过自己同样的问题。尽管这里的人们尽力解释,但在我看来,我发现的最好的(也是最简单的)答案是Python 文档中对 Class 方法的描述。
还有对静态方法的引用。如果有人已经知道实例方法(我假设),这个答案可能是将它们组合在一起的最后一部分...
在文档中也可以找到有关该主题的进一步更深入的阐述:
标准类型层次结构(向下滚动到实例方法部分)
解决方案 16:
当然,类定义了一组实例。类的方法作用于各个实例。类方法(和变量)是挂载与整个实例集相关的其他信息的地方。
例如,如果您的类定义了一组学生,您可能需要类变量或方法来定义学生可以所属的年级集合等内容。
您还可以使用类方法来定义用于处理整个集合的工具。例如,Student.all_of_em() 可能会返回所有已知学生。显然,如果您的实例集的结构比集合更多,则可以提供类方法来了解该结构。Students.all_of_em(grade='juniors')
此类技术往往会导致将实例集的成员存储到以类变量为根的数据结构中。您需要小心谨慎,以免妨碍垃圾收集。
解决方案 17:
@classmethod
可以用于轻松地从外部资源实例化该类的对象。考虑以下内容:
import settings
class SomeClass:
@classmethod
def from_settings(cls):
return cls(settings=settings)
def __init__(self, settings=None):
if settings is not None:
self.x = settings['x']
self.y = settings['y']
然后在另一个文件中:
from some_package import SomeClass
inst = SomeClass.from_settings()
访问 inst.x 将给出与 settings['x'] 相同的值。
解决方案 18:
类和对象概念在组织事物时非常有用。确实,所有可以通过方法完成的操作也可以使用静态函数完成。
想象一下这样的场景:建立一个学生数据库系统来维护学生的详细信息。您需要掌握学生、教师和教职员工的详细信息。您需要构建函数来计算费用、工资、分数等。费用和分数仅适用于学生,工资仅适用于教职员工和教师。因此,如果您为每种类型的人创建单独的类,代码就会井井有条。
扫码咨询,免费领取项目管理大礼包!