py7 函数与变量作用域
目录
暂缺
函数
定义函数
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
参数传递
在 python 中,类型属于对象,变量是没有类型的:
a=[1,2,3]
a="Runoob"
以上代码中,[1,2,3] 是 List 类型,"Runoob" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
-
不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
-
可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
def ChangeInt( a ): a = 10 b = 2 ChangeInt(b) print( b ) # 结果是 2
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
# 可写函数说明 def changeme( mylist ): "修改传入的列表" mylist.append([1,2,3,4]) print ("函数内取值: ", mylist) return # 调用changeme函数 mylist = [10,20,30] changeme( mylist ) print ("函数外取值: ", mylist)
传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
函数外取值: [10, 20, 30, [1, 2, 3, 4]]
参数
以下是调用函数时可使用的正式参数类型:
- 必需参数(位置参数positional参数)
- 关键字参数
- 默认参数
- 不定长参数
必需参数(位置参数positional参数)
必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
关键字参数必须在位置参数后面,不能出现在它前面,否则报错
#可写函数说明 def printme( str ): "打印任何传入的字符串" print (str) return #调用printme函数 printme( str = "菜鸟教程")
默认参数
调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:
def printinfo( name, age = 35 ): "打印任何传入的字符串" print ("名字: ", name) print ("年龄: ", age) return #调用printinfo函数 printinfo( age=50, name="runoob" ) print ("------------------------") printinfo( name="runoob" )
以上实例输出结果:
名字: runoob
年龄: 50
------------------------
名字: runoob
年龄: 35
不定长参数
可变长指的是实参值的个数不固定
而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs
加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。
加了两个星号 ** 的参数会以字典的形式导入。
特殊的:
def func(*args,**kwargs)定义了不定长参数,传参时,可以直接传元组和字典内的格式也可直接传列表和字典但是传列表和字典时,前面一定要加*号,如下 li = [11,2,2,3,3,4,54] func(*li) di = {'name':'wupeiqi', age:18, 'gender':'male'} func(*
不定长参数的嵌套和使用问题
def func(*a, **k): def fun1(*a, **k): # 先看fun2 print(a) print(k) pass print(a) fun1(a, k) fun1((465,), 789, {}) fun1(*a, **k) def fun2(*a, **k): print('fun2 a--', a) # a代表*a接收的元素的元组形式 print('fun2 k--', k) # k代表**k接收的元素的字典形式 print('fun2 *a--', *a) # *a代表*a接收的内容(原始内容,不是元组) print('fun2 **k--', **k) # **k代表**k接收的内容(原始内容,不是字典) pass fun2(*a, **k) # *a也是传递*a包含的原始内容,([1,2,3,4])若如此传参,*a接收的就是([1,2,3,4]), # 在列表,元组前面加*相当于取列表或者元组或者字典的内容 # 可以(*[1,2,3,4]),则*a接收的就是(1,2,3,4)
# 在列表,元组前面加*相当于取列表或者元组或者字典的内容
func(123) c = ((123,),) print(c) print(c.__len__() ) 输出结果为 (123,) ((123,), {}) {} ((465,), 789, {}) {} (123,) {} fun2 a-- (123,) fun2 k-- {} fun2 *a-- 123 fun2 **k-- ((123,),) 1
===========*args=========== def foo(x,y,*args): print(x,y) print(args) foo(1,2,3,4,5) def foo(x,y,*args): print(x,y) print(args) foo(1,2,*[3,4,5]) def foo(x,y,z): print(x,y,z) foo(*[1,2,3]) ===========**kwargs=========== def foo(x,y,**kwargs): print(x,y) print(kwargs) foo(1,y=2,a=1,b=2,c=3) def foo(x,y,**kwargs): print(x,y) print(kwargs) foo(1,y=2,**{'a':1,'b':2,'c':3}) def foo(x,y,z): print(x,y,z) foo(**{'z':1,'x':2,'y':3}) ===========*args+**kwargs=========== def foo(x,y): print(x,y) def wrapper(*args,**kwargs): print('====>') foo(*args,**kwargs) #5、命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递 可以保证,传入的参数中一定包含某些关键字 def foo(x,y,*args,a=1,b,**kwargs): print(x,y) print(args) print(a) print(b) print(kwargs) foo(1,2,3,4,5,b=3,c=4,d=5) 结果: 2 (3, 4, 5) 3 {'c': 4, 'd': 5} 此乃重点知识!!!
闭包函数
一 什么是闭包?
内部函数包含对外部作用域而非全局作用域的引用
提示:之前我们都是通过参数将外部的值传给函数,闭包提供了另外一种思路,包起来喽,包起呦,包起来哇
def counter(): n=0 def incr(): nonlocal n x=n n+=1 return x return incr c=counter() print(c()) print(c()) print(c()) print(c.__closure__[0].cell_contents) #查看闭包的元素
二 闭包的意义与应用
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
应用领域:延迟计算(原来我们是传参,现在我们是包起来)
from urllib.request import urlopen def index(url): def get(): return urlopen(url).read() return get baidu=index('http://www.baidu.com') print(baidu().decode('utf-8'))
尾调用函数
普通的函数递归或者函数调用时,因为外层函数并未结束,所以有一个”调用栈“的概念去储存函数的调用状态,这无形会浪费大量空间时间,无形使代码效率变低。
但是利用尾调函数进行优化的话,即在函数的最后一步(不一定是最后一行)调用别的函数,不会生成“调用”栈,因为在函数的最后一步调用别的函数的话原函数的状态不会保留,相当于原函数已经结束。
这里要注意:return fun(x)是尾调,而 return fun(x)+1不是,因为后者相当于又把调用后的结果赋值给了return,所以调用不是最后一步。不写return在函数尾部直接调用函数也是尾调。
return语句
return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句(或不写return)返回None
def test1(): print('1') def test2(): print('2') return 0 def test3(): print('3') return 0, 'qwe',[1,'qwee',[]],{'1':'dsf'} def test4(): print('4') return test2 def test5(): print('5') return test2() a = test1() b = test2() c = test3() d = test4() e = test5() print(a) print(b) print(c) print(d) print(e) 1 2 3 None <function test3 at 0x000001672A31A7B8> (0, 'qwe', [1, 'qwee', []], {'1': 'dsf'})
变量作用域
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:
- L (Local) 局部作用域
- E (Enclosing) 闭包函数外的函数中
- G (Global) 全局作用域
- B (Built-in) 内建作用域
以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
x = int(2.9) # 内建作用域 g_count = 0 # 全局作用域 def outer(): o_count = 1 # 闭包函数外的函数中 def inner(): i_count = 2 # 局部作用域
Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的
也就是说这些语句内定义的变量,外部也可以访问,如果将变量定义在函数中,则它就是局部变量,外部不能访问
全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
global 和 nonlocal关键字
当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。
num = 1 def fun1(): global num # 需要使用 global 关键字声明 print(num) num = 123 print(num) fun1() print(num) 以上实例输出结果: 1 123 123
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了:
def outer(): num = 10 def inner(): nonlocal num # nonlocal关键字声明 num = 100 print(num) inner() print(num) outer() 以上实例输出结果: 100 100
注:另外有一种特殊情况,假设下面这段代码被运行:
a = 10 def test(): # 给函数设定一个参数a就好了 a = a + 1 print(a) test()
以上程序执行,报错信息如下:UnboundLocalError: local variable 'a' referenced before assignment
错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。
内置函数
函数对象(函数也是对象)
秉承着一切皆对象的理念,我们再次回头来看函数(function)。函数也是一个对象,具有属性(可以使用dir()查询)。
作为对象,它还可以赋值给其它对象名,或者作为参数传递。
lambda函数(匿名函数)
关键字lambda
表示匿名函数,冒号前面的表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
两个参数的匿名函数
print func(3,4)
以上定义可以写成以下形式:
return x + y
函数作为参数传递
函数可以作为一个对象,进行参数传递。函数名(比如func)即该对象。比如说:
print 'test'
print f(a, b)
test(func, 3, 5)
我们因此可以提高程序的灵活性。可以使用上面的test函数,带入不同的函数参数。比如:
map()函数
map()是Python的内置函数。它的第一个参数是一个函数对象。
在Python 3.X中,map()的返回值是一个可迭代对象。可以利用list()函数,将该循环对象转换成表。
如果作为参数的函数对象有多个参数,可使用下面的方式,向map()传递函数参数的多个参数:
re = map((lambda x,y: x+y),[1,2,3],[6,7,9])
filter()函数
filter函数的第一个参数也是一个函数对象。它也是将作为参数的函数对象作用于多个元素。如果函数对象返回的是True,则该次的元素被储存于返回的表中。filter通过读入的函数来筛选数据。同样,在Python 3.X中,filter返回的不是表,而是循环对象。
filter函数的使用如下例:
if a > 100:
return True
else:
return False
print filter(func,[10,56,101,500])
reduce()函数
reduce函数的第一个参数也是函数,但有一个要求,就是这个函数自身能接收两个参数。reduce可以累进地将函数作用于各个参数。如下例:
reduce将表中的前两个元素(1和2)传递给lambda函数,得到3。该返回值(3)将作为lambda函数的第一个参数,而表中的下一个元素(5)作为lambda函数的第二个参数,进行下一次的对lambda函数的调用,得到8。依次调用lambda函数,每次lambda函数的第一个参数是上一次运算结果,而第二个参数为表中的下一个元素,直到表中没有剩余元素。
上面例子,相当于(((1+2)+5)+7)+9
根据mmufhy的提醒: reduce()函数在3.0里面不能直接用的,它被定义在了functools包里面,需要引入包,见评论区。
实现了 __call__ 的类也可以作为函数
对于一个自定义的类,如果实现了 __call__ 方法,那么该类的实例对象的行为就是一个函数,是一个可以被调用(callable)的对象。例如:
class Add:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
>>> add = Add(1)
>>> add(4)
>>> 5
执行 add(4) 相当于调用 Add.__call__(add, 4),self 就是实例对象 add,self.n 等于 1,所以返回值为 1+4
add(4)
||
Add(1)(4)
||
Add.__call__(add, 4)