python3基础知识复习 -- 函数的递归,可迭代对象,迭代器,生成器

本文详细探讨了函数递归的原理与限制,包括递归深度限制及其优化方法——尾递归。此外,解释了迭代器和可迭代对象的概念,强调了生成器在内存效率和无限序列生成方面的优势。通过实例展示了如何用生成器实现斐波那契数列和汉诺塔问题,并讨论了列表推导式、字典推导式和其他推导式的使用。文章最后通过一个杨辉三角的生成器实例,展示了生成器在处理序列计算中的灵活性。
摘要由CSDN通过智能技术生成

函数的递归


  • 自己调用自己,重复直到超过queue depth.
  • 使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
  • 递归(分置思想)
    不正确使用递归的话,耗时 影响效率, 速度没有迭代快.
def recursion():

	return recursion()

RecursionError: maximum recursion depth exceeded

#更改queue depth:

import sys
sys.setrecursionlimit(10000)
  • 递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

eg 递归求阶乘
1

""" 求5的阶乘 54321 """

def factorial(num):
	if num == 1:
		return 1
	else:
		return num * factorial(num - 1)

number = int(input("请输入一个正整数:"))
result = factorial(number)
print("%d 的阶乘是:%d" %(number, result))
  • 解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
  • 尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
    上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
      def fact(n):
          return fact_iter(n, 1)
      
      def fact_iter(num, product):
          if num == 1:
              return product
          return fact_iter(num - 1, num * product)
  
  可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
  fact(5)对应的fact_iter(5, 1)的调用如下:
      ===> fact_iter(5, 1)
      ===> fact_iter(4, 5)
      ===> fact_iter(3, 20)
      ===> fact_iter(2, 60)
      ===> fact_iter(1, 120)
  • 尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
    遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

  • Fibonacci 斐波那契数列
    在这里插入图片描述
    在这里插入图片描述

def fib(num):
    if num < 0:
        return -1
    if num == 1 or num == 2:
        return 1
    else:
        return fib(num - 1) + fib(num - 2)


num = int(input('Please input a number here: '))
if fib(num) < 0:
    print('Invalid number here!')
else:
    print(fib(num))

def power(x, y): #模拟pow(x, y) == x ** y
	if y == 1:
		return x

	else:
		return x * power(x, y-1)

power(3 , 4)
81


def gcd(x, y): # 欧几里得算法求最大公约数(除数除以余数)
	if x % y == 0:	
		return y

	else:
		return gcd(y, x%y)

gcd(10, 2)
2
  • 递归 - 汉诺塔 the towers of hanoi
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
""" the game of Hanoi
有三根柱子A、B、C,A柱上有若干盘子
每次移动一块盘子,小的只能叠在大的上面
把所有盘子从A柱全部移到C柱上
"""

count = 0

def hanoi(n, a, b, c):
    'the game of hanoi with recursion function'
    global count
    count += 1
    if n == 1:
        print(a, '-->', c)

    else:
        hanoi(n - 1, a, c, b)
        print(a, '-->', c)
        hanoi(n - 1, b, c, a)


hanoi(3, 'A', 'B', 'C')
print('total steps: ', count)

eg.1234--> [1, 2, 3, 4,]

result = []

def get_digits(n):

	if n > 0:

	result.insert(0, n%10) #insert方法保证数字往前放

	get_digits(n//10) # 递归取得前三位-->前二位。。。

get_digits(12345)
print(result)

迭代器 vs 可迭代对象


  • 可迭代对象定义:
    可以直接作用于for循环的对象统称为可迭代对象:Iterable
    • 一类是集合数据类型,如list、tuple、dict、set、str等;
    • 一类是generator,包括生成器和带yield的generator function。
      # 使用collections.abc中的Iterable模块判断迭代器和迭代对象
 >>> from collections.abc import Iterable
    >>> isinstance([], Iterable)
    True
  • 迭代器定义:
    可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
    # 使用collections.abc中的Iterator模块判断迭代器和迭代对象
 >>> from collections.abc import Iterator
  >>> isinstance((x for x in range(10)), Iterator)
  True
  • 一个迭代器肯定是一个可迭代对象

  • 可迭代对象可以重复使用而迭代器则是一次性的

  • 生成器都是Iterator对象,但list、dict、str虽然是Iterable可迭代对象,却不是Iterator迭代器。

  • 把list、dict、str等Iterable变成Iterator可以使用iter()函数

    • 你可能会问,为什么list、dict、str等数据类型不是Iterator?

      这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
      Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

  • type() — 看元素类型

  • next(x, “没有啦”) – 如果迭代器没元素了就是说"没有啦"

  • 默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。

  • 字符串,元组等都是可迭代对象。

  • 如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对(枚举对象),这样就可以在for循环中同时迭代索引和元素本身(索引从0开始):

>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C
  # 查找列表中的最大和最小值,组成元组返回
    def findMinAndMax(L):
        if L == []:
            return (None, None)
        small = big = L[0]
        for each in L:
            if each > big:
                big = each
            if each < small:
                small = each
        return small, big
    
    
    print(findMinAndMax([]))

生成器(generator)


  • 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
  • 通常,一般的函数从第一行代码开始执行,并在什么情况下结束?

对于调用一个普通的 Python 函数,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数所有语句执行完毕。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。如果再次调用这个函数时,一切都将重新开始。

使python更为简洁,迭代器需要定义一个类并实现相应的方法才能使用,而生成器只需要在普通函数里面加上一个yield语句, 生成器的发明使得python模仿协同程序的概念得以实现.

协同程序:可以运行的独立函数调用,函数可以暂停或挂起,并在需要的时候从程序离开的地方继续或者重新开始> 生成器是一个特殊的函数,调用完成后可以暂停住,之后可以继续使用,

  • 生成器是一个特殊的迭代器的实现,因为生成器事实上就是基于迭代器来实现的,生成器只需要一个 yield 语句即可,但它内部会自动创建 iter() 和 next() 方法。
    • generator 是用来产生一系列值的
    • yield 则像是 generator 函数的返回结果
    • yield 唯一所做的另一件事就是保存一个 generator 函数的状态
    • generator 就是一个特殊类型的迭代器(iterator)
    • 和迭代器相似,我们可以通过使用 next() 来从 generator 中获取下一个值
    • 通过隐式地调用 next() 来忽略一些值
    • 生成器被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了
Eg:生成器实现Fibonacci

    # -*- coding: UTF-8 -*-
    def fib(num):
        n, a, b = 0, 0, 1
        while n < num:
            yield b
            a, b = b, a + b
            n += 1
    
    a = fib(10)
    print(a)
    
    for i in a:
        print(i)
  • 其他推导式
 #列表推导式
   a = [i for i in range(100) if not (i % 2) and i % 3]
   a
  [2, 4, 8, 10, 14, 16, 20, 22, 26, 28, 32, 34, 38, 40, 44, 46, 50, 52, 56, 58, 62, 64, 68, 70, 74, 76, 80, 82, 86, 88, 92, 94, 98]
  
  #字典推导式
   a = {i:i % 2 == 0 for i in range(10)}
   a
  {0: True, 1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False}
  
  #集合推导式
   a = {i % 3 for i in range(100)}
   a
  {0, 1, 2}
  
  #字符串推导式(X)-- 不可能
   a = "i for i in range(10)"
   a
  'i for i in range(10)'
  • 生成器推导式(使用圆括号包起来)(可以将列表推导式直接改外层括号即可产生生成器推导式)
>>> g = (a for a in range(10))
>>> g
<generator object <genexpr> at 0x034E7728>

>>> for each in g:
	print(each)

	
0
1
2
3
4
5
6
7
8
9
  • 生成器推导式可以不加圆括号直接用于函数的参数。
sum(i for i in range(20) if i % 2)

100
  • 调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值.
    • NOTE: 调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
      举个简单的例子,定义一个generator函数,依次返回数字1,3,5:
def odd():
        print('step 1')
        yield 1
        print('step 2')
        yield(3)
        print('step 3')
        yield(5)
    调用该generator函数时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:
    
    >>> o = odd()
    >>> next(o)
    step 1
    1
    >>> next(o)
    step 2
    3
    >>> next(o)
    step 3
    5
    >>> next(o)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    可以看到,odd不是普通函数,而是generator函数,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。
    
     请务必注意:调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。
    有的童鞋会发现这样调用next()每次都返回1:
    >>> next(odd())
    step 1
    1
    >>> next(odd())
    step 1
    1
  # -*- coding: UTF-8 -*-
  """ 使用把每一行看做一个list,试写一个generator,不断输出下一行的list:
  杨辉三角定义如下:
  
            1
           / \
          1   1
         / \ / \
        1   2   1
       / \ / \ / \
      1   3   3   1
     / \ / \ / \ / \
    1   4   6   4   1
   / \ / \ / \ / \ / \
  1   5   10  10  5   1
  """
  
  
  def yanghui(num):
      n = 0
      L = [1]
      while n < num:
          yield L
          L = [1] + [L[i] + L[i + 1] for i in range(len(L) - 1)] + [1]
          n += 1
  
  ylist = yanghui(6)
  
  for each in ylist:
      print(each)

觉得有帮助的记得三连哟 😃

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值