前段时间的思考题的答案v2.0

211 阅读5分钟
原文链接: zhuanlan.zhihu.com

给大家留一道Python的思考题 - 知乎专栏

前两天的Python思考题,给大家一个参考答案 - 知乎专栏

hubo1016/pychecktype

前段时间出了个类型检查函数的思考题,以及参考答案。有的时候一旦写出一个有点通用性的东西,就会去想怎么增加它的扩展性和易用性。

之前我们的算法里已经解决了递归结构(自引用)带来的各种问题,在之前的阶段中我们是将这个问题看成了一个数学问题,这可以算是“技术”,但还不够“工程”,如果有人想要利用这种算法,就必须对整个算法有比较深入的了解,根据原理重新实现自己需要的功能。而“工程”,需要让使用者对这个算法只有最小程度的了解,就可以充分利用它。要做到这一点,我们就需要对问题和算法都有更深入的抽象。

-----------------------------------------------------------------------------------------------

我们之前的算法中实现了对list和dict类型的递归结构的支持,现在我们想要将它推广到一般性的递归数据结构中。首先我们将问题进一步抽象化:

我们有数据V和数据类型T,转换的结果表示为f(V, T)。不幸的是直接计算f(V, T)的过程可能回过头来依赖f(V, T)。好在f(V, T)是个纯函数,对于相同的数据和类型永远返回相同的结果。

------------------------------------------------------------------------------------------------

我们解决这个问题主要有两种策略:

  1. 生成一个递归结构,如[]和{}中的那样:首先创建一个空的结构存起来,然后递归,如果递归过程中又遇到了f(V, T),直接返回提前保存了的引用,从而创建递归结构。
  2. 禁止自引用,如将标量转换成只有一个元素的列表时候的情况:如果没有经过其他递归结构直接重新调用了f(V, T),则直接失败

两种情况都可以归结为以下的过程:

  1. 准备 - 对于递归结构来说,创建空的结构并存起来;对于禁止递归来说,保存禁止递归的标记
  2. 递归 - 开始递归计算,如果递归中遇到了标记则进行相应的处理(直接返回或失败)

-----------------------------------------------------------------------------------------------

做到这一步,我们接下来要为这个算法提取一个面向工程的扩展接口,让使用这个接口的人只需要对算法有最小的了解即可。重新看上面的结论,对于标记的处理是固定的,需要扩展的只有:

  1. 如何创建空的结构(或者不创建,即禁止自引用)
  2. 如何递归计算

我们设计的接口也只要提供这样的能力就足够了,这就是答案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的参数很直白,就是要检查的值,在这个过程中不允许进行递归检查,通常类只做最少最必要的检查,然后选择:

  1. 如果该类型支持递归,则创建一个空的结构,然后返回这个新创建的对象;
  2. 否则返回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标识的非递归结构等等……

它基本上变成一种新的编程语言了(捂脸)