1

函数

随着程序的功能增多,如果将所有的代码都放在一起,代码的 可读性会很差, 不方便以后维护, 不方便功能的扩展, 复用性也不高,函数就是很好的解决上面的问题的。

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_maxfunc 语法无错误,而在调用阶段用 res_maxfunc 都已存在在内存中

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_iteratorset_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

无常
21 声望0 粉丝