变量的作用域
-
讲到了函数就必须介绍变量的作用域相关。
- 作用域指的是变量的有效范围。变量并不是在哪个位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内赋的值。变量在哪个作用域内赋值,则表示该变量的作用域就是该区域,变量只可以在其作用域指定区域被访问。
-
通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在Python中,没有块级作用域,也就是类似if语句块、for语句块、while语句块,with上下文管理器等等是不存在作用域概念的,他们等同于普通的语句。
-
核心:
-
通常,函数内部的变量无法被函数外部访问,但内部可以访问;类内部的变量无法被外部访问,但类的内部可以。通俗来讲,就是内部代码可以访问外部变量,而外部代码通常无法访问内部变量。
-
age = 10 def func(): name = 'bobo' print(age,name)#函数内部可以访问外部变量 func() print(name) #在函数外部是无法访问函数内部变量
-
def outer(): def inner(): print('i am inner') inner() #在函数内部可以调用inner return inner inner() #在函数外部不可以调用inner,因为超出了inner的作用域
-
全局变量和局部变量
-
局部变量
- 定义在函数内部的变量拥有一个局部作用域,被叫做局部变量
-
全局变量
- 定义在函数外的拥有全局作用域的变量,被称为全局变量。(类、模块等同理)
num = 123 #全局变量
def func():
email = 'www.123.com' #局部变量
print(email,num)
func()
#全局变量在全局区域都可以被访问
- 注意:
- 所谓的局部变量是相对的。局部变量也有可能是更小范围内的变量的外部变量。
a = 1 #全局变量
def func():
b = 2 #局部变量
print(a) #全局变量可以在全局区域被访问
def inner():
c = 3 #更局部的变量
print(a)
print(b)
print(c)
return inner
result = func()
result()
global关键字
-
先来看一个例子
-
total = 0 #全局变量 def plus(arg1,arg2): total = arg1 + arg2 print('函数内的局部变量total=',total) print('函数内的total的地址是:',id(total)) return total plus(10,20) print('函数外部全局变量total=',total) print('函数外部变量total的地址:',id(total))
-
很明显,函数plus内部通过total = arg1 + arg2语句,新建了一个局部变量total,它和外面的全局变量total是两码事。而如果我们,想要在函数内部修改外面的全局变量total呢?使用global关键字!
-
global:
- 指定当前变量使用外部的全局变量
-
total = 0 #全局变量 def plus(arg1,arg2): global total #在函数内部引用了外部的全局变量total total = arg1 + arg2 print('函数内的局部变量total=',total) print('函数内的total的地址是:',id(total)) return total plus(10,20) print('函数外部全局变量total=',total) print('函数外部变量total的地址:',id(total))
函数其他
我们将函数涉及到的其他几点进行讲解
- range()函数
- 递归函数
- 匿名函数
range函数
-
range函数是内置函数,无须特别导入,在任何地方都可以直接使用它,下面看一下具体用法:
-
1.提供一个数字参数,直接遍历数字:
-
for item in range(5): print(item)
- 只给一个数字类型参数,range会遍历从0到参数减1的数字。要特别注意,range默认从0开始,到参数减1,也就是左闭右开的规则,这也是Python很多地方的规则,比如切片。
-
2.也可以指定遍历的区间:
-
for item in range(1,5): print(item)
-
3.还可以指定步长,就像切片一样
-
for item in range(1,11,3): print(item)
-
4.但更多的时候是结合range和len函数,遍历一个序列的索引
-
alist = [1,2,3,4,5] for i in range(len(alist)): print(alist[i])
-
5.指定步长为-1可以倒序遍历
-
for i in range(6,1,-1): print(i)
递归函数
-
如果一个函数在内部调用了自身,这个函数就被称为递归函数。
-
def func(): print('正在调用func函数') func() func()
-
What?函数可以自己调用自己?会不会进入死循环,永远退出不了?我们先看一个例子,典型的高斯求和问题,1+2+3+4+…+99+100,不使用递归的话,我们可以用循环,这么做:
-
def sum_number(n): sum = 0 for i in range(1,n+1): sum += i return sum result = sum_number(5) print(result)
-
但如果使用递归函数来写,是这样的:
-
def sum_number(n): if n == 1: return 1 return n + sum_number(n-1) print(sum_number(5))
- 分析一下代码,当n小于等于0的时候,直接给出和值为0,这句不能省。当n大于0时,结果是n加上sum_number(n-1)。这里的sum_number(n-1)又是一次sum_number函数的调用,不过参数的值变成了n-1,要得sum_number(n)到的值就必须等待sum_number(n-1)的值被计算出来,同样要得到sum_number(n-1)的值必须等待sum_number(n-2)的值,如此一路推算下去,直到sum_number(0),因为if语句的存在,它不需要等待sum_number(-1)的计算了,而是直接给出结果0。然后程序一路返回,直到回到最初的sum_number(n),并给出最终结果。
-
递归最核心的思想是:
- 每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果!
-
递归的优点
- 递归函数的优点是定义简单,代码量少,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
-
注意:
- 使用递归函数需要注意防止递归深度溢出,在Python中,通常情况下,这个深度是1000层,超过将抛出异常。在计算机中,函数递归调用是通过栈(stack)这种数据结构实现的,每当进入一个递归时,栈就会加一层,每当函数返回一次,栈就会减一层。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
def sum_number(n):
if n == 1:
return 1
return n + sum_number(n-1)
print(sum_number(5))
'''
递归调用的执行过程:
sum_number(5):5 + sum_number(4)
sum_number(4):4 + sum_number(3)
sum_number(3):3 + sum_number(2)
sum_number(2):2 + sum_number(1)
sum_number(1):1
'''
匿名函数
-
提问:
- 刚开始编程的同学可能会遇到一个头疼的问题,就是变量或者函数命名的问题,虽然命名规范很简单,但是如果涉及到的操作过多给每一个操作的函数都起一个高可读性的名称,必然会挖空你的英语词汇量。
-
当我们在创建函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。这省去了我们挖空心思为函数命名的麻烦,也能少写不少代码,很多编程语言都提供这一特性。匿名函数用好了,会有画龙点睛的效果,没用好,就容易“画虎不成反类犬”,需要我们在平时的代码过程中,多学、多看、多琢磨。
-
匿名函数的使用
- Python语言使用lambda关键字来创建匿名函数。
- 所谓匿名,即不再使用def语句这样标准的形式定义一个函数。
- lambda只是一个表达式,而不是一个代码块,函数体比def简单很多。
- 仅仅能在lambda表达式中封装有限的逻辑。
- 定义语法:
- 其形式通常是这样的:lambda 参数: 表达式
-
例子:它相当于下面的函数:
-
def func(num): if num > 0: return 1 else: return 0 #该匿名函数就等同于上面的有名函数 result = lambda x:1 if x > 0 else 0 #x就是匿名函数的参数,0和1就是返回值 print(result(1)) #调用匿名函数
-
匿名函数只能有一个表达式,不用也不能写return语句,表达式的结果就是其返回值。