Python的语法非常简练,因此用Python编写的程序可读性强、容易理解。本章将向读者介绍Python的基本语法及其概念,并将其与目前流行的开发语言进行比较。Python的语法与其他高级语言有很多不同,Python使用了许多标记作为语法的一部分,例如空格缩进、冒号等。
本章的知识点:
·Python文件的扩展名
·Python的编码规则
·数据类型
·变量和常量的定义和使用
·运算符及表达式
Python文件类型分为3种,分别是源代码、字节代码、优化代码。这些代码可以直接运行,不需要进行编译或者链接。这正是Python这门语言的特性,Python的文件通过Python解释器解释运行。Windows中有python.exe与pythonw.exe,一般安装在路径C:\Python33中,当然也可以改变它的路径,只要保证环境变量设置正确即可。在*nix系统中,Python解释器被安装在目标机器的/usr/local/bin/python目录下,将/usr/local/bin路径放进shell的搜索路径中,即可通过python命令在终端调用。
2.1.1 源代码
Python源代码的扩展名以py结尾,可在控制台下运行。Python语言写的程序不需要编译成二进制代码,可以直接运行源代码。pyw是Windows下开发图形用户接口(Graphical user interface)的源文件,作为桌面应用程序的后缀名。这种文件是专门用于开发图形界面的,由pythonw.exe解释运行。以py和pyw为后缀名的文件可以用文本工具打开,并修改文件的内容。
2.1.2 字节代码
Python源文件编译后生成pyc后缀的文件,pyc是编译过的字节文件,这种文件不能使用文本编辑工具打开或修改。pyc文件是与平台无关的,因此Python的程序可以运行在Windows、UNIX、Linux等操作系统上。py文件直接运行后即可得到pyc类型的文件,或通过脚本生成该类型的文件。下面这段脚本可以把hello.py编译为hello.pyc。
import py_compile py_compile.compile('hello.py')
保存此脚本,运行后即可得到hello.pyc文件。
2.1.3 优化代码
扩展名为pyo的文件是优化过的源文件,pyo类型的文件需要用命令行工具生成。pyo文件也不能使用文本编辑工具打开或修改。下面把hello.py编译成hello.pyo。
(1)启动命令行窗口,进入hello.py文件所在的目录。例如:
cd /D D:\developer\python\example\02\2.1
D:\developer\python\example\02\2.1是笔者设置的hello.py文件所在的目录,读者可根据自己的环境进行修改。
(2)在命令行中输入python-O-m py_compile hello.py,并按回车键。
python -O –m py_compile hello.py
【代码说明】
·参数“-O”表示生成优化代码。
·参数“-m”表示把导入的py_compile模块作为脚本运行。编译hello.pyo需要调用py_compile模块的compile()方法。
·参数“hello.py”是待编译的文件名。
最后,查看hello.py文件所在的目录,此时目录中生成了一个名为hello.pyo的文件。
Python语言有自己独特的编码规则,包括命名规则、代码书写规则等。本节将详细介绍Python中常用的规则,并解释这些规则的原理和由来。
2.2.1 命名规则
Python语言有一套自己的命名规则,用户也可以借鉴Java语言的命名规则形成自己命名的规则。命名规则并不是规定,只是一种习惯用法。下面介绍几个常见规范。
1.变量名、包名、模块名
变量名、包名、模块名通常采用小写,可使用下划线,示例如下。
01 # 变量、模块名的命名规则 02 # Filename: ruleModule.py 03 04 _rule = "rule information"
【代码说明】
·第2行代码声明模块的名称,模块名采用小写。也可以不指定模块名,以py后缀的文件就是一个模块。模块名就是文件名。
·第4行代码定义了一个全局变量_rule。
2.类名、对象名
类名首字母采用大写,对象名采用小写。类的属性和方法名以对象作为前缀。类的私有变量、私有方法以两个下划线作为前缀。下面这段代码演示了类的定义和实例化的规范写法。
01 class Student: # 类名大写 02 __name = "" # 私有实例变量前必须有两个下划线 03 def __init__(self, name): 04 self.__name = name # self相当于Java中的this 05 def getName(self): # 方法名首字母小写,其后每个单词的首字母大写 06 return self.__name 07 08 if __name__ == "__main__": 09 student = Student("borphi") # 对象名小写 10 print(student.getName())
【代码说明】
·第1行代码定义了一个名为Student的类,类名首字母大写。
·第2行代码定义了一个私有的实例变量,变量名前有两个下划线。
·第4行代码使用self前缀说明__name变量属于Student类。
·第5行代码定义了一个公有的方法,方法名首字母小写,其后的单词Name首字母大写。函数的命名规则和方法名相同。
·第9行代码创建了一个student对象,对象名小写。
说明 关于面向对象的知识会在第9章详细介绍,这里读者只需要知道类、对象、属性以及方法的书写方式即可。
3.函数名
函数名通常采用小写,并用下划线或单词首字母大写增加名称的可读性,导入的函数以模块名作前缀。下例中,为了演示导入函数前缀写法,使用了生成随机数的模块random。该模块有一个函数randrange()。该函数可以根据给定的数字范围生成随机数。randrange()声明如下所示:
randrange(start, stop[, step])
【代码说明】
·参数start表示生成随机数所在范围的开始数字。
·参数stop表示生成随机数所在范围的结束数字,但不包括数字stop。
·参数step表示从start开始往后的步数。生成的随机数在[start,stop-1]的范围内,取值等于start+step。
例如:
randrange(1, 9, 2)
随机数的范围在1、3、5、7之间选取。下面这段代码演示了函数的规范写法,其中定义了一个compareNum(),该函数用于比较两个数字的大小,并返回对应的结果。
01 # 函数中的命名规则 02 import random 03 04 def compareNum(num1, num2): 05 if(num1 > num2): 06 return 1 07 elif(num1 == num2): 08 return 0 09 else: 10 return -1 11 num1 = random.randrange(1, 9) 12 num2 = random.randrange(1, 9) 13 print( "num1 =", num1) 14 print ("num2 =", num2) 15 print (compareNum(num1, num2))
【代码说明】
·第2行代码导入了random模块。
·第4行代码定义了一个函数compareNum(),参数num1、num2为待比较的两个变量。
·第5行到第10行代码比较两个数的大小,返回不同的结果。
·第11、12行代码调用random模块的randrange()函数,返回两个随机数。
·第13、14行代码输出随机数,不同的机器、不同的执行时间得到的随机数均不相同。
·第15行代码调用compareNum(),并把产生的两个随机数作为参数传入。
良好命名可以提高编程效率,可以使代码阅读者在不了解文档的情况下,也能理解代码的内容。下面以变量的命名为例说明如何定义有价值的名称。许多程序员对变量的命名带有随意性,如使用i、j、k等单个字母。代码阅读者并不知道这些变量的真实含义,需要阅读文档或仔细查看源代码才能了解其含义。下面是一个命名不规范的例子。
01 # 不规范的变量命名 02 sum = 0 03 i = 2000 04 j = 1200 05 sum = i + 12 * j
【代码说明】这段代码定义了一个求和变量sum,以及两个变量i、j。如果只看代码片段,并不知道运算的含义是什么,需要通读整个函数或功能模块才能理解此处表达式的含义。
下面是一个良好命名的例子。
01 # 规范的变量命名 02 sumPay = 0 03 bonusOfYear = 2000 04 monthPay = 1200 05 sumPay = bonusOfYear + 12 * monthPay
【代码说明】bonusOfYear表示年终奖金、monthPay表示月薪,因此sumPay表示全年的薪水。命名良好的变量可以节省阅读程序的时间,更快地理解程序的含义。
注意 变量的命名应尽可能地表达此变量的作用,尽量避免使用缩写,以至于任何人都能理解变量名的含义。不用担心变量名的长度,长的变量名往往能更清楚地表达意思。
以上讨论的命名方式同样适用于模块名、类名、方法名、属性名等。命名规则会带来很多益处。统一命名规则便于开发团队合作开发同一个项目;便于统一代码的风格,理解不同程序员编写的代码;命名规范的变量名使函数的内容更容易被理解;避免项目中随意命名变量的情况,促进程序员之间的交流。规则并不是绝对的,统一规则、表达清楚名称的含义才是制定规则的原因。
2.2.2 代码缩进与冒号
代码缩进是指通过在每行代码前输入空格或制表符的方式,表示每行代码之间的层次关系。任何编程语言都需要用代码缩进清晰程序的结构,采用代码缩进的编程风格有利于代码的阅读和理解。对于C、C++、Java等语言,代码缩进只是作为编程的一种良好习惯而延承下来。对于Python而言,代码缩进是一种语法,Python语言中没有采用花括号或begin…end…分隔代码块,而是使用冒号和代码缩进区分代码之间的层次。
使用IDE开发工具或EditPlus等编辑器书写代码时,编辑器会自动缩进代码、补齐冒号,提高编码效率。下面这段代码中的条件语句采用了代码缩进的语法。
01 x = 1 02 if x == 1: 03 print( "x =", x) # 代码缩进 04 else: 05 print( "x =", x) # 代码缩进 06 x = x + 1 # 代码缩进 07 print ("x =", x)
【代码说明】
·第1行代码创建了变量x,并赋值为1。在赋值运算符的两侧各添加一个空格,这是一种良好的书写习惯,提高了程序的可读性。
·第2行代码使用了条件语句if,判断x的值是否等于1。if表达式后输入了一个冒号,冒号后面的代码块需要缩进编写。本行代码与第1行代码处于同一个层次,直接从最左端书写代码。
·第3行代码表示x的值等于1时输出的结果。当if条件成立时,程序才能执行到第3行,所以第3行代码位于第2行代码的下一个层次。在编码时,首先在最左端输入4个空格或制表键(不建议),然后再书写print语句。输出结果:
x = 1
·第4行代码的else关键字后是一段新的代码块。当x的值不等于1时,程序将执行第5、6行代码。
·第5、6行代码采用缩进式的代码风格。
·第7行代码输出结果:
x = 1
Python对代码缩进要求非常严格。如果程序中不采用代码缩进的编码风格,将抛出一个IndentationError。下面这段代码是错误的编码方式。
01 x = 0 02 if x == 1: 03 print( "x =", x) 04 else: 05 print( "x =", x) # 代码缩进 06 x = x + 1 # 代码缩进 07 print ("x =", x)
【代码说明】第3行没有缩进代码,python不能识别出代码的层次关系,python误认为if x==1:语句后没有代码块。代码运行后输出如下错误:
IndentationError: expected an indented block
注意 如果缩进的代码前只有一个空格或几个制表符也是符合语法的,但是这种写法并不推荐。最佳的方式是编码前统一代码的书写规则,所有代码前的空格数保持一致,最好使用4个空格缩进。
每行代码缩进的情况不同,代码执行的结果也不同。例如,前文提到的hello.py,else子句包含的两条语句都采用了代码缩进的格式,这两条语句组成了一个代码块。如果把hello.py的代码修改为如下的内容,则变量x的值将有所变化。
01 x = 1 02 if x == 1: 03 print( "x =", x) # 代码缩进 04 else: 05 print( "x =", x) # 代码缩进 06 x = x + 1 07 print( "x =", x)
【代码说明】
·第6行代码与第1行代码处于同一个层次,是主程序必须执行的代码,因此变量x的值为2。
·第7行代码的输出结果如下。
x = 2
注意 当程序出现问题时,程序员首先要检查代码的书写格式,是否因为代码缩进的问题导致了不期望的计算结果。
2.2.3 模块导入的规范
模块是类或函数的集合,用于处理一类问题。模块的导入和Java中包的导入的概念很相似,都使用import语句。在Python中,如果需要在程序中调用标准库或其他第三方库的类,需要先使用import或from…import…语句导入相关的模块。
1.import语句
下面这段代码使用import语句导入sys模块,并打印相关内容。
01 # 规范导入方式 02 import sys 03 04 print (sys.path) 05 print (sys.argv)
【代码说明】
·第2行代码使用import语句导入了sys模块,sys模块是处理系统环境的函数的集合。
·第4行代码输出Python环境下的查找路径的集合,Python默认情况下会查找sys.path返回的目录列表。列表是Python内置的数据结构,定义了一组相同意义的数据,通常用来作为参数或返回值。关于列表的知识请参考第4章的内容。本行代码的输出结果如下。
['D:\\developer\\python\\example\\02\\2.2\\2.2.3','C:\\WINDOWS\\system32\\python25.zip', 'D:\\developer\\python\\DLLs', 'D:\\developer\\python\\lib', 'D:\\developer\\python\\lib\\plat-win', 'D:\\developer\\python\\lib\\lib-tk','D:\\developer\\python', 'D:\\developer\\python\\lib\\site-packages','D:\\developer\\python\\lib\\site-packages\\win32', 'D:\\developer\\python\\lib\\site-packages\\win32\\lib', 'D:\\developer\\python\\lib\\site-packages\\Pythonwin', 'D:\\developer\\python\\lib\\site-packages\\wx-2.8-msw-unicode']
·第5行代码中的sys.argv是存储输入参数的列表。默认情况下,argv自带的参数是文件名。输出结果如下。
['import.py']
2.from…import…语句
使用from…import…语句导入与使用import语句导入有所不同,区别是前者只导入模块中的一部分内容,并在当前的命名空间中创建导入对象的引用;而后者在当前程序的命名空间中创建导入模块的引用,从而可以使用“sys.path”的方式调用sys模块中的内容。
下面这段代码实现了与上面代码相同的功能,不同的是使用了from…import…语句导入指定内容。
01 #不规范导入方式 02 from sys import path 03 from sys import argv 04 05 print (path) 06 print (argv)
【代码说明】第5、6行代码直接调用path、argv列表的内容,没有模块名的限定,但这种写法不够规范。如果程序比较复杂,导入了很多模块,阅读程序的人并不了解path、argv来自哪个模块。而通过sys.path、sys.argv的写法可以清楚地知道path、argv来自sys模块。
2.2.4 使用空行分隔代码
函数之间或类的方法之间用空行分隔,表示一段新的代码的开始。类和函数入口之间也用一行空行分隔,突出函数入口的开始。下面这段代码创建了一个类A,类A中定义了两个方法funX()和funY()。
01 class A: 02 def funX(self): 03 print( "funY()") 04 05 def funY(self): 06 print ("funY()") 07 08 if __name__ == "__main__": 09 a = A() 10 a.funX() 11 a.funY()
【代码说明】
·第4行代码插入了一个空行,便于程序员阅读代码,表示区分方法之间的间隔。
·第7行代码也是一个空行。因为下面的if语句是主程序的入口,用于创建类A的对象,并调用其方法。
空行与代码缩进不同,空行并不是Python语法的一部分。书写时不插入空行,Python解释器运行也不会出错。但是空行的作用在于分隔了两段不同功能或含义的代码,便于日后代码的维护或重构。记住,空行也是程序代码的一部分。
2.2.5 正确的注释
注释是用于说明代码实现的功能、采用的算法、代码的编写者以及代码创建和修改的时间等信息。注释是代码的一部分,注释起到了对代码补充说明的作用。C、C++、Java均采用“//”或“/**/”作为注释的标记,Python的注释方式有所不同。如果只对一行代码注释,使用“#”加若干空格开始,后面是注释的内容。如果对一段代码进行注释,也使用“#”,段落之间以一个“#”行分隔。Python会忽略“#”行的内容,跳过“#”行执行后面的内容。下面的代码演示了Python的注释。
01 # note - show Python's note 02 # Copyright (C) 2008 bigmarten 03 # 04 # This program is free software. you can redistribute it and/or modify 05 # it under the terms of the GNU General Public License as published by 06 # the Free Software Foundation 07 # 08 ######################################################################## 09 # 10 # Version is 1.0 11 # 12 # Its contents are calculate payment 13 # 14 ######################################################################## 15 16 # 规范的变量命名 17 sumPay = 0 # 年薪 18 bonusOfYear = 2000 # 年终奖金 19 monthPay = 1200 # 月薪 20 sumPay = bonusOfYear + 12 * monthPay # 年薪 = 年终奖金 + 12 * 月薪
【代码说明】
·第1行代码对本文件进行摘要说明。
·第2行代码声明了版权信息。
·第3行代码是空行,说明下面将另起一段,注释其他的内容。
·第4行到第6行代码说明程序的许可信息。
·第8行到第14行代码说明程序的版本和实现的功能。
·第16行开始的代码实现程序的功能,并在行末对每行代码进行单行注释。
Python还有一些特殊的注释,完成一些特别的功能,如中文注释、程序的跨平台。
(1)中文注释:如果需要在代码中使用中文注释,必须在Python文件的最前面加上如下注释说明。
# -*- coding: UTF-8 -*-
注意 Python3中默认的编码是Unicode,所以不需在每个Python文件中再加以上注释,但在Python2中若使用中文则必须加上。
(2)跨平台注释:如果需要使Python程序运行在*nix系统中,最好在Python文件的最前面加上如下注释说明。
#!/usr/bin/python
此外,注释也用于调试程序。由于文本编辑器不具备调试功能,因此,可以使用注释辅助调试。如果一个程序很长,可以把程序分成若干个部分编写。为了更好地调试目前正在编写的程序模块,可以将那些已经编译通过的部分注释掉;或者把多余代码注释掉,把主要精力集中在当前编写的逻辑上。
例如,编写一个比较两个数字大小的函数。
01 def compareNum(num1, num2): 02 if(num1 > num2): 03 return str(num1)+" > "+str(num2) 04 elif(num1 < num2): 05 return str(num1)+" = "+str(num2) 06 elif(num1 == num2): 07 return str(num1)+" = "+str(num2) 08 else: 09 return ""
【代码说明】本段代码中的str()函数实现了数字类型到字符串类型的转换。
编译后显示如下内容:
---------- python ---------- 输出完成 (耗时: 1 秒) - 正常终止
说明这个函数编译通过,至少在语法上没有任何错误。下面就可以编写这个函数的调用程序,证明这个程序的逻辑是否正确。在前面的代码段后面添加如下代码。
01 num1 = 2 02 num2 = 1 03 print (compareNum(num1, num2)) 04 num1 = 2 05 num2 = 2 06 print (compareNum(num1, num2)) 07 num1 = 1 08 num2 = 2 09 print (compareNum(num1, num2))
运行程序,发现第3行的输出结果有误。
2 > 1 2 = 2 1 = 2
第1行和第2行的输出证明函数compareNum()中num1>num2和num1==num2的判断是正确的,于是想到程序可能是在num1<num2的条件判断的逻辑上出现了错误。为了证明这个观点,注释掉num1<num2的分支判断语句。注释后的compareNum()的代码如下。
01 def compareNum(num1, num2): 02 if(num1 > num2): 03 return str(num1)+" > "+str(num2) 04 #elif(num1 < num2): 05 # return str(num1)+" = "+str(num2) 06 elif(num1 == num2): 07 return str(num1)+" = "+str(num2) 08 else: 09 return ""
此时,程序编译通过,证明逻辑错误就在num1<num2的分支语句中。仔细检查注释的语句,发现return语句的返回值表达式写错了,其中的“>”误写为“=”。compareNum()正确的写法如下所示。
01 def compareNum(num1, num2): 02 if(num1 > num2): 03 return str(num1)+" > "+str(num2) 04 elif(num1 < num2): 05 return str(num1)+" < "+str(num2) 06 elif(num1 == num2): 07 return str(num1)+" = "+str(num2) 08 else: 09 return ""
再次运行整个程序,输出结果正确显示。
2 > 1 2 = 2 1 < 2
合理地使用注释可以检查程序中的错误。这段代码演示了注释问题语句的做法,并检查这些语句错误的过程。另一种用法是,注释正确语句,单独运行问题语句,检查语句错误。这两种用法可以根据实际情况分别应用,后者更适合于代码比较长的情况。注释对于程序非常重要,表2-1说明了注释的用法和作用。
表2-1 注释的用法
2.2.6 语句的分隔
分号是C、Java等语言中标识语句结束的标志。Python也支持分号,同样可以用分号作为一行语句的结束标识。但在Python中分号的作用已经不像在C、Java中那么重要了,在C、Java中分号是必需的;而Python中的分号可以省略,主要通过换行来识别语句的结束。例如,以下两行代码是等价的。
01 # 下面两条语句是等价的 02 print( "hello world!") 03 print( "hello world!");
【代码说明】
·第1行代码的输出结果:
hello world!
·第2行代码的输出结果:
hello world!
如果要在一行中书写多个语句,就必须使用分号分隔了,否则Python无法识别语句之间的间隔。
01 # 使用分号分隔语句 02 x = 1; y = 1 ; z = 1
【代码说明】第2行代码中有3条赋值语句,语句之间需要用分号隔开。如果不隔开语句,则Python解释器不能正确解释,会提示语法错误。
SyntaxError: invalid syntax
注意 分号并不是Python推荐使用的符号,Python倾向于使用换行作为每条语句的分隔。简单直白是Python语法的特点,通常一行只写一条语句,这样便于阅读和理解程序。一行写多条语句的方式是不好的实践。
Python同样支持多行写一条语句,Python使用“\”作为换行符。在实践中,一条语句写在多行也是很常见的。例如,把SQL语句作为参数传递给函数,由于SQL非常长,因此需要换行书写,提高阅读的方便性。
01 # 字符串的换行 02 # 写法一 03 sql = "select id,name \ 04 from dept \ 05 where name = 'A'" 06 print (sql) 07 # 写法二 08 sql = "select id,name " \ 09 "from dept " \ 10 "where name = 'A'" 11 print (sql)
【代码说明】
·写法一只使用了一对双引号,把SQL语句分为select、from、where 3部分分别书写。
·第6行代码输出结果如下。
select id,name from dept where name = 'A'
·写法二使用了3对双引号,select、from、where分别对应一对双引号。
·第11行代码输出结果如下。
select id,name from dept where name = 'A'
第二种写法比第一种写法的可读性更强,可以使用空格和制表符对齐语句,使代码显得更工整。对于简短的语句不推荐使用换行的写法,这种写法只会造成阅读的复杂性。下面这段程序是不合理的换行写法。
01 # 一条语句写在多行 02 print (\ 03 "hello world!")
【代码说明】第2、3行代码是一个整体,调用print输出“hello world!”,这种情况不适合分行书写。
变量是计算机内存中的一块区域,变量可以存储任何值,而且值可以改变。常量是一块只读的内存区域,常量一旦初始化就不能修改。
2.3.1 变量的命名
变量由字母、数字或下划线组成。变量的第1个字符必须是字母或下划线,其他字符可以由字母、数字或下划线组成。例如:
01 # 正确的变量命名 02 var_1 = 1 03 print (var_1) 04 _var1 = 2 05 print( _var1)
【代码说明】
·第2行代码定义了一个名为var_1的变量,该变量的初始值为1。这个变量以字母开头,后面的字符由字母、下划线和数字组成。
·第3行代码输出结果如下。
1
·第4行代码定义了一个名为_var1的变量,该变量的初始值为2。这个变量以下划线开头,后面的字符由字母和数字组成。
·第5行代码输出结果如下。
2
下面这段代码演示了错误的变量命名方式。
01 # 错误的变量命名 02 1_var = 3 03 print (1_var) 04 $var = 4 05 print ($var)
【代码说明】
·第2行代码定义了一个名为1_var的变量,该变量以数字开头,后面的字符由字母、下划线组成。
·第3行代码,变量以数字开头,不符合变量命名的规则。提示如下错误:
SyntaxError: invalid syntax
·第4行代码定义了一个名为$var的变量,该变量以$符号开头。
·第5行代码,变量以$符号开头,不符合变量命名的规则。提示如下错误:
SyntaxError: invalid syntax
2.3.2 变量的赋值
Python中的变量不需要声明,变量的赋值操作即是变量声明和定义的过程。每个变量在内存中创建,都包括变量的标识、名称和数据这些信息。例如:
x = 1
上面的代码创建了一个变量x,并且赋值为1,如图2-1所示。
图2-1 变量的内部结构
Python中一次新的赋值,将创建一个新的变量。即使变量的名称相同,变量的标识并不相同。下面的代码演示了Python的变量声明以及赋值操作。
01 # 一次新的赋值操作,将创建一个新的变量 02 x = 1 03 print (id(x)) 04 x = 2 05 print( id(x))
【代码说明】
·第2行代码定义了一个名为x的变量,该变量的初始值为1。
·第3行代码,输出变量x的标识。输出结果如下。
11229424
·第4行代码再次定义了一个x的变量,该变量的初始值为2。该变量与前面的变量x并不是同一变量。
·第5行代码,输出变量x的标识。输出结果如下。
11229412
如果变量没有赋值,Python将认为该变量不存在。例如:
print y
运行后,解释器提示:
NameError: name 'y' is not defined
在变量y没有赋值的前提下,不能直接输出y的值。每个变量在使用前都必须赋值,这样可以避免由于变量的空值引起的一些异常。Python支持对一些变量同时赋值的操作,例如:
01 # 给多个变量赋值 02 a = (1, 2, 3) 03 (x, y, z) = a 04 print( "x =", x) 05 print( "y =", y) 06 print( "z =", z)
【代码说明】
·第2行代码定义了一个序列a,这个序列有3个值:1、2、3。
·第3行代码,把序列a的值分别赋值给序列(x,y,z)中的变量x、y、z。
·第4行代码输出变量x的值。输出结果:
x = 1
·第5行代码输出变量y的值。输出结果:
y = 2
·第6行代码输出变量z的值。输出结果:
z = 3
通过序列的装包和拆包操作,实现了同时给多个变量赋值。关于序列的概念参见第4章的内容。
2.3.3 局部变量
局部变量是只能在函数或代码段内使用的变量。函数或代码段一旦结束,局部变量的生命周期也就结束。局部变量的作用范围只在其被创建的函数内有效。例如,文件1的fun()中定义了一个局部变量,则该局部变量只能被fun()访问,而不能被fun2()访问,也不能被文件2访问,如图2-2所示。
下面定义了一个函数fun(),该函数中定义了一个局部变量。
01 # 局部变量 02 def fun(): 03 local = 1 04 print(local) 05 fun()
【代码说明】
·第2行代码定义了一个函数fun()。
·第3行代码定义了一个局部变量local。
·第4行代码输出local的值。输出结果如下。
1
·第5行代码调用函数fun()。此时已超出local变量的作用范围。
注意 Python创建的变量就是一个对象,Python会管理变量的生命周期。Python对变量的回收采用的是垃圾回收机制。
2.3.4 全局变量
全局变量是能够被不同的函数、类或文件共享的变量,在函数之外定义的变量都可以称为全局变量。全局变量可以被文件内部的任何函数和外部文件访问。例如,如果文件1中定义了一个全局变量,文件1中的函数fun()可以访问该全局变量。此外,该全局变量也能被文件1、文件2访问,如图2-3所示。
图2-2 局部变量的作用范围
图2-3 全局变量的作用范围
全局变量通常在文件的开始处定义。下面定义了两个全局变量_a、_b和两个函数add()、sub(),这两个函数将调用全局变量执行加法和减法计算。
01 # 在文件的开头定义全局变量 02 _a = 1 03 _b = 2 04 def add(): 05 global _a 06 _a = 3 07 return "_a + _b =", _a + _b 08 def sub(): 09 global _b 10 _b = 4 11 return "_a - _b =", _a - _b 12 print (add()) 13 print( sub())
【代码说明】
·第2行代码定义了一个名为_a的全局变量,这个变量的作用范围从定义处到文件的结尾。之所以使用下划线是为了区分于其他变量,引起程序员对全局变量出现的重视。
·第3行代码定义了一个名为_b的全局变量。同样,变量_b的作用范围从定义处到文件的结尾。
·第4行代码定义了一个函数add(),用于执行加法计算。
·第5行代码引用全局变量_a。这里使用了global关键字,global用于引用全局变量。
·第6行代码对全局变量_a重新赋值。
·第7行代码返回_a+_b的值。
·第8行代码定义了一个函数sub(),用于执行减法运算。函数内的实现方式和add()相同。
·第12代码调用函数add()。输出结果如下。
('_a + _b =', 5)
·第13行代码调用函数sub()。输出结果如下。
('_a - _b =', -1)
如果不使用global关键字引用全局变量,而直接对_a、_b赋值,将得到不正确的结果。
01 # 错误地使用全局变量 02 _a = 1 03 _b = 2 04 def add(): 05 _a = 3 06 return "_a + _b =", _a + _b 07 def sub(): 08 _b = 4 09 return "_a - _b =", _a - _b 10 print (add()) 11 print (sub())
【代码说明】
·第5行代码中的_a并不是前面定义的全局变量,而是函数add()中的局部变量。虽然输出的结果相同,但是运算的对象并不相同。
·第6行代码中的_b还是前面定义的全局变量_b。
·第8行代码中的_b是局部变量。
·第10行代码的输出结果如下。
('_a + _b =', 5)
·第11行代码的输出结果如下。
('_a - _b =', -3)
注意 变量名相同的两个变量可能并不是同一个变量,变量的名称只是起标识的作用。变量出现的位置不同,变量的含义也不同。
同样可以把全局变量放到一个专门的文件中,便于统一管理和修改。创建一个名为gl.py的文件。
01 # 全局变量 02 _a = 1 03 _b = 2
【代码说明】pl.py创建了两个全局变量_a和_b。
再创建一个调用全局变量的文件use_global.py。
01 # 调用全局变量 02 import gl 03 def fun(): 04 print(gl._a) 05 print(gl._b) 06 fun()
【代码说明】
·第2行代码导入前面创建的文件gl.py,即模块gl。
·第3行代码定义了一个函数fun(),该函数调用全局变量_a和_b。这里不需要使用global引用gl.py中的全局变量,因为前导符可以定位全局变量_a和_b。
·第4行代码输出_a的值,使用前导符gl定位。输出结果:
1
·第5行代码输出_b的值,使用前导符gl定位。输出结果:
2
·第6行代码调用fun()。
应该尽量避免使用全局变量。因为不同的模块都可以自由地访问全局变量,可能会导致全局变量的不可预知性。对于gl.py中的全局变量,如果程序员甲修改了_a的值,程序员乙同时也要使用_a,这时可能导致程序中的错误。这种错误是很难发现和更正的。
全局变量降低了函数或模块之间的通用性,不同的函数或模块都要依赖于全局变量。同样,全局变量降低了代码的可读性,阅读者可能并不知道调用的某个变量是全局变量。
2.3.5 常量
常量是指一旦初始化后就不能改变的变量。例如,数字5、字符串“abc”都是常量。C++中使用const关键字指定常量,Java使用static和final关键字指定常量,而Python并没有提供定义常量的关键字。Python是一门功能强大的语言,可以自己定义一个常量类来实现常量的功能。在《Python Cookbook》一书中定义了一个常量模块const。
01 class _const: # 定义常量类_const 02 class ConstError(TypeError): pass # 继承自TypeError 03 def __setattr__(self,name,value): 04 if self.__dict__.has_key(name): # 如果__dict__中不包含对应的key则抛出错误 05 raise self.ConstError, "Can't rebind const(%s)"%name 06 self.__dict__[name]=value 07 import sys 08 sys.modules[__name__]=_const() # 将const注册进sys.modules的全局dict中
【代码说明】
·这个类定义了一个方法__setattr__()和一个异常类型ConstError,ConstError类继承自TypeError。通过调用类自带的字典__dict__,判断定义的常量是否包含在字典中。如果字典中包含此常量,将抛出异常。否则,给新创建的常量赋值。
·最后两行代码的作用是把const类注册到sys.modules这个全局字典中。
以下代码在use_const.py中调用const,定义常量。
01 import const 02 const.magic = 23 03 const.magic = 33
【代码说明】
·第1行代码导入const模块。
·第2行代码定义了一个常量magic。
·第3行代码修改常量magic的值,抛出异常。
const.ConstError: Can't rebind const(magic)
数据类型是构成编程语言语法的基础。不同的编程语言有不同的数据类型,但都具有常用的几种数据类型。Python有几种内置的数据类型——数字、字符串、元组、列表和字典。本节将重点介绍数字和字符串。
2.4.1 数字
Python3的数字类型分为整型、浮点型、布尔型、分数类型、复数类型。使用Python编写程序时,不需要声明变量的类型。由Python内置的基本数据类型来管理变量,在程序的后台实现数值与类型的关联,以及类型转换等操作。Python与其他高级语言定义变量的方式及内部原理有很大的不同。在C或Java中,定义一个整型的变量,可以采用如下方式表示:
int i = 1;
在Python中,定义整型变量的表达方式更简练。
i = 1
Python根据变量的值自动判断变量的类型,程序员不需要关心变量究竟是什么类型,只要知道创建的变量中存放了一个数,以后的工作只是对这个数值进行操作,Python会对这个数的生命周期负责。
更重要的一点是,C或Java只是创建了一个int型的普通变量;而Python创建的是一个整型对象,并且Python自动完成了整型对象的创建工作,不再需要通过构造函数创建。Python内部没有普通类型,任何类型都是对象。如果C或Java需要修改变量i的值,只要重新赋值即可;而Python并不能修改对象i的值。例如:
01 #下面的两个i并不是同一个对象 02 i = 1 03 print(id(i)) 04 i = 2 05 print (id(i))
如果需要查看变量的类型,可以使用Python定义的type类。type是__builtin__模块的一个类,该类能返回变量的类型或创建一个新的类型。__builtin__模块是Python的内联模块,内联模块不需要import语句,由Python解释器自动导入。后面还会接触到更多内联模块的类和函数。
下面这段代码返回了各种变量的类型。
01 #整型 02 i = 1 03 print( type(i)) 04 #长整型 05 l = 999999999999999999990 # 什么时候python将int转为float跟操作系统位数相关 06 print type(l) 07 #浮点型 08 f = 1.2 09 print( type(f)) 10 #布尔型 11 b = True 12 print (type(b))
【代码说明】
·第3行代码输出结果:
·第6行代码输出结果:
·第9行代码输出结果:
·第12行代码输出结果:
用Python来进行科学计算也很方便,因为Python内置了复数类型。Java、C#等高级语言则没有提供复数类型。
01 #复数类型 02 c = 7 + 8j 03 print (type(c))
第3行代码输出结果:<class'complex'>
注意 复数类型的写法与数学中的写法相同,如果写为c=7+8i,Python不能识别其中的“i”,将提示语法错误。
2.4.2 字符串
在Python中有3种表示字符串的方式——单引号、双引号、三引号。单引号和双引号的作用是一样的,对于不同的程序员可以根据自己的习惯使用单引号或双引号。PHP程序员可能更习惯使用单引号表示字符串,C、Java程序员则习惯使用双引号表示字符串。下面这段代码中单引号和双引号的使用是等价的。
01 # 单引号和双引号的使用是等价的 02 str = "hello world!" 03 print (str) 04 str = 'hello world!' 05 print (str)
【代码说明】第3行代码输出结果:
hello world!
第5行代码输出结果:
hello world!
三引号的用法是Python特别的语法,三引号中可以输入单引号、双引号或换行等字符。
01 # 三引号的用法 02 str = '''he say "hello world!"''' 03 print( str)
【代码说明】第3行代码的三引号中带有双引号,双引号也会被输出。输出结果:
he say "hello world!"
三引号的另一种用法是制作文档字符串。Python的每个对象都有一个属性__doc__,这个属性用于描述该对象的作用。
01 # 三引号制作doc文档 02 class Hello: 03 '''hello class''' 04 def printHello(): 05 '''print hello world''' 06 print ("hello world!") 07 print( Hello.__doc__) 08 print (Hello.printHello.__doc__)
【代码说明】
·第2行代码定义了一个名为Hello的类。
·第3行是对Hello类的描述,该字符串将被存放在类的__doc__属性中。
·第4行代码定义了一个方法printHello()。
·第5行代码描述了printHello(),并把字符串存放在该函数的__doc__属性中。
·第6行代码输出结果:
hello world!
·第7行代码输出Hello的__doc__属性的内容。输出结果:
hello class
·第8行代码输出printHello()的__doc__属性的内容。输出结果:
print hello world
如果要输出含有特殊字符(单引号、双引号等)的字符串,需要使用转义字符。Python中转义字符为“\”,和C、Java中的转义字符相同。转义操作只要在特殊字符的前面加上“\”即可。下面这段代码说明了特殊字符的转义用法。
01 # 转义字符 02 str = 'he say:\'hello world!\'' 03 print (str)
【代码说明】第2行代码中的单引号是特殊字符,需要在“'”前加上转义字符。第3行代码的输出结果:
he say:'hello world!'
使用双引号或三引号可以直接输出含有特殊字符的字符串,不需要使用转义字符。
01 # 直接输出特殊字符 02 str = "he say:'hello world!'" 03 print (str) 04 str = '''he say:'hello world!' ''' 05 print (str)
【代码说明】
·第2行代码中使用了双引号表示字符串变量str,因此Python能够识别出双引号内部的单引号只是作为输出的字符。
·第3行代码的输出结果:
he say:'hello world!'
·第4行代码使用三引号表示字符串变量str,注意最后一个单引号后面留有一个空格,这个空格是为了让Python识别出三引号留下的。如果不留下这个空格,4个单引号连在一起,Python解释器不能正确识别三引号。提示如下错误:
SyntaxError: EOL while scanning single-quoted string
·第5行代码的输出结果:
he say:'hello world!'
注意 输出的字符串中含有单引号,使用双引号表示字符串即可。相反,输出的字符串中含有双引号时,可使用单引号表示字符串。
Python的运算符号包括算术运算符、关系运算符和逻辑运算符。表达式是由数字或字符串和运算符组成的式子。表达式通常用于判断语句和循环语句的条件使用,表达式是学习控制语句编写的基础。本节将介绍Python中的各种表达式的使用。
2.5.1 算术运算符和算术表达式
算术运算符包括四则运算符、求模运算符和求幂运算符。Python中的算术运算符和表达式如表2-2所示。
表2-2 Python中的算术运算符和表达式
注意 与C、Java语言不同,Python不支持自增运算符和自减运算符。例如,i++、i--是错误的语法。
下面这段代码演示了Python中算术运算符的使用方法。
01 print( "1 + 1 =", 1 + 1) 02 print ("2 - 1 =", 2 - 1) 03 print( "2 * 3 =", 2 * 3) 04 print ("4 / 2 =", 4 / 2) 05 print( "1 / 2 =", 1 / 2) 06 print ("1 / 2 =", 1.0 / 2.0) 07 print ("3 % 2 =", 3 % 2) 08 print ("2 ** 3 =", 2 ** 3)
【代码说明】
·第1行代码的输出结果:1+1=2
·第2行代码的输出结果:2-1=1
·第3行代码的输出结果:2*3=6
·第4行代码的输出结果:4/2=2
·第5行代码的输出结果:1/2=0.5
·第6行代码中的被除数是1.0,除数是2.0。Python把这两个数作为浮点型处理,因此相除后可以得到正确的结果。输出结果:1.0/2.0=0.5
·第7行代码中,求模的值为3除以2后的余数。输出结果:3%2=1
·第8行代码的输出结果:2**3=8
注意 Python2中执行“1/2”算术表达式的结果略有不同,Python2认为1和2是整型,相除后的结果会被截断,因此得到的值为0。
Python的算术表达式具有结合性和优先性。结合性是指表达式按照从左往右、先乘除后加减的原则。即从表达式的左边开始计算,先执行乘法和除法运算,再执行加法和减法运算。例如:
a + b * c % d
以上表达式先执行b*c,然后执行b*c%d,最后执行加法运算。
优先性是指先执行圆括号内的表达式,再按照结合性的原则进行计算。例如:
(a + b) * (c % d)
以上表达式先计算a+b的值,然后计算c%d的值,最后把两个值相乘。下面这段代码演示了算术运算的优先级。
01 # 算术运算的优先级 02 a = 1 03 b = 2 04 c = 3 05 d = 4 06 print ("a + b * c % d =", a + b * c % d) 07 print ("(a + b) * (c % d) =", (a + b) * (c % d))
第6行代码的输出结果:
a + b * c % d = 3
第7行代码的输出结果:
(a + b) * (c % d) = 9
2.5.2 关系运算符和关系表达式
关系运算符即对两个对象进行比较的符号。Python中的关系运算符和表达式如表2-3所示。
表2-3 Python中的关系运算符和表达式
下面这段代码演示了关系表达式的逻辑输出。
01 # 关系表达式 02 print (2 > 1) 03 print (1 <= 2) 04 print (1 == 2) 05 print (1 != 2)
【代码说明】
·第2行代码,2>1的逻辑正确。输出结果:True
·第3行代码,1<=2的逻辑正确。输出结果:True
·第4行代码,1==2的逻辑错误。输出结果:False
·第5行代码,1!=2的逻辑正确。输出结果:True
不同的关系运算符优先级别不同。其中<、<=、>、>=4个运算符的优先级别相等,==、!=的优先级别相等。而<、<=、>、>=的优先级别大于==、!=的优先级别。例如:a>=b==c等价于(a>=b)==c。
关系运算符的优先级低于算术运算符。下面的代码演示了关系运算符的优先级别。
01 # 关系表达式的优先级别 02 print( "1 + 2 < 3 - 1 =>", 1 + 2, "<", 3 - 1, "=>", 1 + 2 < 3 - 1) 03 print ("1 + 2 <= 3 > 5 % 2 =>", 1 + 2, "<=", 3, ">", 5 % 2, "=>", 1 + 2 <= 3 > 5 % 2)
【代码说明】
·第2行代码,先执行1+2=3,然后执行3–1=2,最后比较3<2。输出结果:
1 + 2 < 3 - 1 => 3 < 2 => False
·第3行代码,先执行1+2=3,然后执行5%2=1,最后比较3<=3>1。输出结果:
1 + 2 <= 3 > 5 % 2 => 3 <= 3 > 1 => True
2.5.3 逻辑运算符和逻辑表达式
逻辑表达式是用逻辑运算符和变量连接起来的式子。任何语言的逻辑运算符都只有3种——逻辑与、逻辑或和逻辑非。C、Java语言的逻辑运算符用&&、||、!表示,Python采用and、or、not表示。表2-4列出了Python中的逻辑运算符和表达式。
表2-4 Python中的逻辑运算符和表达式
下面的代码演示了逻辑表达式的运算。
01 # 逻辑运算符 02 print( not True) 03 print( False and True) 04 print (True and False) 05 print (True or False)
【代码说明】
·第2行代码,True的逻辑非为False。输出结果:False
·第3行代码,检测到and运算符左侧的False,就直接返回False。输出结果:False
·第4行代码,检测到and运算符左侧为True,然后继续检测右侧,右侧的值为False,于是返回False。输出结果:False
·第5行代码,or运算符的左侧为True,于是返回True。输出结果:True
逻辑非的优先级大于逻辑与和逻辑或的优先级,而逻辑与和逻辑或的优先级相等。逻辑运算符的优先级低于关系运算符,必须先计算关系运算符,然后再计算逻辑运算符。下面这段代码演示了逻辑运算符、关系运算符和算术运算符的优先级别。
01 # 逻辑表达式的优先级别 02 print( "not 1 and 0 =>", not 1 and 0) 03 print( "not (1 and 0) =>", not (1 and 0)) 04 print ("(1 <= 2) and False or True =>", (1 <= 2) and False or True) 05 print ("(1 <= 2) or 1 > 1 + 2 =>", 1 <= 2, "or", 1 > 2, "=>", (1 <= 2) or (1 < 2))
【代码说明】
·第2行代码,先执行not 1,再执行and运算。输出结果:
not 1 and 0 => False
·第3行代码,先执行括号内的1 and 0,再执行not运算。输出结果:
not (1 and 0) => True
·第4行代码,先执行1<=2的关系运算表达式,再执行and运算,最后执行or运算。输出结果:
(1 <= 2) and False or True => True
·第5行代码,先执行1<=2的关系运算表达式,再执行表达式1>1+2,最后执行or运算。输出结果:
(1 <= 2) or 1 > 1 + 2 => True or False => True
本章讲解了Python的基本语法和基本概念,包括Python的文件类型、编码规则、变量、数据类型以及运算符和表达式等内容。重点讲解了Python的编码规则、命名规则、缩进式的写法、注释等。Python中的变量与C、Java有很大的不同,本章通过比较的方式阐述Python变量的分类和特性。最后讲解了运算符和表达式的知识,这些知识是编写控制语句的基础。下一章将会学习到Python控制语句的编写,控制语句将用到本章介绍的变量和表达式。条件语句和循环语句都是通过表达式返回的布尔值控制程序流程的,这些内容是编写程序的基本要素。
1.以下变量命名不正确的是( )。
A.foo=the_value
B.foo=1_value
C.foo=_value
D.foo=value_&
2.计算2的38次方的值。
3.以下逻辑运算的结果:
a)True and False
b)False and True
c)True or False
d)False or True
e)True or False and True
f)True and False or False
4.编写程序计算1+2+3+…+100的结果。