函数进阶

创建函数

def 语句

  • 标题行由 def 关键字、函数的名字,以及参数的集合(如果有的话)组成
  • def 子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必需的函数体

前向引用

  • 函数不允许在函数未声明之前,对其进行引用或者调用
1
2
3
4
5
6
7
8
def foo():  # 定义函数foo(),先调用bar()函数,报错,下面定义以后,报错取消
print('in foo')
bar()

def bar(): # 定义函数bar()
print('in bar')

foo() # 函数foo()已经被定义,可以直接调用

注意

  • 定义函数时,函数的先后顺序不重要,重要的是 函数在什么位置被调用

调用函数

  • 使用一对圆括号() 调用函数,如果没有圆括号,只是对函数的引用
  • 任何输入的参数都是必须放置在括号中
1
2
3
4
5
>>> def foo():		# 定义函数foo()
... print('in foo')
...
>>> foo # 调用函数时,函数名后必须有小括号,否则返回一个位置对象
>>> foo() # 函数得正确调用方式

关键字参数

  • 关键字参数的概念仅仅针对函数的调用
  • 这种理念是 让调用者通过函数调用中的参数名字来区分参数
  • 这种规范允许参数不按顺序
  • 位置参数应写在关键字参数前面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 默认参数:定义函数,给形参的默认值
def get_info(name, age=20):
print("%s is %s years old" % (name, age))
# 按照位置传参
get_info("nfx") # name="nfx" age=20
get_info("nfx", 18) # name="nfx" age=18
get_info(18, "nfx") # name=18 age="nfx"
# 关键字传参(指名道姓): 可以不按照顺序传参
get_info(name="nfx", age=18)
get_info(age=18, name="nfx")
# 注意: 关键字参数后不能有位置参数
get_info("nfx", age=20)
# get_info(name="nfx", 20): 报错
print("hello", "world", sep="---", end="!!\n")

练习 1:简单的加减法数学游戏

需求

  • 随机生成两个100以内的数字
  • 随机选择加法或是减法
  • 总是使用大的数字减去小的数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import random
def exam():
nums = [random.randint(1,100) for i in range(2)]
nums.sort(reverse=True)# 列表降序排列 [大数, 小数]
tmp = random.choice("+-") # 随机出操作符
# result: 是题目的正确答案
if tmp == "+":
result = nums[0] + nums[1]
else: # tmp == "-"
result = nums[0] - nums[1]
# 10 + 5 = ?? 请作答: answer: 计算的答案
answer = int(input("%s %s %s = ?? 请作答: " % (nums[0], tmp, nums[1])))
if answer == result:
print("Very Good~~~")
else:
print("Wrong answer!!!")

def show_menu():
while True:
exam()
tmp = input("退出(n/N): ") # 只限制退出
if tmp in "nN":
print("Byebye~")
break

if __name__ == '__main__':
show_menu()

匿名函数

  • python 允许用 lambda 关键字创造匿名函数
  • 匿名是因为不需要以标准的 def 方式来声明
  • 一个完整的 lambda “语句”代表了一个表达式,这个表达式定义体必须和声明放在同一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def add(x, y):
return x+y
def func01(num):
# if num % 2 == 1:
# return True
# else:
# return False
# if表达式
return True if num % 2 == 1 else False
test = lambda num: True if num % 2 == 1 else False
print(test(100)) # False
myadd = lambda x, y: x+y # 显示调用不推荐
print(myadd(100, 200)) # x = 100, y = 200
print(add(1, 2))

filter() 函数

  • filter(func, seq): 调用一个布尔函数 func 来迭代遍历每个序列中的元素;返回一个使 func 返回值为 true 的元素的序列
  • 如果布尔函数比较简单,直接使用 lambda 匿名函数就显得非常方便了

filter(func, seq)函数的使用,如果seq序列中的元素,传入函数func后,返回值为True,则保留下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# filter(布尔函数, 列表):
# 让列表中的元素经过布尔函数的过滤
# 将过滤结果为True的元素留下,False的元素扔掉
def func01(num):
# if num % 2 == 1:
# return True
# else:
# return False
return True if num % 2 == 1 else False
def func02(num):
# if num > 5:
# return True
# else:
# return False
return True if num > 5 else False
# def func03(num):
# return True if num % 2 == 0 else False
if __name__ == '__main__':
list01 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data1 = filter(func01, list01) # 1 3 5 7 9
print("data1:", list(data1)) # [1, 3, 5, 7, 9]
data2 = filter(func02, list01) # 6 7 8 9 10
print("data2:", list(data2)) # [6, 7, 8, 9, 10]
# data3 = filter(func03, list01)
data3 = filter(
lambda num: True if num % 2 == 0 else False,list01
)
print("data3:", list(data3)) # [2, 4, 6, 8, 10]
# 匿名函数过滤
d4=filter(lambda num:True if num>6 else False,list01)
print("data4:", list(d4)) # [7, 8, 9, 10]

map() 函数

  • map(func,seq): 调用一个函数func 来迭代遍历每个序列中的元素;返回一个经过func处理过的元素序列

map(func, seq)函数的使用,将seq序列中的元素,传入函数func后,经过处理后全部保留下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# map(加工函数, 列表):将列表中的每一个元素统一经过函数处理
def func01(num):
return num * 2 + 10
if __name__ == '__main__':
list01 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
data1 = map(func01, list01)
print("data1:", list(data1))
# [12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
# 匿名函数
data2 = map(lambda num: num+5, list01)
print("data2:", list(data2))
# map: 传布尔函数的情况
d3 = map(lambda num:True if num>5 else False,list01)
print("data3:", list(d3))
# [False, False, False, False, False, True, True, True, True, True]

总结

image-20210902173003230

变量作用域

全局变量

  • 标识符的作用域是定义为其声明在程序里的可应用范围,也就是变量的可见性
  • 在一个模块中最高级别的变量有全局作用域
  • 全局变量的一个特征是除非被删除掉,否则他们会存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的

全局变量的使用

1
2
3
4
5
>>> x = 10			# 定义全局变量x
>>> def func1(): # 定义函数func1(),函数内部可以直接使用变量x
... print(x)
...
>>> func1() #调用函数func1(),结果为10

局部变量

  • 局部变量只是暂时的存在,仅仅只依赖于定义他们的函数现阶段是否处于活动
  • 当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了
  • 一旦函数完成,框架被释放,变量将会离开作用域

局部变量只在函数内部起作用

1
2
3
4
5
6
7
8
9
10
11
>>> def func2():		#定义函数func2(), 其中的变量a为局部变量,只在函数内部有效
... a = 10
... print(a)
...
>>> def func3(): #定义函数func2(), 其中的变量a为局部变量,只在函数内部有效
... a = 'hello'
... print(a)
...
>>> func2() #调用函数func2(),结果为10
>>> func3() #调用函数func3(), 结果为hello
>>> a #查看a的值,没有被定义,函数内部的a为局部变量,只在该函数内部有效

如果局部变量与全局变量有相同的名称,那么函数运行时,局部变量的名称将会把全局变量的名称遮盖住

1
2
3
4
5
6
7
8
9
>>> x = 100		# 定义全局变量x
>>> def func5(): # 声明函数func5(), 函数内有局部变量x=200
... x = 200
... print(x)
...
>>> func5() # 局部变量
200
>>> x # 查看x【全局变量】,没有发生变化
100

global 语句

  • 因为全局变量的名字能被局部变量给遮盖掉
  • 为了明确地引用一个已命名的全局变量,必须使用 global 语句
1
2
3
4
5
6
7
8
9
>>> x = 100		#定义全局变量x
>>> def func6(): #定义函数func6()
... global x #引用全局变量x
... x = 200 #为全局变量x赋值为200
... print(x) #打印变量x的值
...

>>> func6() #调用函数func6()
>>> x

查找变量或函数的顺序

  • 首先在函数的内部去查找
  • 函数内部没有,然后去全局去查找,看是否定义
  • 全局也没有,最后会去内建函数中查找
1
2
3
4
5
6
7
# 验证python查找变量或函数的顺序,定义函数func7(),统计字符'abcd'的长度
>>> def func7():
... print(len('abcd'))
...
>>> func7() #调用函数,结果为4,正确
>>> len #全局查看是否有len,没有,不存在
# 先在函数func7()内部查找方法len(),再在全局查找,最后在内建中查找len()

生成器

Python 使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

Python有两种不同的方式提供生成器:

  • 生成器函数:

    • 常规函数定义,但是,使用 yield 语句而不是 return 语句返回结果
    • yield 语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 生成器函数
    # return: 函数执行的终止
    # yield: 表示函数执行的暂停
    def func02():
    a = 1
    yield a
    b = "hello"
    yield b
    c = [1, 2]
    yield c
    gen2 = func02()
    print("gen2:", gen2)
    # <generator object func02 at 0x7f94da357a98>
    print(gen2.__next__()) # 1
    print(gen2.__next__()) # hello
    for item in gen2:
    print("for:", item) # [1, 2]
    # gen2.__next__() # 报错
  • 生成器表达式:

    • 类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 列表推导式
    import random
    list01 = [random.randint(1, 5) for i in range(5)]
    print(list01) # [1, 3, 4, 1, 2]
    # 生成器表达式
    gen01 = (random.randint(1, 5) for i in range(5))
    print(gen01) # <generator object <genexpr> at 0x7f18cc1c8830>
    # 生成器当中的元素只能获取一遍
    # for 生成器.__next__()
    print("data:", gen01.__next__()) # 第一个数据
    print("data:", gen01.__next__()) # 第二个数据
    for item in gen01:
    print("for:", item)
    # print("data:", gen01.__next__()) # 报错:StopIteration
    • 生成器的好处在于延迟操作,需要的时候再使用,所以会节省空间
    1
    2
    sum([i for i in range(100000000)])
    sum((i for i in range(100000000)))
  • 注意事项

    • 生成器的唯一注意事项就是:生成器只能遍历一次
    • 自定义生成器函数的过程
      • 在函数内部,有很多 yield 返回中间结果;
      • 程序向函数取值时,当函数执行到第1个yield时,会暂停函数运行并返回中间结果;
      • 当主程序再次调用函数时,函数会从上次暂停的位置继续运行,当遇到第2个yield,会再次暂停运行函数并返回数据;
      • 重复以上操作,一直到函数内部的yield全部执行完成为止

练习 :文件生成器

需求:通过生成器完成以下功能

  • 使用函数实现生成器 yield
  • 函数接受一个文件对象作为参数(读文件)
  • 生成器函数每次返回文件的 10 行数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 程序 = 数据结构(列表) + 算法
# 算法:
# 1. 读取文件,按行读(readline, readlines)
# 2. 读一行给列表添加一个元素(行)
# 3. 判断列表长度是否为10
# 是: 通过yield返回列表,清空列表 不是:接着读取文件
# 4. 文件读取完毕,再次判断列表是否为空,如果不为空,将剩余行数返回
def gen_file(fobj): # fobj: 文件管家,形式参数
lines = [] # 每次存储10行记录
for item in fobj.readlines(): # for item in fobj:
lines.append(item) # 读一行给列表添加一个元素
if len(lines) == 10:
yield lines # 通过yield返回列表
lines.clear() # 清空列表
if len(lines) != 0: # 判断列表是否为空
yield lines # 将剩余行数返回
if __name__ == '__main__':
fr = open("/etc/passwd", mode="r")
gen = gen_file(fr) # fobj = fr, gen: 生成器
for item in gen: # 通过for获取生成器中的元素
print(item)
print("============================")
fr.close()

# with打开文件的方式
# with open("/etc/passwd", mode="r") as fr:
# gen = gen_file(fr)
# for item in gen: # 通过for获取生成器中的元素
# print(item)
# print("============================")

模块详解

模块

基本概念

  • 模块支持从逻辑上组织 python 代码
  • 当代码量变得非常大的时候,最好把代码分成一些有组织的代码段
  • 代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数
  • 这些代码段是共享的,所有 python 允许 “调入” 一个模块,允许使用其他模块的属性来利用之前的工作成果,实现代码重用

作用

  • 模块可以实现代码的重用,导入模块,就可以使用模块中已经定义好的类,函数和变量,减少代码的冗余性

模块文件

  • 模块是从逻辑来组织 python 代码的方法,文件是物理层上组织模块的方法
  • 一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件
  • 模块的文件名就是模块的名字加上扩展名 .py

搜索路径

  • 模块的导入需要一个叫做 “路径搜索” 的过程
  • python 在文件系统 “预定义区域” 中查找要调用的模块
  • 搜索路径在 sys.path 中定义
  • 也可以通过 PYTHONPATH 环境变量引入自定义目录

导入模块

查看模块的默认搜索路径

1
2
>>> import sys		#导入模块sys
>>> sys.path #path, 查看python搜索模块时的默认查找路径

模块导入方法

  • 使用import导入模块
  • 可以在一行导入多个模块,但是可读性会下降
  • 可以只导入模块的某些属性
  • 导入模块时,可以为模块取别名
1
2
3
>>> import time, os, sys
>>> from random import choice
>>> import pickle as p
  • 当导入模块时,模块的顶层代码会被执行
  • 一个模块不管被导入(import)多少次,只会被加载(load)一次
1
2
3
4
5
6
7
[root@localhost day07]# mkdir /tmp/mylibs  #自定义目录,用于存放自己的模块文件【目录可以随意指定】
[root@localhost day07]# cp 自定义模块.py /tmp/mylibs/ #拷贝自定义模块.py 文件到/tmp/mylibs中,作为一个模块文件

[root@localhost day07]# export PYTHONPATH=/tmp/mylibs #声明全局变量
[root@localhost day07]# cd /home/ #切换到任意目录下
[root@localhost day07]# python3 #进入到python的交互式解释器下
>>> import 自定义模块 #导入模块自定义模块成功

内置模块

hashlib 模块

  • hashlib 用来替换 MD5 和 sha 模块,并使他们的API一致,专门提供hash算法
  • 包括md5、sha1、sha224、sha256、sha384、sha512,使用非常简单、方便
1
2
3
4
5
6
7
8
9
10
11
12
# 使用hashlib模块,计算bytes类型数据的md5值
>>> import hashlib
# 一次读取所有数据,计算出文件的md5值,适合于小文件数据
>>> m = hashlib.md5(b'123456') #计算b'123456' 的md5值,返回一个对象
>>> m.hexdigest() #以16进制的方式,显示m的md5值

# 每次读取少量数据,最后计算出文件的md5值,适合于大文件数据
>>> m1 = hashlib.md5() #返回一个空数据的md5值
>>> m1.update(b'12') #更新b'12'的md5值
>>> m1.update(b'34')
>>> m1.update(b'56')
>>> m1.hexdigest() #以16进制的方式,显示m1的md5值,结果相同

练习 5:计算文件 md5 值

需求

  • 编写用于计算文件 md5 值的脚本
  • 文件名通过位置参数获得
  • 打印出文件 md5 值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建新的python文件check_md5.py,计算文件的md5值
import hashlib #计算文件的md5值

def check_md5(fname): #使用def 定义函数check_md5(),计算文件的md5值
m = hashlib.md5() #返回一个空数据的md5值
with open(fname, mode='rb') as fobj:
while 1:
data = fobj.read(4096) #每次读取4096个字节【4k】
if len(data) == 0: #data为空时,退出while循环
break
m.update(data) #更新data的md5值
return m.hexdigest() #返回给函数16进制的mdf值

if __name__ == '__main__':
print(check_md5("/etc/hosts"))

[root@localhost xxx]# python3 check_md5.py
54fb6627dbaa37721048e4549db3224d

tarfile 模块

  • tarfile模块允许创建、访问 tar 文件
  • 同时支持 gzip、bzip2 格式
1
2
3
4
5
6
7
8
9
10
11
>>> import tarfile		#在local下, 导入模块tarfile【实现文件的打包和解压】
>>> tar = tarfile.open('/tmp/demo.tar.gz', 'w:gz') #以'w:gz'的格式,打开包文件,文件不存在则会自动创建
>>> tar.add('/etc/hosts') #向包文件中压缩文件/etc/hosts
>>> tar.add('/etc/security') #向包文件中压缩目录/etc/security
>>> tar.close() #关闭文件
[root@localhost day02]# ls -l /tmp/demo.tar.gz

>>> tar = tarfile.open('/tmp/demo.tar.gz') #打开文件,文件已经存在,则不需要指定类型,python会自动选择
>>> tar.extractall(path='/var/tmp') #解压到 /var/tmp目录下,不指定解压到当前目录
>>> tar.close() #关闭文件
[root@localhost day07]# ls /var/tmp/etc/ #查看软件包demo.tar.gz是否解压成功
更新于

请我喝[茶]~( ̄▽ ̄)~*

Chen 微信支付

微信支付

Chen 支付宝

支付宝

Chen 贝宝

贝宝