1-Python - 递归与面向过程编程

递归

我们来聊点函数部分的高级话题,递归调用。

递归调用与无限递归

程序在调用一个函数的过程中,直接或者间接调用了该函数本身,我们称为递归调用:

def recursion():  
    print('recursion function')  
    recursion()  

recursion()  # RecursionError: maximum recursion depth exceeded while calling a Python object  

def foo():  
    print('foo function')  
    bar()  

    def bar():  
    print('bar function')  
    foo()  

foo()  # RecursionError: maximum recursion depth exceeded while calling a Python object  

上例中,recursion函数在执行打印任务后又再直接次调用自己,再次执行打印任务,然后再次调用自己,这样无休止的打印、调用自己。而foo和bar函数,在foo函数中,调用bar函数,在bar函数中调用foo函数,这样称为间接调用自己。而无论哪种调用,函数执行最后都报了同样的错误RecursionError。Python解释器自动终止了函数的无休止的调用。这种递归也称为无限递归(死递归)。而Python解释器为了阻止这种死递归,设置了递归深度。也就是限制了递归的次数。如下示例:

import sys  
print(sys.getrecursionlimit())  # 1000  
sys.setrecursionlimit(1100)     # 手动设置递归深度  
print(sys.getrecursionlimit())  # 1100  

通过sys模块在第2行获取Python解释器默认的递归深度(根据平台不同数值可能有所出入,比如997),通过第3行手动设置递归深度并立即生效(第4行)。再通过一些例子来学习一下递归函数的应用。

递归的应用

计算1+2+3+…+x的和

def mySum(x):  
    if x == 1:  								# 递归结束条件
        return 1  
    return mySum(x - 1) + x  
  
print(mySum(5))    							# 15  
''' 
mySum(5)     
    mySum(4) + 5    15 
        mySum(3) + 4    10 
            mySum(2) + 3    6 
                mySum(1) + 2    3 
                              1           
'''  

上例中,第6行执行mySum递归函数并传递参数x,第2行为递归设立结束条件,第4行开始进行递归循环。而循环过程正如第8行开始执行递归到最深层第12行,当变量x为1的时候,结束递归,开始执行第4行的return计算,而计算过程就是从第12行往上执行到了第8行。计算过程是,在递归结束时返回了1,而1就是第12行的mySum函数这一层递归时返回的结果为1,加上这一层的变量x,结果为3。而3则是第11行mySum执行结果,再加上当前层变量值为6,就这样,当前层的执行结果为上一层的函数执行结果,加上当前层的变量值,直到第9行,得到最终的结果15。

上面的例子还可以有另一种更加优美的写法——三元表达式:

def mySum(x):  
    return 1 if x == 1 else mySum(x - 1) + x  
print(mySum(5))    							# 15  

但有时,递归更偏技巧一些,不如循环语句自然:

def whileSum(x):  
    sum = 0  
    i = 1  
    while i <= x:  
        sum += i  
        i = i + 1  
    return sum  
print(whileSum(5))  							# 15 

def forSum(x):  
    sum = 0  
    for i in range(1, x + 1):  
        sum += i  
    return sum  
print(forSum(5))    							# 15   

最后对递归做个总结:

  • 递归必须有明确的结束条件,避免陷入死递归。

  • 递归使代码更加整洁、优美。

  • 递归调用过程晦涩难懂。

  • 递归非常的占用内存空间,因为每层递归都保留当前的层的状态信息,也会造成递归的时间成本更高,效率低下。

面向过程编程

我们通过一个简单的计算器来了解面向过程编程:

import re  
def cal_mini_exp(mini):  
    ''' 
    乘除计算 
    '''  
    if '*' in mini:  
        val1, val2 = mini.split('*')  
        return str(float(val1) * float(val2))  # 为了后面的替换,在这里把int转为str  
    elif '/' in mini:  
        val1, val2 = mini.split('/')  
        return str(float(val1) / float(val2))  
def dealwith(exp):  
    ''' 
    整理表达式内的符号 
    '''  
    return exp.replace('--', '+').replace('+-', '-').replace('-+', '-').replace('++', '+')  
def calculate(son_exp):  
    ''' 
        计算没有括号的表达式 
    '''  
    son_exp = son_exp.strip('()')  
    while 1:  # 完成了表达式中乘除法的计算  
        ret = re.search('\d+\.?\d*[*/]-?\d+\.?\d*', son_exp)  
        if ret:  
            mini_exp = ret.group()  
            res = cal_mini_exp(mini_exp)  # 乘除计算结果并返回结果  
            son_exp = son_exp.replace(mini_exp, res, 1)  
        else:  
            break  
    son_exp = dealwith(son_exp)  # 整理那些加加减减去重 3-+1--2之类的  
    # 最后的加减法计算  
    res = re.findall('[+-]?\d+\.?\d*', son_exp)  
    sum = 0  
    for i in res:  
        sum += float(i)  
    return str(sum)  
def remove_bracket(express):  
    ''' 
    去括号 
    把内部不再有小括号的表达式匹配出来   :\([^()]+\) 
    '''  
    while 1:  
        ret = re.search('\([^()]+\)', express)  # 是否匹配上的对象  
        if ret:  
            son_exp = ret.group()  # 子表达式  
            # 计算,先乘除后加减  
            ret = calculate(son_exp)  
            express = express.replace(son_exp, ret, 1)  
        else:  
            break  
    return express  
def main(express):  
    express = express.replace(' ', '')  # 首先是去空格  
    express = remove_bracket(express)  
    ret = calculate(express)  
    return ret  
def core():  
    print('输入计算的内容或输入Q退出'.center(30, '*'))  
    while 1:  
        express = '-1 + 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'  
        # express = input('please enter: ')  
        # express = '1 + 1'  
        if express == 'Q' or express == 'q':  
            break  
        elif '/0' in express:  
            print('0不能为被除数')  
        elif express.count('(') != express.count(')') or '=' in express:  
            print('表达式错误,请重新输入')  
        else:  
            ret = main(express)  
            print('计算结果:', ret)  
            break  
    print('eval计算结果: ', eval(express))  

core()

上例中。该计算器的逻辑就是core函数首先判断该字符串(可以是用户输入的)是否合法。然后交给另一个函数main执行去空格,然后交给remove_bracket函数去括号,然后取出其中最小括号内的算式,再将这个算式交给calculate函数执行计算,calculate函数再调用cal_mini_exp来计算乘除,然后调用dealwith函数来整理符号,经过一系列的处理后,calculate函数将计算结果替换到原来的字符串中。然后经过下一次循环,正则再匹配出一个最小括号内的算式,再去计算,然后在将计算结果替换到原来的字符串中。最后再算出结果。

由上面的例子可以可以看到,各函数相互依赖,每个函数都负责简单的任务,一起完成大的功能。这种编程方式为函数式编程,函数式编程的思想为面向过程的程序思想(Procedure Oriented Programming,简称POP),即将一组函数按照规定顺序执行,为了简化程序设计难度,面向过程的编程思想是把大的功能拆分为小的功能函数来实现。那么,面向过程思想编程有什么特点呢?

面向过程编程的优点:

  • 思路清晰。

  • 代码可读性高。

面向过程编程的缺点:

  • 由于各功能环环相套,程序扩展性差。

  • 程序的耦合性较高,导致可维护性差。

  • 在整个大的功能中,其中一个小的环节出问题,可能导致整个程序的崩溃。

我们使用函数时,应该遵循以下规则:

  • 不到万不得已,不要使用全局变量。由于全局变量的特点,多个函数同时使用一个全局变量时,如果处理不当,会增加程序的调试难度和降低可维护性,通常return语句就是解决依赖全局变量的有效手段。

  • 解耦合性,不要轻易的改变别的模块中的变量,以免导致别的模块工作异常。

  • 通常建议函数的功能应该足够精简,功能单一,一个函数完成一件事,如果一个函数有很深的嵌套,或者占了很多的代码行,这是在暗示可能函数设计的有缺陷,那么就要考虑拆分成若干的函数了。

  • 函数保持简洁,尽量简单。

  • 在函数中,注释是必要的。


欢迎斧正,that's all
posted @ 2020-11-10 11:01  听雨危楼  阅读(263)  评论(0编辑  收藏  举报