python_study

python学习

基础语法

#开头的语句是注释,注释是给人看的,可以是任意内容,解释器会忽略掉注释。其他每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。按照约定俗成的惯例,应该始终坚持使用4个空格的缩进。Python程序是大小写敏感的,如果写错了大小写,程序会报错。

数据类型和变量

python中数字有四种类型:整数、布尔型、浮点数和复数。

  • int (整数), 如 1, 只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。
  • bool (布尔), 如 True。
  • float (浮点数), 如 1.23、3E-2
  • complex (复数), 如 1 + 2j、 1.1 + 2.2j
数据类型

在Python中,能够直接处理的数据类型有以下几种:

  • 整数

    Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1100-80800,等等。

    计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用0x前缀和0-9,a-f表示,例如:0xff000xa5b4c3d2,等等。

    对于很大的数,例如10000000000,很难数清楚0的个数。Python允许在数字中间以_分隔,因此,写成10_000_000_00010000000000是完全一样的。十六进制数也可以写成0xa1b2_c3d4

  • 浮点数

    浮点数也就是小数,之所以称为浮点数,是因为按照科学记数法表示时,一个浮点数的小数点位置是可变的,比如, 1.23 x 1 0 9 1.23x10^9 1.23x109 12.3 x 1 0 8 12.3x10^8 12.3x108是完全相等的。浮点数可以用数学写法,如1.233.14-9.01,等等。但是对于很大或很小的浮点数,就必须用科学计数法表示,把10用e替代, 1.23 x 1 0 9 1.23x10^9 1.23x109就是1.23e9,或者12.3e8,0.000012可以写成1.2e-5,等等。

    整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。

  • 字符串

    字符串是以单引号'或双引号"括起来的任意文本,比如'abc'"xyz"等等。请注意,''""本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc'只有abc这3个字符。如果'本身也是一个字符,那就可以用""括起来,比如"I'm OK"包含的字符是I'm,空格,OK这6个字符。

    **如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识,**比如:

    'I\'m \"OK\"!'
    

    表示的字符串内容是:

    I'm "OK"!
    

    转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\

    >>> print('I\'m ok.')
    I'm ok.
    >>> print('I\'m learning\nPython.')
    I'm learning
    Python.
    >>> print('\\\n\\')
    \
    \
    

    如果字符串里面有很多字符都需要转义,就需要加很多\,为了简化,Python还允许用r''表示''内部的字符串默认不转义

    >>> print('\\\t\\')
    \       \
    >>> print(r'\\\t\\')
    \\\t\\
    

    如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容

    >>> print('''line1
    ... line2
    ... line3''')
    line1
    line2
    line3
    

    上面是在交互式命令行内输入,注意在输入多行内容时,提示符由>>>变为...,提示你可以接着上一行输入,注意...是提示符,不是代码的一部分:

    ┌────────────────────────────────────────────────────────┐
    │Command Prompt - python                           _ □ x │
    ├────────────────────────────────────────────────────────┤
    │>>> print('''line1                                      │
    │... line2                                               │
    │... line3''')                                           │
    │line1                                                   │
    │line2                                                   │
    │line3                                                   │
    │                                                        │
    │>>> _                                                   │
    │                                                        │
    │                                                        │
    │                                                        │
    └────────────────────────────────────────────────────────┘
    

    当输入完结束符`````和括号)后,执行该语句并打印结果。

    如果写成程序并存为.py文件,就是:

    print('''line1
    line2
    line3''')
    

    多行字符串'''...'''还可以在前面加上r使用

  • 布尔值

    布尔值和布尔代数的表示完全一致,一个布尔值只有TrueFalse两种值,要么是True,要么是False,在Python中,可以直接用TrueFalse表示布尔值(请注意大小写),也可以通过布尔运算计算出来:

    >>> True
    True
    >>> False
    False
    >>> 3 > 2
    True
    >>> 3 > 5
    False
    

    布尔值可以用andornot运算。

    and运算是与运算,只有所有都为Trueand运算结果才是True

    >>> True and True
    True
    >>> True and False
    False
    >>> False and False
    False
    >>> 5 > 3 and 3 > 1
    True
    

    or运算是或运算,只要其中有一个为Trueor运算结果就是True

    >>> True or True
    True
    >>> True or False
    True
    >>> False or False
    False
    >>> 5 > 3 or 1 > 3
    True
    

    not运算是非运算,它是一个单目运算符,把True变成FalseFalse变成True

    >>> not True
    False
    >>> not False
    True
    >>> not 1 > 2
    True
    

    布尔值经常用在条件判断中,比如:

    if age >= 18:
        print('adult')
    else:
        print('teenager')
    
  • 空值

    空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。

变量

变量不仅可以是数字,还可以是任意数据类型。变量名必须是大小写英文、数字和_的组合,且不能用数字开头。

等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量

# -*- coding: utf-8 -*-
a = 123 # a是整数
print(a)
a = 'ABC' # a变为字符串
print(a)

这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言,赋值语句如下(// 表示注释):

int a = 123; // a是整数类型变量
a = "ABC"; // 错误:不能把字符串赋给整型变量

理解变量在计算机内存中的表示也非常重要。当我们写:

a = 'ABC'

时,Python解释器干了两件事情:

             * 在内存中创建了一个`'ABC'`的字符串;
             * 在内存中创建了一个名为`a`的变量,并把它指向`'ABC'`。

一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据

常量

所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量

PI = 3.14159265359

但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。

最后解释一下整数的除法为什么也是精确的。在Python中,有两种除法,一种除法是/

>>> 10 / 3
3.3333333333333335

/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:

>>> 9 / 3
3.0

还有一种除法是//,称为地板除,两个整数的除法仍然是整数:

>>> 10 // 3
3

你没有看错,整数的地板除//永远是整数,即使除不尽。要做精确的除法,使用/就可以。

因为//除法只取结果的整数部分,所以Python还提供一个余数运算,可以得到两个整数相除的余数:

>>> 10 % 3
1

无论整数做//除法还是取余数,结果永远是整数,所以,整数运算结果永远是精确的。

Python支持多种数据类型,在计算机内部,可以把任何数据都看成一个“对象”,而变量就是在程序中用来指向这些数据对象的,对变量赋值就是把数据和变量给关联起来。

对变量赋值x = y是把变量x指向真正的对象,该对象是变量y所指向的。随后对变量y的赋值不影响变量x的指向。

注意:Python的整数没有大小限制,而某些语言的整数根据其存储长度是有大小限制的,例如Java对32位整数的范围限制在-2147483648-2147483647

Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。

字符串和编码

字符编码

Unicode把所有语言都统一到一套编码里,Unicode标准也在不断发展,但最常用的是UCS-16编码,用两个字节表示一个字符。

ASCII编码和Unicode编码的区别:

ASCII编码是1个字节,而Unicode编码通常是2个字节。

字母A用ASCII编码是十进制的65,二进制的01000001

字符0用ASCII编码是十进制的48,二进制的00110000,注意字符'0'和整数0是不同的;

汉字已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101

如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001

文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间

“可变长编码”的UTF-8编码

字符ASCIIUnicodeUTF-8
A0100000100000000 0100000101000001
x01001110 0010110111100100 10111000 10101101

ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0LPTF3Ro-1677229279070)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210813112052664.png)]

python 字符串

在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言,例如:

>>> print('包含中文的str')
包含中文的str

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

如果知道字符的整数编码,还可以用十六进制这么写str

>>> '\u4e2d\u6587'
'中文'

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes

Python对bytes类型的数据用带b前缀的单引号或双引号表示:

x = b'ABC'

'ABC'b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

**纯英文的str可以用ASCII编码为bytes,含有中文的str可以用UTF-8编码为bytes。**含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。

bytes中,无法显示为ASCII字符的字节,用\x##显示。

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

如果bytes中包含无法解码的字节,decode()方法会报错:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8')
Traceback (most recent call last):
  ...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 3: invalid start byte

如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节:

>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'

要计算str包含多少个字符,可以用len()函数:

>>> len('ABC')
3
>>> len('中文')
2

len()函数计算的是str的字符数,如果换成byteslen()函数就计算字节数:

>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

可见,1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。

为了避免乱码问题,应当始终坚持使用UTF-8编码对strbytes进行转换。

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。

格式化

输出格式化的字符串,在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:

>>> print('Hello, %s' % 'world')
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

%运算符就是用来格式化字符串的。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。

常见的占位符有:

占位符替换内容
%d整数
%f浮点数
%s字符串
%x十六进制整数

其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:

# -*- coding: utf-8 -*-
print('%2d-%02d' % (3, 1))
print('%.2f' % 3.1415926)

#输出
3-01
3.14

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串:

>>> 'Age: %s. Gender: %s' % (25, True)
'Age: 25. Gender: True'

有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个%

>>> 'growth rate: %d %%' % 7
'growth rate: 7 %'

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}{1}……,不过这种方式写起来比%要麻烦得多:

>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

最后一种格式化字符串的方法是使用以f开头的字符串,称之为f-string,它和普通字符串不同之处在于,字符串如果包含{xxx},就会以对应的变量替换:

>>> r = 2.5
>>> s = 3.14 * r ** 2
>>> print(f'The area of a circle with radius {r} is {s:.2f}')
The area of a circle with radius 2.5 is 19.62

上述代码中,{r}被变量r的值替换,{s:.2f}被变量s的值替换,并且:后面的.2f指定了格式化参数(即保留两位小数),因此,{s:.2f}的替换结果是19.62

列表list和元组tuple

列表

list是一种有序的集合,可以随时添加和删除其中的元素,用方括号。

列出班里所有同学的名字,就可以用一个list表示:

>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

变量classmates就是一个list。len()函数可以获得list元素的个数:

>>> len(classmates)
3

用索引来访问list中每一个位置的元素,记得索引是从0开始的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UQ6ikQxj-1677229279074)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210813152014483.png)]

>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1

如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXYpFxOw-1677229279076)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210813152037926.png)]

>>> classmates[-1]
'Tracy'

以此类推,可以获取倒数第2个、倒数第3个:

>>> classmates[-2]
'Bob'
>>> classmates[-3]
'Michael'
>>> classmates[-4]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

当然,倒数第4个就越界了。

切片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M2wNij6R-1677229279077)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210813152112101.png)]

list是一个可变的有序表,所以,可以往list中追加元素到末尾:

>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

也可以把元素插入到指定的位置,比如索引号为1的位置:

>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

要**删除list末尾的元素,用pop()方法:或者 del **

>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

del classmates[1]
>>>['Michael', 'Bob', 'Tracy']

要删除指定位置的元素,用pop(i)方法,其中i是索引位置:

>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']

要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list里面的元素的数据类型也可以不同,比如:

>>> L = ['Apple', 123, True]

list元素也可以是另一个list,比如:

>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:

>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']

要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。

如果一个list中一个元素也没有,就是一个空的list,它的长度为0:

>>> L = []
>>> len(L)
0

列表对 + 和 * 的操作符与字符串相似。+ 号用于组合列表,* 号用于重复列表.

Python 表达式结果描述
len([1, 2, 3])3长度
[1, 2, 3] + [4, 5, 6][1, 2, 3, 4, 5, 6]组合
[‘Hi!’] * 4[‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’]重复
3 in [1, 2, 3]True元素是否存在于列表中
for x in [1, 2, 3]: print(x, end=" ")1 2 3迭代
序号函数
1len(list) 列表元素个数
2max(list) 返回列表元素最大值
3min(list) 返回列表元素最小值
4list(seq) 将元组转换为列表

Python包含以下方法:

序号方法
1list.append(obj) 在列表末尾添加新的对象
2list.count(obj) 统计某个元素在列表中出现的次数
3list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
4list.index(obj) 从列表中找出某个值第一个匹配项的索引位置
5list.insert(index, obj) 将对象插入列表
6list.pop([index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
7list.remove(obj) 移除列表中某个值的第一个匹配项
8list.reverse() 反向列表中元素
9list.sort( key=None, reverse=False) 对原列表进行排序
10list.clear() 清空列表
11list.copy() 复制列表
元组

tuple和list非常类似,但是tuple一旦初始化就不能修改,用圆括号比如同样是列出同学的名字:

>>> classmates = ('Michael', 'Bob', 'Tracy')

现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0]classmates[-1],但不能赋值成另外的元素。

不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。

tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:

>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的tuple,可以写成()

>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的tuple,如果你这么定义:

>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1

所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义

>>> t = (1,)
>>> t
(1,)

Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。

最后来看一个“可变的”tuple:

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个tuple定义的时候有3个元素,分别是'a''b'和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?

别急,我们先看看定义的时候tuple包含的3个元素:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkMDs65b-1677229279078)(https://www.liaoxuefeng.com/files/attachments/923973516787680/0)]

当我们把list的元素'A''B'修改为'X''Y'后,tuple变为:

tuple-1

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,如下实例:

\#!/usr/bin/python3  

tup1 = (12, 34.56) 
tup2 = ('abc', 'xyz')  # 以下修改元组元素操作是非法的。

# tup1[0] = 100 
# 创建一个新的元组 
tup3 = tup1 + tup2 
print (tup3)

元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组

Python 表达式结果描述
len((1, 2, 3))3计算元素个数
(1, 2, 3) + (4, 5, 6)(1, 2, 3, 4, 5, 6)连接
(‘Hi!’,) * 4(‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’)复制
3 in (1, 2, 3)True元素是否存在
for x in (1, 2, 3): print (x,)1 2 3迭代
序号方法及描述
1len(tuple) 计算元组元素个数。
2max(tuple) 返回元组中元素最大值。
3min(tuple) 返回元组中元素最小值。
4tuple(iterable) 将可迭代系列转换为元组。

list和tuple是Python内置的有序集合,一个可变,一个不可变。

条件判断

计算机之所以能做很多自动化的任务,因为它可以自己做条件判断。

比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:

age = 20
if age >= 18:
    print('your age is', age)
    print('adult')

根据Python的缩进规则,如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。

也可以给if添加一个else语句,意思是,如果if判断是False,不要执行if的内容,去把else执行了:

age = 3
if age >= 18:
    print('your age is', age)
    print('adult')
else:
    print('your age is', age)
    print('teenager')

注意不要少写了冒号:

当然上面的判断是很粗略的,完全可以用elif做更细致的判断:

age = 3
if age >= 18:
    print('adult')
elif age >= 6:
    print('teenager')
else:
    print('kid')

elifelse if的缩写,完全可以有多个elif,所以if语句的完整形式就是:

if <条件判断1>:
    <执行1>
elif <条件判断2>:
    <执行2>
else:
    <执行3>

if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elifelse,所以,请测试并解释为什么下面的程序打印的是teenager

age = 20
if age >= 6:
    print('teenager')
elif age >= 18:
    print('adult')
else:
    print('kid')

if判断条件还可以简写,比如写:

if x:
    print('True')

只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

  • 再议 input

最后看一个有问题的条件判断。很多同学会用input()读取用户的输入,这样可以自己输入,程序运行得更有意思:

birth = input('birth: ')
if birth < 2000:
    print('00前')
else:
    print('00后')

输入1982,结果报错:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()

这是因为**input()返回的数据类型是str**,str不能直接和整数比较,必须先把str转换成整数。Python提供了int()函数来完成这件事情:

s = input('birth: ')
birth = int(s)
if birth < 2000:
    print('00前')
else:
    print('00后')

再次运行,就可以得到正确地结果。但是,如果输入abc呢?又会得到一个错误信息:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

原来int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了。

循环

for 循环

Python的循环有两种,一种是for…in循环,依次把list或tuple中的每个元素迭代出来,看例子:

names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

执行这段代码,会依次打印names的每一个元素:

Michael # 输出
Bob
Tracy

所以for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。

再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:

sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    sum = sum + x
print(sum)

如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:

>>> list(range(5))
[0, 1, 2, 3, 4]

range(101)就可以生成0-100的整数序列,计算如下:

# -*- coding: utf-8 -*-
for x in range(101):
    sum = sum + x
print(sum)
while循环

第二种循环是while循环,**只要条件满足,就不断循环,条件不满足时退出循环。**比如我们要计算100以内所有奇数之和,可以用while循环实现:

sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

在循环内部变量n不断自减,直到变为-1时,不再满足while条件,循环退出。

break

在循环中,break语句可以提前退出循环。例如,本来要循环打印1~100的数字:

n = 1
while n <= 100:
    print(n)
    n = n + 1
print('END')

上面的代码可以打印出1~100。

如果要提前结束循环,可以用break语句:

n = 1
while n <= 100:
    if n > 10: # 当n = 11时,条件满足,执行break语句
        break # break语句会结束当前循环
    print(n)
    n = n + 1
print('END')

执行上面的代码可以看到,打印出1~10后,紧接着打印END,程序结束。

可见break的作用是提前结束循环。

continue

在循环过程中,也可以通过continue语句,跳过当前的这次循环,直接开始下一次循环。

n = 0
while n < 10:
    n = n + 1
    print(n)

上面的程序可以打印出1~10。但是,如果我们想只打印奇数,可以用continue语句跳过某些循环:

n = 0
while n < 10:
    n = n + 1
    if n % 2 == 0: # 如果n是偶数,执行continue语句
        continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
    print(n)

执行上面的代码可以看到,打印的不再是1~10,而是1,3,5,7,9。

可见continue的作用是提前结束本轮循环,并直接开始下一轮循环。

循环是让计算机做重复任务的有效的方法。

break语句可以在循环过程中直接退出循环,而continue语句可以提前结束本轮循环,并直接开始下一轮循环。这两个语句通常都必须配合if语句使用。

breakcontinue会造成代码执行逻辑分叉过多,容易出错。大多数循环并不需要用到breakcontinue语句,上面的两个例子,都可以通过改写循环条件或者修改循环逻辑,去掉breakcontinue语句。

可以用Ctrl+C退出程序,或者强制结束Python进程。

字典dict和set

dict

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。**字典使用花括号{}。**键必须是唯一的,但值则不必。

举个例子,假设要根据同学的名字查找对应的成绩,

如果用dict实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

这种key-value存储方式,在放进去的时候,必须根据key算出value的存放位置,这样,取的时候才能根据key直接拿到value。

把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:

>>> d['Adam'] = 67
>>> d['Adam']
67

由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:

>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

在字典中遍历时,关键字和对应的值可以使用 items() 方法同时解读出来:

\>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
\>>> for k, v in knights.items():
...      print(k, v)
...
gallahad the pure
robin the brave

如果key不存在,dict就会报错:

>>> d['Thomas']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Thomas'

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

>>> 'Thomas' in d
False

二是通过dict提供的**get()方法**,如果key不存在,可以返回None,或者自己指定的value:

>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

注意:返回None的时候Python的交互环境不显示结果。

删除一个key,用pop(key)方法,对应的value也会从dict中删除,del同理:

>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。

键必须不可变,所以可以用数字,字符串或元组充当,而用列表就不行

type(variable)
返回输入的变量类型,如果变量是字典就返回字典类型

和list比较,dict有以下几个特点:

  1. 查找和插入的速度极快,不会随着key的增加而变慢;
  2. 需要占用大量的内存,内存浪费多。

而list相反:

  1. 查找和插入的时间随着元素的增加而增加;
  2. 占用空间小,浪费内存很少。

所以,dict是用空间来换取时间的一种方法。

dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象

这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。

要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:

>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。

可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。

要创建一个set,需要提供一个list作为输入集合:

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。

重复元素在set中自动被过滤:

>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:

>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

可以添加元素,且参数可以是列表,元组,字典

通过remove(key)方法可以删除元素:

>>> s.remove(4)
>>> s
{1, 2, 3}

set 集合的 pop 方法会对集合进行无序的排列,然后将这个无序排列集合的左面第一个元素进行删除。

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。

再议不可变对象

上面我们讲了,str是不变对象,而list是可变对象。

对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:

>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象,比如str,对str进行操作呢:

>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

虽然字符串有个replace()方法,也确实变出了'Abc',但变量a最后仍是'abc',应该怎么理解呢?

我们先把代码改成下面这样:

>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

要始终牢记的是,a是变量,而'abc'才是字符串对象!有些时候,我们经常说,对象a的内容是'abc',但其实是指,a本身是一个变量,它指向的对象的内容才是'abc'

┌───┐                  ┌───────┐
│ a │─────────────────>│ 'abc' │
└───┘                  └───────┘

当我们调用a.replace('a', 'A')时,实际上调用方法replace是作用在字符串对象'abc'上的,而这个方法虽然名字叫replace,但却没有改变字符串'abc'的内容。相反,replace方法创建了一个新字符串'Abc'并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc',但变量b却指向新字符串'Abc'了:

┌───┐                  ┌───────┐
│ a │─────────────────>│ 'abc' │
└───┘                  └───────┘
┌───┐                  ┌───────┐
│ b │─────────────────>│ 'Abc' │
└───┘                  └───────┘

所以,对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。

使用key-value存储结构的dict在Python中非常有用,选择不可变对象作为key很重要,最常用的key是字符串。

tuple虽然是不变对象,但试试把(1, 2, 3)(1, [2, 3])放入dict或set中,并解释结果。

迭代器与生成器

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter()next()

字符串,列表或元组对象都可用于创建迭代器:

\>>> list=[1,2,3,4]
\>>> it = iter(list)   # 创建迭代器对象
\>>> **print** (next(it))  # 输出迭代器的下一个元素
1
\>>> **print** (next(it))
2
\>>>

迭代器对象可以使用常规for语句进行遍历:

#!/usr/bin/python3
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")

也可以使用 next() 函数:

import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()

函数

定义函数

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()
  • 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号 : 起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4D7A9yk2-1677229279082)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210813155630234.png)]

  • 语法

Python 定义函数使用 def 关键字,一般格式如下:

def 函数名(参数列表):
    函数体

默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的

让我们使用函数来输出"Hello World!":

\#!/usr/bin/python3

**def** hello() :
  **print**("Hello World!")

hello()

更复杂点的应用,函数中带上参数变量:

实例(Python 3.0+)

比较两个数,并返回较大的数:

\#!/usr/bin/python3  
def max(a, b):    
    if a > b:        
        return a    
    else:        
        return b  
a = 4 
b = 5 
print(max(a, b))

函数调用

可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行

如下实例调用了 printme() 函数:

实例(Python 3.0+)

\#!/usr/bin/python3  
# 定义函数 
def printme( str ):  # 打印任何传入的字符串   
    print (str)   
    return  
# 调用函数 
printme("我要调用用户自定义函数!") 
printme("再次调用同一函数")

参数传递

在 python 中,类型属于对象,变量是没有类型的:

a=[1,2,3]

a="Runoob"

以上代码中,[1,2,3] 是 List 类型,“Runoob” 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。

可更改(mutable)与不可更改(immutable)对象

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

  • **不可变类型:**变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
  • **可变类型:**变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

  • **不可变类型:**类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
  • **可变类型:**类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

python 传不可变对象实例

通过 id() 函数来查看内存地址变化:

实例(Python 3.0+)

def change(a): print(id(a)) # 指向的是同一个对象 a=10 print(id(a)) # 一个新对象 a=1 print(id(a)) change(a)

以上实例输出结果为:

4379369136
4379369136
4379369424

可以看见在调用函数前后,形参和实参指向的是同一个对象(对象 id 相同),在函数内部修改形参后,形参指向的是不同的 id。

传可变对象实例

可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。例如:

实例(Python 3.0+)

#!/usr/bin/python3 # 可写函数说明 def changeme( mylist ): “修改传入的列表” mylist.append([1,2,3,4]) print ("函数内取值: ", mylist) return # 调用changeme函数 mylist = [10,20,30] changeme( mylist ) print ("函数外取值: ", mylist)

传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:

函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
函数外取值:  [10, 20, 30, [1, 2, 3, 4]]

参数

以下是调用函数时可使用的正式参数类型:

  • 必需参数
  • 关键字参数
  • 默认参数
  • 不定长参数
必需参数

必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。

调用 printme() 函数,你必须传入一个参数,不然会出现语法错误:

实例(Python 3.0+)

#!/usr/bin/python3  
#可写函数说明 
def printme( str ):   
    "打印任何传入的字符串"   
    print (str)   
    return  
# 调用 printme 函数,不加参数会报错 
printme()
关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

以下实例在函数 printme() 调用时使用参数名:

#!/usr/bin/python3
 
#可写函数说明
def printinfo( name, age ):
   "打印任何传入的字符串"
   print ("名字: ", name)
   print ("年龄: ", age)
   return
 
#调用printinfo函数
printinfo( age=50, name="runoob" )
默认参数

调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:

\#!/usr/bin/python3  
#可写函数说明 
def printinfo( name, age = 35 ):   
    "打印任何传入的字符串"   
    print ("名字: ", name)   
    print ("年龄: ", age)   
    return  
#调用printinfo函数 
printinfo( age=50, name="runoob" ) 
print ("------------------------") 
printinfo( name="runoob" )

以上实例输出结果:

名字:  runoob
年龄:  50
------------------------
名字:  runoob
年龄:  35
不定长参数

你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:

def functionname([formal_args,] *var_args_tuple ):
   "函数_文档字符串"
   function_suite
   return [expression]

加了星号 ***** 的参数会以元组(tuple)的形式导入,加了两个星号 ** 的参数会以字典的形式导入,存放所有未命名的变量参数。

\#!/usr/bin/python3   
# 可写函数说明 
def printinfo( arg1, *vartuple ):   
    "打印任何传入的参数"   
    print ("输出: ")   
    print (arg1)   
    print (vartuple)  
    # 调用printinfo 函数 
    printinfo( 70, 60, 50 )

以上实例输出结果:

输出: 
70
(60, 50)

匿名函数

python 使用 lambda 来创建匿名函数。

所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。

  • lambda 只是一个表达式,函数体比 def 简单很多。
  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
  • lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
  • 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

语法

lambda 函数的语法只包含一个语句,如下:

lambda [arg1 [,arg2,.....argn]]:expression
\#!/usr/bin/python3  
# 可写函数说明 
sum = lambda arg1, arg2: arg1 + arg2  
#调用sum函数 
print ("相加后的值为 : ", sum( 10, 20 )) 
print ("相加后的值为 : ", sum( 20, 20 ))

return语句

return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。之前的例子都没有示范如何返回数值,以下实例演示了 return 语句的用法:

\#!/usr/bin/python3  
# 可写函数说明 
def sum( arg1, arg2 ):   
    # 返回2个参数的和."   
    total = arg1 + arg2   
    print ("函数内 : ", total)   
    return total  
# 调用sum函数 
total = sum( 10, 20 ) 
print ("函数外 : ", total)
函数内 :  30
函数外 :  30

全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

#!/usr/bin/python3
 
total = 0 # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
    #返回2个参数的和."
    total = arg1 + arg2 # total在这里是局部变量.
    print ("函数内是局部变量 : ", total)
    return total
 
#调用sum函数
sum( 10, 20 )
print ("函数外是全局变量 : ", total)
函数内是局部变量 :  30
函数外是全局变量 :  0

当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了。

#!/usr/bin/python3
 
num = 1
def fun1():
    global num  # 需要使用 global 关键字声明
    print(num) 
    num = 123
    print(num)
fun1()
print(num)

#以上实例输出结果:

1
123
123

如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,

#!/usr/bin/python3
 
def outer():
    num = 10
    def inner():
        nonlocal num   # nonlocal关键字声明
        num = 100
        print(num)
    inner()
    print(num)
outer()
# 以上实例输出结果:

100
100

模块

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。

在 python 用 import 或者 from…import 来导入相应的模块。

将整个模块(somemodule)导入,格式为: import somemodule

从某个模块中导入某个函数,格式为: from somemodule import somefunction

从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc

将某个模块中的全部函数导入,格式为: from somemodule import *

内置的函数 dir() 可以找到模块内定义的所有名称。以一个字符串列表的形式返回:

如果没有给定参数,那么 dir() 函数会罗列出当前定义的所有名称:

正则表达式 regex

字符类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-25Pt5XTq-1677229279084)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210810190719636.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rwCr3QC-1677229279085)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210810190809538.png)]

\d: 0-9的数字

\D:除0-9的数字外的任何字符

\w:任何字母、数字以及下划线

\W:除字母、数字以及下划线以外的任何字符

\s:空格、制表符或换行符

\S:除空格、制表符或换行符外的任何字符

\d{3}-\d{3}-\d{4}

查找与匹配过程

  • **re.compile()**传入字符串,表示正则表达式

    加上r将字符串标记为原始字符串

    import re
    phoneNumRegex=re.compile(r'\d{3}-\d{3}-\d{4}')
    mo=phoneNumRegex.search('My number is 415-555-4343.')
    print('Phone number found:'+mo.group())
    # 输出
    Phone number found:415-555-4343
    

search()返回March对象,group()返回被查找的字符串

添加括号分组 (\d{3})-(\d{3}-\d{4})

group()返回完整组,group(1)返回第一组,group(2)返回第二组

  phoneNumRegex=re.compile(r'(\d{3})-(\d{3}-\d{4})')
  mo=phoneNumRegex.search('My number is 415-555-4343.')
  print(mo.group())
  print(mo.group(1))
  print(mo.group(2))
  
  #输出
  415-555-4343
  415
  555-4343

管道|匹配多个分组 ‘Batman|Tina Fey’ 将匹配Batman或Tina Fey

**问号?**表示可选0次或1次 ()?

‘colou?r‘可以匹配 color 或者 colour

*匹配零次或多次 ()

'runoo*b’,可以匹配 runob、runoob、runoooooob 等

+匹配1次或多次 ()+,至少出现一次

‘runoo+b’,可以匹配 runoob、runooob、runoooooob 等

{}限定次数 (){3},(H){,5}表示匹配0到5次

贪心与非贪心匹配

***和 +限定符都是贪婪的,**因为它们会尽可能多的匹配文字,只有在它们的后面加上一个 ? 就可以实现非贪婪或最小匹配。

在HHHHH中,(H){3,5}匹配HHHHH,**(H){3,5}?**匹配HHH

**findall()**将找到所有匹配的地方,如表达式没有分组,返回匹配字符串的列表,有分组,则返回字符串的元组的列表

**通配字符.**匹配除换行字符外的所有字符,

  atRe=re.compile(r'.at')
  atRe.findall('The cat in the hat sat on the flat mat')
  >>>['cat','hat','sat','lat','mat']
  
  
方括号[]内,普通的正则表达式不会被解释。
[^aeiou] #非aeiou都可匹配
r'^Hello' #匹配必须发生在被查找文本的开始处
r'\d$' #匹配以数字结束

传入re.DOTALL作为re.compile()的第二个参数,匹配所有字符包括换行字符

newlineRe=re.compile('.*',re.DOTALL)

匹配不区分大小写,传入re.IGNORECASE或re.I作为re.compile()的第二个参数

替换字符串sub(new,old)

sub(pattern, repl, string, count=0, flags=0)
(1)pattern:该参数表示正则中的模式字符串;
(2)repl:该参数表示要替换的字符串(即匹配到pattern后替换为repl),也可以是个函数;
(3)string:该参数表示要被处理(查找替换)的原始字符串;
(4)count:可选参数,表示是要替换的最大次数,而且必须是非负整数,该参数默认为0,即所有的匹配都会被替换;
(5)flags:可选参数,表示编译时用的匹配模式(如忽略大小写、多行模式等),数字形式,默认为0

(1)(2)(3)是必要的

#coding="utf-8"
import re
res=" I’m 18 years old. Today is 2020/01/01."
rint(re.sub(r'[0-9]', '*', res))
#输出
" I’m ** years old.Today is ****/**/**."

忽略注释,re.VERBOSE作为re.compile()的第二个参数

# coding="utf-8"#识别中文

读写文件(os)

文件与文件路径

windows C:\,OSX 和Linux,根文件夹是/

附加卷,如DVD驱动器或USB,在windows系统上是D:\或E:\,在OSX上表示为新文件夹,在/Volumes文件夹下,在Linux上在/mnt上

import os
os.path.join('usr','bin','spam')
>>>'usr\\bin\\spam'

每个倒斜杠\需要由另一个倒斜杠转义

当前工作目录 os.getcwd(),更改当前工作目录 os.chdir(),.表示"这个目录"的缩写

绝对路径从根文件夹开始

相对路径是相对于程序的当前工作目录,开始处是可选的

创捷新文件夹os.makedirs(),包含完整路径名

os 模块提供了非常丰富的方法用来处理文件和目录

**os.path()**包含与文件名与文件路径相关的函数

序号方法及描述
1os.access(path, mode) 检验权限模式
2os.chdir(path) 改变当前工作目录
3os.chflags(path, flags) 设置路径的标记为数字标记。
4os.chmod(path, mode) 更改权限
5os.chown(path, uid, gid) 更改文件所有者
6os.chroot(path) 改变当前进程的根目录
7os.close(fd) 关闭文件描述符 fd
8os.closerange(fd_low, fd_high) 关闭所有文件描述符,从 fd_low (包含) 到 fd_high (不包含), 错误会忽略
9os.dup(fd) 复制文件描述符 fd
10os.dup2(fd, fd2) 将一个文件描述符 fd 复制到另一个 fd2
11os.fchdir(fd) 通过文件描述符改变当前工作目录
12os.fchmod(fd, mode) 改变一个文件的访问权限,该文件由参数fd指定,参数mode是Unix下的文件访问权限。
13os.fchown(fd, uid, gid) 修改一个文件的所有权,这个函数修改一个文件的用户ID和用户组ID,该文件由文件描述符fd指定。
14os.fdatasync(fd) 强制将文件写入磁盘,该文件由文件描述符fd指定,但是不强制更新文件的状态信息。
15[os.fdopen(fd, mode[, bufsize]]) 通过文件描述符 fd 创建一个文件对象,并返回这个文件对象
16os.fpathconf(fd, name) 返回一个打开的文件的系统配置信息。name为检索的系统配置的值,它也许是一个定义系统值的字符串,这些名字在很多标准中指定(POSIX.1, Unix 95, Unix 98, 和其它)。
17os.fstat(fd) 返回文件描述符fd的状态,像stat()。
18os.fstatvfs(fd) 返回包含文件描述符fd的文件的文件系统的信息,Python 3.3 相等于 statvfs()。
19os.fsync(fd) 强制将文件描述符为fd的文件写入硬盘。
20os.ftruncate(fd, length) 裁剪文件描述符fd对应的文件, 所以它最大不能超过文件大小。
21os.getcwd() 返回当前工作目录
22os.getcwdb() 返回一个当前工作目录的Unicode对象
23os.isatty(fd) 如果文件描述符fd是打开的,同时与tty(-like)设备相连,则返回true, 否则False。
24os.lchflags(path, flags) 设置路径的标记为数字标记,类似 chflags(),但是没有软链接
25os.lchmod(path, mode) 修改连接文件权限
26os.lchown(path, uid, gid) 更改文件所有者,类似 chown,但是不追踪链接。
27os.link(src, dst) 创建硬链接,名为参数 dst,指向参数 src
28os.listdir(path) 返回path指定的文件夹包含的文件或文件夹的名字的列表。
29os.lseek(fd, pos, how) 设置文件描述符 fd当前位置为pos, how方式修改: SEEK_SET 或者 0 设置从文件开始的计算的pos; SEEK_CUR或者 1 则从当前位置计算; os.SEEK_END或者2则从文件尾部开始. 在unix,Windows中有效
30os.lstat(path) 像stat(),但是没有软链接
31os.major(device) 从原始的设备号中提取设备major号码 (使用stat中的st_dev或者st_rdev field)。
32os.makedev(major, minor) 以major和minor设备号组成一个原始设备号
33[os.makedirs(path, mode]) 递归文件夹创建函数。像mkdir(), 但创建的所有intermediate-level文件夹需要包含子文件夹。
34os.minor(device) 从原始的设备号中提取设备minor号码 (使用stat中的st_dev或者st_rdev field )。
35[os.mkdir(path, mode]) 以数字mode的mode创建一个名为path的文件夹.默认的 mode 是 0777 (八进制)。
36[os.mkfifo(path, mode]) 创建命名管道,mode 为数字,默认为 0666 (八进制)
37[os.mknod(filename, mode=0600, device]) 创建一个名为filename文件系统节点(文件,设备特别文件或者命名pipe)。
38[os.open(file, flags, mode]) 打开一个文件,并且设置需要的打开选项,mode参数是可选的
39os.openpty() 打开一个新的伪终端对。返回 pty 和 tty的文件描述符。
40os.pathconf(path, name) 返回相关文件的系统配置信息。
41os.pipe() 创建一个管道. 返回一对文件描述符(r, w) 分别为读和写
42[os.popen(command, mode[, bufsize]]) 从一个 command 打开一个管道
43os.read(fd, n) 从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd对应文件已达到结尾, 返回一个空字符串。
44os.readlink(path) 返回软链接所指向的文件
45os.remove(path) 删除路径为path的文件。如果path 是一个文件夹,将抛出OSError; 查看下面的rmdir()删除一个 directory。
46os.removedirs(path) 递归删除目录。
47os.rename(src, dst) 重命名文件或目录,从 src 到 dst
48os.renames(old, new) 递归地对目录进行更名,也可以对文件进行更名。
49os.rmdir(path) 删除path指定的空目录,如果目录非空,则抛出一个OSError异常。
50os.stat(path) 获取path指定的路径的信息,功能等同于C API中的stat()系统调用。
51[os.stat_float_times(newvalue]) 决定stat_result是否以float对象显示时间戳
52os.statvfs(path) 获取指定路径的文件系统统计信息
53os.symlink(src, dst) 创建一个软链接
54os.tcgetpgrp(fd) 返回与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组
55os.tcsetpgrp(fd, pg) 设置与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组为pg。
59os.ttyname(fd) 返回一个字符串,它表示与文件描述符fd 关联的终端设备。如果fd 没有与终端设备关联,则引发一个异常。
60os.unlink(path) 删除文件路径
61os.utime(path, times) 返回指定的path文件的访问和修改的时间。
62[os.walk(top, topdown=True[, οnerrοr=None[, followlinks=False]]]) 输出在文件夹中的文件名通过在树中游走,向上或者向下。
63os.write(fd, str) 写入字符串到文件描述符 fd中. 返回实际写入的字符串长度
64os.path 模块 获取文件的属性信息。
65os.pardir() 获取当前目录的父目录,以字符串形式显示目录名。
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')

相对路径转为绝对路径os.path.abspath(path),os.path.isabs()判断绝对路径

os.path.relpath(path,start)返回从start路径到path路径的相对路径的字符串

C:\Windows\System32\cala.exe

os.path.basename('C:\\Windows\\System32\\cala.exe')
>>>'cala.exe'#返回基本名称,最后一个斜杠之后的内容

os.path.dirname('C:\\Windows\\System32\\cala.exe')
>>>'C:\\Windows\\System32'#返回目录名称

os.path.split('C:\\Windows\\System32\\cala.exe')
>>>('C:\\Windows\\System32','cala.exe')#返回目录名称与基本名称的元组

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:

part-1/part-2

而Windows下会返回这样的字符串:

part-1\part-2

os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便:

>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')

这些合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。

文件操作使用下面的函数。假定当前目录下有一个test.txt文件:

# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')

但是复制文件的函数居然在os模块中不存在!原因是复制文件并非由操作系统提供的系统调用。理论上讲,我们通过上一节的读写文件可以完成文件复制,只不过要多写很多代码。

幸运的是shutil模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。

最后看看如何利用Python的特性来过滤文件。比如我们要列出当前目录下的所有目录,只需要一行代码:

>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]

要列出所有的.py文件,也只需一行代码:

>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']

查看文件夹大小以及文件夹内容

os.path.getsize()返回文件夹的字节数

os.listdir()返回文件名字符串的列表

totalSize=0
for filename in os.listdir('C:\\Windows\\System32'):
                       totalSize=totalSize+os.path.getsize(os.path.join('C:\\Windows\\System32',filename))
print(totalSize)#这个目录下所有文件的总字节数
模式描述
t文本模式 (默认)。
x写模式,新建一个文件,如果该文件已存在则会报错。
b二进制模式。
+打开一个文件进行更新(可读可写)。
U通用换行模式(Python 3 不支持)。
r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+打开一个文件用于读写。文件指针将会放在文件的开头。
rb+以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

检查路径有效性

文件或文件夹存在 os.path.exists()返回ture

文件存在 os.path.isfile()返回ture

文件夹存在 os.path.isdir()返回ture

文件读写过程

  1. 调用open(),返回file对象,默认读模式

    helloFile=open('C:\\Users\\00308559\\hello.txt')#windows环境
    helloFile=open('/Users/00308559/hello.txt')#OS X环境
    
  2. f调用file对象的read()或write()

    #返回单个字符串
    helloContent=f.read()
    #返回字符串列表,每行都是一个字符串,除最后一行外都以\n结束
    helloContent=f.readlines()
    
  3. 调用file对象的close(),关闭该文件

    写入文件

    baconFile=open('bacon.txt','w')#写模式,从头开始覆写原有的文件没有此文件将会创建
    baconFile,write('Hello world\n')
    baconFile.close()
    baconFile=open('bacon.txt','a')#添加模式,已有文件的末尾添加文本
    baconFile,write('hi, girl')
    baconFile.close()
    

文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:

>>> f.close()

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现:

try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()

但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

with open('/path/to/file', 'r') as f:
    print(f.read())

调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。

# 打开一个文件
f = open("/tmp/foo.txt", "r")

str = f.readline()
print(str)

# 关闭打开的文件
f.close()
# 打开一个文件
f = open("/tmp/foo.txt", "r")

str = f.readlines()
print(str)

# 关闭打开的文件
f.close()

f = open("/tmp/foo.txt", "r")
for line in f:
    print(line, end='')
f.close()

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉

要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'

shelve模块

将变量保存为二进制shelf文件。

import shelve 
shelfFile=shelve.open('mydata')#windows生成.bak,.dat,.dir,OSX生成.db
cats=['sdfa','addw']
shelveFile['cats']=cats#键keys()和值values()
shelve.close()
shelfFile=shelve.open('mydata')#可读可写
shelveFile['cats']
shelve.close()

pprint.pformat()保存变量

pprint.pformat()提供一个字符串,可以写入.py文件,该文件可以成为自己的模块

import pprint
cats=[{'name':'Zophie','desc':'chubby'},{'desc':'fluffy','name':'pooka'}]
#字典不排序,items()返回键值对的元组
pprint.pformat(cats)
fileObj=open('myCats.py','w')#创建.py文件,成为模块
fileObj.write('cats='+pprint.pformat(cats)+'\n')#将变量存入模块
fileObj.close()
#调用模块
import myCats
myCats.cats
myCats.cats[0]['name']

环境变量

在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:

>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})

要获取某个环境变量的值,可以调用os.environ.get('key')

>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'

调试

抛出异常

使用 raise 语句抛出一个指定的异常。

停止运行这个函数的代码,转到except语句

raise Exception('This is the error message')

try和except语句调用该函数的代码

try 语句按照如下方式工作;

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。

一个 try 语句可能包含多个except子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。

一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:

except(RuntimeError, TypeError, NameError):
  pass
def boxPrint(symbol,width,height):
    if len(symbol) !=1:
        raise Exception('Symbol must be a single character string')
    if width <=2:
        raise Exception('Width must be greater than 2')
    if height <=2:
        raise Exception('Height must be greater than 2')
    print(symbol * width)
    for i in range(height-2):
        print(symbol+(' '*(width-2))+symbol)
    print(symbol * width)

try:
  boxPrint('%',3,5)
except Exception as err:#Exception对象传递给err
    print('An exception happened:'+str(err))#转换为字符串

try/except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。

else 子句将在 try 子句没有发生任何异常的时候执行。

image-20210812092622430

try-finally 语句无论是否发生异常都将执行最后的代码

image-20210812092836001

取得反向跟踪的字符串

抛出的异常没有被处理,就会显示反向跟踪,调用traceback.format_exc()得到字符串形式。

import traceback
try:
    raise Exception('This is the error message.')
except:
    errorFile=open('errorInfo.txt','w')
    errorFile.write(traceback.format_exc())#返回字符数
    errorFile.close()
    print('The traceback info was written to errorInfo.txt')

断言

检查代码,确保无明显错误,由assert语句执行。

**断言针对的是程序员的错误,而不是用户的错误。**对于可恢复的错误(如文件找不到,用户输入无效数据),请抛出异常。

podBayDoorStatus='open'
assert podBayDoorStatus=='open' ,'The pod bay doors need to be "open".'

确保podBayDoorStatus是’open’,才能按照期望工作.

禁用断言,运行时传入_O选项

日志logging

使用日志模块
import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s-%(levelname)s-%(message)s')

将此写在程序顶部。当Python记录日志时,他会创建一个LogRecord对象。

import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s-%(levelname)s-%(message)s')
logging.debug('Start of program')
'''%(levelno)s: 打印日志级别的数值
 %(levelname)s: 打印日志级别名称
 %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
 %(filename)s: 打印当前执行程序名
 %(funcName)s: 打印日志的当前函数
 %(lineno)d: 打印日志的当前行号
 %(asctime)s: 打印日志的时间
 %(thread)d: 打印线程ID
 %(threadName)s: 打印线程名称
 %(process)d: 打印进程ID
 %(message)s: 打印日志信息
'''
def factorial(n):
    logging.debug('Start of fractorial(%s)'%(n))#在字符串中使用%s占位,在字符串后使用%替换值来替换
    total=1
    for i in range(1,n+1):
        total*=i
        logging.debug('i is '+str(i)+',total is '+str(total))
    logging.debug('End of fractorial(%s)'%(n))
    return total
print(factorial(4))
logging.debug('End of program') 

image-20210810143536268
%%百分号标记 就是输出一个%
%c字符及其ASCII码
%d有符号整数(十进制)
%u无符号整数(十进制)
%e浮点数字(科学计数法)
%E浮点数字(科学计数法,用E代替e)
%f浮点数字(用小数点符号)
%g浮点数字(根据值的大小采用%e或%f)
%G浮点数字(类似于%g)
%n存储输出字符的数量放进参数列表的下一个变量中
日志级别

向basicConfig()传入logging.DEBUG(或INFO、WARNING、ERROR、CRITICAL)作为level关键参数

import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s-%(levelname)s-%(message)s')

DEBUG是最低级别,往上依次是INFO、WARNING、ERROR、CRITICAL,

禁用日志

向logging.disable()传入一个日志级别,将禁止该级别以及更低级别的所有日志消息。

将日志记录到文件
import logging
logging.basicConfig(filename='myProgramLog.txt',level=logging.DEBUG,format='%(asctime)s-%(levelname)s-%(message)s')

将日志信息写入文件,保持屏幕干净。

从Web抓取信息

webbrowser打开浏览器获取指定页面
request从网上下载文件以及网页
Beautiful Soup解析HTML,即网页编写的格式
selenium启动并控制一个Web浏览器

Webbrowser模块

import webbrowser
webbrowser.open('https://it.zte.com.cn/its/login/ssoLogin.action?rand=1628496490798')

request 模块

  • 下载网页

    调用type()返回Response对象,检查Response对象的status_code属性,了解对网页的请求是否成功。

    import requests
    res=requests.get('https://it.zte.com.cn/its/login/ssoLogin.action?rand=1628496490798')
    print(res)                # <eResponse [200]>
    print(type(res))          # <class 'requests.models.Response'>  
    print(res.status_code)    # 200,HTTP协议中“ok”的状态码是200
    

​ 当将单个参数传递给type()函数时,它将返回对象的类型。

  • 检查错误

    res.raise_for_status()
    

    下载出错就会抛出异常

将下载的文件保存到硬盘

可以用open()函数和write()方法,但必须用"写二进制"模式打开该文件,即将‘wb’作为open()的第二参数,目的是为了保存文本中的“Unicode”编码。

playFile=open('itZte.txt','wb')
for chunk in res.iter_content(10000):
    playFile.write(chunk)
playFile.close()

下载并保存到文件的步骤:
①调用requests.get()下载该文件
②用’wb’调用open(),以写二进制的方式打开一个新文件
③利用Respose对象的iter_content()方法循环
④在每次迭代中调用write(),将内容写入该文件
⑤调用close()关闭该文件

HTML

超文本标记语言HTML是编写Web页面的格式。HTML文件是纯文本文件,

<strong>hello</strong>world!

<strong>是标签,表明它包围的文本将使用粗体。

不要用正则表达式解析HTML。

BeautifulSoup模块解析HTML

导入bs4.

从HTML中创建BeautifulSoup对象

从网站上下载网页

import requests, bs4
res=requests.get('https://hrportal.zte.com.cn/HRPortal/UI/index.aspx')
res.raise_for_status()
hrSoup=bs4.BeautifulSoup(res.text)#响应的texts属性传给bs4.BeautifulSoup,返回对象保存在变量hrSoup
type(hrSoup)

或者打开一个html文档

exampleFile=open('example.html')
exampleSoup=bs4.BeautifulSoup(exampleFile)
type(exampleSoup)
select()寻找元素

调用method()方法,传入一个字符串作为CSS选择器。select()返回一个Tag对象的列表,Tag值可以传递给str()函数,显示HTML标签。

从BeautifulSoup对象中找出

元素

pElems=exampleSoup.select('p')
str(pElems[0])# 输出带有<p>的第一个字符串
pElems[0].getText()# 输出不含标签文本内容
通过元素属性获取数据

元素属性

element.clear():清除文本。
element.send_keys(value):输入文字或键盘按键(需导入Keys模块)。
element.click():单击元素。
element.get_attribute(name):获得属性值
element.is_displayed():返回元素结果是否可见(True 或 False)
element.is_selected():返回元素结果是否被选中(True 或 False)
id 获取元素的标示
size 获取元素的宽与高,返回一个字典
rect 除了获取元素的宽与高,还获取元素的坐标
tag_name 获取元素的标签名称
text 获取元素的文本内容

pElems[0].get('id')

selenium 模块

  • 启动浏览器

    from selenium import webdriver
    driver = webdriver.Chrome()
    driver.get('https://www.baidu.com/') 
    
  • 寻找元素

    • find_element_ by_tag_name()返回一个WebElement对象

    • find_elements_ 返回WebElement_对象的列表,包含所有匹配元素

    • find_element(By.TAG_NAME, “input”)

      #selenium 元素查找find_element_by_id方法,找到元素后输入信息 
      driver.find_element_by_id('kw').send_keys('selenium') 
      
      #find_element(By.," ")
      driver.find_element(By.ID, "kw")
      driver.find_elements(By.TAG_NAME, "input")
      
      driver.quit()#退出
      
      
  • _by_tag_name()区分大小写,如无匹配元素,就抛出NoSuchElement异常,需添加try和except语句。

    driver.implicitly_wait(30) # 隐性等待,最长等30秒
    time.sleep(3) # 引入time 模块,强制等待3秒再执行下一步

    from selenium import webdriver
    driver = webdriver.Chrome()
    
    driver.get('https://www.baidu.com/')
    try:
        elem=driver.find_element_by_class_name('kw')
        print('Found <%s> element with that class name.'%\
              (elem.tag.name))
    except:
        print('Was not able to find an element with that name.')
        
    
  • 点击页面

    #selenium 元素查找find_element_by_id方法,找到元素后进行点击
    driver.find_element_by_id('su').click() 
    
  • 填写表单

    找到文本字段的或元素,然后调用send_keys()

    elem=driver.find_element_by_id('kw')#输入框标识
    elem.send_keys('selenium') #输入内容
    elem.submit()# 提交
    
  • 发送特殊键

    # 删除多输入的一个 m
    driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
    
    # 输入空格键+“教程”
    driver.find_element_by_id("kw").send_keys(Keys.SPACE)
    
    # ctrl+a 全选输入框内容
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'a')
    
    # ctrl+x 剪切输入框内容
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'x')
    
    # ctrl+v 粘贴内容到输入框
    driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'v')
    
    # 通过回车键来代替单击操作
    driver.find_element_by_id("su").send_keys(Keys.ENTER)
    
    send_keys(Keys.TAB) # 制表键(Tab)
    send_keys(Keys.ESCAPE) # 回退键(Esc)
    send_keys(Keys.F1) # 键盘 F1
    send_keys(Keys.F12) # 键盘 F12
    
  • 点击浏览器按钮

    driver.back()点击“返回”按钮。
    driver.forward()点击“前进”按钮。
    driver.refresh()点击“刷新”按钮。
    driver.quit()点击“关闭窗口”按钮。

CSV文件与JSON数据

CSV是简化的电子表格,保存为纯文本文件。JSON是一种格式,以JavaScript源代码的形式,将信息保存在纯文本文件中。

CSV模块

CSV文件中每行代表电子表格的一行,逗号分割了该行的单元格。CSV文件中所有东西都是字符串。并非每个逗号,都表示单元格的分界。

Reader 对象

csv.reader()

#coding="utf-8"
import csv
exampleFile=open('example.csv')
exampleReader=csv.reader(exampleFile)#open()返回的对象传递给csv.reader()
exampleData=list(exampleReader)#在Reader对象上应用list(),将返回列表的列表

#输出
[['number', 'symbol', 'time'], ['1', 'beibei', '2014'], ['2', 'jinjin', '2015'], ['3', 'nini', '2016']]

文件太大,可用for循环

#coding="utf-8"
import csv
exampleFile=open('example.csv')
exampleReader=csv.reader(exampleFile)
for row in exampleReader:
    print('Row #'+str(exampleReader.line_num)+' '+str(row))
 
# 输出
Row #1 ['number', 'symbol', 'time']
Row #2 ['1', 'beibei', '2014']
Row #3 ['2', 'jinjin', '2015']
Row #4 ['3', 'nini', '2016']
Writer 对象

调用open()传入’w’,以写模式打开文件并创建对象,然后传递给csv.writer(),创建Writer对象。需要为open()函数的newline关键字参数传入空字符串,否则生成的文件表格行距将有2倍。

outputFile=open('output.csv','w',newline='')
outputWriter=csv.writer(outputFile)
outputWriter.writerow(['bei,bei','jinjin','huanhuan','yinyin','nini'])
outputWriter.writerow(['2008','2009','2010','2011','2012'])

#输出
"bei,bei",jinjin,huanhuan,yinyin,nini# Writer对象自动转义了'bei,bei'的逗号,在csv文件中使用双引号
2008,2009,2010,2011,2012
delimiter和lineteminator

默认情况下,CSV文件中的分隔符是逗号,行终止字符是行末的字符,默认是换行符。利用csv.writer()的delimiter和lineterminator关键字参数,将这些字符改成不同的值。

csvWriter=csv.writer(csvFile,delimiter='\t',lineterminator='\n\n')#单元格之间的字符变为制表符,\行之间的字符变为两个换行符

单元格是由制表符分隔的,就使用文件扩展名.tsv.

从CSV文件中删除表头

  • os.makedirs()循环遍历csv文件
  • 创建Reader对象,读取文件内容,用line_num确定跳过哪一行
  • 创建Writer对象,将读入的数据写入新文件

JSON和API

JSON: JavaScript Object Notation(JavaScript 对象表示法)

“应用程序编程接口”——API:

  • 从网站抓取原始数据
  • 自动从一个社交网络账户下载新帖子,并发布到另一个账户
  • 从维基百科等提取数据,放到计算机的一个文本文件中
JSON语法
  • 数据在名称/值对中

    JSON 数据的书写格式是:

    key : value
    

    名称/值对包括字段名称(在双引号中),后面写一个冒号,然后是值:

    “name” : “菜鸟教程”

    这很容易理解,等价于这条 JavaScript 语句:

    name = “菜鸟教程”

  • 数据由逗号分隔

    JSON 值可以是:

    • 数字(整数或浮点数)
    • 字符串(在双引号中)
    • 逻辑值(true 或 false)
    • 数组(在中括号中)
    • 对象(在大括号中)
    • null

    JSON 数字可以是整型或者浮点型:

    { “age”:30 }

  • 大括号 {} 保存对象

    {key1 : value1, key2 : value2, ... keyN : valueN }
    

    对象可以包含多个名称/值对:

    { "name":"菜鸟教程" , "url":"www.runoob.com" }
    

    这一点也容易理解,与这条 JavaScript 语句等价:

    name = "菜鸟教程" url = "www.runoob.com"
    
  • 中括号 [] 保存数组,数组可以包含多个对象

    数组可包含多个对象:

    [
        { key1 : value1-1 , key2:value1-2 }, 
        { key1 : value2-1 , key2:value2-2 }, 
        { key1 : value3-1 , key2:value3-2 }, 
        ...
        { keyN : valueN-1 , keyN:valueN-2 }, 
    ]
    
    {   
        "sites": [
            { "name":"菜鸟教程" , "url":"www.runoob.com" }, 
            { "name":"google" , "url":"www.google.com" },  
            { "name":"微博" , "url":"www.weibo.com" }    
        ]
    }
    

    在上面的例子中,对象 sites 是包含三个对象的数组。每个对象代表一条关于某个网站(name、url)的记录。

    JSON 文件

    • JSON 文件的文件类型是 .json
    • JSON 文本的 MIME 类型是 application/json
JSON模块
  • json.dumps(): 对数据进行编码。
  • json.loads(): 对数据进行解码
image-20210812105331813
PythonJSON
dictobject
list, tuplearray
strstring
int, float, int- & float-derived Enumsnumber
Truetrue
Falsefalse
Nonenull

repr() 函数将对象转化为供解释器读取的形式,返回一个对象的 string 格式。

# coding="utf-8"
import json

# Python 字典类型转换为 JSON 对象
data = {
    'no': 1,
    'name': 'Runoob',
    'url': 'http://www.runoob.com'
}

json_str = json.dumps(data)
print("Python 原始数据:", repr(data))
print("JSON 对象:", json_str)

# 将 JSON 对象转换为 Python 字典
data2 = json.loads(json_str)
print ("data2['name']: ", data2['name'])
print ("data2['url']: ", data2['url'])

如果你要处理的是文件而不是字符串,你可以使用 json.dump()json.load() 来编码和解码JSON数据。

保持时间、计划任务和启动程序

time模块

读取系统时钟的时间。

time.time()

Unix纪元:1970年1月1日0点,即协调世界时(UTC).time.time()返回自Unix纪元以来的秒数,是一个浮点值。可用来测量一段代码运行的时间。

# coding="utf-8"
import time

print("time.time(): %f " %  time.time())
print (time.localtime( time.time() ))
print (time.asctime( time.localtime(time.time()) ))

# 输出
time.time(): 1628738999.768378 
time.struct_time(tm_year=2021, tm_mon=8, tm_mday=12, tm_hour=11, tm_min=29, tm_sec=59, tm_wday=3, tm_yday=224, tm_isdst=0)# 时间元组
Thu Aug 12 11:29:59 2021
序号时间元组属性
0tm_year2008
1tm_mon1 到 12
2tm_mday1 到 31
3tm_hour0 到 23
4tm_min0 到 59
5tm_sec0 到 61 (60或61 是闰秒)
6tm_wday0到6 (0是周一)
7tm_yday1 到 366(儒略历)
8tm_isdst-1, 0, 1, -1是决定是否为夏令时的旗帜

struct_time元组

strptime()

根据指定的格式把一个时间字符串解析为时间元组。

  time.strptime(string[, format]) 

string – 时间字符串。format – 格式化字符串。返回struct_time对象。

struct_time = time.strptime("30 Nov 00", "%d %b %y")
print(struct_time)

# 输出
time.struct_time(tm_year=2000, tm_mon=11, tm_mday=30, 
tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=335, tm_isdst=-1)

%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00-59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为 0,星期一为 1,以此类推。
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

strftime()

函数接收以时间元组,并返回以可读字符串表示的当地时间,格式由参数 format 决定。

time.strftime(format[, t])

返回以可读字符串表示的当地时间。

time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))

#返回
2021-08-12 14:09:27
     
date = input("输入时间(格式如:2017-04-04):")
t = time.strptime(date,"%Y-%m-%d")
print(time.strftime("今年的第%j天",t))
time.sleep()

推迟调用线程的运行,可通过参数secs指秒数,表示进程挂起的时间。

time.sleep(t)

​ t–推迟的秒数

数字四舍五入

round() 方法返回浮点数x的四舍五入值。

round( x , n  )
  • x – 数值表达式。
  • n – 数值表达式,表示从小数点位数。
print(round(43672.9276635,3))

# 输出
43672.928

datetime 模块

datetime是Python处理日期和时间的标准库。

from datetime import datetime
>>> now = datetime.now() # 获取当前datetime
>>> print(now)
2015-05-18 16:28:07.198690
>>> print(type(now))
<class 'datetime.datetime'>

注意到datetime是模块,datetime模块还包含一个datetime类,通过from datetime import datetime导入的才是datetime这个类。

如果仅导入import datetime,则必须引用全名datetime.datetime

datetime.now()返回当前日期和时间,其类型是datetime

要指定某个日期和时间,我们直接用参数构造一个datetime

>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> print(dt)
2015-04-19 12:20:00

我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。

把一个datetime类型转换为timestamp只需要简单调用timestamp()方法:

>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> dt.timestamp() # 把datetime转换为timestamp
1429417200.0

注意Python的timestamp是一个浮点数,整数位表示秒。

要把timestamp转换为datetime,使用datetime提供的fromtimestamp()方法:

>>> from datetime import datetime
>>> t = 1429417200.0
>>> print(datetime.fromtimestamp(t))
2015-04-19 12:20:00

多线程(threading)

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理。
  • 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
  • 程序的运行速度可能加快。
  • 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

  • 线程可以被抢占(中断)。
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让。

线程可以分为:

  • **内核线程:**由操作系统内核创建和撤销。
  • **用户线程:**不需要内核支持而在用户程序中实现的线程。

Python3 线程中常用的两个模块为:

  • _thread
  • threading(推荐使用)

thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread" 模块。为了兼容性,Python3 将 thread 重命名为 “_thread”。

线程模块

Python3 通过两个标准库 _thread 和 threading 提供对线程的支持。

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

  • run(): 用以表示线程活动的方法。
  • start(): 启动线程活动。
  • join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。
创建线程
\#!/usr/bin/python3

import threading
import time

exitFlag = 0

class myThread (threading.Thread):
  def __init__(self, threadID, name, counter):
    threading.Thread.__init__(self)
    self.threadID = threadID
    self.name = name
    self.counter = counter
  def run(self):
    print ("开始线程:" + self.name)
    print_time(self.name, self.counter, 5)
    print ("退出线程:" + self.name)

def print_time(threadName, delay, counter):
  while counter:
    if exitFlag:
      threadName.exit()
    time.sleep(delay)
    print ("%s: %s" % (threadName, time.ctime(time.time())))
    counter -= 1

\# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

\# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")
开始线程:Thread-1
开始线程:Thread-2
Thread-1: Wed Apr  6 11:46:46 2016
Thread-1: Wed Apr  6 11:46:47 2016
Thread-2: Wed Apr  6 11:46:47 2016
Thread-1: Wed Apr  6 11:46:48 2016
Thread-1: Wed Apr  6 11:46:49 2016
Thread-2: Wed Apr  6 11:46:49 2016
Thread-1: Wed Apr  6 11:46:50 2016
退出线程:Thread-1
Thread-2: Wed Apr  6 11:46:51 2016
Thread-2: Wed Apr  6 11:46:53 2016
Thread-2: Wed Apr  6 11:46:55 2016
退出线程:Thread-2
退出主线程
线程同步

使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。

考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。

那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。

\#!/usr/bin/python3

**import** threading
**import** time

**class** myThread (threading.Thread):
  **def** __init__(self, threadID, name, counter):
    threading.Thread.__init__(self)
    self.threadID = threadID
    self.name = name
    self.counter = counter
  **def** run(self):
    **print** ("开启线程: " + self.name)
    \# 获取锁,用于线程同步
    threadLock.acquire()
    print_time(self.name, self.counter, 3)
    \# 释放锁,开启下一个线程
    threadLock.release()

**def** print_time(threadName, delay, counter):
  **while** counter:
    time.sleep(delay)
    **print** ("%s: %s" % (threadName, time.ctime(time.time())))
    counter -= 1

threadLock = threading.Lock()
threads = []

\# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

\# 开启新线程
thread1.start()
thread2.start()

\# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

\# 等待所有线程完成
**for** t **in** threads:
  t.join()
**print** ("退出主线程")
线程优先级队列(queue)

Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。

这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

Queue 模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作
#!/usr/bin/python3

import queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print ("开启线程:" + self.name)
        process_data(self.name, self.q)
        print ("退出线程:" + self.name)

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print ("%s processing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")
开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出线程:Thread-3
退出线程:Thread-2
退出线程:Thread-1
退出主线程

面向对象

以一个例子来说明面向过程和面向对象在程序流程上的不同之处。

假设我们要处理学生的成绩表,为了表示一个学生的成绩,面向过程的程序可以用一个dict表示:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

而处理学生成绩可以通过函数实现,比如打印学生的成绩:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % /
              (self.name, self.score))

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • **方法:**类中定义的函数。
  • **类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • **数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • **方法重写:**如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • **局部变量:**定义在方法中的变量,只作用于当前实例的类。
  • **实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
  • **继承:**即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • **实例化:**创建一个类的实例,类的具体对象。
  • **对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

和其它编程语言相比,Python 在尽可能不增加新的语法和语义的情况下加入了类机制。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

数据封装、继承和多态是面向对象的三大特点

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

仍以Student类为例,在Python中,定义类是通过class关键字:

class Student(object):
    pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

类对象支持两种操作:属性引用和实例化。

属性引用使用和 Python 中所有的属性引用一样的标准语法:obj.name

类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:

class MyClass:
    """一个简单的类实例"""
    i = 12345
    def f(self):
        return 'hello world'
 
# 实例化类
x = MyClass()
 
# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())

以上创建了一个新的类实例并将该对象赋给局部变量 x,x 为空的对象。

执行以上程序输出结果为:

MyClass 类的属性 i 为: 12345
MyClass 类的方法 f 输出为: hello world
#类定义
class people:
    #定义基本属性
    name = ''
    age = 0
    #定义私有属性,私有属性在类外部无法直接进行访问
    __weight = 0
    #定义构造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 说: 我 %d 岁。" %(self.name,self.age))
 
# 实例化类
p = people('runoob',10,30)
p.speak()

#执行以上程序输出结果为:

runoob 说:10 岁。

类有一个名为特殊的__init__方法(构造方法),该方法在类实例化时会自动调用,像下面这样:

def __init__(self):
    self.data = []

类定义了 的__init__方法,类的实例化操作会自动调用的__init__方法。如下实例化类 MyClass,对应的__init__方法就会被调用:

x = MyClass()

当然, __init__ 方法可以有参数,参数通过__init__传递到类的实例化操作上。例如:

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)   # 输出结果:3.0 -4.5

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()

以上实例执行结果为:

<__main__.Test instance at 0x100771878>
__main__.Test

从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。

数据封装

面向对象编程的一个重要特点就是数据封装。Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))
        
bart = Student('Bart Simpson', 59)
bart.print_score()
Bart Simpson: 59

这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

封装的另一个好处是可以给Student类增加新的方法,比如get_grade

class Student(object):
    ...

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'
        
        
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

访问限制

从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

class Animal(object):
    def run(self):
        print('Animal is running...')

当我们需要编写DogCat类时,就可以直接从Animal类继承:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。CatDog类似。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,DogCat作为它的子类,什么事也没干,就自动拥有了run()方法:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

运行结果如下:

Animal is running...
Animal is running...

也可以对子类增加一些方法,比如Dog类:

class Dog(Animal):

    def run(self):
        print('Dog is running...')

    def eat(self):
        print('Eating meat...')

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

看来abc确实对应着listAnimalDog这3种类型。

isinstance(c, Animal)
True

看来c不仅仅是Dogc还是Animal

不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

>>> b = Animal()
>>> isinstance(b, Dog)
False

Dog可以看成Animal,但Animal不可以看成Dog

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal):
    animal.run()
    animal.run()

当我们传入Dog的实例时,run_twice()就打印出:

>>> run_twice(Dog())
Dog is running...
Dog is running...

看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

当我们调用run_twice()时,传入Tortoise的实例:

>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入DogCatTortoise……时,我们只需要接收Animal类型就可以了,因为DogCatTortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在AnimalDogCat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:

获取对象信息

使用type()

首先,我们来判断对象类型,使用type()函数:

基本类型都可以用type()判断:

>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

我们回顾上次的例子,如果继承关系是:

object -> Animal -> Dog -> Husky

那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

然后,判断:

>>> isinstance(h, Husky)
True

没有问题,因为h变量指向的就是Husky对象。

再判断:

>>> isinstance(h, Dog)
True

h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

因此,我们可以确信,h还是Animal类型:

>>> isinstance(h, Animal)
True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

​ 我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

>>> class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)
100

单元测试 unittest

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

比如对函数abs(),我们可以编写出以下几个测试用例:

  1. 输入正数,比如11.20.99,期待返回值与输入相同;
  2. 输入负数,比如-1-1.2-0.99,期待返回值与输入相反;
  3. 输入0,期待返回0
  4. 输入非数值类型,比如None[]{},期待抛出TypeError

把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。

如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。

单元测试通过后有什么意义呢?如果我们对abs()函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。

Python标准库中的模块unittest 提供了代码测试工具。单元测试 用于核实函数的某个方面没有问题;测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

我们来编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,用起来就像下面这样:

>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a
1

mydict.py代码如下:

class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

为了编写单元测试,我们需要引入Python自带的unittest模块,编写mydict_test.py如下:

import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。

test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual()

self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等

另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的key时,断言会抛出KeyError

with self.assertRaises(KeyError):
    value = d['empty']

而通过d.empty访问不存在的key时,我们期待抛出AttributeError

with self.assertRaises(AttributeError):
    value = d.empty

一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在mydict_test.py的最后加上两行代码:

if __name__ == '__main__':
    unittest.main()

另一种方法是在命令行通过参数-m unittest直接运行单元测试:

$ python -m unittest mydict_test
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

可以在单元测试中编写两个特殊的setUp()tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。

setUp()tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:

class TestDict(unittest.TestCase):

    def setUp(self):
        print('setUp...')

    def tearDown(self):
        print('tearDown...')

可以再次运行测试看看每个测试方法调用前后是否会打印出setUp...tearDown...

img

unittest case的运行流程:

  • 写好一个完整的TestCase
  • 多个TestCase 由TestLoder被加载到TestSuite里面, TestSuite也可以嵌套TestSuite
  • 由TextTestRunner来执行TestSuite,测试的结果保存在TextTestResult中
  • TestFixture指的是环境准备和恢复

IO编程

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。

stringIO和BytesIO

stringIO

StringIO顾名思义就是在内存中读写str。

要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

getvalue()方法用于获得写入后的str。

要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!
BytesIO

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。

BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:

>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

请注意,写入的不是str,而是经过UTF-8编码的bytes。

和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

序列化pickle

把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling.序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

首先,我们尝试把一个对象序列化并写入文件:

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object:

>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

看看写入的dump.txt文件,一堆乱七八糟的内容,这些都是Python保存的对象内部信息。

>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

collections

namedtuple

tuple可以表示不变集合,例如,一个点的二维坐标就可以表示成:

>>> p = (1, 2)

但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。

定义一个class又小题大做了,这时,namedtuple就派上了用场:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2

namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。

这样一来,我们用namedtuple可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用,使用十分方便。

类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:

# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])

deque

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。

deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])

deque除了实现list的append()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

defaultdict

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict

>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'

注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入。

除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。

OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。

如果要保持Key的顺序,可以用OrderedDict

>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是无序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:

>>> od = OrderedDict()
>>> od['z'] = 1
>>> od['y'] = 2
>>> od['x'] = 3
>>> list(od.keys()) # 按照插入的Key的顺序返回
['z', 'y', 'x']

OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key:

from collections import OrderedDict

class LastUpdatedOrderedDict(OrderedDict):

    def __init__(self, capacity):
        super(LastUpdatedOrderedDict, self).__init__()
        self._capacity = capacity

    def __setitem__(self, key, value):
        containsKey = 1 if key in self else 0
        if len(self) - containsKey >= self._capacity:
            last = self.popitem(last=False)
            print('remove:', last)
        if containsKey:
            del self[key]
            print('set:', (key, value))
        else:
            print('add:', (key, value))
        OrderedDict.__setitem__(self, key, value)

ChainMap

ChainMap可以把一组dict串起来并组成一个逻辑上的dictChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。

什么时候使用ChainMap最合适?举个例子:应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。我们可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有,就使用默认参数。

下面的代码演示了如何查找usercolor这两个参数:

from collections import ChainMap
import os, argparse

# 构造缺省参数:
defaults = {
    'color': 'red',
    'user': 'guest'
}

# 构造命令行参数:
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = { k: v for k, v in vars(namespace).items() if v }

# 组合成ChainMap:
combined = ChainMap(command_line_args, os.environ, defaults)

# 打印参数:
print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

没有任何参数时,打印出默认参数:

$ python3 use_chainmap.py 
color=red
user=guest

当传入命令行参数时,优先使用命令行参数:

$ python3 use_chainmap.py -u bob
color=red
user=bob

同时传入命令行参数和环境变量,命令行参数的优先级较高:

$ user=admin color=green python3 use_chainmap.py -u bob
color=green
user=bob

argparse

argparse 是python自带的命令行参数解析包,可以用来方便地读取命令行参数。

import argparse

def main():
    parser = argparse.ArgumentParser(description="Demo of argparse")
    parser.add_argument('-n','--name', default=' Li ')
    parser.add_argument('-y','--year', default='20')
    args = parser.parse_args()
    print(args)
    name = args.name
    year = args.year
    print('Hello {}  {}'.format(name,year))

if __name__ == '__main__':
    main()

执行命令python fun_test.py结果如下:

Namespace(name=' Li ', year='20')
Hello  Li   20

在上面的代码中,我们先导入了argparse这个包,然后包中的ArgumentParser类生成一个parser对象(好多博客中把这个叫做参数解析器),其中的description描述这个参数解析器是干什么的,当我们在命令行显示帮助信息的时候会看到description描述的信息。
接着我们通过对象的add_argument函数来增加参数。这里我们增加了两个参数name和year,其中’-n’,‘–name’表示同一个参数,default参数表示我们在运行命令时若没有提供参数,程序会将此值当做参数值。执行结果如上图所示。
最后采用对象的parse_args获取解析的参数,由上图可以看到,Namespace中有两个属性(也叫成员)这里要注意个问题,当’-‘和’–'同时出现的时候,系统默认后者为参数名,前者不是,但是在命令行输入的时候没有这个区分接下来就是打印参数信息了。
当执行命令python fun_test.py -n Wang --year '26’结果如下

Namespace(name=' Wang ', year='26')
Hello  Wang   26

Counter

Counter是一个简单的计数器,例如,统计字符出现的个数:

>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
...     c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
>>> c.update('hello') # 也可以一次性update
>>> c
Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})

Counter实际上也是dict的一个子类,上面的结果可以看出每个字符出现的次数。

struct

Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。

structpack函数把任意数据类型变成bytes

>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'

pack的第一个参数是处理指令,'>I'的意思是:

>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。

后面的参数个数要和处理指令一致。

unpackbytes变成相应的数据类型:

>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)

根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。

操作网页urllib

Python urllib 库用于操作网页 URL,并对网页的内容进行抓取处理。

本文主要介绍 Python3 的 urllib。

urllib 包 包含以下几个模块:

  • urllib.request - 打开和读取 URL。
  • urllib.error - 包含 urllib.request 抛出的异常。
  • urllib.parse - 解析 URL。
  • urllib.robotparser - 解析 robots.txt 文件。
img

urllib.request

urllib.request 定义了一些打开 URL 的函数和类,包含授权验证、重定向、浏览器 cookies等。

urllib.request 可以模拟浏览器的一个请求发起过程。

我们可以使用 urllib.request 的 urlopen 方法来打开一个 URL,语法格式如下:

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
  • url:url 地址。
  • data:发送到服务器的其他数据对象,默认为 None。
  • timeout:设置访问超时时间。
  • cafile 和 capath:cafile 为 CA 证书, capath 为 CA 证书的路径,使用 HTTPS 需要用到。
  • cadefault:已经被弃用。
  • context:ssl.SSLContext类型,用来指定 SSL 设置。
from urllib.request import urlopen

myURL = urlopen("https://www.runoob.com/")
print(myURL.read(300))


以上代码使用 urlopen 打开一个 URL,然后使用 read() 函数获取网页的 HTML 实体代码。read() 是读取整个网页内容,我们可以指定读取的长度

在对网页进行抓取时,经常需要判断网页是否可以正常访问,这里我们就可以使用 getcode() 函数获取网页状态码,返回 200 说明网页正常,返回 404 说明网页不存在:

import urllib.request

myURL1 = urllib.request.urlopen("https://www.runoob.com/")
print(myURL1.getcode())   # 200

try:
    myURL2 = urllib.request.urlopen("https://www.runoob.com/no.html")
except urllib.error.HTTPError as e:
    if e.code == 404:
        print(404)   # 404

抓取网页保持到本地

from urllib.request import urlopen

myURL = urlopen("https://www.runoob.com/")
f = open("runoob_urllib_test.html", "wb")
content = myURL.read()  # 读取网页内容
f.write(content)
f.close()
##执行以上代码,在本地就会生成一个 runoob_urllib_test.html 文件,里面包含了 https://www.runoob.com/ 网页的内容。

URL 的编码与解码可以使用 urllib.request.quote()urllib.request.unquote() 方法:

import urllib.request

encode_url = urllib.request.quote("https://www.runoob.com/")  # 编码
print(encode_url)

unencode_url = urllib.request.unquote(encode_url)    # 解码
print(unencode_url)
https%3A//www.runoob.com/
https://www.runoob.com/

模拟头部信息

我们抓取网页一般需要对 headers(网页头信息)进行模拟,这时候需要使用到 urllib.request.Request 类:

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
  • url:url 地址。
  • data:发送到服务器的其他数据对象,默认为 None。
  • headers:HTTP 请求的头部信息,字典格式。
  • origin_req_host:请求的主机地址,IP 或域名。
  • unverifiable:很少用整个参数,用于设置网页是否需要验证,默认是False。。
  • method:请求方法, 如 GET、POST、DELETE、PUT等。
import urllib.request
import urllib.parse

url = 'https://www.runoob.com/?s='  # 菜鸟教程搜索页面
keyword = 'Python 教程'
key_code = urllib.request.quote(keyword)  # 对请求进行编码
url_all = url+key_code
header = {
    'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}   #头部信息
request = urllib.request.Request(url_all,headers=header)
reponse = urllib.request.urlopen(request).read()

fh = open("./urllib_test_runoob_search.html","wb")    # 将文件写入到当前目录中
fh.write(reponse)
fh.close()

img

post

如果要以POST发送一个请求,只需要把参数data以bytes形式传入。

表单 POST 传递数据,我们先创建一个表单,代码如下,我这里使用了 PHP 代码来获取表单的数据:

<html>
<head>
<meta charset=“utf-8”>
<title>菜鸟教程(runoob.com) urllib POST 测试</title>
</head>
<body>
<form action=“” method=“post” name=“myForm”>
Name: <input type=“text” name=“name”><br>
Tag: <input type=“text” name=“tag”><br>
<input type=“submit” value=“提交”>
</form>
<hr>

<?php // 使用 PHP 来获取表单提交的数据,你可以换成其他的 if(isset($_POST['name']) && $_POST['tag'] ) { echo $_POST["name"] . ', ' . $_POST['tag']; } ?>

</body>
</html>

import urllib.request
import urllib.parse

url = 'https://www.runoob.com/try/py3/py3_urllib_test.php' # 提交到表单页面
data = {'name':'RUNOOB', 'tag' : '菜鸟教程'}  # 提交数据
header = {
  'User-Agent':'Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}  #头部信息
data = urllib.parse.urlencode(data).encode('utf8') # 对参数进行编码,解码使用 urllib.parse.urldecode
request=urllib.request.Request(url, data, header)  # 请求处理
reponse=urllib.request.urlopen(request).read()    # 读取结果

fh = open("./urllib_test_post_runoob.html","wb")   # 将文件写入到当前目录中
fh.write(reponse)
fh.close()

打开 urllib_test_post_runoob.html 文件(可以使用浏览器打开),显示结果如下:

img

urllib.error

urllib.error 模块为 urllib.request 所引发的异常定义了异常类,基础异常类是 URLError。

urllib.error 包含了两个方法,URLError 和 HTTPError。

URLError 是 OSError 的一个子类,用于处理程序在遇到问题时会引发此异常(或其派生的异常),包含的属性 reason 为引发异常的原因。

HTTPError 是 URLError 的一个子类,用于处理特殊 HTTP 错误例如作为认证请求的时候,包含的属性 code 为 HTTP 的状态码, reason 为引发异常的原因,headers 为导致 HTTPError 的特定 HTTP 请求的 HTTP 响应头。

import urllib.request
import urllib.error

myURL1 = urllib.request.urlopen("https://www.runoob.com/")
print(myURL1.getcode())   # 200

try:
    myURL2 = urllib.request.urlopen("https://www.runoob.com/no.html")
except urllib.error.HTTPError as e:
    if e.code == 404:
        print(404)   # 404

urllib.parse

urllib.parse 用于解析 URL,格式如下:

urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)

urlstring 为 字符串的 url 地址,scheme 为协议类型,

allow_fragments 参数为 false,则无法识别片段标识符。相反,它们被解析为路径,参数或查询组件的一部分,并 fragment 在返回值中设置为空字符串。

from urllib.parse import urlparse

o = urlparse("https://www.runoob.com/?s=python+%E6%95%99%E7%A8%8B")
print(o)

从结果可以看出,内容是一个元组,包含 6 个字符串:协议,位置,路径,参数,查询,判断。

属性索引值(如果不存在)
scheme0URL协议scheme 参数
netloc1网络位置部分空字符串
path2分层路径空字符串
params3最后路径元素的参数空字符串
query4查询组件空字符串
fragment5片段识别空字符串
username用户名None
password密码None
hostname主机名(小写)None
port端口号为整数(如果存在)None

urllib.robotparser

urllib.robotparser 用于解析 robots.txt 文件。

robots.txt(统一小写)是一种存放于网站根目录下的 robots 协议,它通常用于告诉搜索引擎对网站的抓取规则。

urllib.robotparser 提供了 RobotFileParser 类,语法如下:

class urllib.robotparser.RobotFileParser(url='')

这个类提供了一些可以读取、解析 robots.txt 文件的方法:

  • set_url(url) - 设置 robots.txt 文件的 URL。
  • read() - 读取 robots.txt URL 并将其输入解析器。
  • parse(lines) - 解析行参数。
  • can_fetch(useragent, url) - 如果允许 useragent 按照被解析 robots.txt 文件中的规则来获取 url 则返回 True。
  • mtime() -返回最近一次获取 robots.txt 文件的时间。 这适用于需要定期检查 robots.txt 文件更新情况的长时间运行的网页爬虫。
  • modified() - 将最近一次获取 robots.txt 文件的时间设置为当前时间。
  • crawl_delay(useragent) -为指定的 useragent 从 robots.txt 返回 Crawl-delay 形参。 如果此形参不存在或不适用于指定的 useragent 或者此形参的 robots.txt 条目存在语法错误,则返回 None。
  • request_rate(useragent) -以 named tuple RequestRate(requests, seconds) 的形式从 robots.txt 返回 Request-rate 形参的内容。 如果此形参不存在或不适用于指定的 useragent 或者此形参的 robots.txt 条目存在语法错误,则返回 None。
  • site_maps() - 以 list() 的形式从 robots.txt 返回 Sitemap 形参的内容。 如果此形参不存在或者此形参的 robots.txt 条目存在语法错误,则返回 None。
>>> import urllib.robotparser
>>> rp = urllib.robotparser.RobotFileParser()
>>> rp.set_url("http://www.musi-cal.com/robots.txt")
>>> rp.read()
>>> rrate = rp.request_rate("*")
>>> rrate.requests
3
>>> rrate.seconds
20
>>> rp.crawl_delay("*")
6
>>> rp.can_fetch("*", "http://www.musi-cal.com/cgi-bin/search?city=San+Francisco")
False
>>> rp.can_fetch("*", "http://www.musi-cal.com/")
True

get

urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:

例如,对豆瓣的一个URLhttps://api.douban.com/v2/book/2129650进行抓取,并返回响应:

from urllib import request

with request.urlopen('https://api.douban.com/v2/book/2129650') as f:
    data = f.read()
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', data.decode('utf-8'))
Status: 200 OK
Server: nginx
Date: Tue, 26 May 2015 10:02:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2049
Connection: close
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
X-DAE-Node: pidl1
Data: {"rating":{"max":10,"numRaters":16,"average":"7.4","min":0},"subtitle":"","author":["廖雪峰编著"],"pubdate":"2007-6",...}

模拟浏览器发送GET请求

import urllib.request
response = urllib.request.urlopen('https://it.zte.com.cn/its/login/ssoLogin.action?rand=1628496490798')
print(response.read().decode('utf-8'))'''

Handler

如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:

proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
    pass

urllib提供的功能就是利用程序去执行各种HTTP请求。如果要模拟浏览器完成特定功能,需要把请求伪装成浏览器。伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头来伪装,User-Agent头就是用来标识浏览器的。

XML

操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。

正常情况下,优先考虑SAX,因为DOM实在太占内存。

在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_elementend_elementchar_data,准备好这3个函数,然后就可以解析xml了。

举个例子,当SAX解析器读到一个节点时:

<a href="/">python</a>

会产生3个事件:

  1. start_element事件,在读取<a href="/">时;
  2. char_data事件,在读取python时;
  3. end_element事件,在读取</a>时。

用代码实验一下:

from xml.parsers.expat import ParserCreate

class DefaultSaxHandler(object):
    def start_element(self, name, attrs):
        print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))

    def end_element(self, name):
        print('sax:end_element: %s' % name)

    def char_data(self, text):
        print('sax:char_data: %s' % text)

xml = r'''<?xml version="1.0"?>
<ol>
    <li><a href="/python">Python</a></li>
    <li><a href="/ruby">Ruby</a></li>
</ol>
'''

handler = DefaultSaxHandler()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
parser.CharacterDataHandler = handler.char_data
parser.Parse(xml)

需要注意的是读取一大段字符串时,CharacterDataHandler可能被多次调用,所以需要自己保存起来,在EndElementHandler里面再合并。

除了解析XML外,如何生成XML呢?99%的情况下需要生成的XML结构都是非常简单的,因此,最简单也是最有效的生成XML的方法是拼接字符串:

L = []
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
return ''.join(L)

HTMLParser

如果我们要编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。

HTML本质上是XML的子集,但是HTML的语法没有XML那么严格

from html.parser import HTMLParser
from html.entities import name2codepoint

class MyHTMLParser(HTMLParser):

    def handle_starttag(self, tag, attrs):
        print('<%s>' % tag)

    def handle_endtag(self, tag):
        print('</%s>' % tag)

    def handle_startendtag(self, tag, attrs):
        print('<%s/>' % tag)

    def handle_data(self, data):
        print(data)

    def handle_comment(self, data):
        print('<!--', data, '-->')

    def handle_entityref(self, name):
        print('&%s;' % name)

    def handle_charref(self, name):
        print('&#%s;' % name)

parser = MyHTMLParser()
parser.feed('''<html>
<head></head>
<body>
<!-- test html parser -->
    <p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>''')

feed()方法可以多次调用,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。

特殊字符有两种,一种是英文表示的 ,一种是数字表示的Ӓ,这两种字符都可以通过Parser解析出来。

chardet

当我们拿到一个bytes时,就可以对其检测编码。用chardet检测编码,只需要一行代码:

>>> chardet.detect(b'Hello, world!')
{'encoding': 'ascii', 'confidence': 1.0, 'language': ''}

检测出的编码是ascii,注意到还有个confidence字段,表示检测的概率是1.0(即100%)。

我们来试试检测GBK编码的中文:

>>> data = '离离原上草,一岁一枯荣'.encode('gbk')
>>> chardet.detect(data)
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}

检测的编码是GB2312,注意到GBK是GB2312的超集,两者是同一种编码,检测正确的概率是74%,language字段指出的语言是'Chinese'

对UTF-8编码进行检测:

>>> data = '离离原上草,一岁一枯荣'.encode('utf-8')
>>> chardet.detect(data)
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}

我们再试试对日文进行检测:

>>> data = '最新の主要ニュース'.encode('euc-jp')
>>> chardet.detect(data)
{'encoding': 'EUC-JP', 'confidence': 0.99, 'language': 'Japanese'}

可见,用chardet检测编码,使用简单。获取到编码后,再转换为str,就可以方便后续处理。

psutil

psutil = process and system utilities,它不仅可以通过一两行代码实现系统监控,还可以跨平台使用,支持Linux/UNIX/OSX/Windows等,是系统管理员和运维小伙伴不可或缺的必备模块。

获取CPU信息

我们先来获取CPU的信息:

>>> import psutil
>>> psutil.cpu_count() # CPU逻辑数量
4
>>> psutil.cpu_count(logical=False) # CPU物理核心
2
# 2说明是双核超线程, 4则是4核非超线程

统计CPU的用户/系统/空闲时间:

>>> psutil.cpu_times()
scputimes(user=10963.31, nice=0.0, system=5138.67, idle=356102.45)

再实现类似top命令的CPU使用率,每秒刷新一次,累计10次:

>>> for x in range(10):
...     print(psutil.cpu_percent(interval=1, percpu=True))
... 
[14.0, 4.0, 4.0, 4.0]
[12.0, 3.0, 4.0, 3.0]
[8.0, 4.0, 3.0, 4.0]
[12.0, 3.0, 3.0, 3.0]
[18.8, 5.1, 5.9, 5.0]
[10.9, 5.0, 4.0, 3.0]
[12.0, 5.0, 4.0, 5.0]
[15.0, 5.0, 4.0, 4.0]
[19.0, 5.0, 5.0, 4.0]
[9.0, 3.0, 2.0, 3.0]

获取内存信息

使用psutil获取物理内存和交换内存信息,分别使用

psutil.virtual_memory()
svmem(total=8589934592, available=2866520064, percent=66.6, used=7201386496, free=216178688, active=3342192640, inactive=2650341376, wired=1208852480)
>>> psutil.swap_memory()
sswap(total=1073741824, used=150732800, free=923009024, percent=14.0, sin=10705981440, sout=40353792)

返回的是字节为单位的整数,可以看到,总内存大小是8589934592 = 8 GB,已用7201386496 = 6.7 GB,使用了66.6%。

而交换区大小是1073741824 = 1 GB。

profiler

CPU Profile

line_profiler

profile的结果需要展示某行代码对CPU和memory的影响,最终帮助我们优化代码。。

import line_profiler
# @profile
def aaa():
    a = [1] * 100
    b = [x * x * x for x in a]
    c = [x for x in b]
    d = c * 2

profile = line_profiler.LineProfiler(aaa)  # 把函数传递到性能分析器
profile.enable()  # 开始分析
aaa()
profile.disable()  # 停止分析
profile.print_stats()

Line:代码行号
Hits:表示每行代码运行的次数
Time:每行代码运行的总时间
Per Hits:每行代码运行一次的时间
% Time:每行代码运行时间的百分比

Total time: 4.89e-05 s
File: D:/study/python/test/profile_study.py
Function: aaa at line 4

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     4                                           def aaa():
     5         1         48.0     48.0      9.8      a = [1] * 100
     6         1        271.0    271.0     55.4      b = [x * x * x for x in a]
     7         1        142.0    142.0     29.0      c = [x for x in b]
     8         1         28.0     28.0      5.7      d = c * 2
cprofiler

问题描述

1、Python开发的程序在使用过程中很慢,想确定下是哪段代码比较慢;

2、Python开发的程序在使用过程中占用内存很大,想确定下是哪段代码引起的;

cProfile是一种确定性分析器,只测量CPU时间,并不关心内存消耗和其他与内存相关联的信息。

Profile(custom_timer=None, time_unit=0.0, subcalls=True, builtins=True)
custom_timer:是一个自定义参数,可以通过与默认函数不同的方式测量时间。
如果custom_timer返回的是一个整数,time_unit是单位时间换成秒数。

返回方法
enable():开始收集性能分析数据。
disable():停止收集性能分析数据。
create_stats():停止收集数据,并为已经收集数据创建stats对象。
print_stats(sort=-1):创建一个stats对象,打印结果。
dump_stats(filename):把当前性能分析的内容写入一个文件。
run(command):和之前的一样。
runctx(command, golabls, locals):和以前一样。
runcall(func, *args, **kwargs):收集被调用函数func的性能分析信息。

from cProfile import Profile

def runRe():
    import re
    re.compile("aaa|bbb")

prof = Profile()
prof.enable()
runRe()
prof.create_stats()
prof.print_stats()

8651 function calls (8539 primitive calls) in 0.019 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:1017(_handle_fromlist)
       19    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:103(release)
       19    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:143(__init__)
       19    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:147(__enter__)
       19    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:151(__exit__)

第一行:189个函数调用被监控,其中184个是原生调用(不涉及递归)
ncalls:函数被调用的次数。如果这一列有两个值,就表示有递归调用,第二个值是原生调用次数,第一个值是总调用次数。
tottime:函数内部消耗的总时间。(可以帮助优化)
percall:是tottime除以ncalls,一个函数每次调用平均消耗时间。
cumtime:之前所有子函数消费时间的累计和。
filename:lineno(function):被分析函数所在文件名、行号、函数名。

Memory Profiler

from memory_profiler import profile

在需要在需要分析的

给出了某行的内存的增量

Line #    Mem usage    Increment  Occurences   Line Contents
2     47.3 MiB     47.3 MiB           1   @profile
 3                                         def run():
 4     47.3 MiB      0.0 MiB           1       a = [1] * 100
 5     47.3 MiB      0.0 MiB         103       b = [x * x * x for x in a]
 6     47.3 MiB      0.0 MiB         103       c = [x for x in b]
 7     47.3 MiB      0.0 MiB           1       d = c * 2

pytest

pytest与unittest区别

一、用例编写规则

1.unittest提供了test cases、test suites、test fixtures、test runner相关的类,让测试更加明确、方便、可控。使用unittest编写用例,必须遵守以下规则:

(1)测试文件必须先import unittest

(2)测试类必须继承unittest.TestCase

(3)测试方法必须以“test_”开头

(4)测试类必须要有unittest.main()方法

2.pytest是python的第三方测试框架,是基于unittest的扩展框架,比unittest更简洁,更高效。使用pytest编写用例,必须遵守以下规则:

​ (1)测试文件名必须以“test_”开头或者"_test"结尾(如:test_ab.py)

(2)测试方法必须以“test_”开头。

(3)测试类命名以"Test"开头。

总结: pytest可以执行unittest风格的测试用例,无须修改unittest用例的任何代码,有较好的兼容性。 pytest插件丰富,比如flask插件,可用于用例出错重跑;还有xdist插件,可用于设备并行执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GsugLRFr-1677229279100)(http://aliyunzixunbucket.oss-cn-beijing.aliyuncs.com/jpg/fd24da68be72f455dcb53a2af81cc70e.jpg?x-oss-process=image/resize,p_100/auto-orient,1/quality,q_90/format,jpg/watermark,image_eXVuY2VzaGk=,t_100,g_se,x_0,y_0)]

二、用例前置和后置

1.unittest提供了setUp/tearDown,每个用例运行前、结束后运行一次。setUpClass和tearDownClass,用例执行前、结束后,只运行一次。

 2.pytest提供了模块级、函数级、类级、方法级的setup/teardown,比unittest的setUp/tearDown更灵活。
  • 模块级(setup_module/teardown_module)开始于模块始末,全局的
  • 函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
  • 类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
  • 方法级(setup_method/teardown_method)开始于方法始末(在类中)
  • 类里面的(setup/teardown)运行在调用方法的前后
 pytest还可以在函数前加@pytest.fixture()装饰器,在测试用例中装在fixture函数。fixture的使用范围可以是function,module,class,session。
 firture相对于setup和teardown来说有以下几点优势:
  • 命名方式灵活,不局限于setup和teardown这几个命名
  • conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置,可供多个py文件调用。
  • scope=“module” 可以实现多个.py跨文件共享前置
  • scope=“session” 以实现多个.py跨文件使用一个session来完成多个用例
  • 用yield来唤醒teardown的执行

三、断言

1.unittest提供了assertEqual、assertIn、assertTrue、assertFalse。

2.pytest直接使用assert 表达式。

四、报告

1.unittest使用HTMLTestRunnerNew库。

2.pytest有pytest-HTML、allure插件。

五、失败重跑

1、unittest无此功能。

2、pytest支持用例执行失败重跑,pytest-rerunfailures插件。

六、参数化

1、unittest需依赖ddt库,

2、pytest直接使用@pytest.mark.parametrize装饰器。

七、用例分类执行

1、unittest默认执行全部用例,也可以通过加载testsuit,执行部分用例。

2、pytest可以通过@pytest.mark来标记类和方法,pytest.main加入参数(“-m”)可以只运行标记的类和方法。

import pytest

@pytest.fixture(scope='function')
def setup_function(request):
    def teardown_function():
        print("teardown_function called.")
    request.addfinalizer(teardown_function)  # 此内嵌函数做teardown工作
    print('setup_function called.')

@pytest.fixture(scope='module')
def setup_module(request):
    def teardown_module():
        print("teardown_module called.")
    request.addfinalizer(teardown_module)
    print('setup_module called.')

@pytest.mark.website
def test_1(setup_function):
    print('Test_1 called.')

def test_2(setup_module):
    print('Test_2 called.')

def test_3(setup_module):
    print('Test_3 called.')
    assert 2==1+1

0827汇报补充

set( ) 不可变对象

strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

image-20210830112908279

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CAb6MGi1-1677229279101)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830112928212.png)]

列表传入set()之后,列表的操作与set()无关

list.extend() 与list.append()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eoAJXYVW-1677229279103)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830113028328.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-giNOoyL8-1677229279104)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830113111400.png)]

__file__与name

__file__

变量__file__表示文件本身,输出的是一个绝对路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hyQX7uAD-1677229279104)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830135657998.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Nq7auTY-1677229279105)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830135720657.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bgkgj2w8-1677229279106)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830135727693.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbyvzBVy-1677229279106)(C:\Users\00308559\AppData\Roaming\Typora\typora-user-images\image-20210830135733397.png)]

img

现在想要在web2/bin.py中调用web3/main.py模块中的方法

在pycharm中执行与在cmd中执行的结果不同(在cmd中找不到项目目录web1的路径),这是因为pycharm会自动将项目的中路径写入环境变量python中.

所以如果想要使得在任何环境下代码都可执行的话,只需手动将项目的路径导入环境变量path中即可,这就需要用到__file__了

import sys
import os
DIR_NAME=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(DIR_NAME)
sys.path.append(DIR_NAME)
__name__是python的一个内置类属性,它存储模块的名称。
python的模块既可以被调用,也可以独立运行。而被调用时__name__存储的是py文件名(模块名称),独立运行时存储的是"__main__"。
那么它的作用主要就是用来区分,当前模块是独立运行还是被调用。

自己的 __name__ 在自己用时就是__main__,当自己作为模块被调用时就是自己的名字
import requests
class requests(object):
    def __init__(self,url):
        self.url=url
        self.result=self.getHTMLText(self.url)
    def getHTMLText(url):
        try:
            r=requests.get(url,timeout=30)
            r.raise_for_status()
            r.encoding=r.apparent_encoding
            return r.text
        except:
            return "This is a error."
print(__name__)
##输出
__main__

当这个 pcRequests.py 作为模块被调用时,则它的 name 就是它自己的名字:

import pcRequestspcRequestsc=pcRequestsc.__name__
#输出
'pcRequests'

sort(key=None,reverse=False)

l=['we','rt','gh']
    l.sort(key=lambda x:(str(x[1])))
    # l.sort(key=str.lower)
    print(l)
 
#输出
['we', 'gh', 'rt']

(1) key参数
key接受的是一个只有一个形参的函数

key接受的函数返回值,表示此元素的权值,sort将按照权值大小进行排序

(2) reverse参数
reverse接受的是一个bool类型的值 (Ture or False),表示是否颠倒排列顺序,一般默认的是False,注意第一个字母是大写的

l=['aa','bbxd','dsa','gywuiq']
def compare(a):
    return len(a)
l.sort(key=compare)

#输出
['aa', 'dsa', 'bbxd', 'gywuiq']

lambda与def的区别

1.lambda通常是用来在python中创建匿名函数的,而用def创建的方法是有名称的
2.lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,需要主动将这个函数对象赋值给一个变量,而def则会把函数对象赋值给一个变量,也就是函数名称了。
3.lambda它只是一个表达式,而def则是一个语句。

语句与表达式:

1、语句是使用关键字来组成命令,告诉解释器完成某个任务的命令。

2、语句可有输出,也可以没输出。

3、表达式没有关键字,可以是由数学操作符组成的算术表达式,也可以是括号调用的函数。

lambda表达式在“:”后只能有一个表达式。在def中,用return可以返回的一般来说也可以直接放在lambda后面,不能用return返回的也一般来说也不能定义在python lambda后面。因此,像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。

对于一些只需要使用一次的函数,用lambda来定义,可以省去函数命名问题

这里写图片描述

模块与包

模块:.py文件

包:将有联系的模块组织到一起,放到同一个文件夹下,有效避免模块名称冲突问题,让应用组织结构更清晰

每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录(文件夹),而不是一个包

假设我们的abcxyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名:只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突

HTTP异常处理

1.URLError

首先解释下URLError可能产生的原因:

  • 网络无连接,即本机无法上网
  • 连接不到特定的服务器
  • 服务器不存在
 from urllib.request import Request, urlopen
 from urllib.error import URLError

    req = Request(r"https:/www.jb51.net /")
    try:
        response = urlopen(req)
    except URLError as e:
        if hasattr(e, 'reason'):
            print('We failed to reach a server.')
            print('Reason: ', e.reason)
        elif hasattr(e, 'code'):
            print(r"The server couldn't fulfill the request.")
            print('Error code: ', e.code)
        else:
            print("good!")
            print(response.read().decode("utf8"))
#输出
We failed to reach a server.
Reason:  no host given

hasattr() 函数用于判断对象是否包含对应的属性。如果对象有该属性返回 True,否则返回 False。

2.HTTPError

HTTPError是URLError的子类,在你利用urlopen方法发出一个请求时,服务器上都会对应一个应答对象response,其中它包含一个数字”状态码”。

HTTPError的父类是URLError,父类的异常应当写到子类异常的后面,如果子类捕获不到,那么可以捕获父类的异常

  • 100:继续 客户端应当继续发送请求。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。
  • 101: 转换协议 在发送完这个响应最后的空行后,服务器将会切换到在Upgrade 消息头中定义的那些协议。只有在切换新的协议更有好处的时候才应该采取类似措施。
  • 102:继续处理 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
  • 200:请求成功 处理方式:获得响应的内容,进行处理
  • 201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到 处理方式:爬虫中不会遇到
  • 202:请求被接受,但处理尚未完成 处理方式:阻塞等待
  • 204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。 处理方式:丢弃
  • 300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃
  • 301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 处理方式:重定向到分配的URL
  • 302:请求到的资源在一个不同的URL处临时保存 处理方式:重定向到临时的URL
  • 304:请求的资源未更新 处理方式:丢弃
  • 400:非法请求 处理方式:丢弃
  • 401:未授权 处理方式:丢弃
  • 403:禁止 处理方式:丢弃
  • 404:没有找到 处理方式:丢弃
  • 500:服务器内部错误 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
  • 501:服务器无法识别 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
  • 502:错误网关 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
  • 503:服务出错 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
 from urllib.request import Request, urlopen
 from urllib.error import URLError, HTTPError

    req = Request(r"https:/www.jb51.net/")
    try:
        response = urlopen(req)
    except HTTPError as e:
        print("The server couldn't fulfill the request.")
        print('Error code: ', e.code)
    except URLError as e:
        print('We failed to reach a server.')
        print('Reason: ', e.reason)
    else:
        print("good!")
        print(response.read().decode("utf8"))
        
#输出
We failed to reach a server.
Reason:  no host given

img-2Nq7auTY-1677229279105)]

[外链图片转存中…(img-Bgkgj2w8-1677229279106)]

[外链图片转存中…(img-TbyvzBVy-1677229279106)]

[外链图片转存中…(img-faHso9g2-1677229279107)]

现在想要在web2/bin.py中调用web3/main.py模块中的方法

在pycharm中执行与在cmd中执行的结果不同(在cmd中找不到项目目录web1的路径),这是因为pycharm会自动将项目的中路径写入环境变量python中.

所以如果想要使得在任何环境下代码都可执行的话,只需手动将项目的路径导入环境变量path中即可,这就需要用到__file__了

import sys
import os
DIR_NAME=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print(DIR_NAME)
sys.path.append(DIR_NAME)
__name__是python的一个内置类属性,它存储模块的名称。
python的模块既可以被调用,也可以独立运行。而被调用时__name__存储的是py文件名(模块名称),独立运行时存储的是"__main__"。
那么它的作用主要就是用来区分,当前模块是独立运行还是被调用。

自己的 __name__ 在自己用时就是__main__,当自己作为模块被调用时就是自己的名字
import requests
class requests(object):
    def __init__(self,url):
        self.url=url
        self.result=self.getHTMLText(self.url)
    def getHTMLText(url):
        try:
            r=requests.get(url,timeout=30)
            r.raise_for_status()
            r.encoding=r.apparent_encoding
            return r.text
        except:
            return "This is a error."
print(__name__)
##输出
__main__

当这个 pcRequests.py 作为模块被调用时,则它的 name 就是它自己的名字:

import pcRequestspcRequestsc=pcRequestsc.__name__
#输出
'pcRequests'

sort(key=None,reverse=False)

l=['we','rt','gh']
    l.sort(key=lambda x:(str(x[1])))
    # l.sort(key=str.lower)
    print(l)
 
#输出
['we', 'gh', 'rt']

(1) key参数
key接受的是一个只有一个形参的函数

key接受的函数返回值,表示此元素的权值,sort将按照权值大小进行排序

(2) reverse参数
reverse接受的是一个bool类型的值 (Ture or False),表示是否颠倒排列顺序,一般默认的是False,注意第一个字母是大写的

l=['aa','bbxd','dsa','gywuiq']
def compare(a):
    return len(a)
l.sort(key=compare)

#输出
['aa', 'dsa', 'bbxd', 'gywuiq']

lambda与def的区别

1.lambda通常是用来在python中创建匿名函数的,而用def创建的方法是有名称的
2.lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,需要主动将这个函数对象赋值给一个变量,而def则会把函数对象赋值给一个变量,也就是函数名称了。
3.lambda它只是一个表达式,而def则是一个语句。

语句与表达式:

1、语句是使用关键字来组成命令,告诉解释器完成某个任务的命令。

2、语句可有输出,也可以没输出。

3、表达式没有关键字,可以是由数学操作符组成的算术表达式,也可以是括号调用的函数。

lambda表达式在“:”后只能有一个表达式。在def中,用return可以返回的一般来说也可以直接放在lambda后面,不能用return返回的也一般来说也不能定义在python lambda后面。因此,像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。

对于一些只需要使用一次的函数,用lambda来定义,可以省去函数命名问题

这里写图片描述

模块与包

模块:.py文件

包:将有联系的模块组织到一起,放到同一个文件夹下,有效避免模块名称冲突问题,让应用组织结构更清晰

每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录(文件夹),而不是一个包

假设我们的abcxyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名:只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突

HTTP异常处理

1.URLError

首先解释下URLError可能产生的原因:

  • 网络无连接,即本机无法上网
  • 连接不到特定的服务器
  • 服务器不存在
 from urllib.request import Request, urlopen
 from urllib.error import URLError

    req = Request(r"https:/www.jb51.net /")
    try:
        response = urlopen(req)
    except URLError as e:
        if hasattr(e, 'reason'):
            print('We failed to reach a server.')
            print('Reason: ', e.reason)
        elif hasattr(e, 'code'):
            print(r"The server couldn't fulfill the request.")
            print('Error code: ', e.code)
        else:
            print("good!")
            print(response.read().decode("utf8"))
#输出
We failed to reach a server.
Reason:  no host given

hasattr() 函数用于判断对象是否包含对应的属性。如果对象有该属性返回 True,否则返回 False。

2.HTTPError

HTTPError是URLError的子类,在你利用urlopen方法发出一个请求时,服务器上都会对应一个应答对象response,其中它包含一个数字”状态码”。

HTTPError的父类是URLError,父类的异常应当写到子类异常的后面,如果子类捕获不到,那么可以捕获父类的异常

  • 100:继续 客户端应当继续发送请求。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。
  • 101: 转换协议 在发送完这个响应最后的空行后,服务器将会切换到在Upgrade 消息头中定义的那些协议。只有在切换新的协议更有好处的时候才应该采取类似措施。
  • 102:继续处理 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
  • 200:请求成功 处理方式:获得响应的内容,进行处理
  • 201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到 处理方式:爬虫中不会遇到
  • 202:请求被接受,但处理尚未完成 处理方式:阻塞等待
  • 204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。 处理方式:丢弃
  • 300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。 处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃
  • 301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源 处理方式:重定向到分配的URL
  • 302:请求到的资源在一个不同的URL处临时保存 处理方式:重定向到临时的URL
  • 304:请求的资源未更新 处理方式:丢弃
  • 400:非法请求 处理方式:丢弃
  • 401:未授权 处理方式:丢弃
  • 403:禁止 处理方式:丢弃
  • 404:没有找到 处理方式:丢弃
  • 500:服务器内部错误 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
  • 501:服务器无法识别 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
  • 502:错误网关 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
  • 503:服务出错 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。
 from urllib.request import Request, urlopen
 from urllib.error import URLError, HTTPError

    req = Request(r"https:/www.jb51.net/")
    try:
        response = urlopen(req)
    except HTTPError as e:
        print("The server couldn't fulfill the request.")
        print('Error code: ', e.code)
    except URLError as e:
        print('We failed to reach a server.')
        print('Reason: ', e.reason)
    else:
        print("good!")
        print(response.read().decode("utf8"))
        
#输出
We failed to reach a server.
Reason:  no host given
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值