Python是一种解释型、面向对象的高级程序设计语言,功能强大,具有很多区别于其他语言的个性化特点。在Python中,一切皆是对象。
Python语法简单,易于阅读和理解,便于快速地构建项目并快速地进行改进。Python用途广泛,对初学者友好,在入门级编程人员中很受欢迎。Python是开源的,有丰富的、不断增长的第三方模块来扩展其功能。Python还拥有一个庞大而活跃的社区,在Python的模块和库方面做出了贡献,并为其他应用者提供了有用的资源。
目前(截至2022年9月)Python的最新版本为3.10.7。在开始学习使用Python之前,选择一个合适的集成开发环境(Integrated Development Environment,IDE),有利于我们快速上手Python,在学习中起到事半功倍的效果。Python的集成开发环境有很多,这里列出本书用到的集成开发环境。
(1)IDLE。IDLE是Python自带的、默认的、入门级集成开发环境,包含交互式和文件式两种模式。在交互式环境下可以编写一行或者多行语句并且立刻看到结果。在文件式环境下可以像其他文本工具类一样编写语句。通常我们只用它进行教学以及展示、测试和调试代码,不建议使用它进行实际的开发工作。
(2)PyCharm。PyCharm是唯一一款专门面向Python的全功能集成开发环境。PyCharm在所有的集成类工具中相对简单且集成度较高,使用人数最多,适合编写较大、较复杂的程序。其代码自动补全功能在同类产品中几乎是最优秀的。
(3)Jupyter Notebook。Jupyter Notebook是一个开源的Web应用程序,非常方便开发者创建和共享代码文档。对于Python学习者而言,Jupyter Notebook提供了一个非常友好的环境,允许把代码写入独立的单元中,可单独执行,无须从头开始执行。
基础语法是编程语言最基础的部分,不同的编程语言,如C语言、C++、Java、Python等,它们在基础语法的细节上都不尽相同,需要区别对待。
(1)标识符
标识符就是变量、常量、函数、类等对象使用的名称。需要注意的是,Python中的标识符是严格区分字母大小写的。标识符的第一个字符必须是字母表中的字母或下画线“_”,其他部分由字母、数字和下画线组成。标识符不能与Python的关键字和内置函数的名称相同。
(2)关键字
关键字也称为保留字,是Python官方确定的语法功能的专用标识符,不能用作任何自定义标识符。关键字只包含小写字母。Python的标准库提供了一个keyword模块,可以输出当前版本的所有关键字。
(3)注释
程序中不仅有代码,还有很多注释。注释有说明性质的,也有帮助性质的。它们在代码执行过程中相当于不存在,但在代码维护、解释、测试等方面发挥着不可或缺的重要作用。
在Python中,以符号“#”为单行注释的开始,从它往后到本行末尾的内容都是注释内容。如果想注释多行语句,则只能在每行的开头加上符号“#”。
很多时候,在一些Python脚本文件的开头都能看到以“#”开头的两行代码,它们不是注释,而是一些设定。这两行代码的特点是位置在文件的顶行、顶左,没有空格和空行。
第一行用于指定运行脚本的Python解释器,为Linux专用,Windows中不需要使用。第二行用于指定代码的编码方式。Python 3全面支持Unicode编码,默认采用UTF-8编码,这里可以不需要。但在Python 2中,通常都需要这一行。
(4)语句缩进
Python最具特色的语法格式就是使用缩进来表示代码块,不像其他编程语言使用花括号或其他符号。相同层次的语句具有相同的缩进。
Python缩进规则是指在定义类、函数、流程控制语句、异常处理语句等时,行尾的冒号和下一行的缩进表示下一个代码块的开始,而缩进的结束表示代码块的结束。
Python官方的代码规范PEP 8建议使用4个空格作为缩进。在PyCharm中,如果缩进的空格数不一致,则会抛出名为“IndentationError”的异常。
(5)多行语句
Python中,通常一行就是一条语句,一条语句通常也不会超过一行。但Python并没有从语法层面完全禁止在一行中使用多条语句,可以使用分号使多条语句在一行,举例如下。
上面这一行包含3条语句,用分号分隔。但是强烈建议不要这么做,因为这样会导致代码阅读困难、维护耗时,且容易出错。
但当一条语句实在太长时,也是可以占用多行的,可以使用反斜线来实现多行语句。例如,以下字符串
可以通过使用反斜线来实现一条语句的多行表示:
方括号、花括号或圆括号中的多行语句不需要使用反斜线,直接按Enter键换行即可。下面的函数参数过多,将所有参数放在一行会导致阅读困难,可以使用多行表示以便阅读。
PEP 8建议每一行的字符不超过79个。
Python是一门弱类型语言,变量使用前无须声明,变量名可以看作对象的引用。
Python中有许多内置的基本数据类型,分为数字(number)、字符串(string)、列表(list)、元组(tuple)、字典(dict)、集合(set)以及一些不太常用的数据类型,如字节串(byte)等。这些数据类型可以分成以下几种类型。
(1)可变类型:列表、字典和集合。
(2)不可变类型:数字、字符串和元组。
(3)序列类型:字符串、列表、元组和字典。
(4)非序列类型:集合。
流程控制指的是代码运行逻辑、分支走向、循环控制,是体现程序执行顺序的操作。流程控制结构一般分为顺序结构、选择结构和循环结构。
(1)顺序结构是指程序逐行执行,所有语句都按照它们在文件中写入的顺序执行。
(2)选择结构也称分支结构,是指程序有选择地执行代码,可以跳过没用的代码,只执行有用的代码。通常有如下两种选择结构。
●条件判断:if/elif/else。
●异常处理:try/except。
(3)循环结构是指程序不断地重复执行同一段代码。通常有如下两种循环结构。
●for循环。
●while循环。
编写代码时,很多地方可能需要实现同样的功能,造成同样的一段代码重复出现。重复出现的代码可能只是一个包含3~5行的代码块,也可能是一个包含更多行的代码序列。当代码需要更新时,必须更新所有重复出现的代码,且容易出错。
此时,可以创建一个函数,包含一段重复出现的代码。每次需要重复使用这段代码时,只需调用函数即可。函数不仅允许命名代码块,还允许通过参数为函数传递不同的数据,并根据参数获得不同的结果。
函数是具有名称、可以根据需要多次调用的代码块。函数允许我们将常用的代码以固定的格式封装成一个独立的模块,只要知道这个模块的名称就可以重复使用它。
(1)函数定义
定义函数使用关键字def,def后跟函数名和圆括号,在圆括号内定义函数接收的参数,也可以不定义参数。圆括号后是冒号,函数体(函数的代码块)以冒号开始,并且统一缩进。使用return语句结束函数,默认返回None。return语句依然在函数体内部,不能回退缩进。直到函数的所有代码写完,才回退缩进,表示函数结束。
函数定义语法如下:
下面定义一个名为configure_intf的函数,函数有3个参数:intf_name、ip、mask。
注意事项如下。
●定义函数时的参数称为形式参数(简称形参)。
●当程序执行到定义函数的这段代码时,只是将这段代码载入内存,函数体中的代码只有在函数被调用时才执行。这类似网络设备中的访问控制列表(Access Control List,ACL)。在网络设备中创建的ACL,在应用之前,其不执行任何操作。
●调用函数时必须传入实际参数(简称实参)。
(2)函数调用
函数只有在被调用时才会被执行。要调用函数,必须使用函数名后跟圆括号的方式。调用时要根据函数的定义,提供相应个数和类型的参数,每个参数之间用逗号分隔。
下面调用函数configure_intf():
此时,函数被调用,执行函数体代码,输出如下:
当前的函数将执行结果输出到标准输出,不能保存到变量中。因为在定义函数时没有定义返回值,默认返回None。
(3)函数返回值
在Python中,用def语句创建函数时,可以用return语句指定该函数返回的值,返回值可以是任意类型的。需要注意的是,return语句在同一个函数中可以有多条,但只要有一条得到执行,就会直接结束函数的执行。
下面的代码改造了configure_intf(),将函数的输出通过return语句返回:
函数可以返回多个值。在这种情况下,它们由return后面的逗号分隔。实际上,此时函数将返回元组。例如:
也可以按照函数返回几个值就定义几个变量的方式来接收相应的返回值。
程序在运行过程中,总会遇到各种各样的错误。有些错误是编写代码时造成的,有些错误是不可预料,但错误完全有可能发生的,如文件不存在、网络拥塞、系统错误等。
(1)异常
在Python中,把这些导致程序在运行过程中出现异常中断或退出的错误称为异常(Exception)。
一个程序发生异常,代表该程序在执行时出现了非正常的情况,无法再执行下去。默认情况下,程序是要终止的。如果要避免程序退出,则可以使用捕获异常的方式获取异常的名称,再通过其他的逻辑代码让程序继续运行。这种根据异常做出的逻辑处理称为异常处理。
Python定义了以下3种异常处理结构。
●try/except结构;
●try/except/else结构;
●try/except/finally结构。
(2)try/except结构
try/except结构的执行流程如下。
●首先执行try块,如果程序执行过程中出现异常,则系统会自动生成一个异常类型,并将该异常提交给Python解释器,此过程被称为捕获异常。
●Python解释器收到异常时,会寻找能处理该异常的except块。如果找到合适的except块,就把该异常交给该except块处理,此过程被称为处理异常。如果Python解释器找不到能处理异常的except块,则程序终止运行,Python解释器也将退出。
下面的代码使用了try/except结构:
(3)try/except/else结构
在try/except结构的基础上,Python异常处理机制还提供了增加一个else块的结构,即try/except/else结构。
try/except/else结构的执行流程如下。
●当try块没有捕获到任何异常时,才会执行使用else包裹的代码。
●如果try块捕获到异常,则只会执行except块中的代码处理异常,不会执行else包裹的代码。
下面的代码使用了try/except/else结构:
(4)try/except/finally结构
Python异常处理机制还提供了增加一个finally块的结构,即try/except/finally结构,在整个异常处理过程中,无论try块是否捕获到异常,最终都要进入finally块,并执行其中的代码。
下面的代码使用了try/except/finally结构:
到目前为止,所有的程序都是从控制台获取输入并将执行结果输出到控制台的,实现了与用户的交互。但控制台上只能显示有限的数据,也无法反复从程序中生成数据,一旦发生意外,所有工作成果将瞬间消失。文件处理在数据需要永久存储到文件时发挥着重要作用,通过文件处理,可以读取、写入、创建、删除和更改文件。
Python提供了内置的文件对象,以及用于对文件、目录进行操作的内置模块,通过这些可以很方便地将数据保存到文件中。
在Windows上,书写路径时使用反斜线作为路径分隔符。但在OS X和Linux上,使用正斜线作为路径分隔符。如果想要程序运行在所有操作系统上,在编写程序时,就必须考虑到这两种情况。
r/R表示原始字符串。所有的字符串都是直接按照字面的意思来使用的,没有转义特殊或不能输出的字符。原始字符串第一个引号前有字母“r”(可以大写),与普通字符串有着几乎完全相同的语法。我们只需要在文件路径字符串引号前加上r或R就可以轻松处理文件路径带来的问题了。
Python中文件操作有很多种,常见的操作是对文件进行读取和写入。文件必须在打开之后才能进行操作,在操作结束之后,还应该将其关闭。因此文件操作可以分为以下3步,每一步都需要借助对应的函数实现。
●打开文件:使用内置的open()函数,该函数会返回一个文件对象。
●对已打开的文件进行读/写操作:读取文件内容,可使用read()、readline()以及readlines()函数;向文件中写入内容,可以使用write()函数。
●关闭文件:完成对文件的读/写操作之后,需要关闭文件,可以使用close()函数。
(1)打开文件
在Python中,要操作文件,首先需要创建或者打开指定的文件,并创建文件对象,而这些工作可以通过内置的open()函数完成。
●'file_name.txt'是要打开文件的名称。不仅可以指定文件名,还可以指定路径(绝对路径或相对路径)。
●'r'是文件打开模式,表示以只读的模式打开文件。open()支持更多的文件打开模式,常用的文件打开模式如表1-1所示。
表1-1 常用的文件打开模式
(2)读取文件
Python提供了如下3种函数来实现读取文件中数据的操作。
●read()函数:逐个字节或者字符读取文件中的内容。
●readline()函数:逐行读取文件中的内容。
●readlines()函数:一次性读取文件中的多行内容。
下面通过readlines()函数读取文件R1.txt,R1.txt文件内容如下:
readlines()函数操作如下:
(3)写入文件
写入文件时,指定正确的文件打开模式非常重要,以免误删。
●w:打开文件进行写入。如果文件存在,则删除其内容。
●a:打开文件以添加数据,数据添加到文件末尾。
如果文件不存在,则在这两种模式下都会创建一个文件。以下函数用于写入文件。
●write():将一行内容写入文件。
●writelines():允许将字符串列表作为参数发送到文件中。
下面通过write()函数将字符串写入文件。
(4)关闭文件
前面在介绍文件操作时,一直强调打开的文件最后一定要关闭,否则会给程序的运行造成意想不到的隐患。但是,即便使用close()函数,如果在打开文件或文件操作过程中抛出了异常,则还是无法及时关闭文件。
为了更好地避免此类问题出现,Python提供了with as语句用来操作上下文管理器(Context Manager),它能够帮助我们自动分配并且释放资源,保证文件自动关闭。
有时需要同时处理两个文件,如将从一个文件中读出的内容再写入另一个文件。在这种情况下,可以按如下方式打开两个文件:
Python提供了强大的模块支持,不仅有大量的标准模块,还有大量的第三方模块。开发者也可以开发自定义模块。这些强大的模块可以极大地提高开发者的开发效率。
模块就是Python程序,任何Python程序都可以作为模块。随着程序功能的复杂化,程序不断变大。为了便于维护,通常会将其分为多个文件(模块),这样不仅可以提高代码的可维护性,还可以提高代码的可重用性。当编写好一个模块后,若编程过程中需要用到该模块的某个功能(由变量、函数、类实现),则无须做重复性的编写工作,直接在程序中导入该模块即可。
(1)导入模块
Python中有几种方法可以导入模块。
●导入整个模块: import模块名 ,如导入sys模块,import sys。
●导入整个模块,并指定别名: import模块名as别名 。
●导入模块中的某个或某些函数: from模块名import函数名 。
●导入指定模块中的所有成员: from模块名import*。
(2)自定义模块
下面是创建自己的模块并将函数从一个模块导入另一个模块的例子。
首先,创建名为check_ip_func.py的文件,其功能是根据参数检查IP地址的正确性,返回IPv4Address或IPv6Address对象;默认情况下,小于2 32 的整数将被视为IPv4地址。如果地址不是有效的IPv4或IPv6地址,则会引发ValueError。代码如下:
上面的代码可以独立运行,执行结果如下:
其次,将check_ip_func.py文件作为模块,供其他Python程序调用。在check_ip_func.py文件同一目录下,创建名为get_correct_ip.py文件,该文件将调用check_ip_func.py模块中定义的check_ip()函数,以从地址列表中选择正确的IP地址。
执行结果如下:
从上面的执行结果中可以看到,Python解释器将模块check_ip_func.py中的代码也一块儿执行了,执行结果中的前3行就是模块check_ip_func.py的执行结果,但这并不是我们想要的结果。
想要避免这种情况的关键在于,要让Python解释器知道,当前要运行的程序是模块本身,还是导入模块的其他程序。在模块check_ip_func.py中,仅仅定义函数,不需要其他的代码,修改后的代码如下:
而get_correct_ip.py程序保持不变,在执行get_correct_ip.py后即可得到我们想要的结果:
下面将介绍常用的3个网络编程模块:ipaddress模块、netaddr模块和tabulate模块。
(1)ipaddress模块
该模块包括IPv4和IPv6地址的类,可以用来生成、验证、查找IP地址。从Python 3.3开始,ipaddress模块正式成为Python标准库中的模块之一,不需要安装,可直接使用。
ipaddress模块中有IPv4Address类和IPv6Address类,可分别用来处理IPv4和IPv6地址。由于IPv4Address和IPv6Address对象共享许多共同属性,下面的案例将只处理IPv4格式,可以以类似的方式处理IPv6格式。
●ipaddress.ip_address()函数会根据传入的字符串自动创建IPv4/IPv6 Address对象。
也可以使用正整数来创建地址,默认小于2 32 的整数是IPv4地址,大于2 32 的整数则是IPv6地址。
使用ipaddress.ip_address()创建IPv4Address对象有很多IPv4地址的属性。
●使用ipaddress.ip_network()函数创建IPv4/IPv6Address对象。
ipaddress.ip_network()函数允许划分网络(子网划分)。默认情况下,它将网络划分为两个子网。
通过prefixlen_diff参数设置允许指定子网的位数。
●ipaddress.ip_interface()函数允许创建IPv4或IPv6接口对象。
(2)netaddr模块
该模块是Python处理IP地址和MAC地址的开源第三方库,是用于对网络地址段进行定义和操作的一个工具类。通过netaddr模块可以以非常灵活的方式定义网段,获取网段的一些常用信息,同时可以和网络地址与网段进行一些包含关系的运算。
netaddr模块不是Python的标准模块,使用前需要安装。
●IPAddress对象表示单个IP地址,可以接收一个IPv4或IPv6地址字符串。
●IPNetwork()是处理IP网段的方法,同样可以接收一个IPv4或IPv6地址字符串。
●cidr_merge()是网段的汇总方法,它只接收列表,列表中必须含有要汇总的网段。
●EUI()是netaddr格式化MAC地址的方法,可以接收任何表达形式的MAC地址字符串。
(3)tabulate模块
通过tabulate模块可以精美地显示数据。它不是Python标准库,因此需要先进行安装。
●tabulate模块支持列表、字典等表格数据类型。模块中的tabulate()函数用于制表。
●tabulate()函数还可以使用headers参数指定列名。
●tabulate()函数还可以使用参数tablefmt输出网格。