Python概述及安装

Python简介

起源

  • 贵铎·范·罗萨姆(Guido van Rossum)于1989年底始创了python
  • 1991年初,python发布了第一个公开发行版
  • 为了更好的完成荷兰的CWI(国家数学和计算机科学研究院)的一个研究项目而创建

版本

  • Python2.x

    目前所有系统默认安装的版本

  • Python3.x

    2009年2月13日发布

    在语法和功能上有较大调整

    Python的发展趋势

特点

  • 高级:有高级的数据结构,缩短开发时间与代码量
  • 面向对象:为数据和逻辑相分离的结构化和过程化添加了新的活力
  • 可升级:提供了基本的开发模块,可以在它上面开发软件,实现代码的重用
  • 可扩展:通过将其分离为多个文件或模块加以组织管理
  • 可移植性:python是用C写的,又由于C的可移植性,使得python可以运行在任何带有ANSI C编译器的平台上
  • 易学:python关键字少、结构简单、语法清晰
  • 易读:没有其他语言通常用来访问变量、定义代码块和进行模式匹配的命令式符号
  • 内存管理器:内存管理是由python解释器负责的

安装与配置

获取python3源码

安装依赖包

1
[root@python ~]# yum install -y gcc gcc-c++ zlib-devel openssl-devel readline-devel libffi-devel sqlite-devel tcl-devel tk-devel

安装python3

1
2
3
4
[root@python ~]# tar xf Python-3.9.16.tar.xz
[root@python ~]# cd Python-3.9.16/
[root@python Python-3.9.16]# ./configure --prefix=/usr/local
[root@python Python-3.9.16]# make && make install

配置Python支持tab键补全

  • 默认情况下,Python不支持按Tab键补全
1
2
3
4
5
6
7
8
[root@python ~]# vim /usr/local/bin/tab.py
from rlcompleter import readline
readline.parse_and_bind('tab: complete')

[root@python ~]# vim .bashrc
export PYTHONSTARTUP='/usr/local/bin/tab.py'

[root@python ~]# source .bashrc

vim实现python提示

120-打造vim为python IDE - 简书 (jianshu.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(mypy) [root@python ~]$ mkdir -p ~/.vim/bundle/
(mypy) [root@python ~]$ cd ~/.vim/bundle/
(mypy) [root@python bundle]$ git clone https://gitee.com/DataTraveler_0817/pydiction.git
(mypy) [root@python bundle]$ ls
pydiction
(mypy) [root@python bundle]$ cp -r pydiction/after/ ~/.vim/
(mypy) [root@python bundle]$ vim ~/.vimrc
filetype plugin on
let g:pydiction_location = '~/.vim/bundle/pydiction/complete-dict'
set ai
set et
set ts=4

#测试,注意文件名必须是以.py结尾,否则没有代码补全

配置Python IDE

Python虚拟环境

什么是虚拟环境

  • 每个项目都需要安装很多库,当项目结束后这些库不需要了,该如何清理
  • 在同一个系统中,需要同时安装两个模块的不同版本该如何实现

创建虚拟环境

  • Python3中已经自带虚拟环境模块venv
  • 将模块作为脚本文件执行,直接创建虚拟环境
1
2
3
4
[root@python ~]# python3 -m venv ~/mypy 
[root@python ~]# source ~/mypy/bin/activate
(mypy) [root@python ~]# python --version
Python 3.9.16

Python IDE

什么是IDE

  • 实际开发中,除编辑器是必须的,还需要很多其他辅助软件:

    编辑器:用来编写代码,并且给代码着色,以方便阅读

    代码提示器:输入部分代码,即可提示全部代码,加速代码的编写过程

    调试器:观察程序的每一个运行步骤,发现程序的逻辑错误

    项目管理工具:对程序涉及到的所有资源进行管理,如源文件、图片等

    漂亮的界面:各种按钮、菜单、窗口等控件整齐排布,操作更方便

  • 这些工具通常被打包在一起,同一发布和安装,统称为IDE(集成开发工具)

PyCharm简介

  • Pycharm是由JetBrains打造的一款Python IDE

  • 支持的功能:

    调试、语法高亮

    Project管理、代码跳转

    智能提示、自动完成

    单元测试、版本控制

  • 下载地址

    https://www.jetbrains.com/pycharm/download

  • 需要图形化界面

1
2
3
4
5
(mypy) [root@python ~]$ yum install java-1.8.0-openjdk -y
(mypy) [root@python ~]$ tar xf pycharm-community-2023.1.tar.gz
(mypy) [root@python ~]$ mv pycharm-community-2023.1 /usr/local/bin/pycharm
(mypy) [root@python ~]$ /usr/local/bin/pycharm/bin/pycharm.sh

PyCharm配置

  • 配置界面风格

  • 新建项目

  • 添加项目解释器

  • 添加Pycharm到菜单

    应用程序–>杂项–>主菜单

1
[root@python ~]$ yum install -y alacarte

Python语法基础

Python运行方式

交互解释器

  • 进入交互解释器
1
2
3
4
5
6
7
8
[root@python ~]# source mypy/bin/activate

(mypy) [root@python ~]# python
Python 3.9.16 (main, Apr 24 2023, 11:33:35)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

  • 退出交互解释器
1
2
3
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
>>> exit()

文件形式

  • 明确指定解释器
1
2
3
4
5
6
7
8
9
10
(mypy) [root@python ~]# which python
/root/mypy/bin/python

(mypy) [root@python ~]# vim hi.py
#!/root/mypy/bin/python
print('hello world!')

(mypy) [root@python ~]# python hi.py
hello world!

  • 赋予python文件可执行权限
1
2
3
(mypy) [root@python ~]# chmod +x hi.py 
(mypy) [root@python ~]# ./hi.py
hello world!

Python语法结构

语句块缩进

  • python代码块通过缩进对齐表达代码逻辑而不是使用大括号

  • 缩进表达一个语句属于哪个代码块

  • 缩进风格

    1或2:可能不够,很难确定代码语句属于哪个块

    8或10:可能太多,如果内嵌从层次太多,就会使得代码很难阅读

    4个空格:非常流行,范·罗萨姆支持的风格

  • 批量缩进,选中需要缩进的行按Tab即可,向左缩进Shift + Tab

1
2
3
if 3>0:
print('yes')
print('no')
  • 缩进相同的一组语句构成一个代码块,称之为代码组
  • 首行以关键字开始,以冒号:结束,该行之后的一行或多行代码构成代码组
  • 如果代码组只有一行,可以将其直接写在冒号后面,但是这样的写法可读性差,不推荐
1
if 10 > 8: print("true")

注释及续航

  • 尽管Python是可读性最好的语言之一,这并不意味着程序员在代码中就可以不写注释

  • 和很多UNIX脚本类似,python注释语句从#字符开始

  • 注释可以在一行的任何地方开始,解释器会忽略掉该行#之后的所有内容

  • 一行过长的语句可以使用反斜杠\分解成几行

  • 批量注释Ctrl + /,亦可批量取消注释

1
2
3
a = 'hello world' + \
'hello tedu'
print(a)

同行多个语句

  • 分号;允许你将多个语句写在同一行上
  • 但是这些语句不能在这行开始一个新的代码块
  • 因为可读性会变差,所以不推荐使用

输出语句

  • 获取帮助
1
>>> help(print)
  • 使用方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> print('hello world!')
hello world!

>>> print('hello' + 'world') # 字符拼接
helloworld

>>> print('hello','world') # 逗号自动添加默认的分隔符,空格
hello world

>>> print('hello','world',sep='***') # 单词间用***分割
hello***world

>>> print('#' * 50) # *号重复50遍

>>> print('how are you?',end='')
how are you?>>> # 默认print会打印回车,end=''表示不要回车

输入语句

  • 获得帮助
1
>>> help(input)
  • 使用方式(注意,返回值一定是字符类型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> num = input("Number: ") # input用于获取键盘输入
Number: 20
>>> print(num)
20

>>> type(num)
<class 'str'>
>>> num + 10 # 为字符串类型不能用于数值运算
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

>>> int(num) + 5 # 使用int()函数将num转换为数值类型
25
>>> num + str(5) # 使用str()函数将5转换为字符串类型
'205'

案例练习

模拟用户登录

  • 创建名为login.py的程序文件
  • 程序提示用户输入用户名
  • 用户输入用户名后,打印欢迎用户的信息
1
2
3
4
name = input("name: ")
print("welcome", name)
print("welcome " + name)
print("welcome %s" % name) # %s为占位符

Python变量及运算符

python变量

变量定义

  • 变量名称约定

    第一个字符只能是大小写字母或下划线

    后续字符只能是大小写字母或数字或下划线

    区分大小写

  • python是动态类型语言,即不需要预先声明变量的类型

  • 推荐采用的全名方法

    变量名全部采用小写字母

    简短、有意义

    多个单词用下划线分隔

    变量名用名词,函数名用谓词(动词+名词)

    类名采用驼峰形式

1
2
3
4
>>> pystr = 'a'
>>> py_str = 'a'
>>> update_phone = ''
>>> MyClass

变量复制

  • 变量的类型和值在赋值那一刻被初始化
  • 变量赋值通过等号来执行
1
2
>>> counter = 0
>>> name = 'bob'
  • python也支持增量赋值
1
2
3
4
5
6
7
8
9
>>> n = 0
>>> n += 1 # 等价于 n = n + 1
>>> n *= 1 # 等价于 n = n * 1

>>> i++ # 不支持该形式
File "<stdin>", line 1
i++
^
SyntaxError: invalid syntax

运算符

  • 标准算数运算符

    + - * / // % **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> 5 / 3  # 除
1.6666666666666667

>>> 5 // 3 # 丢弃余数,只保留商
1

>>> 5 % 3 # 求余数
2

>>> divmod(5,3) # 取商和余数
(1, 2)

>>> a , b = divmod(5,3) # 可按顺序赋值
>>> a
1
>>> b
2

>>> 5 ** 3 # 5的3次方
125
  • 比较运算符

    < <= > >= == !=

1
2
3
4
5
6
7
8
9
10
11
12
>>> 2 < 3
True
>>> 5 < 3
False
> 5 == 3 # 比较
False
>>> 5 = 3 # 赋值
File "<stdin>", line 1
5 = 3
^
SyntaxError: cannot assign to literal

  • 逻辑运算符

    and not or

1
2
3
4
5
6
>>> 5 > 3 and 10 > 5     #两者都为True,返回True
True
>>> 5 > 3 or 5 < 2 #只要由一方为True,即为True
True
>>> not 5 > 3 #取反
False

数字和字符串

数字

基本数字类型

  • int:有符号整数

  • bool:布尔值

    True:1

    False:0

  • float:浮点数

  • complex:复数

1
2
3
4
5
6
7
8
>>> type(5)
<class 'int'>
>>> type(5.0)
<class 'float'>
>>> True + 3
4
>>> False * 3
0

整数数字表示方式

  • python默认以十进制数显示
  • 数字以0o或0O开头表示8进制数
  • 数字以0x或0X开头表示16进制数
  • 数字以0b或0B开头表示2进制数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 11
11
>>> 0o11
9
>>> 0x11
17
>>> 0b11
3

>> 100
100
>>> oct(100) #转换为8进制
'0o144'
>>> hex(100) #转换为16进制
'0x64'
>>> bin(100) #转换为2进制
'0b1100100'

字符串

定义字符串

  • python中字符串被定义为引号之间的字符集合
  • python支持使用成对的单引号或双引号
  • 无论单引号,还是双引号,表示意义相同
  • python还支持三引号(三个连续的单引号或者双引号),可以用来包含特殊字符
  • python不区分字符和字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 单引号中间还有单引号,可以转义
>>> sentence = 'tom\'s pet is a cat'
>>> sentence
"tom's pet is a cat"

# 也可以用双引号包含单引号
>>> sentence = "tom's pet is a cat"
>>> sentence
"tom's pet is a cat"

# 三个连续的单引号或双引号,可以保存输入格式,允许输入多行字符串
>>> user = """tom
... jerry
... bob
... alice
... """

>>> print(user)
tom
jerry
bob
alice

>>> user
'tom\njerry\nbob\nalice\n'

字符串切片

  • 使用索引运算符[]和切片运算符[:]可得到子字符串
  • 第一个字符的索引是0,最后一个字符的索引是-1
  • 字符串包含切片中的起始下标,但不包含下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> py_str = 'python'
>>> len(py_str) #显示长度
6

>>> py_str[0]
'p'
>>> py_str[-2]
'o'
>>> py_str[2:4]
'th'
>>> py_str[2:]
'thon'
>>> py_str[:4]
'pyth'
>>> py_str[:]
'python'

>>> py_str[::2] #每隔两个字符取出
'pto'
>>> py_str[1::2] #从下标为1的字符开始取,每个两个取出
'yhn'
>>> py_str[::-1] #翻过来取
'nohtyp'

字符串连接操作

  • 使用+号可以将多个字符串拼接在一起
  • 使用*号可以将一个字符串重复多次
1
2
3
4
5
6
>>> py_str = 'python'
>>> is_cool = 'is Cool'
>>> print( py_str + '' + is_cool )
pythonis Cool
>>> py_str * 2
'pythonpython'
  • 判断字符是否在字符串中包含
1
2
3
4
5
6
7
8
9
10
>>> py_str
'python'
>>> 't' in py_str
True
>>> 'th' in py_str
True
>>> 'to' in py_str
False
>>> 'to' not in py_str
True

列表、元组和字典

列表和元组

定义列表

  • 可以将列表当初普通的“数组”,它能保存任意数量任意类型的python对象
  • 像字符串一样,列表也支持下标和切片操作
  • 列表中的项目可以改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> alist = [10,20,"tom","jerry",[1,2]]
>>> len(alist)
5

>>> alist[0]
10
>>> alist[-1]
[1, 2]
>>> alist[-1][-1] # 因为最后一项是列表,列表还可以继续取下标
2
>>> alist[2:4]
['tom', 'jerry']

>>> alist + [100] # 此拼接操作不会写入列表
[10, 20, 'tom', 'jerry', [1, 2], 100]
>>> alist
[10, 20, 'tom', 'jerry', [1, 2]]

>>> alist[-1] = 100 # 修改值
>>> alist
[10, 20, 'tom', 'jerry', 100]


列表操作

  • 使用in或not in判断成员关系
  • 使用append方法向列表中追加元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> alist
[10, 20, 'tom', 'jerry', 100]
>>> 'tom' in alist
True
>>> '1' in alist
False
>>> [1,2] in alist
True


#添加元素
>>> alist.append(200)
>>> alist
[10, 20, 'tom', 'jerry', 100, 200]

#删除元素
>>> alist.remove(10)
>>> alist
[20, 'tom', 'jerry', 100, 200]

#清空
>>> alist.clear()
>>> alist
[]

元组的定义及操作

  • 可以认为元组是‘静态’的列表
  • 元组一旦定义,不能改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> atuple =  (1,2,'tom','jerry')
>>> len(atuple)
4

>>> atuple[0]
1
>>> atuple[2:]
('tom', 'jerry')

>>> atuple + (100,200) # 不会写入元组
(1, 2, 'tom', 'jerry', 100, 200)
>>> atuple
(1, 2, 'tom', 'jerry')

>>> 'tom' in atuple
True

>>> atuple[0] = 3 # 不能修改
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

字典

字典的定义及操作

  • 字典是由键-值(key-value)对构成的映射数据类型
  • 通过键取值,不支持下标操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> adict = {'name': 'bob','age': 20}
>>> len(adict)
2
>>> 'bob' in adict
False
>>> 'name' in adict
True

#修改value
>>> adict['age'] = 22
>>> adict
{'name': 'bob', 'age': 22}

#字典中没有的key, 则添加key和value
>>> adict['email'] = 'bob@tedu.cn'
>>> adict
{'name': 'bob', 'age': 22, 'email': 'bob@tedu.cn'}

数据类型比较

  • 按存储模型分类

    标量类型:数值、字符串

    容器类型:列表、元组、字典

  • 按更新模型分类

    可变类型:列表、字典

    不可变类型:数值、字符串、元组

1
2
3
4
5
6
7
8
9
10
11
>>> py_str = 'python'
>>> alist = [10,20,30]
>>> alist[0] = 100
>>> alist
[100, 20, 30]
>>> py_str[0]
'p'
>>> py_str[0] = 'P' # 不可更改
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

列表之间赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> alist = [10, 20, 30]
>>> alist
[10, 20, 30]

#把alist直接赋值给blist,alist会跟随blist改变而改变
>>> blist = alist
>>> blist
[10, 20, 30]
>>> blist.append(40)
>>> blist
[10, 20, 30, 40]
>>> alist
[10, 20, 30, 40]

#把alist切片赋值给clist,alist不会跟随clist改变而改变
>>> clist = alist[:]
>>> clist
[10, 20, 30, 40]
>>> clist.append(50)
>>> clist
[10, 20, 30, 40, 50]
>>> alist
[10, 20, 30, 40]
  • 按访问模型分类

    直接访问:数值

    顺序访问:字符串、列表、元组

    映射访问:字典

判断语句

if语句

if语句语法结构

  • 标准if条件语句的语法
1
2
3
4
if expression:
if_suite
else:
else_suite
  • 如果表达式的值非0或者为布尔值True,则代码组if_suite被执行,否则就去执行else_suite
  • 代码组是一个python术语,它由一条或多条语句组成,表示一个子代码块

if语句实例解析

  • 只要表达式数字为非零值即为True
1
2
3
4
>>> if 10:
... print('yes')
...
yes
  • 空字符串、空列表、空元组,空字典的值均为False
1
2
3
4
5
6
>>> if "":
... print('yes')
... else:
... print('no')
...
no

条件表达式

  • python在很长一段时间里没有条件表达式(C?X:Y),或称三元运算符,因为范·罗萨姆一直拒绝加入这样的功能
  • 从python2.5集成的语法确定为:X if C else Y
1
2
3
4
5
6
7
8
9
10
11
a = 10
b = 20
if a > b:
smaller = b
else:
smaller = a
print(smaller)

#等同于
s = a if a <= b else b
print(s)

案例

  • 提示用户输入用户名和密码
  • 获取相关信息后,将其保存在变量中
  • 如果用户输入的用户名为bob,密码为123456,则输出登录成功,否则输出登录失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user = input('username: ')
passwd = input('password: ')
if user == 'bob' and passwd == '123456': # 需要加引号
print('Login successful')
else:
print('Login inorrect')

import getpass # 使密码不会明文显示,若pycharm调用失败可能是不兼容,用Linux终端运行即可

user = input('username: ')
passwd = getpass.getpass('password: ')
if user == 'bob' and passwd == '123456':
print('Login successful')
else:
print('Login inorrect')

扩展if语句

  • 扩展if语句结构
1
2
3
4
5
6
7
8
9
if expression1:
if_suite
elif expression2:
elif_suite
······
elif ecpressionN:
elif_suite
else:
else_suite

判断语句案例

编写判断成绩的程序

  • 创建grade.py脚本,根据用户输入的成绩分档,要求如下
  • 如果成绩大于60分,输出‘及格’
  • 如果成绩大于70分,输出‘良’
  • 如果成绩大于80分,输出‘好’
  • 如果成绩大于90分,输出‘优秀’
  • 否则输出‘继续努力’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
grades = int(input('yours grades: '))

if grades >= 90:
print('excellent')

elif grades >= 80: # 也可以从下往上写,写成 80 <= grades < 90
print('good')

elif grades >= 70:
print('favorable')

elif grades >= 60:
print('flunk')

else:
print('Keep up the good work')

编写石头剪刀布小游戏

  • 编写game.py,要求如下
  • 计算机随机出拳
  • 玩家自己决定如何出拳
  • 代码尽量简化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import random    # 随机变量
all_choice = ['stone','scissors','cloth']
compute = random.choice(all_choice) # 从列表中随机选取
user = input("Please punch(from stone/scissors/cloth): ")

# 把所有赢的可能写入列表
win_list = [['stone','cloth'],['scissors','stone'],['cloth','scissors']]

# 输出各自出拳内容
print('your choice: %s , compute choice: %s' % (user,compute))

if compute == user: # \033[32;1m \033[0m加入颜色
print('\033[32;1mdraw\033[0m')

elif [compute,user] in win_list:
print('\033[31;1myou win\033[0m')

else:
print('\033[32;1myou lose\033[0m')

while循环基础

循环语句基础

循环概述

  • 一组被重复执行的语句称之为循环体,能否继续重复,决定循环的中止条件
  • python中的循环有while循环和for循环
  • 循环次数未知的情况下,建议采用while循环
  • 循环次数可以预知的情况下,建议采用for循环

while循环语法结构

  • 当需要语句不断重复执行时,可以使用while循环
1
2
while expression:
while_suite
  • 语句while_suite会连续不断的循环执行,直到表达式的值变为0或False
1
2
3
4
5
6
sum100 = 0
counter = 1
while counter <= 100:
sum100 += counter
counter += 1
print('result is %d' % sum100)

案例

完善剪刀石头布小游戏,实现三局两胜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import random
all_choice = ['stone', 'scissors', 'cloth']
win_list = [['stone', 'cloth'], ['scissors', 'stone'], ['cloth', 'scissors']]

prompt = """(0) stone
(1) scissors
(2) cloth
please choice(0/1/2): """

compute_win = 0
user_win = 0

while compute_win < 2 and user_win < 2:
compute = random.choice(all_choice)
ind = int(input(prompt)) # 将用户输入的转换为数值类型
user = all_choice[ind] # 通过索引赋值
print('your choice: %s , compute choice: %s' % (user, compute))

if compute == user:
print('\033[32;1mdraw\033[0m')

elif [compute,user] in win_list:
print('\033[31;1myou win\033[0m')
user_win += 1
else:
print('\033[32;1myou lose\033[0m')
compute_win += 1

while循环扩展语法

循环语句进阶

break语句

  • break语句可以结束当前循环然后跳转到下条语句
  • 写程序的时候,尽量避免重复的代码,在这种情况下可以使用while-break结构
1
2
3
4
5
6
7
8
9
name = input('username: ')
while name != 'tom':
name = input('username: ')

# 可以替换为
while True:
name = input('username: ')
if name == 'tom':
break

continue语句

  • 当遇到continue语句时,程序会中止当前循环,并忽略剩余语句,然后回到循环的顶端
  • 如果仍然满足循环条件,循环体内语句继续执行,否则退出循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
result = 0
counter = 0
while counter < 100:
counter += 1
if counter % 2 == 1: # 奇数跳过
continue
else:
result += counter
print(result)

# 替换为
result = 0
counter = 0
while counter < 100:
counter += 1
if counter % 2: # counter % 2 结果只能是1或0,1为真,0为假
continue
result += counter
print(result)

else语句

  • python中while语句也支持else子句
  • else子句只能循环完成后执行
  • break语句也会跳过else
1
2
3
4
5
6
7
sum10 = 0
i = 1
while i <= 10:
sum10 += i
i += 1
else:
print(sum10)

案例

猜数程序

  • 系统随机生成100以内的数字
  • 要求用户猜生成的数字大小
  • 最多猜5次,猜对结束程序
  • 如果5次全错,则输出正确结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random
compute = random.randint(1,100) # 随机生成100以内的数字
c = 1
while c <= 5:
c += 1
user = int(input('please guess(1-100): '))
if compute < user:
print('big')
elif compute > user:
print('low')
else:
print('good')
break
else:
print(compute)

for循环及range函数

for循环

for循环语法结构

  • python中的for接受可迭代对象(例如序列或迭代器)作为其参数,每次迭代其中一个元素
1
2
for iter_var in iterable:
suite_to_repeat
  • while语句一样,支持breakcontinueelse语句
  • 一般情况下,循环次数未知的情况下采用while循环,循环次数已知采用for循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
str = 'hello'
for ch in str:
print(ch)
h
e
l
l
o


alist = [10,20,30]
for i in alist:
print(i)
10
20
30


users = ('bob','alice','tom')
for user in users:
print(user)
bob
alice
tom


adict = {'user': 'tom','age': 22}
for key in adict:
print(key,adict[key])
user tom
age 22

range函数

  • for循环常与range函数一起使用

  • range函数提供循环条件

  • range函数的完整语法为:

    range(start,end,step = 1)

1
2
3
4
5
6
7
8
9
10
>>> range(10)
range(0, 10)
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 起始默认为0,结束为10,不包含10
>>> list(range(6,10))
[6, 7, 8, 9]
>>> list(range(1,11,2)) # 从起始值开始,每隔两个值取一个
[1, 3, 5, 7, 9]
>>> list(range(10,0,-1))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

示例

1
2
3
4
sum = 0
for i in range(1,10000001):
sum += i
print(sum)

案例

斐波那契数列

  • 斐波那契数列就是某一个数,总是前两个数之和,比如:0,1,1,2,3,5,8
  • 使用for循环和range函数编写一个程序,计算有10个数字的斐波那契数列
  • 改进程序,要求用户输入一个数字,可以生成用户需要长度的斐波那契数列
1
2
3
4
5
6
7
8
9
10
11
alist = [0,1]
for i in range(8):
alist.append(alist[-1]+alist[-2])
print(alist)


num = int(input('num: ')) - 2
alist = [0,1]
for i in range(num):
alist.append(alist[-1]+alist[-2])
print(alist)

for循环练习与列表解析

列表解析

  • 它是一个非常有用、简单、而且灵活的工具,可以用来动态的创建列表

  • 语法:

    [expr for iter_var in iterable]

  • 这个语句的核心是for循环,它迭代iterable对象的所有条目

  • expr应用于序列的每个成员,最后的结果值是该表达式产生的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> [5]
[5]
>>> [5 + 5]
[10]
>>> [5 + 5 for i in range(10)]
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]

>>> [5 + i for i in range(1,11)]
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

>>> [5 + i for i in range(1,11) if i % 2 == 1]
[6, 8, 10, 12, 14]

>>> ['192.168.1.%s' % i for i in range(1,11)]
['192.168.1.1', '192.168.1.2', '192.168.1.3', '192.168.1.4', '192.168.1.5', '192.168.1.6', '192.168.1.7', '192.168.1.8', '192.168.1.9', '192.168.1.10']

>>> domains = ['tedu','qq','tmall','jd']
>>> ['%s.com' % domain for domain in domains]
['tedu.com', 'qq.com', 'tmall.com', 'jd.com']

案例

九九乘法表

  • 程序运行后,可以在屏幕上打印出九九乘法表
  • 修改程序,由用户输入数字,可打印任意数字的乘法表
1
2
3
4
for i in range(1,10):
for j in range(1,i + 1):
print('%s*%s=%s' % (i,j,i*j),end=' ')
print()

文件打开方法及读取

文件打开方法

open内建函数

  • 作为打开文件的“钥匙”,内建函数open()提供了初始化输入/输出(I/O)操作通用接口

  • 成功打开文件后会返回一个文件对象,否则引发一个错误

  • 基本语法:

    file_object = open(file_name, mode=’r’, buffering=-1)

文件对象访问模式

文件模式 操作
r 读方式打开(文件不存在则报错)
w 以写方式打开(文件存在则清空,不存在则创建)
a 以追加模式打开(必要时创建新文件)
r+ 以读写模式打开(参见r)
w+ 以读写模式打开(参见w)
a+ 以读写模式打开(参见a)
b 以二进制模式打开

文件输入

read方法

  • read()方法用来直接读取字节到字符串中,最多读取的给定数目个字节
  • 如果没有给定size参数(默认值为-1)或者size值为负,文件将读取直至末尾

readline方法

  • 读取打开文件的一行(读取下个行结束符之前的所有字节)
  • 然后整行,包括行结束符,作为字符串返回
  • 它也有一个size参数,默认值为-1,代表读至行结束
  • 如果提供了该参数,那么在超过size个字节后会返回不完整的行

readlines方法

  • readlines()方法读取所有(剩余的)行然后把它们作为一个字符串列表返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(mypy) [root@python pycharm]# cp /etc/passwd /tmp/
(mypy) [root@python pycharm]# python
>>> f = open('/tmp/passwd')
>>> f
<_io.TextIOWrapper name='/tmp/passwd' mode='r' encoding='UTF-8'>
>>> data = f.read()
>>> data
root:x:0:0:root:/root:/bin/bash\n
······
>>> print(data)
root:x:0:0:root:/root:/bin/bash
······


>>> data = f.read() # 存在文件指针,当我们读取后文件指针会向后移,当我们读完后,指针已经指到最后,再读没有数据了
>>> data
''

>>> f.close() # 若想重新读取,先关闭文件
>>> f = open('/tmp/passwd') # 重新打开文件,读取
>>> f.read(1) # 每读取一次,指针就会向后移动 输出’root‘
'r'
>>> f.read(1)
'o'
>>> f.read(1)
'o'
>>> f.read(1)
't'

>>> f.readline() # 读取整行,因为开头的root已经读取,就读取剩余内容
':x:0:0:root:/root:/bin/bash\n'
>>> f.readline()
'bin:x:1:1:bin:/bin:/sbin/nologin\n'

>>> f.readlines() # readlines把剩余内容作为列表全部读出来,
['daemon:x:2:2:daemon:/sbin:/sbin/nologin\n', ······]

其他类型的文件

1
2
3
4
5
6
7
8
# 2进制
(mypy) [root@python pycharm]# cp /usr/bin/ls /tmp/
(mypy) [root@python pycharm]# python

>>> f = open('/tmp/ls', 'rb')
>>> f.read(10)
b'\x7fELF\x02\x01\x01\x00\x00\x00'

文件迭代

  • 如果需要逐行处理文件,可以结合for循环迭代文件
  • 迭代文件的方法于处理其他序列类型的数据类似
1
2
3
4
5
6
7
>>> f.close()
>>> f = open('/tmp/passwd')

>>> for line in f:
... print(line,end='')
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin

写文件及文件指针

文件输出

write方法

  • write()内建方法功能与read()readline()相反。它把含有文本数据或二进制数据块的字符串写入到文件中去
  • 写文件时,不会自动添加结束标准,需要程序员手工输入
1
2
3
4
5
>>> f.close()
>>> f = open('/tmp/passwd', 'a+')
>>> f.write('hello world\n')
12 # 表示写入的字节 最初存储在内存中,当数据量未达到4k时,或者关闭文件才会写入硬盘
>>> f.flush() # 直接同步数据

writelines方法

  • readlines()一样,writelines()方法是针对列表的操作
  • 它接受一个字符串列表作为参数,将它们写入文件
  • 行结束符不会自动被加入,所以如果有需要的话,必须在调用writelines()前给每行结尾加上行结束符
1
>>> f.writelines(['hello world\n','python\n'])

操作文件

with子句

  • with语句是用来简化代码的
  • 在将打开文件的操作放在with语句中,代码块结束后,文件将自动关闭
1
2
3
4
5
>>> with open('/tmp/ls','rb') as f: 
... data = f.readlines()
...
>>> f.closed # 查看是否关闭
True

文件内移动

  • seek(offset[,whence]):移动文件指针到不同的位置

    offset是相当于某个位置的偏移量

    whence的值,0表示文件开头,1表示当前位置,2表示文件结尾

  • tell():返回当前文件指针的位置

1
2
3
4
5
6
7
8
9
>>> f = open('/tmp/passwd','rb')   # 若只用r打开,移动指针受限
>>> f.tell()
0
>>> f.seek(6,0) # 从开头位置向后偏移6个字节
6
>>> f.seek(2,1) # 从当前位置相后偏移2个字节
8
>>> f.seek(-7,2) # 从结尾位置向前偏移7个字节
2213

案例:模拟cp操作

  • 将/bin/ls“拷贝”到/tmp/目录下
  • 不要修改原始文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
src_fname = '/bin/ls'
dst_fname = '/tmp/ls'

src_fobj = open(src_fname,'rb')
dst_fobj = open(dst_fname,'wb')

while 1:
data = src_fobj.read(4096)
if data == b'': # 当data为空值时,因为使用rb模式打开,每行的内容都是b'······'
break
dst_fobj.write(data)

src_fobj.close()
dst_fobj.close()

函数基础

函数基本操作

函数的基本概念

  • 函数是对程序逻辑进行结构化或过程化的一种编程方法
  • 将整块代码巧妙的隔离成易于管理的小块
  • 把重复代码放到函数中而不是进行大量的拷贝,这样既能节省空间,也有助于保持一致性
  • 通常函数都是用于实现某一种功能

创建函数

  • 函数是用def语句来创建的,语法如下:
1
2
3
def function_name(arguments):
"function_documentation_string"
function_body_suite
  • 标题行由def关键字,函数的名字,以及参数的集合(如果有的话)组成
  • def子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必需的函数体

调用函数

  • 同大多数语言相同,python用一对圆括号调用函数
  • 如果没有加圆括号,只是对函数的引用
1
2
3
4
5
6
7
8
>>> def foo():
... print('*' * 10)
...
>>> foo()
**********
>>> foo # 存在内存的0x7fa01a3579d0位置
<function foo at 0x7fa01a3579d0>

函数的返回值

  • 多数情况下,函数并不直接输出数据,而是向调用者返回值
  • 函数的返回值使用return关键字
  • 没有return的话,函数默认返回None
1
2
3
4
5
6
>>> def foo():
... res = 3 + 4
...
>>> i = foo()
>>> print(i)
None

案例:斐波那契数列函数

  • 将斐波那契数列代码改为函数
  • 数列长度由用户指定
  • 要求返回结果用return返回
1
2
3
4
5
6
7
def fbnqsl():
a = int(input("num: ")) - 2
list = [0,1]
for i in range(a):
list.append(list[-1]+list[-2])
return list
print(fbnqsl())

函数参数

定义参数

  • 形式参数

    函数定义时,紧跟在函数名后(圆括号内)的参数成为形式参数,简称形参。由于它不是实际存在的变量,所以又称为虚拟变量

  • 实际参数

    在主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为“实际参数”,简称实参

传递参数

  • 调用函数时,实参的个数需要与形参个数一致
  • 实参将依次传递给形参
1
2
3
4
5
>>> def foo(x,y):  # x y 形参
... print('x=%s,y=%s' % (x,y))
...
>>> foo(3,4) # 3 4 实参
x=3,y=4

斐波那契数列函数优化

1
2
3
4
5
6
7
8
9
def fbnqsl(x):
list = [0,1]
for i in range(x-2):
list.append(list[-1]+list[-2])
return list

alist = [10,20,30]
for i in alist:
print(fbnqsl(i))

位置参数

  • shell脚本类似,程序名以及参数都以位置参数的方式传递给python程序
  • 使用sys模块的argv列表接收
1
2
3
4
5
6
(mypy) [root@python ~]# vim ceshi.py
import sys
print(sys.argv)

(mypy) [root@python ~]# python ceshi.py hao 123
['ceshi.py', 'hao', '123']

默认参数

  • 默认参数就是声明了默认值的参数
  • 因为给参数赋予了默认值,所以在函数调用的时候,不向该参数传入值也是允许的
1
2
3
4
5
6
7
>>> def pstar(num = 30):
... print('*' * num)
...
>>> pstar()
******************************
>>> pstar(40)
****************************************

案例:复制文件函数

  • 修改文件练习中的拷贝程序
  • 将程序改为函数形式
  • 源文件和目标文件要求通过参数形式传递
  • 实参要求来自于命令行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import sys
def copy(src,dst):
src_fobj = open(src, 'rb')
dst_fobj = open(dst, 'wb')

while 1:
data = src_fobj.read(4096)
if data == b'':
break
dst_fobj.write(data)
src_fobj.close()
dst_fobj.close()

return print('ok')
copy(sys.argv[1],sys.argv[2])

(mypy) [root@python pycharm]# python day3.py /bin/ls /tmp/ls3
ok
(mypy) [root@python pycharm]# md5sum /bin/ls /tmp/ls3
a78c13d806e594dc4014d145d689f23d /bin/ls
a78c13d806e594dc4014d145d689f23d /tmp/ls3

模块基础

定义模块

模块基本概念

  • 模块是从逻辑上组织python代码的形式
  • 当代码量变得相当大的时候,最好把代码分出一些有组织的代码段,前提是保证它们的彼此交互
  • 这些代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数

创建模块

  • 模块物理层面上组织的方法是文件,每一个以.py结尾的python文件都是一个模块
  • 模块名称切记不要与系统中已存在的模块重名
  • 模块文件名字去掉后面的扩展名(.py)即为模块名

导入模块(import)

  • 使用import导入模块
  • 模块属性通过“模块名.属性”的方法调用
  • 如果仅需要模块中的某些属性,也可以单独引用
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import sys  
>>> import os,string # 导入多个模块,不建议
>>> string.digits
'0123456789'

>>> from random import randint # 从random模块中导入randint
>>> randint(1,10)# 因为只导入randint,所以不需要random.randint()
3

>>> import getpass as gp # 给导入的模块起一个别名
>>> gp.getpass()
Password:
'123456'

模块加载(load)

  • 一个模块只能被加载一次,无论它被导入多少次
  • 只加载一次可以阻止多重导入时代码被多次执行
  • 如果两个文件相互导入,防止了无限的相互加载
  • 模块加载时,顶层代码会自动执行,所以只将函数放入模块的顶层是良好的编程习惯

案例:创建模块

  • 创建star模块
  • 模块中包含一个变量hi值为Hello World!
  • 模块中包含一个函数pstar,默认打印30个星号
  • 模块中包含文档字符串
  • 调用模块,测试功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"""演示模块

这是一个演示模块,它包括了一个变量和一个函数
"""

hi = "Hello World!"
def pstar(n=30):
"该函数用于打印星号,如果没有传参,默认打印30个星号"
print("*" * n)


>>> import star
>>> star.pstar()
******************************
>>> star.hi
'Hello World!'

>>> help(star) # 查看模块帮助,模块中引号中写的是模块的描述
Help on module star:

NAME
star - 演示模块

DESCRIPTION
这是一个演示模块,它包括了一个变量和一个函数

FUNCTIONS
pstar(n=30)
该函数用于打印星号,如果没有传参,默认打印30个星号
DATA
hi = 'Hello World!'

FILE
/root/pycharm/star.py

>>> help(star.pstar) # 查看单个函数的描述
Help on function pstar in module star:

pstar(n=30)
该函数用于打印星号,如果没有传参,默认打印30个星号

模块特性及案例

模块特性

模块导入的特性

  • 模块具有一个__name__特殊属性(双下划线
  • 当模块文件直接执行时,__name__的值为__main__
  • 当模块被另一个文件导入时,__name__的值就是该模块的名字
1
2
3
4
5
6
7
8
9
10
(mypy) [root@python pycharm]# vim foo.py
print(__name__)

(mypy) [root@python pycharm]# python foo.py
__main__

(mypy) [root@python pycharm]# python
>>> import foo
foo

案例

生成随机密码

  • 创建randpass.py脚本,要求如下:
  • 编写一个能生成8位随机密码的程序
  • 使用random的choice函数随机取出字符
  • 改进程序,用户可以自己决定生成多少位的密码
1
2
3
4
5
6
7
8
9
10
11
12
13
import random
from string import ascii_letters, digits # 随机参数字符串和数字
all_chs = ascii_letters + digits

def randpass(n = 8):
result = ''
for i in range(n):
ch = random.choice(all_chs)
result += ch
return result

if __name__ == '__main__': # 当作为脚本执行的时候
print(randpass())

shutil模块

复制和移动

  • shutil.copy(src,dst,*,follow_symlinks=True)

    将文件src复制到文件或目录dstsrcdst应为字符串。如果dst指定目录,则文件将使用src的基本文件名复制到dst中。返回新创建的文件的路径

  • shutil.copy2(src,dst,*,follow_symlinks=True)

    copy()相同,但copy2()也尝试保留所有文件元数据

  • shutil.move(src,dst,copy_function=copy2)

    递归的将文件或目录src移动到另一个位置dst,并返回目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
>>> import shutil
>>> f1 = open('/etc/passwd','rb')
>>> f2 = open('/tmp/passwd','wb')
>>> shutil.copyfileobj(f1,f2) # 拷贝文件对象
>>> f1.close()
>>> f2.close()

(mypy) [root@python pycharm]# md5sum /etc/passwd /tmp/passwd
708b712979d8de2fd00a3335043e482b /etc/passwd
708b712979d8de2fd00a3335043e482b /tmp/passwd


# 不用打开文件 其底层代码就是shutil.copyfileobj和shutil.copymode
>>> shutil.copy('/etc/hosts','/tmp/zj.txt')
'/tmp/zj.txt'

(mypy) [root@python ~]# md5sum /etc/hosts /tmp/zj.txt
54fb6627dbaa37721048e4549db3224d /etc/hosts
54fb6627dbaa37721048e4549db3224d /tmp/zj.txt

# 但是两个文件元数据不一致 使用copy2就会尽量一致
(mypy) [root@python ~]# stat /etc/hosts
文件:"/etc/hosts"
大小:158 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:1074772527 硬链接:1
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
环境:system_u:object_r:net_conf_t:s0
最近访问:2023-04-26 11:05:50.891465475 +0800
最近更改:2013-06-07 22:31:32.000000000 +0800
最近改动:2023-04-24 10:47:41.887735760 +0800
创建时间:-
(mypy) [root@python ~]# stat /tmp/zj.txt
文件:"/tmp/zj.txt"
大小:158 块:8 IO 块:4096 普通文件
设备:fd00h/64768d Inode:1075654904 硬链接:1
权限:(0644/-rw-r--r--) Uid:( 0/ root) Gid:( 0/ root)
环境:unconfined_u:object_r:user_tmp_t:s0
最近访问:2023-04-27 10:56:04.602515496 +0800
最近更改:2023-04-27 10:54:51.006825541 +0800
最近改动:2023-04-27 10:54:51.006825541 +0800
创建时间:-

# 移动
>>> shutil.move('/tmp/zj.txt','/var/tmp/zj.txt')
'/var/tmp/zj.txt'

目录操作

  • shutil.copytree(src,dst,symlinks=False,ignore=None,copy_function=copy2,ignore_dangling_symlinks=False)

    递归的复制以src为根的整个目录树,返回目标目录。由dst命名的目标目录不能已经存在

  • shutil.retree(path,ignore_errors=False,onerror=None)

    删除整个目录树,路径必须指向目录(而不能是指向目录的符号链接)

  • shutil模块没有提供删除文件的函数

1
2
3
4
5
>>> shutil.copytree('/etc/security','/tmp/security') 
'/tmp/security'

>>> shutil.rmtree('/tmp/security')
>>>

权限管理

  • shutil.copymode(src,dst,*,follow_symlinks=True)

    将权限从src复制到dst。文件内容,所有者和组不受影响。srcdst是以字符串类似给出的路径

  • shutil.copystat(src,dst,*,follow_symlinks=True)

    将权限位,最后访问时间,上次修改的时间和标志从src复制到dst

  • shutil.chown(path,user=None,group=None)

    更改给定路径的所有者用户和/或

1
2
3
4
5
6
7
(mypy) [root@python ~]# ll /tmp/zj.txt
-rw-r--r--. 1 root root 158 427 10:54 /tmp/zj.txt

>>> shutil.chown('/tmp/zj.txt',user='wsq',group='wsq')

(mypy) [root@python ~]# ll /tmp/zj.txt
-rw-r--r--. 1 wsq wsq 158 427 10:54 /tmp/zj.txt

subprocess模块

概述

  • subprocess模块主要用于执行系统命令
  • subprocess模块允许你产生新的进程,连接到它们的输入/输出/错误管道,并获得它们的返回代码
  • 本模块旨在替换几个较早的模块和功能,如os.systemos.spawn*

run方法

  • subprocess.run方法在python3.5引入。早期版本可以使用subprocess.call方法
  • 直接执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import subprocess

>>> subprocess.run('ls')

>>> subprocess.run(['ls','/home']) # 不支持环境变量

>>> subprocess.run('ls /home')
FileNotFoundError: [Errno 2] No such file or directory: 'ls /home'

>>> subprocess.run(['ls','~'])
ls: 无法访问~: 没有那个文件或目录
CompletedProcess(args=['ls', '~'], returncode=2)

  • 通过shell执行命令
1
2
3
>>> subprocess.run('ls /home',shell=True)

>>> subprocess.run(['ls','~'],shell=True)
  • run方法返回值
1
2
3
4
5
6
>>> result = subprocess.run('ls /home',shell=True)
wsq
>>> result.args # 显示参数
'ls /home'
>>> result.returncode # 命令执行结果,同 $?
0

输出和错误

  • run方法执行的结果默认打印在屏幕上,也可以通过管道将其存储在标准输出和标准错误中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> result = subprocess.run('id root; id zhangsan',s
hell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

>>> result
CompletedProcess(args='id root; id zhangsan', returncode=1, stdout=b'uid=0(root) gid=0(root) \xe7\xbb\x84=0(root)\n', stderr=b'id: zhangsan: no such user\n')

>>> result.args
'id root; id zhangsan'

>>> result.returncode
1

>>> result.stdout # 输出
b'uid=0(root) gid=0(root) \xe7\xbb\x84=0(root)\n'
>>> result.stdout.decode()
'uid=0(root) gid=0(root) 组=0(root)\n'

>>> result.stderr # 错误
b'id: zhangsan: no such user\n'

>>> print(result.stdout.decode())
uid=0(root) gid=0(root) 组=0(root)

案例:调用ping命令

  • 编写ping函数
  • 用于测试远程主机联通性
  • ping通显示:x.x.x.x:up
  • ping不通显示:x.x.x.x:down
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import subprocess
import sys

def ping(host):
result = subprocess.run('ping -c2 %s >& /dev/null' % host,shell=True)
if result.returncode == 0:
return '%s:up' % host
else:
return '%s:down' % host

if __name__ == '__main__':
host = sys.argv[1]
print(ping(host))

(mypy) [root@python pycharm]# python ping.py baidu.com
baidu.com:up
(mypy) [root@python pycharm]# python ping.py 192.168.1.5
192.168.1.5:down

python语法风格和模块结构布局

语法风格

变量赋值

  • python支持链式多重赋值

    x = y = 10

  • 另一种将多个变量同时赋值的方法称为多元赋值,采用这种方式赋值时,等号两边对象都是元组

    a,b = 10,20

合法标识符

  • python标识符字符串规则和其他大部分用c语言编写的高级语言相似
  • 第一个字符必须是字母或下划线_
  • 剩下的字符可以是字母和数字和下划线
  • 大小写敏感

关键字

  • 和其他的高级语言一样,python也有一些被称作关键字的保留字符
  • 任何语言的关键字应该保持相对的稳定,但是因为python是一门不断成长和进化的语言,其关键字偶尔会更新
  • 关键字列表和iskeyword()函数都放入了keyword模块以便查阅

内键

  • 除了关键字之外。python还有可以在任何一级代码使用的“内建”的名字集合,这些名字可以由解释器设置或使用
  • 虽然built-in不是关键字,但是应该把它当作“系统保留字”

模块结构及布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/local/bin/python3		# 起始行
"this is a test module" # 模块文档字符串

import sys # 导入模块
import os

debug = True # 全局变量声明

class FooClass(object): # 类定义
'Foo class'
pass

def test(): # 定义函数
"test function"
foo = FooClass()

if __name__ == '__main__': # 程序主体
test()

模块布局案例

案例:测试字符串是否为合法标识符

  • 编写用于测试字符串的函数
  • 函数用于确定字符串是否为合法标识符
  • 字符串不能为关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys
import keyword
from string import ascii_letters,digits

frist_str = ascii_letters + '_'
other_str = frist_str + digits

def string(str):

if keyword.iskeyword(str):
return '%s是关键字' % str

if str[0] not in frist_str:
return '第一个字符不合法'

for i in range(len(str)):
if i == 0:
continue
if str[i] not in other_str:
return '%s的第%s字符(%s)不合法' % (str,i+1,str[i])

return '%s是合法标识符' % str

if __name__ == '__main__':
str = sys.argv[1]
print(string(str))


(mypy) [root@python pycharm]# python test.py tg.ds
tg.ds的第2字符(.)不合法
(mypy) [root@python pycharm]# python test.py abc
abc是合法标识符

案例:创建文件

  • 编写一个程序,要求用户输入文件名
  • 如果文件已经存在,要求用重新输入
  • 提示用户输入数据,每行数据先写到列表中
  • 将列表中数据写入用户输入的文件名中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import os

def get_name():
while 1:
fname = input("file name: ")
if not os.path.exists(fname):
break
print("file is exist,please retry")
return fname

def get_content():
content = []
print("Start writing,Enter 'end' to quit")
while 1:
line = input("(end to quit)> ")
if line == 'end':
break
content.append(line)
return content

def wfile(fname,content):
with open(fname,'w') as fobjs:
fobjs.writelines(content)

if __name__ == '__main__':
fname = get_name()
content = get_content()
content = ['%s\n' % line for line in content]
wfile(fname,content)


(mypy) [root@python pycharm]# python mkfile.py
file name: /tmp/a.txt
Start writing,Enter 'end' to quit
(end to quit)> Hello World!
(end to quit)> 2nd line
(end to quit)> end
(mypy) [root@python pycharm]# cat /tmp/a.txt
Hello World!
2nd line

序列对象及字符串格式化

序列

序列类型操作符

序列操作符 作用
seq[ind] 获得下标为ind的元素
seq[ind1:ind2] 获得下标从ind1到ind2间的元素集合
seq * expr 序列重复expr次
seq1 + seq2 连接序列seq1和seq2
obj in seq 判断obj元素是否包含在seq中
obj not in seq 判断obj元素是否不包含在seq中

内建函数

函数 含义
list(iter) 把可迭代对象转换为列表
str(obj) 把obj对象转换为字符串
tuple(iter) 把一个可迭代对象转换为一个元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> list('hello')
['h', 'e', 'l', 'l', 'o']

>>> list(('hello','world'))
['hello', 'world']

>>> str(['hello', 'world'])
"['hello', 'world']"

>>> tuple('hello')
('h', 'e', 'l', 'l', 'o')

>>> tuple(['hello','world'])
('hello', 'world')

内建函数

  • len(seq):返回seq的长度
  • enumerate:接受可迭代对象作为参数,返回一个enumerate对象
  • reversed(seq):接受序列作为参数,返回一个以逆序访问的迭代器
  • sorted(iter):接受可迭代对象作为参数,返回一个有序的列表
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> user = ['tom','jerry','bob','alice']
>>> enumerate(user)
<enumerate object at 0x7f0b2dd05e00>
>>> list(enumerate(user))
[(0, 'tom'), (1, 'jerry'), (2, 'bob'), (3, 'alice')]

>>> reversed(user)
<list_reverseiterator object at 0x7f0b2dda7ca0>
>>> list(reversed(user))
['alice', 'bob', 'jerry', 'tom']

>>> sorted(user)
['alice', 'bob', 'jerry', 'tom']

字符串

字符串操作符

  • 比较操作符:字符串大小按ASCII码值大小进行比较
  • 切片操作符:[]、[:]、[::]
  • 成员关系操作符:in、not in

格式化操作符

  • 字符串可以使用格式化符号来表示特定含义
格式化字符 转换方式
%c 数字转换成字符
%s 优先用str()函数进行字符串转换
%d / %i 转换成有符号十进制数
%o 转换成无符号八进制数
%e / %E 转换成科学计数法
%f / %F 转换成浮点数
  • 字符串可以使用格式化符号来表示特定含义
辅助指令 作用
* 定义宽度或小数点精度
- 左对齐
+ 在正数前面显示加号
在正数前面显示空格
# 在八进制数前面显示零0,在十六进制前面显示‘0x’或者‘0X’
0 显示的数字前面填充0而不是默认的空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>>> '%s is %s years old' % ('tom',20)
'tom is 20 years old'

>>> '%s is %d years old' % ('tom',20)
'tom is 20 years old'

>>> '%s is %d years old' % ('tom',20.5) # %d 不能转换小数
'tom is 20 years old'

>>> '%s is %f years old' % ('tom',20.5)
'tom is 20.500000 years old'

>>> '%s is %.1f years old' % ('tom',20.5) # 保留一位小数
'tom is 20.5 years old'


>>> '%f' % (5 / 3)
'1.666667'

>>> '%.2f' % (5 / 3)
'1.67'

>>> '%6.2f' % (5 / 3) # 总长度为6位,小数位两位
' 1.67'


>>> '%s%s' % ('name','age')
'nameage'

>>> '%10s%8s' % ('name','age') # 默认右对齐
' name age'

>>> '%-10s%-8s' % ('name','age') # 改为左对齐
'name age '


>>> "%c" % 97
'a'
>>> "%o" % 100
'144'
>>> "%#o" % 100
'0o144'
>>> "%#x" % 100
'0x64'
>>> "%e" % 1000000
'000000e+06'
>>> "%E" % 1000000
'1.000000E+06'

原始字符串操作

  • 原始字符串操作符是为了对付那些在字符串中出现的特殊字符
  • 在原始字符串里,所有的字符都是直接按照字面意思来使用,没有转义特殊或不能打印的字符
1
2
3
4
5
6
7
8
9
>>> win_path = 'c:\tmp'
>>> print(win_path)
c: mp

>>> wpath = r'c:\tmp'
>>> print(wpath)
c:\tmp
>>> wpath
'c:\\tmp'

案例:创建用户

  • 编写一个程序,实现创建用户的功能
  • 提示用户输入用户名
  • 随机生成8位密码
  • 创建用户并设置密码
  • 将用户相关信息写入指定文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 之前写的随机密码 randpass.py
import random
from string import ascii_letters, digits
all_chs = ascii_letters + digits

def randpass(n = 8):
result = ''
for i in range(n):
ch = random.choice(all_chs)
result += ch
return result
if __name__ == '__main__':
print(randpass())




import sys
import randpass # 调用随机密码模块(上面自己写的)
import subprocess

def adduser(user,passwd,fname):
result = subprocess.run('id %s &> /dev/null' % user,shell=True)
if result.returncode == 0:
print("%s is exist" % user)
return
subprocess.run('useradd -p %s %s' % (passwd,user),shell=True)

info = """用户信息
用户名: %s
密码: %s
"""% (user,passwd)
with open(fname,'a') as fobj:
fobj.write(info)

if __name__ == '__main__':
passwd = randpass.randpass()
adduser(sys.argv[1],passwd,'/tmp/users.txt')



(mypy) [root@python pycharm]# python createuser.py alice
(mypy) [root@python pycharm]# python createuser.py jerry
(mypy) [root@python pycharm]# cat /tmp/users.txt
用户信息
用户名: alice
密码: gi3K36bE
用户信息
用户名: jerry
密码: Hpj8AV9d

字符串

字符串常用方法

  • string.capitalize():把字符串的第一个字符大写
  • string.center(width):返回一个元字符居中,并且使用空格填充至长度width的新字符串
  • string.count(str,beg=0,end=len(strint)):返回strstring里面出现的次数,如果beg或者end指定则返回指定范围内str出现的次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> s = 'hello world!'
>>> s.capitalize() # 首个字符大写
'Hello world!'
>>> s.title() # 所有字符的首个字符大写
'Hello World!'
>>> s.upper() # 字符转换为大写
'HELLO WORLD!'
>>> s2 = s.upper()
>>> s2
'HELLO WORLD!'
>>> s2.lower()
'hello world!' # 字符转换为小写

>>> s.center(50) # 居中对齐,用空格填充
' hello world! '
>>> s.center(50,'*') # 居中对齐,用*填充
'*******************hello world!*******************'
>>> s.ljust(50,'#') # 左对齐,用#填充
'hello world!######################################'
>>> s.rjust(50,'#') # 右对齐,用#填充
'######################################hello world!'

>>> s.count('l') # 统计字符l在字符串中出现的次数
3
>>> s.count('ll')
1

内建函数

  • string.endswith(obj,beg=0,end=len(string)):检查字符串是否以obj结束,如果beg或者end指定则检查指定的范围内是否以obj结束,如果是,返回True,否则返回False
  • string.islower():如果string中包含至少一个区分大小写的字符,并且所有这些字符都是小写,则返回True,否则返回False
  • string.strip():删除string字符两端的空白
  • string.upper():转换string中的小写字母为大写
  • string.split(str="",num=string.count(str)):以str为分隔符切片string,如果num有指定值,则仅分割num个字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
>>> s
'hello world!'
>>> s.startswith('h') # 是否以h开头
True
>>> s.startswith('hello')
True
>>> s.endswith('world!') # 是否以world!结尾
True


>>> s.islower() # 字符串是否都是小写
True
>>> s.isupper() # 字符串是否都是大写
False


>>> s.isalpha() # 是否都是由字母构成(s中包含空格)
False
>>> 'Hello'.isalpha()
True


>>> 'abc_123'.isidentifier() # 检测是否为标识符
True


>>> s3 = '\thello world ' # \t为Tab键
>>> s3
'\thello world '
>>> print(s3)
hello world
>>> s3.strip() # 去除两端空白字符
'hello world'
>>> s3.lstrip() # 去除左边空白字符
'hello world '
>>> s3.rstrip() # 去除右边空白字符
'\thello world'


>>> s
'hello world!'
>>> s.split() # 切割
['hello', 'world!']
>>> 'hello.tar.gz'.split() # 默认以空格切割
['hello.tar.gz']
>>> 'hello.tar.gz'.split('.') # 指定以.切割
['hello', 'tar', 'gz']

>>> alist = ['tom','jerry','bob','alice']
>>> '_'.join(alist) # 以_为分隔符拼接
'tom_jerry_bob_alice'
>>> ' '.join(alist) # 以空格拼接
'tom jerry bob alice'

列表和元组

列表

创建及访问列表

  • 列表是有序、可变的数据类型
  • 列表中可以包含不同类型的对象
  • 列表可以由[]或工厂函数创建
  • 支持下标及切片操作

更新列表

  • 通过下标只能更新值,不能使用下标添加新值
1
2
3
4
5
6
7
8
>>> alist = [10,35,20,80]
>>> alist[-1] = 100
>>> alist
[10, 35, 20, 100]

>>> alist[1:3] = [30,50,80] # 取出一个片段赋值
>>> alist
[10, 30, 50, 80, 100]

列表常用方法

列表方法 操作
list.append(obj) 向列表中添加一个对象obj
list.count(obj) 返回一个对象obj在列表中出现的次数
list.extend(seq) 把序列seq的内容添加到列表中
list.insert(index,obj) 向索引量为index的位置插入对象obj
list.reverse() 原地翻转列表
list.sort() 排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
>>> alist
[10, 30, 50, 80, 100]

>>> alist.append(22) # 末尾追加对象
>>> alist
[10, 30, 50, 80, 100, 22]

>>> alist.insert(2,8) # 在指定下标处插入对象
>>> alist
[10, 30, 8, 50, 80, 100, 22]

>>> alist.count(8) # 显示指定对象在列表中出现的次数
1

>>> blist = [5,19,23]
>>> alist.extend(blist) # 插入其他列表的内容
>>> alist
[10, 30, 8, 50, 80, 100, 22, 5, 19, 23]
>>> alist.append(blist) # 默认把列表当成一个元素
>>> alist
[10, 30, 8, 50, 80, 100, 22, 5, 19, 23, [5, 19, 23]]

>>> alist.pop() # 弹出/删除,默认弹出最后一位
[5, 19, 23]
>>> alist
[10, 30, 8, 50, 80, 100, 22, 5, 19, 23]
>>> alist.pop(5) # 删除指定下标的元素
100
>>> alist
[10, 30, 8, 50, 80, 22, 5, 19, 23]

>>> alist.index(23) # 返回元素在列表中的下标
8

>>> alist.remove(8) # 删除指定元素,当列表中有多个时,默认删除第一个
>>> alist
[10, 30, 50, 80, 22, 5, 19, 23]

>>> alist.reverse() # 列表翻转
>>> alist
[23, 19, 5, 22, 80, 50, 30, 10]

>>> alist.sort() # 排序,默认升序
>>> alist
[5, 10, 19, 22, 23, 30, 50, 80]

>>> alist.sort(reverse=True) # 降序
>>> alist
[80, 50, 30, 23, 22, 19, 10, 5]

>>> clist = alist.copy() # 把a列表拷贝给c列表,a不会跟随c变化
>>> clist
[80, 50, 30, 23, 22, 19, 10, 5]
>>> clist.append(100)
>>> clist
[80, 50, 30, 23, 22, 19, 10, 5, 100]
>>> alist
[80, 50, 30, 23, 22, 19, 10, 5]

>>> blist = alist # 直接赋值,a会跟随b变化
>>> blist.append(25)
>>> blist
[80, 50, 30, 23, 22, 19, 10, 5, 25]
>>> alist
[80, 50, 30, 23, 22, 19, 10, 5, 25]

>>> alist.clear() # 清除所有内容
>>> alist
[]

元组

创建元组

  • 通过()或工厂函数tuple()创建元组
  • 元组是有序的、不可变类型
  • 与列表类似,作用于列表的操作,绝大多数可以作用于元组
1
2
3
4
5
6
>>> atuble = (10,20,30,7)

>>> atuble.count(7) # 显示指定对象在元组中出现的次数
1
>>> atuble.index(10) # 返回元素在元组中的下标
0

单元素元组

  • 如果一个元组中只有一个元素,那么创建该元组的时候,需要加一个逗号
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> btuple = ('hello')		# 不加逗号,就是一个字符串
>>> print(btuple)
hello
>>> type(btuple)
<class 'str'>

>>> atuble = ('hello',)
>>> print(atuble)
('hello',)
>>> type(atuble)
<class 'tuple'>
>>> len(atuble) # 长度为1
1

列表案例

用列表构建栈结构

  • 栈是一个后进先出的结构
  • 编写一个程序,用列表实现栈结构
  • 需要支持压栈、出栈、查询功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
stack = []

def push_it(): # 压栈
data = input('\033[32;1mdata: \033[0m').strip()
if data:
stack.append(data)
else:
print('\033[31;1mGot nothing\033[0m')

def pop_it(): # 出栈
if stack:
print('\033[31;1mFrom stack, popped: %s\033[0m' % stack.pop())
else:
print('\033[31;1mEmpty stack\033[0m')

def view_it(): # 查询
print('\033[32;1m%s\033[0m' % stack)

def show_menu(): # 菜单
cmds = {'0': push_it, '1': pop_it, '2':view_it}
menu = '''(0) push it
(1) pop it
(2) view it
(3) exit
plesae input your choice(0/1/2/3): '''

while 1:
choice = input(menu)
if choice not in ['0','1','2','3']:
print('Invalid input, try again')
continue

if choice == '3':
print('Bye-bye')
break

cmds[choice]() # 通过字典的key-value特性调用函数

if __name__ == '__main__':
show_menu()

字典及常用方法

字典基础操作

创建字典

  • 通过{}操作符创建字典
  • 通过dict()工厂方法创建字典
  • 通过fromkeys()创建具有相同值的默认字典
1
2
3
4
5
6
7
8
9
>>> adict = {'name': 'bob', 'age': 23}

>>> bdict = dict((['name','bob'],['age',23])) # 可以为字符串、列表、元组,但必须是两个元素
>>> bdict
{'name': 'bob', 'age': 23}

>>> cdict = {}.fromkeys(('bob','alice'),23)
>>> cdict
{'bob': 23, 'alice': 23}

访问字典

  • 字典是映射类型,意味着它没有下标,访问字典中的值需要使用相应的键
1
2
3
4
5
6
7
8
>>> for key in adict:
... print('key=%s,value=%s' % (key,adict[key]))
...
key=name,value=bob
key=age,value=23

>>> print('%(name)s' % adict) # 输出键为name的值
bob

更新字典

  • 通过键更新字典
  • 如果字典中有该键,则更新相关值;没有该键,则向字典中添加新值
1
2
3
4
5
6
7
8
9
10
>>> adict
{'name': 'bob', 'age': 23}

>>> adict['age'] = 18 # 更新
>>> adict
{'name': 'bob', 'age': 18}

>>> adict['email'] = 'bob@tarena.com.cn' # 添加
>>> adict
{'name': 'bob', 'age': 18, 'email': 'bob@tarena.com.cn'}

删除字典

  • 通过del可以删除字典中的元素或整个字典
  • 使用内部方法clear()可以清空字典;pop()方法“弹出”字典中的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> adict
{'name': 'bob', 'age': 18, 'email': 'bob@tarena.com.cn'}

>>> del adict['email']
>>> adict
{'name': 'bob', 'age': 18}

>>> adict.pop('age')
18
>>> adict
{'name': 'bob'}

>>> adict.clear()
>>> adict
{}

字典操作符

  • 使用字典键查找操作符[],查找键所对应的值
  • 使用innot in判断键是否存在于字典中
1
2
3
4
5
6
7
>>> adict = {'name': 'bob', 'age': 23}
>>> adict['name']
'bob'
>>> 'bob' in adict
False
>>> 'name' in adict
True

字典相关函数

作用于字典的函数

  • len():返回字典中元素的数目
  • hash():不是为字典设计,可以判断对象是否可以作为字典的键
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> adict
{'name': 'bob', 'age': 23}

>>> len(adict)
2

>>> hash(3) # 不报错就是可以作为键
3

>>> hash('name')
3465381350475709982

>>> hash([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

字典方法

  • dict.copy():返回字典的副本
  • dist.get(key,default=None):对字典dict中的键key,返回它对应的值value,如果字典中不存在此键,则返回default的值
  • dict.setdefault(key,default=None):如果字典不存在key键,由dict[key]=default为它赋值
  • dict.items():返回一个包含字典中的(键,值)对元组的列表
  • dict.keys():返回一个包含字典中键的列表
  • dict.values():返回一个包含字典所有值的列表
  • dict.update(dict2):将字典dict2的键-值对添加到字典dict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
>>> adict
{'name': 'bob', 'age': 23}

>>> adict.get('name')
'bob'
>>> adict.get('email')
>>> print(adict.get('email'))
None

>>> adict.get('name','not found') # 查找name如果没找到返回not found,字典中有name键就返回name对应的值
'bob'
>>> adict.get('email','not found') # 查找email键,没有返回not found
'not found'

>>> adict.keys() # 列出所有键
dict_keys(['name', 'age'])
>>> adict.values() # 列出所有值
dict_values(['bob', 23])
>>> adict.items() # 同时得到key和value
dict_items([('name', 'bob'), ('age', 23)])
>>> list(adict.items())
[('name', 'bob'), ('age', 23)]

>>> adict.popitem() # 删除随机键值对
('age', 23)
>>> adict.pop('name') # 删除指定键值对
'bob'
>>> adict
{}

>>> bdict = {'email': 'tom@tedu.cn','qq': '123456'}
>>> adict.update(bdict) # 把b字典跟新到a字典
>>> adict
{'email': 'tom@tedu.cn', 'qq': '123456'}

>>> cdict = adict.copy() # 复制
>>> cdict
{'email': 'tom@tedu.cn', 'qq': '123456'}

>>> cdict.clear() # 清空
>>> cdict
{}
>>> adict
{'email': 'tom@tedu.cn', 'qq': '123456'}

>>> adict.setdefault('email','tom@123.com') # 如果不存在email键,就添加值为tom@123.com
'tom@tedu.cn' # 存在返回值
>>> adict.setdefault('age','22')
'22'
>>> adict
{'email': 'tom@tedu.cn', 'qq': '123456', 'age': '22'}

字典案例

案例:模拟用户登录信息系统

  • 支持新用户登录,新用户名和密码注册到字典中
  • 支持老用户登录,用户名和密码正确提示登录成功
  • 主程序通过循环询问进行何种操作,根据用户的选择,执行注册或是登录操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import getpass

users = {}

def Sign_in():
user_name = input('user name: ').strip()
passwd = getpass.getpass('password: ')

# if (user_name not in users) or (passwd != users[user_name]):
if users.get(user_name) != passwd:
print('\033[31;1mLogin failed\033[0m')
else:
print('\033[32;1mLogin successful\033[0m')


def Sign_up():
user_name = input('user name: ').strip()

if user_name and user_name not in users:
passwd = input('password: ')
users[user_name] = passwd
else:
print('\033[31;1mAn invalid user name or user already exists\033[0m')


def login():
cmds = {'1': Sign_in, '2': Sign_up}

menu = '''(1)Sign in
(2)Sign up
(3)exit
plesae input your choice(1/2/3): '''

while 1:
choice = input(menu).strip()

if choice not in ['1','2','3']:
print('\033[31;1mInvalid input, try again\033[0m')
continue

if choice == '3':
print('\033[32;1mBye-bye\033[0m')
break

cmds[choice]()

if __name__ == '__main__':
login()

集合及常用方法

集合基础

创建集合

  • 数学上,把set称作由不同元素组成的集合,集合(set)的成员通常被称作集合元素

  • 集合对象是一组无序列的可哈希的值

  • 集合有两种类型

    可变集合set

    不可变集合frozenset

1
2
3
4
5
6
>>> s1 = set('hello')
>>> s2 = frozenset('hello')
>>> s1
{'h', 'e', 'o', 'l'}
>>> s2
frozenset({'h', 'e', 'o', 'l'}) # 不能重复

集合操作符

  • 集合支持用innot in操作符检查成员
  • 能够通过len()检查集合大小
  • 能够使用for迭代集合成员
  • 不能取切片
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 'h' in s1
True

>>> len(s1)
4

>>> for i in s1:
... print(i)
...
h
e
o
l
  • |:联合,取并集
  • &:交集
  • -:差补
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> s1 = set('abc')
>>> s2 = set('cde')

>>> s1 | s2 # 两个集合中所有的元素,不能重复
{'c', 'a', 'e', 'd', 'b'}

>>> s1 & s2 # 两个集合中都有的元素
{'c'}

>>> s1 - s2 # s1去除与s2相同的元素,剩余的输出
{'b', 'a'}
>>> s2 - s1 # s2去除与s1相同的元素,剩余的输出
{'e', 'd'}

集合方法

集合常用方法

  • set.add():添加成员
  • set.update():批量添加成员
  • set.remove():移除成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> s1.add('new')		# 把字符串当作一个元素
>>> s1
{'b', 'c', 'a', 'new'}

>>> s1.add(('hello','world')) # 把元组当作一个元素
>>> s1
{'a', 'n', 'b', 'c', 'e', ('hello', 'world'), 'w'}

>>> s1.update('new') # 把字符串拆开
>>> s1
{'a', 'n', 'b', 'new', 'c', 'e', 'w'}

>>> s1.update(('hello','world')) # 把元组拆开
>>> s1
{'a', 'n', 'b', 'new', 'c', 'hello', 'e', ('hello', 'world'), 'w', 'world'}

>>> s1.remove('new')
>>> s1
{'a', 'n', 'b', 'c', 'hello', 'e', ('hello', 'world'), 'w', 'world'}
  • s.issubset(t):如果st的子集,则返回True,否则返回False
  • s.issuperset(t):如果ts的超集,则返回True,否则返回False
  • s.union(t):返回一个新集合,该集合是st的并集
  • s.intersection(t):返回一个新集合,该集合是st的交集
  • s.difference(t):返回一个新集合,该集合是s的成员,但不能是t的成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> aset = set('abcde')
>>> bset = set('bcd') # a子集包含b子集,a集合是b集合的超集,b集合是a集合的子集

>>> aset.issubset(bset) # 判断a集合是否为b集合的子集
False

>>> aset.issuperset(bset) # 判断a集合是否为b集合的超集
True

>>> bset.issubset(aset) # 判断b集合是否为a集合的子集
True

>>> aset.union(bset) # 返回a集合和b集合的并集
{'c', 'a', 'e', 'd', 'b'}

>>> aset.intersection(bset) # 返回a集合和b集合的交集
{'b', 'c', 'd'}

>>> aset.difference(bset) # 返回一个集合,是a集合的元素,但不是b集合的元素
{'a', 'e'}

集合案例

案例:去重

  • 创建一个具有20个随机数字的列表
  • 去除列表中重复的数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> from random import randint

>>> nums = [randint(1,50) for i in range(20)]

>>> nums
[14, 15, 28, 26, 8, 28, 36, 18, 46, 18, 19, 28, 13, 21, 45, 16, 5, 43, 15, 15]
>>> len(nums)
20

>>> aset = set(nums)
>>> aset
{36, 5, 8, 43, 13, 14, 15, 46, 45, 18, 19, 16, 21, 26, 28}
>>> len(aset)
15

>>> alist = list(aset) # 转换为列表
>>> alist
[36, 5, 8, 43, 13, 14, 15, 46, 45, 18, 19, 16, 21, 26, 28]

案例: 比较文件内容

  • 有两个文件:a.log和b.log
  • 两个文件中有大量重复内容
  • 取出只有在b.log中存在的行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(mypy) [root@python pycharm]# cp /etc/passwd /tmp/a.log
(mypy) [root@python pycharm]# cp /etc/passwd /tmp/b.log

# 在a.log中随机删除几行
# 在b.log中多复制几行,使其存在重复行,再添加几行新行

(mypy) [root@python pycharm]# wc -l /tmp/a.log /tmp/b.log
33 /tmp/a.log
85 /tmp/b.log
118 总用量



fname1 = '/tmp/a.log'
fname2 = '/tmp/b.log'

with open(fname1) as fobj1:
aset = set(fobj1)

with open(fname2) as fobj2: # 集合自动去重
bset = set(fobj2)

with open('/tmp/c.log','w') as fobj3:
fobj3.writelines(bset - aset) # 取差补



(mypy) [root@python pycharm]# wc -l /tmp/c.log
12 /tmp/c.log

time模块

时间表示方式

  • 时间戳timestamp:表示的是从1970年1月1日00:00:00开始按秒计算的偏移量
  • UTC(Coordinated Universal Time,世界协调时)亦即格林威治天文时间,世界标准时间。在中国为UTC+8DST(Daylight Saving Time)即夏令时
  • 元组(struct_time):由9个元素组成

struct_time元组

索引 属性
0 tm_year 2000
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
6 tm_wday 0-6(0表示周一)
7 tm_yday(一年中的第几天) 1-366
8 tm_isdst(是否为dst时间) 默认为-1

time模块方法

  • time.localtime([secs]):将一个时间戳转换为当前时区的struct_timesecs未提供,则以当前时间为准
  • time.gmtime([secs]):和localtime()方法类似,gmtime()方法是将一个时间戳转换为UTC时区(0时区)的struct_time
  • time.time():返回当前时间的时间戳
  • time.mktime(t):将一个struct_time转化为时间戳
  • time.sleep(secs):线程推迟指定的时间运行。单位为秒
  • time.asctime([t]):把一个时间的元组或者struct_time表示为这种形式:‘Sun Jun 20 23:23:05 1993’ 。如果没有参数,将会将time.localtime()作为参数传入
  • time.ctime([secs]):把一个时间戳(按秒计算的浮点数)转换为time.asctime()形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> import time

>>> time.time() # 时间戳
1683279106.127239

>>> time.ctime() # 本机时间
'Fri May 5 17:32:11 2023'

>>> time.localtime() # 当前时间元组
time.struct_time(tm_year=2023, tm_mon=5, tm_mday=5, tm_hour=17, tm_min=32, tm_sec=19, tm_wday=4, tm_yday=125, tm_isdst=0)

>>> time.gmtime()
time.struct_time(tm_year=2023, tm_mon=5, tm_mday=5, tm_hour=10, tm_min=3, tm_sec=27, tm_wday=4, tm_yday=125, tm_isdst=0)

>>> atime = time.localtime()
>>> time.mktime(atime) # 把元组转换为时间戳
1683281060.0

>>> time.sleep(3) # 休眠3秒

>>> time.strftime('%Y-%m-%d %H:%M:%S') # 把当前时间转换为字符串
'2023-05-05 18:06:22'

# 把字符串转换为元组,需要指定格式
>>> time.strptime('2050-1-1 8:00:00', '%Y-%m-%d %H:%M:%S')
time.struct_time(tm_year=2050, tm_mon=1, tm_mday=1, tm_hour=8, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=1, tm_isdst=-1)
  • time.strftime(format[,t]):把一个代表时间的元组或者struct_time(如由time.localtime()time.gmtime()返回)转化为格式化的时间字符串。如果t未指定,将传入time.localtime()
  • time.strptime(string[,format]):把一个格式化时间字符串转化为struct_time。实际上它与strftime()是逆操作

时间样式

格式 含义 格式 含义
%a 本地简化星期名称 %m 月份(01-12)
%A 本地完整星期名称 %M 分钟数(00-59)
%b 本地简化月份名称 %p 本地am或者pm的相应符
%B 本地完整月份名称 %S 秒(01-61)
%c 本地相应的日期和时间 %U 一年中的星期数(00-53,星期日是一个星期的开始)
%d 一个月中的第几天(01-31) %w 一个星期中的第几天(0-6,0是星期天)
%H 一天中的第几个小时(24小时制,00-23) %x 本地相应日期
%I 第几个小时(12小时制,01-12) %X 本地相应时间
%j 一年中的第几天(001-336) %y 去掉世纪的年份(00-59)
%Z 时区的名字 %Y 完整的年份

案例:编写进度条

  • 模拟一个进度条程序
  • 首先在屏幕上打印一行#
  • @符号在这行#号中穿过去
  • 当@符号到达尾部,再返回开头
1
2
3
4
5
6
7
8
9
10
11
12
import time

print('#' * 20, end='')
counter = 0

while 1:
time.sleep(0.3)
print('\r%s@%s' % ('#' * counter,'#' * (19 - counter)),end='')
counter += 1

if counter == 20:
counter = 0

datetime模块

datetime模块方法

  • datetime.today():返回一个表示当前本地时间的datetime对象
  • datetime.now([tz]):返回一个表示当前本地时间的datetime对象,如果提供了参数tz,则获取tz参数所指时区的本地时间
  • datetime.strptime(date_string, format):将格式字符串转换为datetime对象
  • datetime.ctime(datetime对象):返回时间格式字符串
  • datetime.strftime(format):返回指定格式字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> from datetime import datetime
>>> datetime.now()
datetime.datetime(2023, 5, 5, 20, 19, 25, 716231)
>>> t = datetime.now()

# 年、月、日、时、分、秒、毫秒
>>> t.year, t.month, t.day, t.hour, t.minute, t.second, t.microsecond
(2023, 5, 5, 20, 19, 38, 642509)

>>> t.time()
datetime.time(20, 19, 38, 642509)

>>> t.today()
datetime.datetime(2023, 5, 5, 20, 21, 35, 599556)

>>> t.strftime('%Y/%m/%d %H:%M:%S')
'2023/05/05 20:19:38'

>>> datetime.strptime('2050-1-1 8:00:00', '%Y-%m-%d %H:%M:%S')
datetime.datetime(2050, 1, 1, 8, 0)

时间计算

  • 使用timedelta可以方便的在日期上做天days,小时hours,分钟,秒,毫秒,微秒的时间计算
1
2
3
4
5
6
7
8
9
10
11
12
>>> import datetime

>>> t = datetime.datetime.now()
>>> t
datetime.datetime(2023, 5, 5, 20, 31, 47, 230203)

>>> days = datetime.timedelta(days=100,hours=3)
>>> days
datetime.timedelta(days=100, seconds=10800)

>>> t + days
datetime.datetime(2023, 8, 13, 23, 31, 47, 230203)

案例:取出指定时间段的文本

  • 有一个日志文件,按时间先后顺序记录日志
  • 给定时间范围,取出该范围内的日志(9点–12点)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(mypy) [root@python pycharm]# vim /tmp/myweb.log
2030-01-02 08:01:43 aaaaaaaaaaaaaaaaaaaaaaaa
2030-01-02 08:34:23 bbbbbbbbbbbbbbbbbbbbbbbb
2030-01-02 09:23:12 cccccccccccccccccccccccc
2030-01-02 10:56:13 dddddddddddddddddddddddd
2030-01-02 11:38:19 eeeeeeeeeeeeeeeeeeeeeeee
2030-01-02 12:02:28 ffffffffffffffffffffffff



from datetime import datetime

t9 = datetime.strptime('2030-01-02 09:00:00','%Y-%m-%d %H:%M:%S')
t12 = datetime.strptime('2030-01-02 12:00:00','%Y-%m-%d %H:%M:%S')

with open('/tmp/myweb.log') as fobj:
for line in fobj:
t = datetime.strptime(line[:19], '%Y-%m-%d %H:%M:%S')

# if t9 <= t <= t12: # 若是有几万行就会很慢
# print(line,end='')

if t > t12:
break

if t >= t9:
print(line, end='')

异常处理

异常

  • 当python检测到一个错误时,解释器就会指出当前流已经无法继续执行下去,这时候就出现了异常

  • 异常是因为程序出现了错误而在正常控制流以外采取的行动

  • 这个行为又分为两个阶段:

    首先是引起异常发生的错误

    然后是检测(和采取可能的措施)阶段

python中的异常

  • 当程序运行时,因为遇到未解的错误而导致中止运行,便会出现traceback消息,打印异常
异常 描述
NameError 未声明/初始化对象
IndexError 序列中没有此索引
SyntaxError 语法错误
KeyboardInterrupt 用户中断执行
EOFError 没有内建输入,到达EOF标记
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> a + 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

>>> 'hello'[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range

>>> a = 10
>>> if a = 10:
File "<stdin>", line 1
if a = 10:
^
SyntaxError: invalid syntax

>>> # Ctrl + c
KeyboardInterrupt

>>> n = input('number: ')
number: Traceback (most recent call last): # Ctrl + d 退出输入
File "<stdin>", line 1, in <module>
EOFError

try-except语句

  • 定义了进行异常监控的一段代码,并且提供了处理异常的机制
1
2
3
4
5
6
7
8
9
10
11
12
try:
try_suite # 监控这里的异常
except Exception[as reason]:
except_suite # 异常处理代码


>>> try:
... f = open('foo.txt')
... except FileNotFoundError:
... print('No such file')
...
No such file
  • 可以把多个except语句连接在一起,处理一个try块中可能发生的多种异常
1
2
3
4
5
6
7
8
9
>>> try:
... data = int(input('input a number: '))
... except KeyboardInterrupt:
... print('user cancelled')
... except ValueError:
... print('you must input a number')
...
input a number: hello
you must input a number

异常参数

  • 异常也可以有参数,异常引发后它会传递给异常处理器
  • 当异常被引发后参数是作为附加帮助信息传递给异常处理器的
1
2
3
4
5
6
>>> try:
... 10/0
... except ZeroDivisionError as e:
... print('error',e)
...
error division by zero

else语句

  • try范围中没有异常被检测到时,执行else子句
  • else范围中的任何代码运行前,try中的所有代码必须完全成功
1
2
3
4
5
6
7
8
9
>>> try:
... result = 100 / int(input('number: '))
... except Exception as e:
... print('Error: ',e)
... else:
... print(result)
...
number: 10
10.0

finally子句

  • finally子句是无论异常是否发生,是否捕捉都会执行的一段代码
  • 如果打开文件后,因为发生异常导致文件没有关闭,可能会发生数据损坏。使用finally可以保证文件总是能正常的关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> try:
... n = int(input('num: '))
... except ValueError as e:
... print('Error: ',e)
... except (KeyboardInterrupt, EOFError):
... print('\nBye-bye')
... else:
... print(n)
... finally:
... print('Done')
...
num: 3
3
Done

案例:简化除法判断

  • 提示用户输入一个数字作为除数
  • 如果用户按下Ctrl+C或Ctrl+D则退出程序
  • 如果用户输入非数字字符,提示用户应该输入数字
  • 如果用户输入0,提示用户0不能作为除数
1
2
3
4
5
6
7
8
9
10
11
12
try:
n = 100 / int(input('输入一个除数: '))

except (ValueError, ZeroDivisionError) as e:
print('输入错误: ',e)

except (KeyboardInterrupt,EOFError):
print('退出')
exit()

else:
print(n)

自定义异常

触发异常

raise语句

  • 想要引发异常,最简单的形式就是输入关键字raise,后面跟要引发的异常的名称
  • 只从raise语句时,Python会创建指定的异常类的一个对象
  • raise语句还可以指定对异常对象进行初始化的参数
1
2
3
4
5
6
7
8
9
>>> raise ValueError()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError

>>> raise ValueError('num is error')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: num is error

断言

  • 断言是一句必须等价于布尔值为真的判定
  • 此外,发生异常也意味着表达式为假
1
2
3
4
5
6
>>> assert 10 > 100, "Wrong"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Wrong
>>> assert 10 < 100
>>>

案例:自定义异常

  • 编写第一个函数,函数接收姓名和年龄,如果年龄不在1-120之间,产生ValueError异常
  • 编写第二个函数,函数接受姓名和年龄,如果年龄不在1-120之间,产生断言异常
1
2
3
4
5
6
7
8
9
10
11
def get_info1(name,age):
if not 0 < age < 120:
raise ValueError()
print('name=%s, age=%s' % (name,age))

def get_info2(name,age):
assert 0 < age < 120, 'Error'
print('name=%s, age=%s' % (name, age))

if __name__ == '__main__':
get_info2('bob',100)

os模块

os模块简介

  • 对文件系统的访问大多数通过python的os模块实现
  • 该模块是python访问操作系统功能的主要接口
  • 有些方法,如copy等,并没有提供,可以使用shutil模块作为补充

os模块方法

函数 作用
symlink() 创建符号链接
listdir() 列出指定目录的文件
getcwd() 返回当前工作目录
mkdir() 创建目录
chmod() 改变权限模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
>>> import os

>>> os.getcwd() # 返回当前目录
'/root/pycharm'

>>> os.listdir('/opt') # 默认为当前路径
['rh', 'centos', 'ls']

>>> os.mkdir('/tmp/mytest')
>>> os.makedirs('/tmp/a/b/c/d') # 创建层级目录

>>> os.chdir('/tmp/a/b/c/d') # 切换目录
>>> os.getcwd()
'/tmp/a/b/c/d'


>>> import shutil # os模块没有copy

>>> shutil.copy('/etc/hosts', '.') # 拷贝
'./hosts'
>>> os.listdir()
['hosts']

>>> os.symlink('/etc/passwd','mima') # 创建链接 等同于ln -s
>>> os.listdir()
['hosts', 'mima']

>>> os.chmod('hosts',0o600) # 改权限,修改为8进制数
>>> 0o600
384
>>> os.chmod('hosts',384)

>>> os.remove('mima') # 删除文件

>>> os.rmdir('/tmp/mytest') # 删除空目录

>>> os.unlink('mima') # 删除链接文件



>>> os.mkdir('abc')
>>> os.path.abspath('abc') # 列出指定文件的绝对路径
'/tmp/a/b/c/d/abc'

>>> os.path.basename('/tmp/a/b/c/d/abc') # 列出文件名
'abc'

>>> os.path.basename('/tmp/a/b/c/d/abc/') # 只返回/最左端
''

>>> os.path.dirname('/tmp/a/b/c/d/abc') # 列出所在目录名
'/tmp/a/b/c/d'

>>> os.path.split('/tmp/a/b/c/d/abc') # 列出所在目录和文件名
('/tmp/a/b/c/d', 'abc')

>>> os.path.splitext('tedu.txt') # 拓展名切割
('tedu', '.txt')

>>> os.path.join('/tmp/a/b/c/d', 'abc') # 路径拼接
'/tmp/a/b/c/d/abc'

>>> os.path.isabs('/tmp/abc/xyz') # 判断是否为绝对路径,文件可以不存在
True

>>> os.path.isdir('/tmp/abc/xyz') # 判断是否为目录
False
>>> os.path.isdir('/tmp/a/b/c') # 文件必须存在
True

>>> os.path.isfile('/etc/hosts') # 判断是否为文件
True

>>> os.path.islink('/etc/grub2.cfg') # 判断是否为链接
True

>>> os.path.ismount('/') # 判断是否为一个挂载点
True

>>> os.path.exists('/etc/hostname') 判断是否存在
True

案例:操作文件系统

  • 编写脚本
  • 切换到/tmp目录下
  • 创建example目录
  • 切换到/tmp/example目录
  • 创建test文件,并写入字符串foo bar
  • 列出/tmp/example目录内容
  • 打印test文件内容
  • 反向操作,把test文件以及example目录删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os
import shutil

os.chdir('/tmp')
os.mkdir('example')
os.chdir('/tmp/example')

with open('/tmp/example/test','w') as fobj:
fobj.write('foo bar')

os.listdir('/tmp/example')

with open('/tmp/example/test','r') as fobj:
a = fobj.read()
print(a)

os.chdir('/root')
shutil.rmtree('/tmp/example')

pickle模块及案例

pickle模块

  • 把数据写入文件时,常规的文件方法只能把字符串对象写入。其他数据需要先转换成字符串再写入文件
  • python提供了一个标准的模块,称为pickle。使用它可以在一个文件中存储任何python对象,之后又可以把它完整无缺的取出来
1
2
3
4
5
>>> f = open('/tmp/a.data','w')
>>> f.write(100)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: write() argument must be str, not int

pickle模块方法

  • 分别调用dump()load()可以存储、写入
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import pickle

>>> shopping_file = 'shoplist.data' # 文件名
>>> shopping_list = ['apple','mango','carrot'] #数据

>>> f = open(shopping_file,'wb')
>>> pickle.dump(shopping_list,f)
>>> f.close()
>>>
>>> f = open(shopping_file,'rb')
>>> stored_list = pickle.load(f)
>>> stored_list
['apple', 'mango', 'carrot']

案例:记账程序

  • 假设在记账时,有一万块钱
  • 无论时开销还是收入都要进行记账
  • 记账内容包括时间、金额和说明
  • 记账数据要求永久存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import os
import pickle
from time import strftime

def save(fname):
date = strftime('%Y-%m-%d')
amount = int(input('金额: '))
comment = input('备注: ')

with open(fname,'rb') as fobj:
content = pickle.load(fobj)

balance = content[-1][-2] + amount
line = [date,amount,0,balance,comment]
content.append(line)

with open(fname,'wb') as fobj:
pickle.dump(content,fobj)


def cost(fname):
date = strftime('%Y-%m-%d')
amount = int(input('金额: '))
comment = input('备注: ')

with open(fname, 'rb') as fobj:
content = pickle.load(fobj)

balance = content[-1][-2] - amount
line = [date, 0, amount, balance, comment]
content.append(line)

with open(fname, 'wb') as fobj:
pickle.dump(content, fobj)

def query(fname):
print('%-12s%-8s%-8s%-12s%-20s' % ('date','save','cost','balance','comment'))
with open(fname,'rb') as fobj:
content = pickle.load(fobj)

for line in content: # 每一个%s对应元组中的每一个元素
print('%-12s%-8s%-8s%-12s%-20s' % tuple(line))

def show_menu():
cmds = {'1': save,'2': cost,'3':query}
fname = 'account.data'
init_data = [['2000-1-1',0,0,10000,'init data']]
prompt = """(1)收入
(2)支出
(3)查询
(4)退出
请输入你的选择(1/2/3/4): """

if not os.path.exists(fname):
with open(fname,'wb') as fobj:
pickle.dump(init_data,fobj)

while 1:
choice = input(prompt)

if choice not in ['1','2','3','4']:
print('无效的选择,请重试')
continue

if choice == '4':
print('\nBye-bye')
break

cmds[choice](fname)

if __name__ == '__main__':
show_menu()

函数进阶

创建函数

def语句

  • 创建函数用def语句创建,语法如下:
1
2
3
def function_name(arguments):
"function_documentation_string"
function_body_suite
  • 标题行由def关键字、函数的名字,以及参数的集合(如果有的话)组成
  • def子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必须的函数体

前向引用

  • 函数不允许在函数为声明之前对其进行引用或者调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def foo():
print('in foo')
bar()

foo() # 报错,因为bar没有定义
#----------------------------------
def foo():
print('in foo')
bar()

def bar():
print('in bar')

foo() # 正常执行,虽然bar的定义在foo定义后面

调用函数

函数操作符

  • 使用一对圆括号()调用函数,如果没有圆括号,只是对函数的引用
  • 任何输入的参数都必须放置在括号中
1
2
3
4
5
6
7
>>> def foo():
... print('hello world')
...
>>> foo()
hello world
>>> foo
<function foo at 0x7f0c528c29d0>

关键字参数

  • 关键字参数的概念仅仅针对函数的调用
  • 这种理念是让调用者通过函数调用中的参数名字来区分参数
  • 这样规范允许参数缺失或者不按顺序
1
2
3
4
5
6
7
8
9
>>> def get_info(name,age):
... print("%s is %s years old" % (name,age))
...

>>> get_info(23,'bob')
23 is bob years old

>>> get_info(age = 23,name = 'bob')
bob is 23 years old

参数组

  • python允许程序员执行一个没有显示定义参数的函数
  • 相应的方法是通过一个把元组(非关键字参数)或字典(关键字参数)作为参数组传递给函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func(*tuple_grp_nonkw_args,**dict_grp_kw_args)

>>> def func1(*args):
... print(args)
...
>>> func1()
()
>>> func1('tom',123,'bob',123)
('tom', 123, 'bob', 123)


>>> def func2(**kw_args):
... print(kw_args)
...
>>> func2()
{}
>>> func2(name='tom',age=20)
{'name': 'tom', 'age': 20}


>>> def func3(a, b):
... return a + b
...
>>> nums = (10,20)
>>> func3(*nums) # 通过元组或列表传参数
30

>>> def func4(name, age):
... print(name,age)
...
>>> adict = {'age':20,'name':'tom'}
>>> func4(**adict) # 通过字典传参数
tom 20

案例:简单的加减法数学游戏

  • 随机生成两个100以内的数字
  • 随机选择加法或是减法
  • 总是使用最大的数字减去最小的数字
  • 如果用户答错三次,程序给出正确答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import random

def exam():
num = [random.randint(1,100) for i in range(2)]
num.sort(reverse=True)

op = random.choice('+-')

if op == '+':
result = num[0] + num[1]
else:
result = num[0] - num[1]

prompt = '%s %s %s = ' % (num[0],op,num[1])

counter = 0
while counter < 3:
try:
aswer = int(input(prompt))
except:
print()
continue

if aswer == result:
print('Very Good!!!')
break

print('Wrong Answer!!!')
counter += 1
else:
print('%s%s' % (prompt,result))

def game():
while 1:
exam()
try:
yn = input('Continue(y/n)? ').strip()[0]
except IndexError:
yn = 'y'
except (KeyboardInterrupt,EOFError):
yn = 'n'

if yn in 'nN':
print('\nBye-bye')
break

if __name__ == '__main__':
game()

匿名函数

lambda

  • python允许用lambda关键字创造匿名函数
  • 匿名函数是因为不需要以标准的def方式来声明
  • 一个完整的lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行
1
2
3
4
5
lambda [arg1[,arg2,...argN]]: expression

>>> a = lambda x,y: x + y
>>> print(a(3,4))
7

filter()函数

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


>>> data = filter(lambda x: x % 2,[num for num in range(10)])
>>> data
<filter object at 0x7f4b1da50dc0>
>>> list(data) # 过滤出10以内的奇数
[1, 3, 5, 7, 9]

map()函数

  • map(func,seq):调用一个函数func来迭代遍历每个序列中的元素;返回一个经过func处理过的元素序列
1
2
3
>>> data = map(lambda x: x * 2 + 1, [num for num in range(10)])
>>> list(data)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

reduce()函数

  • reduce(func,seq):调用一个函数func来迭代遍历每个序列中的元素;返回一个经过func处理后得到的结果
1
2
3
>>> from functools import reduce
>>> reduce(lambda a,b:a+b,[1,2,3]) # 先遍历前两个参数,把计算结果和第三个参数传入
6

案例:修改数学游戏程序

  • 将数学运算改为匿名函数形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import random

def exam():
cmds = {'+': lambda x,y: x + y,'-': lambda x,y: x - y}
num = [random.randint(1,100) for i in range(2)]
num.sort(reverse=True)

op = random.choice('+-')

result = cmds[op](*num)


# if op == '+':
# result = num[0] + num[1]
# else:
# result = num[0] - num[1]

prompt = '%s %s %s = ' % (num[0],op,num[1])

counter = 0
while counter < 3:
try:
aswer = int(input(prompt))
except:
print()
continue

if aswer == result:
print('Very Good!!!')
break

print('Wrong Answer!!!')
counter += 1
else:
print('%s%s' % (prompt,result))

def game():
while 1:
exam()
try:
yn = input('Continue(y/n)? ').strip()[0]
except IndexError:
yn = 'y'
except (KeyboardInterrupt,EOFError):
yn = 'n'

if yn in 'nN':
print('\nBye-bye')
break

if __name__ == '__main__':
game()

变量作用域及偏函数

变量作用域

全局变量

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

局部变量

  • 局部变量只是暂时的存在,仅仅只依赖于定义它们的函数现阶段是否处于活动

  • 当一个函数调用出现时,其局部变量就进入它们的作用域。在那一刻,一个新的局部变量名就为那个对象创建了

  • 一旦函数完成,框架被释放,变量就会离开作用域

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

1
2
3
4
5
6
7
8
9
>>> x = 4
>>> def foo():
... x = 10
... print('in foo, x=',x)
...
>>> foo()
in foo, x= 10
>>> print('in main,x=',x)
in main,x= 4

global语句

  • 因为全局变量的名字能被局部变量给遮盖掉,所以为了明确的引用一个已命名的全局变量,必须使用global语句
1
2
3
4
5
6
7
8
9
10
>>> x = 4
>>> def foo():
... global x
... x = 10
... print('in foo, x=',x)
...
>>> foo()
in foo, x= 10
>>> print('in main,x=',x)
in main,x= 10

函数式编程

偏函数

  • 偏函数的概念是将函数式编程的概念和默认参数以及可变参数结合在一起
  • 一个带有多个参数的函数,如果其中某些参数基本上固定的,那么就可以通过偏函数为这些参数赋值
  • 通过partial来改造另外一个函数,把某一些参数固定,生成新函数
1
2
3
4
5
6
7
8
9
10
11
>>> from functools import partial

>>> def add(a,b,c,d,e):
... return a+b+c+d+e
...
>>> add(10,20,30,40,1)
101

>>> newadd = partial(add,10,20,30,40) # 固定前4个参数
>>> newadd(1)
101

案例:创建进制转换函数

  • 修改int函数,生成的新函数int2可以转换成2进制数据
  • 修改int函数,生成的新函数int8可以转换成8进制数据
  • 修改int函数,生成的新函数int16可以转换成16进制数据
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from functools import partial

>>> int2 = partial(int,base=2)
>>> int2('101011')
43

>>> int8 = partial(int,base=8)
>>> int8('10')
8

>>> int16 = partial(int,base=16)
>>> int16('10')
16

递归函数

递归函数

  • 如果函数包含了对其自身的调用,该函数就是递归的
  • 在操作系统中,查看某一目录内所有文件等操作都是递归的应用
1
2
3
4
5
6
7
8
9
10
>>> def func(num):
... if num == 1:
... return 1
... else:
... return num * func(num - 1)
...
>>> print(func(5))
120
>>> print(func(10))
3628800

案例:快速排序

  • 随机生成10个数字
  • 利用递归,实现快速排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from random import randint

def qsort(seq):
if len(seq) < 2:
return seq

middle = seq[0]
smaller = []
larger = []

for item in seq[1:]:
if item <= middle:
smaller.append(item)
else:
larger.append(item)

return qsort(smaller) + [middle] + qsort(larger)


if __name__ == '__main__':
nums = [randint(1,100) for i in range(10)]
print(nums)
print(qsort(nums))

生成器

生成器

  • 从句法上讲,生成器是一个带yield语句的函数
  • 一个函数或者子程序只返回一次,但一个生成器能暂停执行并返回一个中间的结果
  • yield语句返回一个值给调用者并暂停执行
  • 当生成器的next()方法被调用的时候,它会准确的从离开地方继续
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> def mygen():
... yield 100 # 相当于return,可以有多个
... a = 10 + 5
... yield a
... yield 'hello world'
...
>>> mg = mygen()
>>> mg
<generator object mygen at 0x7f279c76f190>

>>> list(mg)
[100, 15, 'hello world']
>>> list(mg) # 通过yield返回的值只能取一次值
[]

>>> mg1 = mygen() # 需要重新赋值
>>> for data in mg1:
... print(data)
...
100
15
hello world

生成器表达式

  • 生成器的另一种形式是生成器表达式
  • 生成器表达式的语法与列表解析一样
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from random import randint
>>> nums = (randint(1,100) for i in range(5)) # 比列表节省空间
>>> nums
<generator object <genexpr> at 0x7f279c76feb0>
>>> for i in nums:
... print(i)
...
53
5
37
38
53

案例:文件生成器

  • 通过生成器完成以下功能:
  • 使用函数实现生成器
  • 函数接受一个文件对象作为参数
  • 生成器函数每次返回文件的10行数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def file(fobj):
lines = []
counter = 0

for line in fobj:
lines.append(line)
counter += 1
if counter == 10:
yield lines
lines = []
counter = 0

if lines:
yield lines


if __name__ == '__main__':
fname = '/etc/passwd'
fobj = open(fname)
for block in file(fobj):
print(block)

fobj.close()

模块详解

模块和文件

什么是模块

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

模块文件

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

搜索路径

  • 模块的导入需要一个叫做“搜索路径”的过程
  • python在文件系统“预定义区域”中查找要调用的模块
  • 搜索路径在sys.path中定义
  • 也可以通过PYTHONPATH环境变量引入自定义目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> import sys
>>> sys.path
['', '/usr/local/lib/python39.zip', '/usr/local/lib/python3.9', '/usr/local/lib/python3.9/lib-dynload', '/root/mypy/lib/python3.9/site-packages']

(mypy) [root@python pycharm]# mkdir /tmp/mylib
(mypy) [root@python pycharm]# mv ping.py /tmp/mylib/
(mypy) [root@python pycharm]# python
>>> import ping
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'ping'

(mypy) [root@python pycharm]# export PYTHONPATH=/tmp/mylib
(mypy) [root@python pycharm]# python
>>> import ping

内置模块

tarfile模块

  • tarfile模块允许创建、访问tar文件
  • 同时支持gzip、bzip2格式
1
2
3
4
5
6
7
8
>>> import tarfile
>>> tar = tarfile.open('/tmp/passwd.tar.gz','w:gz') # 以写模式打开,为gz类型
>>> tar.add('/tmp/passwd') # 压缩文件
>>> tar.close()

>>> tar = tarfile.open('/tmp/passwd.tar.gz')
>>> tar.extractall(path='/var/tmp') # 解压文件,默认当前路径
>>> tar.close()

hashlib模块

  • hashlib用来替换md5sha模块,并使它们的API一致,专门提供hash算法
  • 包括md5、sha1、sha224、sha256、sha384、sha512,使用非常简单方便
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import hashlib
>>> m = hashlib.md5(b'123456') # 把123456传入
>>> m.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'

>>> m1 = hashlib.md5()
>>> m1.update(b'12') # 更新/添加数据
>>> m1.update(b'34')
>>> m1.update(b'56')
>>> m1.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'

>>> m2 = hashlib.md5()
>>> m2.update(b'hello world!')
>>> m2.hexdigest()
'fc3ff98e8c6a0d3087d515c0473f8677'

案例:计算文件md5值

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

def check_md5(fname):
m = hashlib.md5()
with open(fname,'rb') as fobj:
while 1:
data = fobj.read(4096)
if not data:
break
m.update(data)

return m.hexdigest()

if __name__ == '__main__':
# print(check_md5(sys.argv[1]))
print(check_md5('/etc/hosts'))

模块作业讲解

案例:备份程序

  • 需要支持完全备份和增量备份
  • 周一执行完全备份
  • 其他时间执行增量备份
  • 备份文件需要打包为tar文件并使用gzip格式压缩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 需要拼接出文件名
>>> from time import strftime
>>> import os
>>> src = '/tmp/demo/security'
>>> dst = '/tmp/demo/backup'

>>> fname = os.path.basename(src) # 取出单个文件名
>>> fname
'security'

>>> fname = '%s_full_%s.tar.gz' % (fname, strftime('%Y%m%d')) # 取出最后文件名
>>> fname
'security_full_20230508.tar.gz'

>>> os.path.join(dst,fname) # 拼接
'/tmp/demo/backup/security_full_20230508.tar.gz'


# 通过md5值来判断是否新增数据,而md5值的计算需要知道目录下有多少文件
>>> os.walk('/tmp/demo/security')
<generator object _walk at 0x7f92b2e65b30>
>>> list(os.walk('/tmp/demo/security'))
[('/tmp/demo/security', ['console.apps', 'console.perms.d', 'limits.d', 'namespace.d'], ['pwquality.conf', 'access.conf', 'chroot.conf', 'console.handlers', 'console.perms', 'group.conf', 'limits.conf', 'namespace.conf', 'namespace.init', 'opasswd', 'pam_env.conf', 'sepermit.conf', 'time.conf']), ('/tmp/demo/security/console.apps', [], ['xserver', 'config-util', 'liveinst', 'setup']), ('/tmp/demo/security/console.perms.d', [], []), ('/tmp/demo/security/limits.d', [], ['20-nproc.conf']), ('/tmp/demo/security/namespace.d', [], [])]


>>> alist = list(os.walk('/tmp/demo/security'))

>>> alist[0] # (目录),[子目录],[目录下文件]
('/tmp/demo/security', ['console.apps', 'console.perms.d', 'limits.d', 'namespace.d'], ['pwquality.conf', 'access.conf', 'chroot.conf', 'console.handlers', 'console.perms', 'group.conf', 'limits.conf', 'namespace.conf', 'namespace.init', 'opasswd', 'pam_env.conf', 'sepermit.conf', 'time.conf'])
>>> alist[1] # 接着遍历子目录
('/tmp/demo/security/console.apps', [], ['xserver', 'config-util', 'liveinst', 'setup'])
>>> alist[2]
('/tmp/demo/security/console.perms.d', [], [])
>>> alist[3]
('/tmp/demo/security/limits.d', [], ['20-nproc.conf'])
>>> alist[4]
('/tmp/demo/security/namespace.d', [], [])

# 拼接出绝对路径
>>> for path,folders,files in os.walk('/tmp/demo/security'):
... for file in files:
... os.path.join(path,file)
...
'/tmp/demo/security/pwquality.conf'
'/tmp/demo/security/access.conf'
'/tmp/demo/security/chroot.conf'
'/tmp/demo/security/console.handlers'
'/tmp/demo/security/console.perms'
'/tmp/demo/security/group.conf'
'/tmp/demo/security/limits.conf'
'/tmp/demo/security/namespace.conf'
'/tmp/demo/security/namespace.init'
'/tmp/demo/security/opasswd'
'/tmp/demo/security/pam_env.conf'
'/tmp/demo/security/sepermit.conf'
'/tmp/demo/security/time.conf'
'/tmp/demo/security/console.apps/xserver'
'/tmp/demo/security/console.apps/config-util'
'/tmp/demo/security/console.apps/liveinst'
'/tmp/demo/security/console.apps/setup'
'/tmp/demo/security/limits.d/20-nproc.conf'

函数文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import os
import tarfile
import hashlib
import pickle
from time import strftime

def check_md5(fname):
m = hashlib.md5()
with open(fname,'rb') as fobj:
while 1:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest()

def full_backup(src,dst,md5file):
# 拼接出文件名
fname = os.path.basename(src)
fname = '%s_full_%s.tar.gz' % (fname, strftime('%Y%m%d'))
fname = os.path.join(dst,fname)

# 备份,打tar包
tar = tarfile.open(fname,'w:gz')
tar.add(src)
tar.close()

# 计算md5值
md5dict = {}
for path, folders, files in os.walk(src):
for file in files:
key = os.path.join(path,file)
md5dict[key] = check_md5(key)

# 将md5字典保存到文件中
with open(md5file,'wb') as fobj:
pickle.dump(md5dict,fobj)


def incr_backup(src,dst,md5file):
# 拼接出文件名
fname = os.path.basename(src)
fname = '%s_incr_%s.tar.gz' % (fname, strftime('%Y%m%d'))
fname = os.path.join(dst, fname)

# 计算md5值
md5dict = {}
for path, folders, files in os.walk(src):
for file in files:
key = os.path.join(path, file)
md5dict[key] = check_md5(key)

# 读取前一天的md5值
with open(md5file,'rb') as fobj:
old_md5 = pickle.load(fobj)

# 将新增的文件和改动的文件进行打包备份
tar = tarfile.open(fname,'w:gz')
for key in md5dict:
# if key not in old_md5 or md5dict[key] != old_md5[key]:
if old_md5.get(key) != md5dict[key]:
tar.add(key)
tar.close()

# 更新md5字典文件
with open(md5file,'wb') as fobj:
pickle.dump(md5dict,fobj)


if __name__ == '__main__':

src = '/tmp/demo/security'
dst = '/tmp/demo/backup'
md5file = '/tmp/demo/md5.data'

if strftime('%a') == 'Mon':
full_backup(src,dst,md5file)
else:
incr_backup(src,dst,md5file)

OOP基础

OOP基础

基本概念

  • 类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例
  • 实例化:创建一个类的实例,类的具体对象
  • 方法:类中定义函数
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法

创建类

  • 使用class语句来创建一个新类,class之后为类的名称并以冒号结尾
  • 类名建议使用驼峰形式
1
2
class BearToy:
pass

创建实例

  • 类是蓝图,实例时根据蓝图创建出来的具体对象
1
2
3
4
5
6
7
>>> tidy = BearToy()
>>> tidy.size = 'Middle'
>>> tidy.color = 'Yellow'
>>> tidy.size
'Middle'
>>> tidy.color
'Yellow'

绑定方法

构造器方法

  • 当实例化类的对象时,构建器方法默认自动调用
  • 实例本身作为第一个参数,传递给self
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BearToy:
def __init__(self,size,color): # __init__名字固定
self.size = size
self.color = color

if __name__ == '__main__':
tidy = BearToy('small','orange')
# self size color


>>> class BearToy:
... def __init__(self,size,color):
... self.size = size
... self.color = color
...
>>> tidy = BearToy('small','orange')
>>> tidy.size
'small'
>>> tidy.color
'orange'z

其他绑定方法

  • 类中定义的方法需要绑定在具体的实例,由实例调用
  • 实例方法需要明确调用
1
2
3
4
5
6
7
8
9
10
11
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color

def speak(self,words):
print('i am a %s color bear, %s' % (self.color,words))

if __name__ == '__main__':
tidy = BearToy('small','orange')
tidy.speak('hello')

案例:编写游戏人物

  • 创建游戏角色类
  • 游戏人物角色拥有名字、武器等属性
  • 游戏任务具有攻击和行走的方法
  • 武器通过武器类实现
1
2
3
4
5
6
7
8
9
10
11
12
class Role:
def __init__(self,name,weapon):
self.name = name
self.weapon = weapon

def attack(self,target):
print('我是%s,正在攻击%s' % (self.name,target))

if __name__ == '__main__':
lb = Role('吕布','方天画戟')
print(lb.name,lb.weapon)
lb.attack('张飞')

组合和派生

组合

什么是组合

  • 类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到你的代码中去
  • 组合就是让不同的类混合并加入到其他来中来增加功能和代码重用性
  • 可以在一个大点的类中创建其他类的实例,实现一些其他属性和方法来增强对原来的类对象

组合应用

  • 两个类明显不同
  • 一个类是另一个类的组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> class Manufacture:
... def __init__(self,phone,email):
... self.phone = phone
... self.email = email
...

>>> class BearToy:
... def __init__(self,size,color,phone,email):
... self.size = size
... self.color = color
... self.vendor = Manufacture(phone,email)
...
>>>
>>> tidy = BearToy('small','red','123456','123@tidy.com')
>>> tidy.size
'small'
>>> tidy.color
'red'

>>> tidy.vendor.phone
'123456'
>>> tidy.vendor.email
'123@tidy.com'

子类

创建子类

  • 当类之间有显著的不同,并且较小的类是较大的类所需要的组件时组合表现很好,但当设计“相同的类但是有一些不同的功能”时,派生就是一个更加合理的选择了

  • OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其他代码片段

  • OOP(面向对象设计)允许类特征在子孙类或子类中进行传承

  • 创建子类只需要在圆括号中写明从哪个父类继承即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> class BearToy:
... def __init__(self,size,color):
... self.size = size
... self.color = color
...

>>> class NewBearToy(BearToy):
... pass
...

>>> tidy = NewBearToy('small','red')
>>> tidy.size
'small'
>>> tidy.color
'red'

继承

  • 继承描述了基类的属性如何“遗传”给派生类
  • 子类可以继承它的基类的任何属性,不管是数据属性还是方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color

def speak(self):
print('i am %s bear' % self.color)


class NewBearToy(BearToy):
pass

if __name__ == '__main__':
tidy = NewBearToy('small','orange')
tidy.speak()

通过继承覆盖方法

  • 如果子类中有和父类同名的方法,父类方法将被覆盖
  • 如果需要访问父类方法,则要调用一个未绑定的父类方法,明确给出子类的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color


class NewBearToy(BearToy):
def __init__(self,size,color,data):
BearToy.__init__(self,size,color) # 把父类方法导入
# super(BearToy,self).__init__(size,color) # 不同写法
self.data = data

if __name__ == '__main__':
tidy = NewBearToy('small','orange','50$')
print('A %s %s toy is %s' % (tidy.size,tidy.color,tidy.data))

多重继承

  • python允许多重继承,即一个类可以是多个父类的子类,子类可以拥有所有父类的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> class A:
... def foo(self):
... print('foo method')
...
>>> class B:
... def bar(self):
... print('bar method')
...
>>> class C(A,B):
... pass
...

>>> c = C()
>>> c.foo()
foo method
>>> c.bar()
bar method

案例:修改游戏人物

  • 增加武器类,使武器是人物的一个属性
  • 创建法师和战士类,他们派生于相同的一个基类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Role:
def __init__(self,name,weapon):
self.name = name
self.weapon = weapon

def show_me(self):
print('我是%s,我使用的武器是%s' % (self.name, self.weapon))

class Warrior(Role):
def __init__(self,name,weapon,ride,):
Role.__init__(self,name,weapon)
self.ride = ride
def show_me(self):
print('我是%s,我使用的武器是%s,坐骑是%s' % (self.name, self.weapon,self.ride))


def attack(self,target):
print('%s与%s贴身肉搏' % (self.name,target))

class Mage(Role):
def attack(self,target):
print('%s远程攻击%s' % (self.name,target))

if __name__ == '__main__':
lb = Warrior('吕布','方天画戟','赤兔马')
km = Mage('孔明','羽扇')
lb.show_me()
km.show_me()
lb.attack('张飞')
km.attack('曹操')

我是吕布,我使用的武器是方天画戟,坐骑是赤兔马
我是孔明,我使用的武器是羽扇
吕布与张飞贴身肉搏
孔明远程攻击曹操

类的特殊方法

魔法方法

magic基础

  • 在python中,所有以“__”双下划线包起来的方法,都统称为“Magic Method”
  • 如果对象实现(重载)了这些魔法方法中的某一个,那么这个方法就会在特殊的情况下被python所调用
  • 通过dir()可以查看对象的全部属性
1
2
>>> dir(10)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', ......]

__init__方法

  • 实例化类实例时默认会调用的方法
1
2
3
4
5
6
7
8
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color

if __name__ == '__main__':
tidy = BearToy('small','orange') # 调用init方法
print(tidy.size,tidy.color)

__str__方法

  • 打印/显示实例时调用的方法
  • 返回字符串
1
2
3
4
5
6
7
8
9
10
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color
def __str__(self):
return '<Bear: %s %s>' % (self.size,self.color)

if __name__ == '__main__':
tidy = BearToy('small','orange')
print(tidy) # 调用str方法,返回字符串

__call__方法

  • 用于创建可调用的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BearToy:
def __init__(self,size,color):
self.size = size
self.color = color
def __str__(self):
return 'Bear: %s %s' % (self.size,self.color)

def __call__(self):
print('i am a %s bear' % self.size)

if __name__ == '__main__':
tidy = BearToy('small','orange')
print(tidy)
tidy() # 调用call方法

案例:出版商程序

  • 为出版商编写一个Book类
  • Book类有书名,作者,页数等属性
  • 打印实例时,输出书名
  • 调用实例时,显示该书由哪个作者编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Book:
def __init__(self,title,author,pages):
self.title = title
self.author = author
self.pages = pages

def __str__(self):
return self.title

def __call__(self):
print('%s is %s wrote' % (self.title,self.author))

if __name__ == '__main__':
pybook = Book('python核心编程','韦斯利','660')
print(pybook)
pybook()

正则表达式及re模块

正则表达式

匹配单个字符

记号 说明
. 匹配任意字符(换行符除外)
[…x-y…] 匹配字符组里的任意字符
[^…x-y…] 匹配不在字符组里的任意字符
\d 匹配任意数字,与[0-9]同义,\D取非数字
\w 匹配任意数字字母字符,与[0-9a-zA-z_]同义,\W取反
\s 匹配空白字符,与[\r\v\f\t\n]同义,\S取反

匹配一组字符

记号 说明
literal 匹配字符串的值
re1|re2 匹配正则表达式re1或re2
* 匹配前面出现的正则表达式零次或多次
+ 匹配前面出现的正则表达式一次或多次
? 匹配前面出现的正则表达式零次或一次
{M,N} 匹配前面出现的正则表达式至少M次或最多N次

其他元字符

记号 说明
^ 匹配字符串的开始
$ 匹配字符串的结尾
\b 匹配字符串的边界
<> 匹配字符串的边界
() 对正则表达式分组
\nn 匹配以保存的子组,(nn为数字)
1
2
3
4
5
# 把MAC地址转换为xx:xx:xx:xx:xx:xx格式
192.168.1.1 000C29123456
192.168.1.2 525400A3B92E

:%s/\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)$/\1:\2:\3:\4:\5:\6/

核心方法

match函数

  • 尝试用正则表达式模式从字符串的开头匹配,如果匹配成功,则返回一个匹配对象;否则返回None
1
2
3
4
5
6
7
8
>>> import re
>>> m = re.match('foo','food') # 从food开头找foo
>>> print(m)
<re.Match object; span=(0, 3), match='foo'> # 0,3 为下标

>>> m = re.match('foo','seafood') # 从seafood开头找foo
>>> print(m)
None

search函数

  • 在字符串中查找正则表达式模式的第一次出现,如果匹配成功,则返回一个匹配对象;否则返回None
1
2
3
4
5
6
7
8
>>> import re
>>> m = re.search('foo','food')
>>> print(m)
<re.Match object; span=(0, 3), match='foo'>

>>> m = re.search('foo','seafood') # 可以匹配字符串中间
>>> print(m)
<re.Match object; span=(3, 6), match='foo'>

group方法

  • 使用matchsearch匹配成功后,返回的匹配对象可以通过group方法获得匹配内容
1
2
3
4
5
6
>>> m = re.match('foo','food')
>>> m.group()
'foo'
>>> m = re.search('foo','food')
>>> m.group()
'foo'

findall函数

  • 在字符串中查找正则表达式模式的所有(非重复)出现;返回一个匹配对象的列表
1
2
3
4
5
6
7
8
>>> import re
>>> m = re.search('foo','seafood is food')
>>> m.group() # search只匹配模式的第一次出现
'foo'

>>> m = re.findall('foo','seafood is food')
>>> m # 获取全部的匹配项
['foo', 'foo']

finditer函数

  • 和findall()函数有相同的功能,但返回的不是列表而是迭代器;对于每个匹配,该迭代器返回一个匹配对象
1
2
3
4
5
6
7
8
9
10
>>> import re
>>> list(re.findall('foo','seafood is food'))
['foo', 'foo']

>>> m = re.finditer('foo','seafood is food')
>>> for item in m:
... print(item.group())
...
foo
foo

split方法

  • 根据正则表达式中的分隔符把字符分割为一个列表,并返回成功匹配的列表
  • 字符串也有类型的方法,但是正则表达式更加灵活
1
2
3
4
>>> import re	# 使用.和-作为字符串的分隔符
>>> mylist = re.split('\.|-','hello-world.data')
>>> print(mylist)
['hello', 'world', 'data']

sub方法

  • 把字符串中所有匹配正则表达式的地方替换成新的字符串
1
2
3
4
5
6
7
>>> import re
>>> m = re.sub('X','Mr.Smith','attn:X\nDear X')
>>> m # 在attn:X\nDear X中把X替换成Mr.Smith
'attn:Mr.Smith\nDear Mr.Smith'
>>> print(m)
attn:Mr.Smith
Dear Mr.Smith

compile函数

  • 对正则表达式模式进行编译,返回一个正则表达式对象
  • 不是必须要用这种方式,但是在大量匹配的情况下,可以提升效率
1
2
3
4
5
>>> import re
>>> patt = re.compile('foo')
>>> m = patt.match('food')
>>> m.group()
'foo'

案例:分析apache访问日志

  • 编写一个apache日志分析脚本
  • 统计每个客户端访问apache服务器的次数
  • 统计信息通过字典的方式显示出来
  • 分别统计客户端是Firefox和MSIE的访问次数
  • 分别使用函数式编程和面向对象编程的方式出现
1
2
3
4
# 日志格式
[root@python ~]# head -2 access_log
192.168.216.252 - - [07/May/2023:03:23:37 +0800] "GET /resource_providers/aggregates HTTP/1.1" 200 18 "-" "keystoneauth1/3.4.0 python-requests/2.14.2 CPython/2.7.5"
192.168.216.252 - - [07/May/2023:03:23:43 +0800] "GET /resource_providers/traits HTTP/1.1" 200 49 "-" "keystoneauth1/3.4.0 python-requests/2.14.2 CPython/2.7.5"

函数形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import re

def count_patt(fname,patt):
patt_dict = {}
cpatt = re.compile(patt)

with open(fname) as fobj:
for line in fobj:
m = cpatt.search(line)
if m:
key = m.group()
patt_dict[key] = patt_dict.get(key,0) + 1
# if key not in patt_dict:
# patt_dict[key] = 1
# else:
# patt_dict[key] += 1
return patt_dict

if __name__ == '__main__':
fname = '/root/access_log'
ip = "^(\d+\.){3}\d+" # \d表示数字+表示出现一次或多次{3}前面出现3次
br = 'python-requests|Chrome' # 通过什么访问
result1 = count_patt(fname,ip)
result2 = count_patt(fname,br)
print(result1)
print(result2)


{'192.168.216.252': 16846, '192.168.216.251': 16855, '192.168.216.110': 517}
{'python-requests': 33701, 'Chrome': 524}

OOP形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import re

class CountPatt:
def count_patt(self,fname,patt):
patt_dict = {}
cpatt = re.compile(patt)
with open(fname) as fobj:
for line in fobj:
m = cpatt.search(line)
if m:
key = m.group()
patt_dict[key] = patt_dict.get(key,0) + 1
return patt_dict

if __name__ == '__main__':
fname = '/root/access_log'
ip = "^(\d+\.){3}\d+"
br = 'python-requests|Chrome'
cp = CountPatt()
result1 = cp.count_patt(fname,ip)
result2 = cp.count_patt(fname,br)
print(result1)
print(result2)

或把文件名作为默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import re

class CountPatt:
def __init__(self,fname):
self.fname = fname

def count_patt(self,patt):
patt_dict = {}
cpatt = re.compile(patt)
with open(self.fname) as fobj:
for line in fobj:
m = cpatt.search(line)
if m:
key = m.group()
patt_dict[key] = patt_dict.get(key,0) + 1
return patt_dict

if __name__ == '__main__':
fname = '/root/access_log'
ip = "^(\d+\.){3}\d+"
br = 'python-requests|Chrome'
cp = CountPatt(fname)
result1 = cp.count_patt(ip)
result2 = cp.count_patt(br)
print(result1)
print(result2)

把返回字典排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> adict = {'192.168.216.252': 16846, '192.168.216.251': 16855, '192.168.216.110': 517}

>>> adict.items()
dict_items([('192.168.216.252', 16846), ('192.168.216.251', 16855), ('192.168.216.110', 517)])

>>> list(adict.items())
[('192.168.216.252', 16846), ('192.168.216.251', 16855), ('192.168.216.110', 517)]

>>> alist = list(adict.items())
>>> alist
[('192.168.216.252', 16846), ('192.168.216.251', 16855), ('192.168.216.110', 517)]


>>> def func1(seq):
... return seq[-1]
...
>>> alist.sort(key=func1)
>>> alist
[('192.168.216.110', 517), ('192.168.216.252', 16846), ('192.168.216.251', 16855)]


# 通过匿名函数
>>> alist.sort(key=lambda seq: seq[-1], reverse=True)
>>> alist
[('192.168.216.251', 16855), ('192.168.216.252', 16846), ('192.168.216.110', 517)]

Python模块安装

PYPI介绍

  • PYPI即PYthon Package Index
  • 是Python官方的第三方库,所有人都可以下载或上传Python库到PYPI
  • 官方站点为https://pypi.org

本地安装模块

本地安装pip

  • pip是python包管理工具
  • 提供了对python包的查找、下载、安装、卸载的功能
  • 默认python3已经安装,没有pip再安装
  • 下载pip后解压缩并安装
1
2
3
[root@python ~]# tar xzf pip-23.1.2.tar.gz 
[root@python ~]# cd pip-23.1.2/
[root@python pip-23.1.2]# python3 setup.py install

安装wget

1
2
3
4
[root@python ~]# source mypy/bin/activate
(mypy) [root@python ~]# unzip wget-3.2.zip
(mypy) [root@python ~]# cd wget-3.2/ # 虚拟环境是python3
(mypy) [root@python wget-3.2]# python setup.py install

在线安装模块

使用国内镜像站点

  • 为了实现安装加速,可以配置pip安装时采用国内镜像站点
1
2
3
4
5
6
7
8
[root@python ~]# mkdir ./.pip
[root@python ~]# vim .pip/pip.conf
[global]
index-url=http://pypi.douban.com/simple/ # 安装源豆瓣
[install]
trusted-host=pypi.douban.com # 信任

(mypy) [root@python ~]# pip install requests

配置pip支持tab键补全

  • pip命令默认不支持按tab键提示
  • 配置pip支持tab键补全的方法如下
1
2
3
4
5
(mypy) [root@python ~]# pip completion --bash > /etc/bash_completion.d/pip        
(mypy) [root@python ~]# source /etc/bash_completion.d/pip

(mypy) [root@python ~]# pip completion --bash >> ~/.bash_profile
(mypy) [root@python ~]# source ~/.bash_profile

查看软件包版本

  • pip在线安装软件包时,默认安装最新版本
  • 查看软件包版本、安装指定版本方式如下:
1
2
3
4
5
6
(mypy) [root@python ~]# pip install wget==
wget== (from versions: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 2.0, 2.1, 2.2, 3.0, 3.1, 3.2)

(mypy) [root@python ~]# pip install wget==3.2

(mypy) [root@python ~]# pip uninstall wget # 卸载

安装pymysql最新版本

1
2
3
4
5
(mypy) [root@python ~]# pip install pymysql==
pymysql== (from versions: 0.3, 0.4, 0.5, 0.6, 0.6.1, 0.6.2, 0.6.3, 0.6.4.dev1, 0.6.4, 0.6.6, 0.6.7, 0.7.0, 0.7.1, 0.7.2, 0.7.3, 0.7.4, 0.7.5, 0.7.6, 0.7.7, 0.7.8, 0.7.9, 0.7.10, 0.7.11, 0.8.0, 0.8.1, 0.9.0, 0.9.1, 0.9.2, 0.9.3, 0.10.0, 0.10.1, 1.0.0, 1.0.1, 1.0.2, 1.0.3rc1, 1.0.3)

(mypy) [root@python ~]# pip install pymysql # 默认最新版本
Successfully installed pymysql-1.0.3

准备数据库

数据库案例

需求分析

  • 你正在为一家小公司编写数据
  • 需要记录员工的基本信息,并且记录工资发放情况
  • 需要记录的数据有:姓名、性别、出生日期、联系方式、部门、发工资日、基本工资、奖金、实发工资等

确定所需的表

  • 如果把所有数据都放在同一张表里会有很多问题,比如数据冗余、数据不一致

  • 分为三张表

    员工表

    部门表

    工资表

数据库范式

  • 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。

  • 数据库范式有第一范式、第二范式、第三范式、巴斯-科德范式、第四范式、第五范式六种

  • 第一范式(1NF)要求所有字段都具有原子性

  • 第二范式(2NF)要求数据库表中的每个实列或记录必须可以被唯一的区分

  • 第三范式(3NF)要求任何非主属性不得传递依赖于主属性

  • 根据1NF,联系方式不具有原子性,需要进一步拆分为家庭住址、email、电话号码等

  • 根据2NF,每张表都需要有一个主键

  • 根据3NF,工资表中的实发工资项不应该出现

案例:准备数据库

  • 安装mariadb-server
  • 启动服务
  • 为root用户修改密码为tedu.cn
  • 创建名为tedu1的数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(mypy) [root@python ~]# yum install mariadb-server -y && systemctl enable --now mariadb

(mypy) [root@python ~]# mysqladmin password tedu.cn
(mypy) [root@python ~]# mysql -uroot -ptedu.cn
MariaDB [(none)]> create database tedu1 default charset utf8;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| tedu1 |
| test |
+--------------------+
5 rows in set (0.00 sec)

PyMySQL模块应用

连接数据库

  • 创建连接是访问数据库的第一步
1
2
3
4
5
6
7
8
9
10
11
import pymysql

# 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='tedu.cn',
db='tedu1',
charset='utf8'
)

游标

  • 游标(cursor)就是游动的标识
  • 通俗的说,一条sql取出对应n条结构资源的接口/句柄,就是游标,沿着游标可以一次取一行
1
2
# 创建游标,通过游标可以实现对数据的增删改查
cur = conn.cursor()

创建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import pymysql

# 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='tedu.cn',
db='tedu1',
charset='utf8'
)

# 创建游标,通过游标可以实现对数据的增删改查
cur = conn.cursor()

# 编写创建表的sql语句
create_dep = '''CREATE TABLE departments(
dep_id INT, dep_name VARCHAR(50),
PRIMARY KEY(dep_id)
)'''
create_emp = '''CREATE TABLE employees(
emp_id INT, emp_name VARCHAR(50), email VARCHAR(50), dep_id INT,
PRIMARY KEY(emp_id), FOREIGN KEY(dep_id) REFERENCES departments(dep_id)
)'''
create_sal = '''CREATE TABLE salary(
id INT, data DATE, emp_id INT, basic INT, awards INT,
PRIMARY KEY(id), FOREIGN KEY(emp_id) REFERENCES employees(emp_id)
)'''

# 执行sql语句
cur.execute(create_dep)
cur.execute(create_emp)
cur.execute(create_sal)

# 确认
conn.commit()

# 关闭
cur.close()
conn.close()
  • 查看数据库
1
2
3
4
5
6
7
8
9
MariaDB [tedu1]> show tables;
+-----------------+
| Tables_in_tedu1 |
+-----------------+
| departments |
| employees |
| salary |
+-----------------+
3 rows in set (0.00 sec)

插入数据

  • 对数据库做修改操作,必须要commit
1
2
3
insert_dep = 'INSERT INTO departments VALUES(%s, %s)'
cur.execute(insert_dep, (1, '人事部')) # 只能插入一行
cur.executemany(insert_dep, [(2, '运维部'),(3, '开发部'),(4, '测试部'),(5, '财务部'),(6, '市场部')]) # 可插入多行
  • 修改为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import pymysql

# 创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
passwd='tedu.cn',
db='tedu1',
charset='utf8'
)

# 创建游标,通过游标可以实现对数据的增删改查
cur = conn.cursor()

# 编写创建表的sql语句
create_dep = '''CREATE TABLE departments(
dep_id INT, dep_name VARCHAR(50),
PRIMARY KEY(dep_id)
)'''
create_emp = '''CREATE TABLE employees(
emp_id INT, emp_name VARCHAR(50), email VARCHAR(50), dep_id INT,
PRIMARY KEY(emp_id), FOREIGN KEY(dep_id) REFERENCES departments(dep_id)
)'''
create_sal = '''CREATE TABLE salary(
id INT, data DATE, emp_id INT, basic INT, awards INT,
PRIMARY KEY(id), FOREIGN KEY(emp_id) REFERENCES employees(emp_id)
)'''

# 执行sql语句
# cur.execute(create_dep)
# cur.execute(create_emp)
# cur.execute(create_sal)

# 插入部门
insert_dep = 'INSERT INTO departments VALUES(%s, %s)'
cur.execute(insert_dep, (1, '人事部'))
cur.executemany(insert_dep, [(2, '运维部'),(3, '开发部'),(4, '测试部'),(5, '财务部'),(6, '市场部')])

# 确认
conn.commit()

# 关闭
cur.close()
conn.close()
  • 数据库查询
1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [tedu1]> select * from departments;
+--------+-----------+
| dep_id | dep_name |
+--------+-----------+
| 1 | 人事部 |
| 2 | 运维部 |
| 3 | 开发部 |
| 4 | 测试部 |
| 5 | 财务部 |
| 6 | 市场部 |
+--------+-----------+
6 rows in set (0.01 sec)

查询数据

  • 可以取出表中一条、多条或全部记录
  • 把插入记录注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
select_dep = 'SELECT * FROM departments'
cur.execute(select_dep)

result1 = cur.fetchone() # 取出第一行
print(result1)

print('*' * 30)

result2 = cur.fetchmany(2) # 继续向下取两行
print(result2)

print('*' * 30)

result3 = cur.fetchall() # 继续向下取出全部数据
print(result3)

修改数据

  • 通过update来修改某一字段的值
1
2
3
# 修改
update_dep = 'UPDATE departments SET dep_name=%s WHERE dep_name=%s'
cur.execute(update_dep, ('人力资源部','人事部'))
  • 查看数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
MariaDB [tedu1]> select * from departments;
+--------+-----------------+
| dep_id | dep_name |
+--------+-----------------+
| 1 | 人力资源部 |
| 2 | 运维部 |
| 3 | 开发部 |
| 4 | 测试部 |
| 5 | 财务部 |
| 6 | 市场部 |
+--------+-----------------+
6 rows in set (0.00 sec)

删除记录

  • 通过delete删除记录
1
2
del_dep = 'DELETE FROM departments WHERE dep_id=%s'
cur.execute(del_dep, (6,)) # 单元素元组必须有逗号
  • 查看数据库
1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [tedu1]> select * from departments;
+--------+-----------------+
| dep_id | dep_name |
+--------+-----------------+
| 1 | 人力资源部 |
| 2 | 运维部 |
| 3 | 开发部 |
| 4 | 测试部 |
| 5 | 财务部 |
+--------+-----------------+
5 rows in set (0.00 sec)

SQLAlchemy概述

简介

  • SQLAlchemy是Python编程语言下的一款开源软件。提供对象关系映射(ORM)工具,使用MIT许可证
  • SQLAlchemy“采用简单的Python语言,为高效和高性能的数据库访问设计,实现了完整的企业级持久模型”
  • SQLAlchemy理念是,SQL数据库的量级和性能重要于对象集合;而对象集合的抽象有重要于表和行
  • 目标是提供能够兼容众多数据库(如SQLite、MySQL、Postaresql、Oracle、MS-SQL、SQLServer)的企业持久性模型

架构

1.png

安装

  • SQLAlchemy由官方收录,可以直接安装
1
(venv) [root@python mypy]# pip install sqlalchemy

连接mysql

  • 通过create_engine实现数据库的连接
1
2
3
4
5
6
7
8
9
10
11
12
13
MariaDB [(none)]> create database tarena default charset utf8;
Query OK, 1 row affected (0.00 sec)


from sqlalchemy import create_engine

# 创建连接数据库的引擎
engine = create_engine(
# mysql+pymysql://用户名:密码@服务器/数据库?参数
'mysql+pymysql://root:tedu.cn@127.0.0.1/tarena?charset=utf8',
encoding='utf8', # 这一步有可能报错,注释即可
echo=True # 表示将日志输出到终端屏幕,默认为False
)

创建会话类

  • ORM访问数据库的句柄被称为Session
1
2
3
4
5
6
>>> from sqlalchemy.orm import sessionmaker
>>> Session = sessionmaker(bing=engine)

# 如果创建session前还未创建engine,操作如下
>>> Session = sessionmaker()
>>> Session.configure(bind=engine) # 创建engine后执行

ORM基础

ORM模型

  • ORM即对象关系映射
  • 数据库是一个二维表,包含多行多列。把一个表的内容用Python的数据结构表示出来的话,可以用一个list表示多行,list的每一个元素是tuple,表示一行记录
1
2
3
4
5
[
('1','Michael'),
('2','Bob'),
('3','Adam')
]
  • 用tuple表示一行很难看出表的结构。如果把一个tuple用class实例表示,就可以更容易的看出表结构来
1
2
3
4
5
6
7
8
9
10
class User:
def __init__(self,id,name):
self.id = id
self.name = name

[
User('1','Michael'),
User('2','Bob'),
User('3','Adam')
]

声明映射

  • 当使用ORM的时候,配置过程从描述数据库开始
  • 通过自定义类映射相应的表
  • 通过声明系统类型实现类映射
  • 首先通过声明系统,定义基类
1
2
3
# 生成实体类的基类(父类)
>>> from sqlalchemy.ext.declarative import declarative_base
>>> Base = declarative_base()

映射说明

  • 数据库中的表映射为sqlalchemy中的类
  • 数据库的字段映射为类变量,对应Column的实例
  • 数据库中的数据类型映射为sqlalchemy相应的类
  • 数据库表中的记录映射为类的实例

数据库对象管理

创建实体类

创建部门类

  • 一旦创建了基类,既可以创建自定义的实体类了
1
2
3
4
5
>>> from sqlalchemy import Column,Integer,String
>>> class Departments(Base):
... __tablename__='departments'
... dep_id = Column(Integer, primary_key=True)
... dep_name = Column(String(20),unique=True) # 不重名

创建部门表

  • 通过实体类,可以在数据库中创建相应的表
  • 如果数据库中已有相关的表,不会重新创建
1
2
3
4
5
6
7
8
9
10
11
12
>>> Base.metadata.create_all(engine)

# 执行查看数据库
MariaDB [tarena]> desc departments;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| dep_id | int(11) | NO | PRI | NULL | auto_increment |
| dep_name | varchar(20) | YES | UNI | NULL | |
+----------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

外键约束

  • ORM映射关系也可用于表间创建外键约束
1
2
3
4
5
6
7
8
from sqlalchemy import ForeignKey

class Employees(Base):
__tablename__ = 'employees'
emp_id = Column(Integer,primary_key=True)
emp_name = Column(String(20))
email = Column(String(50))
dep_id = Column(Integer,ForeignKey('departments.dep_id'))
  • 查看数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MariaDB [tarena]> show tables;
+------------------+
| Tables_in_tarena |
+------------------+
| departments |
| employees |
+------------------+
2 rows in set (0.00 sec)

MariaDB [tarena]> desc employees;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| emp_id | int(11) | NO | PRI | NULL | auto_increment |
| emp_name | varchar(20) | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
| dep_id | int(11) | YES | MUL | NULL | |
+----------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

创建工资表salary

1
2
3
4
5
6
7
8
9
10
from sqlalchemy import Date


class Salary(Base):
__tablename__ = 'salary'
id = Column(Integer,primary_key=True)
data = Column(Date)
emp_id = Column(Integer,ForeignKey('employees.emp_id'))
basic = Column(Integer)
awards = Column(Integer)
  • 数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MariaDB [tarena]> show tables;
+------------------+
| Tables_in_tarena |
+------------------+
| departments |
| employees |
| salary |
+------------------+
3 rows in set (0.00 sec)

MariaDB [tarena]> desc salary;
+--------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| data | date | YES | | NULL | |
| emp_id | int(11) | YES | MUL | NULL | |
| basic | int(11) | YES | | NULL | |
| awards | int(11) | YES | | NULL | |
+--------+---------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

完整代码文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Date
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base


# 创建连接数据库的引擎
engine = create_engine(
# mysql+pymysql://用户名:密码@服务器/数据库?参数
'mysql+pymysql://root:tedu.cn@localhost/tarena?charset=utf8',
# encoding='utf8',
# echo=True
)

# 创建会话类
Session = sessionmaker(bind=engine)

# 生成实体类的基类(父类)
Base = declarative_base()

class Departments(Base):
__tablename__ = 'departments'
dep_id = Column(Integer, primary_key=True)
dep_name = Column(String(20), unique=True) # 不重名

class Employees(Base):
__tablename__ = 'employees'
emp_id = Column(Integer,primary_key=True)
emp_name = Column(String(20))
email = Column(String(50))
dep_id = Column(Integer,ForeignKey('departments.dep_id'))

class Salary(Base):
__tablename__ = 'salary'
id = Column(Integer,primary_key=True)
data = Column(Date)
emp_id = Column(Integer,ForeignKey('employees.emp_id'))
basic = Column(Integer)
awards = Column(Integer)



if __name__ == '__main__':
Base.metadata.create_all(engine)

SQLAIchemy应用

添加新对象

  • 会话类的实例对象用于绑定到数据库
  • 实例化的对象,并不打开任何连接
  • 当实例初次使用,它将从Engine维护的连接池中获得一个连接
  • 当所有的事务均被commit或会话对象被关闭时,连接结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 导入上面写的文件模块
from sqlalchemy_lizi import Session,Departments,Employees

# 创建到数据库的会话连接
session = Session()

# 创建部门实例
hr = Departments(dep_id=1,dep_name='人事部')
session.add(hr)

# 确认
session.commit()

# 关闭
session.close()
  • 查看数据库
1
2
3
4
5
6
7
MariaDB [tarena]> select * from departments;
+--------+-----------+
| dep_id | dep_name |
+--------+-----------+
| 1 | 人事部 |
+--------+-----------+
1 row in set (0.00 sec)

批量添加新对象

  • 可以创建多个实例,批量添加记录
1
2
3
4
5
6
7
# 批量添加部门
finance = Departments(dep_id=2,dep_name='财务部')
ops = Departments(dep_id=3,dep_name='运维部')
dev= Departments(dep_id=4,dep_name='开发部')
qa = Departments(dep_id=5,dep_name='测试部')
market = Departments(dep_id=6,dep_name='市场部')
session.add_all([finance,ops,dev,qa,market])
  • 查看数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MariaDB [tarena]> select * from departments;
+--------+-----------+
| dep_id | dep_name |
+--------+-----------+
| 1 | 人事部 |
| 6 | 市场部 |
| 4 | 开发部 |
| 5 | 测试部 |
| 2 | 财务部 |
| 3 | 运维部 |
+--------+-----------+
6 rows in set (0.00 sec)

MariaDB [tarena]> select * from departments order by dep_id;
+--------+-----------+
| dep_id | dep_name |
+--------+-----------+
| 1 | 人事部 |
| 2 | 财务部 |
| 3 | 运维部 |
| 4 | 开发部 |
| 5 | 测试部 |
| 6 | 市场部 |
+--------+-----------+
6 rows in set (0.01 sec)

批量添加员工数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 批量添加员工数据
alice = Employees(
emp_id=1,emp_name='alice',
email='3311@123.com',dep_id=1
)
bob = Employees(
emp_id=2,emp_name='bob',
email='31@123.com',dep_id=2
)
tom = Employees(
emp_id=3,emp_name='tom',
email='3123.com',dep_id=3
)
jerry = Employees(
emp_id=4,emp_name='jerry',
email='331123.com',dep_id=4
)
wsq = Employees(
emp_id=5,emp_name='wsq',
email='3311.com',dep_id=5
)
llj = Employees(
emp_id=6,emp_name='llj',
email='3311com',dep_id=6
)
session.add_all([alice,bob,tom,jerry,wsq,llj])
  • 查看数据库
1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [tarena]> select * from employees;
+--------+----------+--------------+--------+
| emp_id | emp_name | email | dep_id |
+--------+----------+--------------+--------+
| 1 | alice | 3311@123.com | 1 |
| 2 | bob | 31@123.com | 2 |
| 3 | tom | 3123.com | 3 |
| 4 | jerry | 331123.com | 4 |
| 5 | wsq | 3311.com | 5 |
| 6 | llj | 3311com | 6 |
+--------+----------+--------------+--------+
6 rows in set (0.00 sec)

查询

基本查询

  • 查询时,query参数是类名,返回的是实例列表
1
2
3
4
5
6
7
8
9
10
11
12
13
# 查询所有的部门
qset1 = session.query(Departments)
# print(qset1) qset1是sql语句,此时不查询数据库,取值时才查询
for dep in qset1:
print(dep.dep_id,dep.dep_name)


1 人事部
6 市场部
4 开发部
5 测试部
2 财务部
3 运维部
  • 查询时,query参数是属性,返回的是实例属性构成的元组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
qset2 = session.query(Employees.emp_name,Employees.email)
for data in qset2:
print(data)

('alice', '3311@123.com')
('bob', '31@123.com')
('tom', '3123.com')
('jerry', '331123.com')
('wsq', '3311.com')
('llj', '3311com')



qset2 = session.query(Employees.emp_name,Employees.email)
for name,email in qset2:
print('%s:%s' % (name,email))

alice:3311@123.com
bob:31@123.com
tom:3123.com
jerry:331123.com
wsq:3311.com
llj:3311com

排序

  • 通过order_by()函数可以实现按指定字段排序
1
2
3
4
5
6
7
8
9
10
11
12
# 查询部门,按id排序
qset3 = session.query(Departments).order_by(Departments.dep_id)
for dep in qset3:
print(dep.dep_id,dep.dep_name)


1 人事部
2 财务部
3 运维部
4 开发部
5 测试部
6 市场部

结果过滤

  • 通过filter()函数实现结果过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
qset4 = session.query(Departments).filter(Departments.dep_id==2)
for dep in qset4:
print(dep.dep_id,dep.dep_name)


# 可叠加使用,查询部门,根据id进行过滤,取出234号
qset5 = session.query(Departments).filter(Departments.dep_id>1)\
.filter(Departments.dep_id<5).order_by(Departments.dep_id)
for dep in qset5:
print(dep.dep_id,dep.dep_name)

2 财务部
3 运维部
4 开发部

常用过滤符

  • 相等:’==’
  • 不相等:’!=’
  • 模糊查询:’.like’
1
2
3
qset5 = session.query(Employees).filter(Employees.email.like('%.com'))
for emp in qset5:
print(emp.emp_name,emp.email)
  • in:’.in_([])’
  • not in:’~Employees.email.in_([])’
  • 字段为空:’.is_(None)’
  • 字段不为空:’.isnot(None)’

查询对象返回值

  • all()返回列表
  • first()返回结果中的第一条记录
1
2
3
4
5
6
7
8
qset8 = session.query(Employees.emp_name,Employees.email)
print(qset8.all())
print('*' * 30)
print(qset8.first())

[('alice', '3311@123.com'), ('bob', '31@123.com'), ('tom', '3123.com'), ('jerry', '331123.com'), ('wsq', '3311.com'), ('llj', '3311com')]
******************************
('alice', '3311@123.com')

多表查询

  • 多表查询,query中先写Employees.emp_name, join时写Departments
  • query中先写Deparments.dep_name, join时写Employees
1
2
3
4
5
6
7
8
9
10
11
qset9 = session.query(Employees.emp_name,Departments.dep_name)\
.join(Departments)
for data in qset9:
print(data)

('alice', '人事部')
('bob', '财务部')
('tom', '运维部')
('jerry', '开发部')
('wsq', '测试部')
('llj', '市场部')

修改

  • 更新数据时,只要找到相应的记录,将其对应的实例属性重新赋值即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
qset10 = session.query(Departments).filter(Departments.dep_name=='人事部')
hr2 = qset10.first()
print(hr2)
hr2.dep_name = '人力资源部'


MariaDB [tarena]> select * from departments;
+--------+-----------------+
| dep_id | dep_name |
+--------+-----------------+
| 1 | 人力资源部 |
| 6 | 市场部 |
| 4 | 开发部 |
| 5 | 测试部 |
| 2 | 财务部 |
| 3 | 运维部 |
+--------+-----------------+
6 rows in set (0.00 sec)

删除

  • 删除时,只要找到对应的记录,将其实例进行删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
qset11 = session.query(Employees)\
.filter(Employees.emp_id=='6')
sales = qset11.first()
print(sales)
session.delete(sales)


MariaDB [tarena]> select * from employees;
+--------+----------+--------------+--------+
| emp_id | emp_name | email | dep_id |
+--------+----------+--------------+--------+
| 1 | alice | 3311@123.com | 1 |
| 2 | bob | 31@123.com | 2 |
| 3 | tom | 3123.com | 3 |
| 4 | jerry | 331123.com | 4 |
| 5 | wsq | 3311.com | 5 |
+--------+----------+--------------+--------+
5 rows in set (0.00 sec)

多进程概述

forking工作原理

什么是forking

  • fork(分岔)在Linux系统中使用非常广泛
  • 当某一命令执行时,父进程(当前进程)fork出一个子进程
  • 父进程将自身资源拷贝一份,命令在子进程中运行时,就具有和父进程完全一样的运维环境
  • (一个脚本通过bash就是生成子进程执行,source就是在当前进程执行)

进程的生命周期

  • 父进程fork出子进程并挂起
  • 子进程运行完毕后,释放大部分资源并通知父进程,这个时候,子进程被称为僵尸进程
  • 父进程获知子进程结束,子进程所有资源释放

2.png

案例:单进程、单线程ping

  • 通过ping测试主机是否可达
  • 如果ping不通,不管什么原因都认为主机不可用
  • 需要通过ping的方式,扫描你主机所在网段的所有IP地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import subprocess

def ping(host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)

if __name__ == '__main__':
ips = ['192.168.1.%s' % i for i in range(1,255)]
for ip in ips:
ping(ip)
  • 通过一个一个ip测试,会发现很慢,通过forking生成255个子进程,一次性测试255个IP会很快

forking编程基本思路

  • 需要使用os模块
  • os.fork()函数实现forking功能
  • python中,绝大多数的函数只返回一次,os.fork将返回两次
  • fork()的调用,针对父进程返回子进程的PID;对于子进程,返回PID0

案例:forking基础应用

编写一个forking脚本

  • 在父进程中打印“in parent”然后睡眠10秒
  • 在子进程中编写循环,循环5次,输出当前系统时间,每次循环结束后睡眠1秒
  • 父子进程结束后,分别打印“parent exit”和“child exit”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import os

print('starting...')
print('hello world')

(venv) [root@python new]# python myfork.py
starting...
hello world

************************************************************

import os

print('starting...')
ret_val = os.fork() # 产生一个子进程,下面命令会先在父进程执行一遍,再进入子进程执行一遍
print('hello world')

(venv) [root@python new]# python myfork.py
starting...
hello world
hello world

************************************************************

import os

print('starting...')
ret_val = os.fork()
if ret_val: # 值非0为真
print('in parent')
else:
print('in child')
print('hello world')

(venv) [root@python new]# python myfork.py
starting...
in parent
hello world
in child
hello world

************************************************************

import os
import time

print('starting')

ret_val = os.fork()
if ret_val:
print('in parent')
time.sleep(10)
print('parent exit')
else:
print('in chile')
for i in range(1,6):
print(i)
time.sleep(1)
print('child exit')

多进程应用

多进程编程思路

  • 父进程负责生成子进程
  • 子进程做具体的工作
  • 子进程结束后彻底退出
1
2
3
4
5
6
7
8
9
import os

print('starting')

for i in range(3):
ret_val = os.fork()
if not ret_val: # 子进程ret_val为0,取反为真
print('Hello World')
exit() # 防止子进程再生成子进程

案例:扫描存活主机

  • 通过ping测试主机是否可达
  • 如果ping不通,不管什么原因都认为主机不可用
  • 需要通过ping的方式,扫描你主机所在网段的所有IP地址
  • 通过fork方式实现并发扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import subprocess
import os

def ping(host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)

if __name__ == '__main__':
ips = ['192.168.1.%s' % i for i in range(1,255)]
for ip in ips:
ret_vel = os.fork()
if not ret_vel:
ping(ip)
exit()

僵尸进程问题

僵尸进程

  • 僵尸进程没有任何可执行代码,也不可被调度
  • 如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
  • 对于系统管理员来说,可以试图杀死父进程或重启系统来消除僵尸进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os
import time

print('starting')
ret_val = os.fork()

if ret_val:
print('in parent')
time.sleep(60)
print('parent done')
else:
print('in child')
time.sleep(15) # 15秒后子进程任务完成,单父进程仍在睡眠,子进程没有任务,也在睡眠,就变成了僵尸进程
print('child done')

************************************************************
(venv) [root@python new]# python3 zb.py

[root@python ~]# watch -n1 ps a # 每一秒执行一次ps -a
very 1.0s: ps a Tue May 30 17:11:59 2023

PID TTY STAT TIME COMMAND
1819 tty1 Ssl+ 1:09 /usr/bin/X :0 -background none -noreset -audit 4 -verbose -
4686 pts/0 Ss 0:00 /bin/bash --rcfile /usr/local/bin/pycharm/plugins/terminal/
6838 pts/1 Ss 0:00 bash
6939 pts/1 Sl 0:00 gedit
7001 pts/0 S+ 0:00 python3 zb.py # 父进程
7002 pts/0 Z+ 0:00 [python3] <defunct> # Z僵尸进程
7069 pts/1 S+ 0:00 watch -n1 ps a
7080 pts/1 S+ 0:00 watch -n1 ps a
7081 pts/1 R+ 0:00 ps a

# 通过杀死父进程来杀死子进程“kill -9 7001”

解决zombie问题

  • 父进程通过os.wait()来得到子进程是否中止的信息
  • 在子进程中止和父进程调用wait()之间的这段时间,子进程被称为zombie(僵尸)进程
  • 如果子进程还没终止,父进程先退出了,那么子进程会持续工作。系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
  • python可以使用waitpid()来处理子进程
  • waitpid()接收两个参数,第一个参数设置为-1,表示与wait()函数相同;第二参数如果设置为0表示挂起父进程,直到子进程退出,设置1表示不挂起父进程
  • waitpid()的返回值:如果子进程尚未结束返回0,否则返回子进程的PID
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import os
import time

print('starting')
ret_val = os.fork()

if ret_val:
print('in parent')
result = os.waitpid(-1,0) # 挂起父进程
print(result)
time.sleep(3)
print('parent done')
else:
print('in child')
time.sleep(5)
print('child done')

************************************************************

starting
in parent
in child
child done
(7358, 0) # 子进程pid,若不挂起父进程返回(0,0)
parent done

多线程应用

多线程工作原理

多线程的动机

  • 在多线程(MT)编程出现之前,电脑程序的运行由一个执行程序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
  • 无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
  • 即使子任务相互独立,互相无关(即,一个子任务的结果不影响其他子任务的结果)时也是这样
  • 如果并行运行这些相互独立的子任务可以大幅度对提升整个任务的效率

多线程任务的工作特点

  • 它们本质上就是异步的,需要有多个并发事务
  • 各个事务的运行顺序可以是不确定的,随机的,不可预测的
  • 这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
  • 根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到的最后的结果

什么是进程

  • 计算机程序只不过是磁盘中可以执行的、二进制(或其他类型)的数据
  • 进程(有时被称作为重量级进程)是程序的一次执行
  • 每个进程都有自己的地址空间、内存以及其他记录其运行轨迹的辅助数据
  • 操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间

什么是线程

  • 线程(有时被称作为轻量级进程)跟进程有些相似。不同的是,所有线程运行在同一个进程中,共享相同的运行环境
  • 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便的共享数据以及相互通讯

多线程编程

多线程相关模块

  • threadthreading模块允许程序员创建和管理线程
  • thread模块提供了基本的线程和锁的支持,而threading提供了更高级别、功能更强的线程管理功能
  • 推荐使用更高级别的threading模块

传递函数给Thread类

  • 多线程编程有很多方法,传递函数给threading模块的Thread类是介绍的第一种方法

  • Threaddui对象使用start()方法开始线程的执行,使用join()方法挂起程序,直到线程结束

  • 传递可调用类给Thread类是介绍的第二种方法

  • 相对于一个或几个函数来说,由于类对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 主线程只负责生成工作进程
# 工作线程做具体的工作
import threading

def say_hi():
print('Hello World')

if __name__ == '__main__':
for i in range(3):
t = threading.Thread(target=say_hi) # 创建工作线程
t.start() # 启动工作线程 target()

************************************************************

import threading

def say_hi(word):
print('Hello %s' % word)

if __name__ == '__main__':
for i in range(3):
t = threading.Thread(target=say_hi, args=('tedu',)) # 创建工作线程
t.start() # 启动工作线程 target(*args)

案例:扫描存活主机

  • 通过ping测试主机是否可达
  • 如果ping不通,不管什么原因都认为主机不可用
  • 通过多线程方式实现并发扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import subprocess
import threading

def ping(host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)

if __name__ == '__main__':
ips = ['192.168.1.%s' % i for i in range(1,255)]
for ip in ips:
t = threading.Thread(target=ping, args=(ip,))
t.start()

************************************************************

import subprocess
import threading

class Ping:
def __call__(self, host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)

if __name__ == '__main__':
ips = ['192.168.1.%s' % i for i in range(1,255)]
for ip in ips:
t = threading.Thread(target=Ping(), args=(ip,))
t.start()

************************************************************

import subprocess
import threading

class Ping:
def __init__(self, host):
self.host = host

def __call__(self):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % self.host, shell=True
)
if result.returncode == 0:
print('%s:up' % self.host)
else:
print('%s:down' % self.host)

if __name__ == '__main__':
ips = ['192.168.1.%s' % i for i in range(1,255)]
for ip in ips:
t = threading.Thread(target=Ping(ip))
t.start()

urllib模块概述

urllib基础

  • Python2版本中,有urlliburlib2两个库可以用来实现request

    发送。而在Python3中,已经不存在urllib2这个库了,统一为urllib

  • urllib中包括了四个模块

    urllib.request可以用来发送request和获取request的结果

    urllib.error包含了urllib.request产生的异常

    urllib.parse用来解析和处理URL

    urllib.robotparse用来解析页面的robots.txt文件

爬取网页

  • 先需要导入用到的模块:urllib.request

  • 在导入了模块之后,我们需要使用urllib.request.urlopen打开并爬取一个网页

  • 读取内容常见的有3种方式 :

    read()读取文件的全部内容,与readlines()不同的是,read()会把读取到的内容赋给一个字符串变量。

    readlines()读取文件的全部内容,readlines()会把读取到的内容赋值给一个列表变量。

    readline()读取文件的一行内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> from urllib import request
>>> html = request.urlopen('http://www.163.com')
>>> html.read(10)
b'<!DOCTYPE '
>>> html.readline()
b'HTML>\n'
>>> html.readline()
b'<!--[if IE 6 ]> <html class="ne_ua_ie6 ne_ua_ielte8" id="ne_wrap"> <![endif]-->\n'
>>> html.readline()
b'<!--[if IE 7 ]> <html class="ne_ua_ie7 ne_ua_ielte8" id="ne_wrap"> <![endif]-->\n'
>>> html.readlines()
# 若是读取报错,是因为服务器端口连接,因为客户端打开一个页面,不操作,会一直占用资源,服务器会先断开连接,当客户端点击页面中链接时,服务器重新建立连接
[......]

************************************************************

# 保存

>>> html = request.urlopen('http://www.163.com')
>>> with open('/tmp/163.html', 'wb') as fobj:
... fobj.write(html.read())
...
604035
(mypy) [root@python ~]# firefox /tmp/163.html # 用火狐打开

爬取图片

1
2
3
4
5
6
7
8
9
10
11
>>> from urllib import request
>>> url='https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF'
>>> image = request.urlopen(url)
>>> with open('/tmp/image.jpg','wb') as fobj:
... fobj.write(image.read())
...
66978
>>> exit()

(mypy) [root@python ~]# eog /tmp/image.jpg # 查看图片

下载网络资源

  • urllib不仅可以下载网页,其他网络资源均可下载

  • 有些文件比较大,需要像读取文件一样,每次读取一部分数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import urllib.request

html = urllib.request.urlopen('http://xxxx')
fobj = open('/tmp/xxx','ab')
while True:
data = html.read(4096)
if not data:
break
fobj.write(data)
fobj.close()

************************************************************

(mypy) [root@python ~]# pip install wget
(mypy) [root@python ~]# python

>>> import wget
>>> wget.download('https://t7.baidu.com/it/u=4036010509,3445021118&fm=193&f=GIF','/tmp/image2.jpg') # 源,本地
100% [..............................................................................] 44644 / 44644'/tmp/image2.jpg'
>>> exit()
(mypy) [root@python ~]# eog /tmp/image2.jpg

案例:爬取图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import os
import wget
import re # 正则表达式模块

def get_url(fname, patt, charset=None):
'用于在指定的文件中取出所有模式,返回一个列表'
result = []
cpatt = re.compile(patt) # 模式编译

with open(fname) as fobj:
for line in fobj:
m = cpatt.search(line)
if m:
result.append(m.group())

return result


if __name__ == '__main__':
path = '/tmp/163'
fname = '/tmp/163/163.html'
url = 'http://www.163.com'

if not os.path.exists(path):
os.mkdir(path)
if not os.path.exists(fname):
wget.download(url, fname)

# 定义图片链接(以http或https开头):(\w数字字符下划线,点‘.’,斜杠‘/’,减号‘-’)‘+’(至少一次).(以jpg或jpeg或png或gif结尾)
url_patt = '(http|https)://[\w./-]+\.(jpg|jpeg|png|gif)'

img_list = get_url(fname, url_patt, 'gbk') # 编码格式gbk
#print(img_list)

for img_url in img_list:
wget.download(img_url, path)

urllib应用

模拟客户端

  • 有些网页为了防止别人恶意采集其信息,所以进行了一些反爬虫的设置,而我们又想进行爬取
  • 可以设置一些Headers信息(User-Agent),模拟成浏览器访问这些网站
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> from urllib import request
>>> url = 'http://www.jianshu.com'
>>> r = request.urlopen(url)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
......
urllib.error.HTTPError: HTTP Error 403: Forbidden # 不允许

************************************************************

>>> from urllib import request
>>> url = 'http://www.jianshu.com'
# 请求头,通过浏览器F12获取
>>> headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.41'}
>>> r = request.Request(url, headers=headers)
>>> html = request.urlopen(r)
>>> html.read(20)
b'<!DOCTYPE html>\n<!--'

数据编码

  • 一般来说,URL标准中只允许一部分ASCII字符,比如数字、字母、部分符号等
  • 而其他的一些字符,比如汉字等,是不符合URL标准的,此时我们需要编码
  • 如果要进行编码,可以使用urllib.request.quote()进行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> import urllib
>>> urllib.request.quote('hello world!')
'hello%20world%21'
>>> urllib.request.unquote('hello%20world%21')
'hello world!'


>>> url = 'https://www.sogou.com/web?query=中国'
>>> request.urlopen(url)
Traceback (most recent call last):

return request.encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 15-16: ordinal not in range(128)


>>> url = 'https://www.sogou.com/web?query=' + request.quote('中国')
>>> url
'https://www.sogou.com/web?query=%E4%B8%AD%E5%9B%BD'

HTTP异常处理

  • 如果访问的页面不存在或拒绝访问,程序将抛出异常
  • 捕获异常需要导入urllib.error模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
(mypy) [root@python new]# yum install httpd -y && systemctl start httpd  
(mypy) [root@python new]# mkdir /var/www/html/ban
(mypy) [root@python new]# chmod 000 /var/www/html/ban



>>> from urllib import request
>>> url1 = 'http://127.0.0.1/abc'
>>> url2 = 'http://127.0.0.1/ban'
>>> request.urlopen(url1)

urllib.error.HTTPError: HTTP Error 404: Not Found

>>> request.urlopen(url2)

urllib.error.HTTPError: HTTP Error 403: Forbidden

************************************************************


from urllib import request,error

url1 = 'http://127.0.0.1/abc'
url2 = 'http://127.0.0.1/ban'

try:
r1 = request.urlopen(url1)
except error.HTTPError as e:
print('错误', e)

try:
r1 = request.urlopen(url1)
except error.HTTPError as e:
print('错误', e)

错误 HTTP Error 404: Not Found
错误 HTTP Error 403: Forbidden

paramiko模块应用

安装paramiko模块

  • 本地安装
1
2
3
[root@python ~]# yum install gcc gcc-c++ python-devel -y   
[root@python ~]# tar xf paramiko-1.15.4.tar.gz
[root@python ~]# python setup.py install
  • 网络安装
1
(mypy) [root@python ~]# pip install paramiko

基础使用介绍

  • SSHClient

    创建用于连接ssh服务器的实例

1
2
>>> import paramiko
>>> ssh = paramiko.SSHClient()
  • paramiko.AutoAddPolicy

    设置自动添加主机密钥

  • ssh.connect

    连接ssh服务器

  • ssh.exec_comand

    在ssh服务器上执行指定命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> import paramiko
>>> ssh = paramiko.SSHClient()

# 相当于输入yes
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
>>> ssh.connect('127.0.0.1', username='root', password='000000')

>>> ssh.exec_command('id root')
(<paramiko.ChannelFile from <paramiko.Channel 0 (open) window=2097152 -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 0 (open) window=2097152 -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>, <paramiko.ChannelFile from <paramiko.Channel 0 (open) window=2097152 -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 1 open channel(s))>>>)

# 输入、输出和错误的类文件对象
>>> result = ssh.exec_command('id root')
>>> result[0]
<paramiko.ChannelFile from <paramiko.Channel 1 (closed) -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>>>
>>> result[1]
<paramiko.ChannelFile from <paramiko.Channel 1 (closed) -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>>>
>>> result[2]
<paramiko.ChannelFile from <paramiko.Channel 1 (closed) -> <paramiko.Transport at 0xe083ca00 (cipher aes128-ctr, 128 bits) (active; 0 open channel(s))>>>

>>> out = result[1].read()
>>> err = result[2].read()
>>> out
b'uid=0(root) gid=0(root) \xe7\xbb\x84=0(root)\n'
>>> err
b''
>>> out.decode() # 把byte类型转换为string类型
'uid=0(root) gid=0(root) 组=0(root)\n'

paramiko实例

  • 编写SSHClient实例
  • 设置添加主机密钥策略
  • 连接ssh服务器
  • 执行指定命令
  • shell命令行中接收用于连接远程服务器的密码以及在远程主机上执行的命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import paramiko

def rcmd(host, user, password , port=22, cmds=None):
# 创建ssh客户端实例,用于远程执行命令
ssh = paramiko.SSHClient()
# 自动接收服务器发来的密钥
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(host,username=user,password=password,port=port)
# 执行命令
stdin, stdout, stderr = ssh.exec_command(cmds)
# 获取执行命令的输出和错误信息
out = stdout.read()
err = stderr.read()
if out: # 如果输出非空
print('[%s] \033[32;1mOUT\033[0m:\n%s' % (host,out.decode()))
if err: # 如果错误非空
print('[%s] \033[31;1mERROR\033[0m:\n%s' % (host,err.decode()))
# 关闭连接
ssh.close()

if __name__ == '__main__':
rcmd('127.0.0.1','root','000000',cmds='id root;id zhangsan')

案例:多线程ssh并发访问

  • 编写脚本程序
  • 在文件中提取出所有远程主机IP信息
  • 在shell命令行中接受远程服务器IP地址文件、远程服务器密码以及在远程主机上执行的命令
  • 通过多线程实现所有远程服务器上并发执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import paramiko
import sys
import os
import getpass
import threading

def rcmd(host, user, password , port=22, cmds=None):
# 创建ssh客户端实例,用于远程执行命令
ssh = paramiko.SSHClient()
# 自动接收服务器发来的密钥
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(host,username=user,password=password,port=port)
# 执行命令
stdin, stdout, stderr = ssh.exec_command(cmds)
# 获取执行命令的输出和错误信息
out = stdout.read()
err = stderr.read()
if out: # 如果输出非空
print('[%s] \033[32;1mOUT\033[0m:\n%s' % (host,out.decode()))
if err: # 如果错误非空
print('[%s] \033[31;1mERROR\033[0m:\n%s' % (host,err.decode()))
# 关闭连接
ssh.close()

if __name__ == '__main__':
# rcmd('127.0.0.1','root','000000',cmds='id root;id zhangsan')
if len(sys.argv) != 3:
print("Usage: %s ipfile 'commands'" % sys.argv[0])
exit(1)

if not os.path.isfile(sys.argv[1]):
print('No such file:', sys.argv[1])
exit(1)

ipfile = sys.argv[1]
password = getpass.getpass()
cmds = sys.argv[2]

with open(ipfile) as fobj:
for line in fobj:
ip = line.strip() # 将文件结尾的\n移除
t = threading.Thread(target=rcmd, args=(ip,'root','000000',22,cmds))
t.start()


(venv) [root@python new]# python rcmd.py server.txt 'sleep 3;id root'
Password:
[127.0.0.1] OUT:
uid=0(root) gid=0(root) 组=0(root)

[127.0.0.1] OUT:
uid=0(root) gid=0(root) 组=0(root)

[127.0.0.1] OUT:
uid=0(root) gid=0(root) 组=0(root)

[127.0.0.1] OUT:
uid=0(root) gid=0(root) 组=0(root)

[127.0.0.1] OUT:
uid=0(root) gid=0(root) 组=0(root)

邮件编程

发送邮件

设置邮件

  • 标准邮件需要三个头部信息

    FROM:发件人

    To:收件人

    Subject:主题

1
2
3
4
5
6
7
8
from email.mime.text import MIMEText
from email.header import Header

message = MIMEText('Python email test','plain','utf8')# 正文
message['From'] = Header('zhangsan','utf8')
message['To'] = Header('root','utf8')
subject = 'Python SMTP email test'
message['Subject'] = Header(subject,'utf8')

SMTP概述

  • SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,使用TCP协议25端口
  • 它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式
  • pythonsmtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装

SMTP对象

  • Python发送邮件,第一步是创建SMTP对象
1
2
import smtplib
smtp_obj = smtplib.SMTP([host[,port[,local_hostname]]])
  • 创建SMTP对象也可以不给定参数,之后再通过对象的其他方法进行绑定

sendmail方法

  • Python SMTP对象使用sendmail方法发送邮件
1
SMTP.sendmail(from_addr,to_addrs,msg[,mail_options,rcpt_options])
  • sendmail方法三个必须的参数有:

    收件人

    发件人

    消息主体msg是一个字符串,表示邮件

  • 将准备好的邮件发送

1
2
3
4
5
6
import smtplib

sender = 'from@tarena.com'
receivers = ['root@localhost']
smtp_obj = smtplib.SMTP('localhost')
smtp_obj.sendmail(sender,receivers,message.as_string())

案例:通过本机发送邮件

  • 创建bob和alice账户
  • 编写发送邮件程序,发件人为root,收件人为本机的bob和alice账户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from email.mime.text import MIMEText
from email.header import Header
import smtplib

# 准备邮件
# plain表示纯文本
msg = MIMEText('Python email test\n','plain','utf8')
# 为邮件添加头部信息
msg['From'] = Header('root','utf8')
msg['To'] = Header('qwe','utf8')
msg['Subject'] = Header('by test','utf8')


# 发送邮件
smtp = smtplib.SMTP('127.0.0.1')
sender = 'root'
receivers = ['qwe','alice']
smtp.sendmail(sender,receivers,msg.as_bytes())


(venv) [root@python new]# useradd qwe
(venv) [root@python new]# mail -u qwe
Heirloom Mail version 12.5 7/5/10. Type ? for help.
"/var/mail/qwe": 1 message 1 new
>N 1 =?utf8?q?root?=@pyth Sun Jun 11 11:30 19/639 ""
&
Message 1:
From root@python.localdomain Sun Jun 11 11:30:47 2023
Return-Path: <root@python.localdomain>
X-Original-To: qwe
Delivered-To: qwe@python.localdomain
Content-Type: text/plain; charset="utf8"
From: root@python.localdomain
To: qwe@python.localdomain
Subject: by test
Date: Sun, 11 Jun 2023 11:30:47 +0800 (CST)
Status: R

Python email test
&

SMTP认证

  • 如果本机没有SMTP功能,也可以使用第三方的邮件服务器
  • 第三方邮件服务器往往需要认证
1
2
3
4
5
6
7
8
mail_host='mail.tedu.cn'
mail_user='zhangsan'
mail_pass='zhangsan_pass'
smtp_obj=smtplib.SMTP()
smtp_obj.connect(mail_host,25) # 25为SMTP端口号
# smtp.starttls() # 如果使用此证书,打开注释
smtp_obj.login(mail_user,mail_pass)
smtp_obj.sendmail(sender,receicers,message.as_string())

案例:通过互联网服务器发送邮件

  • 通过自己互联网注册的邮箱,为其他同学发送邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from email.mime.text import MIMEText
from email.header import Header
import smtplib
import getpass

def inet_mail(host, sender, passwd, receivers, body, subject):
# 准备邮件
msg = MIMEText(body, 'plain', 'utf8')
# 为邮件添加头部信息
msg['From'] = Header(sender, 'utf8')
msg['To'] = Header(receivers[0], 'utf8')
msg['Subject'] = Header(subject, 'utf8')


# 发送邮件
smtp = smtplib.SMTP()
smtp.connect(host)
# smtp.starttls() #
smtp.login(sender, passwd)
smtp.sendmail(sender, receivers, msg.as_bytes())

if __name__ == '__main__':
server = 'smtp.qq.com'
sender = 'xxx@qq.com'
receivers = ['xxx@qq.com', 'xxx@qq.com']
passwd = getpass.getpass()
body = 'python email test\n'
subject = 'py python'
inet_mail(server, sender, passwd, receivers, body, subject)

JSON

JSON概述

  • JSON(JavaScript Object Notation)是一种轻量级的数据交换格式
  • 易于人阅读和编写,同时也易于机器解析和生成
  • 基于JavaScript Programming Language
  • JSON采用完全独立于语言的文本格式,但是也使用了类似C语言家族的习惯(包括C,C++,C#,Java,JavaScript,Perl,Python等)
  • 这些特性是JSON成为理想的数据交换语言

JSON结构

  • JSON主要有两种结构

    “键/值”对的集合:python中主要对应成字典

    值的有序列表:在大部分语言中,它被理解为数组

Python json
dict object
list, tuple array
str string
int, float number
True true
False false
None null

dumps方法

  • 使用简单的json.dumps()方法对简单数据类型进行编码

loads方法

  • 对编码后的json对象进行decode解码,得到原始数据,需要使用json.loads()函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> import json
>>> number = json.dumps(100)
>>> json.loads(number)
100


>>> adict = {'user': 'tom', 'age': 20}
>>> json.dumps(adict)
'{"user": "tom", "age": 20}'
>>> data = json.dumps(adict)
>>> type(data)
<class 'str'>

>>> json.loads(data)
{'user': 'tom', 'age': 20}
>>> jdata = json.loads(data)
>>> type(jdata)
<class 'dict'>

天气预报查询

requests基础

requests基础

requests简介

  • Requests是用Python语言编写的、优雅而简单的HTTP
  • Requests内部采用urllib3
  • Requests使用起来肯定会比urllib3更简单便捷
  • Requests需要单独安装

requests特性

  • 支持keep-alive的连接池
  • 支持通用的域名以及URL地址
  • 支持使用cookie
  • 支持使用类型浏览器的SSL验证
  • 文件上传、下载

GET和POST

  • 通过requests发送一个GET请求,需要在URL里请求的参数可通过params传递
1
r = requests.get(url="", params={}, headers={}, cookies={})
  • GET不同的是,POST请求新增了一个可选参数data,需要通过POST请求传递的body里的数据可以通过data传递
1
r = requests.post(url="", data={}, params={}, file={}, headers={}, cookies={})

其他方法

  • 其他HTTP请求类型:PUTDELETEHEAD以及OPETIONS使用起来一样简单
1
2
3
4
>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
>>> r = requests.delete('http://httpbin.org/delete')
>>> r = requests.head('http://httpbin.org/get')
>>> r = requests.options('http://httpbin.org/get')

请求参数

  • 当访问一个URL时,我们经常需要发送一些查询的字段作为过滤信息,例如:httpbin.com/get?key=val,这里的key=val就是限定返回条件的参数键值对
  • 当利用pythonrequests去发送一个需要包含这些参数键值对时,可以将它们传给params
1
2
3
4
5
6
7
8
9
10
11
12
payload = {'key1':'value1','key2':'value2'}
r = requests.get('http://httpbin.com/get',params = payload)

# 查快递例子
>>> kd_url = 'http://www.kuaidi100.com/query'
>>> params = {'type': 'youzhengguonei', 'postid': '9893442769997'} # 邮政国内,单号

# URL为http://www.kuaidi100.com/query?type=youzhengguonei&postid=9893442769997(?传参)

>>> r = requests.get(kd_url, params=params)
>>> r.json()
{'message': 'ok', 'nu': '9893442769997', 'ischeck': '0', 'condition': '00', 'com': 'youzhengguonei', 'status': '200', 'state': '0', 'data': [{'time': '2023-06-09 10:27:57', 'ftime': '2023-06-09 10:27:57', 'context': '【咸阳市】西安分拨中心已发出,下一站新疆阿克苏集散站电话【099-72661378;18096928353】', 'location': '咸阳市 西安分拨中心'}, {'time': '2023-06-07 02:09:57', 'ftime': '2023-06-07 02:09:57', 'context': '【咸阳市】西安分拨中心快件已到达', 'location': '咸阳市 西安分拨中心'}, {'time': '2023-06-06 05:50:17', 'ftime': '2023-06-06 05:50:17', 'context': '【杭州市】萧山分拨中心已发出,下一站西安分拨中心', 'location': '杭州市 萧山分拨中心'}, {'time': '2023-06-05 22:58:45', 'ftime': '2023-06-05 22:58:45', 'context': '【杭州市】萧山分拨中心快件已到达', 'location': '杭州市 萧山分拨中心'}, {'time': '2023-06-05 16:30:26', 'ftime': '2023-06-05 16:30:26', 'context': '【绍兴市】嵊州正大国际收件员已揽件', 'location': '绍兴市 嵊州正大国际'}]}

设定头部

  • 用户也可以自己设定请求头
1
2
3
4
5
6
7
8
9
url = 'https://api.github.com/some/endpoint'
headers = {'Accept': 'application/json'}
r = requests.get(url,headers = headers)

# 访问简书案例
>>> js_url = 'http://jianshu.com'
>>> headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0'}
>>> r2 = requests.get(js_url, headers=headers)
>>> r2.text

发送请求数据

  • 有时候,用户需要将一些数据放在请求的body内;这时候,就需要对data传参了(仅POSTDELETEPUT等方法有该参数,GET没有,因为GET请求没有body
1
2
payload = {'key1':'value1','key2':'value2'}
r = requests.post('http://httpbin.org/post',data = payload)

requests应用

相应内容

  • 读取服务器相应的内容
1
2
3
4
5
6
>>> r = requests.get('http://www.baidu.com')
>>> r.text

>>> url = 'http://www.baidu.com'
>>> r = requests.get(url)
>>> r.text
  • 请求发出后,Requests会基于HTTP头部对相应的编码做出有根据的推测
  • 可以找出Requests使用了什么编码,并且能够使用r.encoding属性来改变它
1
2
3
4
>>> r.encoding
'ISO-8859-1'
>>> r.encoding = 'utf8'
>>> r.text

其他相应内容格式

  • 也可以用字节的方式访问请求相应体,尤其是非文本请求(二进制bytes)
1
>>> r.content
  • Requests中还有一个内置的JSON解码器,助你处理JSON数据
1
>>> r.json()

响应头

  • 可以查看以字典形式展开的服务器响应头
1
2
>>> r.headers
{'Server': 'nginx', 'Date': 'Sun, 11 Jun 2023 09:53:41 GMT', 'Content-Type': 'text/html;charset=UTF-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'P3P': 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"', 'Cache-Control': 'no-cache', 'Set-Cookie': '_adadqeqwe1321312dasddocTitle=kuaidi100; domain=.kuaidi100.com; path=/;, _adadqeqwe1321312dasddocReferrer=; domain=.kuaidi100.com; path=/;, _adadqeqwe1321312dasddocHref=; domain=.kuaidi100.com; path=/;, _adadqeqwe1321312dasddocTitle=kuaidi100; domain=.kuaidi100.com; path=/all;', 'Content-Encoding': 'gzip'}