欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python入门

程序员文章站 2024-01-14 19:55:52
...

python入门


​ 学习物联网时候有很多语言可以选择,对于python这一个新星,很多人还不熟悉他在物联网中的用法。随着物联网中硬件的降低,人力成本的增加,快速开发产品其实也是一个非常关键的点,python语言因为其简单的语法、丰富的第三方库,在各个领域都受到欢迎,如果想开发一个物联网产品,你可以选择先用python进行快速开发,将需要提升速度的地方用c/c++重写。这章将从0开始搭建python运行环境,并在此基础上进行python的语法学习。希望读者可以掌握python这门看似简单的语言,编写复杂的python代码。

3.1学习新语言的步骤

​ 对于已经熟练掌握一种面向过程和面向对象思想的人来说,学习使用一门新的语言真的不需要话太多时间,在我看来一上午就够了。如果要把这门语言当做主要的工作学习编程语言,那就要另说了,在学习这些语言过程中我们首先要知道这门语言的设计初衷,它是为了解决什么问题而创造的,第二就要知道它是什么类型的语言,应用在哪些领域。支持面向对象和面向过程两种思想的语言在本质上有所不同。第三就是要学习基本的语法,对于这些语法无外乎基本数据类型,变量,三大控制语句,函数的集中使用方法,高级数据类型,类,异常处理,多线程进程,网络开发,一些该语言本身具有的特性。学完基础语法之后可以了解该语言的常用库,在github上找python项目,综合起来学习一下。差不多做到这就已经掌握了python语言。

3.1.1为什么要设计python?

Python是由吉多·范罗苏姆创造,第一版发布于1991年,可以视之为一种改良的LISP。Python的设计哲学强调代码的可读性和简洁的语法。相比于C++或Java,Python让开发者能够用更少的代码表达想法。有一句流行的话:人生苦短,我用python。

3.1.2 python的应用领域

Web 和 Internet开发:socket,ftp等
科学计算和统计:numpy,matlibplot
人工智能:深度学习
教育:学习编程语言
桌面界面开发:pyqt等
软件开发:定义广泛
后端开发:Django等框架开发

3.2搭建python开发环境

3.2.1安装的说明

通常大家都会选择将python集成开发环境安装到windows下,开发环境对新手友好。在物联网中我们需要使用linux系统作为后台服务器,所以在本章节我们在电脑虚拟机上安装ubuntu系统,window下安装anconda+vscode,在window下编写代码在虚拟机ubuntu下运行web后台服务器和嵌入式。因为本书的写作原因,建议读者可以将环境全部放入ubuntu中,这样有利于开发。

3.2.2 python版本的选择

python具有两个版本,初学者可能会纠结二者的区别,现在大多数的开发人员都会选择python3,而且python3在字符编码,多线程上进行了优化,现在深度学习的框架等第三方安装包对python3大的支持也是越来越大。本书选择的是python3进行开发。

3.2.3 安装anconda 和vscode…

​ anaconda是Python的一个开源发行版本,主要面向科学计算。我们可以简单理解为,Anaconda是一个预装了很多我们用的到或用不到的第三方库的Python。而且相比于大家熟悉的pip install命令,Anaconda中增加了conda install命令。当你熟悉了Anaconda以后会发现,conda install会比pip install更方便一些。比如大家经常烦恼的lxml包的问题,在Windows下pip是无法顺利安装的,而conda命令则可以。

下载

从官网下载:https://www.anaconda.com/download/不过官网速度比较慢,不太推荐。

与Python相对应,Anaconda的版本分为Anaconda2和Anaconda3,大家可以自行下载日常常用的版本,提供32位和64位下载。(2.x和3.x混用的同学也不要担心,后面我会讲具体的版本管理)

当然了,如果你真的选择去官网下载Anaconda的话会发现,速度慢到令人发指;当你等待了30多分钟下载安装完以后想要安装或者更新其中的包时,又会发现其速度慢到会断开连接安装报错……

从清华镜像下载:Tsinghua Open Source Mirror(推荐)

选择相应的版本进行下载就好(直接找2019年最新版的Anaconda2或Anaconda3)。

安装

下载完成后安装。C盘不吃紧的同学可以一路next,C盘如果吃紧最好换个地方,日积月累Anaconda会占用不小的地方……

下载过程中除了安装位置外,还有要确定下图中第一个将anaconda添加到环境变量中去,这样cmd命令行可以直接使用conda的一些命令,第二个是否默认安装3.6,可能现在的版本已经到了3.7,这里我建议不要安装,自己在后面的环境搭建中安装。如果是3.6的话没问题,因为目前一些深度学习框架并不支持3.7.

python入门

安装完成之后最近安装里面出现如下的软件:

python入门

测试安装

一路安装完成以后,就可以打开cmd测试一下安装结果。

分别输入python、ipython、conda、jupyter notebook等命令,会看到相应的结果,说明安装成功。(python是进入python交互命令行;ipython是进入ipython交互命令行,很强大;conda是Anaconda的配置命令;jupyter notebook则会启动Web端的ipython notebook)

配置下载地址

设置国内镜像
别着急,现在还没完事呢。
如果你现在就猴急猴急地去安装很多packages,你会被conda的龟速感动得声泪俱下,因为Anaconda.org的服务器在国外。所幸的是,清华TUNA镜像源有Anaconda仓库的镜像,我们将其加入conda的配置即可。

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/

conda config --set show_channel_urls yes

设置一个新的python环境

# 查看当前环境下已安装的包
 
conda list
 
# 查看某个指定环境的已安装包
 
conda list -n python36
 
# 查找package信息
 
conda search numpy
 
# 安装package
 
conda install -n python36 numpy# 如果不用-n指定环境名称,则被安装在当前活跃环境# 也可以通过-c指定通过某个channel安装
 
# 更新package
 
conda update numpy
 
# 删除package
 
conda remove numpy

使用jupyter notebook编辑代码

​ Jupyter Notebook是Ipython的升级版,而Ipython可以说是一个加强版的交互式 Shell,也就是说,它比在terminal里运行python会更方便,界面更友好,功能也更强大。怎么强大法,往下看就知道了。 此架构的一个优点是,内核无需运行 Python。由于 notebook 和内核分开,因此可以在两者之间发送任何语言的代码。例如,早期的两个非 Python 内核分别是 R 语言和 Julia 语言。使用 R 内核时,用 R 编写的代码将发送给执行该代码的 R 内核,这与在 Python 内核上运行 Python 代码完全一样。IPython notebook 已被改名,因为 notebook 变得与编程语言无关。新的名称 Jupyter 由 Julia、Python 和 R 组合而成。如果有兴趣,不妨看看可用内核的列表。 另一个优点是,你可以在任何地方运行 notebook 服务器,并且可通过互联网访问服务器。通常,你会在存储所有数据和 notebook 文件的自有计算机上运行服务器。但是,你也可以在远程计算机或云实例(如 Amazon 的 EC2)上设置服务器。之后,你就可以在世界上任何地方通过浏览器访问 notebook。

如图点击运行即在浏览器中使用notebook编写python代码:

python入门

进入到浏览器之后点击下图的python3进入ipython文件,编写Python代码。

python入门

效果如下:

python入门

3.3搭建虚拟机ubuntu环境

​ 如果你想在ubuntu下开发python是十分方便的,anaconda和vscode都支持在unbuntu下安装。这节我们只使用虚拟机环境进行嵌入式开发和服务器后台开发,在window下开发的代码完全可以直接拖入虚拟机中执行。

3.3.1安装vmware

vmware(virtual machine ware)是虚拟运行环境,他可以在window下运行其他操作系统,如果你本身装有ubuntu系统,那么你也可以安装vm加载window系统。虚拟机安装系统有一个优势就是不用切换系统,如果安装双系统的话需要重启才能进入下一个系统,目前vm适合一些比较大的系统,在后面如果使用docker的话,完成可以使用一些轻量级的虚拟软件。

安装vmware首先需要进入官网进行下载,我安装的14版本,一般安装最新的,vm都是向下兼容。在安装过程中没有多余的选项,直接安装即可。如果你没有购买秘钥的话只能免费使用三十天。有的话直接输入秘钥即可使用。安装完成后打开的界面如下:

python入门

3.3.2 在虚拟机上安装ubuntu

在ubuntu的官网:www.ubuntu.com上下载16.04桌面版本iso镜像,下载完成之后在vm上文件菜单栏里点击新建虚拟机,在弹出对话框选择典型后出现下图:

python入门

选择好下载完成iso镜像文件点击下一步设置密码和路径,之后其他都可以默认配置参数,最后一步结束后出现下面的画面:

python入门

等待一段时间后ubuntu系统安装完成。
python入门

3.3.2 虚拟机的网络配置

虚拟机配置网络不是一个简单的事情,我们在开发过程中会使用ubuntu模拟服务器进行物联网服务器端的开发和测试,然后将通过测试的代码放入到真实的服务器上,并且在ubuntu下还要进行一些软件的下载,因此需要对虚拟机的网络进行配置。打开虚拟机网络连接界面一共有以下两种配置方式:
桥接模式:在这种模式下,VMWare虚拟出来的操作系统就像是局域网中的一*立的主机,它可以访问网内任何一台机器,你需要手工为虚拟系统配置IP地址、子网掩码,而且还要和宿主机器处于同一网段,这样虚拟系统才能和宿主机器进行通信,虚拟系统和宿主机器的关系,就像连接在同一个Hub上的两台电脑。从网络技术上理解相当于在宿主机前端加设了一个虚拟交换机,然后宿主机和所有虚拟机共享这个交换机。
使用NAT模式,就是让虚拟系统借助NAT的功能,通过宿主机所在的网络来访问公网。在这种模式下宿主机成为双网卡主机,同时参与现有的宿主局域网和新建的虚拟局域网,但由于加设了一个虚拟的NAT服务器,使得虚拟局域网内的虚拟机在对外访问时,使用的则是宿主机的IP地址,这样从外部网络来看,只能看到宿主机,完全看不到新建的虚拟局域网。
采用NAT模式最大的优势是虚拟系统接入互联网非常简单,你不需要进行任何其他的配置,只需要宿主机器能访问互联网即可。如果你想利用VMWare安装一个新的虚拟系统,在虚拟系统中不用进行任何手工配置就能直接访问互联网,建议你采用NAT模式。

​ 在这本书中我们前面需要下载一些软件安装包,并且我使用的校园网有些特殊,并不是设置好IP就能连接到互联网,所以在下载软件的时候切换到NAT模式,但是在后面服务器端口程序开发以及和网关通信的时候选择桥接模式,并且执行ifconfig eth0 命令设置成同一网段。读者如果不了解统一网段的知识建议在网络上查找一些网段划分、主机号、子网掩码的博客进行学习。

python入门

3.3.3修改python的版本

ubuntu中默认安装python,但是这个python的版本是2.7,我们需要将默认的python环境安装成3.*版本。在ubuntu中ctrl+alt+T打开终端,输入:

python -V 查看当前的python版本

cd /usr/bin 进入到用户的可执行命令目录

ls -l python* 显示当前系统安装的python版本

sudo rm python 删除2.7的链接

ln -s python3 python 更换成3版本

python -V 再次查看版本

gedit hello.py 打开一个新的python文件文件里输入print("hello")

python hello.py 执行刚才编写的程序。

如果能成功显示hello代表环境已经配置完成,至此ubuntu下python和windows下python都已经搭建好,在学习基础语法的时候两个环境都可以,但是如果涉及到后面图像处理等需求,ubuntu里面还需要安装anconda。

3.4开始python之旅

​ 终于到了激动人心的hello world ,其实从0开始编写代码输出helloworld也不容易,恭喜你完成了这一步,相信你在搭建环境的过程中会遇到一些问题,别害怕,别气托,遇到问题时正是该提升水平的时候,一条路不可能一帆风顺的走下去,除非那条路是一条不归路。好了,我们开始学习python的基本语法。

3.4.1 python的输入和输出

python语言是一个解释型语言,具有很好地脚本语言性质。对于脚本语言来说不用写主函数即可运行,像C语言这种高级语言就必须写main函数了。在c语言中使用scanf和printf函数作为标准输入输出,而在pyhton中使用input和print做为输入输出。

inputData = input("请输出你想输入的内容")
temp = "hello"
print("inputData")
print(inputData)
print("你输入的值为:"+inputData)
print("你输入的值为:",inputData)
print("%s 你输入的值为: %s" %(temp,inputData))



请输出你想输入的内容0
inputData
0
你输入的值为:0
你输入的值为: 0
hello 你输入的值为: 0

代码第一行我们调用了input函数,将输入的值返回到inputData这个变量中,然后我们调用几种print的方法,第一个print直接输出了引号的内容,第二个输出了inputData变量里的内容,第三个和第四个都是将变量和引号内容结合起来的方法。第七行是一个多输入的使用方法,注意“”与变量中间并没有,相隔。在print语句中可以使用一些特殊字符,他们可以控制输出的格式。

特殊字符 含义
单引号
\ 下一行也归属到这条执行命令
\b 退格
\n 换行
\r 回车
\t tab
\x 十六进制

3.4.2 代码编写规则

python语言和其他语言相比,在编写规则上有一个很大的优势。它不是通过;来确定一条执行语句的结束,也不通过{}来确定同一循环体的范围。它是用过空格和缩进来确定程序运行的顺序。在编写过程中通常用tab代替四个空格,但是在有些情况下tab不一定代表4个空格,如果你在使用别人写好的代码时候发现出现空格错误,但是自己感觉没啥问题,那么很可能是tab的原因,这时候就需要使用notepeed等软件查看确定的空格数,进行修改。

var = 3
temp = 2
if var >1:
    if temp ==2:
         print(temp)     
else :
    if temp == 2:
        print("nothing")
2

在上面的程序中我们可以看到控制语句没有(),也没有{},只有:,这就是python的控制语法,同一列下的代码构成一个代码块。正常的执行语句中并没有;,这个可以看做是系统默认添加了。对于代码过长的语句也可以使用\在第二行继续写。

3.4.3 命名规范和注释

如果写一首好的代码要从这两件事情抓起。对于一些代码量的代码,如果不遵循团队的命名规范并且注释很少,会给团队的其他人带来很大的烦恼,有时候甚至不如人家自己从头写,其他人阅读你的代码是一件让人很开心的事情,但是如果你不注重代码规范和注释的话这种开心有可能只有一次。

命名规范:

  • 变量:变量名长一些并没有关系,关键的变量一定要让变量名体现出他的本身作用。在编写过程中一般有两种命名方式,一种是在每个单词中间加-,如要声明一个保存你密码的变量,在这里就是my_passwd,还有一种就是首字母不大写,后面字母全部大写的命名规则,这也是很多公司通用的命名规则,myPasswd。这里笔者采用第二中方式进行变量申请。
  • 函数: 函数一般都是所有首字母大写,如果定义一个保存密码的函数,则可以申明为MyPasswd(),这样也可以区别变量myPasswd.
  • 类:类一般都是首字母大写。

注释:
一般代码量过大的工程,需要至少有百分之25以上的注释。在注释的时候有两种方式,一种# 是用在单行注释,它是用来说明某行代码或者某个函数的功能,多行注释用在代码量比较大的函数中或者文件开始,介绍文件的一些信息。合适充足的注释对于读者和自己都有好处,很多程序你放到后面可能自己都不知道当时怎么想的了,注释能够帮助你快速回忆起来。

"""
data:2019.10.03
author:liupeng
function: nothing
"""
#print("hello")
print("hello")
hello

3.5 python基础

本节我们学习python数据类型,这些数据类型的变量操作方法,以及条件和循环的使用方法。

3.5.1 变量

不同数据类型的变量和常量属性不一样,一些高级的数据类型可能并没有常量。在程序声明变量的时候,计算机会将内存中一块相应大小分配给它,如果你理解了这些变量在内存中的分布,你可以很好的调用变量的每个子元素,就像C语言的指针一样灵活。在python中没有常量,也没有const这样的静态数据类型声明,其实没有常量我觉得因为是动态语言可以理解,但是const没有的话确实尴尬,可能是因为保持动态性吧。在声明变量的时候它和c/c++的规则一样,由大小写字母,下划线和数字组成,但是数字不能打头。

temp  = "hello" #因为一个单词就表达了暂时的意思,如果变量用两个单词进行表示,那么使用tempAbb。
print(temp)
hello

3.5.2数据类型

数据类型代表着数据在内存中的存储类型,对于一门语言来说,数字,字符串,数组是必备的,但是不同语言在数组上会有一些改动,或者添加新的数据类型。python的数据类型有以下6种类型:

  1. 数字
  2. 字符串
  3. 列表
  4. 元组
  5. 集合
  6. 字典(其实和C语言中的枚举一样)

数字:
python有4种类型;int,float,complex,bool。
int类型其实还有无符号整型,有符号整型,长整型,短整型。
float类型可以保存小数。
bool类型有两种True,False两种值,也可以使用0,1代替。
complex复数类型表达使用complex(a,b),其中a,b都是float类型。

a = 1
b = 1.1
c = True
d =complex(2.2,3.5)
print(a)
print(b)
print(c)
print(d)
1
1.1
True
(2.2+3.5j)

字符串在python中用法方便且灵活。在python中不区分单引号和双引号,'abc’和"abc"是一样的,如果在引号内容上使用引号需要用\来进行转义,让编译器能够识别。字符串的访问从0开始,0向右就是1234,但是如果从后面来计的话就是-1,-2,-3。访问的时候加入一个字符串有9个字符,那么a[-1]和a[8]代表的是同一个字符。另外字符串也可以进行加乘运算。在python里面字符串属于一个类,所以要修改的话需要调用相关的函数进行修改,不能通过a[]=‘a’进行修改,可以使用a.replace()
字符串使用方法如下:

string = "hello"
print(string)
print(string[0])
print(string[-1])
print(string[1:6])
print(string*3)
print(string + "world")
print("%s" %string )
print("\"i  am ok\"")
hello
h
o
ello
hellohellohello
helloworld
hello
"i  am ok"

列表是任意对象的集合,在列表内每个子元素数据类型可以不相同,具有很好地灵活性。列表中的元素是可以进行修改的,用[]来表示,它里面重载了很多通用预算符
方法。元组和列表十分相似,使用(),元组定义好之后内部的元素是不允许修改的。下面用一个例子比较一下区别:

listOne = [55,"hello",True]
tupOne =(55,"hello",True)
print(listOne)
print(tupOne)
print(listOne[-1])
print(tupOne[-1])
print(listOne[1:])
print(tupOne[1:])
print(listOne[1]*2)
print(tupOne[1]*2)
listOne[1] = "world"
print(listOne)
tupOne[1] = "world"#会报错,元组不可改变
[55, 'hello', True]
(55, 'hello', True)
True
True
['hello', True]
('hello', True)
hellohello
hellohello
[55, 'world', True]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-37-88976b7f20ea> in <module>()
     11 listOne[1] = "world"
     12 print(listOne)
---> 13 tupOne[1] = "world"
TypeError: 'tuple' object does not support item assignment

集合和字典可以放到一起来学习,相比较元组和列表,他们不可以通过索引1234访问。集合使用{}进行数据的存储,在存储的子集合里面不能有完全重复的,因为不按照***进行存储,
如果还保持一样的话那么将无法工作了。字典和列表有一个相似的地方,保存对象,不过他不是通过1234进行查找索引的,而是通过键值进行索引。字典是一对一的键key:值value组成的无序集合。键只能使用不可变的数据类型,不能用列表。

#编写集合测试代码:
temp = {1,2,3.4,"five"}
test = {7,5,3.4,"six"}
print(temp)
if 5 in temp:
    print("5 is in temp")
else: 
    print("5 is ont  in temp")
print(temp&test)#交集
print(temp-test)#差集
print(temp|test)#合并

{1, 2, 3.4, 'five'}
5 is ont  in temp
{3.4}
{1, 2, 'five'}
{1, 2, 3.4, 5, 7, 'six', 'five'}

#编写字典测试代码:
dictOne = {"name":"liupeng","sex":"man"}

print(dictOne["name"])
print(dictOne.keys())
print(dictOne.values())
print(dictOne.values())
dictOne["name"] = "zhangsan"
print(dictOne["name"])


liupeng
dict_keys(['name', 'sex'])
dict_values(['liupeng', 'man'])
dict_values(['liupeng', 'man'])
zhangsan

补充:这一节学习了一些基本的数据类型,并且学会定义了如何操作这些类型的变量。在讲解完函数之后我们在学习变量的作用域和一些数据类型的高级用法。

3.5.3 控制语句

学习完之数据类型之后就要学习语言的控制语句,通常来说控制语句有四个:if elif else, while break, for break,switch case。switch在python中没有,其实goto也算一个控制语句,但是它的出现会破坏程序的可读性,所以一般大家都不会去使用它。通过基本数据类型,输入输出函数和控制语句可以实现小黑窗口里的所有算法题目了。这也是任何语言的三个基本点。讲语句之前需要知道几种条件运算算符:==(等于),!=不等于 >大于,>=大于或者等于,<小于,<=小于或者等于。他们在控制语句中经常用到。

if语句的用法

inputData = int(input("请输入数字"))
if inputData ==1:
    print("this data is 1")
elif inputData ==2:
    print("this data is 2")
else :
    print("this data is is not 1 or 2")    



请输入数字2
this data is 2

短短的7行代码就已经将if语句的用法汇聚到其中。如果需要添加更深一层的if语句,请注意空格。

while语句的用法

inputData = int(input("请输入数字"))
while inputData >0:
    if inputData ==4:
        inputData -=1
        continue;
    if inputData ==1:
        break;   
    print("%d\n" %inputData)
    inputData -=1
    
print("end!")

请输入数字5
5

3

2

end!

while语句条件判断如果一直为真的话就会一直执行下去,遇到break会直接跳出去,执行while子程序框外的程序指令,如果遇到continue会跳过循环体内continue后的指令,进入下一次循环。

for 循环

一般for循环使用range()生成一个序列进行依次遍历,这和c语言中的for()循环概念一致。

n = 0
for n in range(1,6):
    if n==3:
        continue
        print(n)
    else:
        print(n)

1
2
4
5

3.6函数

函数式可以重复利用的代码段,他可以使程序模块化、提高程序代码的重复利用,函数式python程序的重要组成部分。

3.6.1函数的定义与调用

格式:
def 函数名(参数):
函数体
定义一个函数首先使用def关键字,然后输入函数名,注意函数名大写的编程规则。在传参()输入参数的名字,对于()内的使用还需要再起一个小节去说。完成了第一行之后需要将该函数的内部逻辑放入函数体内。
代码如下:

def PrintName(name):
    print(name)
    return 
printname("hello")#代码中第四行调用了定义好的PrintName函数

hello

3.6.2 函数的参数

确定了函数的参数名称和数量之后,函数的调用者就只需要将函数的对应参数正确天啊如其中就可以得到该function的返回值,其实对于python而言,参数灵活性很大,易操作性强。函数参数主要有以下几种:必须参数,默认参数,可变参数,关键字参数,命名关键字参数。

def PrintName(name):#name是必须参数,在调用的时候不能缺省。
    print(name)
    return 
PrintName("hello")

hello

def PrintName(name,sex="man"):#name是必须参数,在调用的时候不能缺省,sex是默认参数,调用时候省略会自动看做是man
    print(name,sex)
    return 
PrintName("liu")
PrintName("liu","wuman")

liu man
liu wuman

很多时候我们输入的值是多个且不确定的话,这时候就需要定义可变变量,可变变量就是在变量前加*,传入的参数会被构建成元组这种可以索引的数据类型。例如下面整数求和例子。

def Sum(*a):
    sum = 0
    for n in a:
        sum = sum + n
    return sum
b= Sum(1,2,3,4,5)
print(b)

15

关键字参数是在变量上添加两个* 在函数调用时候使用关键字参数不仅需要传入数据,还要加上参数名,这样会被自动组装成一个字典(key是参数名,键值是数据)

def PrintName(name,sex="man",**others):#name是必须参数,在调用的时候不能缺省,sex是默认参数,调用时候省略会自动看做是man
    print(name,sex,others)
    return 
PrintName("liu")
PrintName("liu","wuman",age=30)
liu man {}
liu wuman {'age': 30}

3.6.3内置函数

在python环境里内置了一些经常使用的函数如数学类abs(),min(),max(),pow(),int(),float(),str(),hex(),list(),print(),cmp(),type(),len()。对于上面的这些函数大家会经常用。

3.7基础python补充

在说完变量,数据类型,控制语句,函数之后还有一些关键知识并没有说到,在这里介绍一下:

3.7.1变量:

变量按作用域作用域分为全局变量和局部变量,全局变量是指在函数外声明的变量,局部变量的生命周期在函数体内,当函数退出时候局部变量就会被销毁,全部变量在整个程序的生命周期都存在。在函数体内我们可以访问到全局变量的值,但是如果想要修改全局变量的值需要进行global 声明。

num = 1 
def ShowData():
    global num
    print(num) 
    num = 2 
    print(num)
ShowData()
1
2

在python中变量的作用域有局部,闭包函数外的函数作用域,全局,内建作用域,函数体内会依次寻找变量。

3.7.2导入模块与包:

随着程序的复杂性提高,为了更好的管理代码,通过模块与包的方式避免变量的重复,相同的名字的函数和变量可以分别在不同的模块中定义和使用。
在同一路径下创建module.py,定义一个ShowData()函数输出输入的变量。新建一个文件调用该模块:

import module
module.ShowData("liupeng")
liupeng

也可以使用from module import ShowData,这样在使用的时候可以不用加包名,但是要注意变量的重名。
from module import *代表着将module里的所有函数都导入其中。

from module import ShowData
ShowData("liupeng")
liupeng

为了解决变量的重名,我们可以将导入模块所在文件放入到一个目录中去并加上_init_.py,多个这样的文件就可以称作是包,这样导入时候就是两级关系,可以有效的避免
重命名的问题。可以使用下面的格式:
moduleOne/
--------init.py
--------module.py
调用方法:import muduleOne.module

3.7.3异常处理

每个程序都有自己的异常处理,在可能会发生异常的地方提前异常处理可以避免程序发生终止。常用的三步与处理机制try: except : finally:
异常处理测试代码为:

try :
    print("try")
    x= 2/0
    print(x)
except ZeroDivisionError :
    print("抛出异常",ZeroDivisionError)
finally:
    print("ok")

    
try
抛出异常 <class 'ZeroDivisionError'>
ok

内置函数里面往往抛出了各种异常,如果想要自己的抛出异常需要定义一个类,并且在会发生异常函数里使用raise error(***)语句。

3.7.4字符编码

ascii码是大家都知道的通用编码,用八个位的数据表示127个字符,这些字符包括大小写字母和常用字符。这里需要记住65代表A,97代表a,依次往前推就行。随着各个国家加入互联网,ascii码已经不符合需求,后来出现了unicode,他可以将所有语言通过两个字节来表示,但是这样要比ASCII码多出一倍的存储空间,为此发明了utf-8(可变长度字符编码),我们使用3个字节代表汉子,1个字节编码英文,这样灵活的设置更好的节省了空间。通常情况下字符串在电脑上使用unicode编码,但是在传输和存储过程中还是转换为utf-8编码方式。
如从存储介质或者传输流内获取的字节类型数据在使用过程中需要转换成字符串使用的unicode编码格式。

x = b"hello"
print(type(x))#字节类型
y = x.decode()
print(type(y))#unicode类型

<class 'bytes'>
<class 'str'>

3.7.5数据类型的函数

在python中基础基础数据类型其实都是类,对于类来说自然就有很多内置方法。pyhton中对于元组,列表,集合,字典有很多类的方法可以调用,如输出变量的最小值,添加数据,查找数据等,如果读者有这个需求的话可以自己查找一下,在这里就不一一列出了。

3.8 python高级用法

python语言作为一门高级语言,也有自己的高级用法。如生成器,迭代器,高阶函数,装饰器

3.8.1生成器

在python中我们通常使用列举法定义列表变量,实际上我们可以使用函数来直接生成列表,这样减少了手动的工作量。如range(start,end)函数会生成
start到end的整型列表。如:

listOne = range(1,5)
for n in listOne:
    print(n)
    

1
2
3
4

但是如果想要生成一些线性的列表,单单使用range函数还行,需要加入列表推导式,他可以对range生成的公式再进行转换。

listOne = [num*num for num in range(1,5) if num!=2]#s输出1 9 16 
print(listOne)

[1, 9, 16]

在上面的基础上我们再提出一个问题,如果要生成1W个数据,我们该如何进行处理?将这些数据全部放入内存对电脑会造成相当大的负载,python为了解决
这个问题提出了生成器generator,我们可以通过生成器表达式将列表改造成一个生成器,只有在用到里面的数据时候在生成数据。列表里面存储的是元素本身,而生成器里面存储的是算法,程序通过调用next()算法生成元素。将列表推导式[]换成()就是一个生成器了。

listOne = (num*num for num in range(1,5) if num!=2)#s输出1 9 16 
print(listOne)
print(next(listOne))
print(next(listOne))
print(next(listOne))
<generator object <genexpr> at 0x000002C4736A3A98>
1
9
16

生成器除了是表达式,也可以函数,生成器是在函数内使用yield。这样使得代码更简洁,生成器内存占用非常少(因为没有创建中间变量来存储),
运行方式也有很大不同(普通函数顺序执行,而生成器遇到yield就返回了,再次调用的时候从返回的那一步继续执行)。
代码如下:

def square(n):
    for num in range(n):
        print("start")
        yield num * num
        print("end")

for num in square(3):
    print(num)
start
0
end
start
1
end
start
4
end

3.8.2 迭代器

迭代器是用在循环语句中的,在学习前面的知识之后我们可以知道字符串,列表,元组,字典和集合以及生成器可以用在for循环中,所以他们是
可迭代的对象,而生成器这些可以不停产生下一个值的对象叫做迭代器。通过isinstance方法可以判断对象是不是可迭代的,是不是迭代器。
对于不是迭代器的可迭代对象可以使用iter()进行转换。具体使用方法如下:

from collections import Iterator#迭代器 
print(isinstance([],Iterator))
isinstance(iter([]),Iterator)


False

True

#使用for循环遍历迭代器
list = [1,2,3,4]
for n in iter(list):
    print(n)

1
2
3
4

3.8.3 高级函数

函数式编程是一个很好的编程方式,纯函数式编程编写的函数没有变量,但是python中还是允许在函数中使用变量,因此python不是纯函数式编程语言。近年来一些工程师发现使用面向对象的思想编程虽然符合人的思维,但在前期框架思考和后期代码重构上一点也不麻烦。函数式编程优点有以下几点:

  1. 模块化
  2. 易于调试
  3. 提高效率
    高阶函数是指把函数名作为参数传入到另外一个变量中去:
def ADD(numOne,numTwo,function):
    return function(numOne)+function(numTwo)
print(ADD(-1,-4,abs))#高级函数
5

其实python内部内置了一些高级函数,如sorted()排序,map映射函数等,这些函数在内部都调用了一些其他函数。搞基函数除了可以接受函数参数外,还能
在函数内部嵌入子函数,并且子函数可以作为结果返回出去。带子函数的高级函数称作闭包,这也是为什么函数式编程可以不需要变量的原因,可以返回实体。

3.8.4装饰器

装饰器是指在不修改源代码的情况下动态添加新功能的机制,装饰器这个词意思也是想要表达这样一个效果,额外添加的意思。比如在sleep1S的函数上上实现计时功能:

import time
def calcTime(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        interval = endTime- startTime
        print("it pass %f secs"%interval)
    return wrapper

@calcTime
def TimeTest():
    time.sleep(1)
    print("it pass %f secs")
TimeTest()
    
    
    
it pass %f secs
it pass 1.000819 secs

这里@calcTime相当于申明了一个装饰器,这个装饰器放在TimeTest,相当于执行了高级函数:TimeTest = calcTime(TimeTest)。装饰器还有很多的用法,比如带参数,带多参数。在实际应用中,装饰器可以无需修改代码就可以为程序添加新的功能,在web开发时候这一点十分方便,比如每个人都可以看新闻,但是如果有人想评论的话就可以使用装饰器添加一个登陆功能。

3.8.5 匿名函数

匿名函数格式为lambda 参数:表达式。这种用法用在逻辑较短的函数逻辑中,可以不懂定义函数名,也避免了函数名称的冲突。通过匿名函数实现一个求和函数:

fun = lambda x,y:x+y
print(fun(1,5))

6

3.9面向对象编程

面向对象是和面向过程不同的实现观念,它将所有物体都看做成一个类,并且通过类来描述一个事物的属性和行为。我们将属性成为类的变量,行为成为类的方法。在面向过程的思想中,我们首先要考虑的就是如何解决这个问题,程序该怎么执行。在面向对象编程思想中,首先要考虑的是如何构造这个物体,这个物体具有哪些属性,哪些方法,通过哪种方法或者哪几种方法组和可以解决这个问题。

比如我想要实现输出一个人的名字,有两种实现的方法:

#面向过程
def PrintName(name):
    print(name)
PrintName("liu")

liu
class Person(object):
    def __init__(self,name):
        self.name =name
    def PrintName(self):
        print(self.name)
Liu = Person("liu")
Liu.PrintName()

liu

上面是两种编程思想的实现方式,但从代码量来看第一种代码少很多,第二种更易懂一些。面向对象是贴近生活的表达,class是python中的关键字,专门用来申明类这个数据类型,calss后面需要跟着类名(),括号里存放的是继承类,这里并没有实现继承,所以就使用object。第二行def __init__函数是类的初始化函数,可以在申请一个实体时候传入一些必要的参数,并且在这个function中要把所有的类成员变量都初始化一下,不然它就不能称作是一个实体。除了init和del这两个类函数,其他的函数都是叫做方法,在定义一个方法的时候函数第一个参数必须是self,这是为了和普通函数进行区分,self代表了该类实体,所以在类中使用基础类型变量也请加上self,不然python会认为你在重新定义一个新变量。如果成员变量不是在方法里访问,可以不用加self。
面向对象的三大特性是封装,继承,多态。在上面的这个例子中体现了封装这一特性,类person将数据和方法封装到了一个抽象化的类中,然后通过变量申请将这个类实体化。后面会继续说继承以及多态。

3.9.1类和对象

前面我们已经定义了一个类,并且实现了对象的方法调用。我们可以发现实际类就是一个高级的数据结构,数据结构等于数据类型+算法,而对象就是承载这些数据结构和算法的一个实体。再举个例子,葫芦娃七兄弟中最厉害的一个应该是七娃,他有一个很强大的葫芦,而我们在实际开发中要造这个葫芦,使用它,将这个葫芦添加更多的方法和属性,这样葫芦会变得更厉害一些。

3.9.2 类成员变量和方法的访问权限

上文中讲到的封装其实还差一个访问权限,对于一些类的隐私变量是不能让外部的程序访问到的,只能通过自己编写的方法进行调用。为什么这样做呢?假如一个软件开发工程师设计一个类公开存储着一个人的银行账号和密码,并设置了存钱取钱这两个操作,这种情况是十分危险的,本来其他开发人员只能通过你设计好的存钱和取钱方法调用这张卡,如果不小心访问到了你的银行卡密码,对客户是一个危险的情况。所以pyhton在类访问权限上也设计了语法,在不想被外部指令直接访问的变量和方法前加__,参考如下代码:

class  BankCard(object):
    def __init__(self,key,id):
        self.idCard = id
        self.key = key
    def GetMoney(self,num):
        if self.idCard ==1111&self.key==1111 :
            print("ok")
        else:
            print("error")
        return 
    def ShowMoney(self):
        print("10000")
myCard = BankCard(1111,1111)
myCard.GetMoney(500)
myCard.ShowMoney()
print(myCard.idCard)

ok
10000
1111

这样轻易得获取到别人的密码是很危险的一件事情,再次基础上我们使用__将成员变量和方法设置成私有变量:

class  BankCard(object):
    def __init__(self,key,id):
        self.__idCard = id
        self.__key = key
    def GetMoney(self,num):
        if self.__idCard ==1111&self.__key==1111 :
            print("ok")
        else:
            print("error")
        return 
    def __ShowMoney(self):
        print("10000")
myCard = BankCard(1111,1111)
myCard.GetMoney(500)

print(myCard.idCard)
ok

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-143-1a089471a61d> in <module>()
     14 myCard.GetMoney(500)
     15 
---> 16 print(myCard.idCard)

AttributeError: 'BankCard' object has no attribute 'idCard'

3.9.3 继承

单继承是父子继承关系,在继承后拥有父类的所有属性和方法,但是对于父类私有变量来说子类的方法不能访问。子类继承了父类之后可以对原本的方法重写,也可以添加其他的变量和方法。

class  BankCard(object):
    def __init__(self,key,id):
        self._idCard = id
        self.key = key
    def GetMoney(self,num):
        if self.idCard ==1111&self.key==1111 :
            print("ok")
        else:
            print("error")
        return 
    def ShowMoney(self):
        print("10000")

class NewBankCard(BankCard):
    def __init__(self,key,id,num):
        BankCard.__init__(self,key,id)
        self.num = num
    def GetMoney(self):
        print(self.num)
Second = NewBankCard(111,111,1000)
#print(Second.idCard)
print(Second.key)
Second.GetMoney()
111
1000

上面的代码实现了父类的继承,对父类的GetMoney()进行了重写,可以发现父类的私有变量在子类是无法访问的。
说完单继承就要谈谈多继承,多继承是指子类可以从多个父类中获取属性,但是在继承过程中如果两个父类也有共同继承父类的关系那么就
需要使用super防止多次初始化。

class A(object):
    def __init__(self):
        print("a")
class B(A):
    def __init__(self):
        super(B,self).__init__()
        print("b")
class C(A):
    def __init__(self):
        super(C,self).__init__()
        print("C")
class D(B,C):
    def __init__(self):
        super(D,self).__init__()
        print("D")
d = D()

a
C
b
D

super方法可以让每个父类只初始化一次。

3.9.4多态

对于一系列继承关系的类,如果都同时修改了一个方法,那么这方法多种实现就体现了多态的作用,这里我们定义一个person父类,在此基础上我们定义学生和老师,并构建一个多态的上课方法:

class Person(object):
    def __init__(self):
        pass
    def StartClass(self):
        print("...")
class Student(Person):
    def __init__(self):
        super(Student,self).__init__()
        pass
    def StartClass(self):
        print("good morning teacher!")
class Teacher(Person):
    def __init__(self):
        super(Teacher,self).__init__()
        pass
    def StartClass(self):
        print("class beggin!")
def Start(temp):
    print(temp.StartClass())

a = Person()
b = Student()
c = Teacher()
Start(a)
Start(b)
Start(c)



    
...
None
good morning teacher!
None
class beggin!
None

python语言是一个动态语言,它有一个特点是在运行时候才检查语法,对函数的输入值并不检查它的类型符合规定,就如上面temp.StartClass(),python不管是哪种类型的变量调用了StartClass这个方法,运行到这能调用出这个方法,python就能继续运行下去。

3.9.5运算符重载

类构建和销毁时候会调用__init__(构造函数),del(析构函数),除此之外还有一些运算符重载函数,常用的有add,sub,or,通过这些函数可以实现类的加减等运算,其实在使用第三方库时候有很多类可以加减是因为在构造类的时候加入了运算符重载函数,使得我们可以直接对类进行方便的运算。
参考代码如下:

class Num():
    def __init__(self,num):
        self.num = num
    def __add__(self,other):
        return self.num+other
a = Num(10)
b = a+5
print(b)
15

3.10 python高级用法

3.10.1 文件IO

读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。
读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)

对utf-8编码文件操作:只读模式打开文件;读文件;关闭文件

f = open('./test.txt', 'r')
print(f.read())
f.close()

dasdsadasdsadasdasdasd

对二进制文件操作:
前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用’rb’模式打开文件即可:

f = open('./10.png', 'rb')
print(f.read())
f.close()
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x9b\x00\x00\x00D\x08\x06\x00\x00\x00\x96\tGv\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x02\xdfIDATx^\xed\xdcM\x8e\xda0\x18\xc6\xf1P\xb1\xe1\n\xb3\x9a%\x9a]\x0fP\xa9'A\xbdE\xcf\xd0[Tl\xb9D\xa5\x1e\xa0,\xd0\x1ca\x96l\xd9 \xb5\xf3\x82_\xea\xba6\xf9\x80<!\xf0\xffIV\xb0\x83C\x84\x1f\xd9!\xca\xcc\xe4\xf7\xbb\n\x10\xf8\x10\aaa@qq.com\xef\x08\x1bdXF\x83\xedv[\xbd\xbd\xbdU\xfb\xfd>\xb4\xe0\xda\x08[\xb0\xd9l\xaa\xe7\xe7\xe7j:\x9d\x86\x96aX\xe0\x9f\x9e\x9eB\xed\xbe\xb0\x8c\x066\xa3\r\x1d\xb4{G\xd8 C\xd8 C\xd8 C\xd8 C\xd8 ssa\xfb\xf8\xf9\xfb\xa94\xd1\xf6\xfd\xe8\xcfl6\x0b\xaf\xfe\xfa\xf9\xf2r\xda\xde\xe4\xcc\xf6\xeb\xc7\x97C\x89\x95B\x95{\xef\x18,\x97\xcb\xf0Jk\xf6\xed\aaa@qq.com\\\x83\x05m\xb7\xdb\x85\xda\x91\x05\xec\xd3\xeb\xebi[\x0c[<\xb8\xb9AV\xb2\xcf\xf6PY\x19\xf2\\.e!\x1b*hJi\xd0L1l\xf1\x8c\x11\xbf\xc6e\x16\x8b\xc5\xa1\x0ce\xf7\xf5\xdf\xd9\xa7\xa9s3bnV\xf3\x80\xf9\xd6\x8c\xe2\x07\xc2\xa5A\x1fzf\xc6Q\xeb\xb0\xc5\x037\xc4 \xda\xe7=\xea,k\xb3KZ\\\xdaV\xb7\xdf\xc5m]\xf6\xb7\xd1:l>\xd0>\xe8V\xd4\x81k\xcb\xcfs\xec|\t\xb4\xad\x17\x1f|\xaf\x1bk\x8b\xeb&\xad\xbb\\\x9f8Pq\xbf\xf8u\x17\x9d\x97\xd1!\x06\xef\x91g\xb56\xba\x04\xe2\x92\x1055\x8ak6C\xd0\xc6o\x14aK\x83\xd6v\xd9\xb6\xf7\xb7\xed\x83\xe6\xec\x97h\xee\x86n\xaa\xf8\xf0dnpl\xc0\xe3\xf6\\\xfdRv\xbc\xf48\xa5s\x89\xe5\xfa9\xef\x7f\xee\xfc\xd6\xebu5\x9f\xcfC\xad?\xb9{l\xf1\xad\x90\xba\x87'\xd3\x0bt_\xfe\xd2v\x13/\x8d\xa5\xfd\xe9\xf5YZw\xa5v\x97\xbb\xfd\x91\xba\xb9'u\xdb\x86\xb7\xed\xfbKTa\xab\xd3$l\xb9\xc1\xbe\x05u\x81\xe3\xb1\xf0`\x0ca\xab\x9b]n\xddh~ \xe0\x180/cD\xd8 C\xd8 C\xd8 C\xd8 C\xd8 C\xd8 C\xd8\x02\xfe\x1a\xbe\x7f\xdc\xd4\r\xf8\xc72\xfd#l\x90a\x19\x85\x0ca\x83\x0ca\x83\x0ca\x83\x0ca\x83\x0ca\x83\x0ca\x83\x0c\xf7\xd9jL&\x93\xc3\xb6\xcb\xd7\xe4}M\xda\xff\xdc\xbe{\xc5\xccV\xe3\x92 X\xdfR\xffs\xfb\xee\x15a\x83\x0ca\x83\x0cak\xc1\xae\xb3\xe2k-\xe7\xed\xb9}]\xf5q\xcc\xa1\x11\xb6\x86l\xd0\xfd:+\aaa@qq.com\xdc\x9e\xee\xeb\xaa\x8fc\xde\x02\xc2\xd6\x90\rz\x89\x85\xc1\x0b\xca\x08\xdb\x15\xc4\xb3\xd0\xb9P>:\xc2ve\xccne\xdc\xd4\xad\x11\x87\xc7\xbe\xaa\xb4nrm&\x17\xbc\\\x1fW\xea\x1b\xb7\x8f\x19a\x83\x0c\xcb(d\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd\x08\x1bd&\xab\xd5\x8aG\x8c \xc1\xf3l\x10\xa9\xaa?\xf6\xf1\xa1\xedw\xe6_\x85\x00\x00\x00\x00IEND\xaeB`\x82"

写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件:

f = open('./test.txt', 'w')
f.write('Hello, world!')
f.close()

如果我们要操作文件、目录,可以在命令行下面输入操作系统提供的各种命令来完成。比如dir、cp等命令。

如果要在Python程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只是简单地调用了操作系统提供的接口函数,Python内置的os模块也可以直接调用操作系统提供的接口函数。
操作文件和目录
操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意一下。查看、创建和删除目录可以这么调用:

import os
# 查看当前目录的绝对路径:
print(os.path.abspath('.'))
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
os.path.join('./', 'testdir')

# 然后创建一个目录:
os.mkdir('./testdir')
# 删掉一个目录:
print(os.rmdir('./testdir'))
C:\Users\liu\Downloads\FaceRecognition
None

3.10.2 多进程和多线程

大家都听说过,现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。
我们前面编写的所有的Python程序,都是执行单任务的进程,也就是只有一个线程。如果我们要同时执行多个任务怎么办?
有两种解决方案:

一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。

还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。

当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。

总结一下就是,多任务的实现有3种方式:

多进程模式;

多线程模式;

多进程+多线程模式。

同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。
因为复杂度高,调试困难,所以,不是迫不得已,我们也不想编写多任务。但是,有很多时候,没有多任务还真不行。想想在电脑上看电影,就必须由一个线程播放视频,另一个线程播放音频,否则,单线程实现的话就只能先把视频播放完再播放音频,或者先把音频播放完再播放视频,这显然是不行的。
Python既支持多进程,又支持多线程,我们会讨论如何编写这两种多任务程序。

如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?
由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:

from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':#python的主函数判断
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test'))
    print('Child process will start.')
    p.start()
    p.join()
    print('Child process end.')


Parent process 6396.
Child process will start.
Child process end.

多任务可以由多进程完成,也可以由一个进程内的多线程完成。我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。由于线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程。

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

import time, threading

# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

3.10.3 网络编程

自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。

计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。

举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上。

由于你的电脑上可能不止浏览器,还有QQ、Skype、Dropbox、邮件客户端等,不同的程序连接的别的计算机也会不同,所以,更确切地说,网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。

网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。

TCP编程:

Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

服务器

服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。

我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello再发回去。

首先,创建一个基于IPv4和TCP协议的Socket:

import socket
def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
    sock.close()
    print('Connection from %s:%s closed.' % addr)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 监听端口:
s.bind(('127.0.0.1', 9997))
s.listen(5)
print('Waiting for connection...')
while True:
    # 接受一个新连接:
    sock, addr = s.accept()
    # 创建新线程来处理TCP连接:
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()


Waiting for connection...

接着创建一个客户端:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9997))
# 接收欢迎消息:

print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 发送数据:
    s.send(data)
    
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

3.10.4 图形界面

python支持很多种图形界面库,如pyqt等,这里我们使用一种内置的小型界面库:在1966年,Seymour Papert和Wally Feurzig发明了一种专门给儿童学习编程的语言——LOGO语言,它的特色就是通过编程指挥一个小海龟(turtle)在屏幕上绘图。
海龟绘图(Turtle Graphics)后来被移植到各种高级语言中,Python内置了turtle库,基本上100%复制了原始的Turtle Graphics的所有功能。
我们来看一个指挥小海龟绘制一个长方形的简单代码:

# 导入turtle包的所有内容:
from turtle import *

# 设置笔刷宽度:
width(4)

# 前进:
forward(200)
# 右转90度:
right(90)

# 笔刷颜色:
pencolor('red')
forward(100)
right(90)

pencolor('green')
forward(200)
right(90)

pencolor('blue')
forward(100)
right(90)

# 调用done()使得窗口等待被关闭,否则将立刻关闭窗口:
done()




3.10.5 数据库

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。

Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

在使用SQLite前,我们先要搞清楚几个概念:

表是数据库中存放关系数据的集合,一个数据库里面通常都包含多个表,比如学生的表,班级的表,学校的表,等等。表和表之间通过外键关联。

要操作关系数据库,首先需要连接到数据库,一个数据库连接称为Connection;

连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后,获得执行结果。

Python定义了一套操作数据库的API接口,任何数据库要连接到Python,只需要提供符合Python标准的数据库驱动即可。

由于SQLite的驱动内置在Python标准库中,所以我们可以直接来操作SQLite数据库。

我们在Python中实践一下:

# 导入SQLite驱动:
import sqlite3
# 连接到SQLite数据库
# 数据库文件是test.db
# 如果文件不存在,会自动在当前目录创建:
conn = sqlite3.connect('test.db')
# 创建一个Cursor:
cursor = conn.cursor()
# 执行一条SQL语句,创建user表:
cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')

# 继续执行一条SQL语句,插入一条记录:
cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')

# 通过rowcount获得插入的行数:
cursor.rowcount
1
# 关闭Cursor:
cursor.close()
# 提交事务:
conn.commit()
# 关闭Connection:
conn.close()


查询是否成功插入数据到表中:

conn = sqlite3.connect('test.db')
cursor = conn.cursor()
# 执行查询语句:
cursor.execute('select * from user where id=?', ('1',))
# 获得查询结果集:
values = cursor.fetchall()
values 

[('1', 'Michael')]

小结
在Python中操作数据库时,要先导入数据库对应的驱动,然后,通过Connection对象和Cursor对象操作数据。

要确保打开的Connection对象和Cursor对象都正确地被关闭,否则,资源就会泄露。

如何才能确保出错的情况下也关闭掉Connection对象和Cursor对象呢?请回忆try:…except:…finally:…的用法。

3.10.6 常用的第三方库介绍

PIL:Python Imaging Library,已经是Python平台事实上的图像处理标准库了。PIL功能非常强大,但API却非常简单易用。
requests:它是一个Python第三方库,处理URL资源特别方便。
chardet:字符串编码一直是令人非常头疼的问题,尤其是我们在处理一些不规范的第三方网页的时候。虽然Python提供了Unicode表示的str和bytes两种数据类型,并且可以通过encode()和decode()方法转换,但是,在不知道编码的情况下,对bytes做decode()不好做。
对于未知编码的bytes,要把它转换成str,需要先“猜测”编码。猜测的方式是先收集各种编码的特征字符,根据特征字符判断,就能有很大概率“猜对”。
当然,我们肯定不能从头自己写这个检测编码的功能,这样做费时费力。chardet这个第三方库正好就派上了用场。用它来检测编码,简单易用。
psutil:psutil = process and system utilities,它不仅可以通过一两行代码实现系统监控,还可以跨平台使用,支持Linux/UNIX/OSX/Windows等,是系统管理员和运维小伙伴不可或缺的必备模块。