Python函数
Python函数基础,涉及到的有函数定义、参数传递、匿名函数和装饰器等等
函数基础
函数定义
语法
def 函数名(参数):
# 内部代码
return 表达式
注意事项:
- 函数代码块以
def
关键词开头,一个空格之后接函数标识符名称和圆括号(),再接个冒号。 - 任何传入的参数必须放在圆括号中间,参数不必定义类型
- 函数的第一行语句后可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- 使用return结束函数。默认返回None。
- return语句依然在函数体内部,不能回退缩进。直到函数的所有代码写完,才回退缩进,表示函数体结束。
return可以返回什么?
- 什么都不返回,仅仅return:
return
- 数字/字符串/任意数据类型:
return 'hello'
- 一个表达式:
return 1+2
- 一个判断语句:
return 100 > 99
- 一个变量:
return a
- 一个函数调用:
return func()
- 甚至是返回自己!:
return self
- 多个返回值,以逗号分隔:
return a, 1+2, "hello"
简而言之,函数可以return几乎任意Python对象。
问题来了,怎么接收多个返回值呢?
那就用多个参数来接收
def func():
return 1, [2, 3], "haha"
a, b, c = func()
参数的传递
参数传递的是变量参数代表的实际对象的地址,Python函数中参数的传递分两种情况
- 不可变的参数类型:例如数字,字符串,传进去,进去后再怎么操作,也不会影响外部的变量,因为传的是实际对象的地址,你函数拿到这个地址,你只能查,不能改,因为参数是不可变的
- 可变的参数类型:例如列表,把列表对象的实际地址传进去了,你函数拿到地址,可以查,还可以append,直接把列表里的内容改了
还有一个注意的地方,传参的时候要注意位置对应(可以指定参数名=值的这种方式,可以不按照参数位置来传参),还要注意自己传的数据类型对不对,因为Python是弱数据类型,传参的时候不会检查你传的数据类型是否正确,到执行到的时候才会抛异常
默认参数
函数传参支持默认参数,不过,默认参数要放在参数列表的最后面;如果有多个默认参数,那么最常用的默认参数要优先往前放,给个demo吧
def print_hi(name, age, score='C', subject='English'):
print(name, age, score, subject)
if __name__ == '__main__':
print_hi('jack', 18) # 全用默认参数
print_hi('sunnyc', 20, 'A') # 改一个默认参数
print_hi('hc', 23, 'A', 'Math') # 改两个默认参数
print_hi(age=18, name='ohhhhhh') # 加上参数名不受位置约束
print_hi(score='B', age=19, name='oppppp') # 甚至可以把默认参数放最前面
注意!默认参数尽量指定不可变的数据类型,如果可变的话,看代码
def func(a=[]):
a.append("A")
return a
if __name__ == '__main__':
print(func())
print(func())
print(func())
第一次调用函数时会在内存中创建一个空的列表对象,然后a指向这个空列表对象,然后对这个对象append操作,后两次调用依然时对同一个列表对象进行操作,运行结果如下:
['A']
['A', 'A']
['A', 'A', 'A']
如何改造?
def func(a=None):
if a is None:
a = []
a.append("A")
return a
动态参数
动态参数顾名思义就是传入的参数是动态的,可以是0个1个2个3个或N个,动态参数要放在常规参数和默认参数后面
1.*args
一个星号表示接收任意个参数。调用时,会将实际参数打包成一个元组传入形式参数。如果参数是个列表,会将整个列表当做一个参数传入,如果想把这个列表中的值当作一个一个的参数传进函数咋传?往列表前加个*
号
def func(*args):
for arg in args:
print(arg)
li = [1, 2, 3]
func(*li)
2.**kwargs
两个星表示接受键值对的动态参数,数量任意。调用的时候会将实际参数打包成字典
def func(**kwargs):
for kwg in kwargs:
print(kwg, kwargs[kwg])
print(type(kwg))
func(k1='v1', k2=[0, 1, 2])
k1 v1
<class 'str'>
k2 [0, 1, 2]
<class 'str'>
而如果我们这样传递一个字典dic呢?我们希望字典内的键值对能够像上面一样被逐一传入。往前面加俩星号就行
def func(**kwargs):
for kwg in kwargs:
print(kwg, kwargs[kwg])
dic = {
'k1': 'v1',
'k2': 'v2'
}
func(**dic)
3.“万能”参数
当*args
和**kwargs
组合起来使用,理论上能接受任何形式和任意数量的参数,在很多代码中我们都能见到这种定义方式。需要注意的是,*args
必须出现在**kwargs
之前。
匿名函数
当我们在创建函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。这省去了我们挖空心思为函数命名的麻烦,也能少写不少代码,很多编程语言都提供这一特性。匿名函数用好了,会有画龙点睛的效果,没用好,就容易“画虎不成反类犬”,需要我们在平时的代码过程中,多学、多看、多琢磨。
Python语言使用lambda
关键字来创建匿名函数。
所谓匿名,即不再使用def
语句这样标准的形式定义一个函数。
- lambda只是一个表达式,而不是一个代码块,函数体比def简单很多。
- 仅仅能在lambda表达式中封装有限的逻辑。
- lambda 函数拥有自己的命名空间。
其形式通常是这样的:**lambda 参数: 表达式
**。
例如:lambda x: x * x
。它相当于下面的函数:
def f(x):
return x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数,x*x是执行代码。
匿名函数只能有一个表达式,不用也不能写return语句,表达式的结果就是其返回值。 匿名函数没有函数名字,不必担心函数名冲突,节省字义空间。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x3216fef44>
>>> f(6)
36
也可以把匿名函数作为别的函数的返回值返回。
def add(string, i):
return lambda: int(string) + i
推导式
属于是高阶玩法了,语法糖,比较花里胡哨
列表推导式
列表推导式是一种快速生成列表的方式。其形式是用方括号括起来的一段语句,如下例子所示:
lis = [x * x for x in range(1, 10)] print(lis) ------------------------------------ 结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]
相当于:
lis = [] for i in range(1, 10): lis.append(i*i) print(lis)
字典推导式
自然就是用花括号括起来的语句,快速生成字典
>>> dic = {x: x**2 for x in (2, 4, 6)} >>> dic {2: 4, 4: 16, 6: 36} >>> type(dic) <class 'dict'>
集合推导式
也是花括号,不过差别是集合没有key,只有value,字典是用冒号分开的
key:value
这种新式>>> a = {x for x in 'abracadabra' if x not in 'abc'} >>> a {'d', 'r'} >>> type(a) <class 'set'>
元组推导式
不是用圆括号了,而是使用
tuple()
括号里放推导式这种形式创建tup = tuple(x for x in range(9)) print(tup) print(type(tup)) ------------------------ 结果: (0, 1, 2, 3, 4, 5, 6, 7, 8) <class 'tuple'>
迭代器
在介绍迭代器之前,先说明下迭代的概念:
迭代:通过for循环遍历对象的每一个元素的过程。
Python的for语法功能非常强大,可以遍历任何可迭代的对象。
在Python中,list/tuple/string/dict/set/bytes都是可以迭代的数据类型。
可以通过collections模块的Iterable类型来判断一个对象是否可迭代:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
迭代器
迭代器是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往后遍历不能回溯,不像列表,你随时可以取后面的数据,也可以返回头取前面的数据。迭代器通常要实现两个基本的方法:iter()
和 next()
。
字符串,列表或元组对象,甚至自定义对象都可用于创建迭代器:
>>> lis=[1,2,3,4]
>>> it = iter(lis) # 使用Python内置的iter()方法创建迭代器对象
>>> next(it) # 使用next()方法获取迭代器的下一个元素
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
4
>>> next(it) # 当后面没有元素可以next的时候,弹出错误
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(it)
StopIteration
或者使用for循环遍历迭代器:
lis = [1,2,3,4]
it = iter(lis) # 创建迭代器对象
for x in it: # 使用for循环遍历迭代对象
print (x, end=" ")
很多时候,为了让我们自己写的类成为一个迭代器,需要在类里实现__iter__()
和__next__()
方法。
总结:Python的迭代器表示的是一个元素流,可以被next()函数调用并不断返回下一个元素,直到没有元素时抛出StopIteration
错误。可以把这个元素流看做是一个有序序列,但却不能提前知道序列的长度,只能不断通过next()函数得到下一个元素,所以迭代器可以节省内存和空间。
迭代器(Iterator)
和可迭代(Iterable)
的区别:
- 凡是可作用于for循环的对象都是可迭代类型;
- 凡是可作用于next()函数的对象都是迭代器类型;
list、dict、str
等是可迭代的但不是迭代器,因为next()函数无法调用它们。可以通过iter()
函数将它们转换成迭代器。- Python的for循环本质上就是通过不断调用
next()
函数实现的。
生成器
不太明白
装饰器
必须要懂,跟Spring里面的AOP
有点类似,可以对一个函数加一个环绕通知,在函数前和函数后加一些逻辑
作为许多语言都存在的高级语法之一,装饰器是你必须掌握的知识点。
装饰器(Decorator):从字面上理解,就是装饰对象的器件。可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。装饰器有很多种,有函数的装饰器,也有类的装饰器。装饰器在很多语言中的名字也不尽相同,它体现的是设计模式中的装饰模式,强调的是开放封闭原则。装饰器的语法是将@装饰器名,放在被装饰对象上面。
@dec
def func():
pass
装饰器无参数
装饰器的一般写法,被装饰的方法带不带参数都可以,因为用了万能参数*args, **kwargs
def auth(func):
"""
登录认证及日志记录
:param func:
:return:
"""
def decorator(*args, **kwargs):
# 认证逻辑
print("认证成功!")
result = func(*args, **kwargs)
# 日志添加逻辑
print("日志添加成功")
return result
return decorator
@auth
def f1():
print("业务部门1数据接口......")
跑一下:
if __name__ == '__main__':
f1()
# 运行结果
认证成功!
业务部门1数据接口......
日志添加成功
装饰器带参数
def action(is_auth=True, is_log=True):
"""
装饰器带参数,被装饰的函数带不带参数都可以,因为用了万能参数*args, **kwargs
:param is_auth: 是否启用登录认证
:param is_log: 是否启用日志记录
:return:
"""
def to_decorator(func):
def decorator(*args, **kwargs):
if is_auth:
print("认证成功!")
else:
print("接口无需认证")
result = func(*args, **kwargs)
if is_log:
print("日志添加成功!")
else:
print("接口无需添加日志")
return result
return decorator
return to_decorator
@action(is_auth=False)
def f2(name, location):
print(name, location, "业务部门2数据接口......")
跑一下:
if __name__ == '__main__':
f2("数据", "北京")
# 运行结果
接口无需认证
数据 北京 业务部门2数据接口......
日志添加成功!