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编码

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
x 01001110 00101101 11100100 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 "", 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 "", 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 "", 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 迭代
序号 函数
1 len(list) 列表元素个数
2 max(list) 返回列表元素最大值
3 min(list) 返回列表元素最小值
4 list(seq) 将元组转换为列表

Python包含以下方法:

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

python_study_第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 迭代
序号 方法及描述
1 len(tuple) 计算元组元素个数。
2 max(tuple) 返回元组中元素最大值。
3 min(tuple) 返回元组中元素最小值。
4 tuple(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 "", line 1, in 
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 "", line 1, in 
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 "", 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 "", 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()**包含与文件名与文件路径相关的函数

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

helloworld!

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

不要用正则表达式解析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])# 输出带有

的第一个字符串 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
Python JSON
dict object
list, tuple array
str string
int, float, int- & float-derived Enums number
True true
False false
None null

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
序号 时间元组属性
0 tm_year 2008
1 tm_mon 1 到 12
2 tm_mday 1 到 31
3 tm_hour 0 到 23
4 tm_min 0 到 59
5 tm_sec 0 到 61 (60或61 是闰秒)
6 tm_wday 0到6 (0是周一)
7 tm_yday 1 到 366(儒略历)
8 tm_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...

python_study_第2张图片

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 文件。

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()

python_study_第3张图片

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>

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 文件(可以使用浏览器打开),显示结果如下:

python_study_第4张图片

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 个字符串:协议,位置,路径,参数,查询,判断。

属性 索引 值(如果不存在)
scheme 0 URL协议 scheme 参数
netloc 1 网络位置部分 空字符串
path 2 分层路径 空字符串
params 3 最后路径元素的参数 空字符串
query 4 查询组件 空字符串
fragment 5 片段识别 空字符串
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解析器读到一个节点时:

python

会产生3个事件:

  1. start_element事件,在读取时;
  2. char_data事件,在读取python时;
  3. end_element事件,在读取时。

用代码实验一下:

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'''
  1. Python
  2. Ruby
'''
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'')
L.append(r'')
L.append(encode('some & data'))
L.append(r'')
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('' % tag)

    def handle_startendtag(self, tag, attrs):
        print('<%s/>' % tag)

    def handle_data(self, data):
        print(data)

    def handle_comment(self, data):
        print('')

    def handle_entityref(self, name):
        print('&%s;' % name)

    def handle_charref(self, name):
        print('&#%s;' % name)

parser = MyHTMLParser()
parser.feed('''



    

Some html HTML tutorial...
END

'''
)

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)]

python_study_第5张图片

现在想要在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

你可能感兴趣的:(python)