Python函数

Python函数基础,涉及到的有函数定义、参数传递、匿名函数和装饰器等等

函数基础

函数定义

语法

1
2
3
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对象。

问题来了,怎么接收多个返回值呢?

那就用多个参数来接收

1
2
3
4
def func():
return 1, [2, 3], "haha"

a, b, c = func()

参数的传递

参数传递的是变量参数代表的实际对象地址,Python函数中参数的传递分两种情况

  • 不可变的参数类型:例如数字,字符串,传进去,进去后再怎么操作,也不会影响外部的变量,因为传的是实际对象的地址,你函数拿到这个地址,你只能查,不能改,因为参数是不可变的
  • 可变的参数类型:例如列表,把列表对象的实际地址传进去了,你函数拿到地址,可以查,还可以append,直接把列表里的内容改了

还有一个注意的地方,传参的时候要注意位置对应(可以指定参数名=值的这种方式,可以不按照参数位置来传参),还要注意自己传的数据类型对不对,因为Python是弱数据类型,传参的时候不会检查你传的数据类型是否正确,到执行到的时候才会抛异常

默认参数

函数传参支持默认参数,不过,默认参数要放在参数列表的最后面;如果有多个默认参数,那么最常用的默认参数要优先往前放,给个demo吧

1
2
3
4
5
6
7
8
9
10
11
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') # 甚至可以把默认参数放最前面

注意!默认参数尽量指定不可变的数据类型,如果可变的话,看代码

1
2
3
4
5
6
7
8
9
def func(a=[]):
a.append("A")
return a


if __name__ == '__main__':
print(func())
print(func())
print(func())

第一次调用函数时会在内存中创建一个空的列表对象,然后a指向这个空列表对象,然后对这个对象append操作,后两次调用依然时对同一个列表对象进行操作,运行结果如下:

1
2
3
['A']
['A', 'A']
['A', 'A', 'A']

如何改造?

1
2
3
4
5
def func(a=None):
if a is None:
a = []
a.append("A")
return a

动态参数

动态参数顾名思义就是传入的参数是动态的,可以是0个1个2个3个或N个,动态参数要放在常规参数和默认参数后面

1.*args

一个星号表示接收任意个参数。调用时,会将实际参数打包成一个元组传入形式参数。如果参数是个列表,会将整个列表当做一个参数传入,如果想把这个列表中的值当作一个一个的参数传进函数咋传?往列表前加个*

1
2
3
4
5
6
def func(*args):
for arg in args:
print(arg)

li = [1, 2, 3]
func(*li)

2.**kwargs

两个星表示接受键值对的动态参数,数量任意。调用的时候会将实际参数打包成字典

1
2
3
4
5
6
def func(**kwargs):
for kwg in kwargs:
print(kwg, kwargs[kwg])
print(type(kwg))

func(k1='v1', k2=[0, 1, 2])
1
2
3
4
k1 v1
<class 'str'>
k2 [0, 1, 2]
<class 'str'>

而如果我们这样传递一个字典dic呢?我们希望字典内的键值对能够像上面一样被逐一传入。往前面加俩星号就行

1
2
3
4
5
6
7
8
9
10
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。它相当于下面的函数:

1
2
def f(x):
return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数,x*x是执行代码。

匿名函数只能有一个表达式,不用也不能写return语句,表达式的结果就是其返回值。 匿名函数没有函数名字,不必担心函数名冲突,节省字义空间。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

1
2
3
4
5
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x3216fef44>
>>> f(6)
36

也可以把匿名函数作为别的函数的返回值返回。

1
2
def add(string, i):
return lambda: int(string) + i

推导式

属于是高阶玩法了,语法糖,比较花里胡哨

  • 列表推导式

    列表推导式是一种快速生成列表的方式。其形式是用方括号括起来的一段语句,如下例子所示:

    1
    2
    3
    4
    5
    lis = [x * x for x in range(1, 10)]

    print(lis)
    ------------------------------------
    结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

    相当于:

    1
    2
    3
    4
    5
    lis = []
    for i in range(1, 10):
    lis.append(i*i)

    print(lis)
  • 字典推导式

    自然就是用花括号括起来的语句,快速生成字典

    1
    2
    3
    4
    5
    >>> 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这种新式

    1
    2
    3
    4
    5
    >>> a = {x for x in 'abracadabra' if x not in 'abc'}
    >>> a
    {'d', 'r'}
    >>> type(a)
    <class 'set'>
  • 元组推导式

    不是用圆括号了,而是使用tuple()括号里放推导式这种形式创建

    1
    2
    3
    4
    5
    6
    7
    8
    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类型来判断一个对象是否可迭代:

1
2
3
4
5
6
7
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

迭代器

迭代器是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往后遍历不能回溯,不像列表,你随时可以取后面的数据,也可以返回头取前面的数据。迭代器通常要实现两个基本的方法:iter()next()

字符串,列表或元组对象,甚至自定义对象都可用于创建迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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循环遍历迭代器:

1
2
3
4
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):从字面上理解,就是装饰对象的器件。可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。装饰器有很多种,有函数的装饰器,也有类的装饰器。装饰器在很多语言中的名字也不尽相同,它体现的是设计模式中的装饰模式,强调的是开放封闭原则。装饰器的语法是将@装饰器名,放在被装饰对象上面。

1
2
3
@dec
def func():
pass

装饰器无参数

装饰器的一般写法,被装饰的方法带不带参数都可以,因为用了万能参数*args, **kwargs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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数据接口......")

跑一下:

1
2
3
4
5
6
if __name__ == '__main__':
f1()
# 运行结果
认证成功!
业务部门1数据接口......
日志添加成功

装饰器带参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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数据接口......")

跑一下:

1
2
3
4
5
6
if __name__ == '__main__':
f2("数据", "北京")
# 运行结果
接口无需认证
数据 北京 业务部门2数据接口......
日志添加成功!