第2章 万丈高楼平地起:运算符、表达式与内置对象
2.1 Python常用内置对象
对象是Python中最基本的概念之一,在Python中一切都是对象,除了整数、实数、复数、字符串、列表、元组、字典、集合,还有zip、map、enumerate、filter等对象,函数和类也是对象。常用的Python内置对象如表2-1所示。
表2-1 Python内置对象
2.1.1 常量与变量
在表2-1中,第3列的示例除了最后4行之外,其他都是合法的Python常量。所谓常量,一般是指不需要改变也不能改变的字面值,例如一个数字3,又例如一个列表[1,2,3],都是常量。与常量相反,变量的值是可以变化的,这一点在Python中更是体现得淋漓尽致。在Python中,不需要事先声明变量名及其类型,直接赋值即可创建任意类型的对象变量。不仅变量的值是可以变化的,变量的类型也是随时可以发生改变的。例如,下面第一条语句创建了整型变量x,并赋值为3。
>>>x=3 #整型变量 >>>type(x) #内置函数type()用来查看变量类型 < class'int'> >>>type(x)==int True >>>isinstance(x, int) #内置函数isinstance()用来测试变量是否为指定类型 True
下面的语句创建了字符串变量x,并赋值为’Hello world.',之前的整型变量x不复存在。
>>>x='Hello world.'#字符串变量
下面的语句创建了列表对象x,并赋值为[1,2,3],之前的字符串变量x也就不再存在了。这一点同样适用于元组、字典、集合和其他Python任意类型的对象,包括自定义类型的对象。
>>>x=[1,2,3]
Python采用基于值的内存管理模式。赋值语句的执行过程是:首先把等号右侧表达式的值计算出来,然后在内存中寻找一个位置把值存放进去,最后创建变量并指向这个内存地址。Python中的变量并不直接存储值,而是存储了值的内存地址或者引用,这也是变量类型随时可以改变的原因。
虽然不需要在使用之前显式地声明变量及其类型,但Python是一种不折不扣的强类型编程语言,Python解释器会根据赋值运算符右侧表达式的值来自动推断变量类型。其工作方式类似于“状态机”,变量被创建以后,除非显式修改变量类型或删除变量,否则变量将一直保持之前的类型。
如果变量出现在赋值运算符或复合赋值运算符(例如+=、*=等)的左边则表示创建变量或修改变量的值,否则表示引用该变量的值,这一点同样适用于使用下标来访问列表、字典等可变序列以及自定义对象中元素的情况。例如:
>>>x=3 #创建整型变量 >>>print(x**2) #访问变量的值 9 >>>x+=6 #修改变量的值 >>>x=[1,2,3] #创建列表对象 >>>x[1]=5 #修改列表元素值 >>>print(x) #输出显示整个列表 [1,5,3] >>>print(x[2]) #输出显示列表指定元素 3
在Python中定义变量名的时候,需要注意以下问题。
(1)变量名必须以字母或下画线开头,但以下画线开头的变量在Python中有特殊含义,请参考第6章内容。
(2)变量名中不能有空格或标点符号(括号、引号、逗号、斜线、反斜线、冒号、句号、问号等)。
(3)不能使用关键字作为变量名,Python关键字的介绍请见2.3节。要注意的是,随着Python版本的变化,关键字列表可能会有所变化。
(4)不建议使用系统内置的模块名、类型名或函数名以及已导入的模块名及其成员名作为变量名,这会改变其类型和含义,甚至会导致其他代码无法正常执行。可以通过dir(__builtins__)查看所有内置对象名称。
(5)变量名对英文字母的大小写敏感,例如student和Student是不同的变量。
2.1.2 数字
在Python中,内置的数字类型有整数、实数和复数,借助于标准库fractions中的Fraction对象可以实现分数及其运算,而fractions中的Decimal类则实现了更高精度的运算。
1.内置的整数、实数与复数
Python支持任意大的数字,具体可以大到什么程度仅受内存大小的限制。由于精度的问题,对于实数运算可能会有一定的误差,应尽量避免在实数之间直接进行相等性测试,而是应该以两者之差的绝对值是否足够小作为两个实数是否相等的依据。在数字的算术运算表达式求值时会进行隐式的类型转换,如果存在复数则都变成复数,如果没有复数但是有实数就都变成实数,如果都是整数则不进行类型转换。
>>>9999**99 #这里**是幂乘运算符,等价于内置函数pow() 9901483535267234876022631247532826255705595288957910573243265291217948378940535 1346442217682691643393258692438667776624403200162375682140043297505120882020498 0098735552703841362304669970510691243800218202840374329378800694920309791954185 1177984343295912121591062986999386699080675733747243312089424255448939109100732 05049031656789220889560732962926226305865706593594917896276756396848514900989999 >>>0.3+0.2 #实数相加 0.5 >>>0.4-0.1 #实数相减,结果稍微有点偏差 0.30000000000000004 >>>0.4-0.1==0.3 #应尽量避免直接比较两个实数是否相等 False >>>abs(0.4-0.1-0.3)<1e-6 #这里1e-6表示10的-6次方 True
Python内置支持复数类型及其运算,并且形式与数学上的复数完全一致。例如:
>>>x=3+4j #使用j或J表示复数虚部 >>>y=5+6j >>>x+y #支持复数之间的加、减、乘、除以及幂乘等运算 (8+10j) >>>x*y (-9+38j) >>>abs(x) #内置函数abs()可用来计算复数的模 5.0 >>>x.imag #虚部 4.0 >>>x.real #实部 3.0 >>>x.conjugate() #共轭复数 (3-4j)
Python 3.6.x支持在数字中间位置使用单个下画线作为分隔来提高数字的可读性,类似于数学上使用逗号作为千位分隔符。在Python数字中单个下画线可以出现在中间任意位置,但不能出现在开头和结尾位置,也不能使用多个连续的下画线。
>>>1_000_000 1000000 >>>1_2_3_4 1234 >>>1_2+3_4j (12+34j) >>>1_2.3_45 12.345
2.分数
Python标准库fractions中的Fraction对象支持分数运算,还提供了用于计算最大公约数的gcd()函数和高精度实数类Decimal,这里重点介绍Fraction对象。
>>>from fractions import Fraction >>>x=Fraction(3,5) #创建分数对象 >>>y=Fraction(3,7) >>>x Fraction(3,5) >>>x**2 #幂运算 Fraction(9,25) >>>x.numerator #查看分子 3 >>>x.denominator #查看分母 5 >>>x+y #支持分数之间的四则运算,自动进行通分 Fraction(36,35) >>>x-y Fraction(6,35) >>>x*y Fraction(9,35) >>>x/y Fraction(7,5) >>>x*2 #分数与数字之间的运算 Fraction(6,5) >>>Fraction(3.5) #把实数转换为分数 Fraction(7,2)
3.高精度实数
标准库fractions和decimal中提供的Decimal类实现了更高精度的运算。
>>>from fractions import Decimal >>>1/9 #内置的实数类型 0.1111111111111111 >>>Decimal(1/9) #高精度实数 Decimal('0.111111111111111104943205418749130330979824066162109375') >>>1/3 0.3333333333333333 >>>Decimal(1/3) Decimal('0.333333333333333314829616256247390992939472198486328125') >>>Decimal(1/9)+Decimal(1/3) Decimal('0.4444444444444444197728216750')
从Python 3.6.x开始,Decimal对象提供了一个新的方法as_integer_ratio(),可以把实数转换成两个整数,这两个整数的商恰好等于该实数。
>>>from decimal import Decimal >>>Decimal('-3.14').as_integer_ratio() (-157,50)
2.1.3 字符串
在Python中,没有字符常量和变量的概念,只有字符串类型的常量和变量,单个字符也是字符串。使用单引号、双引号、三单引号、三双引号作为定界符(delimiter)来表示字符串,并且不同的定界符之间可以互相嵌套。Python 3.x全面支持中文,中文和英文字母都作为一个字符对待,甚至可以使用中文作为变量名。除了支持使用加号运算符连接字符串以外,Python字符串还提供了大量的方法支持查找、替换、排版等操作。很多内置函数和标准库对象也支持对字符串的操作,将在第7章进行详细介绍。这里先简单介绍一下字符串对象的创建和连接。
>>>x='Hello world.' #使用单引号作为定界符 >>>x="Python is a great language." #使用双引号作为定界符 >>>x='''Tom said, "Let's go."''' #不同定界符之间可以互相嵌套 >>>print(x) Tom said, "Let's go." >>>x='good'+'morning' #连接字符串 >>>x 'good morning' >>>x='good''morning' #连接字符串,仅适用于字符串常量 >>>x 'good morning' >>>x='good' >>>x=x'morning' #不适用于字符串变量 SyntaxError: invalid syntax >>>x=x+'morning' #字符串变量之间的连接可以使用加号 >>>x 'good morning'
Python 3.x除了支持Unicode编码的str类型字符串之外,还支持字节串类型bytes。对str类型的字符串调用其encode()方法进行编码得到bytes字节串,对bytes字节串调用其decode()方法并指定正确的编码格式则得到str字符串。例如:
>>>type('Hello world') #默认字符串类型为str < class'str'> >>>type(b'Hello world') #在定界符前加上字母b表示字节串 < class'bytes'> >>>'Hello world'.encode('utf-8') #使用UTF-8编码格式进行编码 b'Hello world' >>>'Hello world'.encode('gbk') #使用gbk编码格式进行编码 b'Hello world' >>>’董付国’.encode('utf-8') #对中文进行编码 b'\xe8\x91\xa3\xe4\xbb\x98\xe5\x9b\xbd' >>>_.decode('utf-8') #一个下画线表示最后一次正确输出结果 '董付国’ >>>’董付国’.encode('gbk') b'\xb6\xad\xb8\xb6\xb9\xfa' >>>_.decode('gbk') #对bytes字节串进行解码 '董付国’
2.1.4 列表、元组、字典、集合
列表、元组、字典和集合是Python中常用的序列类型,很多复杂的业务逻辑最终还是由这些基本数据类型来实现。表2-2比较了这几种结构的区别。
表2-2 列表、元组、字典、集合的对比
下面的代码简单演示了这几种对象的创建与使用,更详细的介绍请参考第3章。
>>>x_list=[1,2,3] #创建列表对象 >>>x_tuple=(1,2,3) #创建元组对象 >>>x_dict={'a':97, 'b':98, 'c':99} #创建字典对象 >>>x_set={1,2,3} #创建集合对象 >>>print(x_list[1]) #使用下标访问指定位置的元素 2 >>>print(x_tuple[1]) #元组也支持使用序号作为下标 2 >>>print(x_dict['a']) #字典对象的下标是“键” 97 >>>3 in x_set #成员测试 True
除了这几种结构之外,前面介绍的字符串也具有相似的操作,详见第7章。另外,Python还提供了range、map、zip、filter、enumerate、reversed等大量迭代对象(迭代对象可以理解为表示数据流的对象,每次返回一个数据)。这些迭代对象大多具有与Python序列相似的操作方法,比较大的区别在于这些迭代对象大多具有惰性求值的特点,仅在需要时才给出新的元素,减少了对内存的占用,详见2.4节。