Python语言基础篇
一、基础语法
(一)、注释
1、单行注释
# 这个就是单行注释,“#”后必须根个空格,只是规范不是强制要求。
2、多行注释
'''
-这就是多行注释
'''
"""
-这也是多行注释
"""
3、TODO注解
# TODO 主要是标明待做任务,注明任务人和任务时间等,一般也作为功能步骤说明。
(二)、代码结束标识
1、缩进
Python 最具特色的就是使用缩进来表示代码块,不像其他语言比如c#那样通过大括号==“{}”==来标识代码块,通过大括号的内嵌匹配来分层级;而python是通过缩进方式,相同的缩进表示相同层级,缩进越深表示越里层。
2、逻辑换行
行分为物理行和逻辑行。
物理行:程序员编写代码的行。
逻辑行:python解释器需要执行的指令。
逻辑换行是指一行代码过长不易解读需要换行,但又能让解释器认为他是一行代码,这就是需要引入==“\”==符号来换行,具体看如下代码(如下两行代码执行出来是一样的):
conf = (SparkConf().setMaster("local[*]")\
.setAppName("test_spark_app"))
conf = (SparkConf().setMaster("local[*]").setAppName("test_spark_app"))
(三)、变量和数据类型
1、变量
1.1 变量的内存存储方式
python变量的内存存储方式有别其他语言,具体区别如下表说明:
事务步骤 | c#语言 | Python语言 |
---|---|---|
变量声明(定义) | int a=0;这时定义的变量a,系统会分配一个内存空间(假设地址1100111)指定给变量a,并在内存中赋值0 | a=0,b=100 系统把0存于内存一个空间(假如地址为1200011),并把地址12000011指向变量a,同理100的存于内存(1200012)并把地址指向b |
改变变量值 | a=100;这时系统会根据变量a的地址(1100111)找到内存的空间,把值改为100。 | a=100,系统发现内存中有100的值无需再存而100值的地址是1200012,就把地址12000012指向变量a |
变量的内存地址变化 | a的内存地址始终为1100111 | a=0时的内存地址是1200011 a=100时的内存地址是1200012 b和a指向的内存地址是一样的 |
注意:数据容器(序列)的内存的存储方式和变量有所区别,容器会被分配个地址,元素的值的地址存于容器的地址空间中,所以一个五个元素的容器将被分配了六个地址,具体看如下例子。
>>> a=1 # 变量的初始赋值
>>> id(a)
4551937760
>>> a=2 # 变量改变赋值,地址发生了改变
>>> id(a)
4551937792
>>> list=[1,2,3] # 初始化一个列表
>>> id(list) # 列表被分配了一个地址4557093440
4557093440
>>> for var in list: # 遍历列表内元素的各个地址
... print(id(var))
...
4551937760 # 1的地址
4551937792 # 2的地址
4551937824 # 3的地址
>>> list[0]=9 # 把第一个元素的值1改为9
>>> for var in list:
... print(id(var))
...
4551938016 # 第一个元素的值改变了,地址也改变了
4551937792
4551937824
>>> id(list) # 列表的地址不变
4557093440
1.2 python变量的特点
- 因为变量的存储方式决定了python的变量是没有数据类型,数据(变量值)才有数据类型,所以变量在声明的时候是无需加上数据类型,也可以理解Python其实首次的赋值就是对变量的声明。
- 变量对应的数据类型是动态的,由于变量是没有数据类型变量a首次给与赋值0,后面还可以给与赋值“hello”字符串(这时a变量的指定的地址也发生了改变)。
- 变量是区分大小写的,并不能以数字开头,以及不能取关键字。
1.3 变量的初始赋值(声明)
a=100
b=c=100
d=1,e=2
str="hello world"
以上这几种变量的定义都可以。
1.4 删除变量
del a # 这就删除了变量,后面就不能被引用了。
后面不在被使用的变量可以直接删除,使用del关键字进行删除即可。
2、数据类型
在Python中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。
Python 3 中有六个标准的数据类型:
- Numbers(数字)
- String(字符串)
- List(列表)
- Tuple(元组)
- Sets(集合)
- Dictionaries(字典)
后面四个属于容器,它是python种经常用到的,在后面容器的章节记录各个容器的使用方法。
2.1 数据类型的转换
- int(x):表示把x转成int类型
- float(x):把x转成float类型
- str(x):把x转成字符串类型
- Bool(int):布尔类型ture=1,false=0,所以布尔类型的转换只能是整型。
- List()、Tuple、Sets、Dictionaries都是采取这种方式进行转换。
注意点:整型和浮点型计算的时候,计算过程中会把整型数据转换成浮点型进行计算,但不改变变量中数据的类型
2.2 关键函数的使用
type():返回数据或变量内数据的数据类型,type(123),type(a) a为变量。
isinstance():校验数据是否是否个类型,是返回True,否则False。
用法:isinstance(123,int)校验123是不是int类型。
isinstance和type的区别在于:
type()不会认为子类是一种父类类型。
isinstance()会认为子类是一种父类类型。
(四)、运算符
Python中提供了各种各样的运算符帮助我们解决各种实际问题。Python中的运算符主要包括算术运算符、比较运算符、位运算符、逻辑运算符和赋值运算符、成员运算符。下面将一一介绍这些运算符的具体种类和使用方法。
1、算术运算符
以下假设变量 a 为 21,变量 b 为 10:
算符 | 描述 | 实例 |
---|---|---|
+ | 加 - 两个对象相加 | a + b 输出结果 31 |
- | 减 - 得到负数或是一个数减去另一个数 | a - b 输出结果 11 |
* | 乘 - 两个数相乘或是返回一个被重复若干次的字符串 | a * b 输出结果 210 |
/ | 除 - x 除以 y | a / b 输出结果 2.1 |
% | 取模 - 返回除法的余数 | a % b 输出结果 1 |
** | 幂 - 返回 x 的 y 次幂 | a ** b 为 21 的 10 次方 |
// | 取整除 - 返回商的整数部分 | 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 |
2、比较运算符
以下假设变量 a 为 10,变量 b 为 20:
运算符 | 描述 | 实例 |
---|---|---|
== | 等于 – 比较对象是否相等 | (a == b) 返回 False。 |
!= | 不等于 – 比较两个对象是否不相等 | (a != b) 返回 True. |
> | 大于 – 返回 x 是否大于 y | (a > b) 返回 False。 |
< | 小于 – 返回 x 是否小于 y。 | (a < b) 返回 True。 |
>= | 大于等于 – 返回 x 是否大于等于 y。 | (a >= b) 返回 False。 |
<= | 小于等于 – 返回 x 是否小于等于 y。 | (a <= b) 返回 True。 |
所有比较运算符返回 1 表示真,返回 0 表示假。这分别与特殊的变量 True 和 False 等价。注意:True和False的首字母为大写。
3、赋值运算符
以下假设变量 a 为 10,变量 b 为 20:
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符 | c = a + b 将 a + b 的运算结果赋值为 c |
+= | 加法赋值运算符 | c += a 等效于 c = c + a |
-= | 减法赋值运算符 | c -= a 等效于 c = c - a |
*= | 乘法赋值运算符 | c *= a 等效于 c = c * a |
/= | 除法赋值运算符 | c /= a 等效于 c = c / a |
%= | 取模赋值运算符 | c %= a 等效于 c = c % a |
**= | 幂赋值运算符 | c **= a 等效于 c = c ** a |
//= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
4、位运算符
按位运算符是把数字看作二进制来进行计算的。Python 中的按位运算法则如下:
下表中变量 a 为 60,b 为 13。
按位与运算(a&b) | 按位或运算(a|b) | 按位异或(a^b) | |
---|---|---|---|
a(60)的二进制表示 | 0011 1100 | 0011 1100 | 0011 1100 |
b(13)的二进制表示 | 0000 1101 | 0000 1101 | 0000 1101 |
运算结果 | 0000 1100 | 0011 1101 | 0011 0001 |
结果的十进制表示 | 12 | 61 | 49 |
按位取反(~a) | 左移(a<<2) | 右移(a>>2) | |
---|---|---|---|
a(60)的二进制表示 | 0011 1100 | 0011 1100 | 0011 1100 |
运算结果 | 1100 0011 | 1111 0000 | 0000 1111 |
运算结果的十进制表示 | -61 | 240 | 15 |
注:关于原码,补码和反码:
原码:假设机器字长为n,原码就是用一个n位的二进制数,其中最高位为符号位:正数是0,负数是1。剩下的表示概数的绝对值,位数如果不够就用0补全。
反码:在原码的基础上,符号位不变其他位取反,也就是就是0变1,1变0。
补码:在反码的基础上加1。
PS:正数的原、反、补码都一样,0的原码跟反码都有两个,因为这里0被分为+0和-0。
按位取反和反码有一定的相似之处但又不尽相同(反码符号位不取反)。
在计算机中,是以补码的形式存放数据的。1100 0011刚好对应-61。
-61的原码-> 1011 1101->反码->1100 0010->补码->1100 0011
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与运算符:参与运算的两个值,如果两个相应位都为 1,则该位的结果为 1,否则为 0 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | 按位或运算符:只要对应的二个二进位有一个为 1 时,结果位就为 1。 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
^ | 按位异或运算符:当两对应的二进位相异(不同)时,结果为 1 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
~ | 按位取反运算符:对数据的每个二进制位取反,即把 1 变为 0,把 0 变为 1 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011 |
<< | 左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补 0。 | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
>> | 右移动运算符:把">>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
5、逻辑运算符
Python 语言支持逻辑运算符,以下假设变量 a 为 10, b 为 20:
运算符 | 逻辑表达式 | 描述 | 实例 |
---|---|---|---|
and | x and y | 布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。 | (a and b) 返回 20。 |
or | x or y | 布尔"或" - 如果 x 是 True,它返回 x的值,否则它返回 y 的计算值。 | (a or b) 返回 10。 |
not | not x | 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 | not(a and b) 返回 False |
6、成员运算符
除了以上的一些运算符之外,Python 还支持成员运算符,测试实例中包含了一系列的成员,包括字符串,列表或元组。
运算符 | 描述 | 实例 |
---|---|---|
in | 如果在指定的序列中找到值返回 True,否则返回 False。 | x 在 y 序列中 , 如果 x 在 y 序列中返回 True。 |
not in | 如果在指定的序列中没有找到值返回 True,否则返回 False。 | x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。 |
(五)、python标识符
标识符是用来标识代码的变量名、函数名、类名、等的标识,为了和系统的关键字不起冲突及代码的可读性,所以进行了一系列的规范。
- 标识符只允许出现:英文、数字、下划线(_)
- 数字不能作为标识符的开始
- Python对英文标识符是大小写敏感的。
- 系统的关键字不能作为标识符使用
- 变量的命名规范做到见名知意思
- 对于多个英文单词建议使用下划线隔开
(六)、数据容器(又称为序列)
数据容器又称为序列,它是python里最常用的数据结构类型;序列是用来存放多个值的连续内存空间。Python序列类似于其他语言中的数组,但功能要强大很多,其提供的序列类型可以说是所有编程语言的类似数据类型中最灵活的,功能最强大的。
1、列表(List)
1.1 列表定义
列表是Python中内置有序、可变序列,它以一个方括号“[]”内包含多个其他数据项(字符串,数字等甚至是另一个列表),数据项间以逗号作为分隔的数据类型。
1.2 列表语法
# 正常列表定义初始化
list1 = ['Google', 'W3Cschool', 1997, 2000]
# 嵌套列表
list2=[1,2,['a','b','c']]
# 以下定义空列表的两种方法
mylist=[]
mylist=list()
1.3 列表下标
下标是列表的索引从0开始标记,可以通过下标找到列表的元素,具体使用说明如下:
-
例子:list1[2],方括号内的2就是下标值,指的是正向方向的第三个元素,输出第三个元素的值。
-
所谓正向方向就是从写入的第一个元素算起的就是正向,反之就是反向方向;正向的第一个下标值是0,下标值以此递增,反向的第一个下标值是-1,下标逐渐递减。
-
正向的第一个即是反向的最后一个,正向的最后一个即是反向的第一个,例子:列表list[1,2,3,4,5],具体说明如下:
- 列表包含5个元素。
- 从正向方向第一个元素(下标为0)是1,到最后一个元素(下标为4)是5,看例子从正向看下标值是递增的。
- 从方向方向第一个元素(下标为-1)是1,到最后一个元素(下标为-5)是5,看例子从反向看下标值是递减的。
- 从二三两条得出,正向的第一个即是反向的最后一个,正向的最后一个即是反向的第一个。
-
嵌套列表的下标,例如两层嵌套,“list1[1][2]”1表示最外层的下标,2表里里层的下标以此类推,所以这个列表表示第二个元素列表里的第三个元素,可以看出从外到内第二元素是列表。
1.4 列表常用的操作函数
函数/方法 | 使用说明 |
---|---|
int=len(list) | 返回列表的元素个数,返回值是int类型 |
var=max(list) | 返回列表元素最大值 |
var=min(list) | 返回列表元素最小值 |
list=list(seq) | seq是元组,集合,将序列(元组,集合等)转换为列表 |
list.index(“元素值”) | 返回指定元素值的正向下标值,如果值不存程序报错抛出异常。 |
list[下标]=“值” | 通过指定下标的列表进行赋值,可以对对应下标的元素进行修改元素值。 |
list.insert(下标,元素) | 在指定下标处插入一个元素,原来下标以下的下标值都加1。 |
list.append(元素) | 在列表中追加一个元素。 |
list.extand(list2) | 在list列表中追加一个列表list2的元素,注意:这个是把列表list2的元素追加进行,而不是把列表追加进去。 |
del list[0] | 删除list列表的第一个元素,列表元素个数减少一个,所有下标-1 |
a=list.pop(0) | 和del list[0]一样的功能,唯一的区别是它在删除前会把元素的值先复制出来给a,让你知道删除的元素是什么。 |
list.remove(元素值) | 删除从正向方向的指定元素值的第一个元素,如果列表中多个元素的值是一样了,它只删除第一个。 |
list.clear() | 清空列表。 |
list.count(元素值) | 找出列表中指定元素值的个数,返回整型 |
list.reverse() | 正反向调个个,返回列表对象 |
list.sort() | 对列表按元素大小进行排序,返回列表对象 |
list.copy() | 对列表进行赋值,返回列表对象 |
列表对 + 和 * 的操作符与字符串相似。+ 号用于组合列表,* 号用于重复列表。
Python 表达式 | 结果 | 描述 |
---|---|---|
len([1, 2, 3]) | 3 | 长度 |
[1, 2, 3] + [4, 5, 6] | [1, 2, 3, 4, 5, 6] | 组合(两个列表元素组合) |
[‘Hi!’] * 4 | [‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’] | 重复 |
3 in [1, 2, 3] | True | 元素是否存在于列表中 |
for x in [1, 2, 3]: print(x, end=" ") | 1 2 3 | 遍历 |
2、元组(tuple)
2.1 元组定义和语法
Python 的元组(tuple,简写为tup)与列表类似,不同之处在于元组的元素不能修改;元组使用小括号()
,列表使用方括号[]
,可以看做是只读列表。
# 元组定义
my_tuple=(1,“sd”,2)
tup3 = "a", "b", "c", "d" # 没有括号也可以
#定义空元组(以下两行写法)
my_tuple=()
my_tuple=tuple()
2.2 元组下标
元组支持下标,用法和列表一样。
元组和列表一样支持嵌套,可以是互相嵌套。
2.3 元组常用操作函数
例子参照列表
元组的常用函数除了不能新增、插入、修改、删除元素外(可以清空元组)其他和列表的常用函数一样,用法一样。
2.4 元组和列表的差异
元组使用
()
,而列表使用[]
二者都是有序的,但元组不可以追加、插入、删除、需改元素,而列表可以。
元组的元素嵌套着列表,元组的内层的列表是可以被修改的,因为元组的元素并没有被破坏,列表的元素发生了改变,对于元组来说它还是列表。
元组和列表一样也可以使用运算进行操作,用法一样。
3、集合(set)
3.1 定义和语法
集合(set)是一个无序的不重复元素序列,所以元素值是不能出现重复,每次输出的值的顺序是不固定了每次都有可能不一样。
集合的定义使用大括号{}
:
# 定义个集合
s={1,2,"hello",3,[1,2,3]}
myset=set("hello") # 这个输出得到是{'e', 'l', 'o', 'h'}
# 定义个空集合
s=set()# 只有这一种方法,不能使用空大括号定义空集合
集合的无序不重复的特点:
- 集合是不支持下标索引的。
- 集合会自动去重,所以在定义的时候,元素如果重复集合会自动去重,重复的元素自动给与过滤掉。
- 集合是无序的,每次输出的结果顺序都是不一样的,执行效果如下代码框。
无序的输出结果:
>>> thisset = set(("Google", "W3Cschool", "Taobao"))
>>> print(thisset)
{'W3Cschool', 'Google', 'Taobao'}
>>> print(thisset)
{'Google', 'Taobao', 'W3Cschool'}
3.2 常用函数
方法 | 描述 |
---|---|
set.add(元素) | 为集合添加元素 |
set.clear() | 移除集合中的所有元素 |
set1=set2.copy() | 拷贝一个集合给另一个集合 |
set3=set1.difference(set2) | 取出集合1不在集合2的内容返回给集合3,不改变集合1和2的元素。 |
set1.difference_update(set2) | 抹去集合1中在集合2出现的元素,集合1元素被改变。 |
set.discard(value) | 删除集合中指定的元素(元素不存在时不抛异常) |
z = x.intersection(y) | 返回xy集合的交集给z集合 |
x.intersection_update(y) | 移除x集合中不在y集合中的元素,x集合元素被改变。 |
z = x.isdisjoint(y) | 判断集合 y 中是否有包含 集合 x 的元素,如果没有返回 True,否则返回 False。 |
z = x.issubset(y) | 判断集合 x 的所有元素是否都包含在集合 y 中,如果都包含返回 True,否则返回 False。 |
z = x.issuperset(y) | 判断集合 y 的所有元素是否都包含在集合 x 中,如果都包含返回 True,否则返回 False |
set.pop() | 随机移除一个元素 |
set.remove(item) | 移除指定元素,元素不存在会抛出异常。 |
z = x.symmetric_difference(y) | 返回两个集合组成的新集合,但会移除两个集合的重复元素 |
x.symmetric_difference_update(y) | 在原始集合 x 中移除与 y 集合中的重复元素,并将不重复的元素插入到集合 x 中 |
z = x.union(y) | 合并两个集合xy给z,重复元素只会出现一次 |
x.update(y) | 合并两个集合xy给x,重复元素只会出现一次: |
a=len(set) | 统计集合的元素个数 |
3.3 集合运算符计算
a-b(a集合中b没有的元素) | b | d | r | |||||
---|---|---|---|---|---|---|---|---|
集合a | b | d | r | a | c | |||
a|b(并集) | b | d | r | a | c | l | z | m |
集合b | a | c | l | z | m | |||
a&b(交集) | a | c | ||||||
a^b(不同时包含于a和b的元素) | b | d | r | l | z | m |
- a-b(a集合中b没有的元素)相当于a.defference(b)
- a|b(并集)合并两个集合 a.union(b)
- a&b(交集)取两个集合相同的部分
- a^b(不同时包含于a和b的元素),取两个集合的元素,但是去掉两个集合都出现的元素。
4、字符串(string,简写str)
4.1 定义和语法
字符串可以看做是一个特殊元组,字符串的每个字符可以看做元组的元素,每个字符串都不允许增删改操作。
字符串定义可以使用""
和''
:
# 定义一个字符串
str="hello"
str1='hello'
# 定义个空字符
str3=""
str4=''
4.2 转义字符
有一些字符因为在python中已经被定义为一些操作(比如单引号和双引号被用来引用字符串),而这些符号我们可能在字符串中需要使用到。为了能够使用这些特殊字符,可以用反斜杠 \ 转义字符(同样地,反斜杠也可以用来转义反斜杠)。如下表:
转义字符 | 描述 | 实例 |
---|---|---|
\(在行尾时) | 续行符 | >>> print(“line1 \ … line2 \ … line3”) 输出:line1 line2 line3 |
\\ | 反斜杠符号 | >>> print("\\") 输出:\ |
\’ | 单引号 | >>> print('\'') 输出:' |
\" | 双引号 | >>> print("\"") 输出:" |
\a | 响铃 | >>> print("\a") 执行后电脑有响声。 |
\b | 退格(Backspace) | >>> print("Hello \b World!") 输出:Hello World! |
\000 | 空 | >>> print("\000") >>> |
\n | 换行,zai | >>> print("hello\nworld") >>> hello world |
\v | 纵向制表符 | >>> print("Hello \v World!") Hello World! >>> |
\t | 横向制表符 | >>> print("Hello \t World!") Hello World! >>> |
\r | 回车,将 \r 后面的内容移到字符串开头,并逐一替换开头部分的字符,直至将 \r 后面的内容完全替换完成。 | >>> print("Hello\rWorld!") World! >>> print('google w3cschool taobao\r123456') 123456 w3cschool taobao |
\f | 换页 | >>> print("Hello \f World!") Hello World! >>> |
\yyy | 八进制数,y 代表 0~7 的字符,例如:\012 代表换行。 | >>> print("\110\145\154\154\157\40\127\157\162\154\144\41") Hello World! |
\xyy | 十六进制数,以 \x 开头,y 代表的字符,例如:\x0a 代表换行 | >>> print("\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21") Hello World! |
\other | 其它的字符以普通格式输出 |
4.3 字符串格式化
Python 支持格式化字符串的输出 。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符==%s==的字符串中。
在 Python 中,字符串的格式化使用与 C/C++中的printf函数有着一样的语法。如下例子:
name = "小明"
age = 10
print ("我叫 %s 今年 %d 岁!" % (name, age)) #这就是格式化输出1
print(f"我叫 {name} 今年 {age} 岁!") #另外一种比较便捷的格式化
输出:
我叫 小明 今年 10 岁!
我叫 小明 今年 10 岁!
格式的两种方式:
使用%关键字引入变量,==%==后面是变量,前面是字符串,变量要填入的地方使用格式化符号替代。
使用f开头的字符串,需要填入变量的地方直接使用{变量},这种方法使用比较方便。
python常用字符串格式化符号:
符 号 | 描述 |
---|---|
%s | 格式化字符串 |
%d | 格式化整型 |
%f | 格式化浮点数字,可指定小数点后的精度 |
%c | 格式化字符及其ASCII码 |
4.4 字符串常用函数
函数/方法 | 描述 |
---|---|
str.replace(old, new[, max]) | 返回字符串中的 old(旧字符串) 替换成 new(新字符串)后生成的新字符串,如果指定第三个参数max,则替换不超过 max 次 |
str.split(str=“”, num=string.count(str)) | 通过指定分隔符对字符串进行切片,如果参数num 有指定值,则仅分隔 num 个子字符串,返回列表。 |
str.strip([chars]) | 移除字符串头尾指定的字符,chars选填,如果不填表示去除空格,有填写去除填写的字符。 |
str.rstrip([chars]) | 移除字符串末尾指定的字符,chars选填,如果不填表示去除空格,有填写去除填写的字符。 |
str.lstrip([chars]) | 移除字符串开头指定的字符,chars选填,如果不填表示去除空格,有填写去除填写的字符。 |
str.capitalize() | 将字符串的第一个字母变成大写,其他字母变小写 |
str.count(sub, start= 0,end=len(string)) | 字符sub在str字符串中出现的次数,start和end选填可以限制范围,不填表示整个字符串。 |
str.islower() | 如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False |
str.lower() | 返回将字符串中所有大写字符转换为小写后生成的字符串 |
str.upper() | 返回小写字母转为大写字母的字符串 |
str.title() | 返回"标题化"的字符串,就是说所有单词都是以大写开始 |
str.swapcase() | 返回大小写字母转换后生成的新字符串 |
str.join(sequence) | 返回通过指定字符连接序列中元素后生成的新字符串 |
str.find(str1, beg=0, end=len(string)) | 检测字符串中是否包含子字符串 str1 ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,如果包含子字符串返回开始的索引值,否则返回-1 |
str.index(str1, beg=0, end=len(string)) | 和find一样用法和作用,区别在str1在str找不到的时候,会抛出异常。 |
4.5 字符串的拼接
普通拼接:字符串是采用”+“来拼接,拼接的数据类型必须都是字符串类型的。
5、字典(dictionary ,简写为dict)
5.1 定义和语法
概念:字典(dictionary ,简写为dict)是另一种可变容器模型,且可存储任意类型对象,和c#的字典和哈希表一样,就是key和value对应的键值对关系数据容器,key必须是唯一的。
用法:字典的每个键值 (key=>value
) 对用冒号 (:) 分割,每个对之间用逗号 (,) 分割,整个字典包括在花括号 ({}
) 中 ,格式如下所示:
# 定义字典
dict = {key1 : value1, key2 : value2 }
#定义空字典
dict={}
- 字典和集合都是使用大括号,在空集合的定义是不允许使用空大括号,是因为被字典占用了。
- 字典也是不能使用下标索引。
- 字典也是可以嵌套,但key不可以为字典,但value可以是字典。
5.2 常用函数
函数/方法 | 描述 |
---|---|
dict[key] | 返回key对应的value值,如果是嵌套的字典就是采用dict[key][key]来获取几层就几个[] |
dict[key]=value | 如果key存在的话,相当于对key的值进行修改,如果key不存在就是新增key和value键值对。 |
dict.pop(key) | 删除指定key的键值对,并返回key对应的value值 |
dict.clear() | 清空字典 |
keys=dict.keys() | 返回字典的所有key值(keys是dict_key类型对象),所以这时可以使用for语句遍历key的值(for key in keys),再来取key对应的value值。 |
key in dict | 判断键是否在字典里,如果键在字典dict里返回true,否则返回false;可以使用for语句进行遍历。 |
dict2=dict1.copy() | 字典1复制给字典2 |
dict.get(key,default=None) | 函数返回指定键的值,如果值不在字典中返回默认值;和dict[key]区别,dict[key]取值时key不在时会抛出异常。 |
dict.setdefault(key,default=None) | 和dict.get一样的作用,区别是在key不存在时,会把key和default写入到字典。 |
dict1.update(dict2) | 把字典2的元素追加到字典1中,追加后的字典1包含了1和2的内容,如果出现重复key了话讲不会被写入。 |
dict.items() | 可以使用在for循环中item in dict.items(),这样循环出(key,value)这样的键值对元组。 也可以在for循环中 key,value in dict.items(),这样可以直接遍历出key和value。 |
6、序列切片(下标的应用)
6.1 定义和语法
切片是Python中一种用于操作序列类型(列表、字符串和元组)的方法。它通过指定起始索引和结束索引来截取出序列的一部分,形成一个新的序列。
![外链图片转存失败,源站可能有防盗链机制,建议将图
如图复习:python的各个字母作为列表(元组、字符串)的元素,p是最早存入,n是最晚存入,所以这些下标可以分为正向(正数)和反向(负数),所以根据书写习惯我们把最早输入的写在最左端,也可以做如下认为:
正向:从左到右,从0开始递增。
反向:从右到左,从-1开始递减。
语法:
sequence[start🔚step]
sequence:表示待切片的序列(列表、字符串、元组)
start:表示起始索引==(包含)
==,不填时表示正向表示0,反向表示最小值。
end:表示结束索引==(不包含)
==,不填时正向表示最大值,反向表示-1。
setp:表示步长(不填默认为1)。
6.2 切片例子
正向例子(例子取列表为例,元组和字符串用法一样;例子采取密令行方式):
>>> list1=[1,2,3,4,5,6,7,8,9] # 设个列表(元组和字符串一样)
>>> list1[2:4] # 步长不填默认为1
[3, 4] # 上一行的输出结果
>>> list1[1:6:2] # 步长为2的例子
[2, 4, 6]
>>> list1[:2] # 起始索引不填,表示从0开始
[1, 2]
>>> list1[1::2] # 结束索引不填,表示到结束
[2, 4, 6, 8]
反向例子:
>>> list1=[1,2,3,4,5,6,7,8,9] # 设个列表(元组和字符串一样)
>>> list1[:-1] # -1是结束索引,所以不包括
[1, 2, 3, 4, 5, 6, 7, 8]
>>> list1[-5:-1] # 切片的顺序是以索引值从小切到大。
[5, 6, 7, 8]
>>> list1[-5:-1:2] # 步长为2
[5, 7]
步长为负数例子:
>>> list1=[1,2,3,4,5,6,7,8,9] # 设个列表(元组和字符串一样)
>>> list1[::-1] # 步长为1,负数表示方向
[9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> list1[::-2] # 步长为2,负数表示方向
[9, 7, 5, 3, 1]
>>> list1[5:2:-1] # 开始索引是5,由于是负数步长反向切片,结束索引要小于开始索引所以是2(结束索引不包含,所以3就不能取)。
[6, 5, 4] # 处理逻辑:找到开始索引5(包含)取值6,回退取索引4,3的值5和4,结束下标2由于不包含就不取了。
*******重点*******
- 序列下标无论正反向,有个共性就是下标值都是从小到大。
- 步长为正数时:起始索引是递增到结束索引,也就是往前走、往大的走,所以start值是小于end值的。
- 步长为负数时:起始索引是递减到结束索引,也就是玩后走(回退),往小的走,所以start值是要大于end值的,输出的结果和正向相反倒着来。
步长为正负数的切片示意图:
7、各种序列小结对比
列表 | 元组 | 字符串 | 集合 | 字典 | |
---|---|---|---|---|---|
元素数量 | 支持多个 | 支持多个 | 支持多个 | 支持多个 | 支持多个 |
元素类型 | 任意 | 任意 | 仅字符串 | 任意 | key和valu都任意 |
下标索引 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
重复元素 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
可修改性 | 支持 | 不支持 | 不支持 | 支持 | 支持 |
数据有序 | 有序 | 有序 | 有序 | 无序 | 无序 |
使用场景 | 可修改、可重复的一 批数据记录场景 | 可修改、可重复的 一批数据记录场景 | 一串字符的记录场景 | 不可重复的数据 记录场景 | 以key检索value的数据 记录场景 |
重点:数据容器通用的函数:len,max,min(元素个数,最大元素,最小元素)字典只是体现key的最大最小值。
(六)、输入和输出介绍(input和print)
这里学习的输入输出主要是学习input和print函数的用法,这两部分是python比较经常使用的函数。
1、input(输入)
input主要是通过键盘输入,让系统得到你所输入的内容。
语法:
str=input(‘提示内容’)
返回:字符串
提示内容:一个字符串
例子:
str=input('请输入一个整数:') # 会输出一串字符串”请输入一个整数“,等待你输入回车后str会收到你的输入结果。
返回值都是字符串,如果希望是整型就需要进行转换。
2、print(输出)
2.1 语法和定义
print是程序直接在程序中输出结果。
语法:
print(values,…,sep=’ ‘,end=’\n’,file=sys.stdout)
values:要输出的对象,可以是数字,字符串,列表等;省略号表示一次可以输出多个对象。
sep:分隔符。当输出两个或者两个以上的对象时,对象与对象之间默认使用空格’ ‘分隔开,可以带入一个符号,输出对象将使用该字符分隔。
end:输出所有对象后,默认换行。’\n’代表换行,为空代表不换行。
file:设置输出设备,默认输出到显示器。
例子:
>>> print('hello','world','dave','ruan','heima') # 默认的使用办法
hello world dave ruan heima
>>> print('hello','world','dave','ruan','heima',sep='/') # 使用了sep参数,输入结果就起了变化
hello/world/dave/ruan/heima
>>> print('hello')+print('world') # 模拟连续输入两个print,默认情况下是会换行的。
hello
world
>>> print('hello',end='')+print('world') # 对第一个print的end参数给与”“(空值)代表不换行,输出结果就不换行了。
helloworld
2.2 输出格式化
方法一:使用占位符(%)格式化输出
语法格式:‘%[-][+][0][m][.n] 格式化字符’%输出对象
-:可选参数,指定左对齐
+:可选参数,指定右对齐
0:可选参数,表示右对齐,用0填充空白处(一般和m参数一起使用)
m:可选参数,指定占用宽度
n:可选参数,指定小数点后保留位数
格式化字符:必须参数。
python字符串格式化符号,在字符串章节有所介绍,主要讲解最常用的的%d,%f,%s,对应整型、浮点型、字符串。
使用例子:
data = 555
print('%-d'%data) #左对齐
输出:
555
data = 555
print('%5d'%data) #占用宽度为5
输出:
555 #左边两个空位
data = 555
print('%05d'%data) #占用宽度为5,用0填充空位
输出:
00555
data = 555.222222
print('%.2f'%data) #保留两位小数
输出:
555.22
方法二、format格式化
format有三种用法:
用法一:format中的内容和花括号的出现顺序一一对应
place = '中国'
age = 18
print('我来自{},我的年龄是{}'.format(place, age))
输出:
我来自中国,我的年龄是18
用法二:花括号中填入数字指定format中的参数(数字从0开始)
place = '中国'
age = 18
print('我来自{0},我的年龄是{1}'.format(place, age))
输出:
我来自中国,我的年龄是18
用法三:通过关键字指定
print('我来自{place},我的年龄是{age}'.format(place = '中国', age = 18))
输出:
我来自中国,我的年龄是18
方法三、f-string(较为常用)
f-string 是 python3.6 之后版本添加的,称之为字面量格式化字符串,是新的格式化字符串的语法。
f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去,实例如下:
name = '张三'
age = 18
print(f'我的名字是{name},我的年龄是{age}')
输出:
我的名字是张三,我的年龄是18
(七)、Python3条件控制语句
1、IF语句
1.1 定义和语法
Python条件语句和所有语言的if语句一样都是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=.%2FPython%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.assets%2F3.jpeg&pos_id=img-cDvEAhtS-1698332285
062)
语法:
if condition_1:
statement_block_1
elif condition_2:
statement_block_2
else:
statement_block_3
注意:
1、每个条件后面要使用冒号(:),表示接下来是满足条件后要执行的语句块。
2、使用缩进来划分语句块,相同缩进数的语句在一起组成一个语句块。
7、match-case语句
7.1 定义和语法
match-case是Python3.0开始才有的,在python2.0时代是没有的;它和c系列的Switch……case类似,但用法还是有点差别了。
语法例子:
i=2
match i:
case 1:
print(1)
case 2:
print(2)
case 3:
print(3)
case 4:
print(4)
case 5:
print(5)
输出:
2
注意:
match-case和C系列的Switch-case的作用一样,用法还是有差异了,Switch每个case都需要break,不然以上的例子就会出现2、3、4、5的结果,而match不会。
(八)、Python循环控制语句
所有的语言都离不开条件判断和循环控制语句,循环控制语句顾名思义就是重复循环执行条件内的代码块,具体流程图如下:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png
?origin_url=.%2FPython%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.assets%2F4.png&pos_id=img-YMflz9Fy-1698332285062)
1、while循环语句
1.1 定义和语法
while 判断条件:
statements
例子:
n = 100
sum = 0
counter = 1
while counter <= n: # 条件后的冒号不能忘记
sum = sum + counter # 循环体内的缩进要注意,要比while语句更缩进代表体内代码块。
counter += 1
print('Sum of 1 until %d: %d' % (n,sum))
2、for循环语句
2.1 定义和语法
python的for语句和while语句是有本质的区别,也不同于其他语言的for语句,更像是C系列语言的foreach语句,它只能适用于数据容器的元素遍历使用。
语法:
for in :
else:
例子:
>>> languages = ["C", "C++", "Perl", "Python"]
>>> for x in languages:
... print (x)
...
C
C++
Perl
Python
>>>
3、循环配套关键字和函数使用(break、continue、pass、range())
3.1 break 和 continue、pass
break 语句:可以跳出 for 和 while 的循环体。如果你从 for 或 while 循环中终止,任何对应的循环 else 块将不执行。
continue 语句:被用来告诉 Python 跳过当前循环中的当此循环,然后继续进行下一轮循环。
pass语句:pass 语句什么都不做。它只在语法上需要一条语句但程序不需要任何操作时使用。
3.2 range()函数
range可以生成随机数,在日后的代码中会经常使用到。
语法:
range(start, stop, step)
start:是指定的起始值
stop:是指定的停止值(不包含在序列中)
step:是指定的步长
返回的是一个range对象可以转成列表等序列。
例子:
>>> print(list(range(1,6))) # 1个步长
[1, 2, 3, 4, 5]
>>> print(list(range(1,6,2))) # 两个步长
[1, 3, 5]
>>>
(九)、函数
一直以来函数和方法在其他语言中都混为一谈,比如c系列语言都是基于面向对象的语言,虽然python也是一个面向对象语言,但相比c系列语言要更有广度,c系列语言的代码都是基于类内的,也就是必须建有命名空间和类从而进行代码实现。而python面向对象开发时和c系列语言无差别需要建立类,但python还支持非面向对象的开发,所以就更能体现出了函数和方法的概念性区别。
其实在Python中方法也是函数没有本质的区别只是定义在的地方不一样叫法就不一样了,定义在类中我们就叫方法,定义在类外我们就叫函数,当然定义的不同使用方法还是稍微有点不同。具体可以看下面介绍。
1、定义和语法
函数和方法的定义就是组织一个代码块实现某些功能,可以被其他代码重复调用使用,实现高效、可读性高的代码特点。
1.1 语法
函数定义语法:
def 函数名(形参1、形参2、……形参n=default):
函数体
return 返回值
注意:
- 当无序返回时,可以不需要return语句。
- 形参有赋值个默认值时不能出现在形参前面。
函数被调用语法两种
1、[变量]=函数名([形参名]=实参)
2、[变量]=函数名(实参)
注意:
- 语法1的实参顺序可以和函数定义的形参的顺序不一样。
- 语法2的实参的顺序必须和函数定义的形参的数据一一对应、所以顺序要一样。
- 二者有个共性如果形参中有默认值的,调用的时候可以带入实参也可以不带入(不带入的时候系统会取默认值)
形参和实参的概念:
- 形参:函数或方法定义时设置的参数叫形参。 —出现于定义
- 实参:调用函数时带入的参数就是实际参数叫实参。 —出现于调用
实现例子:
def test(a,b,c=0):# 形参c=0代表设置的默认值为0,注意:有默认值的参数不能出现在没有默认值参数的前面。
'''
功能说明:返回几个数字的乘积
-param a:参数a的说明
-param b:参数b的说明
-param c:参数c的说
-return:返回三个数的乘积
'''
return a*b*c
# 函数调用方法:
# 带入形参名的调用,顺序可以打乱
test(b=2,a=3,c=8)
输出:48 # 3*2*8=48
# 由于c有默认值可以不带入
test(b=2,a=3)
输出:0 # 3*2*0=0
# 不带入形参名的
test(3,2,8)
输出:48 # 3*2*8=48
# 不带入形参名切有默认值的
test(3,2)
输出:0 # 3*2*0=0
以上代码是标准的规范化写法,2到8行是函数的说明文档,让人家知道你定义函数的目的和用法,说白的就是一份说明书
2、函数参数传递
所谓的参数传递就是函数在调用的时候,使用实参的值传递给形参的过程,称为参数传递。
根据前面[变量的内存存储特性](# 1.1 变量的内存存储方式),参数在传递的过程中如果形参在函数执行过程中被重新赋值,对于普通变量和数据容器,将产生出不同的结果。
2.1 普通变量作为实参传递
普通变量作为实参传递给形参的是值的地址,如果在函数执行过程中形参被重新赋值,相当于新值的地址重新赋值给与形参,对实参没有任何影响,具体看例子:
def test(a):
print(f"形参未被重新赋值前的地址:{id(a)} ,值a={a}")
a=100
print(f"形参被重新赋值前的地址:{id(a)} ,值a={a}")
x=0
print(f"实参的初始赋值地址:{id(x)} , 值x={x}")
test(x)
print(f"函数执行后的实参地址:{id(x)} , 值x={x}")
输出结果:
实参的初始赋值地址:4434841280 , 值x=0
形参未被重新赋值前的地址:4434841280 ,值a=0
形参被重新赋值前的地址:4434844480 ,值a=100
函数执行后的实参地址:4434841280 , 值x=0
从上例子分析:
数值0的地址是4434841280,实参把这个地址给了形参,所以形参在没有被重新赋值前的地址也是4434841280。
当形参重新赋值100,数值100的地址是4434844480,所以形参指向的地址变为4434844480,对实参不构成影所以实参x的指向的还是地址4434841280。
2.2 数据容器(序列)作为实参传递
数据容器传递,实参是把容器的地址传递给了形参,形参对元素进行增删改动作的时候会把对应值得地址修改到数据容器中,对实参产生了影响,所以实参的数据容器的内容也将发生变化,具体看例子说明:
def test(p_lsit:list):
print(f"形参未被改变的列表内存地址:{id(p_lsit)}")
# 遍历改变前各个元素的地址
for var in p_lsit:
print(f"\t形参改变前下标为:{p_lsit.index(var)} 号元素值:{var} 内存地址:{id(var)}")
p_lsit[0]=100
print(f"形参元素被改变的列表内存地址:{id(p_lsit)}")
# 遍历改变后各个元素的地址
for var in p_lsit:
print(f"\t形参改变后下标为:{p_lsit.index(var)} 号元素值:{var} 内存地址:{id(var)}")
my_list=[1,2]
print(f"函数执行前实参的列表内存地址:{id(my_list)}")
for var in my_list:
print(f"\t函数执行前实参列表下标为:{my_list.index(var)} 号元素值:{var} 内存地址:{id(var)}")
test(my_list)
print(f"函数执行后实参的列表内存地址:{id(my_list)}")
for var in my_list:
print(f"\t函数执行后实参列表下标为:{my_list.index(var)} 号元素值:{var} 内存地址:{id(var)}")
输出结果:
函数执行前实参的列表内存地址:4308093184
函数执行前实参列表下标为:0 号元素值:1 内存地址:4304908000
函数执行前实参列表下标为:1 号元素值:2 内存地址:4304908032
形参未被改变的列表内存地址:4308093184
形参改变前下标为:0 号元素值:1 内存地址:4304908000
形参改变前下标为:1 号元素值:2 内存地址:4304908032
形参元素被改变的列表内存地址:4308093184
形参改变后下标为:0 号元素值:100 内存地址:`4304911168` # 对0号元素的值修改为100,0号的地址改变了,以为了列表空间存的地址也改变了
形参改变后下标为:1 号元素值:2 内存地址:4304908032
函数执行后实参的列表内存地址:4308093184
函数执行后实参列表下标为:0 号元素值:100 内存地址:`4304911168` # 实参列表地址中的元素地址被修改了。
函数执行后实参列表下标为:1 号元素值:2 内存地址:4304908032
例子分析说明:
- 实参和形参的地址都是4308093184,形参列表内容被修改后形参和实参的列表地址还是4308093184
- 形参和实参中元素的地址也都一样,但形参对0号元素值进行修改后,100的地址修改了1的地址,可以理解为直接对4308093184中0号元素的地址进行了修改,这样反应出了实参的元素也发生了改变,因为形参和实参是指向了同一个地址,地址内的内容被修改当然反应在形参和实参的值上了。
- 由于字符串和元组是不可更改的序列,所以形参的改变不会影响到实参的结果。
3、函数的多个返回值
有时函数需要返回多个值,例如:x,y
例如:
# 定义个函数有两个返回值
def test():
return 1,2
# 调用函数,赋值变量的顺序和函数定义返回的顺序以一一对应,1和2分别赋值于x和y。
x,y=test()
4、函数的多种参数形式
4.1 可变参数(*args)
定义语法:
def test(*args)
args:是个元组
调用语法:
test(1,2,3,4) # args得到值就是(1,2,3,4)
test((1,2,3)) # args得到值是((1,2,3))元组作为一个实参传入,将变成形参的元组的一个元素
- 可以以N个实参传入给形参,形参收到的是一个元组,通过元组获得实参传入的信息。
- 容器也可以做实参传入,但容器在形参的体现就是元组的一个元素。
4.2 可变参数(**kwargs)
定义语法:
def test(**kwargs)
kwargs:是个字典
调用语法:
test(key=value,……)
test(name=‘lucy’,age=12) # 采用这种方式,形参得到{‘name’:‘lucy’,‘age’:12}
调用函数时,实参的key无序变成字符串。
4.3 函数作为参数
这个就有点类似c#的委托,具体看示例:
def test_func(compute): # compute 就是一个函数参数
result=compute(3,5)
return result
def add(x,y):
return x+y
result=test_func(add) #调用是,直接把函数add作为实参传入
print(result)
输出:
8
5、函数变量作用域
5.1 变量作用域
提到变量的作用域就要提全局变量(Global variables)和局部变量(Local variables)两个概念。
- 全局变量:就是在模块中所有函数都可以调用的变量,一般在函数体外被定义。这个全局只是限定在模块中,模块在后面章节介绍。
- 局部变量:就是在函数体内的变量,在python中冒号“:”后面的变量都是局部变量,当然局部与全局也是一个相对的概念。比如出现函数嵌套的情况。
注意:
- 从局部变量的定义”:“以后的都是变量,后面学的class(类),所以类内也应该是局部变量。
- 局部变量在函数被执行时系统会在内存空间指派局部变量存储,函数结束空间自然被解放,所以局部变量只存在于函数内。
例子:
_mycount=10
def test():
_mycount=100 #这里的_mycount这个变量是局部变量,和全局变量同名而已,如果同名函数默认使用局部变量。
print(_mycount)
print(_mycount)
test()
print(_mycount)
输出:
10
100
10
思考:
name = 'Charlie'
def test_1():
print(name) # 这里打印出来的是全局变量
def test_2():
print(name) # 代码执行到这里报错
name='lucy'
test_1()
test_2()
输出:
Charlie
Traceback (most recent call last):
File "/Users/dave.ruan/Documents/Dave/python学习例子/myproject12/match_test.py", line 11, in <module>
test_2()
File "/Users/dave.ruan/Documents/Dave/python学习例子/myproject12/match_test.py", line 7, in test_2
print(name)
^^^^
UnboundLocalError: cannot access local variable 'name' where it is not associated with a value
思考的问题是:test_2()为什么print(name)为什么会报错难道不能打印出全局变量吗?
回答: 报错翻译:”python无法访问与值无关的本地变量’name’“,这里的name被认为是局部变量,所以找到不到局部变量的初始值报错。python解释器是由内向外寻找变量的,这例子函数体内就是局域变量层找到了变量的初始值就认为它存在局部变量,它就会当做局部变量来处理,由于print(name)在变量的初始化前面,就会被报错局部变量无关联值。
5.2 global关键字提升函数体内变量为全局变量
根据5.1发现函数体内的局部变量在函数执行结束后,内存空间就被解放了,局部变量就不存在了,就不能在函数体外被引用了。如果要实现函数体内对全局变量进行赋值而不被解释器误认为是局部变量的赋值产生误会,这就需要引入Global关键字,让变量直接指向全局变量。
name = 'Charlie'
def test_1():
print(name)
def test_2():
global name # 这里声明本函数内的name是全局变量。
print(name) # 这里再也不会认为是局部变量了就不会报错了
name='lucy'
test_1()
test_2()
# 输出的结果:
# Charlie
# Charlie
global的作用:是在函数体内对变量进行声明,让变量指向全局变量。
6、匿名函数lambda
6.1 定义和语法
匿名函数也称为lambda函数,是一种没有函数名的函数。它是一种一次性的、在需要的时候定义,用完即丢弃的函数。
语法:
lambda arguments:expression
arguments:参数
expression:表达式(表达式只有一行)
示例:
lambda x,y:x+y # 表示这个函数有形参x和y,表达是return x+y。
# 前面章节有函数作为函数的参数进行使用的,我们也可以使用lambda更便捷.
def test_func(compute):
result=compute(3,5)
return result
result=test_func(lambda x,y:x+y)
print(result)
# 输出结果:8
4.3函数作为参数的例子和本例子对比,本例简洁明了。
7、python函数不支持重载原因
7.1 重载的概念
重载不是python的概念而是其语言的概念,讲的是函数(方法)根据形参的数据类型不同或形参个数不同,建立多个同名不同形参的函数(方法)称为函数的重载。
7.2 python不存在重载
由于python的变量没有数据类型这个概念,以及函数的参数的可变性已解决了对不同参数的调用方式,所以就不用建立同名函数,而不允许同名函数的存在;综上描述python函数或者后面学习的类内方法都不允许同名,也就是说不允许重载。
(十)、python模块和包的应用
1、模块(moudle)
##### 1.1 定义:
模块就是一个后缀是py的文件,里面包含着类、变量、方法等,我们可以引入模块而使用模块中的类、方法等成员;这和c#的一个命名空间代表一个文件一样。我们写了任何一个py文件都能称为模块,模块的名称就是文件名。
1.2 模块导入
[from 模块] import [模块|类|变量|函数|*] [as 别名]
使用from 模块时import 可以是*(表示全部导入)也可以指定某个函数或者某个变量导入。
可以不使用from直接使用import 模块,表示整个模块导入。
各种导入方式的用法差异:
- 使用 from 模块 import * 或者是指定函数导入的,在使用的过程中可以直接使用函数调用就可以了。
- 使用import 模块 导入的,使用模块里的函数时,需要使用模块名.函数名()。
如果使用第一种方法导入时,如果本模块中存在同名的函数时,调用的时候会默认调用本模块的函数,如果要调用导入模块的函数名就需要带入模块名。
1.3 模块的内置变量
__name__ # 当前模块被执行时,__name__=__main__ 如果是被导入了话值等于模块名称
__all__ # 卸载模块头,一般低于模块导入行,这时个列表,
__all__ = ["public_function", "PublicClass"] # 表示模块被导入时,只可以使用列表中的函数或类
all内置变量在模块被导入时,限制使用all列表列出的函数或类,只针对from 模块 import *这样的导入密令,其他密令无效。
2、包(package)
2.1 包概念
从物理上看就是个文件夹,是模块的归类,包里面放着相同类的模块,包里还必须包含一个__init__.py模块。切记init文件的存在才能称为包。
2.2 包的导入
导入语法如下四种:
from 包 import 模块
from 包 import *
from 包.模块 import 函数(变量
from 包.模块 import *
2.3 __init__.py文件
__init__文件的使用特点:
__init__文件中也可以设置内置变量__int__,列表中指定模块,表示只有被指定的模块才能被导入,一样也只能使用在from 包 import *语句中,其余语句导入不受限制。
在init文件中写入 from 包.模块 import 类/函数,然后被导入的时候只要输入 from 包 import 类就可以直接使用类了,方法也是一样的。
2.4 第三方包的导入
使用pip(pip3)pip3是3.0版本以后的使用。pip install 包名,就可以了有的包可以自动从网络上下载,这样是连国外的网站下载比较慢,所以我们可以这样:pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名,这个使用了清华的网站进行下,下载速度会比较快点。
(十一)、程序异常捕获处理
1、什么是异常
当我们写程序难免遇到报错,专业的称呼叫做异常,行业俗语叫做bug,由于异常的存在程序会被报错停止异常,而对于有些异常并不需要程序停止运行,只要给用户一个提示,让用户根据异常提示内容进行操作即可。
2、异常捕获语法
2.1 语法
try:
# 待监测的代码(可能会出错的代码)
except Exception as e: # e就是系统提示的错误信息
# 针对各种常见的错误类型全部统一处理
else:
# try的子代码正常运行结束没有任何的报错后 再执行else子代码,此段代码可以不需要存在
finally:
# 无论try的子代码是否报错 最后都要执行finally子代码
Exception:是顶级的错误类型,如果需要针对不同的异常做不同的处理,看下面常见的一些错误类型。
异常类型名 | 描述 |
---|---|
NameError | NameError是当某个局部或全局名称未找到时将被引发,也就是指变量名称发生错误,比如用户试图调用一个还未被赋值或初始化的变量时会被触发。 |
SyntaxError | SyntaxError主要是因为当解析器遇到语法错误,比如少个冒号、多个引号之类的,编程时稍微疏忽大意一下就会出错,应该是最常见的一种异常错误了。 |
IndexError | 当序列抽取超出范围时将被引发,也就是索引超出范围,比如最常见下标索引超出了序列边界,比如当某个序列m只有三个元素,却试图访问m[4] |
IOError | 输入/输出异常,主要是无法打开文件 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除法运算中除数0 或者 取模运算中模数为0 |
AttributeError | 访问的对象属性不存在 |
ImportError | 无法导入模块或者对象,主要是路径有误或名称错误 |
AttributeError | AttributeError是属性错误,当属性引用或赋值失败时就会出现。比如列表有index方法,而字典却没有,所以对一个字典对象调用该方法就会引发该异常。 |
SystemError | 当解释器发现内部错误,但情况看起来尚未严重到要放弃所有希望时将被引发。 关联的值是一个指明发生了什么问题的字符串(表示为低层级的符号)。 |
ValueError | 当操作或函数接收到具有正确类型但值不适合的参数,也就是值错误,比如想获取一个列表中某个不存在值的索引。 |
如果不知道具体异常类型时可以使用顶级异常(Exception)替代,它可以捕获所有异常。
3、异常的传递性
异常是可以传递的怎么理解呢?嵌套调用或者函数嵌套时,无论多少层从里到外的错误都可以被最外层捕获到异常信息。
4、主动抛出异常
在编写函数的过程可能会对一些值进行判断,不符合项目意思的判断需要告知用户,让用户做出调整,就需要我们抛出异常。例如:在做用户登录校验时,我们对密码进行判断,如果不正确我们就要抛出异常让用知道错误是什么。
语法:
raise 异常类型名称(‘异常描述’)
例子:
try:
if len(self._node_list) == 0:
raise ValueError('Wrong parameter format') # 抛出异常,并把错误信息告知用户。
except ValueError as e:
print(e)
(十二)、面向对象编程
python本身就是一个面向对象的语言,要实现面向对象的就要有类、对象、封装继承、多态的现象,在下面章节意义有介绍。
面向对象概念比较抽象化,那什么是对象呢?它的概念很抽象不进行语言描述,举个简单的例子:你买了一辆编号为aodi110001的奥迪A4车,那这辆(编号为:aodi110001)的奥迪A4车就是奥迪A4的实例对象(简称实例或对象)。怎么理解呢?奥迪A4是个概念是一个类别生成成编号aodi110001的车,这个生产的过程就是实例化。
1、类的介绍
1.1 类的定义和语法
如面向对象的介绍奥迪A4是概念,你买了一辆车,这辆车是实物(实例化的产物)就是实例对象,而奥迪A4是一个类(包含了众多属性和方法的类)。
实例对象:是类实例化的产物通常称实例或对象(一个类可以有多个实例或对象),实例对象是类衍生出来的实例。
调用实例对象方法:实例名.方法名()或实例名.变量或self.变量
类对象:类被定义出来就是一个对象,叫做类对象(一个类只有一个类对象),类对象其实就类本身。
调用类对象方法:类名.变量,类对象不能调用类方法。
1.1.1 定义语法
class 类名:
1.1.2 实例化语法
对象名=类名() # 这就是产生了一个实例化。
1.2 类成员
类的成员有类变量、属性(实例变量)、类方法
1.2.1 变量的概念和定义
类的变量和属性(实例变量也可以叫对象变量)他们都是声明在类内并且是方法外的变量,所以他们都算局部变量,属于类以内的局部变量。
- 类变量:属于类所有,它定义在类内方法外,是所有对象共享的变量及变量值的改变所有对象都能发现,比如奥体A4这个名称只对应到这个类,比如哪天奥迪A4改名为马车A4,你们手上的车是不是也改叫马车A4了。
- 属性(对象变量):属于对象所有,它是实例化后的一个属性,它可以定义在方法内,也可以通过类变量来定义属性,实例之间互不干扰;好比车的颜色是奥体A4的属性,每个客户买了车颜色都不一定是相同的,这就是每个对象的属性都不一定是一样的。
注意:属性(对象变量)也可以通过类变量来定义的理解,属性可以直接使用类变量作为自己的属性,直到属性被重新赋值,否则属性的值一直等于同名类变量。
类变量的用法:
类名.变量名 # 无论在类内还是类外,都是以这种方式实现
类变量的概念和c#的类里的静态变量类似,它是属于类所有并且是所有对象共享的;使用方法,都是类名.变量。
属性(对象变量)的用法:
类内的用法:self.变量名
类外的使用:对象名.变量名
类内方法外(基本上都是类的开头)定义的变量,被对象所引用它就是对象变量,被类所引用它就是类变量。怎么理解这句话呢?
由于程序启动后会对类进行初始化,会给变量在内存栈里存储变量,会把初始化值的地址指向变量,这时候的变量可以理解为类变量,程序的运行过程中对类进行多次的实例化,每次的实例化都会为变量存到栈里,从这一步可以理解同一个变量名就成两个变量了,知道实例完成,变量在栈里的空间被收回。简单点理解是,实例化的开始就是类变量复制成实例变量的。
# 类的定义
class Students:
Name="" #类变量的定义
Age=0
def SayHi(self): # 类成员方法的定义
print(self.Name) # 实例(对象)变量
print(Students.Name) # 类变量
1.2.2 方法的概念和定义
方法是类行为的实现函数,好比奥迪A4的刹车行为、方向转动行为,每辆车(对象)都有这些行为,车主使用这些行为就是对象调用方法的过程。
方法的定义:
def 方法名(self,参数1,参数2……参数n):# 首个形参必须带self,调用的时候可以不用理会这个形参
方法体
[return 语句]
注意:
类方法的参数首个必须带self,调用的时候可以当做不存在即可。
self其实对应的实参是对象,因为类方法是给实例对象调用的,类本身不能调用方法。
方法内不能直接使用方法外定义的变量(全局变量除外,若是全局变量需要使用global声明),必须使用self.变量名或类名.变量名(对象变量和类变量)。
1.2.3 变量和方法定义的综合例子
为了更好的理解类内变量和方法定义,类别两和属性(对象变量)的作用的不同,请查看下面列子,分析请看输出结果的每行分析:
class Students:
Name=None #类变量的定义
Age=0
def SayHi(self,_name): # 类成员方法的定义
self.Name = _name # 对对象变量赋值
if Students.Name==None: # 对属性(对象变量)进行赋值
print(f"我是{_name},我是第一个打招呼的人")
else:
print(f"我是{_name},上个打招呼的同学是{Students.Name}")
print(f"我开始打招呼,大家好,我是:{self.Name}")
Students.Name=_name # 对类变量进行赋值
print(f"看看上一个打招呼人是否是None,{Students.Name}")
p1=Students() # 实例化一个同学李雷
p2=Students() # 实例化一个同学李丽
print(f"李雷看看上一个打招呼的是谁,{Students.Name}")
p1.SayHi("李雷")
print(f"我已报名我是谁,{p1.Name}")
print(f"李丽看上一个打招呼的是谁,{Students.Name}")
print(f"我未报名我是谁{p2.Name}")
p2.SayHi("李丽")
print(f"最后一个打招呼的是:{Students.Name}")
print(f"我已报名我是谁,{p1.Name}")
print(f"我已报名我是谁,{p2.Name}")
输出结果:
看看上一个打招呼人是否是None,None `代码13行的输出,这时类变量未被改变还是初始值`
李雷看看上一个打招呼的是谁,None `代码17行输出,这时只是实例化了两个对象,类变量还是未被改变,还是初始值`
我是李雷,我是第一个打招呼的人 `代码18行执行并跳转到代码7行输出,打招呼方法正在执行中……,属性Name已被‘李雷’赋值`
我开始打招呼,大家好,我是:李雷 `代码11行输出。`
我已报名我是谁,李雷 `代码19行输出,方法执行完毕 说明:p1对象的属性name意识李雷了`
李丽看上一个打招呼的是谁,李雷 `代码21行输出,说明类变量name的值已是李雷了`
我未报名我是谁李雷 `代码22行输出,说明对象p2的name变量已建立,赋值的初始值是类变量的初始值。`
`体现了属性可以通过类变量来定义,p2对象未执行方法属性未被赋值,所以一直使用类变量的值。`
我是李丽,上个打招呼的同学是李雷 `代码23行执行跳转9行输出,输出传入(报名)的李丽,输出类变量=李雷。`
我开始打招呼,大家好,我是:李丽 `代码11行输出`
最后一个打招呼的是:李丽 `代码25行输出,说明类变量都被不同对象调用方法给修改了,也发生了变化。`
我已报名我是谁,李雷 ` 代码26行输出,说名对象变量p1.Name=李雷`
我已报名我是谁,李丽 `代码27行输出,说名对象变量p2.Name=李丽`
`从最后两行的输出可以看出属性(对象变量)是属于对象的,互不干扰,两个对象的属性分别是李雷和李丽。`
1.3 类构造方法和魔术方法
1.3.1 构造方法概念和定义
在众语言中,构造方法就是一种特殊的方法,它在实例化对象时被调用,通常是用来初始化对象属性的。在Python中,构造方法存在同样的意义,构造方法的名称是__init__,和其他语言一样它属于特殊方法,用来初始化对象的属性。
构造方法的语法:
构造方法的定义语法和普通方法的定义没有什么大的区别,唯一不一样的地方是方法名是__init__。
def __init__(self,args1,args2,……argsn):
self.args1=args1
self.args2=args2
构造方法是在被对象创建(实例化)的时候自动调用,不能被认为的调用。
构造方法的调用
调用就是一个类实例的化的过程,关注类参数即可。
my_class=MyClass(‘lucy’,12) # 类名后面的参数和构造方法的参数要一一对应。
注意:构造方法和其他语言的区别,其他语言构造函数和方法可以重载,在函数章节介绍过了python是不允许重载的。
1.3.2 魔术方法
魔方方法是类内内置的方法,通常也称内置方法。
__str__()方法:定义对象的字符串表示,具体看例子解释,注意:一定要返回一个字符串。
例子:
class Students:
_name=None
_age=0
def __init__(self,name,age):
self._name=name
self._age=age
def __str__(self):
return("大家好,欢迎到students类对象来。")
def __call__(self,str): #让类名作为函数名替代__call__方法。
print(f"我输出的是:{str}")
st=Students('lucy',12)
print(st)
st("我是类的__call__方法")
输出:大家好,欢迎到students类对象来。
# 如果没有__str__方法,输出:<__main__.Students object at 0x1082a9a00> 就是一个对象地址
我输出的是:我是类的__call__方法
还有很多的魔术方法不做一一的描述,请查阅相关资料。
1.4 类方法链式调用
链式调用,或者也可以称为方法链(Method Chaining),从字面意思上来说就是将一些列的操作或函数方法像链子一样穿起来的代码方式。
链式调用的示例:
class MyClass:
def test(self,a:str):
print(f"this is test's function:{a}")
return self # 需要满足链式调用就需要返回对象
def test2(self,a:str):
print(f"this is test2's function:{a}")
return self
# 正常的调用MyClass两个方法的步骤是:
my_class=MyClass()
my_class.test("test测试")
my_class.test2("test2测试")
# 链式调用MyClass两个方法的步骤是:
my_class=MyClass().test("test测试").test2("test2测试")
重点:链式调用简洁,要使用链式调用的前提是在方法里面必须返回对象,否则没有办法满足链式调用。
2、封装
2.1 封装概念
众多语言在面向对象内容开发的过程不可避免的出现封装,python也是一个面向对象开发语言不可避免的要有封装概念,其他语言主要是类、类成员的封装,python主要是对类成员进行封装,什么是封装?封装其实比较好理解,就是在开发过程中不可变存在类被外部所调用,而处于数据的私密性需要把类的某些成员变量、成员方法,让他们只限于类内使用,不可被外部所调用或使用,这就是封装。其实也就是对类内成员做了访问控制。
在其他语言的访问控制都有公有(public)、保护(protected)、私有(private)三种。
访问控制 | 类内是否可以访问 | 是否可以被继承 | 类外是否可以访问 | 类对象是否可以访问 | 实例对象是否可以访问 |
---|---|---|---|---|---|
公有(public | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
保护(protected) | ✔️ | ✔️ | ❌ | ✔️ | ✔️ |
私有(private | ✔️ | ❌ | ❌ | ❌ | ❌ |
注意:原则上保护变量(protected)类外是不可以访问了,但是也能被访问,只能说是建议不要访问。
而私有变量非要在类外访问的话,可以做些修改,_类名+私有变量拼接后就可以访问了。
2.2 私有成员和保护成员的定义
保护成员(protected):是在变量或方法的前面加一个下划线,例如:_name
私有成员(private):是在变量或方法的前面加两个下划线,例如:__nam
3、继承
3.1 继承概念
继承是什么概念,就是一种子承父的过程,在开发的过程会遇到开发功能性很接近的多个类对象,这样会照常不必要的重复代码,照常不必要的人力资源的浪费,这时我们只要把共同的部分写到一个类,然后所有的类去继承它就可以大大的节省开发,比如小学生、初中生、高中生、大学生的类,但是这些都是学生有个共同属性、共同方法,我们只要建立个学生类,然后让小学生、初中生类去继承是不是可以减少很多的代码量呢?
名词解释:
父类:又称为基类,从名字可以看出来是被继承了。
子类:又称为派生类,从父类(基类)派生出来的。
3.2 继承的定义
语法:class subclass(parentclass1,parentclass2):
python继承类的特点:
- 有别其他的类继承是单继承,而python是多继承的,子类可以继承于多个父类。
- 如果多父类继承时如果遇到同名变量或者同名方法时,python有自己的处理,在子类定义的时候最左边的父类有优先权,同名变量或方法取优先权高的类所属。
3.3 父类方法可被子类重写
- 子类重写父类的方法,只要定义个和父类同名的方法即可,父类的代码将被覆盖。
- 子类在重新父类方法的过中,还要使用父类的方法,可以在在重写的过程使用super().方法名(),就可以调用父类方法
- 构造函数也可以被重写,也可以使用super调用父类的方法和变量。
3.4 继承示例
# 大学生继承学生的学习,大学又兼职打工。
class Students:
name=None
gender=None
jobs="我的职业是学生"
def study(self):
print("我在学校学习!")
class Worker:
name=None
gender=None
jobs="我的职业是打工人"
def work(self):
print("我在便利店兼职")
class College_Students(Students,Worker):
def __init__(self,name,gender):
self.name=name
self.gender=gender
def study(self,years): # 重写父类方法
print(f"我是:{self.name},性别:{self.gender}")
super().study() # 调取父类方法
print(f"{years}年我在清华上大学")
p=College_Students('LUCY',25)
print(f"我的职业是:{p.jobs}")
p.study('2023')
p.work()
输出结果:
我的职业是:我的职业是学生 `23行代码输出,说明了:继承多个父类时,靠前的类是有优先权,同名变量取优先权的父类`
我是:LUCY,性别:25 `24行代码转19行输出,说明了:父类方法被重写后调用的就是子类的方法`
我在学校学习! `24行代码转20行输出,说明了:使用super()调用父类方法`
2023年我在清华上大学 `24行代码转21行输出`
我在便利店兼职 `24行代码转11行输出,说明了:子类为重写,就直接调用父类的方法`
4、封装和继承综合例子
了解了封装和继承,需要综合看看封装的访问控制权,看看如下例子:
class VarTest:
classVar = "classVar" # 公有变量
__privateVar= "__privateVar" # 私有变量
_proectvar = "_proectvar" # 保护变量
def __init__(self,value=3):
self.objectVar = value
self.__objectprivate = "__objectprivate"
self._proectvars = "_proectvars"
def getvar(self):
print("在类方法中类变量",VarTest.classVar)
print("在类方法中访问公有实例变量",self.objectVar)
print("在类方法中访问类的私有变量",self.__objectprivate)
print("在类方法中访问类的保护变量",self._proectvars)
class Subclass(VarTest): # 继承子类
def getsubvar(self):
print("在子类方法中访问父类的类变量",VarTest.classVar)
print("在子类方法中访问父类的实例变量",self.objectVar)
# print("在子类方法中访问父类的私有变量",self.__objectprivate) # 访问不到,会报错(说明,私有变量不可以被继承)
print("在子类方法中访问父类的保护变量",self._proectvars)
if __name__ == '__main__':
print("类外直接访问类变量", VarTest.classVar)
print("类外直接访问类的保护变量", VarTest._proectvar)
# print("类外直接访问类的保护变量", VarTest.__privateVar) # 访问不到,会报错(私有变量不能再类外被类对象访问)
test = VarTest()
print("类外实例对象访问类的私有变量", test._VarTest__privateVar)
print("类外实例对象访问公有实例变量", test.objectVar)
print("类外实例对象访问被保护的实例变量", test._proectvars)
# print("类外直接访问类的保护变量", test.__privateVar) # 访问不到,会报错(私有变量不可以在类外被实例对象访问)
test.getvar()
subtest=Subclass()
subtest.getsubvar()
输出结果如下:
类外直接访问类变量 classVar `代码22行结果:说明类公有变量,类对象可以访问`
类外直接访问类的保护变量 _proectvar `代码23行结果:说明类的保护对象,类对象也是可以访问`
类外实例对象访问类的私有变量 __privateVar `代码27行结果:说明是有变量想在类外被访问的话,可以在私有变量名前拼接上”_类名“(切记下划线`)
类外实例对象访问公有实例变量 3 `代码28行结果:说明公有变量可以在类外被实例访问`
类外实例对象访问被保护的实例变量 _proectvars `代码29行解雇:说明保护变量可以在类外被实例访问`
在类方法中类变量 classVar `31行转10行:说明了类内可以访问公有类变量`
在类方法中访问公有实例变量 3 `31行转11行:说明类内可以访问公有的实例变量`
在类方法中访问类的私有变量 __objectprivate `31行转12行:说明类内可以访问私有变量`
在类方法中访问类的保护变量 _proectvars `31行转13行:说明类内可以访问保护变量`
在子类方法中访问父类的类变量 classVar `33行转16行:说明公有变量可以被继承`
在子类方法中访问父类的实例变量 3
在子类方法中访问父类的保护变量 _proectvars `33行转19行:说明保护变量可以被继承`
6、类型注解
6.1 类型注解的概念
python语言有别别的语言,在变量、方法形参以及返回值时,都不需要给你指定变量的数据类型,因为python的特性告诉你变量是没有数据类型,只有数据有类型。但我们在实际的开发过程中对变量还是有给你个设定默认类型,这个不是系统设定而是程序员设定。
类型注解存在的意义:比如有个方法的形参,程序员给与的设定是列表,函数体内就对列表进行操作,如果参数传进来的不是列表就会出问题,如果我们给与形参类型注解,在调用函数的时候ide平台会根据你的注解给与提示形参需要输入列表,便捷于程序开发。
类型注解,只是一个开发规范,系统没有做强制的要求。
6.2 变量类型注解
定义:
普通变量注解 name:str=‘lucy’
对象变量注解 stu: Student = Student()
数据容器注解:
# 容器类型详细注解
# 元组类型详细注解,需要将每一个元素都标记出来
# 字典类型详细注解,需要两个类型,第一个是key第二个是value
my_list: list[int] = [1, 2, 3]
my_tuple: tuple[str, int, bool] = ("itheima", 666, True)
my_set:set[int] = {1, 2, 3}
my_dict:dict[str, int] = {"itheima": 666}
6.3 方法类型注解
定义:
def func(data:int)->int:
return data
形参注解和变量注解一样。
返回值注解:->int
6.4 union联合类型注解
联合类型注解,比如列表里面元素有多种类型存在就需要union联合注解来注解。
使用联合注解,需要引入个包,具体如下:
from typing import Union
联合注解定义如下例子:
from typing import Union # 导入包和模块
my_utils:list[Union[int, str]] = [1, 2, "itheima"] # 列表的联合注解
def func(data:Union[int, str])->Union[int, str]: # 方法的联合注解
return data
7、多态
7.1 什么是多态
一个对象呈现处多种的形态叫做多态。
多态是基于类的继承的基础上才能呈现的。
python的特性形参没有指定类型的所以在多态上呈现出来没有其他语言那么明显,就拿c#语言来说,一个方法的形参是父类,然后由子类的实例对象作为实参来实现方法的工程,这样就是一个父类对象形成多个子类的功能形态叫做多态。
多态例子:
class Animal:
def speak(self): # 抽象方法
pass
class dog(Animal):
def speak(self):
print("这是狗仔在叫")
class cat(Animal):
def speak(self):
print("这是猫在叫")
def who_speak(animal:Animal):
animal.speak()
if __name__ == '__main__':
who_speak(dog()) # 输出:这是狗仔在叫
who_speak(cat()) # 输出:这是猫在叫
7.2 抽象类
抽象类概念:
python的抽象类概念没有其他语言那么复杂、那么限制,它非常简单存在抽象方法的类就是抽象类。
抽象方法概念:
抽象方法是在父类中定义的功能给子类复写功能,即父类中定义空方法(方法体是pass)给子类去复写方法的功能实现。
class Animal:
def speak(self):
pass
上面就是抽象类和抽象方法。
(十三)、闭包和装饰器
1、闭包
1.1 闭包概念
闭包(closure)指的是在函数内部定义了另外一个函数,并返回了这个内部函数作为函数对象,同时还保存了外层函数的状态信息。这个内部函数可以依赖外层函数的变量和参数,而且外层函数返回的是这个内部函数的引用。这种在函数内部定义函数并返回的方式称为闭包。
1.2 闭包的定义
# 以下四行就是闭包的定义
def outer(log):
def inner(msg): # 闭包特点:内嵌定义一个函数(函数内部定义了另外一个函数)
print(f"<{log}>{msg}<{log}>") # 使用到了外部函数的参数log,(依赖外层函数的变量和参数)
return inner # 返回的是内部函数的引用
func_outer=outer('福建')
func_outer('海边人') # 以下两行执行的结果都会依赖在外层函数下,所以程序未停止,外层函数一直在内存中以满足内存函数的使用。
func_outer('山里洞人') # 程序未结束,闭包的外函数将一直被内部函数使用。
输出结果:
<福建>海边人<福建>
<福建>山里洞人<福建>
注意:这个和变量作用域一样,python的解释器寻找变量是由内向外寻找,所以内部函数没有定义变量时,解释器找不到该变量时就会向外找,就会找到外层函数,如果一直找不到就会报错,当然了由于由内到外的顺序寻找变量的特性,所以外部函数寻找变量就不会到内层函数去找,所以内层函数定义的变量不会被外层函数所使用。
组成闭包的要素:
- 有个函数,内部定义了一个函数。
- 外部函数返回内部函数的对象。
- 外部函数的变量和参数被内部函数所依赖。
1.3 闭包的优点
闭包的存在有以下几个优点:
- 可以保留f函数的状态信息:由于内层函数保留了外层函数的状态信息,因此闭包可以用来创建一些在多次调用中保持状态的对象,例如装饰器。
- 可以让函数的参数更加灵活:某些函数的参数可能是固定的,但是有时候需要在函数调用过程中更改参数的值。闭包可以通过保存外层函数的参数和变量,让函数的参数更加灵活。
- 可以简化大型程序的代码结构:通过使用闭包,可以将大型程序拆分为多个小函数,并且它们之间可以共享变量和参数。
2、装饰器
2.1 装饰器的概念
装饰器是用来扩展一个函数功能的闭包,前提是不更改这个函数的内容、函数名、参数,同时也不改变函数的调用方式和调用代码而为函数增加新的功能。
下面我们用例子来说明装饰器的概念:
1)、这是一段常规的函数和函数被调用的过程
import time
def type_word():
print("我是个打字员,正在努力的打字")
time.sleep(1)
type_word()
2)、现在更改需求,执行函数的内容同时计算执行的时间,常规的做法1:更改函数内容如下:
import time
def type_word():
begin_times = time.time()
print("我是个打字员,开始努力的打字")
time.sleep(1)
end_times = time.time()
total_times=end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
type_word()
3)现在更改需求,执行函数的内容同时计算执行的时间,常规的做法2:更改函数内容如下:
def get_times(func):
begin_times = time.time()
func()
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
def type_word():
print("我是个打字员,开始努力的打字")
time.sleep(1)
get_times(type_word)
常规扩展功能的两个用法分析:
例子2)、为了对函数type_word进行增加功能,我们改变了函数type_word的内容,如果我们有多个函数需要增加同样的功能了话就要一一的修改过去。
例子3)、在不更改原函数内容的情况下,增加了一个函数计算时间解决扩展的问题,但调用函数不是原来的type_word,而是get_times。
4)采用闭包的方式来解决以上的需求
import time
def get_times(func):
def wrapper():
begin_times = time.time()
func()
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
return wrapper
def type_word():
print("我是个打字员,开始努力的打字")
time.sleep(1)
type_word=get_times(type_word) # 前的type_word并非是一个函数而是对象。
type_word()
5)上面这种用法显着很灵活,应用对象名和函数名同名的方式都能满足(不更改原函数,调用的时候还是使用原函数名),但是否还有更解决的写法呢?看下面例子:
import time
def get_times(func): # 这个就是装饰器
def wrapper():
begin_times = time.time()
func()
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
return wrapper
@get_times # 给type_word装饰(这就是装饰器的实现)
def type_word():
print("我是个打字员,开始努力的打字")
time.sleep(1)
type_word()
6)上面这个例子就是装饰器的简单例子,可以在装饰器中对函数的进行扩展;还有其他函数也要使用这个装饰器了话,就是直接在函数上方@装饰器名即可。
装饰器的前提就是建立装饰器,然后给函数带上装饰器,函数的扩展实在装饰器里,建立装饰器的前提就是建立闭包。
装饰器无论有没有调用函数,装饰器装饰了函数,在程序执行时就会执行,执行完后才会执行函数执行的内容。
2.2 装饰器的多种实现
装饰器根据功能不同可以有多少形式存在,具体如下例子说明。
2.2.1 带函数带参数的装饰器
import time
def get_times(func): # 这个就是装饰器
def wrapper(*args,**kwargs): # *args,**kwargs,可变参数和可变keyvalue参数,表示接收所有形式的参数都可以。
begin_times = time.time()
func(*args,**kwargs)
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
return wrapper
@get_times # 给type_word装饰(这就是装饰器的实现)
def type_word(a,b):
print("我是个打字员,开始努力的打字")
print(a*b)
time.sleep(1)
type_word(2,8)
2.2.2 装饰器带参数
携带参数的装饰器,闭包必须是三层函数。
import time
def sleep_times(num): # 这个闭包必须三层
def get_times(func): # 这个就是装饰器
def wrapper(*args, **kwargs):
begin_times = time.time()
func(*args, **kwargs)
time.sleep(num)
print(f"装饰器内延时{num}s")
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
return wrapper
return get_times
@sleep_times(3) # 这是带了参数的装饰器
def type_word(a,b):
print("我是个打字员,开始努力的打字")
print(a*b)
time.sleep(1)
type_word(2,8)
2.2.3 有返回值的函数的装饰
import time
def get_times(func): # 这个就是装饰器
def wrapper(*args,**kwargs):
begin_times = time.time()
func_return=func(*args,**kwargs) # 带有返回的函数
end_times = time.time()
total_times = end_times - begin_times
print("打印结束,整个的打印过程耗时:{:.2f}s".format(total_times))
return func_return
return wrapper
@get_times # 给type_word装饰(这就是装饰器的实现)
def type_word(a,b)->int:
print("我是个打字员,开始努力的打字")
time.sleep(1)
return a*b # 函数是带有返回的
result=type_word(2,8)
print(result)
2.2.4 函数被多个装饰器装饰
一个装饰器可以装饰多个函数,同时一个函数也能被多个装饰器所装饰,在函数定义的上面逐行写上装饰器即可。
执行顺序:
装饰器执行顺序:靠近函数的装饰器限制性以此类推。
函数执行的顺序:假设两个装饰器靠近函数的装饰器2(demo2),远点的是装饰器1(demo1),他们执行的书序是demo1(demo2(func())),这里的顺序只是装饰的扩展部分,函数只被执行一次而已。
2.2.5 类装饰器(不带参数)
基于类装饰器的实现,必须实现__call__ 和 __init__ 两个内置函数。
__init__ :接收被装饰函数;
__call__ :实现装饰逻辑。
class logger:
def __init__(self,func): # 接收函数
self.func=func
def __call__(self, *args, **kwargs):# 装饰器功能的实现
print(f"函数【{self.func.__name__}】开始运行……")
self.func(*args,**kwargs)
@logger
def say(str):
print(f"函数说了:{str}")
say("我是中国人")
输出结果:
函数【say】开始运行……
函数说了:我是中国人
2.2.6 类装饰器(带参数)
带参数的装饰器装饰函数使用三层,第一次带参数,第二层带函数,第三层实现装饰逻辑,带参数的类装饰器也一样分三个步骤:
__init__ :接收装饰器的参数;
__call__ :两层嵌套函数(由外到里)分别是接收,实现装饰逻辑。
class logger:
def __init__(self,level="INFO"): # 接收参数
self.level=level
def __call__(self, func): # 接收函数
def wapper(*args,**kwargs): # 实现装饰逻辑
print(f"「{self.level}」函数【{func.__name__}】开始运行……")
func(*args,**kwargs)
return wapper
@logger(level='WARNING')
def say(str):
print(f"函数说了:{str}")
say("我是中国人")