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之禅来决定何时使用哪种。 如果封装的逻辑量很大,则类的方法可能会更好。 而对于处理简单操作的情况,函数方法可能会更好。

Previous
Next