Oop
简介
Python有时被描述为面向对象编程的语言,这多少是个需要澄清的误导。在Python中 一切都是对象,并且能按对象的方式处理。这么说的意思是,例如函数是一等对象。 函数、类、字符串乃至类型都是Python对象:与其他对象一样,他们有类型,能作为 函数参数传递,并且还可能有自己的方法和属性。这样理解的话,Python是一种面向 对象语言。
然而,与Java不同的是,Python并没有将面向对象编程作为最主要的编程范式。非面向 对象的Python项目(比如,使用较少甚至不使用类定义,类继承,或其它面向对象编程的 机制)也是完全可行的。
此外在 模块 章节里曾提到,Python管理模块与命名空间的方式提供给开发者一个自然 的方式以实现抽象层的封装和分离,这是使用面向对象最常见的原因。因而,如果业务逻辑 没有要求,Python开发者有更多自由去选择不使用面向对象。
在一些情况下,需要避免不必要的面向对象。当我们想要将状态与功能结合起来,使用 标准类定义是有效的。但正如函数式编程所讨论的那个问题,函数式的“变量”状态与类的 状态并不相同。
在某些架构中,典型代表是web应用,大量Python进程实例被产生以响应可能同时到达的 外部请求。在这种情况下,在实例化对象内保持某些状态,即保持某些环境静态信息, 容易出现并发问题或竞态条件。有时候在对象状态的初始化(通常通过 __init__()
方法实现)和在其方法中使用该状态之间,环境发生了变化,保留的状态可能已经过时。 举个例子,某个请求将对象加载到内存中并标记它为已读。如果同时另一个请求要删除 这个对象,删除操作可能刚好发生在第一个请求加载完该对象之后,结果就是第一个请 求标记了一个已经被删除的对象为已读。
这些问题使我们产生一个想法:使用无状态的函数是一种更好的编程范式。另一种建议 是尽量使用隐式上下文和副作用较小的函数与程序。函数的隐式上下文由函数内部访问 到的所有全局变量与持久层对象组成。副作用即函数可能使其隐式上下文发生改变。如 果函数保存或删除全局变量或持久层中数据,这种行为称为副作用。
把有隐式上下文和副作用的函数与仅包含逻辑的函数(纯函数)谨慎地区分开来,会带来 以下好处:
- 纯函数的结果是确定的:给定一个输入,输出总是固定相同。
- 当需要重构或优化时,纯函数更易于更改或替换。
- 纯函数更容易做单元测试:很少需要复杂的上下文配置和之后的数据清除工作。
- 纯函数更容易操作、修饰和分发。
总之,对于某些架构而言,纯函数比类和对象在构建模块时更有效率,因为他们没有任何 上下文和副作用。但显然在很多情况下,面向对象编程是有用甚至必要的。例如图形桌面 应用或游戏的开发过程中,操作的元素(窗口、按钮、角色、车辆)在计算机内存里拥有相 对较长的生命周期。
装饰器
Python语言提供一个简单而强大的语法: ‘装饰器’。装饰器是一个函数或类,它可以 包装(或装饰)一个函数或方法。被 ‘装饰’ 的函数或方法会替换原来的函数或方法。 由于在Python中函数是一等对象,它也可以被 ‘手动操作’,但是使用@decorators 语法更清晰,因此首选这种方式。
def foo():
# 实现语句
def decorator(func):
# 操作func语句
return func
foo = decorator(foo) # 手动装饰
@decorator
def bar():
# 实现语句
# bar()被装饰了
这个机制对于分离概念和避免外部不相关逻辑“污染”主要逻辑很有用处。 记忆化 https://en.wikipedia.org/wiki/Memoization#Overview 或缓存就是一个很 好的使用装饰器的例子:您需要在table中储存一个耗时函数的结果,并且下次能直接 使用该结果,而不是再计算一次。这显然不属于函数的逻辑部分。
上下文管理器
上下文管理器是一个Python对象,为操作提供了额外的上下文信息。 这种额外的信息, 在使用 with
语句初始化上下文,以及完成 with
块中的所有代码时,采用可调用的形式。 这里展示了使用上下文管理器的为人熟知的示例,打开文件:
with open('file.txt') as f:
contents = f.read()
任何熟悉这种模式的人都知道以这种形式调用 open
能确保 f
的 ``close` 方法会在某个时候被调用。 这样可以减少开发人员的认知负担,并使代码更容易阅读。
实现这个功能有两种简单的方法:使用类或使用生成器。 让我们自己实现上面的功能,以使用类方式开始:
class CustomOpen(object):
def __init__(self, filename):
self.file = open(filename)
def __enter__(self):
return self.file
def __exit__(self, ctx_type, ctx_value, ctx_traceback):
self.file.close()
with CustomOpen('file') as f:
contents = f.read()
这只是一个常规的Python对象,它有两个由 with
语句使用的额外方法。 CustomOpen 首先被实例化,然后调用它的__enter__``方法,而且
enter的返回值在
as f语句中被赋给
f。 当
with 块中的内容执行完后,会调用 __exit__
方法。
而生成器方式使用了Python自带的 contextlib:
from contextlib import contextmanager
@contextmanager
def custom_open(filename):
f = open(filename)
try:
yield f
finally:
f.close()
with custom_open('file') as f:
contents = f.read()
这与上面的类示例道理相通,尽管它更简洁。custom_open
函数一直运行到 yield
语句。 然后它将控制权返回给 with
语句,然后在 as f
部分将yield的 f 赋值给f。 finally
确保不论 with
中是否发生异常, close()
都会被调用。
由于这两种方法都是一样的,所以我们应该遵循Python之禅来决定何时使用哪种。 如果封装的逻辑量很大,则类的方法可能会更好。 而对于处理简单操作的情况,函数方法可能会更好。