前两天的Python思考题,给大家一个参考答案 - 知乎专栏
前段时间出了个类型检查函数的思考题,以及参考答案。有的时候一旦写出一个有点通用性的东西,就会去想怎么增加它的扩展性和易用性。
之前我们的算法里已经解决了递归结构(自引用)带来的各种问题,在之前的阶段中我们是将这个问题看成了一个数学问题,这可以算是“技术”,但还不够“工程”,如果有人想要利用这种算法,就必须对整个算法有比较深入的了解,根据原理重新实现自己需要的功能。而“工程”,需要让使用者对这个算法只有最小程度的了解,就可以充分利用它。要做到这一点,我们就需要对问题和算法都有更深入的抽象。
-----------------------------------------------------------------------------------------------
我们之前的算法中实现了对list和dict类型的递归结构的支持,现在我们想要将它推广到一般性的递归数据结构中。首先我们将问题进一步抽象化:
我们有数据V和数据类型T,转换的结果表示为f(V, T)。不幸的是直接计算f(V, T)的过程可能回过头来依赖f(V, T)。好在f(V, T)是个纯函数,对于相同的数据和类型永远返回相同的结果。
------------------------------------------------------------------------------------------------
我们解决这个问题主要有两种策略:
- 生成一个递归结构,如[]和{}中的那样:首先创建一个空的结构存起来,然后递归,如果递归过程中又遇到了f(V, T),直接返回提前保存了的引用,从而创建递归结构。
- 禁止自引用,如将标量转换成只有一个元素的列表时候的情况:如果没有经过其他递归结构直接重新调用了f(V, T),则直接失败
两种情况都可以归结为以下的过程:
- 准备 - 对于递归结构来说,创建空的结构并存起来;对于禁止递归来说,保存禁止递归的标记
- 递归 - 开始递归计算,如果递归中遇到了标记则进行相应的处理(直接返回或失败)
-----------------------------------------------------------------------------------------------
做到这一步,我们接下来要为这个算法提取一个面向工程的扩展接口,让使用这个接口的人只需要对算法有最小的了解即可。重新看上面的结论,对于标记的处理是固定的,需要扩展的只有:
- 如何创建空的结构(或者不创建,即禁止自引用)
- 如何递归计算
我们设计的接口也只要提供这样的能力就足够了,这就是答案2.0版本中对于类型检查过程的抽象:
class CustomizedChecker(object):
"""
Inherit from this class to create a customized type checker
"""
def __init__(self, *args, **kwargs):
"""
Call bind()
"""
if args or kwargs:
self.bind(*args, **kwargs)
def bind(self):
"""
Allow delayed init
"""
pass
def pre_check_type(self, value):
"""
First-step check for value. This step should not do any recursive check.
:param value: value to be checked
:return: An object if recursive creation if needed, or None if not needed.
TypeMismatchException should be raised if there is something wrong.
"""
return None
def final_check_type(self, value, current_result, recursive_check_type):
"""
Second-step check for value. This step can use recursive check.
:param value: value to be checked
:param current_result: value returned by pre_check_type. If this value is not None, the return value must be the same object.
:param recursive_check_type: a function recursive_check_type(value, type) to be called to do recursive type check
"""
raise NotImplementedError这里比较重要的过程是pre_check_type和final_check_type两个,其中pre_check_type的参数很直白,就是要检查的值,在这个过程中不允许进行递归检查,通常类只做最少最必要的检查,然后选择:
- 如果该类型支持递归,则创建一个空的结构,然后返回这个新创建的对象;
- 否则返回None,表示不支持直接递归
final_check_type是进行递归的地方,它有三个参数,value还是刚才的值,current_result是pre_check_type的返回值,如果pre_check_type返回了一个非None的对象,这个过程应该修改这个对象并返回相同的引用;否则应该返回一个新的对象。recursive_check_type是个函数,调用recursive_check_type就可以进行递归的类型检查和类型转换,在其中会自动处理自引用或者禁止自引用的逻辑,而扩展接口中不需要关心这些,实现细节都封闭在了recursive_check_type这个闭包中。这样要扩展一个类型就非常容易了。
经过这一步之后,我们可以将以前的处理dict和list的代码都统一重构成CustomizedChecker的子类,主函数中的逻辑也更加简洁了:
def _customized_check(checker):
if check_id in list_loop:
raise TypeMismatchException(value, type_)
current_result = checker.pre_check_type(value)
if current_result is None:
# Prevent an infinite loop
list_loop.add(check_id)
try:
current_result = checker.final_check_type(value, None, lambda value, type: _check_type_inner(value, type, _recursive_check))
finally:
list_loop.discard(check_id)
else:
current_check[check_id] = current_result
# backup succedded check: it may depends on current result. If the type match fails, revert all succeeded check
_new_recursive_check = (current_check, dict(succeded_check), failed_check, set())
checker.final_check_type(value, current_result, lambda value, type: _check_type_inner(value, type, _new_recursive_check))
# copy succeeded checks
succeded_check.clear()
succeded_check.update(_new_recursive_check[1])
return current_result使用新的面向工程的接口,在做自定义扩展的时候就一点都不担心会做错了。
新版本的功能扩展到了什么程度呢?代码里有个例子,它可以检查一个单链表,并将它转换成一个双向链表……这并不是极限,实际上它还可以将多叉树转换成二叉树(孩子转为左子树,兄弟转为右子树),边表转换成邻接表,递归结构转换成id标识的非递归结构等等……
它基本上变成一种新的编程语言了(捂脸)