python基础补充讲解(1)
在前面的讲述里,已经给大家介绍过Python的常用的一些基本语法。但是在处理复杂一些的问题时,这些基本的功能就有些不够用了。因此在开始正式的数据结构之旅前,需要补充一些Python的另一些没有那么常用的功能。
标识符、对象和赋值语句
python是一种解释语言,命令通常需要在python解释器的软件中执行。并且python是一种面对对象的动态语言,我们之前介绍的一些常用的数据类型,如list,int,float,都是python的内置类。我们用到的赋值语句,其实就是类的实例化了。下面我们来举一个简单的例子:
t=98
f=98
# 分别查看t和f的地址
print(id(t),'\n',id(f),sep='')
# 输出结果:140712455058256
# 140712455058256
由这个例子可以看出,我们给两个不同的标识符(t和f)分别赋给相同的值98,而这两个变量的地址都是一样的。下面我们对代码修改一下:
t=98
f=98
t+=5
print(id(t),'\n',id(f),sep='')
# 输出结果为:140712843490288
# 140712843490128
这次运行的结果,t和f的地址不相同了,但同时与上一次相比,f的地址也变化了。这说明解释器会先给数字分配一个随机地址,然后标识符找到数字的地址。因此尽管标识符名称不同,他们的赋值过程都是找到对应值的地址来实现的,这便与c语言不同。我们的加法本质上也是找到数字103的地址并重新赋值给t,从而使t的地址发生了变化。
创建和使用对象
创建一个类的新实例的过程被称为实例化,上面的代码就是一种对int类的实例化。
通常来说,一个类有属性和方法两个部分组成。一般来说,python通过调用类的构造函数来实例化对象。构造函数可以理解成一个会自动运行的方法。
对于一个实例化的对象,我们可以使用加点的形式调用它的方法。下面我们来看个具体的例子:
a=['5','7','1','3','6','z','a'] # 实例化一个列表
a.sort() # 调用list类的sort方法排序
# sort方法返回值是None,所以如果直接写成print(a.sort()),将会输出None
print(a)
# 输出结果为:['1', '3', '5', '6', '7', 'a', 'z']
当使用一个类的方法时,了解他的行为很重要。一些方法返回一个对象的状态信息,但并不改变其状态。这些方法被称为访问器,如我们刚提到的sort方法,就会改变实例化的列表类状态。这些方法被称为应用程序或更新方法。
python的内置类
python中有很多内置类,如果类的每个对象在实例化时有一个固定的值,并且随后也不会发生改变,就称它为不可变类。例如,一个float类实例是不可变的。我们对其进行四则运算,只是创建了一个新的float类。
类 | 描述 | 是否不可变 |
---|---|---|
bool | 布尔值 | 是 |
int | 整数(大小不限) | 是 |
float | 浮点数 | 是 |
list | 对象可变序列 | 否 |
tuple | 对象不可变序列 | 是 |
str | 字符串 | 是 |
set | 不同对象的无序集合 | 否 |
frozenset | 集合(set)的不可变形式 | 是 |
dict | 关联映射(字典) | 否 |
下面对部分类做一下解释:
-
因为python是动态语言,所以list尽管类似于c语言中的数组,然而list可以根据储存内容的长度调整列表长度,在创建之前也不用声明列表的长度。tuple和dic类也有相似的设定。
-
str类用于存储不变的字符序列,它的内部标识更加紧凑。当我们使用单引号进行引用时,如果需要在字符内部添加引号,则需要使用转义字符,如下所示:
print('Don\'t worry') # 输出为:Don't worry
str类的成员也可以使用下标查找具体位置的值:
str_='Don\'t worry' print(str_[3]) # 输出为:'
同时,如果我们希望一口气打印很多行文字,而不是使用换行符\n,就可以使用’''或"“”,示例如下:
str_="""please don't worry""" print(str_) print(str_[1]) # 输出为:please # don't # worry # l
-
set类和frozenset类
set类代表一个集合的数学概念,集合中没有重复的元素,而且这些元素之间没有内在的联系。使用集合的主要优点是他有一个高度的优化方法来检查特定的元素是否包含在集合内。frozenset类是set的一种不可变形式,因此set类也可以由frozenset类的实例组成。
我们可以将一个str类的实例转换为set类,但是会遵守set类的原则,即不会出现重复元素:s=set('hello') print(s) # 结果为:{'o', 'l', 'e', 'h'}
由于历史原因,如果我们使用{}来创建空集合是行不通的,这样只会创建一个空字典,我们需要使用set()来创建空集合:
s1={} s2=set() print(type(s1)) print(type(s2)) # 输出结果为:<class 'dict'> # <class 'set'>
-
dict类
dict类接受一个现有映射作为参数,也可以接收没有显性键值对应关系的数据:s=dict([('ga','Irish'),('de','German')]) print(s) # 结果为:{'ga': 'Irish', 'de': 'German'}
表达式、运算符和优先级
逻辑运算符
逻辑运算包含与、或、非,其运算结果为布尔值:
运算符 | 含义 |
---|---|
not | 逻辑非 |
and | 逻辑与 |
or | 逻辑或 |
and和or运算符是短路保护的,举个例子:
if a or b or c:
...
该表达式会从左到右执行,如果a条件为真,则表达式不会继续运算,直接按真处理,执行if内的语句,但如果a条件为假,则会继续判断b是否为真,以此类推。
另外,还有一种表达式:
val1 if cond else val2
这个表达式类似于c语言中的a=cond?val1:val2表达式,示例如下:
a=3
b=5
c=3 if a>b else 2
print(c)
# 输出结果为:2
相等运算符
python支持以下运算符去测试两个概念的相等性:
运算符 | 含义 |
---|---|
is | 判定前后是否为同一实体 |
is not | 判定前后是否为不同实体 |
== | 判定前后两者是否等价 |
!= | 判定前后两者是否不等价 |
在很多情况下,‘is’和’=='判断结果是一样的,不过对于更一般的实体,举个例子:
a1=5
b1=5
print(a1==b1)
print(a1 is b1)
a2=float(5)
b2=float(5)
print(a2==b2)
print(a2 is b2)
# 输出为:True
# True
# True
# False
首先看a1和b1,这样的复制方式我们之前讲过,a1和b1只是标识符,他们实际代表的都是整形数字5的的地址,所以无论是用is判断还是用==判断,结果都是True。而第二种赋值方式,相当于创建了两个实例,彼此的地址并不相同,既a和b并非指向同一对象,is也就会输出False了。然而双等号的判断是判断标识符对应的实际值是否相同。也因判断结果是True。
然而,is的比较也并非全依赖于地址,比如:
a={'i','s'}
b={'s','i'}
print(a is b)
print(a==b)
print(id(a))
print(id(b))
# 输出为:False
# True
# 2324338066816
# 2324381943168
可以看出,针对于集合这种特殊的类型,由于其内部元素没有顺序的特性,因此从数学意义上讲,两个集合只要满足所包含的元素大小和数量相同,即应判断为相等。这时,用==判断就会出现问题。因此判断集合是否相等,要用is(注意:对于集合而言,不同的地址不会影响is判断的结果)
比较运算符
数据类型可以通过以下运算符定义有一个自然次序:
运算符 | 含义 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
这些运算符可以对比项同类型之间的数据大小。与c语言不同的是python支持使用以下方式进行判断:
a=3
b=1
print(1<=(a+b)<5)
# 输出结果为:True
当然,如果我们要使用比较运算符比较两种不同类型数据的大小,会收获一个异常:
print(5<'hello')
算术运算符
python中支持的算术运算符很多和c语言一样,但是也有些许不同:
运算符 | 含义 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
// | 整数除法 |
% | 模运算符 |
其中,除法与C语言中差距最大。对于‘/’除法运算符,python语言中只需要除数与被除数之一是float类型,那么结果就默认为float类型,也不会只保留整数部分。对于‘//’的除法运算,则会只保留除法结果的整数部分。当除数与被除数均为整数类型时,‘/’运算符和‘//’运算符的结果是一样的。需要注意的是,python中的float类型相当于C语言中的double类型类似,而C语言中的float类型在python中没有真正对应的类型。
位运算符
运算符 | 含义 |
---|---|
~ | 取反(前缀一元运算符) |
& | 按位与 |
l | 按位或 |
^ | 按位异或 |
<< | (二进制下)左移位,右侧用0填充 |
>> | (二进制下)右移位,按符号位填充 |
这些运算符的符号与C语言略有差异,但含义大体相同,这里不做过多介绍。
序列运算符
python的内置类型的序列(str、tuple和list)都支持以下的操作符语法:
运算符 | 含义 |
---|---|
s[j] | 索引下标为j的元素 |
s[start:stop] | 切片得到索引为[start,stop)的序列 |
s[start:stop:step] | 类似上一个切片方法,只不过取值步长为step |
s+t | 序列连接 |
k*s | 序列连接,连接k个s的内容为一个新列表 |
val in s | 检查val是否在s内 |
val not in s | 检查val是否不在s内 |
下面我们举例说明:
a=[1,2,3,4,5,6,7,8,9]
b=[1,10,11,12]
c=[3,5,9]
print(a[2:8])
print(a[2:8:2])
print(a+b)
print(3*b)
print(3 in a)
print(c not in a)
print(b>a)
输出结果为:
可以看到,对于列表而言,使用c in a这种判断方式无法判断c是否含于a的。a+b可以生成一个新的列表,即将b中的元素放在a中的元素后面。再观察一下下面的代码:
a='example'
c='amp'
print(c in a)
# 输出为:True
可以看到,对于字符串来说,可以使用c in a判断c是否是a的字串。
Python中序列的比较都是基于字典顺序,即一个元素接着一个元素的比较,直到找到第一个不同的元素或者便利完整个序列,表达式如下:
表达式 | 含义 |
---|---|
s==t | 相等(s与t每个元素对应相等) |
s!=t | s与t不相等 |
s<t | 字典序的小于 |
s<=t | 字典序的小于等于 |
s>t | 字典序的大于 |
s>=t | 字典序的大于等于 |
在之前的代码里有过相关的例子,这里不做赘述。
集合和字典的运算符
集合
set和frozenset支持以下操作:
表达式 | 含义 |
---|---|
key in s | 检查key是否是s的成员 |
key not in s | 检查key是否不是s的成员 |
s1==s2 | s1与s2等价 |
s1!=s2 | s1与s2不等价 |
s1<=s2 | s1是s2的子集 |
s1<s2 | s1是s2的真子集 |
s1>=s2 | s2是s1的子集 |
s1>s2 | s2是s1的真子集 |
s1ls2 | s1与s2的并集 |
s1&s2 | s1与s2的交集 |
s1-s2 | s1与s2的差集 |
s1^s2 | (s1并s2) - (s1交s2) |
由于集合的特殊性,比较运算符不是以字典顺序进行比较的,大家可以自行尝试。
字典
字典与集合和一样,它的元素没有明确定义的顺序。此外,对于字典,子集的概念并没有实际意义。也因此,字典并不支持比较运算。然而,字典可以支持等价的概念,如果两个字典包含相同的键值以及对应关系,可以认为两个字典等价。
表达式 | 含义 |
---|---|
d[key] | 查找d字典中key键所对应的值 |
d[key]==value | 判断d字典中key键对应的值是否等于value |
del d[key] | 从字典中删除key对应的键值对 |
key in d | 检查key是否是d的键 |
key not in d | 检查key是否不是d的键 |
d1==d2 | d1是否等价于d2 |
d1!=d2 | d1是否不等价于d2 |
我们看一些具体的例子:
d1={'zhang':100,'wang':98,'li':96,'zhao':87}
d2={'wang':98,'zhang':100,'li':96,'zhao':87}
print(d1['zhang']==98)
print('zhang' in d1)
print(d1==d2)
# 运行结果为:False
# True
# True
扩展赋值
python中不支持++运算符,但是+=运算符是支持的。如果对一个序列类型(如list)使用+=运算,会对原内容进行扩展:
a1=[1,2,3]
a2=[1,2,3]
b=[4,5,6]
print(id(a1))
print(id(a2))
a1+=b
print(id(a1))
a2=a2+b
print(id(a2))
b+=6,7
print(a1,'\n',a2,'\n',b,sep='')
运行结果为:
注意,虽然a1和a2结果相同,但具体操作下有些不同。使用a1+=b语句,实质上是在原列表a1的末位添加b列表的内容,完成扩展后,a1的地址没有变化。而使用a2=a2+b语句,则是创建一个新的列表,并将其赋值为a与b列表拼接后的内容。这也可以从a2列表地址的变动上体现出来。
复合表达式和运算符的优先级
优先等级 | 表达式/运算符 | 含义 |
---|---|---|
1 | expr.member | 成员访问 |
2 | expr(…);expr[…] | 函数/方法调用;取下标/切片 |
3 | ** | 乘方 |
4 | +expr;-expr;~expr | 一元运算符 |
5 | *;/ | 乘除 |
6 | +;- | 加减 |
7 | <<;>> | 按位移位 |
8 | & | 按位与 |
9 | ^ | 按位异或 |
10 | l | 按位或 |
11 | is,is not;!=,==;<等 | 比较/包含 |
12 | not | 逻辑非 |
13 | and | 逻辑与 |
14 | or | 逻辑或 |
15 | val1 if cond else val2 | 条件判断 |
16 | =,+=,-=等 | 赋值 |
如果需要进行复合运算,一定要注意表达式的优先级。如果拿不准或者记不住,可以通过小括号来规范运算顺序。
这次的补充就先到这里了,本次主要补充讲述了python的语言特点,介绍了之前没有涉及的数据类型以及规范和补充了运算符的使用。后面还会继续补关于python的基础知识,并尽快和大家一起学习数据结构,期待大家的支持。