我正在自学Python,最近的课程是Python不是Java,因此我花了一段时间将所有Class方法都转换为函数。
我现在意识到我不需要使用Class方法来完成static
Java中的方法,但是现在我不确定何时使用它们。我可以找到的有关Python类方法的所有建议都是像我这样的新手提出的,应该避免使用它们,并且在讨论它们时,标准文档最为模糊。
有没有人有在Python中使用Class方法的好例子,或者至少有人可以告诉我何时可以明智地使用Class方法?
我正在自学Python,最近的课程是Python不是Java,因此我花了一段时间将所有Class方法都转换为函数。
我现在意识到我不需要使用Class方法来完成static
Java中的方法,但是现在我不确定何时使用它们。我可以找到的有关Python类方法的所有建议都是像我这样的新手提出的,应该避免使用它们,并且在讨论它们时,标准文档最为模糊。
有没有人有在Python中使用Class方法的好例子,或者至少有人可以告诉我何时可以明智地使用Class方法?
Answers:
类方法用于需要不特定于任何特定实例但仍以某种方式涉及到类的方法。关于它们的最有趣的事情是它们可以被子类覆盖,这在Java的静态方法或Python的模块级函数中根本是不可能的。
如果您有一个类MyClass
,以及一个在MyClass上运行的模块级函数(工厂,依赖项注入存根等),请将其设置为classmethod
。然后它将对子类可用。
obj.cls_mthd(...)
或type(obj).cls_mthd(...)
?
工厂方法(替代构造函数)确实是类方法的经典示例。
基本上,类方法适合任何您想自然地适合于类的名称空间但不与类的特定实例相关联的方法。
例如,在出色的unipath模块中:
Path.cwd()
Path("/tmp/my_temp_dir")
。这是一个类方法。.chdir()
由于当前目录是进程范围的,因此该cwd
方法没有应与之关联的特定实例。但是,将更cwd
改为给定Path
实例的目录确实应该是一个实例方法。
嗯... Path.cwd()
确实确实返回了一个Path
实例,我想它可以被认为是工厂方法...
这样考虑:普通方法对隐藏调度细节很有用:您可以键入myobj.foo()
而不必担心该foo()
方法是由myobj
对象的类或其父类之一实现的。类方法与此完全类似,但是使用类对象:它们使您可以调用,MyClass.foo()
而不必担心是否由于需要它自己的专用版本foo()
而特别实现MyClass
,或者是否让其父类处理该调用。
当您在创建实际实例之前进行设置或计算时,类方法必不可少,因为在该实例存在之前,您显然不能将实例用作方法调用的分配点。可以在SQLAlchemy源代码中查看一个很好的示例。dbapi()
在以下链接中查看类方法:
您可以看到,dbapi()
数据库后端用来按需导入特定于供应商的数据库库的方法是一个类方法,因为它需要在开始创建特定数据库连接的实例之前运行,但是它不能运行是一个简单的函数或静态函数,因为他们希望它能够调用其他支持的方法,这些方法可能类似地需要在子类中而不是在其父类中更具体地编写。而且,如果您分派给一个函数或静态类,那么您将“忘记”并失去有关哪个类正在执行初始化的知识。
我最近想要一个非常轻量级的日志记录类,该类将根据可以通过编程设置的日志记录级别输出不同数量的输出。但是我不想每次想输出调试消息,错误或警告时都实例化该类。但是我还想封装该日志记录工具的功能,并在不声明任何全局变量的情况下使其可重用。
因此,我使用了类变量和@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()
替代构造函数是经典示例。
当用户登录我的网站时,将从用户名和密码实例化User()对象。
如果我需要一个用户对象而不需要用户在那里登录(例如,管理员用户可能要删除另一个用户帐户,那么我需要实例化该用户并调用其delete方法):
我有获取用户对象的类方法。
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()
我认为最明确的答案是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()时,您就可以解决此问题并同时复制两个函数可以并排使用。
FileUtil
类方法复制为file_util
模块的函数,则在没有对象实际存在的情况下,它是可比较的并且不会滥用OOP(实际上,您可以说这是更可取的,因为您不会以from file_util import FileUtil
其他冗长的词结尾)。可以通过import file_util
代替来在过程代码中类似地避免名称冲突from file_util import ...
。
老实说 我从未找到用于staticmethod或classmethod的方法。我还没有看到使用全局函数或实例方法无法完成的操作。
如果python更像Java那样使用私有成员和受保护成员,那将是不同的。在Java中,我需要一个静态方法来访问实例的私有成员以执行操作。在Python中,这几乎没有必要。
通常,当人们真正需要做的就是更好地使用python的模块级命名空间时,我会看到人们使用staticmethod和classmethod。
它允许您编写可与任何兼容类一起使用的通用类方法。
例如:
@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
我曾经使用过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."\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()
聪明吧?
B.setInnerVar(20)
忽略,它将打印10
两次(而不是在第二个未定义no的echoInnerBar()调用上给出错误inner_var
。)
类方法提供“语义糖”(不知道此术语是否被广泛使用)或“语义便利”。
示例:您获得了一组表示对象的类。您可能需要类方法all()
或find()
编写User.all()
or User.find(firstname='Guido')
。当然可以使用模块级别的功能来完成...
刚从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专家还很远,如果其中任何一个错误,我想发表评论。
super()
)。AFICT,设置或读取属性时会发生“魔术”。obj.meth(arg)
Python中的调用(与Ruby不同)仅表示(obj.meth)(arg)
。没有任何东西可以在任何地方静默传递,obj.meth
它只是一个可调用对象,它接受的参数比创建该参数的函数少。
obj.meth
依次意味着简单getattr(obj, "meth")
。
这是一个有趣的话题。我认为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
@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']相同的值。
当然,一个类定义了一组实例。一类的方法适用于各个实例。类方法(和变量)用于挂起与所有实例集相关的其他信息的位置。
例如,如果您的班级定义了一组学生,则您可能需要班级变量或方法来定义诸如学生可以成为其成员的年级组之类的东西。
您还可以使用类方法来定义用于处理整个集合的工具。例如,Student.all_of_em()可能会返回所有已知的学生。显然,如果您的实例集具有比一组实例更多的结构,则可以提供类方法来了解该结构。Students.all_of_em(grade ='juniors')
诸如此类的技术往往导致将实例集的成员存储到以类变量为根的数据结构中。您需要注意避免破坏垃圾收集。