赞
踩
我正在自学Python,而我最近的经验是Python不是Java,所以我花了一些时间将所有的类方法转换成函数。
我现在意识到,我不需要使用类方法来完成我在Java中使用static方法所做的工作,但是现在我不确定什么时候使用它们。关于Python类方法,我能找到的所有建议都是像我这样的新手应该避免使用的,并且在讨论它们时,标准文档是最不透明的。
有没有人有在Python中使用类方法的好例子,或者至少有人能告诉我什么时候可以合理地使用类方法?
类方法用于当您需要不特定于任何特定实例,但仍然以某种方式涉及类的方法时。它们最有趣的地方是可以被子类覆盖,这在Java的静态方法或Python的模块级函数中是不可能的。
如果您有一个类MyClass和一个在MyClass(工厂、依赖注入存根等)上操作的模块级函数,那么将它设置为classmethod。然后它将对子类可用。
工厂方法(可选构造函数)确实是类方法的一个经典例子。
基本上,只要您希望有一个方法自然地适合于类的名称空间,但又不与类的特定实例关联,那么类方法就非常适合。
例如,在优秀的unipath模块中:当前目录
Path.cwd()返回当前目录;例如,Path("/tmp/my_temp_dir")。这是一个类方法。.chdir()使self成为当前目录。
由于当前目录是进程范围的,cwd方法没有应该与之关联的特定实例。但是,将cwd更改为给定的Path实例的目录确实应该是一个实例方法。
嗯…由于Path.cwd()确实返回了一个Path实例,我想它可以被认为是一个工厂方法……
"当函数作为对象的方法被调用时,它会无声地传递"——我认为这并不准确。在Python中,没有什么是"悄无声息地传递"的。在我看来,Python在这个意义上比Ruby更有意义(除了super())。当属性被设置或读取时,"魔法"就会发生。Python中的调用obj.meth(arg)(与Ruby不同)只表示(obj.meth)(arg)。任何地方都不会无声地传递任何东西,obj.meth只是一个可调用的对象,它接受的参数比创建它的函数少一个。
obj.meth的意思是简单的getattr(obj,"meth")。
为来自其他共同语言的人量身定制答案是有意义的。然而,这段对话中缺少的一件将其联系在一起的事情是Python描述符的概念。函数是描述符,这就是当函数是类的属性时,第一个参数"自动地"传递给实例的方式。它也是如何实现类方法的,在how to I链接中提供了一个纯python实现。事实上,它也是一个装饰器是一种辅助
这样考虑:普通方法对于隐藏分派的细节很有用:您可以输入myobj.foo(),而不用担心myobj对象的类或它的父类之一是否实现了foo()方法。类方法与此完全类似,但是使用的是类对象:它们允许您调用MyClass.foo(),而不必担心foo()是由MyClass专门实现的,因为它需要自己的专门化版本,还是让它的父类处理调用。
在创建实际实例之前进行设置或计算时,类方法是必不可少的,因为在实例存在之前,显然不能将实例用作方法调用的分派点。一个很好的例子可以在SQLAlchemy源代码中查看;请看下面链接中的dbapi()类方法:
https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47
dbapi()方法可以看出,一个数据库后端使用导入特定于供应商的数据库库需要随需应变,是一个类方法,因为它需要运行在一个特定的实例开始创建数据库连接,但它绝不是一个简单的函数或静态函数,因为他们希望它能够调用其他支持方法可能同样需要写更具体子类的父类。如果您分派给函数或静态类,那么您将"忘记"并丢失关于哪个类正在进行初始化的知识。
是的…首先想到的是工厂的方法。与此同时,还需要实现单例模式,比如:getAndOptionallyCreateSomeSingleInstanceOfSomeResource()
在python中,这些是静态方法,而不是类方法。
我最近想要一个非常轻量级的日志类,它可以根据可编程设置的日志级别输出不同数量的输出。但我还想封装这个日志工具的功能,使其无需声明任何全局变量即可重用。
因此,我使用类变量和@classmethod装饰器来实现这一点。
使用我的简单日志类,我可以做以下事情:
1Logger._level = Logger.DEBUG
然后,在我的代码中,如果我想输出一堆调试信息,我只需编写代码
1Logger.debug("this is some annoying message I only want to see while debugging" )
错误可以被排除
1Logger.error("Wow, something really awful happened." )
在"生产"环境中,我可以指定
1Logger._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
41class 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
16def 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()
我明白了。但是,如果我只是想对一个不需要MyClass的方法进行分类,但在另一个MyClass中进行分组又有什么意义呢?我可以考虑把它移到helper模块。
我有一个和第一个问题有关的问题,你可以同时回答吗?调用对象的类方法的方法是"更好"还是"更习惯":obj.cls_mthd(...)还是type(obj).cls_mthd(...)?
替代构造函数就是一个经典的例子。
我认为最明确的答案是AmanKow的一个。归结起来就是你想要如何组织你的代码。你可以把所有的东西都写成模块级的函数,这些函数被封装在模块的命名空间中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21module.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
9class 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()时,可以删除问题,并且可以同时使用这两个复制函数。
您的Python示例的一个潜在问题是,如果省略了B.setInnerVar(20),它将两次打印10(而不是在第二个echoInnerBar()调用中给出一个错误,即没有定义inner_var)。
当用户登录到我的网站时,user()对象由用户名和密码实例化。
如果我需要一个没有用户登录的用户对象(例如,管理员用户可能想删除另一个用户帐户,所以我需要实例化该用户并调用其删除方法):
我有获取user对象的类方法。
1
2
3
4
5
6
7
8
9
10
11
12class 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()
在我看来,这并不是一个好例子类方法的优点,因为它可以用一种不那么复杂的方法来完成,只需要让所有的类方法都成为模块级函数,并完全删除类。
哦,更重要的是,Python提供了一个非常简单易用的日志类。因此,比创建一个不太理想的解决方案更糟糕的是,我重新发明了轮子。双推手。但它至少提供了一个可行的例子。不过,在概念层面上,我不确定是否同意取消这门课。有时候,您希望封装代码以方便重用,日志记录方法是重用的一个很好的目标。
为什么不用静态方法呢?
它允许您编写可以与任何兼容类一起使用的泛型类方法。
例如:
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()
如果你不使用@classmethod,你可以用self关键字,但它需要一个类的实例:
1
2
3
4
5
6
7
8
9
10def 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
27class 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
21class 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()
聪明,不是吗?
类方法提供了一个"语义糖"(不知道这个术语是否被广泛使用)——或者"语义便利性"。
示例:您得到了一组表示对象的类。您可能希望使用类方法all()或find()来编写User.all()或User.find(firstname="Guido")。当然,这可以使用模块级函数来实现……
Private: _variable_name和Protected: _variable_name
unittest的setUpClass和tearDownClass是一个不可缺少的用途。您使用的是单元测试,对吗?:)
让我印象深刻的是,来自Ruby的一个所谓的类方法和一个所谓的实例方法只是一个将语义应用于其第一个参数的函数,当函数作为一个对象的方法(即obj.meth())被调用时,它将无声地传递。
通常该对象必须是一个实例,但是@classmethod方法修饰符修改规则来传递一个类。您可以在一个实例上调用一个类方法(它只是一个函数)——第一个argyment将是它的类。
因为它只是一个函数,它只能在任何给定的范围内声明一次(例如class定义)。因此,让Rubyist感到意外的是,您不能拥有同名的类方法和实例方法。
考虑一下:
1
2
3class Foo():
def foo(x):
print(x)
您可以对一个实例调用foo
1
2Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>
但不是在课堂上:
1
2
3
4Foo.foo()
Traceback (most recent call last):
File"", line 1, in
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)
现在添加@classmethod:
1
2
3
4class Foo():
@classmethod
def foo(x):
print(x)
调用一个实例现在传递它的类:
1
2Foo().foo()
__main__.Foo
就像调用一个类一样:
1
2Foo.foo()
__main__.Foo
只有惯例规定我们对实例方法的第一个参数使用self,对类方法使用cls。我在这里没有使用任何一个来说明这只是一个论点。在Ruby中,self是一个关键字。
与Ruby:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class 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 #
""""
""
<<
""""
""
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。