大鹏

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

函数进阶

 本章内容如下:

    1、楔子
    2、命名空间和作用域
      2.1 命名空间
      2.2作用域
      2.3局部变量和全局变量
        局部变量
        全局变量
        global关键字
    3、函数的嵌套和作用域链
    4、内嵌函数
    5、闭包
    6、lambda表达式
    7、介绍两个BIF:filter()和map()
    8、递归
    9、本章小结
    10、作业
    11、扩展阅读

 

++++++++++++++++++++++++++++++

 

 

1、楔子

假如有一个函数,实现返回两个数中的较大值:

def my_max(x,y):
    m = x if x>y else y
    return m
bigger = my_max(10,20)
print(bigger)

之前是不是我告诉你们要把结果return回来你们就照做了?可是你们有没有想过,我们为什么要把结果返回?如果我们不返回m,直接在程序中打印,行不行?

来看结果:

复制代码
>>> def my_max(x,y):
...     m = x if x>y else y
... 
>>> my_max(10,20)
>>> print(m)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'm' is not defined
复制代码

报错了!错误是“name 'm' is not defined”。变量m没有被定义。。。为啥?我明明定义了呀!

在这里我们首先回忆一下python代码运行的时候遇到函数是怎么做的。

从python解释器开始执行之后,就在内存中开辟了一个空间

每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。

但是当遇到函数定义的时候解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。

等执行到函数调用的时候,python解释器会再开辟一块内存来存储这个函数里的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内存中。函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

我们给这个“存放名字与值的关系”的空间起了一个名字——叫做命名空间

代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间

 

 

2、命名空间和作用域

命名空间的本质:存放名字与值的绑定关系

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
python之禅

在python之禅中提到过:命名空间是一种绝妙的理念,让我们尽情的使用发挥吧!

 

2.1 命名空间一共分为三种:

  全局命名空间

  局部命名空间

  内置命名空间

*内置命名空间中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。

三种命名空间之间的加载与取值顺序:

加载顺序:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

取值:

  在局部调用:局部命名空间->全局命名空间->内置命名空间

#在局部使用变量取值情况
x = 1
def f(x):
    print(x)

print(10)
在局部使用变量取值情况

  在全局调用:全局命名空间->内置命名空间

#在全局引用变量x
x = 1
def f(x):
    print(x)

f(10)
print(x)
在全局引用变量x
#在全局引用内置max
print(max)
在全局引用内置max

 

 

2.2作用域

作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。

全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效

局部作用域:局部名称空间,只能在局部范围生效

globals和locals方法

#在全局调用globals和locals
print(globals())
print(locals())
在全局调用globals和locals
def func():
    a = 12
    b = 20
    print(locals())
    print(globals())

func()
在局部调用globals和locals

 

global关键字

a = 10
def func():
    global a
    a = 20

print(a)
func()
print(a)
global关键字

 

2.3局部变量和全局变量

局部变量

#局部变量
#例子1
"""
discounts()函数中,有参数prince和rate,还有一个final_price,他们都是discounts()函数中的局部变量。
"""
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)
"""
在函数里边定义的参数以及变量,都称为局部变量,出了这个函数,这些变量就是无效的。
事实上的原理是python在运行函数的时候,利用栈(stack)进行存储,当执行完该函数后,函数中的所有数据都会被自动删除。所以
在函数外边是无法访问到函数内部的局部变量的
"""

#例子2
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)  #程序报错,不能调用局部变量

 

全局变量

#全局变量
#例子3
def discounts(price, rate):
    final_price = price * rate
    print('这里试图打印全局变量old_price的值:', old_price)
    return final_price

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





#如果在函数内部试图修改全局变量,那么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)



"""
全局变量在整段代码中都是可以访问到的,但是不要试图在函数内部去修改全局变量的值,因为那样Python会自动在函数内部新建一个名字
一样的局部变量替代
"""

 

global关键字

"""
全局变量的作用域是整个模块(整个代码段),也就是代码段内所有函数内部都可以访问到全局变量。
但是要注意,在函数内部仅仅是去访问全局变量就好,不要试图去修改它。

因为那样的话,python会使用屏蔽(shadowing)的方式"保护"全局变量:一旦函数内部试图修改全局变量,
python就会在函数内部自动创建一个名字一模一样的局部变量,这样修改的结果只会修改到局部变量,而不会影响全局变量。

"""

#举例
count = 5
def myFun():
    count = 10
    print(count)

myFun()   # 10 
print(count)  # 5

 

要想实现修改全局变量,我们可以使用global关键字来达到目的

#使用关键字global关键字来修改函数内部的全局变量
count = 5
def myFun():
    global count
    count = 10
    print(count)

myFun()   # 10
print(count)  # 10

 

3、函数的嵌套和作用域链

 

4、内嵌函数

"""
python的函数定义的可以嵌套的,也就是允许在函数内部创建另一个函数,这种函数叫作内嵌函数或者内部函数
"""


#内部函数整个作用域都在外部函数之内
#fun2()整个函数的作用域都是在fun1()里边
def fun1():
    print("fun1()正在被调用....")
    def fun2():
        print("fun2()正在被调用....")
    fun2()

#调用函数fun1()
fun1()
"""
结果:
fun1()正在被调用....
fun2()正在被调用....

"""

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

# 结果:NameError: name 'fun2' is not defined

 

5、闭包

闭包

 闭包的定义及示例

''' 
python里的闭包定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,
那么内部函数就被认为是闭包(closure)
'''
def funX(x):
    def funY(y):
        return x * y
    return funY

i = funX(8)
print(i(5))  # 40

#也可以直接这么写
j =funX(8)(5)
print(j)
#40

"""
如果在一个内部函数里(funY就是这个内部函数)对外部作用域(但不是全局作用域)的变量进行引用
(x就是被引用的变量,x在外部作用域funX函数里面,但不在全局作用域里),则这个内部函数(funY)就是一个闭包。

使用闭包需要注意的地方是:因为闭包的概念就是由内部函数而来的,所以你额不能在外部函数以外的地方对内部函数进行调用。

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

 

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

funX()()   #报错 UnboundLocalError: local variable 'x' referenced before assignment

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

 

改进方案一:

#改进方案一:在python3之前没有直接的方案,只能间接的通过容器类型来存放,因为容器类型不是放在栈里,所有不会被屏蔽掉。
def funX():
    x = [5]   #使用容器,如字符串,列表元组
    def funY():
        x[0] *= x[0]
        return x[0]
    return funY

funX()() 

 

改进方案二:使用nonlocal的关键字

#改进方案二:使用nonlocal的关键字
#使用关键字nonlocal,在内部函数里修改外部函数里的局部变量的值
def funX():
    x = 5
    def funY():
        nonlocal x
        x *= x
        return x
    return funY


y = funX()()
print(y)   # 25

 

[扩展阅读] 游戏中的角色移动:闭包(closure)在实际开发中的作用

 

6、lambda表达式

 

#使用lambda关键字来创建匿名函数

''' 
lambda 表达式语法:
在冒号(:)左边放原函数的参数,可以有多个参数,用逗号(,)隔开即可;冒号右边是返回值。
'''
def ds(x):
    return 2 * x + 1

ds(5)

#使用lambda 来定义
g = lambda x:2 * x + 1
g(5)
示例
#普通函数
def add(x,y):
    return x + y

add(3,4)

#把它转换为lambda 表达式
g = lambda x,y:x + y
g(3,4)

'''
lambda表达式的作用:

'''

 

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

 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的值筛选出来
'''

temp = filter(None,[1,0,False,True])  #第一个参数为None
print(list(temp))
# [1, True]
#写一个筛选奇数的过滤器
def odd(x):
    return x % 2

temp= filter(odd,range(10))  #第一个参数为函数
print(list(temp))
# [1, 3, 5, 7, 9]


#使用lambda表达式来改写
g = list(filter(lambda x:x % 2,range(10)))
print(g)
#[1, 3, 5, 7, 9]

 

 map()

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

# 与filter()类似
g = list(map(lambda x: x * 2 ,range(10)))
print(g)
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] 

 

 

8、递归

 递归函数

 8.1递归是什么“神马”

#递归,从原理上来说就是函数调用自身这么一个行为

def recursion():
    recursion()  #调用自身


recursion() #执行函数


++++++++++++++++++++++
import  sys
sys.setrecursionlimit(100000)  #设置递归的深度

 

8.2 写一个求阶乘的函数

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

    return result

number = int(input('请输入一个正整数:'))
result = factorial(number)

print("%d 的阶乘是:%d" % (number, result))

改进的递归版本:

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、设置了正确的返回条件

 

8.3 这帮小兔崽子

 

迭代的方式:

def fab(n):
    n1 = 1
    n2 = 1
    n3 = 1

    if n < 1:
        print('输入有误!')
        return -1

    while (n-2) > 0:
        n3 = n2 + n1
        n1 = n2
        n2 = n3
        n -= 1
    
    return n3

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(35)
if result != -1:
    print('总共有%d对小兔崽子诞生!' % result)

 

8.4汉诺塔

 

def hanoi(n, x, y, z):
    if n == 1:
        print(x, ' --> ', z)
    else:
        hanoi(n-1, x, z, y) # 将前n-1个盘子从x移动到y上
        print(x, ' --> ', z) # 将最底下的最后一个盘子从x移动到z上
        hanoi(n-1, y, x, z) # 将y上的n-1个盘子移动到z上

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

 

9、本章小结

命名空间:

  一共有三种命名空间从大范围到小范围的顺序:内置命名空间、全局命名空间、局部命名空间

作用域(包括函数的作用域链):

小范围的可以用大范围的
但是大范围的不能用小范围的
范围从大到小(图)

在小范围内,如果要用一个变量,是当前这个小范围有的,就用自己的
如果在小范围内没有,就用上一级的,上一级没有就用上上一级的,以此类推。
如果都没有,报错

函数的嵌套:

  嵌套调用

  嵌套定义:定义在内部的函数无法直接在全局被调用

函数名的本质:

  就是一个变量,保存了函数所在的内存地址

闭包:

  内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数

 

10、作业

[课后作业] 第020讲:函数:内嵌函数和闭包 | 课后测试题及答案

[课后作业] 第021讲:函数:lambda表达式 | 课后测试题及答案

[课后作业] 第022讲:函数:递归是神马 | 课后测试题及答案

[课后作业] 第023、024讲:递归:这帮小兔崽子、汉诺塔 | 课后测试题及答案

 

11、扩展阅读

 

posted on 2018-12-23 12:29  pf42280  阅读(438)  评论(0)    收藏  举报