[toc]
python 函数(一)
函数
数学定义: y=f(x),y是x的函数,x是自变量
- python 函数
由苦干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元格
完成一定的功能
- 函数的作用
结构化编程对代码的最基本的封装,一般按照功能组织一段代码;
封装的目的为了复用,减少冗余代码
代码更加简洁美化、可读易懂
- 函数的分类
内建的函数,如max()、reversed()等
库函数,如math.ceil()等
函数定义、调用
def语句定义函数
def 函数名(参数列表):
函数体(代码块)
[return 返回值]函数名就是标识符,命名要求一样:
- 语句块必须缩进,约定4个空格;
- Python 的函数没有return语句,隐式返回 一个None值
定义中的参数列表成为形式参数,只是一种符号表达,简称形参
调用
函数定义,只是声明了一个函数,它不会被执行,需要调用;
调用的方式,就是函数名加上小括号,括号内写上参数;
调用时写的参数是实际参数,是实实在在传的值,简称实参
- 函数举例
In [3]: def add(x,y): ...: result = x+y ...: return result ...: ...: In [4]: out = add(100,255) In [5]: out Out[5]: 355
上面只是一个函数的定义,有一个函数叫做add,接收2个参数
计算的结果,通过返回值返回
调用通过函数名add加2个参数,返回值可使用变量接收;
定义需要在调用前,也就是说调用时,已经被定义过了,否则抛NameError异常
函数是可调用的对象,callable()
函数参数
- 参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
- 位置参数
def f(x, y, z) 调用使用 f(1, 3, 5)
按照参数定义顺序传入实参
关键字参数
def f(x, y ,z) 调用使用f(x=1,y=3,z=5)
使用形参的名字来出入实参的方式,如果使用了形象名字,那么传参顺序就可以定义顺序不同
- 传参
f(z=None, y=10, x=[1]) f((1,), z=6, y=4.1) f(y=5,z=6.2) # 要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的
函数参数默认值
- 定义时,在形参后跟上一个值
In [6]: def add(x=1,y=5): ...: return x+y In [7]: callable(add) Out[7]: True In [8]: add() Out[8]: 6 In [9]: add(6) Out[9]: 11 In [10]: add(6,7) Out[10]: 13 In [11]: add(y=8,x=1) Out[11]: 9
- 作用
参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
参数的非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
In [12]: def add(x,y=5): ...: return x+y In [13]: add(3) Out[13]: 8 In [14]: add(3,6) Out[14]: 9
- 举例
定义一个函数login,参数名称为host、port、username、passwd
def login(host='127.0.0.1',port='8080',username='rj',password='test'): print('{}:{}@{}/{}'.format(host,port,username,password)) login() login('127.0.0.1',80,'tom','tom') login('127.0.0.1',username='root') login('localhost', port=80,password='com') login(port=80,password='test',host='web')
可变参数
- 问题
有多个数,需要求累加求和
In [15]: def add(nums): ...: sum = 0 ...: for x in nums: ...: sum += x ...: return sum In [16]: add([1,2,5]) Out[16]: 8 In [17]: add((2,4,6)) Out[17]: 12
- 可变参数
一个形参可以匹配任意个参数
有多个数,需要累加求和
In [22]: def add(*nums): ...: sum = 0 ...: print(type(nums)) ...: for x in nums: ...: sum += x ...: print(sum) ...: In [23]: add(3,6,9) <class 'tuple'> 18
在形参前使用*表示该形参是可变参数,可以接收多个实参;
收集的实参名称和值组成一个tuple;
可变参数
关键字参数的可变参数
- 配置信息打印
In [9]: def showconfig(**kwargs): ...: for k,v in kwargs.items(): ...: print('{} = {}'.format(k,v)) ...: In [10]: In [10]: showconfig(host='127.0.0.1',port='8080',username='asjin',password='ssjinyao') host = 127.0.0.1 port = 8080 username = asjin password = ssjinyao
- 形参前使用**符号,表示可以接收多个关键字参数
- 收集的实参名称和值组成一个字典
混着写,关键的默认定义好,其它的用kwargs
def showconfig(username, password, **kwargs) def showconfig(username, *args, **kwargs) def showconfig(username, password,**kwargs, *args)
总结
- 有位置可变参数和关键字可变参数;
- 位置可变参数在形参前使用一个星号*;
- 关键字可变参在形参前使用两个星号**;
- 位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict;
- 混合使用参数的时候,可变参数要放到参数列表的最后,普通参数要放到参数列表前面,位置可变参数需要在关键字可变参数之前。
举例
def fn(x, y, *args, **kwargs): print(x) print(y) print(args) print(kwargs) fn(3, 5, 7, 9, 10,a=1,b='python') fn(3, 5) fn(3, 5, 7) fn(3, 5, a=1,b='python')
举例
def fn(*args, x, y, **kwargs) print(x) print(y) print(args) print(kwargs) fn(7, 9, y=5, x=3, a=1, b='python' )
keyword-only 参数
- keyword-only 参数(Python3 加入)
如果在一个星号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数了,而是keyword-only参数
def fn(*args, x): print(x) print(args) fn(3, 5, x=7)
args 可以看做已经截获了所有的位置参数,x不使用关键字参数就可能拿到实参
- keyword-only 参数另一种形式
def fn(*, x, y): print(x,y) fn(x=5, y=6)
*号之后,普通形参都变成了必须给出的keyword-only参数
- 可变参数和参数默认值
举例
def fn(*args, x=5): print(x) print(args) fn() # 等价于fn(x=5) fn(5) fn(x=6) fn(1 , 2, 3, x=10)
x 是keyword-only 参数
举例
def fn(x=5, **kwargs): print('x={}'.format(x)) print(kwargs) fn() fn(5) fn(x=6) fn(y=3,x=10) fn(3,y=10)
函数参数
- 参数规则
参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
def fn(x, y, z=3, *arg, m=4, n, **kwargs): print(x, y, z, m,n) print(args) print(kwargs)
def connect(host='localhost',port='3306',user='admin',password='admin',**kwargs): print(host,port) print(user,passowrd) print(kwargs) connect(db='cmdb') connect(host='172.16.0.8',db='cmdb') connect(host='172.160.0.9',db='cmdb',password='mysql')
参数解构
举例: 加法函数
def add(x, y): return x+y add(4, 5) add((4, 5)) t = (4, 5) add(t[0], t[1]) # add(*t) 或 add(*(4, 5)) add(*[4,5]) add(*{4,6}) # add(*range(1,3)) In [1]: def add(x, y): ...: return x+y In [3]: add((1,2)[0],[3][0]) Out[3]: 4
In [8]: def add(x, y): ...: return x+y In [9]: lst = [1,3] In [10]: add(*lst) Out[10]: 4 # 当给定一个集合 In [12]: add(*{5,6}) Out[12]: 11
例:
In [28]: def add(*keys): ...: sumnum = 0 ...: for i in keys: ...: sumnum = sumnum + i ...: return sumnum In [29]: add(*range(1,101)) Out[29]: 5050
- 参数解构
给函数提供实参的时候,可以在集合类型前使用* 或者** , 把集合类型的结构解开,提取所有元素做为函数的实参
非字典类型使用*解构成位置参数
字典类型使用*解构成位置参数
提取出来的元素数王要和参数的要求匹配,也要和参数的类型匹配
def add(x, y): return x+y add(*(4 ,5)) add(*[4,5]) add(*{4,6}) d = {'x': 5, 'y':6} add(**d) add(**{'a':5, 'b':6})
In [39]: def add(x, y): ...: return x+y In [40]: dct = {'x': 1, 'y':6} In [41]: add(*dct.values()) Out[41]: 7
练习
编写一个函数,能够接受至少2个参数,返回最小值和最大值。
In [49]: def mums(x, y, *args): ...: print(min(x,y,*args)) ...: print(max(x,y,*args)) In [50]: mums(*range(1,100001)) 1 100000
方法二
In [51]: import random In [52]: def double_values(*nums): ...: print(nums) ...: return max(nums), min(nums) In [53]: print(*double_values(*[random.randint(10,20) for _ in range(10)])) (14, 11, 19, 11, 17, 12, 13, 18, 12, 13) 19 11
编写 一个函数,接受一个参数n, n为正整数, 左右两种打印方式, 要求数字必须对齐
In [59]: def trangle_print(n): ...: for i in range(1, n+1): ...: for j in range(n, 0, -1): ...: if i < j: ...: print(' '*len(str(j)), end=' ') ...: else: ...: print(j, end=' ') ...: print() ...: In [60]: trangle_print(16) 1 2 1 3 2 1 4 3 2 1 5 4 3 2 1 6 5 4 3 2 1 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 11 10 9 8 7 6 5 4 3 2 1 12 11 10 9 8 7 6 5 4 3 2 1 13 12 11 10 9 8 7 6 5 4 3 2 1 14 13 12 11 10 9 8 7 6 5 4 3 2 1 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
方法二
In [61]: def show(n): ...: tail = " ".join([str(i) for i in range(n,0,-1)]) ...: print(tail) ...: width = len(tail) ...: for i in range(1,n): ...: print("{:>{}}".format(" ".join([str(j) for j in range(i,0,-1)]), width)) ...: print(tail) ...: In [62]: show(12) 12 11 10 9 8 7 6 5 4 3 2 1 1 2 1 3 2 1 4 3 2 1 5 4 3 2 1 6 5 4 3 2 1 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 11 10 9 8 7 6 5 4 3 2 1 12 11 10 9 8 7 6 5 4 3 2 1
In [63]: def showtail(n): ...: tail = " ".join([str(i) for i in range(n,0,-1)]) ...: print(tail) ...: for i in range(len(tail)): ...: if tail[i] == ' ': ...: print(' '*i, tail[i+1:]) ...: In [64]: showtail(12) 12 11 10 9 8 7 6 5 4 3 2 1 11 10 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1 9 8 7 6 5 4 3 2 1 8 7 6 5 4 3 2 1 7 6 5 4 3 2 1 6 5 4 3 2 1 5 4 3 2 1 4 3 2 1 3 2 1 2 1 1
直接插入排序
在未排序序列中,构建一个子排序序列,直至全部数据排序完成;
将待排序的数,插到已经排序的序列中合适的位置;
增加一个哨兵,放入待比较值,让它和后面已经排好的序列比较,找到适合的插入点;
- 增加一个哨兵位,每轮比较将待比较数入;
- 哨兵依次和待比较数的前一个数据比较,大数靠右移动,找到哨兵中值的插入位置;
- 每一轮结束后,得到一个从开始到待比较数位置的一个有序序列
实例
m_list = [[1, 9, 8, 5, 6, 7, 4, 3, 2 ], [1, 2, 3, 4, 5, 6, 7, 8, 9], [9, 8, 7, 6, 5, 4, 3, 2, 1 ], [1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2]] nums = [0] + m_list[0] sentinel, *origin = nums #哨兵位,待比较数字 count_swap = 0 count_iter = 0 length = len(nums) for i in range(2,length): #从2开始 nums[0] = nums[i] # 放置哨兵 j = i -1 count_iter +=1 if nums[j] > nums[0]: # 大数右移,找到插入位置 while nums[j] > nums[0]: nums[j+1] = nums[j] # 依次右移 j -= 1 count_swap +=1 nums[j+1] = nums[0] # 将哨兵插入,注意挺好入的右侧要+1 print(nums, count_swap, count_iter)
作用域
- 一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域
- 举例,对比左右2个函数
In [4]: x = 50 In [5]: def show(): ...: print(x) In [7]: show() 50
看似是可见的,但尝试以下语句
In [8]: x = 5 In [9]: def foo(): ...: x += 1 ...: print(x) In [10]: foo() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-10-c19b6d9633cf> in <module>() ----> 1 foo() <ipython-input-9-69a7705e0064> in foo() 1 def foo(): ----> 2 x += 1 3 print(x) 4 UnboundLocalError: local variable 'x' referenced before assignment In [11]:
全局作用域
在整个程序运行环境中都可见局部作用域
在函数、类等内部可见
局部变量使用范围不能超过期所有的局部作用域
def fn1(): x = 1 # 局部作用域,在fn1内 def fn2(): print(x) print(x)
In [16]: def outer(): ...: o = 65 ...: def inner(): ...: print("inner {}".format(o)) ...: print(chr(o)) ...: print("outer {}".format(o)) ...: inner() In [18]: outer() outer 65 inner 65 A
对比
In [19]: def outer2(): ...: o = 65 ...: def inner(): ...: o = 97 ...: print("inner {}".format(o)) ...: print(chr(o)) ...: print("outer {}".format(o)) ...: inner() ...: In [20]: outer2() outer 65 inner 97 a
全局变量global
x = 5 def foo(): global x x += 1
- 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域定义的x
- 全局作用域中必须有x的定义
x = 100 def foo(): global x x = 10 x += 1 print(x) foo() print(x) 11 11
- 但是, x=10 赋值即定义, x 在内部作用域为一个外部作用域的变量赋值, 所以x+=1会报错。
- 注意,这里的x的作用域还是全局的
global 总结
- x+=1这种是特珠形式产生的错误的原因? 先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法,在这条语句前加x=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查变量定义
- 内部作用域作用x =5 之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值
- global的使用原则
- 外部作用域亦是会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请使用函数的形参传参解决
闭包
- 自由变量: 未在本地作用域中定义的变量。例如定义在内存函数外的外层函数作用域中的变量
- 闭包: 就是一个概念,出现在嵌套函数中,指的内层函数引用到了外层函数的自由变量就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
In [8]: def counter(): ...: c = [0] ...: def inner(): ...: c[0] +=1 ...: return c[0] ...: return inner In [9]: foo = counter() In [11]: print(type(foo)) <class 'function'> In [12]: print(type(foo())) <class 'int'> In [13]: print(foo(),foo()) 2 3 In [15]: print(foo()) 4