Python学习3(函数、装饰器)

千峰教育b站的课程

函数

函数定义

参数可有可无

def 函数名([参数...]):
    代码

直接打印函数名,会看到函数所在的内存地址,函数名相当于一个变量,指向了这个内存地址

def gen():
    print(1)
print(gen) # <function gen at 0x000002B1092DC268>

调用,函数名()

调用函数时参数的顺序

>>> def test(a,b):
...     print(a,b)
... 
>>> test(1,2)  # 位置参数
1 2
>>> test(b=1,a=2)  # 关键字参数
2 1
>>> 
>>> test(b=1,2)  # 关键字参数写在位置参数之前会导致出错
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

怎么保证传入的参数符合类型要求
isinstance(变量, 类型)

def get_sum(a,b):
    if isinstance(a, int) and isinstance(b, int):
        print(a + b)
    else:
        print('type error')
get_sum(1.2,2)

参数默认值

在定义参数的时候可以给参数赋默认值,这样在调用的时候可以不传递这个参数
且默认值参数必须在普通参数的后面(其实也很好理解,因为调用函数传递的时候是按位置传递的)

# def get_sum(a, c = 100,b): # 报错
def get_sum(a,b, c = 100): # c给定了默认值100
    if isinstance(a, int) and isinstance(b, int):
        print(a + b + c)
    else:
        print('type error')
get_sum(1,2) #103

关键字参数

调用的时候明确使用参数名赋值,因为默认是按位置顺序赋值的,为了防止因为位置传递发生错误,可以使用关键字参数赋值
例如下面例子中,如果c想用默认值,而d要传递参数,就需要用到关键字参数

def get_sum(a,b, c = 100,d=200):
    if isinstance(a, int) and isinstance(b, int):
        print(a + b + c + d)
    else:
        print('type error')
get_sum(1,2, d = 5)

可变参数

*args 接收单独的变量
**kwargs

*args

考虑场景:如果想用一个函数求两个数、三个数…多个数的和
可以使用可变参数,这样将传入的多个参数形成一个元组,传递给函数
也可以什么都不传,相当于一个空的元组
这种自动形成元组的行为成为装包

def get_sum(*args):
    print(args)
get_sum(1,2)     # (1, 2) 输出的是元组
get_sum(1,2,3,4,5) # (1, 2, 3, 4, 5)
get_sum() # ()

这里是列表

a,*b,c,d = 1,2,3,4,5,6
print(a,b,c,d)
# 1 [2, 3, 4] 5 6

如果有两个可变参数,报错

*a,*b,c,d = 1,2,3,4,5,6  
# SyntaxError: two starred expressions in assignment

拆包

a,b,c = (1,2,3)
print(a,b,c)  # 1 2 3
# 先拆包后装包
*a,b,c = (1,2,3,5,6,7)
print(a,b,c)  # [1, 2, 3, 5] 6 7

在可变参数的函数中传入一个列表,可以看到输出的是只有一个元素的元组
那么怎么才能使得传入的函数的时候是分开的几个数呢?
可以在调用的时候进行拆包,即在调用的时候加*号

def get_sum(*args):
    print(args)
get_sum([1,2,3]) # ([1, 2, 3],)
get_sum(*[1,2,3]) # (1, 2, 3)

def get_sum(a, *args):
    print(a,args)
get_sum(1,2)     # 1 (2,)
get_sum(1,2,3,4,5) # 1 (2, 3, 4, 5)
get_sum([1,2,3]) # [1, 2, 3] ()
get_sum(*[1,2,3]) # 1 (2, 3)

**kwargs

输出是一个字典,而传递给两个位置参数的话会报错,显示需要0个位置参数
那么应该怎么传递呢?
可以传递关键词参数,会转换为字典
kw指的是key word
必须传递关键字参数,会将其转换成key:value的形式,封装到字典里

def show_book(**kwargs):
    print(kwargs)
show_book() # {}
show_book('a','b') # 报错:TypeError: show_book() takes 0 positional arguments but 2 were given
show_book(name ='a') # {'name': 'a'}

# 拆包传字典
dict1 = {'name':'a', 'num': 2}
show_book(**dict1) # {'name': 'a', 'num': 2}
# 用一个*拆包,拆出来是两个key值
print(*dict1) # name num
# 直接输出**dict1会报错
# print(**dict1) # TypeError: 'name' is an invalid keyword argument for print()

两种一起用

def show(*args, **kwargs):
    print(args)
    print(kwargs)
show(1,2)  # (1, 2)/n  {}

dict1 = {'name':'a', 'num': 2}
show(1,2,**dict1)
# (1, 2)
# {'name': 'a', 'num': 2}

如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 args的后面, 但如果有*kwargs的话,kwargs必须是最后的

def sum_nums_3(a, *args, b=22, c=33, **kwargs):
    print(a)
    print(b)
    print(c)
    print(args)
    print(kwargs)

sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)

一般情况下默认值参数会放在*args的后面

# c在前面的话,传参的时候第三个参数会赋值给c
def fun(a, b, c = 10, *args, **kwargs):
    print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 3 (4, 5) {'name': 'a'}

# c在可变参数后面,赋值的时候c需要用关键字参数赋值方法
def fun(a, b, *args, c = 10, **kwargs):
    print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 10 (3, 4, 5) {'name': 'a'}

kwargs必须在最后面
在这里插入图片描述

返回值

python中函数可以返回多个返回值
如果返回多个值,会将多个值封装到一个元组里返回

def returning():
    return 1,2
res = returning()
a,b = returning() # 1,2
print(res) # (1, 2)

全局变量和局部变量

当函数内出现局部变量和全局变量相同名字时,函数内部中的 变量名 = 数据 此时理解为定义了一个局部变量,而不是修改全局变量的值
(这里和之前学其他语言好像不太一样,在java中的一个方法中可以直接对全局变量进行更改,而在python中不能对函数外的变量直接进行修改,只能做查看的操作;为什么呢,刚刚去写了一段java代码感受了一下,可以做一个不专业的直观的解释,因为java中对变量的声明是需要加变量类型的,所以如果在函数内声明了和全部变量一样的变量,那么在函数内使用的就是这个局部变量,简单来说就是修改变量和声明变量代码是不一样的;而在python中声明变量不需要加变量类型,同样如果在函数中声明了一个和全局变量一样的变量,那么这只能认为是一个声明变量的操作,而不能作为一个修改全局变量的操作,否则就会出现问题,所以在python中函数不能直接对全局变量进行修改)

那么怎么才能在函数中修改全局变量呢?
需要在函数中声明我用的就是全局变量

a = 10
def check():
    global a # 如果不加这句话会报错,因为不能直接对全局变量进行修改
    a -= 10
    print(a)
check() # 0

如果在函数中出现global 全局变量的名字 那么这个函数中即使出现和全局变量名相同的变量名 = 数据 也理解为对全局变量进行修改,而不是定义局部变量
如果在一个函数中需要对多个全局变量进行修改,那么可以一次性全部声明,也可以分开声明

# 可以使用一次global对多个全局变量进行声明
global a, b
# 还可以用多次global声明都是可以的
# global a
# global b

查看所有的全局变量和局部变量

Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。

def test():
    a = 100
    b = 40
    print(locals())  # {'a': 100, 'b': 40}

test()

x = 'good'
y = True
print(globals())  # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101710630>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/jiangwei/Desktop/Test/test.py', '__cached__': None, 'test': <function test at 0x101695268>, 'x': 'good', 'y': True}

可变数据类型和不可变数据类型

所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变
可变类型(修改数据,内存地址不会发生变化)有: 列表、字典、集合
不可变类型(修改数据,内存地址必定发生变化)有:int、float、bool、字符串、元组
(python中有小整数对象池,字符串缓存池)
只有不可变数据类型才需要加global

函数中的注释

在函数的第一行用一个字符串作为函数文档

>>> def test(a,b):
...     "用来完成对2个数求和"  # 函数第一行写一个字符串作为函数文档
...     print("%d"%(a+b))
... 
>>> 
>>> test(11,22)  # 函数可以正常调用
33
>>>
>>> help(test)  # 使用 help 查看test函数的文档说明
Help on function test in module __main__:

test(a, b)
    用来完成对2个数求和

在函数内加一对三引号,会自动出现param…类似的,与java中的/** */相似

def get_info(name: str, age: int):
    """
    接收用户的名字和年龄,拼接一个字符串并返回

    :param name: 接收一个名字
    :param age: 接收用户的年龄,必须是 0-200 间的一个整数
    :return: 返回拼接好的字符串
    """
    return "我的名字叫 %s,今年是 %d 岁" % (name, age)


get_info("吴彦祖", 19)
get_info(520, 19)  # 注意,形参上标注的类型只是提高代码的可读性,并不会限制实参的类型
help(get_info)

引用

sys.getrefcount() 获取引用个数(本身调用这个方法也有一个引用指向目标地址),例如下面的例子 3 + 1 = 4

import sys
list1 = [1,2,3,4]
list2 = list1
list3 = list1
print(sys.getrefcount(list1)) # 4

# del list3
# print(sys.getrefcount(list1)) # 3

del list1 
print(sys.getrefcount(list2)) # 3

嵌套函数

函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。

函数嵌套
函数也是一个变量,所以函数内部也可以定义函数,相当于声明了一个变量

如果要修改外部函数的变量,需要在内部函数中声明对外部变量的使用,即加关键字nonlocal

def outer():
    a = 10
    print('outer----hello')
    def inner():  # inner这个函数是在outer函数内部定义的
        b = 20
        nonlocal a # nonlocal表示引用内部函数外侧的局部变量a
        a += b
        print('inner----hello')
    inner()  # inner函数只在outer函数内部可见
    print(inner)   # <function outer.<locals>.inner at 0x00000134E031AEA0>
    # 用来查看局部变量
    print(locals())  # {'a': 10, 'inner': <function outer.<locals>.inner at 0x00000223594EAEA0>}

outer()
# inner()  这里会报错,在outer函数外部无法访问到inner函数

闭包

符合以下三个条件是闭包:

是一个嵌套函数
内部函数引用了外部函数的变量
返回值是内部函数

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。

下面这段代码前两个调用中,因为内部函数返回的是函数名,我们知道,相当于一个变量,打印出来是它的地址
而在后两个调用中,因为outer(x)返回的是一个函数名,用函数名调用内部函数,所以返回的是内部函数的返回值,相当于res = outer(x), t = res()

因为无法直接在外部调用内部的inner()函数,所以这里在外部函数中将inner()当做返回值返回,就可以在外部使用这个内部函数

def outer(n):
    num = n
    def inner():
        return num+1
    return inner

print(outer(3))  # <function outer.<locals>.inner at 0x0000020E2905AEA0>
print(outer(5))  # <function outer.<locals>.inner at 0x0000020E2905AEA0>

print(outer(3)()) # 4
print(outer(5)()) # 6

在这段程序中,函数 inner 是函数 outer 的内嵌函数,并且 inner 函数是outer函数的返回值。我们注意到一个问题:内嵌函数 inner 中引用到外层函数中的局部变量num,Python解释器会这么处理这个问题呢? 先让我们来看看这段代码的运行结果,当我们调用分别由不同的参数调用 outer 函数得到的函数时,得到的结果是隔离的(相互不影响),也就是说每次调用outer函数后都将生成并保存一个新的局部变量num,这里outer函数返回的就是闭包。 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).

闭包一般在装饰器中使用

装饰器

首先明白,函数名也是一个变量
如下面代码中,将函数名b赋值给a,a变量指向了b函数的地址,因此运行结果是b

def a():
    print('a')


def b():
    print('b')

a = b
a() # b

为了在不修改原来已有函数的基础上,为函数添加新的功能(遵循开放封闭原则),出现了装饰器

为了给房子刷墙,写了一个匿名函数,直接执行发现,貌似没有调用任何函数,但是出现了12的结果,说明@decorator 执行了装饰器函数,装饰器函数的参数是house,即将house函数作为参数传入装饰器中,因此会打印那两行横线,然后return 返回了wapper,house接收这个返回值,house = wrapper
此时,house函数经过了装饰,已经变成了新的函数wrapper,这时候执行house会输出wrapper函数的执行结果

def decorator(func):
    print('--------------1')
    print(func)
    def wrapper():
        print('---------')
        func()
        print('刷墙')

    print('--------------2')
    return wrapper

# house = decorator(house)
@decorator
def house():
    print('毛坯房')

# 输出
# --------------1
# <function house at 0x000002112911AEA0>  (这句话可以看出在执行之前,会进行装饰,将house函数传入装饰器中)
# --------------2

# 装饰后的house变成了wrapper
print(house)  # <function decorator.<locals>.wrapper at 0x000002112911C1E0>  
house()
# 输出
# ---------
# 毛坯房
# 刷墙

装饰器的功能

  • 引入日志
  • 函数执行时间统计
  • 执行函数前预备处理
  • 执行函数后清理功能
  • 权限校验等场景
  • 缓存

带有参数的装饰器

如果原函数有参数,则装饰器内部函数也需要有参数

def check_time(action):
    def do_action(a,b):
        action(a,b)
    return do_action

@check_time
def go_to_bed(a,b):
     print('{}去{}睡觉'.format(a,b))

go_to_bed("zhangsan","床上")

不定长参数

def test(cal):
    def do_cal(*args,**kwargs):
    		# 此时传入内部函数的是一个元组,需要拆包
        cal(*args,**kwargs)
    return do_cal

# 体会多个参数的传递,需要*args
@test
def demo(a, b):
    sum = a + b
    print(sum)

# 体会关键字参数的传递,需要**kwargs
@test
def demo2(a, b, c = 4):
	sum = a + b + c
	print(sum)
	
demo(1, 2)
demo2(1, b = 2)

所以一般情况下,装饰器中的参数都会写成这两个可变参数

装饰器修饰有返回值的函数

一般情况下为了让装饰器更通用,可以有return

def t_2(cal):
    def do_cal(*args,**kwargs):
        return cal(*args,**kwargs)  # 需要再这里写return语句,表示调用函数,获取函数的返回值并返回
    return do_cal

@t_2
def demo(a,b):
    return a + b

print(demo(1, 2))  #3

装饰器带参数

了解

执行顺序如下,先检测到outer_check(23),将参数23带入outer_check函数中,执行该函数,返回check_time
然后执行check_time,将play_game传入函数中,返回do_action,此时play_game = check_time

def outer_check(time):
    def check_time(action):
        def do_action():
            if time < 22:
                return action()
            else:
                return '对不起,您不具有该权限'
        return do_action
    return check_time

@outer_check(23)
def play_game():
    return '玩儿游戏'

print(play_game())

带有多个装饰器

可见,先加载最靠近的装饰器,再加载较远的装饰器

# 定义函数:完成包裹数据
def makeBold(fn):
    print(1)

    def wrapped():
        return "<b>" + fn() + "</b>"

    print(2)
    return wrapped


# 定义函数:完成包裹数据
def makeItalic(fn):
    print(3)

    def wrapped():
        return "<i>" + fn() + "</i>"

    print(4)
    return wrapped


def make(fn):
    print(5)
    def wrapped():
        return "<z>" + fn() + "</z>"
    print(6)
    return wrapped

@make
@makeBold
@makeItalic
def t_3():
    return "hello world-3"


print(t_3())
# 输出:3 4 1 2 5 6
# <z><b><i>hello world-3</i></b></z>

匿名函数

一般不用

用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。

lambda函数的语法只包含一个语句,如下:
lambda 参数列表: 返回值表达式

sum = lambda arg1, arg2: arg1 + arg2

# 调用sum函数
print("Value of total : %d" % sum( 10, 20 ))
print("Value of total : %d" % sum( 20, 20 ))

Lambda函数能接收任何数量的参数但只能返回一个表达式的值

匿名函数可以执行任意表达式(甚至print函数),但是一般认为表达式应该有一个计算结果供返回使用。

python在编写一些执行脚本的时候可以使用lambda,这样可以接受定义函数的过程,比如写一个简单的脚本管理服务器。

应用场合:

可以作为参数传递

>>> def fun(a, b, opt):
...     print("a = " % a)
...     print("b = " % b)
...     print("result =" % opt(a, b))
...
>>> add = lambda x,y:x+y
>>> fun(1, 2, add)  # 把 add 作为实参传递
a = 1
b = 2
result = 3

Python中使用函数作为参数的内置函数和类:
在这里插入图片描述

系统高阶函数

在Python中,函数其实也是一种数据类型。
函数对应的数据类型是 function,可以把它当做是一种复杂的数据类型。
既然同样都是一种数据类型,我们就可以把它当做数字或者字符串来处理。

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。

函数作为另一个函数的参数如上已经说过,作为返回值就如闭包中返回内部函数一样,将函数名作为返回值

def test():
    print('我是test函数里输入的内容')

def demo():
    print('我是demo里输入的内容')
    return test  # test 函数作为demo函数的返回值

result = demo()  # 我是demo里输入的内容  调用 demo 函数,把demo函数的返回值赋值给 result
print(type(result)) # <class 'function'>  result 的类型是一个函数

result() # 我是demo里输入的内容    我是test函数里输入的内容   既然result是一个函数,那么就可以直接使用() 调用这个函数

demo()()  # 我是demo里输入的内容    我是test函数里输入的内容

系统高阶函数:
max,min,sorted
如要对以下内容找出年龄最大的一项

students = [
    {'name': 'zhangsan', 'age': 18, 'score': 92},
    {'name': 'lisi', 'age': 20, 'score': 90},
    {'name': 'wangwu', 'age': 19, 'score': 95},
    {'name': 'jerry', 'age': 21, 'score': 98},
    {'name': 'chris', 'age': 17, 'score': 100},
]

直接调用max不可行,max中的key关键字可以传入一个函数,表示排序的依据

students = [
    {'name': 'zhangsan', 'age': 18, 'score': 92},
    {'name': 'lisi', 'age': 20, 'score': 90},
    {'name': 'wangwu', 'age': 19, 'score': 95},
    {'name': 'jerry', 'age': 21, 'score': 98},
    {'name': 'chris', 'age': 17, 'score': 100}
]
print(max(students, key=lambda x: x['age']))

filter() 过滤,filter里的匿名函数要求返回值必须是布尔类型
选出年龄大于等于20的人

res = filter(lambda x : x.get('age') >= 20, students)
print(list(res))
# [{'name': 'lisi', 'age': 20, 'score': 90}, {'name': 'jerry', 'age': 21, 'score': 98}]

map() 函数,通过匿名函数想提取的内容,并对内容进行加工,放在一个可迭代的对象里

print(list(map(lambda x: x['score'] / 2, students)))
# [46.0, 45.0, 47.5, 49.0, 50.0]

reduce,在functools模块里,对序列进行压缩运算
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值