25 KiB
第1章 Python语法基础
Python 是一种面向对象的、解释型的、通用的、开源的脚本编程语言。
Python是由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计,作为一门叫做ABC语言的替代品。Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的编程语言,随着版本的不断更新和语言新功能的添加,逐渐被用于独立的、大型项目的开发。
目前主流的Python版本已经到3.x,从2020年开始,Python官方将不再对Python2进行维护,官方建议开发者尽快迁移到Python3上。需要注意的是,虽然两个版本多数语法比较类似,Python3并不兼容Python2,也就是说此前使用Python2编写的程序并不能保证可以在Python3环境下正常运行。对于开发者来说,入门学习自然是要紧跟官方建议,本书的语法内容也是基于Python3编写而成的。
1.1 初识Python
Python因为其语法简洁、优雅、易于理解,一直被认为是最“简单”的开发语言。从入门角度来说,这样的评价不无道理,对于有其他开发语言经验的学习者来说,Python的基础语法也许只需要短短几天就能比较熟悉了,但是如果想要真正地掌握Python这门语言,仅仅是熟悉语法基础还远远不够。Python最大的特点是拥有海量的第三方类库,作为开发者,如何用最小的代价开发出所需要的功能,则需要对Python的常用类库有所了解。从这个方面来讲,Python的学习,熟悉语法后不断去使用、练习,才能在日后的使用中得心应手。
1.1.1 为什么要学习Python
人生苦短,我用Python!这是入坑Python之后听到的第一句名言,Python的优势在于丰富的第三方库,尤其是人工智能方向。事实证明,当一门语言和某个火热的行业绑定时,它必将前途无量!正如10年前Android的崛起带动Java的再次起飞,最近几年人工智能技术的火热使得Python炙手可热了起来。
下图1-1是截至2021年9月的TIOBE编程语言排行榜。
图1-1-1 TIOBE编程语言排行榜从排行榜上可以看出,时至今日,Python已经力压Java成为第二受欢迎的编程语言,距离排名第一的C语言也只有0.16%的微弱差距,从趋势来看,Python登顶最受欢迎的编程语言榜首似乎只是时间问题。
正是因为有了如此繁荣的生态环境,无数的开发者也为Python提供了庞大的第三方开源类库供用户调用。为了实现一个功能,在其他语言中往往需要几十数百行的代码,在Python中也许就只是一句类库的调用就足够了。Python以其优雅和易于理解的语法著称,就连《Thinking in C++》和《Thinking in Java》的作者Bruce Eckel也曾感叹:“Life is short,you need Python(人生苦短,你该用Python)”。作为一门脚本语言,只要在拥有运行Python的环境,也许是你正在使用的PC上,也许是在公司的服务器上,也许就只是在一台Open Wrt路由器上,Python程序都可以无需编译就直接运行。
可能很多读者会问,Python是不是更适合搞人工智能,不适合区块链行业呢?其实不然。正如前面提到的,人生苦短,你需要使用Python,在区块链领域也没有问题。区块链系统就像是数据库一样,会为大多数开发者提供各种语言的SDK,作为这么受欢迎的语言,任何一个区块链平台都不会放弃Python的市场。就拿以太坊来说,它的节点客户端就有Go语言、Java、C++、Python等语言的不同实现版本。以太坊创始人维塔利克·布特林正在努力开发vyperlang,它是一款类似Python的智能合约开发语言。由此可见,Python可以和区块链很好的结合,Python+区块链绝对是强强组合。
1.1.2 Python开发环境搭建
Python官网(https://www.python.org/) 已经为各个主流系统提供了安装包文件,只需要按照自己的系统版本下载对应的安装包即可。
在Python官网主页的Downloads下拉框中,会根据你当前正在使用的系统,在右侧提供最新版的Python版本的下载链接,如果读者需要下载指定版本或指定平台的Python安装包,可以在左侧的列表中根据实际情况自行选择。
本书以Windows系统为例,点击下载Python3.10.0后双击安装包,开始安装。
Install Now会自动安装到默认目录中,Customize installation可以自定义Python的安装目录,修改是否下载附加组件等内容。
安装完成后,点击键盘的Win+R,输入“cmd”进入Windows的命令行模式。 输入:
python --version
如果命令行返回
Python 3.10.0
说明你的Python环境已经安装完成了。
1.1.3 选择一个适合的IDE
工欲善其事,必先利其器。想要快速开发Python程序,使用一款适合的IDE是非常关键的。下面,我们介绍可以用于开发Python代码的IDE。
-
文本编辑器
文本编辑器其实不能称之为IDE,作为初学者,最开始学习Python基础语法时,使用类记事本的编辑器也未尝不是一个好的选择。当然这里我们还是不建议你选择windows自带的记事本程序,因为该程序在最终保存时,会默认在文本的开头部分添加一个(UTF-8 BOM)的头部标识,这会导致程序在运行时出现一些莫名其妙的问题。
就轻量级的文本编辑器而言,笔者推荐使用Notepad++或Editra等第三方文本编辑器。
-
IDLE
IDLE是Python默认提供的集成开发环境,当我们安装好Python后,也会默认安装上IDLE。IDLE具备基本的IDE功能,具有自动缩进、语法高亮、关键词自动完成等功能。在这些功能的帮助下,可以有效地提高开发者阅读和编写代码的效率。但是一般情况下不建议直接使用IDLE作为日常开发Python的IDE工具,相较于下面介绍的几种IDE来说,IDLE还是略显简易。
-
PyCharm
使用文本编辑器写一些简单的小程序当然是没有问题的,但是到了大型项目中,往往需要同时管理数百个文件,各个文件之间还存在相互引用关系,这个时候使用智能IDE几乎就成了必然的选择。如果此前你有过Java、JS等其他语言的编程经验,一定听说过Jetbrains公司的大名,PyCharm就是他们为Python开发者提供的一款IDE工具,这款工具提供代码编写、调试、测试工具的一整套工具链。
你可以通过这个地址下载Pycharm (https://www.jetbrains.com/pycharm/download/),Jetbrains提供了Professional(专业版)和Community(社区版)两种版本,相比于免费的社区版,专业版内置了连接Sql数据库,支持在线编写代码,编辑Html、JS等强大功能,但是对于大多数Python开发者而言,社区版的功能已经足以满足绝大多数的开发场景。
-
VS Code
VS Code是微软推出的一款跨平台、支持插件扩展的智能IDE工具,它会在安装后自动检测系统中是否已经安装过Python的开发环境,智能提示你安装所需的插件。同时支持语法高亮、自动补全,相比较文本编辑器而言功能较为完善,又比PyCharm更轻量级。
本书中接下来的Python代码都将在PyCharm中完成,这里笔者也建议读者朋友们可以使用PyCharm来作为开发Python的首选IDE。
1.1.4 写下你的第一个Python程序
所有学习编程的开发者都是从"Hello World"开始自己的编程之旅,学习Python时也不例外。
打开你的IDE,在任意的英文目录中新建一个HelloWorld.py文件
图1-1-4 创建HelloWorld.py打开这个文件后,在文件内写下
print("Hello World!")
随后命令行进入这个文件所在目录,使用
python HelloWorld.py
运行这个程序,你将会在控制台中看到打印出来的“Hello World!”语句。
图1-1-5 运行HelloWorld.py只是简单打印一句语句看起来还不够“酷”?没问题,可以让我们的程序更加“智能”一点。
name = input("请输入您的姓名:") # 获取用户输入内容
print("Hello", name)
这样在运行程序后,界面会打印出一行文字:
在输入您的姓名后,程序会自动跟您打招呼。
1.5 异常处理
Python中一般会出现两类错误问题,第一种是在编写时不符合语言规范的语法错误,另外一种则是所谓的程序异常。
语法错误如下:
# 1.5.1 语法错误,后一个单引号是中文字符,这也是很多初学编程的开发者经常犯的错误
name = 'name’
使用Pycharm编写代码时,当出现语法错误时一般会通过下划红线提示出来。如下图1-1-7所示:
图1-1-7 语法错误这样的错误一般无需运行程序,在编写代码时IDE就能检测出来,运行程序也会有对应的错误提示。
另外一种情况则要稍微复杂一些,这也是我们本节要重点介绍的内容。代码语法是正确的,但是到了运行时,却会因为用户输入内容有误、程序获取的数据不符合预期等原因,导致程序无法获得期望的输入数据,或者不能按照期望的流程执行。
例如下面这段代码,程序期望获取的是一个数字,计算这个数字的一半是多少。
# 1.5.2 期望获取一个数字,并计算它的一半是多少
num = "50"
print(num/2)
但是num的值却是一个字符串,这样在运行时就会出现下图这样的异常。
图1-1-8 程序异常可以看到,编译器会提示我们“unsupported operand type(s) for /: 'str' and 'int'”,不支持对str(字符型)和int(整型)做除法操作。
这样的异常情况在大型项目中会经常出现,程序运行过程中出现了预期之外的情况出现,这又会导致后续的流程无法正常运行。因此提前预判这样的情况以保证程序正常运行,这就是我们需要去处理的所谓异常。
对于程序异常,Python提供了完善的异常检查机制,可以将异常情况“扼杀在摇篮里”。
1.5.1 错误处理思想
Python中已经预设了多种异常情况,具体如下表所示:
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
Python处理异常主要通过预设在代码中的try-except语句来捕获可能发生的异常,如果存在不在上表异常类型的情况,Python同样支持自定义异常类型,通过raise将该异常类型抛出,由调用方通过try-except来捕获这个异常。
1.5.2 try语句使用
Python的异常捕获语句和Java语法极为相似,try代码块内是检测异常的区域,在该区域内存在except中定义的异常,会直接跳转到对应的except代码块内。
# 1.5.3 try-except的使用
try:
num = "50"
print(num/2)
except TypeError as e:
print("捕获到异常:", e)
这样就可以捕获到前面我们提到的异常。
如果在一段程序中,无论是否出现异常,都期望程序能执行一些特定的操作,可以通过try-except-finally来实现,finally代码段的内容,无论是否出现异常情况都会执行。
# 1.5.4 try-except-finally的使用
try:
num = "50"
print(num/2)
except TypeError as e:
print("捕获到异常:", e)
finally:
print("无论是否发生异常,这里都会执行")
这种情况多数用在一些对数据资源的访问上,例如打开一个文本文件,无论是否在读取过程中出错,都需要最终关闭这个文件以释放资源,这个操作就可以在finally中来完成。
如果想要在一个except中同时捕获多个预定义异常,有两种方式来处理
-
多个并行的except
# 1.5.5 多个except的使用 try: num = "50" print(num / 2) except TypeError as e: print("捕获到的字符串与数字数学运算的异常:", e) except ZeroDivisionError as e: print("捕获到的除数为0的异常", e) finally: print("无论是否发生异常,这里都会执行")
-
一个except中包含多个类型的异常
# 1.5.6 多个except的使用 try: num = 50 print(num / 0) except (TypeError, ZeroDivisionError) as e: print("捕获到的的异常:", e) finally: print("无论是否发生异常,这里都会执行")
如果不确定try代码块中的异常类型,也可以不指定异常类型,或者捕获所有异常的父类BaseException,这样捕捉到的任何异常都会在except代码块中处理。
-
不指定异常类型
# 1.5.7 捕获所有异常 try: num = 50 print(num / 0) except: print("捕获到的任意异常") finally: print("无论是否发生异常,这里都会执行")
-
捕获异常的基类
默认情况下所有异常都是继承自BaseException,所以捕获BaseException就可以匹配到所有的异常类型。
# 1.5.8 捕获所有异常 try: num = 50 print(num / 0) except BaseException as e: print("捕获到的任意异常:", e) finally: print("无论是否发生异常,这里都会执行")
可以看到,相比于不指定异常类型,捕获BaseException的优点在于,可以获取到导致程序异常的具体信息,我们也可以在except中对异常做相应的判断和针对性的处理。
-
使用raise向外抛出异常
使用raise可以不对异常做处理,而是将该异常抛给上一层,也就是调用方,由调用方来处理这个异常。这样的处理多是在方法定义中,可以让外部的调用方有更灵活的异常处理方式。
def addNum(): num1Str = input("请输入第1个数字:") if(not num1Str.isdigit()): raise Exception("您输入的不是数字!", num1Str) num2Str = input("请输入第2个数字:") if(not num2Str.isdigit()): raise Exception("您输入的不是数字!", num2Str) floatNum1 = float(num1Str) floatNum2 = float(num2Str) return floatNum1 + floatNum2 try: addNum() except BaseException as e: print(e)
上面的示例中,我们定义了一个加法函数,提示用户输入两个数字,函数会自动计算两个数字的和。如果用户输入的内容不是数字,那么这个加法操作流程必然无法执行,因此我们需要提前判断用户输入的内容。
在Python中,通过input获取到的输入内容都是string字符串,我们通过字符串内置的isdigit()方法来判断其是否为数字类型,如果不是,函数会通过raise向外抛出一个Exception异常,并提示用户“您输入的不是数字!”,这样在调用该函数时,通过try-except就可以实时判断用户输入的内容是否符合函数的要求。
1.5.3 断言语句
断言一般用于单元测试中,用于判断一个表达式,以及这个表达式为False时触发的异常。在Python中,断言的语法为
assert expression [, arguments]
当assert中的条件为假时,会抛出第二个参数定义的异常信息。
num1Str = "m"
assert(num1Str.isdigit(), "num1Str不是数字!")
等价于
num1Str = "m"
if(num1Str.isdigit()):
pass
else:
raise AssertionError("num1Str不是数字!")
需要注意的是,断言只是作为调试程序的一种手段,一般是用来发现Python脚本中的异常。断言所检测的异常,在执行Python代码时是可以被忽略的。因此当我们通过断言定位并修改问题后,断言内容随后也就可以去掉。
1.6 面向对象编程
几乎所有的高级语言都是面向对象的,相比于面向过程,面向对象的编程方式更适合人类的思维模式。在中大型项目中,面向对象的编程风格也可以极大地节约代码量。
Python提供了对面向对象编程的支持,有着完善的类、对象以及方法的定义和使用规范。
1.6.1 面向对象的编程思想
面向对象最重要的三个特点即为封装、继承、多态。
封装性可以保证在编程时可以将同一类事物的属性和方法封装到一个类中, 通过方法来修改和执行业务, 有利于后期的修改和维护。继承可以实现方法的多态性和代码的可重用性。多态则是为了解决现实生活中的多样性问题, 相同的指令对应不同的子对象,会有该对象特有的执行方式。
举一个简单的例子,假设有一个学生类,学生又都有年龄、年级的属性以及学习这样的方法。在面向对象编程中,我们就可以定义一个学生的父类,其中包含了上述的属性和方法,这里就体现了面向对象的封装性,将同一类事物封装在一个类中。学生中又分为小学生、中学生和大学生,可以根据这些分类创建不同的子类,这些子类也就继承了父类中的属性和方法,这里就体现了面向对象的继承性。不同的学生学习这个方法最终实现会有所不同,例如小学生学习的是小学知识,中学生学习的自然是中学知识,因此仅就“学习”这一个相同的方法,不同的子类可能会有不同的处理,这里就体现出了面向对象的多态性。
1.6.2 Python的面向对象特色
Python中定义一个类
class Student(object):
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def study(self):
print(self.name+"在学习基础知识")
baseStudent = Student("学生0", 10, 3)
print("baseStudent的name:", baseStudent.name)
print("baseStudent的age:", baseStudent.age)
print("baseStudent的grade:", baseStudent.grade)
baseStudent.study()
class是Python中用来定义类的标准字符,Student(object)说明当前定义的类继承自object类,如同Java中的类都是继承自Object类一样,Python中的类也都是继承自object类。Student类中的__init__方法是构造函数的定义,是这个类的对象被初始化时默认会调用的方法。
像__init__这样以前后双下划线的方式命名的方法名,是Python中的“魔术方法(magic method)”,对Python系统来说,这将确保不会与用户自定义的名称冲突。
Python中定义对象直接命名对象名即可,不能像Java那样,在对象名前显式指定类名。等号后的Student("学生0", 10, 3)就是对Student对象的实例化,这行代码会自动调用类构造函数,需要注意的是,在Python类定义的函数,第一个参数都是self,这个参数代表当前正在使用的对象,我们可以通过它获取到当前对象内的属性。
接下来我们定义Student的两个子类:PrimaryStudent和JuniorStudent,它们会各自实现自己的study方法
class PrimaryStudent(Student):
def study(self):
print(self.name+"在学习小学知识")
class JuniorStudent(Student):
def study(self):
print(self.name+"在学习初中知识")
primaryStudent = PrimaryStudent("小学生", 7, 1)
print("primaryStudent的name:", primaryStudent.name)
print("primaryStudent的age:", primaryStudent.age)
print("primaryStudent的grade:", primaryStudent.grade)
primaryStudent.study()
juniorStudent = JuniorStudent("中学生", 13, 7)
print("juniorStudent的name:", juniorStudent.name)
print("juniorStudent的age:", juniorStudent.age)
print("juniorStudent的grade:", juniorStudent.grade)
juniorStudent.study()
上面的示例可以看到,PrimaryStudent和JuniorStudent虽然都没有在类中定义自己的age、grade以及name,但是因为在定义类时,继承了Student,它们也就继承了父类Student定义的这些属性。而在两个子类中各自重写了父类的study方法后,也会自动调用子类中的方法。
类属性和方法的访问限制
Python中对类属性和方法的访问限制,主要是通过名称来控制的。当名称包含两个下划线“__”,则说明这是一个私有变量或私有方法。上面的例子中,当我们把name改为__name,在外部就无法访问到这个属性了。
class Student(object):
def __init__(self, name, age, grade):
self.__name = name
self.age = age
self.grade = grade
def study(self):
print(self.__name+"在学习基础知识")
student = Student("小学生", 7, 1)
print("student的name:", student.__name)
执行上面的程序,会提示Student没有__name这个属性,因为__name对外部是不可见的。
AttributeError: 'Student' object has no attribute '__name'
在Python中,如果属性名或方法名以单个下划线开头,则说明这是一个受保护的属性或方法,它表示“虽然我可以在外部被访问,但是最好不要这样做”。
疑难解答
1. 为什么建议使用python 3.x 而不是python 2.x?
2. 为什么都说Python相比于其它语言更简单?
事实上想要真正精通Python一点也不简单,只是Python的入门基础知识比较简单,学习曲线较为平滑,而这些基础知识已经可以应付生活工作中的80%以上的场景。不像有些语言那样,很多初学者往往都是倒在入门的门槛上,根本都没有机会能够流畅地学完所有的基础语法。
另外Python因为具备海量的第三方库,很多即使是刚刚入门的同学也能迅速结合这些第三方库,“黏合”出一个看上去比较复杂的程序,就像造一辆汽车,C语言往往需要从如何挖矿开始,Java需要从如何拧螺丝钉开始,而Python则是直接提供了轮子、方向盘、底盘,你只需要按照自己的想法将这些组件搭建起来,就可以“造”出一辆汽车,这其中哪一个难度更低就显而易见了。
简单来讲,Python可以在学习中给予学习者更多的正反馈,这种正反馈也就让学习者有了Python相较其他语言更“简单”的感觉。