python基础总结

目录
  • 第一章 python基础
    • 初识
      • *的魔术用法:
      • 格式化输出(3.6版本之后)
      • 编码
      • 数据类型
      • 序列化
      • 软件开发规范
    • str
    • list
    • tuple
    • 字典
    • set
    • 代码块、缓存机制
    • 深浅copy
    • 文件的操作
    • 函数
    • 命称空间
    • 迭代器
    • 生成器
    • 内置函数
    • 闭包、装饰器
    • 自定义模块
    • 常用模块
      • random模块
      • time模块
      • datetime模块
      • os模块
      • sys模块
      • json模块
      • pickle模块
      • hashlib模块
      • collections模块

第一章 python基础

初识

  1. cpu 内存 硬盘 操作系统

    cpu:计算机的运算和计算中心,相当于人类大脑。
    ​ 内存:暂时存储数据,临时加载数据应用程序,运行速度快,高铁,断电即消失,造价很高。
    ​ 硬盘:磁盘,长期存储数据。
    ​ 操作系统:一个软件,连接计算机的硬件与所有软件之间的一个软件。

  2. python的编程语言分类

    • 编译型:将代码一次性全部编译成二进制,然后再执行。优点:执行效率高。缺点:开发效率低,不能跨平台。代表语言:C

    • 解释型:逐行解释成二进制,逐行运行。优点:开发效率高,可以跨平台。缺点:执行效率低。代表语言:python。

  3. python的种类

    • Cpython:官方推荐解释器。可以转化成C语言能识别的字节码。
    • Jpython: 可以转化成Java语言能识别的字节码。
    • Ironpython:可以转化成.net语言能识别的字节码
    • pypy: 动态编译。
      ......
  4. 变量:只是指向某种数据

  5. 常量:python中没有真正的常量,为了应和其他语言的口味,全部大写的变量称之为常量。

  6. while/for 的else:如果循环没有被break终止则else语句会被执行,如果while被break终止,则不执行else语句。

  7. 运算符:算数运算符+、-等;比较运算符>、==等;赋值运算符=、+=等;逻辑运算符not、and、or;成员运算符in、not in。
    and or not

    • 在没有()的情况下,优先级:not > and > or,同一优先级从左至右依次计算
  8. 三元运算符:简单的if-else语句可以简化为三元运算符。如判断a,b的大小c = a if a>b else b

  9. 编写代码原则:开放封闭原则,代码对于未来的一些拓展一定是要开放的(接口)可以添加一些功能。
    开放封闭原则:
    开放:对源码的拓展是开放的。
    封闭:对源码的修改是封闭的。

*的魔术用法:

  • *的聚合:用于万能形参,在函数的定义时, *将所有的位置实参聚合成一个元祖,将这个元祖赋值给args。**将所有的关键字参数聚合成一个一个字典,将这个字典赋值给kargs。

  • *的打散,在函数的调用时*打散的是迭代对象,必须用在可迭代对象上(str,list等)**打散的是字典。

    def func(*args):
        print(args)
    funs([1,2,3],[4,5,6])   #([1, 2, 3], [4, 5, 6])
    funs(*[1,2,3],*[4,5,6])     #*的打散,等同于funs(1, 2, 3, 4, 5, 6)
    #(1, 2, 3, 4, 5, 6)
    
    def func(*args,**kargs):
        print(args)
        print(kargs)
    funs({'百度网盘群': '980173694'})
    #({'百度网盘群': '980173694'})  {}
    
    funs(**{'百度网盘群': '980173694'})#**的打散,等同于funs(百度网盘群='980173694')
    #()  {'number': '980173694', 'massage': '欢迎加入百度网盘群'}
    

格式化输出(3.6版本之后)

基础表达:

name = 'python'
age = '18'
msg = f'我叫{name},今年{age}'    #在引号前添加一个字符f

可以加表达式:

count = 2
print(f'最终结果:{count**2}')
name = 'python'
print(f'我的名字是{name.upper()}')
dic = {'name':'python','age':'18'}
msg = f'我叫{dic["name"]},今年{dic["age"]}'   #注意双引号与单引号的使
list = ['python','18']
msg = f'我叫{list[0]},今年{list[1]}'

可以结合函数:

def sum1(a,b):
    return a+b
print(f'最终结果是{sum1(1,2)}')

优点:1.结构更加优化。2.可以结合表达式和函数使用。3.效率更高

编码

计算机存储文件,存储数据,以及通过网络发送出去,储存发送数据底层都是0与1的二进制。
密码本:0101011001 二进制与文字之间的关系

ASCII码:只包含英文字母,数字,特殊字符。共8位,但只用了7位,可以表示128(2**7)个不同的字符。ASCII码预留了一位,第8位为0。

字节:8bit(位) = 1byte(字节)

中国:gbk(最多能表示2**16个中文),只包含英文字母,数字,特殊字符(ASCII)和中文,也叫国标(国家标准)。一个英文字母用一个字节表示,一个中文用两个字节。

Unicode:万国码:把世界上的所有的文字都记录到这个密码本。用4个字节表示,可表示2**32=4294967296个文字。

utf-8:(Unicodes升级版本)最少用1个字节表示字符(英语),欧洲用2个字节,中文用3个字节
'我们12ax' :GBK :8个字节
'我们12ax' :UTF-8:10个字节

单位的转换:
8bit = 1byte
1024byte = 1kb
1024kb = 1MB
......GB、TB、PB、EB、 ZB 、YB、 NB

不同的编码方式之间不能相互识别:

  • 数据在内存中全部是以Unicode编码的,但是当数据用于网络传输或者存储到硬盘中,必须是以非Unicode编码(utf-8、gbk等)

  • python中的数据从内存(Unicode编码)存储到硬盘或进行网络传输时要经历一个特殊的转化过程,要转化为一个非Unicode编码类型的特殊数据才能进行传输或储存至硬盘,即bytes类型(内存中的编码方式:非Unicode)

  • bytes与str的操作方式大部分都是一样,bytes can only contain ASCII literal characters,bytes用于文件的储存、网络的传输等。直接将非ASCII码字符串转化为bytes类型会报错,要使用encode()

    #ASCII码中的字符转bytes:
    a = b'iloveyou'
    print(a,type(a))  #b'iloveyou' 
    
    #将中文转化为bytes类型:
    b = b'山就在那儿'
    print(b)
    #SyntaxError: bytes can only contain ASCII literal characters
    
    #正确方法为:
    c = '山就在那儿'
    b = c.encode('utf-8')
    print(c.encode('utf-8'))    #一般指定utf-8的编码形式,   (encode:编码)
    >>>b'\xe5\xb1\xb1\xe5\xb0\xb1\xe5\x9c\xa8\xe9\x82\xa3\xe5\x84\xbf'
    
  • bytes可转化为字符串类型(Unicode)(decode,解码)。用什么编码类型转换为bytes数据类型的就用什么解码。

    b = b'\xe5\xb1\xb1\xe5\xb0\xb1\xe5\x9c\xa8\xe9\x82\xa3\xe5\x84\xbf'
    print(b.decode('utf-8'))   #山就在那儿
    
    #用什么编码类型转换为bytes数据类型的就用什么解码。
    b = b'\xe5\xb1\xb1\xe5\xb0\xb1\xe5\x9c\xa8\xe9\x82\xa3\xe5\x84\xbf'
    c = b.decode('gbk')
    print(c)
    >>>UnicodeDecodeError: 'gbk' codec can't decode byte 0xbf in position 14: incomplete multibyte sequence
    
  • 小题试做:gbk转换为utf-8

    #分析,所有的编码都与Unicode有关(计算机内存中以Unicode编码),因此可先将gbk转换为Unicode编码,再转换为utf-8编码。
    gbk = b'\xc9\xbd\xbe\xcd\xd4\xda\xc4\xc7\xb6\xf9'
    decode1 = gbk.decode('gbk')    #解码为Unicode编码的字符串,可print(decode1)查看。
    print(decode1.encode('utf-8'))  #以utf-8编码
    >>>b'\xe5\xb1\xb1\xe5\xb0\xb1\xe5\x9c\xa8\xe9\x82\xa3\xe5\x84\xbf'
    

数据类型

  1. 数据类型的分类(可变与不可变):

    • 可变(不可哈希)的数据类型:list dict set(集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键))
    • 不可变的数据类型(可哈希): str bool int tuple(该对象本身不可变),(做不可变类型做任何操作,方法都不会改变原对象)
  2. 数据之间类型的转换

    • int bool str 三者转换

    • str list 两者转换

    • list set 两者转换

    • str bytes 两者转换,只有字符串能和bytes互换。

    • 所有数据都可以转换成bool值,转换成bool值为False的数据类型有:

      '',0,(),{},[],set(),None
      
  3. 按照储存空间的占用分(从低到高)

    • int
    • str
    • set : 无序
    • tuple: 有序,不可变
    • list: 有序,可变
    • dict: 有序(3.6版本之后),可变

序列化

结构化数据:内存中的数据是结构化数据,存在许多指针(有指向关系的数据)。将数据组合为有指向关系的数据是结构化的过程。

线性数据:磁盘中的文件是一个典型的线性数据,数据间没有引用关系。

序列化(serialization):将内存中的数据结构(list、str、tuple、dict、set等)转换成字节或字符串,用以保存在文件或通过网络传输,称为序列化过程。

反序列化(deserilzation):从磁盘文件中,网络中获取的数据类型(字节),转换成内存中原来的数据数据结构,称为反序列化过程。

序列化模块:json、pickle、shelve等

软件开发规范

配置文件(conf-setting.py):静态(变量不能改变只能引用)的路径,数据库连接的设置

主逻辑函数(core-src.py):

公共组件(lib-common):装饰器、日志函数、

启动函数(bin-starts.py):只有一个文件。

数据库(db-register):文本数据

日志(log-access.log):日志的数据

READEME

str

  • 存储少量的数据。切片还是对其进行任何操作,获取的内容全都是str类型,存储的数据单一。
  • capitalize() 首字母(第一个单词)大写,其余变小写
  • title() 每个单词的首字母大写。(以特殊字符(非字母)隔开的即为一个单词)
  • swapcase() 大小写翻转
  • center() 居中,有1个必选参数:宽度,一个非必选参数:填充)
  • find() 通过元素找索引,找到第一个就返回索引,找不到返回-1。
  • index() 通过元素找索引,找到第一个就返回索引,找不到就报错。

list

列表可以储存大量的数据,但数据间的关联性不强且列表的查询速度比字典慢。

  • 在列表末尾增加一个数据项(list.append()方法),直接修改列表没有返回值。
  • 在列表末尾增加一个数据项集合( 添加多个元素的方法,迭代追加)(list.extend()方法);
l1 = [1,2]
l1.extend('abcd')
print(l1)
>>>[1, 2, 'a', 'b', 'c', 'd']
l2 = [1,2]
l2.extend(['asd',1,2])
print(l2)
>>>[1, 2, 'asd', 1, 2]
  • 列表的特殊插入法,在特定位置增加一个数据项(list.insert()方法):
a=['b','c','d']
a.insert(0,'a')
a[0:1]      #['a']
  • 从列表末尾删除数据,按照索引删除(list.pop()方法),若果没有给出索引值则默认删除最后列表的一个元素,有返回值,返回删除的元素。
l1 = [1,2,3,4]
print(l1.pop(0))    #1
  • 在列表中删除一个特定项(list.remove()方法);如果有重复的则默认删除第一个元素(从左开始)
  • list.clear()清空列表的方法,del 按照索引删除,也可以按照切片删除(可加步长),无返回值
l1 = [1,2,3,4,5,6]
del l1[0]
print(l1)       #[2.3.4,5,6]

del l1[0:4:2]
print(l1)       #[3,5,6]
  • 按照索引改元素;按照切片更改元素(迭代增加),也可按照切片加步长更改,但必须一 一 对应。
l1 = [1,2,3,4]
l1[0] = 0

l1[1:] = 'abcd'
print(l1)
>>>[0, 'a', 'b', 'c', 'd']

l1[::2]='123'
print(l1)
>>>['1', 'a', '2', 'c', '3']
  • index() 通过元素找索引

  • sort() 默认从小到大排序,设置reverse参数则可从小到大。

  • reverse() 反转

  • 列表相加 (3.4以上版本)

  • 列表与数字相乘 (3.4以上版本)

    l1 = [2,'a',[1,'b']]
    l2 = l1*3
    print(l2)
    >>>[2, 'a', [1, 'b'], 2, 'a', [1, 'b'], 2, 'a', [1, 'b']]
    
  • 列表的特殊性:正向循环一个列表时如果删除某个元素,那么这个元素后面的所有元素都会向前进一位,它们的索引相比之前也会前进一位,因此,在循环一个列表时的过程中,如果要改变列表的大小(增加值或者删除值),那么结果很可能会出错或者报错。

    l1 = [1,2,3,4,5,6]  #删除列表中索引位为偶数的元素。
    for i in range(0,len(l1),2):
        l1.pop(i)
    print(l1)
    >>>IndexError: pop index out of range
    

    解决此问题有三种方式:

1.直接删除 (按照元素删除,按照索引删除,切片加步长

#切片加步长
l1 = [1,2,3,4,5,6]
del l1[1::2]
print(l1)

2.倒叙删除

l1 = [1,2,3,4,5,6]
for i in range(len(l1)-1,-1,-2):
    l1.pop(i)
print(l1)
>>>[1,3,5]

#不能用以下代码;
l1 = [1,2,3,4,5,6]
for i in range(1,len(l1),2):
    l1.pop(-i)

3.思维转换

l1 = [1,2,3,4,5,6]
l2 = []
for i in range(0,len(l1),2):
    l2.append(l1[i])
l1 = l2
print(l1)
  • b=sorted(a,reverse=True) 函数按照长短、大小、英文字母的顺序给列表中的元素进行排序,但不会改变列表本身

  • 列表是有序的,可以用enumerate()函数在索引的时候得到每个元素的具体位置:

l1 = ['a','b','c']
for num,int in enumerate(l1):
print(num,int)
>>>0 a
1 b
2 c
  • 列表的嵌套

  • 列表推导式:用一行代码构建一个比较复杂有规律的列表。

    l1 = [i for i in range(10)]  #可将range换为可迭代对象。
    
    • 列表推导式可分为两类。

      • 循环模式:需遍历每一个元素。[变量(加工后的变量) for 变量 in iterable]
      #例1:将10以内所有整数的平方写入列表:
      l1 = [i*i for i in range(11)]
      print(l1)
      #例2:100以内的所有奇数写入列表:
      l1 = [i for i in range(1,100,2)]
      print(l1)
      #例3:从python1到python10写入列表:
      l1 = [f'python{i}' for i in range(1,11)]
      print(l1)
      
      • 筛选模式:[变量(加工后的变量) for 变量 in iterable if 条件]
      #例1:将10以内能够被2整除的数写入列表
      l1 = [i for i in range(11) if i%2==0]
      print(l1)
      #例2:过滤列表l1中小于3的字符串,将剩下的转换成大写。l1 = ['nonl','globals','as','in']
      l1 = ['nonl','globals','as','in']
      l2 = [i.upper() for i in l1 if len(i)>=3]
      print(l2)
      #例3:将num列表中含有两个0的字符串生成一个新列表。num = [['021','001','201'],['100','012','010']]
      l1 = [j for i in num for j in i if j.count('0') == 2]
      print(l1)
      
    • 缺点:

      1. 只能构建计较复杂并且有规律的列表;
      2. 超过三层循环才能构建成功的,不建议使用列表推导式;
      3. 无法找查错误(debug模式)

tuple

为只读列表。可存大量的数据,可以索引,切片(按步长),不可更改,但元祖中列表里的元素可以按照列表更改,示例: (1, True, [1, 2, 3])。

  • 特殊性:元祖中只有一个元素,并且没有’,‘,则它不是元祖,它与括号中的数据类型一致

    print(type((1,2))) #
    print(type((1)))   #
    print(type((1,)))   # 
    
  • count() 计数

  • index() 找索引

  • 元祖的拆包:分别赋值,必须一 一对应。(列表也可以拆包,但一般不用)

a,b=(1,2)
print(a,b)    #1 2

#与*(聚合)使用:
a,b,*c = (1,2,3,4,5,6,7,)
print(a,b,c)    #1 2 [3, 4, 5, 6, 7]

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

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

a,*b,c=range(10)
print(a,b,c)       #0 [1, 2, 3, 4, 5, 6, 7, 8] 9

字典

键必须是不可变的数据类型,最常用的是str int,键是不能改变且无法修改,而值可以改变,可修改,可以是任何对象。键是不能重复的,而值可以重复。字典在3.5x版本之前(包括3.5)是无序的;字典在3.6x会按照初次建立字典的顺序排序的,学术上不认为是有序的。字典3.7x以后都是有序的。字典以空间换时间,查询速度非常快,内存消耗巨大。

字典的创建方式:

dic = dict((('i',1),('love',2),('you',3)))  #用列表也可以dic = dict([('i',1),('love',2),('you',3)]),列表或元祖中的每个元素是一个二元组就可以用dict()转换为字典。
print(dic)
>>>{'i': 1, 'love': 2, 'you': 3}

dic = dict(i=1,love=2,you=3)
print(dic)
>>>{'i': 1, 'love': 2, 'you': 3}

dic = dict({'i': 1, 'love': 2, 'you': 3})
print(dic)
>>>{'i': 1, 'love': 2, 'you': 3}

#字典推导式
dic = {i:i+1 for i in range(3)}

增,添加多个元素的方法:

  • update():有键则改,无键则增加。

  • setdefault():有则不变,无则增加

  • fromkeys():第一个参数必须为可迭代对象,可迭代的对象共用第二个参数(id相同)。

    dic = {}
    dic['age'] = '18'
    
    dic.setdefault('able')
    print(dic)      #{'able':None}
    dic.setdefault('hobby':python)
    
    dic = dict.fromkeys('abc',1)
    print(dic)      #{'a': 1, 'b': 1, 'c': 1}
    
    dic = dict.fromkeys([1,2,3],[])
    print(dic)    #{1: [], 2: [], 3: []}
    
    dic[1].append('a')
    print(dic)      #{1: ['a'], 2: ['a'], 3: ['a']}
    

删:

  • popitem() 3.5版本之前,随机删除,3.6版本之后,删除最后一个,有返回值。

  • pop(),按照键删除,有返回值,返回的为字典的值,如果没有要删除的键,则会报错,但可以设置第二个两个参数,无论字典中是否有此键,都不会报错,若有此键则返回值为此键的值,若无此键则,返回设置的参数。

  • del,若无键会报错,不推荐使用

  • clear 清空

    dic = {}
    print(dic.pop('hobby','没有此键值对'))    #没有此键值对。
    del dic['age']   #报错
    dic.claer()
    

  • dic['name'] = 18

  • update() 有则覆盖,无则增加

    #1.增加/改键值对
    dic0 = {1:'i'}
    dic1 = {3: 'c'}
    dic0.update(a='love')   #a位置不能是int类型
    dic0.update(dic)
    

dic = {'name':'山就在那儿','hobby_list':['book','python']}
print(dic['hobby_list'])  #若没有此键则会报错,不建议用

l1 = dic.get('hobby_list')   #若没有键则会返回None,可以定义第二个参数,第二个参数即为返回值

#keys()
print(dic.keys())   #会返回一个dict_keys类型,包含字典中所有的键,和列表相似,但不能索引,转化成列表后可遍历
>>>dict_keys(['name', 'hobby_list'])

#values()
print(dic.values())   #会返回一个dict_values类型,包含字典中所有的值,和列表相似,但不能索引,可转化成列表后可遍历
>>>dict_values(['山就在那儿', ['book', 'python']])

#items()
for k,v in dic.items():     #元祖的拆包  for i in dic.items()  print(i)   打印的结果数据为元祖

#小题试做:将字典dic中的以‘k’开头的键值对删除,(循环一个字典时,若果改变字典的大小则或报错。)
dic = {'k1':'a','k2':'b','k3':'c','a':'d'}
l1 = []
for key in dic:
    if key.startswith('k'):
        l1.append(key)
for i in l1:
    dic.pop(i)
#改进
for key in list(dic.keys()):   #将其转换为一个列表,若不加list则会报错。
    if 'k' in key:
        dic.pop(key)

字典的嵌套

字典推导式

l1 = ['1','2','3']
l2 = ['一','二','三']
dic = {l1[i]:l2[i] for i in range(len(l1))}

set

集合:容器型数据类型,要求它里面的元素是不可变的数据(可哈希),但它本身是可变的数据类型(不可哈希)。集合是无序的。以{}存放数据。

作用:列表的去重;关系的测试(交集,并集…)

集合的创建:

set = {1,2,'a'}
#空集合的表示:
set1 = set()   #set1 = {}表示空字典

增:add()、update():迭代增加,有重复的则去重

set1 = {1,2}
set1.add('a')

set1.update('asdfdsa')
print(set1)
>>>{'a', 1, 2, 'f', 's', 'd'}

删:remove()(按照元素删除,pop()随机删除,clear()清空集合,del 删除集合,discard()有则删除,无则pass,不报错。

改:先删除再增加

交集。(&或者intersection) 集合共同有的元素

set1 = {1,2,3}
set2 = {2,3,4}
print(set1 & set2) #or print(set1.intersection)

并集。(|或者union)集合所有的元素

set1 = {1,2}
set2 = {2,3}
print(set1 | set2) #or print(set1.union(set2))

差集 ( - 或者difference) ,前一集合独有的元素

set1 = {1,2,3,4,5}
set2 = {2,4,6}
print(set1 - set2) #or print(set1.difference(set2))

反交集。(^ 或者symmetric_difference)每个集合独有的元素

set1 = {1,2,3,4,5}
set2 = {3,4,5,6,7}
print(set1 ^ set2) #or print(set1.symmetric_difference(set2))
>>>{1,2,6,7}

子集与超集

set1 = {1,2}
set2 = {1,2,3}
print(set1 < set2)  #or  print(set1.issubset(set2))     #or  print(set2.issuperset(set1))
>>>True   #set1是set2的子集
print(set2 > set1)
>>>True   #set2是set1的超集

frozenset()让集合变为不可变类型

s = frozenset('qweasd')
print(s,type(s))
>>>frozenset({'q', 'e', 'w', 's', 'a', 'd'}) 

S集合推导式

set1 = {i for i in range(10)}

代码块、缓存机制

id、is、==

  • id: 数据的内存地址具有唯一性,若id 相同则为同一个数据。id():获取数据的内存地址(随机的地址:内存临时加载,存储数据,当程序运行结束后,内存地址即被丢弃)

  • is 判断id是否相同,==:判断是否相同。id相同,值一定相同,值相同,id不一定相同。

    l1 = [1,2,3]
    l2 = [1,2,3]
    print(l1 == l2)  #True    #比较的是两边的值是否相等。
    
    l1 = [1,2,3]
    l2 = [1,2,3]
    print(l1 is l2)   #False   判断的是(id)是否相同。
    s1 = 'iam'
    s2 = 'iam'
    print(s1 is s2)   #True     与同一代码块下的缓存机制有关
    
    l1 = [1,2,3]
    l2 = l1
    print(l1 is l2)   #True
    

代码:python的程序是由代码块构造的。块是一个python程序的脚本,它是作为一个单元执行的。一个模块,一个函数,一个类,一个文件等都是代码块。而作为互交命令方式输入的每个命令都是一个代码块。

代码块的两个缓存机制:如果在同一代码块下,则采用同一代码块下的换缓存机制。如果是不同代码块,则采用小数据池的驻留机制。优点:需要值相同的字符串,整数的时候,直接拿来用,避免频繁的创建和销毁,提升效率,节约内存

同一个代码块的缓存机制:Python在执行同一个代码块的初始化对象的命令时,会检查是否其值是否已经存在,如果存在,会将其重用。换句话说:执行同一个代码块时,遇到初始化对象的命令时,它会将初始化的这个变量与值存储在一个字典中,在遇到新的变量时,会先在字典中查询记录,如果有同样的记录那么它会重复使用这个字典中的之前的这个值,即:id相同。

  • 适用对象
    int:任何数字在同一代码块下都会复用。
    str:几乎所有的字符串都会符合缓存机制
    bool:True和False在字典中会以1,0方式存在,并且复用。

    s1 = 'iam'
    s2 = 'iam'
    print(s1 is s2)
    >>>True
    
  • 在不同一个代码块内的缓存机制:小数据池,也称为小整数缓存机制,或者称为驻留机制等等。Python自动将-5~256的整数进行了缓存,将一定规则的字符串在字符串驻留池中创建一份。无论是缓存还是字符串驻留池,都是python做的一个优化,就是将~5-256的整数,和一定规则的字符串,放在一个‘池’(容器,或者字典)中,无论程序中哪些变量指向这些范围内的整数或者字符串,那么它直接在这个‘池’中引用,并不会重新创建对象,而是使用已经创建好的缓存对象。言外之意,就是内存中之创建一个。

    • 适用对象:
      int(float):对于整数,小数据池的范围是-5~256 ,如果多个变量都是指向同一个(在这个范围内的)数字,他们在内存中指向的都是一个内存地址。
      str:字符串的长度只含有大小写字母,数字,下划线时,才会默认驻留。
      bool值:True、False,无论创建多少个变量指向True,False,那么它在内存中只存在一个。

      #通过交互方式中执行下面代码,这是不同代码块下,则采用小数据池的驻留机制。
      >>> i1 = 1000
      >>> i2 = 1000
      >>> print(i1 is i2)
      False  # 不同代码块下的小数据池驻留机制 数字的范围只是-5~256.
      
      #4.指定驻留
      >>>from sys import intern
      >>>a = intern('hello!@'*20)
      >>>b = intern('hello!@'*20)
      >>>print(a is b)
      True
      #指定驻留是你可以指定任意的字符串加入到小数据池中,让其只在内存中创建一个对象,多个变量都是指向这一个字
      # 虽然在同一个文件中,但是函数本身就是代码块,所以这是在两个不同的代码块下,满足小数据池(驻存机制),则指向两个不同的地址。
      

def func():
i1 = 1000
print(id(i1)) # 2288555806672
def func2():
i1 = 1000
print(id(i1)) # 2288557317392
func()
func2()
```

深浅copy

浅copy:嵌套的可变的数据类型是同一个。深copy:嵌套的可变的数据类型不是同一个。

浅copy,列表是一个一个的槽位,它储存的是对象的内存地址。浅拷贝仍然使用原来的对象的内存地址。对储存的可变对象可以进行更改;若改变不可变类型,则改变的不是不可变类型本身,而是变量的指向关系,切片是浅copy。

l1 = [1,2]
l2 = l1
l1.append(3)
print(l2)
>>>[1,2,3] #l1,l2两变量指向同一个id(数据)

#浅copy
l3 = [1,2,['a']]
l4 = l3.copy()
l3.append(3)
print(l3)
>>>[1,2,['a'],3]
print(l4)
>>>[1,2,['a']]

l3[-2].append(4)    #or l4[-1].append(4)
print(l3)
>>>[1, 2, ['a', 4], 3]
print(l4)  
>>>[1,2,['a',4]] #l4与l3列表中的数据id是相同的,但l4与l3列表id不相同,即l3中的每个元素与l4中的每个元素使用的是相同的一个id,但l4与l3用的不是同一个id。

深copy,需导入模块copy。深拷贝会给对象创建一个新的内存地址,python对深copy做了一个优化,不可变数据类型对象的内存地址沿用同一个,只为可变数据类型对象再重新创建内存地址。

import copy
l3 = [1,2,['a']]
l4 = copy.deepcopy(l3)
l3[-1].append(3)
print(l3)
>>>[1,2,['a',3]]
print(l4)
>>>[1,2,['a']]

文件的操作

open() 内置函数,open底层调用的是操作系统的接口。有三个参数:1. 文件路径 (文件夹路径+文件名+文件类型) 2. 编码方式:encoding,参数不写会以操作系统默认的编码本打开,windows默认编码:gbk(windows10是utf-8,Linux:utf-8,mac:utf-8)。3.模式:mode,需要有两个方向给予指定:打开文本的模式、打开文件的方向,默认不写第一个方向则以读(r)的方式打开,默认不写第二个则以文本模式打开。

文件的方向:t(text,文本模式)、b(binary,字节模式)。

文件的模式:a(append,追加)、w(write,写)、r(read,读)

文件句柄:变量,一般在文件操作是设置的约定俗成的变量,也有写作为f1,fh,file_handler,f_h等,也被称为文件句柄。通过对文件进行的任何操作都要作用于文件句柄(如fl.raed())。
常见报错原因:

  1. UnicodeDecodeError:文件储存时与文件打开时编码本运用不一致。

  2. 路径分隔符产生问题:\ (反斜杠) 有转义符的意思。解决方法:在文件路径前加r,让转义符失效,也可使用两次转义:\\

文件的读:

  • rt、rb、r+、r+b等。

    • read() 若括号中无参数则一次全部读出,若写参数(数字)则可以按照字符(从1开始)读取,文件中的换行符算作一个字符。

    • readline() 若括号中无参数则读一行,若写参数(数字)则可以按照字符(从1开始)读取字符(同read),文件中的换行符算作一个字符。注意,文本中有换行符,而print()函数也默认换行。

  • readlines() 若括号中无参数读取所有行,返回列表,列表的每个元素为源文件的每一行,若写参数(数字)则可以按照每一行读取。

    • 循环读取,文件句柄可遍历(文件句柄是迭代器,每次for循环时都只读取文件一行,节省内存,而read,readlines等是一次读取至内存中,若果文件过大,则会出现问题)。

      f = open(r'd:\python.txt',encoding='utf-8')
      for line in f:
        print(line)
      f.close()    #每次操作文件后一定要关闭。
      
    • rb:操作的是非文本的文件,比如:图片,视频,音频等。rb模式不用encoding参数。

        fl = open(r'd:\雪景.jpg',mode='rb')  #雪景.jpg是张照片
      

      以b方式打开,字节没有行的概念。若文件过大最好设置每次读取多少字节

    • r+ 读写功能(读并追加),必须先读或将指针调制文档末尾才能追加否则按照字节覆盖,极易出现乱码问题或报错。

文件的写:

  • wt、wb、w+、w+b等。

  • w,wb: 若已有相同的文件则会先清空原有文件的内容再写入 ,若无则创建。清空:打开文件后会先清空原文件再写入。

  • 文件的追加:

    • at、ab、a+、a+b等。
    • a:若无文件则会创建文件。若有则直接在原文件追加
  • 指针:

    • 文件的读取都有指针定位,文件中的指针起始位置在文件最前面。

    • tell():读取指针的位子,以****字节**为单位(utf-8编码:一个中文三个字节,一个字母1个字节)

    • seek():调整光标的位置,以字节为单位

  • flush():强制刷新(保存),一般在写文件时使用,在写后一般要对文件句柄使用flush方法,以免保存失败。

  • 打开文件的另一种方式

    • with open() as f1:
      优点:不用手动关闭文件句柄,会在一定时间内关闭;一个with可以操作多个文件。

      #打开多个文件:
      with open(r'd:\text.txt',encoding='utf-8',mode='a') as f1,open(r'd:\python.txt',encoding='utf-8',mode='a') as f2:
      
  • 各大操作文件的软件(word、笔记本等等)底层都以以下基本方式操作文件:

    1. 以读的模式打开原文件
    2. 以写的模式创建一个新文件
    3. 将原文件的内容读出来修改成新的内容,写入新文件
    4. 将原文件删除
    5. 将新文件重命名
    • 小题试练:将d盘下的python.txt文件中的小写o全变为大写。

      import os   #引入os模块
      #1.以读的模式打开原文件
      #2.以写的模式创建一个新文件
      with open(r'd:\python.txt',encoding='utf-8') as f1,\
      open(r'd:\python.bak',encoding='utf-8',mode='w') as f2:   #.bak是一种备份文件类型
      #3.将原文件的内容读出来修改成新的内容,写入新文件
          for old_line in f1:
          new_line = old_line.replace('o','O')
          f2.write(new_line)  
      #4.将原文件删除
      os.remove('d:\python.txt')
      #5.将新文件重命名
      os.rename('d:\python.bak','d:\python.txt')
      

函数

return: 在函数中遇到return直接结束函数;可以给函数外部返回一个返回值,将数据返回给函数的执行者,调用者。返回值可被print打印出,若无返回值,print会打印出None。return可返回多个值,会以元祖的形式将多个元素返回给函数的执行者。(元祖可以进行拆包)

参数:实参,函数的执行时传入的参数。形参,函数的定义时接受的参数

实参角度:

  • 位置参数:按照实参位置参数与形参的对应顺序(从左到右)依次传入。

  • 关键字参数

  • 混合传参

形参角度:

  • 位置参数
  • 默认参数(默认值参数)
  • 万能参数:*args 接受所有的位置参数 ; **kargs 接受所有的关键字参数。
  • 仅限关键字参数(3.4版本以后):只能接受关键字参数。若设置此参数必须给此参数传入值,否则会报错。此参数只能在*args与**kargs之间。

形参的顺序:调用函数时,会自动从左至右传入参数。形参的顺序如下:1.位置参数 2.*args 3.默认参数 3. 仅限关键字参数 4.**kargs。仅限关键字参数与默认参数可互换。

def func(a,b,*args,c='',d,**kargs):
    pass       #a,b:位置参数     d:仅限关键字参数,只能有关键字参数为其传参。

函数不能改变全局不可变的变量,可变数据仍然可改变。

l1 = [1,2]
def a(l1):
    l1.pop(1)
a(l1)
print(l1)

匿名函数(用lambda构建):结构比较简单的函数。形式:lambda 参数 : 返回值

def func(a,b):
    return a+b
#构建匿名函数:
func1 = lambda a,b:a+b
print(func1(1,2))

lambda 参数 : 返回值:lambda后直接加形参,形参加多少都可以,但一般只用位置参数,参数之间需要用”,“隔开。

#例1:接受一个可切片的数据,以元祖形式返回索引位0与2的对应元素
func = lambda a:(a[0],a[2])
#例2:接收两个int参数,将较大的数据返回。
func = lambda a,b:a if a>b else b

命称空间

在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间,函数中的变量只能在函数内部使用。随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空。我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。

全局名称空间:代码在运行伊始,创建的存储'变量名与值的关系'的空间叫做全局命名空间。即 在py文件中, 除函数外声明的变量都属于全局命名空间。程序不结束,全局名称空间不会消失。

局部名称空间:在函数的运行中开辟的临时的空间叫做局部命名空间也叫做临时名称空间,临时存放函数中的变量与值的关系。即在函数中声明的变量会放在局部命名空间。函数结束时局部命名空间就会消失

内置名称空间:内置名称空间存放的就是一些内置函数等拿来即用的特殊的变量:input,print,list等。

加载顺序:这个三个空间在内存中创建的先后顺序,他们不能同时创建,在启动python解释器之后,即使没有创建任何的变量或者函数,还是会有一些函数直接可以用,因此在启动Python解释器的时候,一些函数就已经导入到内存当中供我们使用,所以是先加载内置名称空间,然后就开始从文件的最上面向下一行一行执行,此时如果遇到了初始化变量,就会创建全局名称空间,将这些对应关系存放进去,然后遇到了函数执行时,在内存中临时开辟一个空间,加载函数中的一些变量等等。所以这三个空间的加载顺序为:内置命名空间(程序运行伊始加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载。

取值顺序:如果在全局名称空间引用一个变量,会先从全局名称空间引用,全局名称空间如果没有,才会向内置名称空间引用。 如果在局部名称空间引用一个变量,先从局部名称空间引用,局部名称空间如果没有,才会向全局名称空间引用,全局名称空间再没有,就会向内置名称空间引用。
所以取值顺序满足的就近原则,从小范围到大范围一层一层的逐步引用且单向不可逆,即LEGB原则。(L:lcoal E:eclose G:global B:builtin)

作用域:作用域就是作用范围, 按照生效范围来看分为全局作用域和局部作用域

  • 全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循从上到下逐⾏执行)。全局作用域: 全局命称空间 + 内置命称空间
  • 局部作用域: 在函数内部可以使用。2. 局部作⽤域: 局部命称空间,局部作用域可以引用全局作用域的变量但不能改变全局变量(当python解释器读取到局部作用域时,若发现有对一个变量进行修改的操作,解释器会认为你在局部已经定义过这个局部变量了,于是就在局部找这个局部变量,若没有就会报错,无法改变全局变量),全局作用域不可义引用局部作用域的变量。

内置函数: globals() local()

  • globals(): 以字典的形式返回全局作用域(内置命称空间以及全局命称空间的所有内容)所有的变量对应关系。

  • locals(): 以字典的形式返回当前作用域的变量所有的对应关系。

  • 函数的嵌套(高阶函数):
    关键点:只要遇见了函数名+()就是函数的调用. 如果没有就不是函数的调用。请说出下面代码的执行顺序:

    #例1:
    def func1():
        print('in func1')
        print(3)
    def func2():
        print('in func2')
        print(4)
    func1()
    print(1)
    func2()
    print(2)
    # 例2:
    def func1():
        print('in func1')
        print(3)
    def func2():
        print('in func2')
        func1()
        print(4)
    print(1)
    func2()
    print(2)
    # 例3:
    def func2():
        print('1in func2')
        def func3():
            print('in func3')
        print('2in func2')
        func3()
        print('3in func2')
    print(3)
    func2()
    print(5)
    

默认参数的陷阱(只针对于默认参数是可变的数据类型):如果默认参数使用的是可变类型数据,那么无论调用多少次这个默认参数,都是同一个(id相同)。默认参数的可变数据类型既不在全局也不再局部,定义后不会消失。

def func(num,nums=[]):
    nums.append(num)
    return nums
ret1 = func(1)
print(ret1)    #[1]
ret2 = func(2)
print(ret2)   #[1,2]  将第一次的数据也包含了。

#例:
def func(a,list=[]):
    list.append(a)
    return list
ret1 = func(10,)
print(ret1)         #[10]
print(func(20,[]))  #[20]  #重新为列表传入参数
print(func(100))    #[10,100]
print(ret1)         #[10,100]

局部作用域的陷阱:在函数中,如果定义一个变量,但是在定义变量之前改变这个变量,即使全局变量有此引用的变量,仍然会报错。

#例1:
count = 1
def func():
    count += 1
    print(count)
func()
IndentationError: unindent does not match any outer indentation level
#例2:
count = 1
def func():
    print(count)
func()      #1
#例3:
count = 1
def func():
    print(count)
    count = 1
func()
UnboundLocalError: local variable 'count' referenced before assignment

global nonlocal

  • global:在局部作用域里声明一个全局变量。

    #1.
    def func():
        global num
        num = 1
    print(num)  #会报错。
    #2.
    num = 0
    def func():
        global num
        num = 1
    func()
    print(num)    #1
    
  • nonlocal:不能够操作全局变量;主要用于内层函数对外层函数的局部变量进行修改。

    def func1():
        count = 1
        def inner():
            nonlocal count
            count+=1
        inner()
    

函数名的运用

  • 函数名指向的是函数的内存地址函数名+()就可以执行函数
  • 函数名可以作为容器类数据类型的元素
  • 函数名可以作为函数的参数
  • 函数名可以作为函数的返回值。

迭代器

可迭代对象(iterable):
对象:python中一切皆对象。 可迭代:可以进行循环更新的一个值。以专业角度来说,内部含有__iter__方法的对象即为可迭代对象。如:str、list、tuple、dict、set、range等。

  • 获取对象的所有方法并且以字符串的形式表现:dir()

    s1 = 'qwer'
    print(dir(s1))
    
  • 判断一个对象是否是可迭代对象:

    s1 = 'qwer'
    print('__iter__' in dir(s1))  #True
    
  • 可迭代对象的优点:

    1. 存储的数据能够直接显示,比较直观。
    2. 拥有的方法比较多,操作起来方便。
  • 可迭代对象的缺点:

    1. 占内存。
    2. 不能直接通过for循环(不能直接取值),python内部自动将其转换为迭代器然后再进行for循环(用next()方法取值)。

迭代器:
迭代器的定义:内部含__iter____next__方法的对象就是迭代器。例如:文件句柄。

  • 判断是否为可迭代器

  • 可迭代对象可以转换为迭代器iter().__iter__()

    s1 = 'qwert'
    obj = iter(s1)
    #或者:
    s1.__iter__()
    print(obj)   #会返回一个迭代器的内存地址
    
  • 对迭代器进行取值next().__next__()

  • 迭代器优点:
    1.节省内存,迭代器在内存中相当于只占一个数据的空间,因为每次取值上一条数据都会在内存释放。迭代器具有惰性机制,next一次,只取一个值,绝不多取。

  • 迭代器的缺点:
    1.不能直观的查看里面的数据。
    2.只能一直向下取值。
    3.速度慢。

  • 可迭代对象与迭代器的对比:
    1.可迭代对象是的操作方法比较多,比较直观,储存数据相对少(几百万个数据,8G内存是可以承受的)的一个数据集。当侧重于对于数据可以灵活处理,并且内存空间足够,可将数据设置为一个可迭代对象。
    3.迭代器是非常节省内存,可以记录取值位置,可以通过循环加next方法取值,但是不直观,操作方法比较单一的一个数据集。当数据量过大,可选择将数据设置为一个迭代器。

  • 用while循环模拟for循环对可迭代对象进行取值:

    l1 = [1,2,3,4,5,6,7,8]
    obj = iter(l1)
    while 1:
        try:     #try:异常处理
            print(next(obj))
        except StopIteration:
            break
    

生成器

生成器:python社区把生成器与迭代器看成同一种,生成器的本质就是迭代器。唯一的区别是:生成器是我们自己用python代码构建的数据结构,迭代器都是python提供的,或者转化的。

获取生成器的方法:

  1. 生成器函数
  2. 生成器表达式
  3. python内部提供的。

生成器函数获取生成器,yield:

def func():
    print(1)
    print(3)
    yield 5
print(func)    #   仍然是一个函数
ret = func()
print(ret)    #
#generator object#生成器对象

#一个next对一个yield的返回值,如果再次调用yield就会接着上次的next.

def func():
    for i in range(1000000):
        yield i
ret = func()
for i in range(5):
    print(next(ret))
for i in range(10):
    print(next(ret))
#0, 2,3,4,5,6,7,8,9,10,11,12,13,14
  • yield与return
    return:一个函数中只能存在一个return结束函数的调用,并且给函数的执行者返回值。
    yield:只要函数中有yield那么它就是生成器函数,生成器函数中可以存在多个yield,一个next对一个yield的返回值,yield不会结束函数的调用,但return会结束函数的调用。

  • yield from(3.4版本以后),将数据变成一个迭代器返回,生成器函数可以直接用for循环

    def func():
        l1 = [1,2,3]  
        yield from l1   #将这个列表变成了一个迭代器返回
    for i in func():
        print(i)   #1 2 3
    

生成器表达式,生成器表达式:与列表推导式的写法几乎一样,生成器也有循环模式和筛选模式,只是将[]变为()。但比列表推导式更节省空间。

l1 = (i for i in range(10))
print(l1)
# at 0x000001BB028F8CC8>
print(next(l1))   #0

for i in l1:    #可直接用for循环,因为for循环本身就是将可迭代对象变为迭代器再循环。
    print(i)

list可以将生成器中的所有元素添加到列表中

def func(*args):
    for i in args:
        for j in i:
            yield i
print(list(func('asdf',(1,2,3))))

#简化上述函数:
def func(*args):
    for i in args:
        yield from i  #优化了内层循环,提高了运行效率。

内置函数

python提供了68个内置函数:abs() enumerate() filter() max() min() open() range() print() len() list() dict() str() float() reversed() set() sum() tuple() type() zip() dir() classmethod() delattr() getattr() issubclass() isinstance() object() property() setattr() staticmethod() super() all() any() bytes() callable() chr() complex() divmod() eval() exec() format() frozenset() globals() hash() help() id() input() int() locals() next() oct() ord() pow() repr() round()

  • eval():剥去字符串的外衣(引号),运算里面的代码,有返回值。工作时最好不用,容易中病毒。

    s1 = '1+1'
    print(s1)   #1+1
    print(eval(s1),type(eval(s1)))  #2 
    
  • exec():与eval()几乎一样,但是它是处理代码流的。工作时最好不用,容易中病毒。

    msg = """
    for i in range(5):
        print(i)"""
    exec(msg)   #0,1,2,3,4
    
  • hash:获取一个对象(可哈希对象:int,str,bool,tuple)的哈希值。哈希值:加密算法之间需要哈希值,与哈希算法有关。

    print(hash('12'))
    
  • help():打印/获取一个对象的使用方法。

    print(help(str))
    print(help(str.upper))
    
  • callable():判断一个对象是否可调用,真为True,假为False。

    l1 = [1,2]
    def func():
        pass
    print(callable(l1))  #False
    print(callable(func))   #True
    
  • int():将字符串类型转换为int类型;取整(舍尾法)

  • float():将int和str转换为float。

  • list():将一个可迭代对象转换成列表

  • tuple():将一个可迭代对象转换成元组

  • dict():通过相应的方式创建字典。

  • abs():返回绝对值

  • sum():求和

  • reversed():将一个序列翻转,返回翻转序列的迭代器。与列表的方法l1 .reverse()区分.

  • complex:创建一个值为real+imag*j的复数;转换一个str或int为复数,如果第一个参数为str则不需要传递第二个参数。(复数:complex)

    print(complex('1'))    #(1+0j)
    
  • bin:将十进制数转换为二进制字符串并返回。

  • oct:将十进制数转换为八进制字符串并返回。

  • hex:将十进制数转换为十六进制字符串并返回。

  • divmod:计算除数与被除数的结果,返回一个包含商和余数的元祖(a//b,a%b)

  • round:保留浮点数的位数,默认保留整数。

  • pow:求x**y的次幂,并可以对所求结果对第三个参数取余

    print(pow(2,2))      #2**2   4
    print(pow(2,2,3))    #(2**2)%3    1  
    
  • bytes:用于不同编码之间的转换。

    s1 = '你好'
    bs1 = s1.encode('utf-8')
    print(bs1)
    #b'\xe4\xbd\xa0\xe5\xa5\xbd'
    s2 = bs1.decode('utf-8')
    print(s1)
    #你好
    s3 = '你好'
    bs2 = bytes(s3,encoding='utf-8')
    print(bs2)
    #b'\xe4\xbd\xa0\xe5\xa5\xbd'
    bs3 = str(bs2,encoding='utf-8')
    print(bs3)
    #你好
    
  • ord():输入字符找该字符编码的位置。(如果在ASCII码中就用ASCII码,否则用Unicode)

  • chr():输入位置数字找出其对应的字符。(如果在ASCII码中就用ASCII码,否则用Unicode)

  • repr():返回一个对象的string形式。(str带有引号的形式),在格式化输出时常用(%r)。

    s1 = 'python'
    print('i love %r'%(s1))   #i love 'python'
    
  • print():源码分析:print(self, *args, sep=' ', end='\n', file=None,flush=False):
    file: 默认是输出到屏幕,如果设置为文件句柄,输出到文件
    sep: 打印多个值之间的分隔符,默认为空格
    end: 每一次打印的结尾,默认为换行符
    flush: 立即把内容输出到流文件,不作缓存

    print(1,2,3)   #1 2 3
    print(1,2,3,sep='@')   # 1@2@3
    print(1,2,3,end='')   #不换行
    
  • all():可迭代对象中,全为True才是True。

  • any():可迭代对象中,有一True即为True。

  • zip(): 拉链方法;函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元祖组成的内容,如果各个迭代器的元素个数不一致,则按照长度最短的返回。

    lst1 = [1,2,3]
    lst2 = ['a','b','c','d']
    lst3 = (11,12,13,14,15)
    for i in zip(lst1,lst2,lst3):
        print(i)
    #(1, 'a', 11)  (2, 'b', 12)  (3, 'c', 13)
    
  • min():求最小值,可以与函数结合(key:自动将可迭代对象中的每个元素按照顺序传入key对应的函数中,然后以返回值比较大小,key=一定是函数名,不能加(),。min/max默认会按照字典的键去比较大小,但可以自己规定。

    #以绝对值的方式取最小值
    l1 = [1,2,-1,-5,3]
    def func(s):
        return abs(s)
    print(min(l1,key=func))   #1
    #也可以直接使用匿名函数:
    print(min(l1,key=lambda s:abs(s)))    #1
    
    dic = {'a':3,'b':2,'c':1}
    min(dic)   #a     默认以键排序,返回键
    
    #以值比较:
    print(min(dic,key=lambda s:dic[s]))   #c   以值排序,返回键(返回的是循环的元素,而不是经函数转换后比较的值。
    
  • max():求最大值,可以与函数结合,同min()。

  • sorted():排序函数;语法: sorted(iterable,key=None,reverse=False)。key: 排序规则(排序函数),返回列表。在sorted内部会将可迭代对象中的每一个元素传递给这个函数的参数.根据函数运算的结果进行排序reverse: 是否是倒叙,True:倒叙;False:正序

    lst = [1,3,2,5,4]
    lst2 = sorted(lst)
    print(lst)  # 原列表不会改变
    print(lst2)  # 返回的新列表是经过排序的
    lst3 = sorted(lst,reverse=True)
    print(lst3)  # 倒叙
    #字典使用sorted排序
    dic = {1: 'a',3: 'c',2: 'b'}
    print(sorted(dic))  #[1,2,3] 字典排序返回的就是排序后的key
    
    #和函数组合使用
    # 定义一个列表,然后根据一元素的长度排序
    lst = ['1','111','11','1111']
    # 计算字符串的长度
    def func(s):
        return len(s)
    print(sorted(lst,key=func))  #['1','11','111','1111']
    lst = [{'id': 1,'name': 'a','age': 18},
            {'id': 2,'name': 'b','age': 17},
            {'id': 3,'name': '3','age': 16},]
    # 按照年龄对学生信息进行排序
    print(sorted(lst,key=lambda e: e['age']))
    
  • filter():筛选过滤,返回一个迭代器(可与列表推导式的筛选模式进行对比);语法: filter(function,iterable),function: 用来筛选的函数,在filter中会自动的把iterable中的元素传递给function,然后根据function返回的True或者False来判断是否保留此项数据

    lst = [{'id':1,'name':'a','age':18},
            {'id':2,'name':'b','age':17},
            {'id':3,'name':'c','age':16},]
    
    ls = filter(lambda e:e['age'] > 16,lst)
    print(list(ls))
    
    #[{'id': 1, 'name': 'alex', 'age': 18},{'id': 1, 'name': 'wusir', 'age': 17}]
    
  • map():映射函数,返回一个迭代器(可与列表推导式的循环模式对比);语法: map(function,iterable) 可以对可迭代对象中的每一个元素进映射,分别取执行function。

    #例1;计算列表中每个元素的平方,返回新列表
    lst = [1,2,3,4,5]
    print(map(lambda s:s*s,lst))   #
    print(list(map(lambda s:s*s,lst)))
    
    #例2;计算两个列表中相同位置的数据的和
    lst1 = [1, 2, 3, 4, 5]
    lst2 = [2, 4, 6, 8, 10]
    print(list(map(lambda x, y: x+y, lst1, lst2)))
    
  • reduce():reduce(函数名,可迭代对象) # 这两个参数必须都要有,缺一个不行,在Python2.x版本中recude是直接 import就可以的, Python3.x版本中需要从functools这个包中导入。

    from functools import reduce
    def func(x,y):
        return x + y
    ret = reduce(func,[1,2,3])
    print(ret)  # 结果 6
    #reduce的作用是先把列表中的前俩个元素取出计算出一个值然后临时保存着,接下来用这个临时保存的值和列表中第三个元素进行计算,求出一个新的值将最开始临时保存的值覆盖掉,然后在用这个新的临时值和列表中第四个元素计算.依次类推。注意:我们放进去的可迭代对象没有更改
    
    #现在有[1,2,3,4]想让列表中的数变成1234
    l = reduce(lambda x,y:x*10+y,[1,2,3,4])
    print(l)
    

闭包、装饰器

全局变量,数据不安全。使用局部变量,保证数据的安全。当内层函数对外层函数非全局变量的引用(使用)时,就会形成闭包。被引用的非全局变量也称作自由变量,这个自由变量会与内层函数产生一个绑定关系,令自由变量不会再内存中消失。闭包现象只能存在函数的嵌套中。

判断一个函数有没有自由变量:

def func1():
    l1= []
    def func2(num):
        l1.append(num)
        return l1
    return func2
a = func1()
# 函数名.__code__.co_freevars 查看函数的自由变量
print(a.__code__.co_freevars)  # ('l1',)

# 函数名.__code__.co_varnames 查看函数的局部变量
print(a.__code__.co_varnames)  # ('num',)

# 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。
print(a.__closure__)
#(,)

# cell_contents 自由变量具体的值
print(a.__closure__[0].cell_contents)  # []

装饰器:完全遵循开放封闭原则,即在不改变原函数的代码以及调用方式的前提下,为其增加新的功能。(装饰器的本质是闭包)python提出了一个‘语法糖’的概念。在编写了装饰器后,需要将装饰器代码放在所有代码的前方。当要对装饰器函数调用时,在要装饰函数的前方使用@+装饰器函数名

标准版的装饰器:

def wrapper(f):
    def inner(*arg,**kargs):
        '''添加额外的功能:执行被装饰函数之前的操作'''
        ret = f(*arg,**kargs)
        '''添加额外的功能:执行被装饰函数之前的操作'''
        return ret
    return inner
#装饰器的调用
@wrapper
def func():
    pass

自定义模块

模块的本质就.py文件,封装语句的最小单位。模块的分类:内置模块(200种左右)、第三方模块(6000多种)、自定义模块。当python脚本/模块运行时,python解释器会自动将一些模块、函数加载到内存。可用import sys print(sys.modules)查看

自定义模块:

  1. 编写自定义模块开头最好用多行注释说明模块内容。

  2. 模块中出现的变量、函数定义、类等称为模块的成员。

  3. 模块(.py文件)的运行方式:

    1. 脚本方式:直接用解释器执行。
    2. 模块方式(导入方式):被其他的模块导入,为导入它的模块提供资源(变量、函数定义、类定义等)。
  4. 模块被其他模块导入时,会将模块中的所有代码加载到内存,其中的可执行语句被会立即执行。因此为了方便调试和使用,可用__name__属性。将print(__name__)编写在模块中时,以脚本方式运行打印的是字符串__main__,以被导入的方式运行时,会打印出模块名。因此模块应包含if __name__=='__main__':以用来判断是脚本方式运行还是导入方式,此时当模块被导入时可执行语句不会直接运行。

    def func():
    ......
    
    def main(): 
    pass
    
    if __name__=='__main__':
    	main()
    

导入模块的方式:

  • import xxximport xxx,yyy,....导入一个模块或导入多个模块。经常将import os,sys这两个模块放在一块导入。可以使用别名(alias,缩写为as)导入:import xxx as z 导入xxx模块重命名为z。

    当运行至此导入模块方法代码时会在内存全局命称空间中开辟以模块名命名的新的名称空间,并将模块中的所有成员加载至命称空间中。被导入的每个模块都有独立的命称空间。

  • from xxx import yfrom xxx import a,b,c,......:从某个模块中导入指定的成员或导入多个成员。from xxx import yyy as z导入xxx模块将成员名重命名为z。

    当运行至此导入模块方法代码时会在脚本的全局命称空间中创建导入的成员的变量,因此不用使用模块名.成员名调用,但用此方法可能会导致命名冲突,后者将前者覆盖。导入后内存全局命称空间中开辟以模块名命名的新的名称空间,变量的内存地址的指向关系仍然不会改变,每个模块仍都有独立的命称空间。

  • from xxx import *:默认从某个模块中导入全部可导入的成员。配合__all__使用,被引用的模块中编写__all__=['','',...]列表中是可被导入的成员。但__all__只对此种导入方法起作用,用于表示模块可以被外界使用的成员,元素是成员名组成的字符串,默认不写是可以导入全部成员。

系统导入模块的搜索路径,以import time示例:先在内存中查看是否有以该模块名命名的命称空间,如果之前成功导入过某个模块,会直接使用已经存在的模块。若没有则再内置模块路径中(在python安装路径中lib和site-packages文件夹下)寻找。若仍没有,就会在sys.path中寻找。

sys.path:查看sys.path内容,用cmd运行:import sys print(sys.path)打印出的列表第一个元素是当前执行脚本的路径。列表动态可修改因此可将自定义模块文件夹所在的绝对路径添加到sys.path中以便导入:

import sys
sys.path.append('模块文件夹路径')

在不同的目录下相对导入自定义模块:
相对导入:针对某个项目中的不同模块之间进行导入,称为相对导入(模块间必须有一个同一个文件夹)。相对导入只有一个语法:from 相对路径 import xxx
相对路径:包含了点号的一个相对路径。
.:表示当前的路径。
..:表示的是父路径
...:表示的是父路径的父路径
..x.y:表示的是父路径下x文件夹下的y文件(夹)

举例:在python文件夹下有t1和t2两个文件夹,t1和t2文件夹下分别有pt1.py和pt2.py两个模块。python文件夹在WorkSpace文件夹下,WorkSpace下有一个main.py文件。

#相对导入(在pt1.py文件中编写):
#1.将pt1作为对外界的接入口:
from ..t2 import pt2    #..:从当前的路径(不包含当前文件,即/WorkSpace/python/t1)的父目录(/WorkSpace/python/)下找t2,在从t2中找pt2。

#测试相对导入(在main.py文件中编写):
import os,sys
sys.path.append(os.path.dirname(__file__))   #把项目所在的父路径加到sys.path中。
from python.t1 import pt1
#使用pt1.py模块文件导入的pt2.py模块中的成员:
pt1.pt2.成员名   #但不推荐这样写。容易向外界暴露pt2模块。
#更改方法(真正的相对导入):
#相对导入(在pt1.py文件中编写):
#1.将pt1作为对外界的接入口:
from ..t2.pt2 import *    #直接导入pt2模块

#测试相对导入(在main.py文件中编写):
import os,sys
sys.path.append(os.path.dirname(__file__))   #把项目所在的父路径加到sys.path中。
from python.t1 import pt1
#使用pt1.py模块文件导入的pt2.py模块中的成员:
pt1.成员名   #成员名:可以是pt1和pt2模块的成员。
#不用相对导入(不推荐):
#1.在pt1模块下编写:
import os,sys
sys.path.append(as.path.dirname(os.path.dirname(__file__))+'/t2')
from pt2 import *

常用模块

使用导入的模块是否要在其后加():如果是一个类(class)或者是一个函数(function)就要加(),如果是一个属性就不要加括号。

random模块

1)random模块(pseudo-random,伪随机数):提供了随机数获取的方法
1)random.random():获取[0.0,1.0)范围内的浮点数。
2)random.randint(a,b):获取[a,b]范围内的一个整数。
3)random.uniform(a,b):获取[a,b]范围内的一个浮点数数。
4)random.shuffle(x):把参数指定的数据中的元素打乱,参数必须是一个可变的数据类型。没有返回值(因为是直接将x打乱,调用改函数后x会被改变,与append无返回值原因相同。)(不支持元祖,可用sample打乱)
5)random.sample(x,k):从x中随机抽取k个数据,组成一个列表返回。
6)random.randrange(start,stop,step):生成一个[start,stop)之间以step为步长的随机整数。默认步长为1。
7)random.seek(a):设置初始化随机数种子。a,随机数种子,可以是整数或浮点数。使用random库产生随机数不一定要设置随机数种子,如果不设置,random库默认以系统时间产生当做随机数种子。设置随机数种子的好处是可以重复在现相同的随机数序列。

l1 = [1,2,3,4,5,6,]   #列表是有序的
s1 = {1,2,3,4,5,6,}
s2 = 'wfwhgfwqnbvc阿尔天花板vc'
t1 = (1,2,3,4,5,6,)
import random
random.seed()
print(random.random())
print(random.uniform(1,8))
print(random.randint(2,8))
print(random.shuffle(l1))    #只能用于有下标类型,并且可以用x[i],x[j]=x[j],x[i]互换,dict、set、tuple不行。
print(l1)
random.shuffle(s2)
print(s2)
s3 = random.sample(t1,len(t1))    #取样,
print(t1)

time模块

封装了获取时间戳和时间戳与字符串形式的一些方法,时间戳:从时间元年(1970年,1月,1日,00:00:00)到现在经过的秒数(毫秒数,不同系统/编程语言不一样。)

import time

# 获取时间戳:
print(time.time())
# 默认使用当前时间戳,

# 获取格式化时间对象(方法:gmtime()、localtime()),格式化时间对象:struct_time:
print(time.gmtime())   #接受参数为时间戳,默认不写使用当前时间戳,获取格林威治(GMT)时间。

print(time.localtime())   #接受参数为时间戳,默认不写使用当前时间戳,获取当地时间,tm_hour与格林威治时间(时区差)不同。

# 格式化时间对象和时间字符串(str)之间的转换。time.strftime(format,str)
a = time.localtime()
print(time.strftime('%Y-%m-%d %H:%M:%S',a))   #接受两个参数,第一个是时间格式,第二个是格式化时间对象(默认不写用当前的时间戳的格式化时间对象)。

# 时间字符串转换为时间对象(time.strptime(str,format)):
print(time.strptime('2000 1 1','%Y %m %d'))   #不指定时间用默认值(小时分钟等用0,月份天等用1)

# 格式化时间对象转换为时间戳:time.mktime()
print(time.mktime(time.localtime()))

# 暂停当前程序:time.sleep()     参数为秒
time.sleep(2)         #暂停2秒

datetime模块

封装了一些日期和时间相关的。主要用于数学计算

date类(年月日):

#date类
d = datetime.date(2010,10,10)
print(d)        #2010-10-10
print(d.year)   #2010
print(d.month)  #10
print(d.day)    #10

time类(时分秒):

#time类
t = datetime.time(10,11,12)
print(t)
print(t.hour)
print(t.minute)
print(t.second)

datetime类

dt = datetime.datetime(2010,10,10,11,11,11)
print(dt)
print(dt.year)

timedelta类: 参与数学运算,创建时间对象(只能和date,datetime,timedelta进行运算),会产生进位,和时间段进行运算的结果类型和另一个被操作的类型相同。

td = datetime.timedelta(seconds=3)
d = datetime.datetime(2000,10,10,10,10,59)
res = d+td
print(res)

练习:随便给出一年,计算2月份有多少天。

import datetime
#1.首先创建出指定年份的3月1号,然后让它往前走一天。
year = int(input("输入年份:"))
#2. 创建指定年份的date对象。
d = datetime.date(year,3,1)
dt = datetime.timedelta(days=1)
res = d-dt     #和时间段进行运算的结果类型和前方被减类型相同。
print(res.day)

os模块

和操作系统相关的操作被封装在这个模块中。

  1. 和文件相关的:重命名、删除、

    import os
    #删除文件
    os.remove()    #参数为文件的路径
    #重命名文件
    os.rename()    #两个参数,第一个为文件路径,第二个为文件新名
    #删除目录,必须是空目录,在程序中删除不会放在回收站中。
    os.removedirs()    #一个参数,目录的路径
    #使用shutil模块可以删除带内容的目录。
    import shutile
    shutile.rmtree()      #一个参数,文件夹目录
    os.getcwd()				#可查看当前的目录
    os.chdir(‘指定目录’)		#可切换目录 
    
  2. 和路径相关的操作,被封装到另一个子模块中:os.path

    绝对路径:从盘符开始定位的路径;相对路径:从当前文件定位的路径

    exists(path)	判断路径是否存在	 若文件不存在则为False
    isdir(s)		判断是否为一个目录   若文件不存在则为False
    isfile()		判断是否是一个文件	若文件不存在则为False
    
    以下方法不会判读文件是否存在
    dirname(path)    取一个路径前的目录(父目录),不会判断目录是否存在,
    basename(path)   取一个路径中最后的部分
    split(path)      分割路径,返回一个二元祖,第一个元素为路径前的(父目录),第二个为路径中最后的部分。
    join(path,paths)	合并路径。
    abspath(path)	将路径变为绝对路径
    isabs()			判断一个路径是否为绝对路径   
    
    import os
    res = os.path.dirname(r'd:/aaa/bbb/ccc/text.py')   
    print(res)		#d:/aaa/bbb/ccc
    
    res = os.path.basename(r'd:/aaa/bbb/ccc/text.py')
    print(res)      #text.py
    
    res = os.path.join('d:\\','aaa','bbb','cc')   #有一个转义字符
    print(res)		#d:\aaa\bbb\cc
    
    res = os.path.abspath(r'd:\aaa\bbb\cc')
    print(res)		#D:\aaa\bbb\cc
    
    
    res = os.path.abspath(r'\aaa\bbb\cc')  #如果是\开头的路径,默认是在当前盘符下。
    print(res)			#D:\aaa\bbb\cc
    
    res = os.path.abspath(r'aaa\bbb\cc')    #如果不是\开头,则是当前脚本的路径。
    print(res)			#D:\WorkSpace\Python\text1\aaa\bbb\cc
    

    使用os模块获取一个文件路径的父路径:import os os.path.dirname(__file__)__file__:获取模块的路径。import os os.__file__

sys模块

提供了解释器使用和维护的变量和函数;没有提供源码,用c语言编写直接集成在解释器上;默认会导入os模块,但若要使用os模块仍需import os

  1. sys.argv:以当前脚本方式执行程序时,从命令行获取参数。

    argv[0]表示的是当前正在执行的脚本名,argv[1]表示第一个参数,以此类推。

    import sys
    print("脚本名:",sys.argv[0])
    print("第一个参数:",sys.argv[1])
    #在命令行运行,传入两个参数,会将两个参数打印出来
    
  2. sys.path系统寻找模块的路径,可以通过PYTHONPATH来进行优化。由于是程序执行的时候进行初始化的,所以路径的第一项path[0]始终是调用解释器的脚本所在的路径,如果是动态调用的脚本,或者是从标准输入读取到的脚本命令,则path[0]是一个空字符串,程序中可以随时对这个路径进行修改,已达到动态添加模块路径的目的。

  3. sys.modules:以字典形式返回系统已经加载的模块。常作为是否重新重新加载模块的判断。

json模块

将数据结构直接转换为字符串用str()可以,但再将字符串数据转换为原先的数据结构就会出现问题。

l1 = ['ab','cd']
s1 = str(l1)
print(s1)	#['ab', 'cd']
print(list(s1))	#['[', "'", 'a', 'b', "'", ',', ' ', "'", 'c', 'd', "'", ']']

JavaScript Object Notation:Java脚本兑现标记语言。已经成为一种简单的数据交换格式。将数据转换成特殊字符串,不同语言都遵循的一种数据转化格式,用于储存dump()和load()或dumps()和loads()或网络传输dumps()和loads()

json序列化:

import json
s = json.dumps([1,2,3,4,])  #将指定的对象转换成json格式的字符串,仍然放在内存中
print(s,type(s))	#[1, 2, 3, 4] 

#元祖序列化后变成列表
import json
s = json.dumps((1,2,3,4,)) 
print(s,type(s))	#[1, 2, 3, 4] 

#集合不可一被json序列化

#将结果直接写到文件中:json.dump(obj,fb)
with open('a.txt',mode='at',encoding='utf-8') as f:
    json.dump(['1,2,3,'],f)

json反序列化:

import	json
res = json.dumps([1,2,3,])
lst = json.loads(res)

#从文件中反序列化:json.loads(fb)
with open('a.txt',encoding='utf-8') as f:
    res = json.load(f)
  • dump()和load()只能一次性写,一次性读。把需要序列化的对象,通过dumps()和loads()的方式,可实现多次读或写,写入时要换行,才能读出再转化。

    with open('a.txt',mode='wt',encoding='utf-8') as f:
        for i in range(10):
    		f.write(json.dumps()+'\n')
    
    with open('a.txt',encoding='utf-8') as f:
        for i in f:
        	json.loads(i.strip())	#	或json.loads(i),默认去除换行
    

pickle模块

可将python中所有的数据类型转换成字节串

元祖在pickle中转换不会改变类型。

pickle也可以转换set类型

pickle中dump()和load()可以多次读写一个文件dump(s,f)可以写入python中的任何对象(变量、函数、类等)

import pickle
bys = pickle.dumps((1,2,3))  #转化为字节
res = pickle.loads(bys)
print(type(res),res) 	# (1, 2, 3)

#把pickle序列化内容写入文件中:
with open('b.txt',mode='wb') as f:
    pickle.dump([1,2,3],f)
    
    
#从文件中反序列化pickle数据:
with open('b.txt',mode='rb') as f:
    pickle.load(f)
  • pickle与json的比较

    json:

    1. 不是所有的数据类型都可以序列化
    2. 不能多次对同一个文件序列化
    3. json数据可以跨语言。
    4. json序列化只支持部分python数据结构:dict、tuple、int、float、True、False、None

    pickle:

    1. 所有的python类型都能序列化,结果为字节串。
    2. 可以多次对同一个文件序列化
    3. 不能跨语言,只能在python中使用。

hashlib模块

封装一些用于加密的类。加密的目的:用于判断和验证,而非解密。给一个数据进行加密,用另个加密的结果和第一次加密的结果进行对比,如果加密结果想同,说明原文相同。

特点:

  • 把一个大的数据切分成不同的块,分别对不同的块进行加密,再汇总的结果和直接对整体数据加密的结果是一致的。
  • 单向加密,不可逆。
  • 原始数据的一点小的变化,将导致加密结果非常大的差异。

md5加密算法:

import hashlib
#获取一个加密对象
m = hashlib.md5()
#使用加密对象的update进行加密
m.update(b'abc')	#将abc变为字节,若有中文则为'abc'.encode('utf-8')
#通过hexdigest()获取加密。
res = m.hexdigest()
print(res)	#900150983cd24fb0d6963f7d28e17f72

给一个数据加密的步骤:

  1. 获取一个加密对象,不只是有md5,还有sha系列sha224', 'sha256', 'sha384', 'sha3_224', 'sha3_256', 'sha3_384', 'sha3_512', 'sha512'等,随着sha系列数字越高,加密越复杂,。在创建加密对象时,可以指定参数,使加密程度更高。

    import hashlib
    m = hashlib.md5('abc'.encode('utf-8'))
    m.update('qaz'.encode('utf-8'))
    print(m.hexdigest())
    
    
2.  使用加密对象的`update()`方法进行加密。加密对象可以调用多次(即为把一个大数据切分成小数据,再把小数据进行累次加密,不是对同一个数据多次加密)。都是对**字节**进加密。相同的加密对象将字节转化成**固定长度**的字符串。

3.  通常通过`hexdigest()`获取加密字符串,也可以通过`digest()`获取加密字节串。

不同加密算法的加密结果长度不同,随着加密结果的变长,加密时间也会变长。

​```python
#注册和登录程序
import hashlib
def register():
    username = input('输入用户名:')
    passwd = input("输入密码:")
    get_passwd = get_md5(passwd)    #加密
    with open('login',mode='at',encoding='utf-8') as f:     #写入文件
        f.write(f'{username}|{get_passwd}\n')

def login():
    username = input('输入用户名:')
    passwd = input('输入密码:')
    get_passwd = get_md5(passwd)
    res = f'{username}|{get_passwd}\n'
    with open('login',mode='rt',encoding='utf-8') as f:
        for line in f:
            if res == line:
                return True
        else:return False

def get_md5(passwd):
    m = hashlib.md5('12')	#再次加密
    m.update(passwd.encode('utf-8'))
    return m.hexdigest()

while True:
    op = input('1.注册 2.登录 3.退出')
    if op == '1':
        register()
    elif op == '2':
        res = login()
        if res:
            print('登录成功')
        else:
            print('登录失败,请重新登录')
    elif op == '3':
        break
    else:print("您输入的有误,请重新输入")

文件的校验:

Linux中一切皆文件:文本文件,非文本文件,音频,视频,图片……。无论下载的视频还是国外的软件往往都会有一个md5值。

collections模块

封装了一些常用的容器类。

namedtuple():命名元祖,可以使用属性的方式引用元祖中的数据。

import collections as co:
#namedtuple()的返回值是一个类
Rectangle = co.namedtuple('Rectangle_class',['length','width']) #第一个参数是对类名的说明信息,不能有空格。namedtuple()的返回值是赋值给了自定的类名。
r = Rectangle(10,5)
#通过属性访问元祖的元素
print(r.length)
print(r.width)
#通过引所的方式访问元素
print(r[0])

defaultdict():默认值字典

#defaultdict():(自定义)函数(不能有参数)充当第一个参数
d = co.defaultdict(lambda :'hello',name='aa',age='10')
print(d['na'])	#hello      若无要查询的键,不会报错,会根据函数返回一定的值,且会自动将此键值增加至字典中
print(d)
#defaultdict( at 0x000001F90D30D378>, {'name': 'aa', 'age': '10', 'na': 'hello'})

Counter():计数器,返回值是字典,必须使用可哈希的数据。

c = co.Counter('asasasasaszszxaxzsxazsx')
print(c)	#Counter({'s': 8, 'a': 7, 'z': 4, 'x': 4})
print(c.most_common(3))		#[('s', 8), ('a', 7), ('z', 4)]

你可能感兴趣的:(python基础总结)