零基础入门Python小甲鱼-笔记9

第六章 函数-下

原文再续,书接上一回

6.3 我的地盘听我的

6.3.1 函数和过程

函数和过程的区别

  • 函数是有返回值的
  • 过程是简单、特殊并且没有返回值的

Python严格来说只有函数,没有过程。函数默认会有返回值

>>> def hello():
    print('Hello')

>>> print(hello())
Hello
None

通过上面小demo可以看出当不写return语句时,默认Python会认为函数是return None。所以说Python所有的函数都有返回值。

6.3.2 再谈一谈返回值

Python同时返回多个值,使用列表或元组打包

Python利用列表打包多种类型的值一次性返回

>>> def test():
    return [1,2,3,4,5]
>>> test()
[1, 2, 3, 4, 5]

6.3.3 函数变量的作用域

变量的作用域就是平时所说的变量可见性。一般的编程语言都有局部变量(Local Variable)和全局变量(Global Variable)之分

通过下面的小demo来学习(可以先把最后面那一条语句注释掉,运行一下先):

def discounts(price,rate):
    final_price = price * rate
    return final_price

old_price = float(input('请输入原价:'))
rate = float(input('请输入折扣率:'))
new_price = discounts(old_price,rate)
print('打折后价格是:',new_price)
print('这里试图打印局部变量final_price的值:',final_price)

运行整个文件的结果,程序有出错:

请输入原价:80
请输入折扣率:0.75
打折后价格是: 60.0
Traceback (most recent call last):
  File "F:/零基础入门学习python-小甲鱼/p6_2.py", line 9, in <module>
    print('这里试图打印局部变量final_price的值:',final_price)
NameError: name 'final_price' is not defined

解释一下函数discounts里面的三个参数都是局部变量。

错误原因:final_price没有被定义过,也就是说Python找不到final_price这个变量。这是因为final_price只是一个局部变量,它的作用范围只在它的地盘上,超出了这个范围,它将什么都不是。

总结:

  • 在函数里边定义的参数以及变量,都称为局部变量,出了这个函数,这些变量都是无效的。
  • 事实上的原理是,Python在运行函数的时候,利用栈(Stack)进行存储,当执行该函数后,函数中的所有数据都会被自动删除。所以在函数外边是无法访问到函数内部的局部变量的。

全局变量:

全局变量拥有更大的作用域,例如可以在函数中可以访问到它们

如果在函数内部试图修改全局变量,那么Python会创建一个新的局部变量替代(名字跟全局变量相同),但是真正的全局变量是没有被改变的。

def discounts(price,rate):
    final_price = price * rate
    old_price = 50 #在这里试图修改全局变量
    print('在局部变量中修改后的old_price的值:',old_price)
    return final_price

old_price = float(input('请输入原价:'))
rate = float(input('请输入折扣率:'))
new_price = discounts(old_price,rate)
print('全局变量old_price的值:',old_price)
print('打折后价格是:',new_price)

运行整个文件的结果:

请输入原价:80
请输入折扣率:0.75
在局部变量中修改后的old_price的值: 50
全局变量old_price的值: 80.0
打折后价格是: 60.0

全局变量的总结:

全局变量在整个代码段中都是可以访问到的,但是不要试图在函数内部去修改全局变量的值

6.4 内嵌函数和闭包

6.4.1 global 关键字

全局变量的作用域是整个模块(整个代码段),也就是代码段内所有的函数内部都可以访问全局变量。

PS.函数内部仅仅去访问全局变量就好,不要试图去修改它。

再说一下:

Python会使用屏蔽的方式”保护”全局变量,一旦函数内部试图修改全局变量,Python就会在函数内部自动创建一个名字一模一样的局部变量,这样修改的结果只会修改到局部变量,而不会影响到全局变量。

小demo:

>>> count = 5
>>> def myFun():
    count = 10
    print(count)    
>>> myFun()
10
>>> count
5

在函数中修改全局变量可能会导致程序可读性变差、出现莫名其妙的BUG、代码的维护成本提高

如果是有必要在函数中去修改这个全局变量,那么你可以使用global关键字来达到目的!!

>>> count = 5
>>> def myFun():
    global count
    count = 10
    print(count)

>>> myFun()
10
>>> count
10

6.4.2 内嵌函数

Python你的函数定义是可以嵌套的,也就是允许在函数内部创建令一个函数,这种函数叫做内嵌函数或者是内部函数。

下面通过一个小demo(函数嵌套最简单的例子):

>>> def fun1():
    print('fun1()正在被调用')
    def fun2():
        print('fun2()正在被调用')
    fun2()

>>> fun1()
fun1()正在被调用
fun2()正在被调用

PS.内部函数整个作用域(fn2()整个函数)都在外部函数(fun1()函数)之内。

fun1()这个函数体可以随意调用fun2()这个内部函数外,出了fun1(),就没有任何可以对fun2()进行的调用。

如果在fun1()外部试图内部函数fun2(),就会报错:

>>> fun2()
Traceback (most recent call last):
  File "<pyshell#59>", line 1, in <module>
    fun2()
NameError: name 'fun2' is not defined

上面的结果说明fun2()只能在fun1()的地盘上面的被访问。

6.4.3 闭包(closure)

闭包是函数式编程的一个重要的语法结构,函数式编程是一种编程分范式。

Python的闭包从表现形式上定义为:

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

小demo:

>>> def funX(x):
    def funY(y):
        return x * y
    return funY
>>> i =funX(8)
>>> i(5)
40
#funX(8)(5)也可以实现上面的效果

如果在一个内部函数里(FunY就是这个内部函数),对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

PS.因为闭包的概念是由内部函数而来的,所以你也不能在外部函数以外的地方对内部函数进行调用,下面的做法是错误的:

>>> funY(5)
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    funY(5)
NameError: name 'funY' is not defined

在闭包中,外部函数的局部变量对应内部的函数的局部变量,事实上也相当于之前的全局变量跟局部变量的对应的关系,在内部函数中,你只能对外部函数的局部变量进行访问,但不能进行修改。

>>> def funX(x):
    x = 5
    def funY(y):
        x *= x
        return x * y
    return funY

>>> funX()()
Traceback (most recent call last):
  File "<pyshell#71>", line 1, in <module>
    funX()()
TypeError: funX() missing 1 required positional argument: 'x'

报错原因:Python认为在内部函数的x是局部变量的时候,外部函数的x被屏蔽了起来,所以执行x *= x的时候,在右边根本就找不到局部变量x的值

解决方法:

  • 方法1:间接地通过容器类型(字符串,列表,元组)来存放,因为容器类型不是放在栈里,所以不会被”屏蔽”掉。
  • 方法2:使用关键字nonlocal

方法1:

>>> def funX():
    x = [5]
    def funY():
        x[0] *= x[0]
        return x[0]
    return funY

>>> funX()()
25

方法2:

>>> def funX():
    x = 5
    def funY():
        nonlocal x
        x *= x
        return x
    return funY

>>> funX()()
25

6.6 lambda 表达式

Python允许使用lambda关键字来创建匿名函数。

使用匿名函数的有什么优势?lambda表达式语法非常精简

匿名函数的语法:

  • 在冒号(:)左边放原函数的参数,可以有多个参数,用逗号(,)隔开即可
  • 冒号右边是返回值

普通函数:

>>> def ds(x):
    return 2 * x +1

>>> ds(5)
11

lambda来定义这个函数:

>>> lambda x:2 * x +1
<function <lambda> at 0x05983CD8>
#lambda语句是实际上是返回一个函数的对象,如果是要使用它,只需要进行简单的赋值操作即可
>>> g = lambda x:2 * x +1
>>> g(5)
11

lambda表达是带两个参数:

#普通函数
>>> g(5)
11
>>> def add(x,y):
    return x + y
>>> add(3,4)
7
#把它转换为lambda表达式
>>> g = lambda x,y:x + y
>>> g(3,4)
7

lambda表达式的作用:

  • Python写一些执行脚本时,使用lambda就可以省下定义函数过程。
  • 使用lambda就不需要考虑命名的问题。对于一些比较抽象并且整个程序执行下来执行要调用一两次的函数,有时候给函数起一个名字也是比较头疼的问题。
  • 简化代码的可读性。

介绍两个BIF :filter() 和map()

1.filter()

这个函数是一个过滤器。通过过滤器,就可以保留你想要的信息(你认为有趣的事情),把其他不感兴趣的东西直接丢掉。

>>> help(filter)
Help on class filter in module builtins:

class filter(object)
 |  filter(function or None, iterable) --> filter object
 |  
 |  Return an iterator yielding those items of iterable for which function(item)
 |  is true. If function is None, return the items that are true.

filter有两个参数。第一个参数是函数/None

  • 如果是一个函数的话,则将第二个可迭代数据里面的每一个元素作为函数的参数进行计算,把返回True的值筛选出来的
  • 如果是None,则直接将第二参数中的True的值筛选。

下面通过小demo:

>>> temp = filter(None,[1,0,False,True])
>>> list(temp)
[1, True]

利用filter(),尝试写一个筛选奇数的过滤器:

>>> def odd(x):
    return x % 2

>>> temp = filter(odd,range(10))
>>> list(temp)
[1, 3, 5, 7, 9]

使用lambda表达式:

>>> list(filter(lambda x : x %2,range(10)))
[1, 3, 5, 7, 9]

2.map()

map一般作作”映射”来解释。map()这个内置函数也有两个参数,仍然是一个函数和一个可迭代序列,将序列的每一个元素作为函数的参数进行加工,直到可迭代序列每个元素都加工完毕,返回所有加工后的元素构成的新的序列。

>>> list(map(lambda x :x * 2,range(10)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

6.6 递归

6.6.1 递归是”神马”

递归:从原理上来说就是函数调用自身这么一个行为(在函数内部你可以调用所有可见的函数,当然也包括自己)

举个栗子:

def recursion():
    recursion()

这个程序将会永远执行下去直至耗尽所有的内存资源。python3对递归的深度默认限制是100层,所以你的代码才会停下来。

方法如下:

import sys
sys.setrecursionlimit(1000000)  #将递归限制设置为100万层

如果你的递归不能停下啦,可以是通过Ctrl+C让它停止下来。

6.6.2 写一个求阶乘的函数

正整数的阶乘是指从1乘以2乘以3乘以4一直乘到所有的所要求的数。

所要求的的数是5,则阶乘式是1×2×3×4×5

非递归版本:

def recursion(n):
    result = n
    for i in range(1,n):
        result *= i
    return result

number = int(input("请输入一个整数:"))
result = recursion(number)
print("%d的阶乘:%d"%(number,result))

程序实现的结果如下:

请输入一个整数:5
5的阶乘:120

递归版本(装逼的版本):

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

number = int(input("请输入一个整数:"))
result = factorial(number)
print("%d的阶乘: %d"%(number,result))

该程序实现的效果跟上面非递归的版本一样的。

递归的条件(麻雀虽小,五脏俱全):

  1. 调用函数本身

  2. 设置了正确的返回条件

普通的程序员用迭代,天才程序员用递归

递归的实现可以是函数自个儿调用自个儿,每次函数的调用都需要压栈、弹栈、保存和恢复寄存器的栈操作,所以在上边是非常消耗时间和空间。

救使用口诀:递归递归,归去来兮!

递归妙在自然简洁、精炼。

6.6.3 这帮兔崽子

斐波那契数列的发明者,是意大利数学家列昂纳多.斐波那契。小甲鱼注重过程和细节,而那个数学家更关心结果。

如果说兔子在出生两个月后,就有了繁殖能力,在拥有繁殖能力之后,这对兔子每个月能生出一对小兔子来。假设所有兔子都不会死去,能够一直繁衍下去,那么一年之后可以繁殖多少对兔子呢?

所经过的月数 1 2 3 4 5 6 7 8 9 10 11 12
兔子的总对数 1 1 2 3 5 8 13 21 34 55 89 144

可以用数学函数来定义:

当n = 1时,F(n) = 1

当n = 2时,F(n) = 1

当n > 2时,F(n) = F(n-1) + F(n-2)

本节使用递归来实现斐波那契数列。

假设需要求出经历了20个月后,总共有多少对小兔崽子,不妨一起来考虑一下分别用迭代和递归如何实现?

迭代实现:

def fab(n):
    a1 = 1
    a2 = 1
    a3 = 1
    if n < 1:
        print('输入有误!')
        return -1
    while(n -2) > 0:
        a3 = a1 + a2
        a1 = a2
        a2 = a3
        n -= 1
    return a3

result = fab(20)
if result != -1:
    print('总共有%d只小兔崽子诞生!'%result)

递归实现:

def fab(n):
    if n < 1:
        print('输入有误!')
        return -1
    if n == 1 or n == 2:
        return 1
    else:
        return fab(n-1) + fab(n-2)


result = fab(20)
if result != -1:
    print('总共有%d只小兔崽子诞生!'%result)

用迭代代码来实现基本是毫秒级的.而使用递归来实现就考验你的CPU能力啦(N秒-N分钟不等)

这就是小甲鱼不支持大家所有东西都用递归求解的原因,本来好好一个代码,给你用了递归,效率反而拉下了一大截。

不懂得递归,试图想要写一个程序来解决问题是相当困难的,如果是是用来递归,你会发现问题变得奇迹般的简单。

6.6.4 汉诺塔

一位法国数学家曾编写过一个印度的古老传说:说的是,在世界中心贝拿勒斯的圣庙里边,有一块黄铜板,上边插着三根宝针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。然后不论白天或者黑夜,总有一个僧侣在按照下面的法则来移动这些金片:”一次只移动一片,不管在哪根针上,小片必须在大片上面。”规则很简单,另外僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。

对于游戏的玩法,可以简单分解为三个步骤:

  1. 将前63个盘子从X移动到Y上,确保大盘在小盘下。
  2. 将最底下的第64个盘子从X移动到Z上。
  3. 将Y上的63个盘子移动到Z上。

在游戏中我们发现由于每一次只能移动一个圆盘,所以在移动的过程中显然要借助另外一根针才可以实施。

思路:步骤(1)将1-63个盘子需要借助Z移到Y上,步骤(3)将Y针上的63个盘子需要借助X移到Z针上。

新思路聚集为以下两个问题。

  • 问题一:将X上的63个盘子借助Z移到Y上
  • 问题二:将Y上的63个盘子借助X移到Z上

解决这两个问题的方法跟刚才第一个问题的思路是一样的,都可以才拆分为3个步骤来实现。

问题一:将X上的63个盘子借助Z移到Y上 拆解为:

  1. 将前62个盘子从X移动到Z上,确保大盘在小盘下句号
  2. 将最底下的第63个盘子移动到Y上
  3. 将Z上的62个盘子移动到Y上

汉诺塔的拆解过程刚好满足递归算法的定义,使用递归来解决

def hanoi(n,x,y,z):
    if n == 1:
        print(x,'--->',z)   #如果只有一层,直接从x移动到z
    else:
        hanoi(n-1,x,z,y)    #将前n-1个盘子从x移动到y上,根据移动的顺序排列
        print(x,'--->',z)   #将最底下的64个盘子从x移动到z上
        hanoi(n-1,y,x,z)    #将y上的63个盘子移动到z上

n = int(input('请输入汉诺塔的层数:'))
hanoi(n,'X','Y','Z')

参考资料是:零基础入门学习Python小甲鱼的书和视频教程

猜你喜欢

转载自blog.csdn.net/xyyojl/article/details/80633278