python数据结构算法题一百六十二:避免重复的属性方法

问题
你在类中需要重复的定义一些执行相同逻辑的属性方法,比如进行类型检查,怎样去简化这些重复代码呢?
解决方案
考虑下一个简单的类,它的属性由属性方法包装:

def __init__(self, name ,age):
self.name = name
self.age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('name must be a string')
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError('age must be an int')
self._age = value

可以看到,为了实现属性值的类型检查我们写了很多的重复代码。只要你以后看到类似这样的代码,你都应该想办法去简化它。一个可行的方法是创建一个函数用来定义属性并返回它。例如:

storage_name = '_' + name
@property
def prop(self):
return getattr(self, storage_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError('{} must be a {}'.format(name, expected_type))
setattr(self, storage_name, value)
return prop
# Example use
class Person:
name = typed_property('name', str)
age = typed_property('age', int)
def __init__(self, name, age):
self.name = name
self.age = age

讨论
本节我们演示内部函数或者闭包的一个重要特性,它们很像一个宏。例子中的函数 typed_property() 看上去有点难理解,其实它所做的仅仅就是为你生成属性并返回这个属性对象。因此,当在一个类中使用它的时候,效果跟将它里面的代码放到类定义中去是一样的。尽管属性的 getter 和 setter 方法访问了本地变量如 name ,expected_type 以及 storate_name ,这个很正常,这些变量的值会保存在闭包当中。
我们还可以使用 functools.partial() 来稍稍改变下这个例子,很有趣。例如,你可以像下面这样:

String = partial(typed_property, expected_type=str)
Integer = partial(typed_property, expected_type=int)
# Example:
class Person:
name = String('name')
age = Integer('age')
def __init__(self, name, age):
self.name = name
self.age = age

定义上下文管理器的简单方法
问题
你想自己去实现一个新的上下文管理器,以便使用 with 语句。
解决方案
实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的@contextmanager 装饰器。下面是一个实现了代码块计时功能的上下文管理器例子:

from contextlib import contextmanager
@contextmanager
def timethis(label):
start = time.time()
try:
yield
finally:
end = time.time()
print('{}: {}'.format(label, end - start))
# Example use
with timethis('counting'):
n = 10000000
while n > 0:
n -= 1

在函数 timethis() 中,yield 之前的代码会在上下文管理器中作为 enter()方法执行,所有在 yield 之后的代码会作为 exit() 方法执行。如果出现了异常,异常会在 yield 语句那里抛出。
下面是一个更加高级一点的上下文管理器,实现了列表对象上的某种事务:

def list_transaction(orig_list):
working = list(orig_list)
yield working
orig_list[:] = working

这段代码的作用是任何对列表的修改只有当所有代码运行完成并且不出现异常的情况下才会生效。下面我们来演示一下:

>>">>>> with list_transaction(items) as working:
... working.append(4)
... working.append(5)
...
>>> items
[1, 2, 3, 4, 5]
>>> with list_transaction(items) as working:
... working.append(6)
... working.append(7)
... raise RuntimeError('oops')
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: oops
>>> items
[1, 2, 3, 4, 5]
>>>

讨论
通常情况下,如果要写一个上下文管理器,你需要定义一个类,里面包含一个__enter__() 和一个 exit() 方法,如下所示:

class timethis:
def __init__(self, label):
self.label = label
def __enter__(self):
self.start = time.time()
def __exit__(self, exc_ty, exc_val, exc_tb):
end = time.time()
print('{}: {}'.format(self.label, end - self.start))

尽管这个也不难写,但是相比较写一个简单的使用 @contextmanager 注解的函数而言还是稍显乏味。
@contextmanager 应该仅仅用来写自包含的上下文管理函数。如果你有一些对象 (比如一个文件、网络连接或锁),需要支持 with 语句,那么你就需要单独实现__enter__() 方法和 exit() 方法。