- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议35:分清 staticmethod 和 classmethod 的适用场景
Python中的静态方法(staticmethod)和类方法(classmethod)都依赖于装饰器(decorator)来实现。其中静态方法的用法如下:
class C(object): @staticmethod def f(arg1, arg2, ...):
而类方法的用法如下:
class C(object): @classmethod def f(cls, arg1, arg2, ...):
静态方法和类方法都可以通过类名.方法名(如C.f())或者实例.方法名(C().f())的形式来访问。其中静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。
class A(object): def instance_method(self,x): print "calling instance method instance_method(%s,%s)"%(self,x) @classmethod def class_method(cls,x): print "calling class_method(%s,%s)"%(cls,x) @staticmethod def static_method(x): print "calling static_method(%s)"%x a = A() a.instance_method("test") # 输出calling instance method instance_method(<__main__.A object at 0x00D66B50>,test) a.class_method("test") # 输出calling class_method(<class '__main__.A'>,test) a.static_method("test") # 输出calling static_method(test)
上面的例子是类方法和静态方法的简单应用,从程序的输出可以看出虽然类方法在调用的时候没有显式声明cls,但实际上类本身是作为隐含参数传入的。
在了解完静态方法和类方法的基本知识之后再来研究这样一个问题:为什么需要静态方法和类方法,它们和普通的实例方法之间存在什么区别?我们通过对具体问题的研究来回答这些问题。假设有水果类Fruit,它用属性total表示总量,Fruit中已经有方法set()来设置总量,print_total()方法来打印水果数量。类Apple和类Orange继承自Fruit。我们需要分别跟踪不同类型的水果的总量。有好几种方法可以实现这个功能。
方法一:利用普通的实例方法来实现。
在Apple和Orange类中分别定义类变量total,然后再覆盖基类的set()和print_total()方法,但这会导致代码冗余,因为本质上这些方法所实现的功能相同(读者可以自行完成)。
方法二:使用类方法实现,具体实现代码清单如下。
class Fruit(object): total = 0 @classmethod def print_total(cls): print cls.total #print id(Fruit.total) #print id(cls.total) @classmethod def set(cls, value): #print "calling class_method(%s,%s)"%(cls,value) cls.total = value class Apple(Fruit): pass class Orange(Fruit): Pass app1 = Apple() app1.set(200) app2 = Apple() org1 = Orange() org1.set(300) org2 = Orange() app1.print_total() #output 200 org1.print_total() #output 300
删除上面代码中的注释语句后运行程序会得到以下结果:
calling class_method(<class '__main__.Apple'>,200) calling class_method(<class '__main__.Orange'>,300) 200 12184820------>Fruit 类的类变量 12186364 -----> 动态生成的Apple 类的类变量 300 12184820------>Fruit 类的类变量 13810996 -----> 动态生成的Orange 类的类变量
简单分析可知,针对不同种类的水果对象调用set()方法的时候隐形传入的参数为该对象所对应的类,在调用set()的过程中动态生成了对应的类的类变量。这就是classmethod的妙处。请读者自行思考:此处将类方法改为静态方法是否可行呢?
我们再来看一个必须使用类方法而不是静态方法的例子:假设对于每一个Fruit类我们提供3个实例属性:area表示区域代码,category表示种类代码,batch表示批次号。现需要一个方法能够将以area-category-batch形式表示的字符串形式的输入转化为对应的属性并以对象返回。
假设Fruit中有如下初始化方法,并且有静态方法Init_Product()能够满足上面所提的要求。
def __init__(self, area="", category="", batch=""): self.area = area self.category = category self.batch = batch @staticmethod def Init_Product(product_info): area, category, batch = map(int, product_info.split('-')) fruit = Fruit(area, category, batch) return fruit
我们首先来看看使用静态方法所带来的问题。
app1 = Apple (2,5,10) org1 = Orange.Init_Product("3-3-9") print "app1 is instance of Apple:"+str(isinstance(app1, Apple)) print "org1 is instance of Orange:"+str(isinstance(org1, Orange))
运行程序我们会发现isinstance(org1, Orange)的值为False。这不奇怪,因为静态方法实际相当于一个定义在类里面的函数,Init_Product返回的实际是Fruit的对象,所以它不会是Orange的实例。Init_Product()的功能类似于工厂方法,能够根据不同的类型返回对应的类的实例,因此使用静态方法并不能获得期望的结果,类方法才是正确的解决方案。可以针对代码做出如下修改:
@classmethod def Init_Product(cls,product_info): area, category, batch = map(int, product_info.split('-')) fruit = cls(area, category, batch) return fruit
也许读者会问:既然这样,静态方法到底有什么用呢?什么情况下可以使用静态方法?继续上面的例子,假设我们还需要一个方法来验证输入的合法性,方法的具体实现如下:
def is_input_valid(product_info): area, category, batch = map(int, product_info.split('-')) try: assert 0 <= area <= 10 assert 0 <= category <= 15 assert 0 <= batch <= 99 except AssertionError: return False return True
那么应该将其声明为静态方法还是类方法呢?答案是两者都可,甚至将其作为一个定义在类的外部的函数都是可以的。但仔细分析该方法会发现它既不跟特定的实例相关也不跟特定的类相关,因此将其定义为静态方法是个不错的选择,这样代码能够一目了然。也许你会问:为什么不将该方法定义成外部函数呢?这是因为静态方法定义在类中,较之外部函数,能够更加有效地将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性。当然,如果有一组独立的方法,将其定义在一个模块中,通过模块来访问这些方法也是一个不错的选择。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论