文章目录
5.1 函数的定义和调用
1.函数的定义
函数是为了解把会重复使用的代码定义成一个对象,在复用的时候可直接调用。
- def 函数名(形参) 函数名的命名等同变量命名规则。
- 函数执行的代码以冒号起始,并且缩进。
- return函数终止执行,并返回结果,没有return返回None。
def func(n1): # 函数执行的代码以冒号起始,并且缩进。
"函数_文档字符串" # 函数说明文档
return n1+10,n1 # 返回值可以是任意类型,如有逗号,会转换成元组
print(n1) # 遇到return就会立即退出函数(在循环中也会终止函数)
func(10) # return结果需要传递给变量
res = func(10)
print(res) # (20, 10)
2. 函数名调用
- 调用过程中会执行print,如有return返结果。没有默认是None。
- help ,查看说明文档。
def func(n1):
print(n1+10) # print在函数调用执行过程中输出内容
print(n1)
res = func(10) # 调用函数,会执行打印
print(res) # 如果函数中没写return,则默认返回None
- 函数名是可哈希的,可作为集合元素或字典的键等。
- 函数赋值给一个变量后,这个变量也具有了此函数的功能
def func():
return 123
cond_dict = {"函数":func}
num = cond_dict['函数']
num() # 123
- 函数名也可当成对象,从另一个函数中返回出来而去构建高阶函数,比如:参数是函数、返回值是函数。
- 函数名做另一个函数的参数
def plus(num):
return num + 100
def handler(func):
res = func(10) # 110
msg = "执行func,并获取到的结果为:{}".format(res)
print(msg) # 执行func,并获取到的结果为:110
# 执行handler函数,将plus作为参数传递给handler的形式参数func
handler(plus)
- 函数名做另一个函数的返回值
def plus(num):
print(num + 100)
def handler():
print("执行handler函数")
return plus
result = handler()
data = result(20) # 120
print(data)
- 同时为参数和返回值
def f1():
print(123)
def f2(arg):
ret = arg()
return f1
v1 = f2(f1) # 在return之前先运行了f1,所以过程中打印了123
v2 = v1() # f2 返回f1 所以v2现在指向f1()
print(v2)
3. 函数修饰器
- 函数修饰器是一种特殊的语法,用于在不修改原始函数定义的情况下,通过@函数修改或增强函数的行为。(批量操作会更有意义)
- 修饰器本身是一个函数,它接收一个函数对象作为参数,并返回一个新的函数对象或一个可调用对象。
- 修饰器以 @ 符号开头写在函数定义的上方,多个修饰器可以叠加使用。
def my_decorator(func):
def wrapper():
print("Before the function is called.")
func()
print("After the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
say_hello.__name__ # 'wrapper'
- functools.wraps能 传递.__name__和.doc。
import functools
def outer(origin):
@functools.wraps(origin) # 传递函数名称和注释
def inner(*args, **kwargs):
# 执行前需执行的代码
res = origin(*args, **kwargs) # 调用原来的func函数
# 执行完成需执行的代码
return res
return inner
@outer # func = outer(func)
def func():
pass
func.__name__ # func
5.2 函数的参数
Python 的函数具有非常灵活多样的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。从简到繁的参数形态如下:
形参与实参:当一个函数被调用时,实参的值被赋给对应的形参,并作为函数内部变量来使用。
def greet(name):
print("Hello, " + name + "!")
greet("Alice")
在这个例子中,name 是 greet() 函数的形参,而 “Alice” 是传递给函数的实参。当函数被调用时,“Alice” 被赋值给 name,并在函数体内输出 “Hello, Alice!”。
1. 位置参数
- 按照顺序依次传递的参数,在函数内部可以通过对应的参数名来访问。
- 位置传参的实参个数不多不少,依次传参。
2. 关键字参数
用于函数调用,通过“键-值”形式加以指定。可以让函数更加清晰、容易使用。
Python 允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
- 通过名称来指定传递给函数的参数,不需要按照定义顺序传递。
- 可以和位置参数混合使用,但必须在位置参数之后。
def print_hello(name,age,sex):
print('Name:%s Age:%d Sex:%s' %(name,age,sex))
print_hello('male',19,'lili') # Name:male Age:19 Sex:lili
print_hello('lili',sex='male',age=18) # Name:lili Age:18 Sex:male
3. 默认参数
- 用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值,如果不传就取定义时候的值,传了改变为新的值.
- 默认参数的值通常应设为不可变类型。默认值是可变类型时,执行函数会修改默认值。
def print_hello(name,age,sex = '女' ):
#sex = '女' 为默认参数
print('Name:%s Age:%s Sex:%s' %(name,age,sex))
print_hello('Muse',26,'男')
4. 可变长度的参数
- 可变长度的位置参数
在位置参数的最后一个形参名前加*,溢出的位置参数都会被接收,以元组的形式保存下来赋值给该形参
def foo(x,z=1,*args): #在最后一个形参名args前加*号
print(x,z,args)
foo(1) # 1 1 ()
foo(1,2) # 1 2 ()
foo(1,2,3) # 1 2 (3, )
- 可变长度的关键字参数
- 在最后一个形参名前面加**,调用函数时,溢出的关键字参数都会被接收,以字典的形式保存下来赋值给该形参。
- 多个形参时,顺序必须是:位置参数、默认参数和可变位置参数、关键字参数。
def func(arg1, arg2=0, *args, **kwargs):
pass
def func(a1, a2, a3, a4=10, *args, a5=[1,2], **kwargs):
a5.append(a2)
return a5,a4,kwargs # 返回的是元组
func(2,4,a5=[2,3],a3=3,a6=6) # ([2, 3, 4], 10, {'a6': 6})
5. 命名关键字参数
- 限制必须使用关键字参数传递,也可以设置默认值。
- 命名关键字参数,在参数nkw 前面加个分隔符
*
。 - 命名关键字参数在可变关键字参数之前
- 即使在调用函数时只有一个参数也必须使用关键字参数传递。
def printinfo(arg1, *, nkw, **kwargs):
print(arg1)
print(nkw)
print(kwargs)
printinfo(70, nkw=10, a=1, b=2)
# 70
# 10
# {'a': 1, 'b': 2}
printinfo(70, 10, a=1, b=2)
# TypeError: printinfo() takes 1 positional argument but 2 were given
# 没有写参数名`nwk`,因此 10 被当成「位置参数」,而原函数只有 1 个位置函数,现在调用了 2 个,因此程序会报错。
6. 解包裹(*
和**
)
- 在传递元组时,
*
让元祖的每一个元素对应一个位置参数 - 在传递字典时,
**
让字典的每个键值对作为一个关键字参数传递给函数
def func(a1,a2):
print(a1,a2)
func( *[11,22] ) # 11 22
func( **{"a1":11,"a2":22} ) # 11 22
def func(*args,**kwargs):
print(args,kwargs)
print(id(args),id(kwargs))
func( [11,22,33], {"k1":1,"k2":2} ) # ([11, 22, 33], {'k1': 1, 'k2': 2}) {}
func( *[11,22,33], **{"k1":1,"k2":2} ) # (11, 22, 33) {'k1': 1, 'k2': 2}
案例1:数据清洗,去除空格! #?,首字母大写
states = [' Alabama ', 'Georgia! ', 'Georgia', 'georgia', 'FlOrIda','south carolina##', 'West virginia? ']
import re
def remove_punctuation(value):
return re.sub('[! #? ]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]
def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
clean_strings(states,clean_ops)
案例2 : 批量调用函数。用 字典 调用函数名和用**
传递参数
# send_msg、send_email、send_wechat之前已经定义好的函数,在这里调用
func_list = [
{"name": send_msg, "params": {'mobile': "15131255089", "content": "你有新短消息"}},
{"name": send_email, "params": {'to_email': "wupeiqi@live.com", "subject": "报警消息", "content": "硬盘容量不够用了"}},
{"name": send_wechat, "params": {'user_id': 1, 'content': "约吗"}},
]
for item in func_list:
func = item['name'] # send_msg
param_dict = item['params'] # {'mobile': "15131255089", "content": "你有新短消息"}
func(**param_dict) # send_msg(**{'mobile': "15131255089", "content": "你有新短消息"})
5.3 变量作用域
- Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
- 函数为本地命名空间,执行结束就会销毁引用计数器为0的变量。
1.全局变量和局部变量
定义
- 定义在函数内部的变量拥有局部作用域,该变量称为局部变量。只能在其被声明的函数内部访问。
- 定义在函数外部的变量拥有全局作用域,该变量称为全局变量。可以在整个程序范围内访问。
变量访问和修改
- 函数内调用变量时,优先当前作用域找,在找上一级。
- 内部作用域变量可以用return传递到外部作用域。
- 在局部作用域对全局变量只能进行读取和修改内部元素(可变类型)
- 当内部作用域想修改外部作用域的变量时,要用
global
和nonlocal
关键字。- global 变量 能修改全局变量。
- nonlocal 变量 先修改上一层变量。
name = 'root'
def outer():
# global把outer函数内 name设置为全局变量。
global name
name = 'bo'
def func():
name = "simba"
def inner():
# nonlocal 调用上一层变量赋值
nonlocal name
name = 123
def inner2():
name = 123
inner2()
# inner2执行结束,会销毁了函数内name变量
print(name) # simba
inner()
print(name) # 123
func()
# func执行结束,会销毁了函数内name变量
print(name) # bo
outer()
print(name) # bo
2. 函数嵌套
- 函数嵌套时,传参的本质是传递的内存地址。
- 当对象指向可变类型时,数据会改变。
# 默认参数会在内部指向一个内存地址。
def func(a1, a2=[1, 2]):
a2.append(a1)
return a2
v1 = func(10)
print(v1) # [1, 2, 10]
v2 = func(20)
print(v2) # [1, 2, 10, 20]
v3 = func(30, [11, 22])
print(v3) # [11, 22, 30]
v4 = func(40)
print(v4) # [1, 2, 10, 20, 40]
# 重新打印时的深坑
print(v1) # v1 = a2 # [1, 2, 10, 20, 40]
print(v2) # V2 = a2 本质上与V1指向了同一个内存地址
print(v3) # [11, 22, 30]
print(v4) # [1, 2, 10, 20, 40]
3. 深浅拷贝
- 可变类型不想一致变化,需要用 copy.deepcopy(),深拷贝来拷贝可变类型或含有可变类型的元组。
import copy
v1 = ("时间", "root", [11, [44, 55], (11, 22), (11, [], 22), 33])
v2 = copy.deepcopy(v1)
# 不可变类型 ,不拷贝
print(id(v1[0])) # 2092678016720
print(id(v2[0])) # 2092678016720
print(id(v1[2][2])) # 2092688076224
print(id(v2[2][2])) # 2092688076224
# 可变类型,或者含有可变类型的元组,会拷贝。
print(id(v1)) # 2092674968512
print(id(v2)) # 2092674831360
print(id(v1[2])) # 140352552779008
print(id(v2[2])) # 140352552920448
print(id(v1[2][3])) # 140675479841152
print(id(v2[2][3])) # 140675480454784
4. 闭包
- 是一种特殊的内嵌函数。能够封装数据,在需要的时候提取。
- 如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。
- 通过闭包可以访问外层非全局作用域的变量,这个作用域称为 闭包作用域。
- 闭包的返回值通常是函数。
def funX(x):
def funY(y):
return x * y
return funY
i = funX(8)
print(type(i)) # <class 'function'>
print(i(5)) # 40
def func(name):
list = []
def inner():
list.append(name)
return list
return inner
v1 = func('bobo')
v2 = func('simba') # 单独生成一个内存区域,不会替代V1得到的值。
print(v1())
print(v2())
def task(arg):
def inner():
print(arg)
return inner # 返回内层函数,需要的时候在调用
inner_func_list = []
for val in [11,22,33]:
inner_func_list.append( task(val) )
inner_func_list[0]() # 11
inner_func_list[1]() # 22
inner_func_list[2]() # 33
【例子】 如果要修改闭包作用域中的变量则需要 nonlocal
关键字
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
# 100
# 100
5. 递归函数
- 如果一个函数在内部调用自身本身,这个函数就是递归函数。
- Python默认递归层数为 100 , 设置递归的层数,
import sys
sys.setrecursionlimit(1000)
遍历字典的值中的子字典
data = {
"time": "2016-08-05T13:13:05",
"some_id": "ID1234",
"grp1": {"fld1": 1, "fld2": 2, },
"xxx2": {"fld3": 0, "fld4": 0.4, },
"fld20":{"fld5": 10,"fld11": 12},
"fld6": 11,
"fld7": 7,
"fld46": 8
}
def select(data,fields):
result = {} # 字典为可变序列,需要每次重新定义为空,清空上一次运行select1函数的结果
field_lst = fields.split('|')
def select1(data,field_lst):
for key in data:
if key in field_lst:
result[key] = data[key]
if type(data[key]) == dict:
select1(data[key], field_lst)
return result
return select1(data,field_lst)
fields = 'fld2|fld7|fld29'
A = select(data, fields)
print(A) # {'fld2': 2, 'fld7': 7}
fields = 'fld2|fld20|fld11'
B = select(data, fields)
print(B) # {'fld2': 2, 'fld20': {'fld5': 10, 'fld11': 12}, 'fld11': 12}
【例子】斐波那契数列 f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1
用递归效率低
# 利用循环
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
k = i + j
lst.append(k)
i = j
j = k
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
# 利用递归
def recur_fibo(n):
if n <= 1:
return n
return recur_fibo(n - 1) + recur_fibo(n - 2)
lst = list()
for k in range(11):
lst.append(recur_fibo(k))
print(lst)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
5.4 lambda表达式
1. 匿名函数定义
lambda argument_list: expression
argument_list
- 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。expression
中没有 return 语句,因为 lambda 不需要它来返回,表达式本身结果就是返回值。- 匿名函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
foo = lambda a1,a2: a1 + a2 + 100
foo(1,2)
data = [lambda:10 for i in range(3)]
data[2]() # 10
func = lambda *args: sum(args)
print(func(1, 2, 3, 4, 5)) # 15
案例: 不同字母的数量排序
strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
strings.sort(key=lambda x: len(set(list(x)))) # ['aaaa', 'foo', 'abab', 'bar', 'card']
2. 匿名函数的应用
匿名函数 常常应用于函数式编程的高阶函数,主要有两种形式:
- 参数是函数 (filter, map)
- 返回值是函数 (closure)
如,在 filter
和map
函数中的应用:
filter(function, iterable)
过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用list()
来转换。
【例子】
odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist)) # [1, 3, 5, 7, 9]
map(function, *iterables)
根据提供的函数对指定序列做映射。
【例子】
m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))
# [1, 4, 9, 16, 25]
m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
print(list(m2))
# [3, 7, 11, 15, 19]
自己定义高阶函数。
def apply_to_list(fun, some_list):
return fun(some_list)
lst = [1, 2, 3, 4, 5]
print(apply_to_list(lambda x: sum(x) / len(x), lst))
# 3.0
3. 柯里化
柯里化:将一个接受多个参数的函数转换为只接受一个参数的函数
def add_numbers(x, y):
return x + y
add_five = lambda y: add_numbers(5, y)
也可以使用自带的标准库functools.partial方法。
from functools import partial
# 定义一个接受两个参数的函数 add
def add_numbers(x, y):
return x + y
# 使用 partial 函数创建一个只接受一个参数的函数 sub
sub = partial(add_numbers, 10)
# 调用 sub 函数并传入参数 5,结果为 15
print(sub(5)) # 输出 15
5.5 生成器
- 使用了 yield 的函数被称为生成器,生成器是一个返回迭代器的函数,只能用于迭代操作。
- 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。也可以用from连用。
- 生成器可以用来处理大量的数据,用一个取一个,而不是一次性生成所有结果。
def func():
text2 = yield 1
print(f'暂停了,V2才开始执行,text2为{text2}')
text3 = yield 'v2'
print(f'send值传递到上一个yield,也就是text3={text3}')
yield 'v3 第三次执行'
print('v4,没有返回值,默认返回None')
yield
print("StopIteration")
data = func()
v1 = next(data)
# 不在继续执行,返回生成器对象。记录在函数中的执行位置
print(v1) # 1
# 下次执行next时,会从上一次的位置基础上再继续向下执行。
v2 = next(data) # 打印 :暂停了,V2才开始执行,text2为None
print(v2) # v2
v3 = data.send("v3")
# send值传递到上一个yield,也就是text3=v3
print(v3) # v3 第三次执行
v4 = next(data)
print(v4) # 打印 v4,没有返回值,默认返回None
# None
v5 =next(data) # StopIteration
# 结束或中途遇到return,程序爆:StopIteration 错误
# from连用
def foo():
yield 2
yield 3
def func():
yield 1
yield from foo()
yield 4
for item in func():
print(item) # 打印1,2,3,4
【例子】生成n以前的斐波那契数列数列
def libs(n):
a = 0
b = 1
while True:
yield a
a, b = b, a + b
if a > n:
return
fib = libs(100)
print(next(fib))
print(next(fib))
print(next(fib))
list(fib)
# 0
# 1
# 1
# [2, 3, 5, 8, 13, 21, 34, 55, 89]
【例子】生成30个随机数验证码,用一个取一个
import random
def gen_random_num(max_count):
counter = 0
while counter < max_count:
yield random.randint(1000, 9999)
counter += 1
data_list = gen_random_num(30)
n1 = next(data_list)
print(n1) # 用完会报错
n = 30
data_list = (random.randint(1000, 9999) for i in range(n))
n1 = next(data_list)
print(n1)
- 用元组推导式简化生成器函数
def _make_gen():
for x in range(100):
yield x ** 2
gen = _make_gen()
gen = (x ** 2 for x in range(100))
元组生成器避免作用域的影响
num1 = [lambda x: i * x for i in range(4)]
[m(2) for m in num1] # [6, 6, 6, 6]
num2 = (lambda x: i * x for i in range(4))
[m(2) for m in num2] # [0, 2, 4, 6]
sum(x ** 2 for x in range(100)) # 328350
dict((i, i **2) for i in range(3,5)) # { 3: 9, 4: 16}
注:内容主体来自阿里天池AI训练营,增加的部分来自于看其他的书籍和报名的付费课程。