第四章 函数、函数调用、返回值、函数的传参、名称空间、函数名、闭包、装饰器

1.什么是函数,为什么要使用函数                                 

1.1什么是函数

    函数是对程序逻辑进行结构化或者过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝。

1.2为什么要使用函数

# 例:len函数的编写
# 在没有函数的时候输出字符串、列表的长度
s1 = 'asdfjkl;'
count = 0
for i in s1:
    count += 1
print(count)

l1 = 'asdfjkl;'
count = 0
for i in l1:
    count += 1
print(count)

### 这样编写的缺点:
# 重复代码多
# 可读性差

2.函数结构调用                                                

2.1函数结构:

def 函数名(参数列表): # 函数名的命名规则与变量与变量相同

  函数体

'''
规则:
  1.函数名是由数字字母下划线任意组合。
  2.函数名不能是数字开头。
  3.函数名不能是Python中的关键字。
   ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
  4.函数名要具有可描述性。
  5.函数名不能使用中文。
  6.函数名不能过长。
推荐:
    字母和下划线的组合
'''

2.2函数调用:

函数名()

#
def func1():
    pass
func1() # 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者
print(func1())
# 1- 执行函数func1(),得到返回值None
# 2- 输出None

3.函数的返回值return                                         

    函数内部尽量不要使用print,因为函数是以功能为导向的。

函数返回值的作用

# 1- 遇到return,结束函数。
def func1():
    print(11)
    print(22)
    return
    print(33)
    print(44)

func1()
# 11
# 22

# 2- 给函数的调用者(执行者)返回值
# 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者
# 返回值的个数对应返回内容
# 2.1- 无return 返回None
def ret():
    pass
print(ret(), type(ret()))
# None <class 'NoneType'>

# 2.2- 无return
# 不写或者None 返回None
def ret():
    pass
    return
print(ret(), type(ret()))
# None <class 'NoneType'>
def ret():
    pass
    return None
print(ret(), type(ret()))
# None <class 'NoneType'>

# 返回单个数 返回相应数据类型
def ret():
    pass
    return 6
print(ret(), type(ret()))
# 6 <class 'int'>

# 返回多个数据 将多个数放在元组中返回
def ret():
    pass
    return 6, 'abc', [1, 2, 3]
print(ret(), type(ret()))
# (6, 'abc', [1, 2, 3]) <class 'tuple'>

# 可以使用类似变量的分别赋值
a, b, c = ret()
print(a)
print(b)
print(c)
# 6
# abc
# [1, 2, 3]

4.函数的传参                                                 

4.1实参与形参

'''
实参和形参
  实参:实际参数,调用函数时传给函数的参数,可以是常量、变量、表达式、函数,传给形参
  形参:形式参数,不占内存空间,形参变量只有在调用时才分配内存单元,目的是函数调用时接收实参
二者区别:
  实参:是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参
  形参:虚拟的,不占用内存空间,形参变量只有被调用时才分配内存单元
'''
#
s1 = 'asdfjkl;'
def my_len(a): # 函数的定义()放的是形式参数,形参
    count = 0
    for i in a:
        count += 1
    return count

ret = my_len(s1) # 函数的执行()放的是实际参数,实参
print(ret)
# 8

4.2按实参分类

# 按实参来分
# 1- 位置参数,实参个数必须与形参个数一一对应,而且有顺序的
#
def func1(x, y):
    print(x, y)
func1(1)
    # func1(1)
# TypeError: func1() missing 1 required positional argument: 'y'
func1(1, 2)
# 1 2

# 2- 关键字参数,实参个数必须与形参个数一一对应,不分顺序
#
def func1(x, y, z):
    print(x, y, z)
func1(y=2, x=1, z=5)
# 1 2 5

# 3- 混合参数,实参个数必须与形参个数一一对应,而且关键字参数必须在位置参数后面。
#
def func2(a, b, c):
    print(a)
    print(b)
    print(c)
func2(1, 2, c=4)
# 1
# 2
# 4
func2(1, c=4, 2) # 关键字参数必须在位置参数后面
#     func2(1, c=4, 2)
# SyntaxError: positional argument follows keyword argument

4.3比较大小练习

# 练习:比大小
def max(x, y):
    if x > y:
        return x
    else:
        return y
print(max(5, 3))
# 5

### 三元运算表达式
def max(x, y):
    ret = x if x > y else y
    return ret
print(max(4, 3))
# 4
# 简写1
def max(x, y):
    return x if x > y else y
print(max(4, 3))
# 4
# 简写2
def max(x, y):return x if x > y else y
print(max(4, 3))
# 4

4.4按形参分类

# 1- 位置参数,形参个数必须与实参个数一一对应,而且有顺序的
#
def func1(x, y):
    print(x, y)
func1(1)
    # func1(1)
# TypeError: func1() missing 1 required positional argument: 'y'
func1(1, 2)
# 1 2

# 2- 默认参数,必须在位置参数后面
# 例:人员信息录入,可以减少大量重复信息的输入
def register(name, sex=''):
    with open('register', encoding='utf-8', mode='a') as f1:
        f1.write('{} {}
'.format(name, sex))

while True:
    name = input('请输入姓名:/q或Q退出 ')
    if name.upper() == 'Q': break
    if 'f' in name:
        sex = input('请输入性别:')
        register(name, sex)
    else:
        register(name)
'''
请输入姓名:/q或Q退出 张三
请输入姓名:/q或Q退出 李四
请输入姓名:/q或Q退出 f小红
请输入性别:女
请输入姓名:/q或Q退出 q
# 张三 男
# 李四 男
# f小红 女
'''

# 3- 动态参数 *args,**kwargs 万能参数
def func2(*args, **kwargs):
    print(args)  # 元组(实参的所有位置参数)
    print(kwargs) # 字典(实参的所有关键字参数的键值对形式)

func2(1, 2, 3, 4, 5, 'alex', [2, 3, 4], a='www', b=222)
# (1, 2, 3, 4, 5, 'alex', [2, 3, 4])
# {'a': 'www', 'b': 222}

4.5形参顺序

# 形参最终顺序:位置参数,*args,默认参数,**kwargs
# 1- *args位置
def func3(a, b, sex='', *args):
    print(a)
    print(b)
    print(sex)
    print(args)
func3(1, 2, '老男孩', 'alex', 'wusir')
# 如果默认参数在args前面,默认参数会被覆盖掉
# 1
# 2
# 老男孩
# ('alex', 'wusir')

def func3(a, b, *args, sex=''):
    print(a)
    print(b)
    print(sex)
    print(args)
func3(1, 2, '老男孩', 'alex', 'wusir')
# 1
# 2
#
# ('老男孩', 'alex', 'wusir')

# **kwargs位置
# def func3(a, b, *args, **kwargs, sex='男'):
    # SyntaxError: invalid syntax 语法错误
def func3(a, b, *args, sex='', **kwargs):
    print(a)
    print(b)
    print(sex)
    print(args)
    print(kwargs)
func3(1, 2, '老男孩', 'alex', 'wusir', sex='', name='wusir')
# 1
# 2
#
# ('老男孩', 'alex', 'wusir')
# {'name': 'wusir'}

4.6参数的聚合和打散(*、**)

#
def func1(*args, **kwargs): # 函数定义时*表示聚合
    print(args)
    print(kwargs)
l1 = [1, 2, 3, 4]
l2 = ['alex', 'wusir', 4]
func1(l1, l2)
# ([1, 2, 3, 4], ['alex', 'wusir', 4])
func1(*l1, *l2)
# 表示将列表l1和l2打散,把每一个元素当成位置参数,传入函数
# (1, 2, 3, 4, 'alex', 'wusir', 4)
# 函数的执行:*打散功能,把每一个元素当成位置参数,传入函数
func1(1, 2, 3, 4, 'alex', 'wusir', 4)
# (1, 2, 3, 4, 'alex', 'wusir', 4)

dic1 = {'name1':'alex'}
dic2 = {'name2':'laonanhai'}
func1(dic1, dic2) # 不添加**表示两个位置参数,传入函数args,输出元组
({'name1': 'alex'}, {'name2': 'laonanhai'})
func1(**dic1, **dic2) # 添加**表示将字典打散,当成两个关键字参数,传入函数kwargs,输出字典
# {'name1': 'alex', 'name2': 'laonanhai'}

5.名称空间                                                   

 5.1名称空间的由来

    Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。

  等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

    我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。

#
# python是解释型编程语言,当程序开始执行时,将代码一行一行的解释成二进制,执行。
name = 'alex'
# 在内存中开辟一块空间,将变量name与值'alex'的对应关系存入内存
age = 12
# 在内存中开辟一块空间,将变量age与值12的对应关系存入内存
def func1():
# 定义函数的时候,解释器在内存中开辟一块空间,象征性的将函数名存入内存,**并不关系函数内部
name1 = 'wusir'
# 函数中的变量只在函数内部使用
age1 = 34
# 函数中的变量只在函数内部使用
func1() # 执行函数的时候,解释器会在内存中再开辟一块空间,才会关注函数内部的结构逻辑,而函数中的变量只在函数内部使用,随着函数的结束,这块内存中的内容也会被清空
5.2名称空间的分类
内置名称空间:中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。
全局名称空间:代码在运行伊始,创建的存储“变量名与值的关系”的空间
局部名称空间:在函数的运行中开辟的临时的空间,该空间在函数调用时生效,调用结束后失效
加载顺序:内置名称空间------>全局名称空间----->局部名称空间
名字的查找顺序:局部名称空间------>全局名称空间----->内置名称空间
5.3作用域
全局作用域:全局名称空间,内置名称空间
局部作用域:局部名称空间
5.4加载顺序,取值顺序
加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时)
取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间
# 例:取值顺序
name1 = 'wusir'
def func1():
    name1 = 'alex'
    print(name1)
func1()
# alex
# 说明:函数先从小的作用域(局部作用域)取值,也就是函数内部取值,打印,结果就为alex
5.5输出全局名称空间变量和局部名称空间变量内置函数globals()和locals()
# globals() 输出全局名称空间的变量
name1 = 'wusir'
def func1():
    name1 = 'alex'
    print(globals())
func1()
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001D4349BB278>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/temp.py', '__cached__': None, 'name1': 'wusir', 'func1': <function func1 at 0x000001D432B42EA0>}

# locals() 输出局部名称空间的变量
name2 = 'wusir'
def func2():
    name2 = 'alex'
    print(locals())
func2()
# {'name2': 'alex'}

# 在局部名称空间,可以对全局名称空间的变量进行引用,但是不能改变
count = 1
def func1():
    count += 1
    print(count)
func1()
#     count += 1
# UnboundLocalError: local variable 'count' referenced before assignment

5.6关键字global、nonlocal

global

# global 1- 在局部名称空间声明一个全局变量,只要函数运行一次,全局变量即生效
#        2- 如果变量存在,可以更改全局变量
# 在全局名称空间引用局部名称空间变量会报错
def func1():
    name = 'alex'
    return
print(name)
#     print(name)
# NameError: name 'name' is not defined

# 添加global关键字,如果全局变量不存在,可以声明全局变量,只要函数运行一次,全局变量即生效
def func1():
    global  name
    name = 'alex'
    return
func1() # 必须执行一次,函数内部的结构,才能加载到内存中
print(name)
# alex

# 不添加global,如果全局变量存在,全局变量不会改变
name = 'wusir'
def func1():
    # global name
    name = 'alex'
    return
func1()
print(name)
# wusir

# 添加global,如果全局变量存在,可以修改全局变量
name = 'wusir'
def func1():
    global name
    name = 'alex'
    return
func1()
print(name)
# alex

nonlocal

# nonlocal 1- 不能修改全局变量。
#         2- 在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
# 例1
name1 = 'alex'
def func1():
    nonlocal name1
    name1 = 'wusir'
func1()
#    nonlocal name1
#     ^
# SyntaxError: no binding for nonlocal 'name1' found

# 例2
def func1():
    name1 = 'alex'
    print('1.'+name1) # 打印局部变量name1 -- alex
    def inner():
        nonlocal name1 # 修改局部变量name1 -- wusir
        name1 = 'wusir'
        print('2.'+name1) # 打印修改后的局部变量name1 -- wusir
    inner()
    print('3.'+name1) # 依然在局部名称空间中,打印修改后的局部变量name1 -- wusir
func1()
# 1.alex
# 2.wusir
# 3.wusir

6.函数名                                                      

# 函数名-- 有普通变量的功能和加()就执行的功能
# 1.可以互相赋值,将函数赋值f1,变量加括号就执行
def func1():
    print(666)
f1 = func1
f1()
# 666

# 2.函数名可以当成函数的参数
def func1():
    print(666)
def func2(argv):
    argv()
    print(777)
func2(func1)
# 666
# 777

# 3.可以当成容器类数据类型的参数
def func1():
    print(666)
def func2():
    print(777)
def func3():
    print(888)
l1 = [func1, func2, func3]
for i in l1:
     i()
# 666
# 777
# 888

# 4.函数名可以当成函数的返回值,普通变量有的功能函数名都有
def func1():
    print(666)

def func2(argv):
    print(777)
    return argv

ret = func2(func1)
ret()
# 777
# 666

7.闭包                                                        

# 闭包 内层函数对外层函数非全局变量的引用,叫做闭包
# 闭包的好处:如果python检测到闭包
# 他有一个机制,你的局部作用域不会随着函数的结束而结束,爬虫的重复利用机制
#
# 这种形式:
def wrapper():
    name1 = 'laonanhai'
    def inner():
        print(name1)
    inner()
wrapper()
# laonanhai

# 这不是闭包,因为引用了全局变量,所以返回None
name1 = 'laonanhai'
def wrapper():
    def inner():
        print(name1)
    inner()
    print(inner.__closure__)
wrapper()
# None

# 这是闭包 内层函数对外层函数非全局变量的引用
def wrapper(argv):
    # argv = 'alex'
    def inner():
        print(argv)
    inner()
    print(inner.__closure__)
name = 'alex'
wrapper(name)
# (<cell at 0x00000159C8B885E8: str object at 0x00000159C8B4C378>,)

# 例:简单爬虫
from urllib.request import urlopen
def index():
    url = "http://www.cnblogs.com/gnaix"
    def get():
        return urlopen(url).read()
    return get

content1 = index()()
content2 = index()()
print(content1)

7.装饰器                                                     

7.1用两种方式执行函数的内嵌函数

# 用两种方式执行函数的内嵌函数
def wrapper():
    def inner():
        name1 = 'alex'
        print(name1)
wrapper()

# 方法一: 在wrapper函数内执行inner函数
def wrapper():
    def inner():
        name1 = 'alex'
        print(name1)
    inner()
wrapper()

# 方法二:利用函数的返回值,将inner函数名返回wrapper函数的调用者
def wrapper():
    def inner():
        name1 = 'alex'
        print(name1)
    return inner

ret = wrapper()
ret()
#
wrapper()()
# alex
# alex

# 错误实例:
def wrapper():
    def inner():
        name1 = 'alex'
        print(name1)
inner()
# 错误提示
#     inner()
# NameError: name 'inner' is not defined
# 原因分析
# 取值问题:取值顺序,有内而外
# 加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时)
# 取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间

7.2装饰器的推导过程

# 装饰器的产生,测试函数的执行效率(时间)
# 1.代码方式检测
import time
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)

start_time = time.time()
func1()
end_time = time.time()
print('函数的执行效率为 %s' % (end_time - start_time))
# 晚上回去吃烧烤...
# 函数的执行效率为 0.3010380268096924

# 2.简单函数方式,不可以重复利用
import time
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)

def timer():
    start_time = time.time()
    func1()
    end_time = time.time()
    print('函数的执行效率为 %s' % (end_time - start_time))

timer()
# 晚上回去吃烧烤...
# 函数的执行效率为 0.30023193359375

# 3.通过传参的函数方式,可以重复利用
# 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1)
# 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题
import time
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)

def func2():
    print('晚上回去喝啤酒...')
    time.sleep(0.3)

def timer(f1):
    start_time = time.time()
    f1()
    end_time = time.time()
    print('函数的执行效率为 %s' % (end_time - start_time))

timer(func1)
timer(func2)
# 晚上回去吃烧烤...
# 函数的执行效率为 0.30021190643310547
# 晚上回去喝啤酒...
# 函数的执行效率为 0.30055713653564453

# 4.通过传参的函数方式,可以重复利用
# 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1)
# 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题
import time
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)

def func2():
    print('晚上回去喝啤酒...')
    time.sleep(0.3)

def timer(f1):
    start_time = time.time()
    f1()
    end_time = time.time()
    print('函数的执行效率为 %s' % (end_time - start_time))

f = func1
func1 = timer
func1(f)
# 晚上回去吃烧烤...
# 函数的执行效率为 0.30002880096435547

# 5.最简单版的装饰器
import time
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)

def func2():
    print('晚上回去喝啤酒...')
    time.sleep(0.3)

def timer(f1):
    def inner():
        start_time = time.time()
        f1()
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    return inner

# 关系转换
func1 = timer(func1) # inner
func1() # inner(),这个func1是一个新的变量
# 晚上回去吃烧烤...
# 函数的执行效率为 0.3011753559112549
# 遇到等号,先执行等号右面的,右面等于执行timer(func1)这里的func1仅代表函数名
# func1 == inner
# Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来, 但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
# 等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中, 函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

# 6.语法糖
# 装饰器,在不改变原函数即原函数的调用的情况下,为原函数
# 增加一些额外的功能,打印日志,执行时间,登录认证等等。
import time
def timer(f1):
    def inner():
        start_time = time.time()
        f1()
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    return inner

@timer # 关系转换,func1 = timer(func1) # inner
def func1():
    print('晚上回去吃烧烤...')
    time.sleep(0.3)
func1()
# 晚上回去吃烧烤...
# 函数的执行效率为 0.30006885528564453

# 7.被装饰函数带参数
import time
def timer(f1): # f = func1
    def inner(*args, **kwargs):
        start_time = time.time()
        f1(*args, **kwargs)
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    return inner

@timer # 关系转换,func1 = timer(func1) # inner
# func1 = timer(func1)
# 遇到等号先执行等号右面的,timer(func1)此时func1是以函数名
# 的形式传入timer的参数,返回给timer的调用者inner函数名
# 所以func1 ==> inner

def func1(a, b):
    print(a, b)
    print('晚上回去吃烧烤...')
    time.sleep(0.3)
func1(111, 222) # ==>timer(func1)(111, 222)
# 111 222
# 晚上回去吃烧烤...
# 函数的执行效率为 0.3001117706298828

# 8.带返回值的装饰器
import time
def timer(f1):
    def inner(*args, **kwargs):
        start_time = time.time()
        ret = f1(*args, **kwargs) # func1()的调用者
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
        return ret
    return inner
@timer # func1 = timer(func1) inner
def func1(a, b):
    print(a, b)
    print('晚上回去吃烧烤...')
    time.sleep(0.3)
    return 666

print(func1(111, 222))
# 111 222
# 晚上回去吃烧烤...
# 函数的执行效率为 0.3004331588745117
# 666

# 9.最终版本装饰器
# 简单版
def wrapper(f1):
    def inner():
        f1()
    return inner

# 带参数,返回值版
def wrapper(f1): #f1被装饰函数的函数名
    def inner(*args, **kwargs):
        '''调用函数之前的操作'''
        ret = f1(*args, **kwargs)
        '''调用函数之后的操作'''
        return ret
    return inner
一鼓作气,再而衰,三而竭。
原文地址:https://www.cnblogs.com/gongniue/p/8855993.html