文章目录
友情提示:本文针对的是非编程零基础的朋友,可以帮助我们快速了解Python语法,接着就可以快乐的投入到实战环节了。如果是零基础,还是老老实实看书最为稳妥。
前言
偶然在知乎上看到了一些好玩的Python项目(学 Python 都用来干嘛的?),让我对Python产生了些许兴趣。距离北漂实习还有两个月时间,正好可以在这段空闲时间里学一学。如果能做出些小工具,说不定对工作还有帮助,何乐而不为呢?
关于环境的安装和IDE就不多说了,网上有很多教程。这里贴出一篇博客,大家按里面的步骤安装就行:VSCode搭建Python开发环境。使用VSCode主要是因为免费,而且有大量插件可以下载,大家可以尽情的定制自己的IDE。如果曾经没有使用过VSCode,最好多了解下哪些必须的插件,优化自己的Coding体验。比如:Python插件推荐。
环境搭建好后,就可以愉快地敲代码了。VSCode需要自己创建Python文件,以.py为后缀。Ctrl+F5运行程序,F5调试程序。
Python基础
注释
单行注释:#
多行注释:’’’ (三个英文单引号开头,三个英文单引号结尾)
# 这是单行注释
'''
这是多行注释
'''
变量
Python的变量定义不需要显式指明数据类型,直接【变量名=值】即可。注意变量名分大小写,如Name和name不是同一个变量。
name = "小王"
print(name) # 输出 小王
数据类型
Python提供6种基础的数据类型:数字类型(number)、字符串类型(string)、列表(list)、元组(tuple)、字典(dictionary)、集合(set)。其中数字类型还包括三种数值类型:整型(int)、浮点型(float)、复数类型(complex)。
列表、元组那些我们留在容器那一节里面讲,先看看数字类型。
浮点型
浮点型表示小数,我们创建一个浮点型变量,再通过type函数看一看它的类型:
pi = 3.1415926
print(type(pi)) # 输出<class 'float'>
int整数型就不说了,其为Integer的缩写。
复数类型
复数类型,所谓复数就是我们中学学的,实数+虚数,比如:
x = 10+1.2j # 虚数以j或J结尾
print(type(x)) # 输出<class 'complex'>
刚开始接触复数时,很纳闷为啥会有这种类型,到底有啥实际作用,遂百度了一番:
mzy0324:微电子方面的运算基本全部都是复数运算。
hilevel:至少复数用来计算向量的旋转要比矩阵方便多了。科学计算和物理应该会用得到吧。PS:我经常把Python当带编程功能的计算器用,用来调试纯粹的数学算法挺方便的。
morris88:Python 的一大应用领域,主要是科学计算,主要用于太空宇航、银行等。
联想到Python平时在算法、科学研究等领域应用颇多,所以也就明白了,只是自己没使用的需求而已。
字符串
字符串类型的变量定义用一对双引号或者单引号括起来。如:
x = "Hello Python"
y = 'Hello Python'
print(x,y) # 输出Hello Python Hello Python
字符串内置函数:
函数 | 作用 |
---|---|
find(str[,start,end]) | 在字符串中查找子串str,可选参数start和end可以限定范围 |
count(str[,start,end]) | 在字符串中统计子串str的个数,可选参数start和end可以限定范围 |
replace(old,new[,count]) | 在字符串中用new子串替换old子串,可选参数count代表替换个数,默认全部替换 |
split(sep[,maxsplit]) | 用指定分隔符sep分割字符,返回一个列表,可选参数maxsplit代表分割几次,默认全部 |
upper()、lower() | 转换大小写 |
join(序列) | 把序列中的元素用指定字符隔开并生成一个字符串。 |
startwith(prefix[,start,end]) | 判断字符串中是否以prefix开头,返回bool类型。还有一个endwith,判断结尾的。 |
strip([,str]) | 去掉字符串开头和结尾的空白字符(包括\n、\t这些),可选参数代表可以去掉指定字符 |
布尔类型
顺便再说一下布尔类型,不过与Java不同的是,布尔类型的True和False,首字母必须大写:
x = True
print(type(x)) # 输出<class 'bool'>
类型转换
说完几个基本的数据类型,不免要提到类型转换。Python内置一些类型转换的函数:
函数名 | 作用 |
---|---|
int(x) | 将x转换为整型(小数转整型会去掉小数部分) |
float(x) | 将x转换为浮点型 |
str(x) | 将x转换为字符串 |
tuple(x) | 将x转换为元组 |
list(x) | 将x转换为列表 |
set(x) | 将x转换为集合,并去重 |
输入与输出
输入函数为input。input函数返回用户输入的信息为字符串类型。所以如果你输入的是数字类型,记得类型转换。
x = input("请输入数字")
print(type(x),x) # 输出<class 'str'> 10
输出前面已经演示了很多次了,函数为print,可以直接输出变量与值。一次输出多个变量可以用逗号隔开,就想上面的演示一样,既要输出类型,也要输出值。不换行输出,可以在print函数里加上end="“这个参数,因为print默认end=”\n",\n就是换行的意思。如果想输出特殊字符,可能需要用到转义字符:\。
x = 10
y = 20
print(x,y,end="") # 输出10 20 加上end="" 不换行
print("Hello \\n Python") # 输出 Hello \n Python
在输出时,还可以格式化输出内容:%s代表字符串格式、%d代表整型、%f代表浮点型
z = 1.2
print("%f"%z) # 输出 1.200000
除了格式化,%d等还可以当作占位符:
name = "小明"
age = 18
print("姓名:%s,年龄:%d"%(name,age)) # 姓名:小明,年龄:18
如果你闲这个占位符麻烦,还可以使用format函数,占位符只用写一对{}:
print("姓名:{},年龄:{}".format(name,age)) # 姓名:小明,年龄:18
运算符
算术运算符
除了加减乘除,还有幂(**)、取模(%)、取整(//)
x = 3 ** 2 # x=9 即3的2次方
y = 5 % 3 # y=2 即5除以3余2
z = 5 // 2 # z=2 即5除以2,整数部分为2
比较运算符
和其他常用编程语言基本一模一样,不等于(!=)、大于等于(>=)、等于(==)。
赋值运算符
Python也支持+=、*=等形式的赋值运算。除此之外,当然也支持前面说到的幂、取模等算术运算符,如取整并赋值(//=)、取模并赋值(%=)。
x = 10
x %= 3
print(x) # 输出1 ,x%=3 意为 x = x%3
逻辑运算符
非(not)、与(and)、或(or)
x = True
print(not x) # 输出 False
if、while、for
这三个和其他编程语言基本没差,就是写法上有点区别。首先没了大括号,条件语句后以冒号开头;代码快有严格的缩进要求,因为没了大括号,缩进就是条件语句判断自己代码快范围的依据。其他的基本一样,比如continue跳过当次循环,break跳出整个循环体。下面看三个简单的例子就明白了:
a = 10
# if或else后面是冒号,代码块还需要缩进
if a >= 10:
print("你好啊老大")
else:
print("滚蛋")
# 同样的while后面也需要冒号,代码块必须缩进。(Python没有num++,得写成num+=1)
# print想不换行打印,最后得加个end="",因为默认有一个end="\n"
# " "*(j-i),代表j-i个空格
i = 1
j = 4
while i <= j:
print(" "*(j-i), end="")
n = 1
while n <= 2*i-1:
print("*", end="")
n += 1
print("")
i += 1
# 语法:for 变量 in 序列 ,还没讲序列,暂时用range表示,代表1-21的序列
# continue略过当次循环,break跳出整个循环
for i in range(1, 21):
if i % 2 == 0:
if(i % 10 == 0):
continue
if(i >= 15):
break
print(i)
容器
列表
列表使用一对[]定义,每个元素用逗号隔开,元素类型不强求相同,通过索引获取列表元素。具体的我们看下面的代码:
info_list = ["小红", 18, "男"] #可以不是同一类型
info_list[2] = "女" # 修改指定索引位置的元素
del info_list[1] # 删除指定索引位置的元素
info_list.remove("女") # 删除列表中指定的值
for att in info_list: # 遍历元素
print(att)
上面的示例代码演示了部分列表的用法,下面再列出一些其他的常用函数或语法:
函数或语法 | 作用 |
---|---|
list.append(element) | 向列表list结尾添加元素(这个元素也可以是个列表) |
list.insert(index,element) | 向列表指定位置添加元素 |
list.extend(new_list) | 向列表list添加new_list的所有元素 |
list.pop([,index]) | 弹出最后一个元素,可选参数index,弹出指定位置元素 |
list.sort([,reverse=True]) | 对列表排序,可选参数reverse=True表示降序 |
list[start:end] | 对列表分片,start和end代表起始结束索引 |
list1+list2 | 拼接两个列表 |
元组
元组用一对()定义。元组也是有序的,它和列表的区别就是,列表可以修改元素,元组不行。正是因为这个特点,元组占用的内存也比列表小。
name_list=("小红","小王")
字典
字典使用一对{}定义,元素是键值对。用法示例如下:
user_info_dict = {"name": "小王", "age": "18", "gender": "男"}
name = user_info_dict["name"] # 直接用key获取value
age = user_info_dict.get("age") # 也可以用get(key)获取value
user_info_dict["tel"] = "13866663333" # 当key不存在,就是往字典添加键值对,如果存在就是修改value
del user_info_dict["tel"] # 删除指定键值对
以上就是常用语法和函数。字典也可以遍历,只是遍历时,需要指定遍历的是key还是value,比如:
for k in dict.keys(): # 遍历所有key
for v in dict.values(): # 遍历所有value
for item in dict.items(): # 也可以直接遍历键值对
集合
集合是无序的,也用一对{}定义,但不是键值对了,是单独且不重复的元素。部分用法如下:
user_id_set = {"1111","22222","3333"} # 元素不重复
print(type(user_id_set)) # 输出<class 'set'>
# 除了直接用{}定义,还可以用set函数传入一个序列,其会为list去重,并返回一个集合(如果是字符串,字符串会被拆成字符)
new_user_id_set = set(list)
上面演示了部分用法,下面我们用一个表格展示一些常用的函数或语法:
函数或语法 | 作用 |
---|---|
element in set | 判断元素是否在集合中,返回布尔类型 |
element not in set | 判断元素是否不在集合中 |
set.add(element) | 向集合添加元素 |
set.update(list,…) | 将序列中的每个元素去重并添加到集合中,如果有多个序列,用逗号隔开 |
set.remove(element) | 删除指定元素,如果元素不存在就会报错 |
set.discard(element) | 删除指定元素,如果元素不存在也不会报错 |
set.pop() | 随机删除集合中的元素,并返回被删除的元素 |
set1 & set2 或set1 intersection set2 | 求两个集合的交集,两种用法结果一样 |
set1 | set2 或set1 union set2 | 求两个集合的并集 |
set1 - set2 或set1.difference(set2) | 求两个集合的差集,注意顺序。set1-set2代表set1有set2没有的元素 |
函数
函数的定义
Python中函数用def定义,格式为:
def function_name(参数列表): # 参数可为空,多个参数用逗号隔开
函数体
return 返回值 #可选
# 函数的调用
function_name(参数列表)
缺省参数
和循环体一样的,因为没有了大括号,所以缩进是严格要求的。除了上面那种比较常见的格式,Python函数的参数中,还有一种缺省参数,即带有默认值的参数。调用带有缺省参数的函数时,可以不用传入缺省参数的值,如果传入了缺省参数的值,则会使用传入的值。
def num_add(x,y=10): # y为缺省函数,如果调用这个函数只传入了x的值,那么y默认为10
命名参数
一般情况下,调用函数传入实参时,都会遵循参数列表的顺序。而命名参数的意思就是,调用函数时,通过参数名传入实参,这样可以不用按照参数定义的顺序传入实参。
def num_add(x, y):
print("x:{},y:{}".format(x, y))
return x+y
# 输出:
# x:10,y:5
# 15
print(num_add(y=5, x=10))
不定长参数
不定长参数可以接收任意多个参数,Python中有两种方法接收:1.在参数前加一个*,传入的参数会放到元组里;2.在参数前加两个**,代表接收的是键值对形式的参数。
# 一个*
def eachNum(*args):
print(type(args))
for num in args:
print(num)
# 输出:
# <class 'tuple'>‘
# (1, 2, 3, 4, 5)
eachNum(1,2,3,4,5)
## 两个**。这个other是想告诉你,在使用不定长参数时,也可以搭配普通的参数
def user_info(other,**info):
print(type(info))
print("其他信息:{}".format(other))
for key in info.keys():
print("{} : {}".format(key,info[key]))
# 传入参数时,不用像定义字典一样,加个大括号再添加键值对,直接当命名参数传入即可
# 输出:
# <class 'dict'>
# 其他信息:管理员
# 略...
user_info("管理员",name="赵四",age=18,gender="男")
上面示例代码中的注释说到了,当使用不定长参数时,不用像字典或者元组的定义那样,直接传入参数即可。但有时候,可能会遇到想把字典、元组等容器中的元素传入到不定长参数的函数中,这个时候就需要用到拆包了。
所谓拆包,其实就是在传入参数时,在容器前面加上一个或两个*。还是以上面的user_info函数为例:
user_info_dict={"name":"赵四","age":18,"gender":"男"}
user_info("管理员",**user_info_dict) # 效果和上面一样
注意,如果接收方的不定长参数只用了一个 * 定义,那么传入实参时,也只能用一个 *。
匿名函数
匿名函数,即没有名字的函数。在定义匿名函数时,既不需要名称,也不需要def关键字。语法如下:
lambda 参数列表: 表达式
多个参数用逗号隔开,匿名函数会自动把表达式的结果return。在使用时,一般会用一个变量接收匿名函数,或者直接把匿名函数当参数传入。
sum = lambda x,y : x+y
print(sum(1,2)) # 输出3
闭包和装饰器
在Python中,函数内还可以定义函数,外面这个函数我们就称为外部函数,里面的函数我们就称为内部函数。而外部函数的返回值是内部函数的引用,这种表达方式就是闭包。内部函数可以调用外部函数的变量,我们看一个示例:
# 外部函数
def sum_closure(x):
# 内部函数
def sum_inner(y):
return x+y
return sum_inner # 返回内部函数
# 获取了内部函数
var1 = sum_closure(1)
print(var1) # 输出<function sum_closure.<locals>.sum_inner at 0x000001D82900E0D0>,是个函数类型
print(var1(2)) # 输出3
说完闭包的用法,接着了解一下装饰器。不知道大家了解过AOP没,即面向切面编程。说人话就是在目标函数前后加上一些公共函数,比如记录日志、权限判断等。Python中当然也提供了实现切面编程的方法,那就是装饰器。装饰器和闭包一起,可以很灵活的实现类似功能,下面看示例:
import datetime #如果没有这个包,在终端里输入pip3 install datetime
# 外部函数,其参数是目标函数
def log(func):
#内部函数,参数得和目标函数一致。也可以使用不定长参数,进一步提升程序灵活性
def do(x, y):
# 假装记录日志,执行切面函数。(第一次datetime是模块、第二个是类、now是方法。在下一节讲到模块)
print("时间:{}".format(datetime.datetime.now()))
print("记录日志")
# 执行目标函数
func(x, y)
return do
# @就是装饰器的语法糖,log外部函数
@ log
def something(x, y):
print(x+y)
# 调用目标函数
# 输出:
# 时间:2021-01-06 16:17:00.677198
# 记录日志
# 30
something(10, 20)
函数相关的就说到这里了,其实还有一些知识没说到,比如变量的作用域、返回值等。这部分内容和其他语言几乎无异,一点区别无非就是返回值不用在乎类型了,毕竟定义函数时也没指定函数返回值类型,这一点各位老司机应该也会想到。
包和模块
包
Python中包与普通文件夹的区别就是,包内要创建一个__init__.py文件,来标识它是一个包。这个文件可以是空白的,也可以定义一些初始化操作。当其他包下的模块调用本包下的模块时,会自动的执行__init__.py文件的内容。
模块
一个Python文件就是一个模块,不同包下的模块可以重名,在使用的时候以“包名.模块名”区别。导入其他模块用import关键字,前面的示例代码中也演示过一次。导入多个模块可以用逗号隔开,也可以直接分开写。除了导入整个模块,还可以导入模块中指定的函数或类:
from model_name import func_name(or class_name)
导入函数或类后,就不要使用模块名了,直接调用导入的类或函数即可。
面向对象
类和对象
Python是一种面向对象的解释型编程语言。面向对象的关键就在于类和对象。Python中类的定义用class关键字,如下:
class 类名:
def 方法名(self[,参数列表])
...
定义在类里面的函数叫做方法,只是与类外部的函数做个区分,不用在意叫法。类里面的方法,参数列表中会有一个默认的参数,表示当前对象,你可以当作Java中的this。因为一个类可以创建多个对象,有了self,Python就知道自己在操作哪个对象了。我们在调用这个方法时,不需要手动传入self。示例代码:
class Demo:
def do(self):
print(self)
# 创建两个Demmo类型的对象
demo1=Demo()
demo1.do() # 输出<__main__.Demo object at 0x0000019C78106FA0>
demo2=Demo()
demo2.do() # 输出<__main__.Demo object at 0x0000019C77FE8640>
print(type(demo1)) # <class '__main__.Demo'>
构造方法
构造方法的作用是在创建一个类的对象时,对对象进行初始化操作。Python中类的构造方法的名称是__init__(两边分别两个下划线)。在创建对象时,__init__方法自动执行。和普通方法一样的,如果你想自定义构造方法,也要接收self参数。示例代码:
class Demo:
# 构造方法,还可以传入其他参数化
def __init__(self,var1,var2):
# 把参数设置到当前对象上,即使类中没有属性也可以设置
self.var1=var1
self.var2=var2
print("初始化完成")
def do(self):
print("Working...")
# 通过构造方法传入实参
demo1=Demo(66,77)
demo1.do()
# 通过当前对象,获取刚刚设置的参数
print(demo1.var1)
print(demo1.var2)
访问权限
Java或C#中有好几种访问权限,在Python中,属性和方法前添加两个下划线即为私有,反之就是共公有。具有私有访问权限的属性和方法,只能在类的内部方法,外部无法访问。和其他语言一样,私有的目的是为了保证属性的准确性和安全性,示例代码如下:
class Demo:
# 为了方便理解,我们显示的设置一个私有属性
__num = 10
# 公有的操作方法,里面加上判断,保证数据的准确性
def do(self, temp):
if temp > 10:
self.__set(temp)
# 私有的设置方法,不让外部直接设置属性
def __set(self, temp):
self.__num = temp
# 公有的get方法
def get(self):
print(self.__num)
demo1 = Demo()
demo1.do(11)
demo1.get() # 输出 11
一堆self.刚开始看时还有点晕乎,把它当作this就好。
继承
继承是面向对象编程里另一大利器,好处之一就是代码重用。子类只能继承父类的公有属性和方法,Python的语法如下:
class SonClass(FatherClass):
当我们创建一个SonClass对象时,直接可以用该对象调用FatherClass的公有方法。Python还支持多继承,如果是多继承就在小括号里把父类用逗号隔开。
如果想在子类里面调用父类的方法,一般有两种方式:1.父类名.方法名(self[,参数列表])。此时的self是子类的self,且需要显示传入;2.super().方法名()。第二种方式因为没有指定父类,所以在多继承的情况下,如果调用了这些父类中同名的方法,Python实际会执行小括号里写在前面的父类中的方法。
如果子类定义了与父类同名的方法,子类的方法就会覆盖父类的方法,这就是重写。
异常处理
捕获异常
捕获异常的语法如下:
try:
代码快 # 可能发生异常的代码
except (异常类型,...) as err: # 多个异常类型用逗号隔开,如果只有一个异常类型可以不要小括号。err是取的别名
异常处理
finally:
代码快 # 无论如何都会执行
在try代码块中,错误代码之后的代码是不会执行的,但不会影响到try … except之外的代码。看个示例代码:
try:
open("123.txt") #打开不存在的文件,发生异常
print("hi") # 这行代码不会执行
except FileNotFoundError as err:
print("发生异常:{}".format(err)) # 异常处理
print("我是try except之外的代码") #正常执行
虽然上面的内容和其他语言相差不大,但是刚刚接触Python鬼知道有哪些异常类型,有没有类似Java的Exception异常类型呢?肯定是有的。Python同样提供了Exception异常类型来捕获全部异常。
那如果发生异常的代码没有用try except捕获呢?这种情况要么直接报错,程序停止运行。要么会被外部的try except捕获到,也就是说异常是可以传递的。比如func1发生异常没有捕获,func2调用了func1并用了try except,那么func1的异常会被传递到func2这里。是不是和Java的throws差不多?
抛出异常
Python中抛出异常的关键字是raise,其作用和Java的throw new差不多。示例代码如下:
def do(x):
if(x>3): # 如果大于3就抛出异常
raise Exception("不能大于3") # 抛出异常,如果你知道具体的异常最好,后面的小括号可以写上异常信息
else:
print(x)
try:
do(4)
except Exception as err:
print("发生异常:{}".format(err)) # 输出 发生异常:不能大于3
文件操作
读写文件
想要操作一个文件,首先得打开它。Python中有个内置的函数:open。使用open打开文件可以有三种模式,分别为:只读(默认的模式,只能读取文件内容,r表示)、只写(会覆盖原文本内容,w表示)、追加(新内容追加到末尾,a表示)。示例如下:
f = open("text.txt","a") # 用追加的方式获取文件对象
因为text.txt和代码在同一目录所以只写了文件名,如果不在同一目录需要写好相对路径或绝对路径。
获取到文件对象后,接下来就可以操作了,反正就是些API,直接看示例:
f = open("text.txt","a",encoding="utf-8") # 以追加的方式打开文件,并设置编码方式,因为接下来要写入中文
f.write("234567\n") # 写入数据,最后的\n是换行符,实现换行
f.writelines(["张三\n","赵四\n","王五\n"]) # write只能写一个字符串,writelines可以写入一列表的字符串
f.close() # 操作完记得关闭
以上是写文件的两个方法。最后记得关闭文件,因为操作系统会把写入的内容缓存起来,万一系统崩溃,写入的数据就会丢失。虽然程序执行完文件会自动关闭,但是实际项目中,肯定不止这点代码。Python也很贴心,防止我们忘了close,提供了一种安全打开文件的方式,语法是 with open() as 别名:,示例如下
with open("test.txt","w") as f: # 安全打开文件,不需要close。
f.write("123")
写完了,该读一读了。示例如下:
f = open("text.txt","r",encoding="utf-8")
data = f.read() # read会一次性读出所有内容
print(data)
f.close()
除了一次性读取完,还可以按行的方式返回全部内容,并用一个列表装起来,这样我们就可以进行遍历了。方法是readlines,示例如下:
f = open("text.txt","r",encoding="utf-8")
lines = f.readlines() # lines是个列表
for line in lines:
print(line)
f.close()
文件管理
在操作文件的时候,肯定不止读写这么简单,可能还会涉及文件的删除、重命名、创建等等。在用Python的函数操作文件之前,需要导入os模式:import os
。下面简单的演示一下重命名的函数,其他的函数我们以表格的形式展现。
import os
os.rename("text.txt","123.txt") # 把text.txt改名为123.txt
函数 | 作用 |
---|---|
os.remove(path) | 删除指定文件 |
os.mkdir(path) | 在指定路径下创建新文件 |
os.getcwd() | 获取程序运行的绝对路径 |
os.listdir(path) | 获取指定路径下的文件列表,包含文件和文件夹 |
os.redir(path) | 删除指定路径下的空文件夹(如果不是空文件夹就会报错) |
操作JSON
学了前面的容器,会发现JSON的格式和Python的字典有点像,都是键值对形式的。虽然格式很像,但还是有点小区别,比如:Python的元组和列表在JSON中都是列表、Python的True和Flase会被转换成小写、空类型None会被转换成null。下面我们来看一些具体的函数把。
在Python中操作JSON格式的数据需要导入json模块。同样的,我这里只演示一个函数,其他常用的用表格列出来。
import json
user_info={"name":"张三","age":18,"gender":"男","hobby":("唱歌","跳舞","打篮球"),"other":None} # 创建一个字典
json_str=json.dumps(user_info,ensure_ascii=False) # dumps函数会把字典转换为json字符串
# 输出 {"name": "张三", "age": 18, "gender": "男", "hobby": ["唱歌", "跳舞", "打篮球"], "other": null}
print(json_str)
需要注意如果数据存在中文,需要在dumps函数加上ensure_ascii=False
。
函数 | 作用 |
---|---|
json.loads(json_str) | 把json字符串转换为Python数据结构 |
json.dump(user_info,file) | 把Python数据写入到json文件,要先获取文件,那个file就是文件对象 |
json.load(file) | 把json文件中的数据转为成Python数据结构,同样需要获取文件 |
关于JSON的操作就说这些。通用的数据格式不止JSON一种,比如还有xml、csv等。为了节约篇幅,就不再赘述了,大家可以根据自己的需求查对应的API即可。
正则表达式
最后一节讲正则表达式,一是因为这也算个基础知识,在很多地方都有可能用到。二是因为后面的爬虫实战,肯定会用到正则表达式来解析各种数据。
Python中内置了re模块来处理正常表达式,有了这个模块我们就可以很方便的对字符串进行各种规则匹配检查。不过正则表达式真正难的是表达式的书写,函数主要就一个:re.match(pattern,string)
,其中pattren就是正则表达式,stirng就是待匹配字符串。如果匹配成功就会返回一个Match对象,否则就返回None。匹配是从左往右,如果不匹配就直接返回None,不会接着匹配下去。示例如下:
import re
res=re.match("asd","asdabcqwe") # 匹配字符串中是否有asd(如果asd不在开头就会返回None)
print(res) # 输出 <re.Match object; span=(0, 3), match='asd'>
print(res.group()) # 输出 asd 如果想获取匹配的子字符就用这个函数
秉着帮人帮到底的精神,下面就简单的介绍下正则表达式的一些规则。
单字符匹配
单字符匹配,顾名思义就是匹配一个字符。除了直接使用某个具体的字符,还可以使用以下符号来进行匹配:
符号 | 作用 |
---|---|
. | 匹配除”\n“以外的任意单个字符 |
\d | 匹配0-9之间的一个数字,等价于[0-9] |
\D | 匹配一个非数字字符,等价于[^0-9] |
\s | 匹配任意空白字符,如空格、\t、\n等 |
\S | 匹配任意非空白字符 |
\w | 匹配单词字符,包括字母、数字、下划线 |
\W | 匹配非单词字符 |
[] | 匹配[]中列举的字符,比如[abc],只要出现这三个字母中的一个即可匹配 |
以防有的朋友从未接触过正则表达式,不知道怎么用,下面我来做个简答的演示。假如我想匹配三个字符:第一个是数字、第二个是空格、第三个是字母,一起来看看怎么写这个正则表达式吧:
import re
pattern = "\d\s\w" # \d匹配数字、\s匹配空格、\w匹配字母(切记是从左往右依次匹配的,只要有一个字符匹配不上就直接返回None)
string = "2 z你好"
res=re.match(pattern,string)
print(res.group()) # 输出:2 z
看到这你可能会想,非得一个个字符匹配,那多麻烦啊,有没有更灵活的规则?当然有了,接着看。
数量表示
如果我们只想匹配字母,但不限制有多少个,该怎么写呢?看下面的表格就知道了:
符号 | 作用 |
---|---|
* | 匹配一个字符出现0次或多次 |
+ | 匹配一个字符至少出现一次,等价于{,1} |
? | 匹配一个字符出现0次或1次,等价于{1,2} |
{m} | 匹配一个字符出现m次 |
{m,} | 匹配一个字符至少出现m次 |
{m,n} | 匹配一个字符出现m到n次 |
数量匹配的符号后面如果加上?
,就会尽可能少的去匹配字符,在Python里面叫非贪婪模式,反之默认的就是贪婪模式。比如{m,}
会尽可能多的去匹配字符,而{m,}?
在满足至少有m个的情况下尽可能少的去匹配字符。其他的同理。
来看一个例子,我想匹配开头是任意个小写字母,接着是1到5个2-6的数字,最后是至少一个空格:
import re
pat = r"[a-z]*[2-6]{1,5}\s+"
str = "abc423 你好"
res=re.match(pat,str)
print(res) #输出 abc423
我们来解析下这个正则表达式,pat字符串开头的r是告诉Python这是个正则表达式,不要转义里面的\,建议写表达式时都加上。[a-z]
代表任意小写字母,不用\w的原因是,\w还包括数字、下划线,没有严格符合我们的要求。加上个*就代表任意数量。这里强调一下单字符匹配和数量表示之间的逻辑关系,以[a-z]*
为例,其表达的是任意个[a-z]
,而不是某个字母有任意个。明白了这个逻辑后,其他的也好理解了。
前面的例子都是我随意编的,其实学了这些,已经可以写出一个有实际作用的表达式了,比如我们来匹配一个手机号。首先手机号只有11位,第一个数字必须是1,第二个是3、5、7、8中的一个。知道了这三个个规律,我们来写一下表达式:1[3578]\d{9}
。看上去好像可以,但是仔细一想,前面不是说了正则表达式是从左往右匹配,只要符合了就会返回结果,也不会管字符串匹配完全没有。如果最后有10个数字,这个表达式也会匹配成功。关于这个问题我们接着看。
边界表示
边界表示符有两个:开头^
和结尾$
。使用起来也很简单,还是以上面的手机号为例,我们再来完善一下:^1[3578]\d{9}$
。其中^1
表示以1开头,\d{9}$
表示以9个数字结尾。其实这个^1
可有可无,毕竟是从左往右的,字符串不是1开头的话直接就会返回None,但是这个结尾符是必须的。
转义字符
假如我们想匹配的字符与正则表达式规定的这些字符一样该怎么办?比如我们想单纯的匹配.
这个字符,但是这个字符在正则表达式中表示的是任意字符。这时候就要用到转义字符\
了。其实这个转义字符在很多语言里都是一样的。那么前面的例子就可以写出\.
。我们再演示个匹配邮箱的例子:
import re
pat = r"^\w{4,10}@qq\.com" # 如果.前面不加\,就代表任意字符了
str = "1234@qq.com"
res=re.match(pat,str)
print(res)
匹配分组
看到上面的匹配邮箱例子,是不是有个疑问,如果我想不止匹配QQ邮箱该怎么办呢。那就要用到分组了,其可以实现匹配多种情况。分组符号如下:
符号 | 作用 |
---|---|
() | 将括号里的内容当作一个分组,每个分组会有一个编号,从1开始 |
| | 连接多个表达式,表达式之间是“或”的关系,可与()一起使用 |
\num | 引用分组,num代表分组编号 |
(?P…) | 给分组取别名,别名写在表达式前面,name不用打引号 |
(?P=name) | 根据别名使用分组中的正则表达式 |
那么我们把上面的例子稍微修改下:^\w{4,10}@(qq|163|outlook|gmail)\.com
。这样就可以匹配多种邮箱了。
简单的演示了下|
的用法,大家可能对其他的分组符号还有点疑惑,下面我们再来演示一下这些符号:
import re
pat = r"<(.+)><(.+)>.*<(/\2)><(/\1)>"
str = "<body><div></div></body>"
res=re.match(pat,str)
print(res)
这个表达式匹配的是由两个标签组成的html字符串。第一眼看上去有点麻烦,实际很简单。再次强调一下,普通字符也可以当表达式来匹配的,比如上面的< >
就是普通字符而已。
我们来分析一下这个表达式,首先一对小括号表示一个分组,里面的.+
表示只有一个非\n字符。中间的.*
用来匹配标签内的内容。/\2
中,第一个斜杠与前面的html标签组成一对,/2表示引用第二个分组的内容。这里为什么要使用分组呢?因为我们还要保证html标签正确匹配。如果后面也使用.+
,大家可以试着把/div
和/body
交换位置,表达式依旧匹配成功,但这显然不符合html的语法。
操作函数
正则表达式的一些规则符号终于讲完了,最后再列举几个Python中操作正则表达式的函数:(re为导入的模块)
函数 | 作用 |
---|---|
re.compile(patt) | 封装正则表达式,并返回一个表达式对象 |
re.search(patt,str) | 从左往右搜索第一个配正则表达式匹配的子字符串 |
re.findall(patt,str) | 在字符串中查找正则表达式匹配到的所有子字符串,并返回一个列表 |
re.finditer(patt,str) | 在字符串中查找正则表达式匹配到的所有子字符串,并返回一个Iterator对象 |
re.sub(patt,newstr,str) | 将字符串中被正则表达式匹配到的子字符串替换成newstr,并返回新的字符串,原字符串不变 |
Python的第一篇文章就到这里了。接下来会边学边写,做一些好玩的Python项目,再一起分享出来。如有错误,感谢指出!
参考资料:《Python 3快速入门与实战》