学习 Python3 基础
目录
- 目录
- 1 变量
- 2 Python 内置数据类型
- 3 文件(file)
- 4 条件与循环
- 5 函数
- 6 模块(module)
- 7 面向对象(OOP)
- 8 异常处理
- 9 常用模块
- 10 对象持久化
- 11 字符串与编码🔨
- 12 调试🔨
- 13 正则表达式(Regular Expression)
- 14 枚举(enum)
- 15 闭包
- 16 装饰器
- 17 并发编程
- 18 网络编程🔨
Q1:为什么学习Python?
A1:避免成为脚本小子,要成为一个有造轮子能力的黑,按余弦说的,这不就是工程化能力嘛。2020/1/19
Q2:Linux 编译安装后的小问题
A2;CentOS7 下使用 Python 自带命令提示符,不能删除输错命令,先安装 yum install readline-devel -y
,然后重新编译 Python 即可。
Q3:Windows 下 Python 怎么安装多个版本?并且可以灵活切换。
A3:下载安装后进行安装会有管理员权限复选框,勾上后,下一步中会有 py launch 选项,勾上完成安装就有了 py.exe,可以通过它来启动或切换不同版本 Python。就算系统目前已经安装了一个,也可以这么操作。
1 变量
一个值经常需要引用就可以存到变量中。
关于变量命名,多个单词的话个人喜欢以下划线分隔,下面示例中第一种。
couse_color
CouseColor
couseColor
其实只要以字母或下划线开头命名就是正确的(下划线开头在 Python 中有特殊含义),还有命名要让人能一眼看出什么意思(文件命名也是同理),变量名中不能有空格,还要避免与 Python 关键字(也称作保留字)和函数名,比如关键字 if
,另外变量名区分大小写。
Python 数据类型是看值,不是看变量类型。
变量值是在内存开辟一块空间进行存储,当变量指向一个新值,原来值会被垃圾回收机制在计数器为 0 的特定时间进行回收。
共享引用:多个变量指向同一个对象(值) a='x' b='x'
,他们在内存中使用是同一块地址,用 a is b
判断他们在内存中是不是使用同一块地址,id(object)
也可以返回地址。只有 0-255 的数字会被 Python 缓存,短字符串也会被缓存,并没指定多长会被缓存。
int、str、tuple 这三种类型的值不可变,而 list、set、dict 这三种引用类型的值可变。
>>> a = 1
>>> b = a
>>> a = 3
>>> print(b) #结果是 1
>>> a = ('A','B',[1,2,3])
>>> b = a
>>> a[2][0] = '1'
>>> print(b) #结果会随 a 变化
搞清楚分类方式,就像看到一个钉子时去选择合适的锤子,能省下不少力气。
2 Python 内置数据类型
#程序注释,用于写上程序说明信息。
注释不会被 Python 解释器当做代码执行import module_name
导入模块
2.1 数字
整型(int)
- 0b(0B) 表示为二进制数
- 0o(0O) 表示为八进制数
- 0x(0X) 表示为十六进制数
- 十进制不需要前缀
- True=1 / False=0
将字符串转换为十进制 int('字符串',进制类型)
,例如 int('0b1111000',2)
结果是十进制 120,字符串中 0b 可不加 int('1111000',2)
,像 oct(i)
其它进制转换方法也是如此。将含有整数字符串转换为小数可以用 float('str')
。要将十进制数输出为其他进制时可以用:bin(i)
二进制,oct(i)
八进制,hex(i)
十六进制,里面的 i 可填十进制或变量。
Python里加减乘除是 +-*/
,整数乘一个小数结果会是 float(1*1.0),2/2
整数除整数结果会是 float,浮点数运算存在不确定尾数,要想得到int需要多加一个 / 整除,例如 2//2
这样就只会保留整数部分。
还有其他运算符,幂运算 **
比如 2**8
,当 x**y
这个 y 是小数则是 x 开平方运算,取模(模运算)%
也就是取余数比如 17/2
。
这些运算符还有优先级(下图优先级从低到高),这里是官方文档,如果要想指定优先级可以用(),比如 (4 * (5 - 1)) ** 2
。
最后一个叫做布尔型它属于整形(int)并对应着整数中 1 和 0,所以用于计算也是没问题的 (True + True) + 1
。
浮点(float)
- 3.14
对应生活中小数
math 模块一些方法可以对 int 和 float 使用。
import math #要想使用得先导入 module
math.trunc(3.99) #取值朝向 0,结果 3,可以理解为将小数截去。
math.trunc(-0.5) #取值朝向 0,结果 0
math.trunc(-1.3) #取值朝向 0,结果 -1
math.ceil(3.99) #不管多大总是向上取整数,结果 4
math.ceil(2.5) #结果 3
math.floor(10.99) #不管多大总是向下取整数,结果 10
math.ceil(5.69) #结果 5
round(number[, ndigits])
对 number 这个数字四舍五入保留多少 ndigits 小数。出现了没有四舍五入的情况,可以看官方的对 round 解释。
数值运算内置函数
abs(x) # 求 x 绝对值
round(x) # 对 x 四舍五入,d 是保留小数几位,可以省略不填。
divmod(x,y) # 同时输出商和余数,相当于 x//y 得出商接着 x%y 得出余数。
pow(x,y[,z]) # 幂运算,其中z参数是用来做模运算的(取余),相当于(x**y)%z。
complex(x) # 将 x 变成复数,增加虚数部分。
max() # 找出最大值
min() # 找出最小值
浮点数用科学计数法表示,<a>e<b>
表示为,4.3e-3 等于 4.3 乘以 10 的负三次方。
复数(complex)
- Python 中表示复数
10j
在后面加上 j。
a+bj # a 是实部分,bj 是虚部。
# 通过复数的属性获得结果
x.real # 获得实部
z.imag # 获得虚部
2.2 布尔(Bool)
- True,False
如果一个人突然冲到你面前兴奋讲道:明天你会变得很有钱!根据你现在程度会想这不可能是真的。这是生活中的真假,在 Python 中表示假就会用到 False
,真代表 True
。
如果要测试一结果可以用 bool(value) 方法,value 如果有值就是 True,如果填 0、0.0、False、空对象(空字符串...)、None 其中任意一种就是 False。
另外,一个自定义对象在实例化后会调用 __bool__(self)
方法,它返回 False 那调用 bool() 为 False,此方法若没定义则调用 __len__(self)
方法,这个方法返回一个数字,如果为 0 同为 False,反之为 True,如果这两个方法都没定义则返回 True。
如果不清楚数据是什么类型,可以用 type()
来确认,例如 >>> type(0o144)
,所有数据类型都可以用这个内置函数判断类型。
>>> b = 1
>>> b += b >= 1 # b>=1 是 True,然后 b=b+True。
>>> b += b < 1 # b<1 是 False,b+False 还是 1。
(1,2,3)<(1,3,2) #一位一位去判断,到 2<3 为 True 就停,后面 3<2 就不判断。
1 and 0 #计算时需要看第二个值,所以返回 0。
1 and 2 #计算时需要看第二个值,所以返回 2。
0 and 2 #计算时看第一个值,所以返回 False,第一个值都为 False 第二个值就不用看了。
1 or 0 #计算只看第一个值,不需要看第二个值返回 1(称作短回环)。
3 or 1 #看第一个值为 True 就不需要看第二个值,返回 3。
0 or 1 #这时第一个是 False,需要看第二个值是什么,返回 1。
not not True #结果True,第一次 not 是 False,第二次 not 就是 True 了。
2.3 序列(Sequence)
序列是指有序排列,每个元素都有序号,用下标访问序列元素。下面是一些通用操作,以列表和字符串举例。
#通过索引访问某一个值
>>> ['Am', 'B7', 'Cadd2', 'Dsus4'][0] #得到 'Am'。这样通过正数序号获得数据称作正向递增序号。
>>> ['Am', 'B7', 'Cadd2', 'Dsus4'][-1:] #得到 ['Dsus4'],加上冒号它就返回列表。这样通过负数序号获得数据称作反向递减序号。
#如果是 -2 会是倒数第二个值,从右向左访问,以此类推。
#对元素进行切片(获取片段数据)
'String'[:7]
#索引 0 开始到第 7 个取出,可没有第 7 个字符,不管结束范围再大只会返回所有字符,结果为 String。
'String'[0:4] #访问 0 到 3 这四个元素,4 是指 4 前面的元素并不包含 4,结果是 Stri。
'String'[0:-1] #访问Strin。
'String'[:6] #这一段是说从前面 0 开始到第 6 前面字符都拿出来,返回 String。
'String'[-3:] #访问 ing。
'String'[:] #访问所有元素。
#按步长(stride)访问范围内的值
'String'[::2]
#访问所有元素。从 0 开始算每两步取 1,不填步长默认是 1,结果是 'Srn'。
#step可以为负数你知道吗?,str_give='123456789'怎么取出987?可以用 str_give[:5:-1],我惊了。
#判断某个内容在序列内,返回 bool。
'内容' in 序列
'内容' not in 序列
#两个列表合并
>>> popular_chord = ['Am', 'B7', 'Cadd2', 'Dsus4']
>>> sevent_chord = ['Cm7']
>>> popular_chord + sevent_chord
>>> ['Am', 'B7', 'Cadd2', 'Dsus4', 'Bdim']
#不会对原来数据做更改,只是返回一个新列表。
#重复出现多次
>>> Chord = ['C', 'Dm', 'Em', 'F', 'G', 'Am', 'Bdim']
>>> Chord * 3 #chord内数据重复 3 次,返回一个新列表。
#返回序列长度
len(obj) #会返回元素个数
#获取大小和总值
max(obj) #大
min(obj) #小,字符串则是比大小 ASCII 码
sum() #求综合运算后的值
#返回值第一次出现索引位置
sequence.index(value)
#返回值出现的次数
sequence.count(value)
2.3.1 可变序列通用操作
#改变某一个索引下的值
s[i] = new_value
#改变某个范围索引下的值
s[i:j] = t #t是可迭代对象(想象成列表)
>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> #a[:3] = [] 前三个值替换为空
>>> #a[:3] = ['a'] 前三个值替换为a,注意不是每一个
>>> #a[:3] = ['ABC','ABC','ABC'] 前三个值每一个替换成ABC
>>> #a[:3] = 'ABC','ABC','ABC' 在练习中发现这样写也可以,不知有什么隐患。
s[i:j:k] = t #这样写要知道经过步长筛选后有几个元素被选中
>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> #a[::2] = ['A','B','C','D','E']
>>> #将02468替换成ABC,因为步长选中了这些元素,替换值个数必须要跟他相等
#删除
del s[i]
del s[i:j]
del s[i:j:k]
s.remove(x) #删除一个指定值,如果有多个相同值,会删除第一个匹配到的值
s.clear() #清空序列,s[:] = []效果一样。
#弹出(删除)一个值
s.pop([i])
#[i]可选的意思,i是指定index,默认返回末尾的值。
#增加一个值
s.append(x)
#增加多个值
s.extend(x)
>>> a.extend(['a','b','d'])
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'd']
#增加一个值到指定位置
s.insert(i,x) #如果索引值超过序列长度,值会放在末尾。
s[i:i] = [value]
#如何插入值到嵌套列表
#复制序列
s.copy() #s[:]也可以,这样复制后是在内存开辟不同空间进行存储。
列表(list)
- ['字符串', 1, True, ['这是嵌套列表']]
列表是一组有序数据,里面可以存放不同类型的值,通过下标引用数据。
常用操作
list(range(5)) #生成列表,range生成 0-4 五个int值。
#排序
s.reverse() #从小到大更改原来序列,不返回值,如果再次使用会再次翻转。
s.sort(revers=False) #默认升序,revers=True 是降序。
#我试了试不能 int 和 str 混合进去排序,只能单一进行。
sorted(t) #不改变原有序列,降序操作后返回一个新 list
>>> a
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> sorted(a)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
集合(set)
- {1, 2, 't', True}
无序集合,自带去重。这个数据类型在 AI 和机器学习中用的多。
{1, 2, 3, 4, 5, 6} - {3, 4} #找出差集
{1, 2, 3, 4, 5, 6} & {3, 4} #找出交集
{1, 2, 3, 4, 5, 6} | {3, 4} #找出并集
set() #定义空集合
字典(dict)
- {key: value, key: value, {key1: value,key2: value}}
它是无序集合,因为你创建的顺序和最终呈现顺序可能不一致,字典表中可以存任何类型对象。
字典key必须是不可变类型,像 list 就不可以做为 key,那字符串、数字、元组这几种可以作为 Key 使用。
字典呢还有点像JSON这种数据格式,在 Python 中会将 JSON 转换为字典这种数据类型,其他语言里也应该有对应的数据类型去对应着JSON,比如 JavaScript 中 Object。
#声明字典表
>>> {} #空字典
>>> {'云': '雨', '雪': '空', '晚照': '晴空', '来鸿': '去燕', '宿鸟' :'鸣虫'}
#可以转换为字典
dict(key=value)
>>> dict(name=9521,age='no')#默认 key 会被转成 string。
{'name': 9521, 'age': 'no'}
>>> dict([('name',9521),('age','none')]) #有规律可迭代对象会被转成 dict。
{'name': 9521, 'age': 'none'}
#将序列作为 key
>>> dict.fromkeys(['name',9521,'age','no'])
{'name': None, 9521: None, 'age': None, 'no': None}
#通过 key 访问数据
>>> a = {'title': '乡土中国', 'auther': '费孝通', '分类':{'类别': '社会学'}}
>>> a['分类']['类别']
>>> '社会学'
get(key[,default]) # key不存在就返回定义的第2个 default 值,没写 default 值就返回 None。
>>> a.get('分类'['类别'], '这个key不存在')
#获取所有keys 和 values
dict.keys() #获取所有 key,返回类型是 dict_view 不是 dict。
dict.values() #获取所有 value,返回类型是 dict_view 不是 dict。
dict.items() #获取所有 key和 value,返回类型是 dict_view 不是 dict。
#返回字典长度
len(dictview) #有多少组数据
#复制字典
dict.copy()
#清空字典
dict.clear()
#更改数据或添加数据
dict['key'] = 'new_value'
#合并字典
dict.update(new_dict) #将 new_dict 插入到 dict 中,不会对 new_dict 有影响。
#删除内容
dict.pop('key'[,defalut])
#pop()会返回键的值,键不存在就抛错,default 在 key 找不到返回这个值,None 不返回消息。
dict.popitem() #弹出整个键值对,3.7 中从后向前,<3.7 随机弹出。
del dict['key'] #删除这个键,包括值。
#查询 key 在不在 dict 内,返回布尔值。
'key' in dict
'key' not in dict
使用字典要注意 key 不能一致,不然键始终指向最后一个数据。
>>> a = {'a': 1, 'a': 3, 1.0:'test', 'a': 2}
>>> a['a']
2
>>>
2.3.2 不可变序列
字符串(str)
"1'0"
加上引号就是字符串,'1"0'
单引号也可以。'''三引号中内容也是字符串(当然也可以用 3 个双引号啦),它表示多行文本,在没有任何操作的情况下可以当作注释用,但本质上还是字符串'''
文字 Python 中叫字符串。
将数字转换成字符串可以用str(number)
函数,里面的 number 可以填上数字,十进制可以不加前缀,其他进制要加上例如:str(0b1111000)
。
写的东西多了挤在一行,自然就看不得不顺眼,这里就可以用多行字符串来写代码,'''
或"""
都可以。
#Linux Style
>>> '''
... "H"e'll'o World
... 这是三引号中的内容
... '''
'\n"H"e\'ll\'o World\n这是三引号中的内容\n'
#IDLE Style
>>> '''
Hello World
这是三引号中的内容
'''
'\nHello World\n这是三引号中的内容\n'
难道就你三引号能表示换行吗?这里的单引号也可替换成双引号。
>>> 'Hellow\
... World'
'Hellow World'
\n(换行)和 \r(回车)它俩不是一个概念,\r 指定一段字符放入行首,会根据自身字符长度替换行首字符,多出来的内容显示在后面。
一个应用场景是做进度条,进度条内容长度一致每次产生变化后直接 \r 回到行首覆盖掉原有内容。
>>> print('1234567多\rtest123')
test123多
转义符r'\n \t \r'
在开头用 r 表示原生字符串(raw),大写R也可以,就只是把引号内的字符当做字符串。
下面这条就是错误示范
print(r'字符串'还是要遵循语法规范开头结尾闭合后才算完整,字符串都无法构成怎么算原生字符串呢?')
b'azx129'
以 b 开头是字符串声明为字节方式,内容要在 ASCII 码范围内或者是十六进制信息。
常用操作
#类型转换
str() #相当于在两侧加上引号
ord(x) #将字符x转换成Unicode码
chr(u) #将Unicode编码对应的字符展示。
#字符串替换
str.replace(old, new[, count])
'SStrinSg'.replace('S', 'A', 1) # 把 S 替换为 A 只替换 1 位,返回一对新字符串。
'A' + 'String'[1:] # 把第一位替换成 A。
#首字母大写
str.capitalize()
#整体转换大小写
str.upper() # 大写
str.lower() # 小写
#判断开头结尾
str.startswith('str') # 判断以什么开头,返回 bool。
str.endswith('str') # 判断以什么结尾,返回 bool。
#判断整体是否为数字或字母
str.isnumeric() # 是否是数字,返回 bool。
st.isalpha() # 是否是字母,返回 bool。
#居中字符串
str.center(width,[fillchar])
>>> "String".center(20, '=') # String 加上填充它的宽度一共 20 个字符,在 String 居中后两侧用 = 填充。
#去除左右侧字符
str.strip(chars) # 去除 str 左右侧中指定 chars。
#拆分连接字符串
str.split(str, [num]) # str 是参照什么分隔,num 默认分隔分割所有指定的字符,返回列表。
>>> "S,:asd.TR,NAG".split(':') # 以冒号分隔
['S,', 'asd.TR,NAG']
>>> "S,:asd.TR,NAG".split(',') # 以逗号分隔
['S', ':asd.TR', 'NAG']
>>> "S,:asd.TR,NAG".split(',', 1) # 以逗号分隔我只分割一次后面的不管。
['S', ':asd.TR,NAG']
>>> "S,:asd.TR,NAG".split() # 不填参数把 str 整个作为参数返回。
['S,:asd.TR,NAG']
str.join(s)
>>> ' '.join('setlkajsdf') # 序列除了最后一个元素,其他元素以空格作为分隔符进行连接。
's e t l k a j s d f'
str.index(sub_str) # 在 str 中匹配 sub_str 子串,成功返回首位字符索引,失败抛出 ValueError 异常
格式化
str.format() #下面是它的语法。
官方原文(有删减):[[fill][align][width][grouping_option][.precision][type]
中文解释:{:<填充><对齐><宽度><千位分隔符><精度><输出类型>}
官方文档:https://docs.python.org/zh-cn/3/library/string.html#formatspec
字段 | 解释 |
---|---|
fill(填充) | 可以是任意字符,默认是空格。 |
align(对齐) | <左对齐 >右对齐(默认值) ^居中 |
width(宽度) | 填充字符总长度,会算上后面参数长度。例如str我填充20,这20就包括了str。 |
grouping_option(分组) | 主要针对整数进行千分位,例如:4000表示为4,000。这里用, 表示 |
.precision(精度) | 表示保留小数点后几位.2就保留2位。 |
type(类型) | b=二进制,c=unicode字符,d=十进制,o=八进制,xorX=十六进制。——整数 e或E=科学计数法,f=定点数表示方法,不填精度默认为6位,%=把数乘以100进行显示百分比,比如1=100%。——浮点数 |
格式化字示例:
>>>'name:{0} age:{1} job:{2}'.format('value1', 'value2', 'value3')
'name:value1 age:value2 job:value3'
#它可以把花括号作为一个占位符,意思就是这个地方我先占着,一会儿我放东西进来。
>>>'name:{1} age:{1} job:{2} test:{0}'.format('value1', 'value2', 'value3')
'name:value2 age:value2 job:value3 test:value1'
#也可以重复使用一个值
>>>'name:{} age{} job{}'.format('value1', 'value', 'value3')
'name:value1 age:value job:value3'
#它的默认顺序就是一对一,所以可以不填。
#前面的占位符数量要和后面参数匹配。不然就抛出错误
>>>'age={} job={} 一会儿再来={hold}'.format('value', 'value', hold='没问题')
'age=value job=value 一会儿再来=没问题'
#也可以在占位符内填写变量一会赋值,总感觉这样可能会忘记,不过灵活是真的。
>>>'name:{hold} age{} 一会儿再来:{}'.format('value1', hold='没问题', 'value3')
'name:value1 age:value job:value3'
#占位符和值一定要对应,不能第一个占位符写hod变量,值却写在第二个,
#一定要一一对应,因为它默认就是一一匹配。
>>> '{:=^20.2f}'.format(1.12312)
'========1.12========'
#这条是针对小数,用=填充,^居中显示,数量20,.2是指显示小数点后2位,f代表这个变量放入槽中的类型。
>>> '{:=^20,}'.format(1502157.12312)
'==1,502,157.12312==='
#用逗号表示千分位显示。
在 3.6 版本中添加了一个新操作,使用起来比 format 方便不少,只不过要操作的值放在冒号左边,那冒号右边是对它格式的设定,其语法和 format 一致。
>>> f'{123123.123123:=^20.2f}'
'=====123123.12======'
元组(tuple)
- (123,123.0,'123', True,['a','b'],('cool',1))
元组内容定义后不能改值,元组只有一个元素需要在元素后面加上逗号(1,)
,不然默认为int类型,以区分括号用来提高优先级做数字运算(1+1)
。
字符串和元组是不可变类型!!!这里不可变是指不可改变值。
#声明元组
a = 1,2 #可以省略,只要前后语法不冲突,但建议加上元括号。
a = 1, #一个元素,如上。
#声明空元组
empty_tuple = ()
tuple() #转换为元组
#一个奇怪的赋值,官方文档称为元组封装,序列解包。
>>> t = 12345, 54321, 'hello!'
>>> x, y, z = t #不一定是变量也可是具体值,等同于x, y, z = 12345, 54321, 'hello!'
>>> print(x, y, z)
>>> 12345 54321 hello!
#只要右侧序列与左侧元组个数相等就可以赋值,已测试str set list tuple dict都可实现。
#下面再记录几个好用的赋值姿势。
>>> x, *y, z = "四个字符"
#'四'赋给x,'个字'赋给y并返回时list,'符'赋z,这种称作序列赋值。
>>> a, b = 10, '11'
>>> a, b = b, a #这样a,b就可以交换变量值
>>> x = y = 5
#将多个变量赋同一个值。y=5,x=y,它们都是5,称作多目标赋值。
>>> x+=10
#等于x = x+10,称作参数化赋值。+ - * / % ** //等运算符都支持。
range(范围)
用于生成数值序列,类型是 range。range(start, stop[, step])
,start 从几开始 stop 到多少结束(范围只填一个数默认是0),开始到结束来取这个范围,step 是步长这个和序列中切片是一个意思,默认是 1。
>>> tuple(range(10)) #默认从0-9不包含10
>>> tuple(range(1, 21)) #从1-20
>>> tuple(range(1, 21, 3))从1-20每隔3步取一位。
range 是不可变序列,不支持原位改变值,但能支持通用序列操作
>>> a = range(1, 11)
>>> a[-1]
>>> 10
>>> a[-1] = 111
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'range' object does not support item assignment
3 文件(file)
文件也是一个可迭代对象。
如果是二进制要在模式后面加 b,如 rb。r=read w=write a=append,模式默认是r
open('Path','Mode',encoding='') #创建实例
数据读取
obj.read() # 一次性读取所有实例内容,对大文件需要内存更大。
obj.read(n) # 一次性读取一段信息,n读取的数量取决于你的模式是字符还是字节。
obj.readlines() # 读取多行内容,返回列表。
obj.readline() # 以\n为分隔符来读取一行内容,返回列表。
读取完成后指针移动到内容后面,再次读取也是空的,将指针移动到内容开头,避免在文件读取完后继续读取是空的情况。
obj.tell() # 可以读取文件指针位置。
obj.seek(0) # 调整指针到指定字符
数据写入
obj.write() #写入一个字符串
obj.writelines() #写入列表中提供的内容
obj.close() #每次操作完成为了节省资源需关闭连接,如果是在写入模式下调用close会将内存数据写入硬盘,不调用Python会按照垃圾回收机制进行处理。
obj.flush #将内存中数据写入硬盘,不然就close()结束操作来写入。
4 条件与循环
表达式:由运算符和操作数所构成的序列
代码块(语句):多个表达式构成代码块
左结构:从左到右运行
右结构:从右到左运行
if
用于决定程序走向(像游戏底特律你的每个决策都会剧情走向不同的结果)。
if condition: # 单分支结构
pass # 占位语句或者是空语句,没有任何作用
# 只要条件满足(为True)就执行
if condition: # 二分支结构
pass
else:
... # 等同于pass
if condition: # 多分支结构
pass
elif:
pass # elif相比大量使用if/else代码量更少。
else:
pass # else一定是和if一起出现,不然会报错。
其他语言的三元运算符像是这样:x > y ? 1 : 2
,用问号作为分隔符,x大于y返回1否则返回2。
在Python中定义如下,条件满足执行if左侧表达式1,否则执行表达式2。true_expression if condition else false_expression
>>> 'yes' if 1 > 2 else 'no'
'no'
>>> 'yes' if 1 > 0 else 'no'
'yes'
while
写while要注意避免死循环,做法是在代码块中改变常量。条件为False循环停止,True就一直执行。
while condition:
pass
else:
pass
#当循环正常结束时(相当于没有break)将会执行else中的语句。
#Ctrl+c键盘终止死循环(keyboardInterrupt)
for
for主要用于遍历序列内的元素。
a = [1,2,3,4,5,6,7]
for x in a:
if x == 6:
continue
#跳过本次循环,不执行continue下面代码,回到外层循环继续运行。
print(x, end=' ')
1 2 3 4 5 7
for x in a:
if x == 5:
break #强制中断循环
if x == 1:
continue
print(x, end=' ')
2 3 4
这里嵌套循环break没有只输出apple,是因为for i in a
把第一个list先存进i再进行嵌套中for x in i
,if判断发现list中有orange就break,重新回到for i in a
,把元组放入i,进行for x in i
。所以第一次循环不是一次性把list和tuple放入i,而是一组一组来。
补充:break只是中断了嵌套循环,不影响外部循环。2019.5.31
a = [['appale', 'orange', 'bannana', 'grape'], (1, 2, 3)]
for i in a:
for x in i:
if x == 'orange':
break
print(x, end=' ')
else:
print('EOF')
appale 1 2 3 EOF
5 函数
将过程化的代码封装起来变成函数,在使用的过程中只需在括号中给定参数,经过函数体处理就能产生想要的结果,这大大提高了代码的复用性。
另一种方式就是使用面向对象来封装代码,一个程序可以采用模块化设计,主程序负责模块与模块之间的关系,子程序是模块,采用两种模式让代码变得更简洁。
紧耦合:两个部分之间交流很多,无法独立存在。
松耦合:两个部分之间交流较少,可以相互独立存在
模块与模块之间松耦合,这样代码可复用性高。模块内部要紧耦合,内政用变量来传递数据所以模块内的代码谁也离不开谁。
help()
查看内置函数说明import this
查看Python之禅
下面是函数的定义,其中def
关键字是用来定义函数的,function_name是函数名,括号中x称作形参(形式参数),pass是函数体,return
是用来返回处理结果的。
另外函数定义完成是不会运行的,需要手动调用,调用方法是函数名加上一个需要处理的参数,当然这个参数是我在定义函数时就写了的,所以必须要填。
def function_name(x):
pass
return x
funtion_name(1)
下面是函数错参数传递需要注意的点。正确和错误示例都有写。
def function_name1(parameter_list, parameter_list2):
pass #代码块
function_name1(parameter_list2="详细指定", parameter_list="参数位置")
#传参时可以明确指定参数方便阅读
function_name1(parameter_list2="详细指定", "参数位置")
#如果你先传parameter_list2参数值,第二个值不写,它会抛错,
#因为它不知道这个值是谁的,应该像上一条那样两个值都明确指定。
#另外定义函数设有几个参数,调用就要写几个参数,不然抛错。
def function_name2(parameter, parameter2="Default"):
pass
#可以给位置参数写个默认参数,在调用时不填参数2会带上默认值,填上就会覆盖默认值。
function_name2(1)
def function_name3(parameter, parameter2="Default", parameter3):
pass
#这样定义函数会抛错的,位置参数永远是在前,默认参数在后,不能混着。
def function_name4(parameter, parameter2, parameter3="Default"):
print(parameter, parameter2, parameter3)
function_name4(parameter2="新值2", parameter="新值1")
#这样传参称为关键字参数,对所有必须要传的参数指定了关键字。
#parameter2是关键字参数,它的值是:新值2,填写前后顺序并不重要。
function_name4(parameter3="新值3", 2, 1)
#调用不能先传默认值,应依照定义参数顺序传递,可填可不填的放在最后。
def function_name5(parameter, parameter2="Default"):
print(parameter, parameter2)
function_name5(parameter="值1", "值2")
#这样调用函数也是错的,官方语法规定:函数调用中,关键字参数必须跟随在位置参数的后面。
def function_name6():
print('不定义形参,去传递实参会报错')
function_name6('s', {'name': 'Bob'}, [1, 2, 3], None)
#不定义形参,去传递实参会报错。
可变参数
给函数传递多个参数,要注意的是参数一定是放在最后,位置参数-->默认参数-->任意参数(可变参数)。
另外一点是*
名称必须出现在**
之前。
>>> # names函数把接收过来的实参当做元组,定义方式*。
... def names(dec, *name):
... print(dec, name)
... print(type(dec), type(name))
...
>>> names("人物:", "富贵儿", "宋凡平", "宋刚", "李光头")
人物: ('富贵儿', '宋凡平', '宋刚', '李光头')
<class 'str'> <class 'tuple'>
>>> a = ("富贵儿", "宋凡平", "宋刚", "李光头")
>>> names("人物介绍:", *a) # 把可迭代对象解包后当做位置参数使用。
人物介绍: ('富贵儿', '宋凡平', '宋刚', '李光头')
<class 'str'> <class 'tuple'>
>>> names(*"富贵儿")
富 ('贵', '儿')
<class 'str'> <class 'tuple'>
>>> names("人物介绍:", *[1, 2, 3])
人物介绍: (1, 2, 3)
<class 'str'> <class 'tuple'>
>>>
>>> # 实参接收过来当做字典,定义方式**。
... def names(book, *dec, **name):
... print(book, dec, name)
...
>>> names('book', '名字', 人物='宋凡平', 人物2='宋刚')
book ('名字',) {'人物': '宋凡平', '人物2': '宋刚'}
>>> a = {'人物1':'富贵儿', '人物2':'宋凡平'}
>>> names('《活着》', '名字', **a) # 把字典对象解包当做关键字参数使用。
《活着》 ('名字',) {'人物1': '富贵儿', '人物2': '宋凡平'}
>>>
>>> # 可变参数不填就打印空值。
>>> def test(*args, **kw):
... print(args, kw)
...
>>> test()
() {}
>>>
参考文章:
函数返回值
函数中return执行过后函数会结束运行,没有return就返回None,返回多个值用逗号隔开,用逗号隔开类型是tuple,其实也可以返回其他类型比如list只不过逗号比较方便。
def fars(a, b):
return a,b
fars(b=1, a=2)
(2, 1)
a = fars(b=1, a=2)
print(a, type(a))
局部变量与全局变量
函数中内部变量(包含函数中定义的参数和内部定义的变量)和函数外定义的全局变量是不同的,函数执行完成局部变量会被垃圾回收。
函数内部可以访问全局变量,要在函数内部重新改写现有全局变量的值或是定义一个全新的全局变量需要加上global
关键字。
#使用模块中现有的全局变量
a = 1
def test():
global a
a += 4
return a
>>> print(a, test(), a)
1 5 5
#用global定义全局变量
def test():
global c
c = 1
test()
>>> print(c)
1
在嵌套函数中操作外层函数变量要用nonlocal
保留字,不加就会被认为是个局部变量。
#使用nonlocal
def test():
a = 10
print('test值:' + str(a))
def test_local():
nonlocal a
a += 1 # 当你不加nonlocal引用外层变量a,就会被认为是调用自己定义的局部变量a。
print('test_local值:' + str(a))
test_local()
>>> test()
test值:10
test_local值:11
函数中使用一个与全局变量中同名变量,这个全局变量的值是可变序列类型,那就等同于函数外全局变量。哪怕是传进来个变量也是一样,因为都指向同一个引用(引用传递)。如果传进来是个不可变类型(值传递),就会自动copy一个副本来引用,和原来的变量完全独立。
a = ['123abc', 'ABC']
def test():
del a [0]
return a
print(a)
print(test())
print(a)
['123abc', 'ABC']
['ABC']
['ABC']
a = {'云':'雨'}
def test():
a['云'] = 'raingray'
return a
print('全局变量:', a)
print('函数进行修改:', test())
print('全局变量:', a)
全局变量: {'云': '雨'}
函数进行修改: {'云': 'raingray'}
全局变量: {'云': 'raingray'}
a = {'云':'雨'}
def test():
new_a = a.copy() #重新复制一份,引用和原来互相不干扰。
new_a['云'] = 'raingray'
return new_a
print('全局变量:', a)
print('函数修改副本:', test())
print('全局变量:', a)
全局变量: {'云': '雨'}
函数修改副本: {'云': 'raingray'}
全局变量: {'云': '雨'}
另一个要提的是作用域链,是函数查找变量的过程,下面为相关示例。
打印3
c = 1
def func1():
c = 2
def func2():
c = 3
print(c)
func2()
func1()
此时打印2,因为相对于func2来说func1中变量c是全局变量
c = 1
def func1():
c = 2
def func2():
# c = 3
print(c)
func2()
func1()
此时打印模块中全局变量c的1,会一层一层往上找,这个称为作用域链。
c = 1
def func1():
# c = 2
def func2():
# c = 3
print(c)
func2()
func1()
递归
递归:函数自己调用自己的方式(过程是递递递递递然后归归归归归归)
基例:存在一个或多个不需要递归的实例,这样就不会死循环(因为在运算的过程中会在内存中不断复制一模一样的函数来占用内存空间)。
链条:计算过程中存在递归链条,也就是自己调用自己来运算。
递归示例
def fact(n):
if n == 0: # 基例
return 1
else:
return n*fact(n-1) # 链条
lambda表达式
lambda是个表达式不是代码块,表达式呢一般用于一些简单计算,像是赋值这种操作是不行的,一般这个表达式是三元运算符和一些列表推导式。这个lambda没有名字,为了引用会赋值给变量,它执行完成后会自己return表达式结果。
>>> lambda_exect = lambda 参数1, 参数2: 表达式
>>> lambda_exect()
>>>
lambda也叫匿名函数,可在Python中它算不上匿名函数啊...连赋值这种操作都不行,像是JavaScript这样的匿名函数能和普通函数功能一致,我觉得这才算真正的匿名函数。
var test = function(x) {
alert('test');
}
test();
函数工具
map(func, *iterables)
将函数作用到可迭代对象中的每一个元素,返回结果是一个可迭代对象(map object),可迭代对象意思是内容像序列,里面的元素可在for in
语句中使用。
>>> c = list(map(lambda s: s + ' ', 'gbb'))
>>> c
['g ', 'b ', 'b ']
函数接收多个参数,iterables数量最少的迭代完整个函数就结束。
>>> a = map(lambda x,y: x+y,[1,2,3] , [1,2])
>>> list(a)
[2, 4]
>>> a = map(lambda x,y: x+y,[1,2] , [1,2,4])
>>> list(a)
[2, 4]
filter(function, iterable)
用于筛选出符合条件的内容。将可迭代对象每个元素放入函数中,经过函数来判断这个元素是否满足条件,返回True就保留下来,False则丢弃。 返回的对象也是可迭代对象(filter object)
>>> b = filter(lambda s: 'g' in s, 'gbb')
>>> b
<filter object at 0x0000021D6E75AB48>
>>> str(b)
'<filter object at 0x0000021D6E75AB48>'
>>> list(b)
['g']
reduce(func, sequence[, initial])
第一个参数依旧是填函数,这个函数需要接收两个参数,用来将两个参数进行合并操作,这个合并不一定是下面的求和操作,也可以是其他运算。
如果一个序列有多个元素,第一步会将前两个元素合并变成一个,再将这个结果和第三个元素进行合并操作,以此类推。拿下面例子来讲,list中的1会放入参数x,2放入参数y,最终他们产生一个结果3,此时结果3变成x,list中第3个元素变成y,一直这样推导下去。
>>> import functools
>>> a = list(range(1,11))
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> def add1(x, y):
... return x + y
...
>>> functools.reduce(add1, a)
55
这个initial参数是第一步迭代时作为第一个参数来使用的,不用一次在序列中取两个元素。下面的例子由于指定了initial=10所以lambda第一个实参是x=10,那第一步运算是10+1,而不是像原来一样取列表第一个元素2。
>>> functools.reduce(lambda x,y: x+y, [1, 2, 3], 10)
16
>>>
迭代&推导
生成器:通过算法边计算边获取数据,不用一下子把所有数据全部放到内存中一次次获取。
迭代器:可通过next()获取数据的对象都是迭代器,所以生成器也是迭代器呀。
将可迭代对象(Iterable)放入自带的__iter__()
方法返回一个迭代器对象(在全局中也可以用iter
方法),这个迭代器对象本身有一个__next__()
方法用来获取内容,也可在全局中用next()
,它是指向__next__()
的。
- 可迭代对象通过自身
iter()
方法转换为迭代器对象 - 经由迭代器对象自身
next()
方法获取内容,内容获取完毕抛出StopIteration异常。
[x for x in iterables condition]
循环可迭代对象将它放入变量x,关键字for前面的x是你对这个变量的操作,condition只能做简单的判断,可以看作循环+操作最后放入中括号也就是列表,我们叫列表推导式。当你把外边的中括号换成{}
就变成了集合(别忘了set自带去重),换成()
就变成一个可迭代的generator对象。
文档字符串(Documentation Strings)
在模块、类、函数等添加,使程序易读易懂。
第一行是对函数功能进行描述,第二行必须是用来区分摘要和描述,从第三行往下是参数说明。
def print_num(x, y):
"""
摘要:打印出。
描述:这两个数都应该是整数
:param x: 传输数值
:param y: 传输数值
:return: 没有返回值
"""
print(x, y)
#help(print_num)
print(print_num.__doc__)
摘要:打印出x和y的值。
描述:这两个数都应该是整数
:param x: 传输数值
:param y: 传输数值
:return: 没有返回值
6 模块(module)
Python组织结构:包——>模块——>类——>函数/变量
命名空间(namespace)
不同包里都有相同名字的模块需要这样区分Apackget.module1,Bpacket.module1,在模块前加上包的名字,这样完整的路径称作命名空间。
比如gbb模块中有个blog函数,表现它的形式是gbb.blog
。
就算其他模块重名了也不会出现问题,具体是aaa模块下有个blog函数,只要写全称aaa.blog
就不会和gbb.blog
发生冲突。
在《Python核心编程-第二版》命令空间:是用来表示模块与具体对象的关系。
包:一个包中会有__init__.py这个文件,没有就是个普通目录,(<=3.3必须存在>3.3可不写)__init__.py这个模块名字就是包的名字。包这个概念可以理解为包含代码的目录。
导入模块
一些需要重复使用到的代码,可以提取到独立模块中供其他模块调用,这样就避免代码冗余,做法是import [包.][子包.]模块名
,导入后用法是,packet.module_login.function
比如user.login.checkpwd
,这就是引用user
包下login
模块中checkpwd
功能,这个功能有可能是函数或变量。
有时路径太长会很麻烦,比如packet.packet2.packet3.module_login.function
,可以用packet.module_login.function as new_name
,来取一个别名,使用时就可以用别名来调用功能。
#宁愿写的长一些,保守一点。
from 包 import 模块 #导入模块和前面import packet.module一样
from . import 模块 #.代表当前包
#不建议使用,因为可能和现在编写的模块命名冲突
from .模块 import 功能 #在现在包中模块下导入某个功能
from 包.模块 import * #一次性导入所有变量和函数(不建议这样做),不会导入__version__双下划线开头和结尾这样的内容。
from 模块 import 变量,函数 #导入对应的变量和函数
from 模块 import (变量,函数,
变量2) #导入多个变量可以用小括号包裹起来进行换行。
包中的__pycache__
目录是导入模块时缓存的字节码文件,下次导入模块加载速度会变快,在Python版本和代码改动时会重新编译。
包中__init__.py
文件所在包或包内模块被其他模块导入时,这个文件会自动执行里面的代码(一般用来初始化类)。既然导入会自动执行那其他模块中需要用的到模块直接在init中导入,在其他模块中导入这个包就可以用。
在文件中写入__all__ = ['模块名']
,别的模块在导入from 包 import *
所有模块,就只会导入你填写的模块。
模块要避免循环导入,你在模块1中导入模块2,然后再模块2又导入模块1,这样就会陷入死循环,因为执行模块1时导入模块2,模块2进而又导向模块1。
模块位置
sys.path
变量值类型是列表,里面儿存着查找模块路径。Python从中查找模块位置。当导入一个模块时,Python解释器会先找内置模块有没有相同名称的,找不到会从sys.path
变量中查找。
列表值sys.path[0]
第一个值时运行Python脚本的目录,如果这个目录不能用会显示为空字符串,另外Python查找sys.path
是从前到后找的。
- 当前模块所处目录(/root/PycharmProjects/LearnPython)
- 在系统变量或自定义的PYTHONPATH变量中设置的目录
- 标准库目录下查找(/usr/lib/python3.7)
- 以.pth后缀结尾文件的内容查找
- site-packages第三方扩展目录下(/usr/lib/python3/dist-packages)
import sys
print(sys.path) #为了方便显示,列表我手动换行过。
[
'/root/PycharmProjects/LearnPython',
'/root/PycharmProjects/LearnPython',
'/usr/lib/python37.zip',
'/usr/lib/python3.7',
'/usr/lib/python3.7/lib-dynload',
'/usr/local/lib/python3.7/dist-packages',
'/usr/lib/python3/dist-packages',
'/usr/lib/python3.7/dist-packages'
]
命令行参数
接受参数,并返回一个列表,以供使用。
import sys
print(sys.argv)
sys.argv
其中包含了被传递给 Python 脚本的命令行参数,也就是说module_using_sys.py
后面传递的才是参数,argv[0]
为脚本的名称(是否是完整的路径名取决于操作系统),参数与参数之间是以空格分割的。
root@gbb:~/PycharmProjects/LearnPython# python3 module_using_sys.py 1 2 3
['module_using_sys.py', '1', '2', '3']
root@gbb:~/PycharmProjects/LearnPython#
__name__
在导入模块时,模块的代码会被执行,所以导入过多内容程序会变慢,你模块内的各种代码被实例化提供给你调用需要时间嘛。
如果一个模块内部分代码不想被执行,可以检测导入模块时当前环境__name__
属性是不是等于'__main__'
,这个属性每个模块都有,用来获取一个模块名字。
if __name__ == '__main__':
print(f'当模块名为__main__时则表示是自己在运行,此时__name__的值是:{__name__}')
print(f'12321,此时模块名为:{__name__}')
自己执行结果
root@gbb:~/PycharmProjects/LearnPython# python module_using_name.py
当模块名为__main__时则表示是自己在运行,此时__name__的值是:__main__
12321,此时模块名为:__main__
其他人调用此模块执行的代码打印12321是因为模块被调用时发现__name__
变量值是字符串module_using_name
而不是'__main__'
所以没执行。
root@gbb:~/PycharmProjects/LearnPython# python3
>>> import module_using_name
12321,此时模块名为:module_using_name
>>>
dir()
内置dir()
函数能够返回当前或指定模块内定义的内容,其中包括函数内所定义的函数、类与变量,官方文档称名称列表。
如果向dir
传递的参数是模块名,将返回指定模块属性列表。不传参,函数将返回当前模块属性列表。
>>> dir()
['__annotations__', '__builtins__',
'__doc__', '__loader__',
'__name__', '__package__',
'__spec__']
>>>
>>> a = 1
>>> def test():
... pass
...
>>> dir()
['__annotations__', '__builtins__',
'__doc__', '__loader__',
'__name__', '__package__',
'__spec__', 'a', 'test']
7 面向对象(OOP)
类是一堆数据的一个概括,通过给类传递不同参数这个模板可以产生很多不同对象。
分析一个类需要的3样东西
- 特征(属性) - 这个对象有什么样的特征。
- 行为(方法) - 它能干什么。
- 关系 - 特征与行为之间的关系。
定义一个类
下面变量a也就是特征(或者叫属性),print_text方法是行为,要使用类需要实例化,就是通过实例化类来产生对象,在实例化过后会产生实例方法和实例变量。
class ClassName: #第一个字母大写,多个单词也是一样。
a = '1' #可以定义多个类变量,这个类变量是和类相关的变量。
b = '2'
def print_text(self):#可以定义函数,但在类中称作方法。
pass
define = ClassName() #实例化类
define.print_text() #调用ClassName中的方法
__init__
构造方法&实例方法
__init__
构造方法(也称魔术方法/构造函数)在类实例化时自动给具体创建的一个个对象赋上默认值。
在类中使用def
关键字定义的方法称作实例方法。
我们在每个方法括号中都加上了self
,它所代表的就是创建当前实例的对象,下面代码中我们对ClassName()
进行实例化赋给了变量define
,这个变量最终指向内存中一个空间,当你define.a
来调用实例变量a,其self
就指的是define
。
那在实例化后调用方法不需要传入一个参数吗?照理调用define.say_hi()
方法跟函数是一致的,不传入参数就报错,实例化后调用这个define.say_hi()
方法此时等同于define.say_hi(define)
,它会把自己传入进去,这样就能调用方法中实例变量了。
实例方法用来操作实例变量,如果要使用实例变量就要加上self
来定义,例如:self.a
。
class ClassName:
d = '类变量d' # 类变量d
b = '类变量b' # 类变量b
# 定义构造函数,每个实例方法第一个参数必须指定,这个值可以自定义,官方推荐使用self。
def __init__(self, a, b):
# 实例化会自动调用构造函数,也可在实例化后手动调用,但没必要。
self.a = a # 用self来存储实例变量的值
self.b = b # 用self来存储实例变量的值
# print(self.__class__.d) # 在实例方法中访问类变量,这个__class__代表当前实例的类。
# print(self.a) # 访问实例变量
return None # 构造函数只能返回None,执行完毕默认也是返回None,不能返回其他值,所以没必要写。
# 定义实例方法,上面的init也是做实例方法,只是调用时有区别。
def say_hi(self):
print('可以调用init方法初始化的值:', self.a, self.b) # 在实例方法中相互调用实例变量和实例方法称作内部调用。
define = ClassName("值1", "值2") # 构造函数定义几个参数就要传几个,不然抛错。
define.say_hi() # init实例化变量可以给所有实例方法使用
print(define.a) # 访问实例变量a,这种实例化后调用或者直接通过类调用,称作外部调用(从类的外面调用嘛)。
print(ClassName.b) # 直接访问类变量b
ClassName.c = '直接可以定义类变量' # 很灵活,但这样做代码不严谨。
print(ClassName.c)
define.c = '直接定义实例变量' # 没有实际作用。
print(define.c) # 调用自定义的实例变量
print(define.d) # 此时调用的是类变量,和变量查找顺序有关。
实例变量查找顺序
在类实例化后访问一个实例变量,这时它查找的顺序是实例变量 -> 类变量
,比如下面代码中test.age
首先会在实例变量中查找,找不到就向类变量中寻找age
这个同名变量,可用__dict__
来查看所有属性。
class Test:
name = ''
age = 0
def __init__(self, name):
self.name = name
test = Test('gbb')
print(test.age)
print(test.name)
print(test.__dict__)
0
gbb
{'name': 'gbb'}
前面说变量的查找顺序,但在实例变量中不是这样的,在构造函数中我们打印实例变量print(name, age, self)
会得到25而不是26,这是因为写的name
是仅仅引用传进来的参数,而不是实例变量。
class Test:
name = ''
age = 0
def __init__(self, name, age):
self.name = name
self.age = age
self.age = int(self.age)+1
print(f'传入的参数:{name} {age} {self}')
print(f'实例变量age的值:{self.age}')
psa = Test('gbb', '25')
gbb 25 <__main__.Test object at 0x0000022B44C4CCC8>
实例变量age:26
特殊方法
特殊方法(也叫魔术方法)在前面已经使用过,就是__init__
。
__repr__
和__str__
不知道干嘛用的...
>>> class Test:
... def __str__(self):
... return '这条信息一般给用户看,__str__未定义时会打印__repr__的值'
... def __repr__(self):
... return '在Python解释器中输入实例化对象会返回这个对象的一些信息,给开发人员看,方便调试'
...
>>> a = Test()
>>> a
在Python解释器中输入实例化对象会返回这个对象的一些信息,给开发人员看,方便调试
>>> print(a)
这条信息一般给用户看,__str__未定义时会打印__repr__的值
属性
class ClassName:
@property
def art(self):
print("调用可以不加括号当做属性来用,本质上还是方法,一般用于做些计算。")
@art.setter #如果想赋值,属性一定要定义在前然后再加上setter。
def art(self, value):
print("可以接收到别人赋值,这是你传的值:" + str(value))
a = ClassName()
a.art #调用属性
a.art = '传值试试' #会自动打印你赋的值
类方法
类方法用来操作类变量。创建的类方法和类变量可以被实例调用。
下面对类变量的操作应当放入类方法中。
class ClassName:
class_number = 0 # 保存实例个数
content = [] # 保存实例内容
def __init__(self, name, age):
self.name, self.age = name, age
# 每创建一个实例自增1
ClassName.class_number += 1
# 添加内容到类变量中
ClassName.content.append(self)
# 类方法和实例方法区别是加了@classmethod装饰器。
@classmethod
def view_obj_content(cls): # 官方建议用cls,也可用其他参数名。cls代表当前类,和实例方法中self一样将自己传进入。
print(cls.content) # 一般使用类方法来操作类变量,也可以在实例方法用self操作(不推荐)。
def __repr__(self):
return f'<{self.name}, {self.age}>' # 引用init初始化的实例变量
a = ClassName('Bob', 21)
print(f'创建了 {ClassName.class_number} 个对象')
print(f'这些对象的内容是:{ClassName.view_obj_content()}') # 调用类方法
print(f'它也可以被实例调用但不推荐:{a.view_obj_content()}') # 通过实例对象调用类方法
静态方法
静态方法使用场景是当类和对象关系不大时使用,不推荐经常使用,和普通函数没有差别。
还有静态和类方法不能调用实例变量,因为你传入self相当于调用方法形参。
class ClassName:
# 静态方法装饰器@staticmethod
@staticmethod
def static_str(): #静态方法不强制传第一个值
print("字符串")
ClassName.static_str() # 通过类调用静态方法
static_obj = ClassName()
static_obj.static_str() # 也可通过对象调静态方法,不过有点多余,它并没有跟实例相关。
依赖
一个类依赖于另一个类,通常其中一个类作为参数传入。相当于说我需要你!!!
class Student:
def __init__(self, name, age, job, gender):
self.name = name
self.age = age
self.job = job
self. gender = gender
def learn_course(self, course_name):
print(f'{self.name} 在学习 {course_name} 课程')
class Course:
def __init__(self, course_name):
self.course_name = course_name
def __repr__(self):
return f'{self.course_name}'
a = Student('gbb', 40, '安全运营', ['Web安全', 'Python', 'Shell', 'Linux'])
b = Course('Python基础')
a.learn_course(b)
# 将实例b传入实例a当作参数,b的数据由repr返回,当然也可以将实例变量b.course_name传入。
关联
两个类关联起来,将类当参数传递过去就可以用点的方式访问其所有内容。相当于说我们相互需要!!!
其实代码上和依赖区别不大都是传递类过去,但是在两个类逻辑关系上有所区别。
class Developer:
def __init__(self, name, department, skill):
self.name = name
self.department = department
self.skill = skill
def __repr__(self):
return f'<Developer :{self.name}>'
class Department:
def __init__(self, name, manager, tel):
self.name = name
self.manager =manager
self.tel = tel
def __repr__(self):
return f'<Department :{self.name}>'
class Project:
def __init__(self, name, team, start_date):
self.name = name
self.team = team
self.start_date = start_date
def __repr__(self):
return f'<Project :{self.name}>'
if __name__ == '__main__':
bumen = Department('安全部', 'Bob', '010-123456')
yuangong = Developer('gbb', bumen, ['Python', 'Security'])
print(yuangong.department.tel)
# 点的方式访问到另外一个类所有内容。
聚合
我拥有你,你存不存在都无所谓这是聚合。还有一个概念组合,我们俩少了谁都不是完整的个体。下面是聚合演示代码。
class Developer:
def __init__(self, name, department, skill):
self.name = name
self.department = department # 保存传递进来的参数
self.skill = skill
self.department.employees.append(self)
# 调用参数中的employees列表的append方法将自己实例化的数据通过__repr__方法追加进去,或者自己手动传递某个参数也行。
def __repr__(self):
return f'<Developer :{self.name}>'
class Department:
def __init__(self, name, team):
self.name = name
self.team = team
self.employees = [] # 定义个实例变量来保存数据
a = Department('安全运营', '蓝队') # 实例化要传递的数据
b = Developer('gbb', a, ['Linux', 'SQL', '渗透测试']) # 把实例对象a作为部门参数传递。
c = Developer('sbb', a, ['Linux', 'SQL', '渗透测试'])
print(a.employees)
成员可见性
主要防止从外部来更改数据
默认是public可以外部访问,双下划线开头就是private不能从外部直接访问。
class Student:
def __init__(self):
self.__score = 0
def marking(self, score):
self.__score = score
print(self.__score)
def __test(self):
print('私有不可以访问')
student = Student()
student.marking(60)
student.__score
# student.__test
60
Traceback (most recent call last):
File "asdf.py", line 14, in <module>
student.__score
AttributeError: 'Student' object has no attribute '__score'
Python是没有真正的private,上述代码可以用__dict__
查看对象的属性。可以看到__score
在实例化后名称被改做_Student__score
,实例变量前面加上下划线和类名。
class Student:
def __init__(self):
self.__score = 0
def marking(self, score):
self.__score = score
print(self.__score)
def __test(self):
print('私有不可以访问')
student = Student()
student.marking(60)
print(student.__dict__)
student._Student__test()
# student.__score
# student.__test
60
{'_Student__score': 60}
私有不可以访问
还有一点是要提到的,访问私有变量或方法会抛错,是因为到对象中去取一个不存在的属性,这在上面代码可以看到。那给它赋值再访问呢?
class Student:
def __init__(self):
self.__score = 0
student = Student()
student.__score = 60
print(student.__score)
print(student.__dict__)
60
{'_Student__score': 0, '__score': 60}
没错居然赋值成功了,这是由于Python动态性这个特性导致的,但是这个实例变量它和在实例方法中定义的可不一样,实例方法中定义的是private,我们在对象外面定义的是public,虽然名称一样,这个要区分开来。
继承&多态
把现有模块中的类继承过来,把要继承的类称作基类/父类/超类,而当前类称作子类/派生类,继承后子类就能使用父类中所有的方法和属性了,避免重复编写相同代码。
每个模块中建议只写一个类,这样结构清晰。
class Employee:
def __init__(self, name, department, title, salary):
self.name = name
self.department = department
self.title = title
self.salary = salary
def __repr__(self):
return f'<员工: {self.name}>'
def working(self):
print(f'{self.name} 在工作')
class Developer(Employee): # 在括号里填要继承的基类
# 要将基类所有信息填入
def __init__(self, name, department, title, salary, skills):
# 将Developer类实例化的参数传入基类实例变量存储,这样就不用反复存储,所有数据都从一个地方来。
Employee.__init__(self, name, department, title, salary)
# 只有一个类时也可用super()来代表父类去调用其中的内容,但在多继承情况下Developer(Employee, test),这样super()就不知道继承其中那个。
# super().__init__(name, department, title, salary)
# 用法:super(子类, self).数据成员
self.skills = skills
class Accountant(Employee):
def __init__(self, name, department, title, salary, skills):
Employee.__init__(self, name, department, title, salary)
self.skills = skills
a = Developer('gbb', '安全部', '安全运营', 5000, '打杂')
b = Developer('Gaa', '安全部', 'Test', 8000, '安全测试')
a.working()
print(a.salary)
print(b.name)
gbb 在工作
5000
Gaa
多态是指每个子类分别对一个行为表现出不同的内容。
下面代码中子类对父类的working重新表现就是一种多态,有些像Java中的重写。
class Employee:
def __init__(self, name, department, title, salary):
self.name = name
self.department = department
self.title = title
self.salary = salary
def __repr__(self):
return f'<员工: {self.name}>'
def working(self):
print(f'{self.name} 在工作')
class Developer(Employee): # 在括号里填要继承的基类
# 要将基类所有信息填入
def __init__(self, name, department, title, salary, skills):
# 将Developer类实例化的参数传入基类实例变量存储,这样就不用反复存储,所有数据都从一个地方来。
Employee.__init__(self, name, department, title, salary)
# 只有一个类时也可用super()来代表父类去调用其中的内容,但在多继承情况下Developer(Employee, test),这样super()就不知道继承其中那个。
# super().__init__(name, department, title, salary)
self.skills = skills
def working(self):
print(f'{self.name} 在敲代码')
class Accountant(Employee): # 在括号里填要继承的基类
def __init__(self, name, department, title, salary, skills):
Employee.__init__(self, name, department, title, salary)
self.skills = skills
def working(self):
print(f'{self.name} 在统计财务报表')
a = Developer('gbb', '安全部', '安全运营', 5000, '打杂')
b = Accountant('Saa', '财务部', '会计', 8000, '对现财务状况进行处理')
a.working()
b.working()
gbb 在敲代码
Saa 在统计财务报表
在实际写代码过程中可以将一个功能作为一个方法来处理,而不是几个功能揉在一个方法内,其次每个方法代码行数不建议超过20行,这样做的目的是让代码可读性变好。
8 异常处理
当程序出现错误时,会停止执行,为了维护用户体验给予友好提示还是有必要的。
捕获所有异常
try:
pass
except:
pass
# 当程序出现任意异常try会捕获,随后会执行except内的语句。也等同于下面写法
except Exception:
pass
捕获指定异常
try:
pass
except NameError:
pass
# 只有出现NameError这种类型的异常,才会执行执行处理语句。
except (KeyError, IndexError):
pass
# 出现字典键不存在和索引超出范围才执行错误处理语句,这是捕获多个错误的用法。
关于异常的更多分支用法
try:
pass
# 你要执行的代码。
except NameError:
pass
# 当出现异常执行此语句。
else:
pass
# 没有出现异常执行此语句。
finally:
# 不管有没有异常都会执行此语句。
# 一般在处理文件和网络资源关闭时使用。
手动触发异常raise
的值一定是Python中Exception子类。
raise NameError('异常内容')
Python还有个with
语句的用法,上面不是提finally
内语句用来关闭资源嘛,那这个with
就可以简化这一操作。
具体原理可以看官方文档1、官方文档2和完全理解Python关键字"with"与上下文管理器这3篇文章。
with open("myfile.txt") as f:
for line in f:
print(line, end="")
9 常用模块
日期时间处理
使用time和datetime标准库。time用来查看时间,datetime用来操作时间,比如,关于时间的计算呀。
datetime
import datetime
datetime.MAXYEAR # 最大年份
9999
datetime.MINYEAR # 最小年份
1
print(datetime.date.today()) # date类下面有个today方法获得今天日期
2019-12-02
# 调用date类下面的实例方法today获得年月日。
cc = datetime.date.today()
cc.year
2019
cc.month
12
cc.weekday() # 显示今天星期几,星期1=0
0
cc.isoweekday() # 按照标准显示周几,正常显示为周1。
1
# 定义年月日
cc = datetime.date(2019, 10, 15)
cc.year
2019
cc.month
10
cc.day
15
# 定义时分秒
h = datetime.time(18, 0, 1)
h.hour
18
h.minute
0
t.second
1
# 获得年、月、日、时、分、秒、微秒等数值,从datetime模块下datetime类下面now方法获取,同样可以通过属性获取对应的值
h = datetime.datetime.now()
datetime.datetime(2019, 12, 2, 18, 58, 47, 649351)
h.year
2019
h.month
12
h.day
2
h.hour
18
h.minute
58
t.second
47
h.microsecond
649351
# 你可以将datetime.date类实例化获得下面的属性,一起定义年、月、日、时、分、秒、微秒这几个值,最少要定义年、月、日这三个值
h = datetime.datetime(2019, 10, 15)
# 有问题可以用help(函数名)来查看函数用法。
字符串转换成时间
import datetime
# 使用strptime转换字符串,%Y代表四位数年份,%y2位年份,%m2位月份,%d2位日志,%H2位小时,%M2位分钟,%S2位秒钟,%f微秒,%w星期数(0-6)
s = '2019-12-2'
datetime.datetime.strptime(s, '%Y-%m-%d')
时间转换为字符串
import datetime
n = datetime.datetime.now()
n.strftime('%Y-%m-%d')
获得时间差
import datetime
d= datetime.datetime(2019, 10, 15, 22, 10)
n = datetime.datetime(2020, 10, 15, 10, 15)
n - d
datetime.timedelta(days=365, seconds=43500)
diff = n - d
diff.total_seconds() #获得两个时间总共差了多少秒
31579500.0
diff.days # 获得两个时间天数(2019-10-15到2020-10-15)间隔
365
diff.seconds # 获得两个时间(10:15到22:10)秒数的间隔
43500
x = datetime.timedelta(days=10) # 使用timedelta类创建一个天数,它的参数还有时、分、秒、毫秒、微秒等值可以传入。
d + x # 将d时间加上十天,想减10天可以将timedelta天数改为负值。
datetime.datetime(2019, 10, 25, 22, 10)
print(d + x)
2019-10-25 22:10:00
time
获取时间
time() # 获取时间戳,这个数字是从1970年1月1日0:00开始。
ctime() # 产生一个人类可读的时间,返回的是字符串。
gmtime() # 生成计算机可处理的时间(叫struct格式),其实是返回一个UTC时间啦,可以提供给其他程序作为参数处理,一般交给strftime()处理。
localtime() # 也是返回一个UTC时间不过会转换为本地时间。
格式化时间
strftime(tpl, [ts]) # tpl是模板参数(具体参数查文档),用来定义怎样输出时间,ts是可以放置一个gmtime()或是localtime()生成的时间。
模板参数文档
https://docs.python.org/zh-cn/3/library/time.html#time.strftime
strftime如果不传入ts参数默认会使用localtime()生成的时间
strptime(str, tpl) # str参数,tpl是能将一个str参数变成计算机内部时间。
程序计时
perf_counter() # 返回一个以当前CPU运行频率的时间(单位为秒),第一次调用数值并不是固定从0开始的,所以在程序运行时调一次,结束掉一次,做一个减法就能获得此程序运行时间啦。
sleep(s) # 让程序休眠,s参数单位是秒,可填写浮点数。
系统操作
使用sys和os标准库
sys
import sys
from pprint import pprint
pprint(dir(sys)) # 查看模块内容
sys.version # 查看系统平台
sys.version # 查看Python解释器版本
sys.modules # 查看已经导入的模块,返回一个字典,key是模块名,value是模块绝对路径
通过sys查看异常信息,traceback回溯对象打印详细信息。
import sys, traceback
try:
raise NameError
except:
print(sys.exc_info())
traceback.print_tb(sys.exc_info()[2])
(<class 'NameError'>, NameError(), <traceback object at 0x0000025717941848>)
File "<input>", line 2, in <module>
os
坏境变量
在windows里没有HOME这个key...坑死了。
>>> os.environ
environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\gbb\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'TEST', 'COMSPEC': 'C:\\WINDOWS\\system32\\cmd.exe', 'DRIVERDATA': 'C:\\Windows\\System32\\Drivers\\DriverData', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\gbb', 'IDEA_INITIAL_DIRECTORY': 'D:\\个人\\learn\\LearnPython3', 'IPYTHONENABLE': 'True', 'LOCALAPPDATA': 'C:\\Users\\gbb\\AppData\\Local', 'LOGONSERVER': '\\\\TEST', 'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT', 'PATH': 'E:\\xftp6\\;E:\\xshell\\;C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;C:\\ProgramData\\chocolatey\\bin;E:\\scoop\\shims;E:\\Python3\\Scripts\\;E:\\Python3\\;C:\\Users\\gbb\\AppData\\Local\\Microsoft\\WindowsApps;E:\\PyCharm 2019.2.1\\bin;C:\\Users\\gbb\\AppData\\Local\\Pandoc\\;D:\\nmap;E:\\Bandizip\\;E:\\cmder;D:\\sqlmap\\;', 'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', 'PROCESSOR_ARCHITECTURE': 'AMD64', 'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 60 Stepping 3, GenuineIntel', 'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3c03', 'PROGRAMDATA': 'C:\\ProgramData', 'PROGRAMFILES': 'C:\\Program Files', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', 'PROGRAMW6432': 'C:\\Program Files', 'PSMODULEPATH': 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM': 'E:\\PyCharm 2019.2.1\\bin;', 'PYCHARM_DISPLAY_PORT': '63342', 'PYCHARM_HOSTED': '1', 'PYCHARM_MATPLOTLIB_INDEX': '0', 'PYCHARM_MATPLOTLIB_INTERACTIVE': 'true', 'PYDEVD_LOAD_VALUES_ASYNC': 'True', 'PYTHONIOENCODING': 'UTF-8', 'PYTHONPATH': 'E:\\PyCharm 2019.2.1\\helpers\\pycharm_matplotlib_backend;E:\\PyCharm 2019.2.1\\helpers\\pycharm_display;E:\\PyCharm 2019.2.1\\helpers\\third_party\\thriftpy;E:\\PyCharm 2019.2.1\\helpers\\pydev', 'PYTHONUNBUFFERED': '1', 'SCOOP': 'E:/scoop', 'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\WINDOWS', 'TEMP': 'C:\\Users\\gbb\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\gbb\\AppData\\Local\\Temp', 'USERDOMAIN': 'TEST', 'USERDOMAIN_ROAMINGPROFILE': 'TEST', 'USERNAME': 'gbb', 'USERPROFILE': 'C:\\Users\\gbb', 'WINDIR': 'C:\\WINDOWS'})
常用操作
os.getcwd() # 获取当前目录
os.listdir([dir]) # 列出目录下的内容
os.chdir(dir) # 改变目录
os.mkdir() # 创建目录,如果上一级目录不存在就报错。
os.makedirs() # 递归的创建目录,上一级目录不存在会自动创建。
os.rmdir() # 删除目录
os.rename(raw_name, new_name) # 更改目录或文件名字
os.remove() # 删除文件
os.getpid() # 查看当前进程ID
os.getppid() # 查看父进程IP
os.system() # 用Python运行Shell命令
os.popen() # 执行系统命令,返回一个文件对象,要注意关闭文件对象,可以使用with语句简化语法。
os.sep # 分隔符
os.pathsep ## 路径分隔符
os.curdir # 表示当前目录的符号
os.pardir # 表示上一级目录的符号
os子模块path
os.path.isdir('.') # 判断是否为目录
True
os.path.isfile('.') # 判断是否为文件,如果在Linux下不知道
会不会为Ture
False
os.path.exists(r'd:\sql_select.sql.txt') # 查看目录或文件是否存在
True
os.path.getsize(r'd:\sql_select.sql.txt') # 获取内容大小(byte)
11612
os.path.split(r'd:\sql_select.sql.txt') # 分割目录和文件返回一个元组
('d:\\', 'sql_select.sql.txt')
name = r'd:\sql_select.sql.txt'
os.path.dirname(name) # 通过路径获取目录名称
'd:\\'
os.path.basename(name) # 通过路径获取文件名
'sql_select.sql.txt'
os.path.splitext(name) # 可以获取文件扩展名
('d:\\sql_select.sql', '.txt')
print(os.path.join('User', "Downloads", "file.txt")) # 将两个或多个路径连接起来,如果前后都是绝对路径只返回后面的。
User\Downloads\file.txt
os.path.join('d:/', 'c:\\')
'c:\\'
os.path.normcase('C:\\e/burpSuite/start.baT') # 将不规范路径转换为符合当前系统的格式,字母会小写,斜杠会变成反斜杠。
'c:\\e\\burpsuite\\start.bat'
os.path.abspath('.') # 获得绝对路径
'D:\\个人\\learn\\LearnPython3'
os.path.abspath('..')
'D:\\个人\\learn'
random标准库
random库是用来产生伪随机数的,因为是通过人为一些复杂算法来产生的,并不是完全随机(随机应该是不可控才对)。
根据数字种子 —>梅森旋转算法—>随机数(伪)
产生种子
random.seed(10) # 用来提供种子,不提供种子来调用random()它会采用系统默认时间作为种子,如果提供了一个种子那这个随机数值与值之间的关系,是可以重新复现的。比如有一个人想要知道这个程序的作者第2次调用随机数的值是多少,那么就可以根据作者提供的种子来调用2次就可以得知随机数的值,因为根据算法提供的值是可预测的。
random.random() # 0.0到1.0的小数
扩展函数
randint(a, b) # 产生a到b之间的随机整数
randrange(m, n[,k]) # 产生m到n以k为步长的随机整数
getrandbits(k) # 产生一个k比特长的随机整数
uniform(a, b) # 生成a到b之间的随机小数,取值精度是在小数点后16位
choice(seq) # 在序列中随机选一个元素
sample(seq, num) # 从序列中随机挑选x位
shuffle(seq) # 把序列随机排列然后返回一个新序列。
pyinstaller库
将源代码变成可以执行文件,能够在Linux、Windows、MacOS等平台运行
第三方库需要单独安装,使用pip安装
pip install pyinstaller
打包后在dist文件夹中会有同名转后的文件
pyinstaller -F file.py
options
-h
--clean # 删除临时缓存文件,主要是删除__cache__、build这两个文件夹。
-D,--onedir # 这是默认值,会将打包结果放到一个文件夹中,里面的文件都是相互依赖的。
-F,--onefile # 在dist目录生成一个独立程序
-i xx.ico # 添加ico图标
10 对象持久化
序列化,将内存数据输出成文件。比如将Python对象转换为JSON字符串的过程。
反序列化,将文件加载入内存。像JSON字符串你把它转换为Python中的一个对象(也就是数据结构)这个就叫反序列化。
import pickle
pickle.dumps(obj) # 将对象转换为字符串(序列化)
pickle.loads(s) # 将字节反序列化为原始对象(反序列化)
pickle.dump(obj, file) # 将对象序列化到文件,文件对象是2进制
pickle.load(file) # 反序列化为原始对象
shelve模块更高级,可将多个对象序列化到一个对象。
import shelve
str1 = 'test_str'
int1 = 9
obj_file = shelve.open('file_name') # 创建一个二进制文件
obj_file['int1'] = int1
obj_file['str1'] = str1
len(obj_file) # 查看文件内有几个对象
obj_file['int1'] # 查看对象内容
del obj_file['int1'] # 删除内容
obj_file.close() # 关闭文件操作
11 字符串与编码🔨
12 调试🔨
print() 简单粗暴的调试
断点
单元测试
assert
# 当assert后面的条件为False则抛出异常
unitls标准库
13 正则表达式(Regular Expression)
在一堆字符中使用规则找到你想要的信息,至于找到后要怎么做得看你的需求。
它的规则就是元字符(metacharacter),元字符只能代表一个字符。
匹配
. # 匹配所有内容除了\n
\d #表示数字,通常是一个,等同于[0-9]。
\D # 除了数字以外的都匹配,等同于[^0-9]。
\s # 只匹配空白字符空白字符,\t\n\r\f\v。
\S # 除了空白字符都匹配,等同于[^\t\n\r\f\v]。
\w # 匹配字母+数字+下划线,等同于[a-zA-z0-9_]。
\W # 匹配非字母+数字+下划线的内容,等同于[^a-zA-z0-9_]。
批量备选(分支)
| # 比如a|b,就是一个逻辑或的操作。
量词(重复)
# 量词是匹配前面一个字符的数量
+ # 至少出现1次任意次
? # 可以不出现,但最多只能出现1次
* # 可以不出现,也可出现任意次
{n} # 重复n次
{n, m} # 重复n到m次
{n,} # 必须出现n次,可以不限重复次数
{,m} # 最多重复m次,可以不限最小次数,也就是可以为0(在测试过程中这个表达式没有生效)
贪婪模式
-.*-
这条表达式会匹配x到y之间的所有内容,输入-awsl- -gkd-
会所有内容都匹配到,它是不撞南墙不回头的那种(超倔的吧),这就是贪婪模式。
但是只想要awsl和gkd这两段字符怎么办?可以在*
加上?
这样就在重复匹配时告诉它别一个劲往前找了,得过且过吧,匹配到就行。
总结:默认为贪婪模式,想要取消可以在量词后面加上?
,比如+?1
。
边界
^ # 代表开头,比如:^aab就会匹配以aab开头的内容。
$ # 代表结尾,比如:1sz$就会匹配以1sz结尾的内容。
# 它俩结合起来使用:^[\d]{4,8}$这段会匹配一段数字最少4位最多8位。
\b # 单词边界,边界很好理解就是用空格、逗号、句号隔开的内容,总结一句话是要匹配的内容前或后不能有字符或单词跟着,两个条件只要满足一个就能匹配上。
# 那\b就是匹配一个单词的开头或结尾,取决于你把\b放在要匹配的内容左边还是右边,比如\bStr就会在一串字符当中搜索Str,输入Strtest会匹配,因为Str前面没有字符挨着所以产生了一个边界,输入test/Strtest也会匹配到想要的str,这个斜线就区分了边界。
\B # 非单词边界,从一堆字符中任意位置搜索包含的字符。py\B,这个表达式表示不匹配以py结尾的的字符,将\B放在前面同理,不匹配py开头的字符。
\A # 输入开头,和脱字符(^)一致,这个是在键盘没有脱字符上使用的(Python2核心编程)。
\Z # 输入结尾,$一致
re 标准库基本使用
compile()
用于编译匹配模式,也就是匹配规则,一个规则要使用多次可以编译为字节码加快运行速度。编译过后支持search
、math
、fullmatch
、split
等一堆函数,具体信息查看文档。
如果你的匹配模式较为复杂,要使用r表示原始字符串避免转义错误,比如:r'\d\\Test'
。
compile(pattern, flags=0)
findall()
会从左往右在字符串中找出所有匹配的结果,去个重用列表返回。练习的时候可以配合正则图形化工具来理解。
re.findall(pattern, string, flags=0)
也可以用前面编译好的模式来调用findall()
方法。
test_pattern = compile(pattern)
test_pattern.findall(string)
finditer()
和findall一致,不过返回内容是迭代器(Iterator),也就是可迭代对象,这个可迭代对象内容是匹配对象。
search(string[, pos[, endpos])
从任意位置开始搜索字符串,匹配成功返回匹配对象,失败返回None
。其中的post和endpost参数是用来指定string一个范围,从这个范围来搜索。
要注意的是,search在匹配到结果后就停下来,不会像findall匹配所有符合规则的内容。
>>> import re
>>> pattern = re.compile('\d+')
>>> text = '2019年唉今天是12月10日'
>>> pattern.match(text)
<re.Match object; span=(0, 4), match='2019'>
>>> print(pattern.search(text))
<re.Match object; span=(1, 5), match='2019'>
match(string[, pos[, endpos]])
从开始(开头)位置搜索字符串,其他和search()
一致。
>>> import re
>>> pattern = re.compile('\d+')
>>> text = '今天是2019年12月10日'
>>> pattern.match(text)
>>> print(pattern.match(text))
None
>>> print(pattern.search(text))
<re.Match object; span=(3, 7), match='2019'>
匹配对象的一些方法,一般用来查看匹配对象的一些信息。
这里有一个()
是分组,可以在一堆字符中匹配完成后保存数据在之后去引用它。
奇怪为什么匹配的不是81和20?这是正则默认的贪婪模式捣鬼,匹配模式其中.*
重复匹配更多内容,因为是任意字符嘛所以当匹配到最后一个20的时候只会匹配到2这个数字就停止,后面的(\d+)
是说至少有一个数字存在,贪婪模式只会给你最基本的条件满足。
>>> import re
>>> p = re.compile(r'(\d+).*(\d+)')
>>> text = 'Tom is 81 year old. Jerry is 16 year old 18. 20'
>>> p.findall(text)
[('81', '0')]
正则对象匹配成功后返回一个匹配对象,它的方法有gourp()
、gourps
、start()
、end()
、span()
、groupdict()
方法...更多内容参考文档。
group([group], ...)
返回匹配到的分组内容,group这个参数填0
(或不填)就返回匹配到的内容,填数字1代表第一个子组,也可传入多个子组,这将以元组的形式返回。
# 以下示例依旧使用前面代码,只将匹配模式改变一下。
>>> p = re.compile(r'(\d+).*?(\d+)')
>>> p.search(text).group()
'81 year old. Jerry is 16'
>>> p.search(text).group(0)
'81 year old. Jerry is 16'
>>> p.search(text).group(1)
'81'
>>> p.search(text).group(2)
'16'
>>> p.search(text).group(0, 1, 2)
('81 year old. Jerry is 16', '81', '16')
groups()
类似于group传入多个子组,同样是以元组的方式返回多个子组。
>>> p.search(text).groups()
('81', '16')
start([group])
和end([group]))
分别用来显示匹配到的子组元素的索引位置,这个group默认为0
,就是匹配到的内容。
>>> text
'Tom is 81 year old. Jerry is 16 year old 18. 20'
>>> p.search(text).group()
'81 year old. Jerry is 16'
>>> p.search(text).start()
7
>>> p.search(text).start(0)
7
>>> p.search(text).start(1)
7
>>> text[7:9]
'81'
>>> p.search(text).start(2) # 第二个子组索引开始的位置
29
>>> text[29:31]
'16'
也可用span()
将start和end一起用元组的方式显示。
>>> p.search(text).span()
(7, 31)
分组也可以用量词等元字符
>>> import re
>>> p = re.compile(r'(\w{3})+?\s(\d{3})+')
>>> p.findall('abc 123 aaa 111')
[('abc', '123'), ('aaa', '111')]
>>>
>>> re.search(r'as(de|df)', 'asde')
<re.Match object; span=(0, 4), match='asde'>
>>> re.search(r'as(de|df)', 'asdf')
<re.Match object; span=(0, 4), match='asdf'>
>>> re.search(r'as(de|df)', 'asdd')
通过编号引用前面分组保存的数据,要注意引用前面分组的数据而不是匹配模式本身,官方文档称作反向引用。
>>> re.search(r'(\d{3})\sa\1', '123 a1234')
<re.Match object; span=(0, 8), match='123 a123'>
>>> re.search(r'(\d{3})\s\1', '123 223')
除了通过编号引用分组内容,还可以给分组命名来引用,命名也可在查看匹配对象上查看具体内容中使用,通过?P<name>
的方式命名。在查看结果可以用groupdict()
来获取,它返回一个字典,key是组名,value是结果。
>>> p1 = re.compile(r'(?P<char>\w{3})+ (?P=char)')
>>> p1.search('abb abb')
<re.Match object; span=(0, 7), match='abb abb'>
>>> p1.search('abb abb').groupdict()
{'char': 'abb'}
>>> p = re.compile(r'(?P<char>\w{3})+?\s(?P<num>\d{3})+')
>>> p.search('abc 123 aaa 111').group('num')
'123'
>>> p.search('abc 123 aaa 111').group('char')
'abc'
>>> p.search('abc 123 aaa 111').group(1)
'123'
>>> p.search('abc 123 aaa 111').group(2)
'abc'
split(pattern, string, maxsplit=0)
,这个方法用来分割字符串返回一个列表,maxsplit参数用来指定分割次数,这个方法也可编译匹配模式后使用。如果将匹配模式做了分组,在分割字符串时会把这个分组内匹配到的字符串也返回。
>>> import re
>>> text = 'a\nc\nd'
>>> re.split(r'\s', text)
['a', 'c', 'd']
>>> re.split(r'\s', text, 1)
['a', 'c\nd']
>>> p = re.compile(r'(\s)')
>>> p.split(text, 1)
['a', '\n', 'c\nd']
sub(pattern, repl, string, count=0, flags=0)
用来替换字符串subn(pattern, repl, string, count=0, flags=0)
他两个作用一样只不过sub返回字符串而subn返回元组,元组中包含替换后的字符串和替换次数。pattern参数是匹配模式,repl替换成什么,下面\g
这样引用分组的方式和前面一样也称作反向引用(也有人叫后向引用),count是替换次数,flags是用来控制元字符的称作修饰符。
>>> import re
>>> re.sub(r'(?P<content>\n+)', '-\g<content>-', '1\n2\n3')
'1-\n-2-\n-3'
>>> re.sub(r'(?P<content>\n+)', '-\g<1>-', '1\n2\n3')
'1-\n-2-\n-3'
>>> re.sub(r'(?P<content>\n+)', '-\g<1>-', '1\n2\n3', 1)
'1-\n-2\n3'
>>> re.sub(r'(?P<char>\w+)+(?P<num>\d+)+\s?', '\g<num>\n\g<char>', 'a1\nb2\nc3')
'1\na2\nb3\nc'
sub新用法
import re
def random_int():
pass
# 将一个内容传入函数,由函数返回新值。
# 函数可当作参数传递...
re.sub(r'pattern', random_int, 'str')
元字符特殊开关(修饰符)
一般用在带有flag
参数就可以使用,比如要匹配一个内容忽略大小写,要启用多个flag可以用|
来隔开。
具体详情可以参考文档:编译标志和模块内容两个文档。
>>> import re
>>> re.findall(r'gbb', 'gbb GBB', re.I)
['gbb', 'GBB']
>>> re.findall(r'.', '\n')
[]
re.findall(r'.', '\n', re.S)
['\n']
下面是一些常用的flag
re.I # 忽略大小写
re.S # 元字符.将会匹配任意字符(包括换行符\n)
...
最后一个是re.escape(pattern)
,它的作用是检测匹配模式并将它们转义为普通字符。
>>> print(re.escape(r'\b.+?\b'))
\\b\.\+\?\\b
参考文章:learn-regex、正则表达式30分钟入门教程
14 枚举(enum)
枚举用来表示类型,在其他编程语言里枚举单独作为一种数据类型存在,它的内容是不变的,和常量有些类似。
from enum import Enum
class TEST(Enum):
ERROR = 1 # 枚举名用大写表示,大写为常量。
# ERROR = 2 # 枚举名称不能相同,否作抛错。
WARING = 1 # WARING与ERROR值相同,则WARING为ERROR的别名
TIMEOUT = 2
class TEST2():
ERROR = 1
# 枚举注意事项
# TEST = TEST(1) # 枚举不可实例化
# TEST.ERROR = 2 # 枚举的值不可改变
# print(TEST.WARING, type(TEST.WARING)) # WARING取值与ERROR相同,则WARING为ERROR的别名,所以打印就会显示ERROR。
# 那为什么不显式的将WARING的名字改为ERROR_ALIAS避免混淆呢?
# 获取枚举内容
# print(TEST.ERROR, type(TEST.ERROR)) # 打印枚举本身这个类
# print(TEST['ERROR'], type(TEST['ERROR'])) # 打印枚举本身这个类
# print(TEST.ERROR.name, type(TEST.ERROR.name)) # 打印枚举名称
# print(TEST.ERROR.value, type(TEST.ERROR.value)) # 打印枚举值
# print(TEST(1), type(TEST(1))) # 可以传入值来获取对应枚举类
# for e in TEST:
# 枚举可迭代,默认不会将别名打印出来,如果需要可以TEST.__members__.items()。
# TEST.__members__会打印所有枚举类
# print(e, type(e))
# 枚举支持比较运算
# print(TEST.ERROR is TEST.TIMEOUT) # 这是比较两个类型是否相等,is操作符功能就是这样
# print(TEST.WARING == TEST.ERROR) # 这是比较两个类型是否相等(也支持!=),TEST.WARING这种访问方式的结果就是类。
给枚举做限制
from enum import IntEnum, unique
@unique # @unique设置不允许枚举类型值相同
class Error(IntEnum): # 继承IntEnum枚举类型的值只能是整数
# WARING = 'W' # 确实会报错
TEST = '1' # ???但这不也是字符串么?咋不报错...
ERROR = 2
# TIMEOUT = 2
print(Error.TEST.value)
15 闭包
见示例。
def test():
"""
Python一切皆对象。
type(type由自己产生实例) --> object(这是所有类的父类) --> 类 --> 对象
"""
a = 1
def test1():
print('test1函数', a) # 当函数引用了环境变量并返回这个函数就形成了闭包。
return test1 # 函数可以当作参数传递
a = 111
t = test() # 此时test1调用的是test内的环境变量它是按照作用域链找的这个变量,所以不受全局变量a的影响,这也是它的优势。
# 此时全局变量t指向test函数返回的内部函数test1。
print('test1引用的环境变量', t.__closure__[0].cell_contents)
t()
test1引用的环境变量 1
test1函数 1
7月小示例
def lvxy():
x = 0
def run(tup):
nonlocal x # 第二种实现方法是直接引用全局变量,但是这样很危险当变量重名时容易被误操作。
x += tup # 当操作环境变量是要使用nonlocal,不然认为你是再操作run里面儿定义的局部变量。
print(f'旅行者走了:{x}步')
return run
l = lvxy()
l(5)
l(5)
旅行者走了:5步
旅行者走了:10步
16 装饰器
当对一个现有功能扩展时,可以用装饰器附加在原有功能上而不用更改原功能代码。这个理念对应着AOP切面编程和开闭原则,其中开闭原则是对一个程序修改是关闭的,扩展是开放的。
给test函数添加一个运行计时功能,定义跟闭包完全一致,由于并没修改test原本代码,此时符合开闭原则,只是在调用时比较麻烦,下面使用语法糖(@)来简化操作。
from time import time
def decorator(func):
def wrapper(name):
start_time = time()
func(name)
print(time() - start_time)
return wrapper
def test(name):
print('测试装饰器', name)
a = decorator(test) # 将要执行的test函数传入到decorator中 --> 最终return wrapper,变量a指向函数wrapper。
a('name') # 相当于给wrapper传入参数'name'
使用语法糖后简化调用过程
from time import time
def decorator(func):
def wrapper(name):
print(time())
func(name)
return wrapper
@decorator
def test(name):
print('测试装饰器', name)
test()
语法糖调用原理我猜是这样的
from time import time
def decorator(func):
def wrapper(name):
print(time(), '这里调用的wrapper')
func(name)
return wrapper
# 将下面的test函数传入到decorator然后返回wrapper函数,但不知怎么给它取的别名,此时再调用这个别名等同于调用wrapper。
def test(name):
print('测试装饰器', name)
test('func')
print('原本test函数', test)
test = decorator(test)
print('wrapper函数', test)
当你写完装饰器后需要给其他函数进行使用,一般的函数都会接收一些参数,为了使装饰器更加通用可采用任意参数的方式来传递它。
其中test2函数没有填形参,但在wrapper内却将空的元组和字典对象后解包后传入,不会报错吗?这点让我存疑。经过实验将func(*args, **kw)
其中的参数替换为*()
*[]
*''
**{}
,在解包后传入没有报错,但你直接传递一个空的对象{}
[]
''
()
会报错,因为test2本身不接收位置参数,而你却传递了。
from time import time
def decorator(func):
def wrapper(*args, **kw):
print(time())
# 因为wrapper接收过来会转成元组和字典,要想将这种类型的数据作为实参传入到另一个函数中就得先解包为输入时的格式。
# 拿调用test1的实参解释,(1,)解包后变为1,而{'style'='jazz', 'style1'='funk'}会被解包为style='jazz', style1='funk'
func(*args, **kw)
return wrapper
@decorator
def test(name):
print('测试装饰器', name)
@decorator
def test1(num, **style):
print('接受多个参数', num, style)
@decorator
def test2():
print('一个参数都不用也行')
test('测试参数')
test1(1, style='jazz', style1='funk')
test2()
17 并发编程
多线程
当任务前后没有联系的情况下可以创建线程来并行处理任务,比如任务2需要任务1完成后的数据,此时任务2就无法单独使用多线程来加速。
线程之间和主进程数据是共享的。
Python的GIL,是CPython解释器特有(用C实现的解释器),意思是在解释器同一时间段只能运行一个线程。运行顺序是线程1运行一段字节码后退出来切换到让下一个进程执行,所以线程之间是来回切换的,不管你CPU有多少核在Python中也用不到,不过GIL在IO密集型这种操作上是不受影响的。
I/O密集型是指SOCKS、WetRequest这种操作。
计算(CPU)密集型是指一段程序中有大量时间被用在CPU计算方面。
thread1
import _thread
from time import sleep, ctime
def loop0():
print('start loop 0 at:', ctime())
sleep(4)
print('loop 0 done at:', ctime())
def loop1():
print('start loop 1 at:', ctime())
sleep(2)
print('loop 1 done at:', ctime())
def main():
print('starting at:', ctime())
_thread.start_new_thread(loop0, ())
_thread.start_new_thread(loop1, ())
# 加入了sleep()是因为要等待线程完全执行结束,不然直接执行下面print会导致main函数结束,
# 那线程的结果就看不到了,下一个程序使用clock(同步原语)同步,确保线程执行结束主进程才结束。
# 所以正常情况下应当是 主进程开始 --> 子进程开始运行 --> 子进程运行完毕 --> 子进程退出 --> 主进程退出
# 锁的概念:使用锁后会让线程执行后把后面的门关上,告诉主进程我们线程还没撤你不许先走。
sleep(4)
print('all DONE at:', ctime())
if __name__ == '__main__':
main()
thread2
import _thread
from time import sleep, ctime
loops = [4, 2]
def loop(nloop, nsec, lock):
print('start loop', nloop, 'at:', ctime())
sleep(nsec)
print('stop loop', nloop, 'done at:', ctime())
lock.release()
def main():
print('【starting at:', ctime())
locks = []
nloops = range(len(loops))
# 创建锁对象
for z in nloops:
lock = _thread.allocate_lock()
lock.acquire()
locks.append(lock)
# 创建线程开始任务
# 单独创建线程不放在上锁的循环里是因为,怕上完锁创建进程后,进程运行太快结束了,
# 那下面判断线程锁的状态的while语句可能直接认为所有锁已经释放,导致主线程结束,其实我们下一个线程还没来得及上锁。
# 另外一点是避免等待造成进程间隔时间过大,从而浪费时间。
for i in nloops:
_thread.start_new_thread(loop, (i, loops[i], locks[i]))
# 判断线程锁的状态,如果没释放就让主进程一直等待。
for x in nloops:
while locks[x].locked():
pass
print('【all DONE at:', ctime())
if __name__ == '__main__':
main()
threadingv1.0
使用threading.Thread更加自动化,不需要判断锁的状态了。
import threading
import datetime
from time import sleep
from time import ctime
def loop0():
print('tart loop 0 at:', ctime())
sleep(4)
print('loop 0 done at:', ctime())
def loop1():
print('start loop 1 at:', ctime())
sleep(2)
print('loop 1 done at:', ctime())
def main():
print('【starting at:', ctime())
thread = []
t1 = threading.Thread(target=loop1, args=())
thread.append(t1)
t2 = threading.Thread(target=loop0, args=())
thread.append(t2)
# t2.setDaemon(True)
# t1.setDaemon(True)
for t in thread:
t.start()
for x in thread:
# 让线程执行完毕主线程再结束,相当于锁机制啦,不过使用join()无需手动控制锁。
# 和前面一样使用同步的原因是当主线程提前结束后剩下未运行的子线程不会继续操作,这样子线程计算的数据就得不到了。
# 但是在测试过程中发现不写join主进程还会等待子线程运行完毕才退出,一脸懵逼,不是应该主进程结束时子线程也会被杀死吗?
# 想要主进程结束时杀死子线程可以将子线程设置为守护线程(daemon),在Thread里加daemon=True,或是开始线程前对线程对象设置setDaemon(True)
# 将线程设置为守护线程后会在主进程结束后会退出此线程,另外一个情况是守护线程全部结束后主进程。
x.join() # 这个join功能是阻塞用的,只有当子线程运行完成后才继续往下执行代码。
print('【all DONE at:', ctime())
if __name__ == '__main__':
main()
threadingv1.1
import threading
import time
def worker(n):
print(f'{threading.current_thread().name} 函数执行开始于: {time.ctime()}')
time.sleep(n)
print(f'{threading.current_thread().name} 函数执行结束于: {time.ctime()}')
class MyThread(threading.Thread):
"""
继承Thread类来实现多线程
"""
def __init__(self, func, args):
# 不用纠结为什么要调用父类的构造函数,看了下源码,不调用就会抛异常。
threading.Thread.__init__(self)
self.func = func
self.args = args
def run(self): # 重写Thread中的run方法,再start()调用时会运行run()
self.func(*self.args)
def main():
print(f'主线程开始于: {time.ctime()}')
thread = []
# 创建进程,但不立刻运行。
thread.append(MyThread(worker, (4,)))
thread.append(MyThread(worker, (2,)))
# 启动线程
for t in thread:
t.start()
# 等待所有线程运行结束,再结束主进程,相当于锁机制啦,不过使用join()无需手动控制锁。
for t in thread:
t.join()
print(f'主线程结束于: {time.ctime()}')
# if __name__ == '__name__':
main()
threadingv2.0
import time
import threading
import random
import pprint
"""
使用同步原语:锁
使用原因是每个线程产生的数据有一定关联,而不是乱糟糟的。
比如有三个线程,线程1运行后会关上门,也就是上锁告诉后面的进程等它完成工作,后面两个线程只有等到门被打开才能进去工作(这是释放锁)。
每次只有一个线程运行,这样效率肯定会低一些,但是进程前后产生的数据可以控制。
这个案例演示没有上锁的情况,
"""
eggs = []
def put_egg(n, lst, name):
for i in range(1, n+1):
lst.append('线程:' + str(name) + ',值:' + str(i))
time.sleep(random.randint(0, 2))
def main():
threads = []
start_time = time.time()
for i in range(3):
t = threading.Thread(target=put_egg, args=(5, eggs, i)) # 用循环创建3个线程
threads.append(t) # 将每个线程放入list
for t in threads:
t.start()
for t in threads:
t.join()
end_time = time.time()
print(f'线程运行时间为: {end_time - start_time}')
pprint.pprint(eggs)
main()
threadingv2.1
import time
import threading
import random
import pprint
"""
使用同步原语:锁
使用原因是每个线程产生的数据有一定关联,而不是乱糟糟的。
比如有三个线程,线程1运行后会关上门,也就是上锁告诉后面的进程等它完成工作,后面两个线程只有等到门被打开才能进去工作(这是释放锁)。
每次只有一个线程运行,这样效率肯定会低一些,但是进程前后产生的数据可以控制。
每次只有一个线程运行,这样效率肯定会低一些,但是进程前后产生的数据可以控制。
"""
eggs = []
lock = threading.Lock()
def put_egg(n, lst):
# lock.acquire() # 上锁
# for i in range(1, n+1):
# lst.append('线程:' + str(name) + ',值:' + str(i))
# time.sleep(random.randint(0, 2))
# lock.release() # 释放锁
# 也可以用with语句来简化写法,它会自动调用acquire和release
with lock:
for i in range(1, n + 1):
lst.append('线程:' + threading.current_thread().name + ',值:' + str(i))
time.sleep(random.randint(0, 2))
def main():
threads = []
start_time = time.time()
for i in range(3):
t = threading.Thread(target=put_egg, args=(5, eggs)) # 用循环创建3个线程
threads.append(t) # 将每个线程放入list
for t in threads:
t.start()
for t in threads:
t.join()
end_time = time.time()
print(f'线程运行时间为: {end_time - start_time}')
pprint.pprint(eggs)
main()
Queue
生产者与消费者问题
消息队列:产生数据 --> 消费数据
多进程🔨
18 网络编程🔨
最近更新:
发布时间: