函数
随着程序的功能增多
,如果将所有的代码都放在一起,代码的 可读性会很差
, 不方便以后维护
, 不方便功能的扩展
, 复用性也不高
,函数就是很好的解决上面的问题的。
1. 什么是函数
函数就是将一段具有独立功能的代码块
整合到一个整体并命名,在需要的位置调用这个名称
即可完成对应的需求, 函数的过程分为定义函数
和调用过程
2. 定义函数
函数的使用必须遵循先定义,后调用的原则
,指的是将内存地址赋值给函数名
,函数名就是对这段代码的引用,没有事先定义函数而直接调用,就相当于在引用一个不存在的变量名
定义语法
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
def
: 定义函数的关键字函数名
:函数名指向函数内存地址
,是对函数体代码的引用
,函数的命名应该反应出函数的的功能括号
: 括号内定义参数,参数可有可无,且不需要指定参数的类型,但是括号一定要有冒号
: 括号后要加冒号,然后在下一行开始缩进编写函数体代码"""文档描述"""
: 描述函数的功能,参数介绍等信息文档,非必要,建议加上,从而增加函数的可读性函数体
: 由语句和表达式组成return 值
: 定义函数的返回值,return 是可有可无
参数
参数是函数的调用者向函数体传值的媒介
,若函数体代码逻辑依赖外部传来的参数时
,则需要定义参数
有参函数
def res_max(x, y):
res = x if x > y else y
return res
无参数函数
def func():
a = 'abc'
print('我是无参函数')
return a
3 调用函数和函数返回值
3.1 调用函数
函数在定义阶段
只检测语法正确与否,不会执行函数体代码,函数在调用阶段
才会执行函数体代码,调用方式 函数名(参数)
定义阶段
def res_max(x, y):
res = x if x > y else y
return res
def func():
a = 'abc'
print('我是无参函数')
return a
调用阶段
res = res_max(1, 4)
data = func()
定义函数res_max
和func
语法无错误,而在调用阶段用res_max
与func
都已存在在内存中
3.2 return
若需要将函数体代码的执行结果返回给调用者
,则需要使用到return
。return 后的无值或着直接省略
return 则默认返回None
, return的返回值无类型限制
,且可以将多个值放入到一个元祖内
示例
def bar(x, y, z):
return x, y, z
data = bar(1, 3, 2)
print(data)
# 结果:(1, 3, 2)
return
是一个函数结束的标志
,函数体可以有多个 return
,但只执行一次
函数就结束了,并把 return 后定义的值作为本次调用的结果返回
3.3 函数的参数
函数的参数在定义和调用阶段,大致可以分为行参
和实参
, 在函数定义阶段
括号内声明的参数,叫做 行参
,行参可以理解为一个变量名,在函数调用的时候用来赋值,在函数调用阶段
括号内传入的值,叫做实参
参数的分类位置参数
调用函数时根据函数定义的参数位置来传递参数
#定义阶段: 定义位置形参
def user_info(name, age, gender):
print(f'您的名字是:{name}, 年龄是:{age}, 性别是:{gender}')
# 调用阶段: 对应关系name=’TOM’,age=20,gender=’男’
user_info('TOM', 20, '男')
# 结果: 您的名字是TOM, 年龄是20, 性别是男
传递和定义参数的顺序及个数必须一致
, 否则程序会抛出异常
关键字参数
通过键=值
形式加以指定,可以不按照定义参数的顺序要求传递
def user_info(name, age, gender):
print(f'您的名字是:{name}, 年龄是:{age}, 性别是:{gender}')
# 调用阶段,关键字参数传递
user_info('Rose', age=20, gender='女')
user_info('小明', gender='男', age=16)
函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面
,但关键字参数之间不存在先后顺序
。
默认参数
用于定义函数,为参数提供默认值
,调用函数时可不传该默认参数的值
,所有位置参数必须出现在默认参数前,包括函数定义和调用
def user_info(name, age, gender='男'):
print(f'您的名字是:{name}, 年龄是:{age}, 性别是:{gender}')
# 调用阶段传递参数
user_info('TOM', 20)
user_info('Rose', 18, '女')
可变长参数
在函数不确定有多少个参数传入时
,可用包裹位置参数
,或者包裹关键字参数
,来进行参数传递
# 可变长参数 *
def foo(x, y, *args): # args=(3, 4, 5, 6)
print(x, y)
print(args)
foo(1, 2, 3, 4, 5, 6)
foo(1, 2, *[3, 4, 5, 6]) # foo(1, 2, 3, 4, 5, 6)
foo(*[1, 2, 3, 4, 5, 6]) # foo(1, 2, 3, 4, 5, 6)
# 可变长参数 **kwargs
def foo(x, y, **kwargs): # kwargs={'a': 3, 'b': 4, 'c': 5}
print(x, y)
print(kwargs)
foo(x=1, y=2, a=3, b=4, c=5)
foo(x=1, **{'y': 1, 'c': 5, 'a': 3, 'b': 4})
foo(**{'y': 1, 'x': 5, 'c': 5, 'a': 3, 'b': 4})
def bar(x, y, z):
print(x, y, z)
# 定义可变成参数
def wrapper(*args, **kwargs):
print(args, kwargs)
# 调用bar函数
bar(*args, **kwargs) # bar(*(1, 2, 3,), ** {'b': 2, 'c': 3})
wrapper(1, 2, 3)
wrapper(1, z=2, y=3)
传进的所有参数都会被args变量收集
,它会根据传进参数的位置合并为一个元组(tuple)
,args是元组类型
,这就是包裹位置传递, 无论是包裹位置传递
还是包裹关键字传递
,实际上都是一个组包的过程。
4. 命名空间
命名空间
简单来说就是存放名字与值的绑定关系
,是一个从名字到对象的映射
, python中大致分为三种命名空间,内置命名空间
, 全局名称空间
,局部名称空间
,它们的加载顺序是 内置->全局->局部
,查找顺序从最近的名称空间里查找
,如果没有然后再往外层名称空间查找
内置命名空间
: python解释器内置的名字
, max, len, print, (python解释器就有)全局命名空间
: 不是函数内部定义的,也不是内置的,就是全局名称空间,文件级别定义的名字
,(执行文件时生效)局部名称空间
:函数内部定义的名字
,它的特点就是函数调用时生效,函数结束时失效
示例
# 全局名称空间
import time
x = 1
def func():
pass
if x == 1:
y = 2
# 局部名称空间
def foo():
x = 10
return x + 1
res = foo()
print(res)
# 结果: 11
print(x)
# 结果: 1
foo()
函数里面用到x
变量,所以首先会在自己局部命令空间
里查找有没有这个变量,所以不会用到全局命名空间
中的x
, 所以结果是11
查看名称空间的名字
python中提供了globals
方法查看全局名称空间
的名字, locals
方法查看局部名称空间
的名字, 内置名称空间存放在 builtins
模块中
def func():
x = 1
print(locals())
return
func()
# 结果: 局部名称空间 {'x': 1}
print(globals())
# 全局名称空间:{'__name__': '__main__', .... 'x': 1, 'func': <function func at 0x10f9f72f0>}
print(dir(globals()['__builtins__']))
# 内置名称空间: ['ArithmeticError', 'AssertionError', 'AttributeError', ... vars', 'zip']
内置名称空间与全局名称空间,全局存活, 全局有效
,globals()
局部名称空间的名字,临时存活,局部有效 locals()
作用域
变量作用域指的是变量生效的范围
,python中主要分为两类:局部变量
和全局变量
。局部变量
指的是定义在函数体内部的变量
,即只在函数体内部生效
,全部变量
指的是在函数体内、外都能生效
的变量。
x = 100
def f1():
x = 1
def f2():
x = 2
print('form foo2', x)
def f3():
x = 3
print('from foo3', x)
f3()
print('from foo', x)
f2()
f1()
"""
from foo 1
form foo2 2
from foo3 3
"""
局部变量修改全局变量
如果想要在局部作用域
修改全局作用域
的变量,python 提供了关键字global
来实现
a = 100
def func():
print(a)
def bar():
# global 关键字声明a是全局变量
global a
a = 200
print(a)
func() # 100
bar() # 200
print(f'全局变量a = {a}') # 全局变量a = 200
内部函数修改外部函数的值
在内外部结构函数中
,内部函数想要修改外部函数作用域的值,python 提供了nolocal
关键字来实现
def outer():
a = 100
print(f'局部变量定义时的值:{a}')
print(f'内存地址为:{id(a)}')
def inner():
nonlocal a
a = 200
print(f'局部变量过后的值:{a}')
print(f'内存地址为:{id(a)}')
return a
inner()
outer()
"""
局部变量定义时的值:100
内存地址为:4303630896
局部变量过后的值:200
内存地址为:4303634096
"""
综合
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
"""
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
"""
5. 闭包
在python中一切皆对象
,函数也一样,函数可以被引用
,可以当作容器类型的元素
,可以被当作参数传入
,可以被当作函数返回值返回
,闭包就是 必须有内外部函数这样的结构
, 内部函数使用到外部函数的变量
, 内部函数被外部函数作为返回值返回
, 这样的好处就是始终以 自带的作用域为准
。
函数可以被引用
def func(x, y):
print(x, y)
f = func
f(1, 6)
# 结果: 1 6
函数当作容器类型的元素
def func():
print('from func-----')
def bar(func):
return func
dd ={'func': func, 'bar': bar}
func = dd['func']()
# 结果: from func-----
可以被当作返回值
def foo():
print('from foo')
def bar(func):
return func
f = bar(foo)
f()
# 结果: from foo
一个简单的闭包函数
z = 1
x = 2
y = 3
def outer():
x = 100
y = 200
def inner():
print(x, y)
return inner
f = outer()
f()
# 结果: 100 200
outer
内的局部变量,应该是调用函数outer()
结束时就已经失效,但是结果发发现并没有失效,这是因为变量被保存到__closure__
属性中
print(f.__closure__[0].cell_contents, f.__closure__[1].cell_contents)
# 结果:100 200
6. 装饰器
装饰器的作用 就是在不修改被装饰对象源代码
和调用方式
的前提下为被装饰对象添加额外的功能
,把一个函数当做参数然后返回一个替代版函数,本质上上还是一个函数,装饰器可以分为无参装饰器
,有参装饰器
为函数添加统计运行时间的需求
import time
def record_time():
time.sleep(3)
print('我正在运行过程中。。。。。。')
return
# 调用函数
record_time()
不使用装饰器实现如下
start_time = time.time()
record_time() # 函数执行
stop_time = time.time()
print(f'函数运行时间为 {(stop_time - start_time)}')
但是这样做的话,代码复用性太差,有很多函数都要实现的就比较困难,可以考虑把函数当作参数传入
def my_wrapper(func):
start_time = time.time()
res = func() # 调用函数
stop_time = time.time()
print(f'函数运行时间为 {(stop_time - start_time)}')
return res
这样的话也实现了需求,但是都要使用my_wrapper(record_time)
或者my_wrapper(其他函数)
,但是这样我们就违背了被装饰函数的调用方式原则
,可以考虑直接返回一个函数给用户使用
def collect_timer(func):
def my_wrapper():
func start_time = time.time()
# 引用外部作用域的变量
res = func()
stop_time = time.time()
print(f'函数运行时间为 {(stop_time - start_time)}')
return res
return my_wrapper
record_time = collect_timer(record_time)
# 得到record_time=my_wrapper,my_wrapper携带对外作用域的引用:func=原始的record_time
record_time()
# 执行的是my_wrapper(),在my_wrapper的函数体内再执行最原始的record_time
我没有修改record_time
函数里面的任何逻辑,只是给record_time
变量重新赋值了,指向了一个新的函数对象。最后调用record_time()
这里的collect_timer
函数其实就是一个装饰器,装饰器是一个带有函数作为参数并返回一个新函数的闭包,本质上装饰器也是函数。collect_timer 函数的返回值是my_wrapper
函数,在my_wrapper
函数中,除了执行计算执行时间操作,还有业务代码,该函数重新赋值给record_time
变量后,调用record_time
就相当于调用my_wrapper()
python中提供了record_time = collect_timer(record_time)
的语法糖形式,需要被装饰的对象的上方添加@collect_timer
,当解释器解释到@collect_timer
时,就会调用collect_timer()
函数,且把它下方的函数名当作实参传入,然后将返回结果重新赋予给原函数名
import time
@collect_timer
def record_time():
time.sleep(3)
print('我正在运行过程中。。。。。。')
return
record_time()
有参数装饰器基本结构
def decorator(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper
示例
import time
def my_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print(f'函数运行时间为 {(stop_time - start_time)}')
return res
return wrapper
@my_decorator
def record_time(name):
time.sleep(3)
print(f'我正在运行过程中。。。。。。{name}')
return
在被装饰之后record_time=wrapper
,想要保留原函数的文档和函数名属性,需要使用functools
中的wraps
装饰
import time
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print(f'函数运行时间为 {(stop_time - start_time)}')
return res
print(wrapper.__name__)
print(func.__name__)
return wrapper
@my_decorator
def record_time(name):
time.sleep(3)
print(f'我正在运行过程中。。。。。。{name}')
return
record_time('action')
7. 迭代器
要明白迭代器之前,首先要知道什么是容器对象
,和可迭代对象
,容器对象
是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,python 中常见的容器类型有:list
,set
,tuple
,str
,dict
,大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是可迭代对象
赋予了容器这种能力。内置有__iter__
方法的对象都是可迭代对象,像常见的list
,set
,tuple
,str
,dict
都是 可迭代对象
可迭代对象
l1 = [1, 2, 3, 4, 5]
print(l1.__iter__)
# <method-wrapper '__iter__' of list object at 0x10ff74f48>
迭代器
实现__next__()
和__iter__()
方法的对象就是迭代器,__iter__()
返回的是迭代器自身,__next__()
, 返回容器中的下一个值,如果容器中没有更多元素了,则抛出Stop Iteration
异常。
迭代器 不依赖于索引迭代取值的方式
,无论有序还是无序对象都可以按照迭代器的方式取值
将可迭代对象
转换为迭代器对象
要依赖于iter()
方法实现,但是实际上就是在调用__iter__()
方法.
迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。迭代器有一种具体的迭代器类型,比如list_iterator
,set_iterator
,可迭代对象
实现了__iter__
方法,这种方法会返回一个迭代对象
l1 = [1, 2, 3, 4, 5]
iterator = iter(l1)
print(next(iterator))
# 1
print(next(iterator))
# 2
print(next(iterator))
# 3
print(next(iterator))
# 4
print(next(iterator))
# 5
next()
方法实际上是在调用__next__()
方法,
当next()
将迭代器对象里面的值迭代完毕,如果还继续调用next()
进行迭代的话,程序会抛出StopIteration
错误
8. 生成器
生成器(Generator)
,是python 后来新加入的一个特性,一个函数可以返回多次结果,而不是像普通函数一样只返回一次
,普通的python函数内部, 加个yield关键字
, python解析器就将该函数视为一个生成器函数,使用生成器表达式或者yield的生成器函数(生成器是一种特殊的迭代器)
,好处就是节省内存
,一次只产生一个值
示例
def my_generator():
yield '红楼梦'
yield ['百年孤独', '往事与随想']
yield 100
g = my_generator()
print(g)
print(g.__iter__)
print(g.__next__)
print(next(g))
print(next(g))
print(next(g))
# <generator object my_generator at 0x10ec4aa98>
# <method-wrapper '__iter__' of generator object at 0x10ec4aa98>
# <method-wrapper '__next__' of generator object at 0x10ec4aa98>
# 红楼梦
# ['百年孤独', '往事与随想']
# 100
打印这个产生的g
发现是一个generator
对象generator
对象内置有__item__
和__next__
魔法方法,打印输出结果,也能看到内容,所以生成器本身也是一个迭代器
我们使用next(g)
这个调用方,发现输出结果为第一次yield
的内容
当我们next(g)
调用次数多于yield
次数,程序会报错, 会提示我们StopIteration
yeil关键字yield
也是用于返回值,但是和 return
不同的是,函数一旦遇到return
就结束了,返回结果,而 yield
可以保存函数的运行状态挂起函数,用来返回多次值。
生成器表达式
创建生成器的方式之一,一种是带 yield
关键字的函数,另一种就是生成器式语法
generator = (expression for item in iterable if condition)
列表生成式返回的是一个 generator 对象
generator = (x for x in range(1, 10))
print(type(generator))
# <class 'generator'>
9. 递归
所谓递归
就是函数自己调用自己本身
,指的在调用一个函数的过程中, 直接或者间接调用该函数本身,称为递归函数,递归有两个阶段,递推
和回溯
,在使用递归的时候,必须要有一个明确的结束点
,不然就会无限的调用自己,可以使用sys.getgetrecursionlimit()
,查看当前计算机所支持的层级,在我们日常开发中,如果要遍历一个文件夹下面所有的文件
示例
3以内数字累加和
list1 = [1, [2, [3, [4, [5, [6, [7]]]]]]]
def func(l):
for item in l:
if isinstance(item, list):
func(item)
else:
print(item)
func(list1)
遍历文件
def get_all_files(dir_path):
import os
for file_path in os.listdir(dir_path):
new_path = os.path.join(dir_path, file_path)
if os.path.isdir(new_path):
get_all_files(new_path)
else:
all_files.append(new_path)
return all_files
dir_path = "/Users/wuchang/Desktop/life/python/python/mine/action"
all_files = get_all_files(dir_path)
10. lambda 匿名函数
对比使用def
关键字创建的是有名函数,使用lambda
创建的是没有名字的函数,即匿名函数,多搭配 高阶函数的使用
语法
lambda 参数1,参数2,...: expression
lambda表达式的参数可有可无
,函数的参数在lambda表达式中完全适用
lambda表达式能接收任何数量的参数
但只能返回一个表达式的值
。
# 1、定义
lambda x, y, z: x + y + z
# 等同于
def func(x, y, z):
return x + y + z
# 2、调用
# 方式一:
res = (lambda x, y, z: x + y + z)(1, 2, 3)
print(res)
# 结果: 6
# 方式二:
func = lambda x, y, z: x + y + z # “匿名”的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res = func(1, 2, 3)
print(res)
# 结果: 6
直接打印lambda表达式
,输出的是此lambda的内存地址
匿名函数与有名函数有相同的作用域
,但是匿名意味着引用计数为0
,使用一次就释放
,所以匿名函数用于临时使用一次的场景,匿名函数常与其他函数配合使用
参数形式无参
fn1 = lambda: 100
print(fn1())
一个参数
fn1 = lambda a: a
print(fn1('hello world'))
默认参数
fn1 = lambda a, b, c=100: a + b + c
print(fn1(10, 20))
可变长参数 *args
fn1 = lambda *args: args
print(fn1(10, 20, 30))
这里的可变参数传入到lambda之后,返回值为元组
可变长参数 **kwargs
fn1 = lambda **kwargs: kwargs
print(fn1(name='python', age=20))
示例
lambda字典根据某一key排序
students = [
{'name': 'TOM', 'age': 20},
{'name': 'ROSE', 'age': 19},
{'name': 'Jack', 'age': 22}
]
# 按name值升序排列
students.sort(key=lambda x: x['name'])
print(students)
# 结果: [{'name': 'Jack', 'age': 22}, {'name': 'ROSE', 'age': 19}, {'name': 'TOM', 'age': 20}]
# 按name值降序排列
students.sort(key=lambda x: x['name'], reverse=True)
print(students)
# 结果: [{'name': 'TOM', 'age': 20}, {'name': 'ROSE', 'age': 19}, {'name': 'Jack', 'age': 22}]
# 按age值升序排列
students.sort(key=lambda x: x['age'])
print(students)
# 结果: [{'name': 'ROSE', 'age': 19}, {'name': 'TOM', 'age': 20}, {'name': 'Jack', 'age': 22}]
11. 高阶函数
把函数作为参数传入
,这样的函数称为高阶函数,高阶函数是函数式编程的体现。函数式编程就是指这种高度抽象的编程范式, python 提供了很多的高阶函数,这里介绍 map
,reduce
, filter
map语法
map(func, lst),将 "传入的函数变量func作用到lst变量的每个元素中",并将结果组成新的列表(Python2)/迭代器(Python3)返回。
list1 = [1, 2, 3, 4, 5]
def func(x):
return x ** 2
result = map(func, list1)
print(result)
# 结果: <map object at 0x0000013769653198>
print(list(result))
# 结果: [1, 4, 9, 16, 25]
reduce语法
reduce(func,lst),其中func必须有 "两个参数"。每次 "func计算的结果继续和序列的下一个元素做累积计算"。
import functools
list1 = [1, 2, 3, 4, 5]
def func(a, b):
return a + b
result = functools.reduce(func, list1)
print(result)
# 结果: 15
filter语法
filter(func, lst)函数用于 "过滤序列", 过滤掉 "不符合条件的元素" , 返回一个 "filter 对象"。如果要转换为列表, 可以使用 "list()" 来转换。
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def func(x):
return x % 2 == 0
result = filter(func, list1)
print(result)
# 结果: <filter object at 0x0000017AF9DC3198>
print(list(result))
# 结果: [2, 4, 6, 8, 10]
12. 拆包与交换变量值
拆包元祖拆包
def return_num():
return 100, 200
num1, num2 = return_num()
print(num1) # 100
print(num2) # 200
字典拆包
dict1 = {'name': 'TOM', 'age': 18}
a, b = dict1
# 对字典进行拆包,取出来的是字典的key
print(a) # name
print(b) # age
print(dict1[a]) # TOM
print(dict1[b]) # 18
交换变量值
a = 100
b = 200
# 1. 定义中间变量
c = 0
# 2. 将a的数据存储到c
c = a
# 3. 将b的数据20赋值到a,此时a = 200
a = b
# 4. 将之前c的数据10赋值到b,此时b = 100
b = c
print(a) # 200
print(b) # 100
a, b = 3, 5
a, b = b, a
print(a) # 5
print(b) # 3
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。