python资料

第一章 环境搭建

1.1 python的由来

Guido van Rossum(吉多·范罗苏姆.荷兰人),从阿姆斯特丹大学获得了数学和计算机硕士学位。所以他可以称得上是一名数学家,或者更确切点是精通数学与计算机的复合型人才。

他希望有一种语言,这种语言能够像C语言那样,能够全面调用计算机的功能接口,又可以像shell那样,可以轻松的编程。

这里写图片描述

1991年,第一个Python编译器诞生。它是用C语言实现的,并能够调用C语言的库文件。从一出生,Python已经具有了:类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统

Python 是一种面向对象的解释型计算机程序设计语言,第一个公开发行版发行于 1991 年。Python 是纯粹的自由软件, 源代码和解 释器 CPython 遵循 GPL(GNU General Public License)协议。

Python 语法简洁清晰,特色之一是强制用空白符(white space)作为语句缩进。Python 具 有丰富和强大的库。它常被昵称为胶水语言。

image-20220315154319866

许多程序员会调侃到:“人生苦短,我用python”

image-20220315154338852

1.2 python 诞生的小故事

image-20220402113226636

说到Python,它的诞生是极具戏曲性的,重度肥皂剧爱好者Guido(龟叔)为了打发圣诞节的无趣,开发了新的脚本解释程序,这就是传说中的Python诞生记。之所以会选择 Python 作为该编程语言的名字,是因为 Guido 是一个叫 Monty Python(蒙提·派森) 戏剧团体的忠实粉丝。

而且,从Java之父、C++之父、PHP之父、C之父这几张照片,很容易看出来,头发密度冠军当属Python之父,所以,想入IT行业,却不想脱发的程序员,Python是个不错的选择。

看似Python 是“不经意间”开发出来的,但Python垄断TIOBE、IEEE、PYPL、Stack Overflow四大榜单,成为当今最火的语言,人工智能+大数据最佳语言,以及上升速度最快的语言。

Python语法简洁清晰,对于初学者规范自己的学习有很大的帮助,在国外Python经常作为儿童编程的入门语言,可谓老少皆宜。

Python的设计哲学

image-20230201134006668
  1. Python开发者的哲学是:用一种方法,最好是只有一种方法来做一件事
  2. 如果面临多种选择,Python开发者一般会拒绝花俏的语法,而选择明确没有或很少有歧义的语法

1.3 Python 的特点

1) Python简单易学

“编程零基础,可以学习 Python 吗”,这是很多初学者经常问的一个问题。当然,计算机基础越好,对学习任何一门新的编程语言越有利,但如果你在编程语言的学习上属于零基础,也完全不用担心。

就我个人的观点,Python 作为学习编程的入门语言是再合适不过的,相比其他编程语言(比如 Java),Python 最大的优势就是非常容易上手。举个简单的例子,如果完成一个功能,使用 Java 实现需要 100 行代码,那么使用 Python 可能只需要 20 行。

有人可能会问,将 C 语言作为入门语言不好吗?就目前的就业形势来说,仅掌握 C 语言是很难找到高薪的工作的,而掌握 Python 则不同。

并且,在使用其它编程语言编程时(例如 C、C++),你需要时刻注意数据类型、内存溢出、边界检查等问题。而 Python 则不用,因为在底层实现时,它已经帮你一一处理好了。

2) Python功能强大

Python 强大的功能是很多用户支持 Python 的最重要的原因,从字符串处理到复杂的 3D 图形编程,Python 借助扩展模块都可以轻松完成。

实际上,Python 的核心模块已经提供了足够强大的功能,使用 Python 精心设计的内置对象可以完成许多功能强大的操作。

此外,Python 的社区也很发达,即使一些小众的应用场景,Python 往往也有对应的开源模块来提供解决方案。

3) Python是解释型语言

编程语言按照程序的执行方式,可以分为编译型和解释型两种,典型的编译型语言有 C、C++ 等,而解释型语言有 Java、Python 等。

相比编译型语言,解释型语言最大的优势就是可移植性强。也就是说,Python 具有非常好的跨平台的特性。

4) Python是面向对象的编程语言

Python 既支持面向过程编程,也支持面向对象编程。在“面向过程”的语言中(如 C 语言),程序仅仅是由可重用代码的函数构建起来的;而在“面向对象”的语言(如 C++)中,程序是由数据和功能组合而成的对象构建起来的。

而且和其它面向对象的编程语言(如 C++ 和 Java)相比,Python 是以一种非常强大,而又简单的方式实现的面向对象编程。

​ 总结一下python的特点:

  • **1.易于学习:**Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单。

  • **2.易于阅读:**Python代码定义的更清晰。

  • **3.易于维护:**Python的成功在于它的源代码是相当容易维护的。

  • **4.一个广泛的标准库:**Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和Macintosh兼容很好。

  • **5.互动模式:**互动模式的支持,您可以从终端输入执行代码并获得结果的语言,互动的测试和调试代码片断。

  • **6.可移植:**基于其开放源代码的特性,Python已经被移植(也就是使其工作)到许多平台。

  • **7.可扩展:**如果你需要一段运行很快的关键代码,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序,然后从你的Python程序中调用。

  • **8.数据库:**Python提供所有主要的商业数据库的接口。

  • **9.GUI编程:**Python支持GUI可以创建和移植到许多系统调用。

  • 10.可嵌入: 你可以将Python嵌入到C/C++程序,让你的程序的用户获得"脚本化"的能力。

    Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。

​ Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法结构。

  • Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。
  • Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码。
  • Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。
  • **Python 是初学者的语言:**Python 对初级程序员而言,是一种伟大的语言,它支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏。

任何编程语言都有缺点,Python也不例外。优点说过了,那Python有哪些缺点呢?

第一个缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言,你的代码在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。而C程序是运行前直接编译成CPU能执行的机器码,所以非常快。

但是大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。例如开发一个下载MP3的网络应用程序,C程序的运行时间需要0.001秒,而Python程序的运行时间需要0.1秒,慢了100倍,但由于网络更慢,需要等待1秒,你想,用户能感觉到1.001秒和1.1秒的区别吗?这就好比F1赛车和普通的出租车在北京三环路上行驶的道理一样,虽然F1赛车理论时速高达400公里,但由于三环路堵车的时速只有20公里,因此,作为乘客,你感觉的时速永远是20公里。

第二个缺点就是代码不能加密。如果要发布你的Python程序,实际上就是发布源代码,这一点跟C语言不同,C语言不用发布源代码,只需要把编译后的机器码(也就是你在Windows上常见的xxx.exe文件)发布出去。要从机器码反推出C代码是不可能的,所以,凡是编译型的语言,都没有这个问题,而解释型的语言,则必须把源码发布出去。

这个缺点仅限于你要编写的软件需要卖给别人挣钱的时候。好消息是目前的互联网时代,靠卖软件授权的商业模式越来越少了,靠网站和移动应用卖服务的模式越来越多了,后一种模式不需要把源码给别人。

再说了,现在如火如荼的开源运动和互联网自由开放的精神是一致的,互联网上有无数非常优秀的像Linux一样的开源代码,我们千万不要高估自己写的代码真的有非常大的“商业价值”。那些大公司的代码不愿意开放的更重要的原因是代码写得太烂了,一旦开源,就没人敢用他们的产品了。

image-20230109103341079

当然,Python还有其他若干小缺点,请自行忽略,就不一一列举了

1.4 Python的政策支持

政策支持,Python引领巨大机遇。人工智能两次被写入《政府工作报告》,并且进入河南、山东、浙江等多省市教材,还被列为全国计算机等级考试。

1.5 python的前景发展

世界上的编程语言有600多种,但真正大家主流在使用的最多二三十种,不同的语言有自己的特点和擅长领域,随着计算机的不断发展,新语言在不断诞生,也同时有很多老旧的语言慢慢无人用了。有个权威的语言排名网站,可以看到主流的编程语言是哪些!

数据来自与:https://www.tiobe.com/tiobe-index/

最新TIOBE指数展示

image-20220402115558986

这是最近10年最常用的10种编程语言的变化图

1、从自身特性看Python发展

Python自身强大的优势决定其不可限量的发展前景。Python作为一种通用语言,几乎可以用在任何领域和场合,角色几乎是无限的。Python具有简单、易学、免费、开源、可移植、可扩展、可嵌入、面向对象等优点,它的面向对象甚至比java和C#.net更彻底。

它是一种很灵活的语言,能帮你轻松完成编程工作。强大的类库支持,使编写文件处理、正则表达式、网络连接等程序变得相当容易。能运行在多种计算机平台和操作系统中l,如Linux、windows、MacOS、OS/2等等,并可作为一种原型开发语言,加快大型程序的开发速度。

2、从企业应用来看Python发展

Python被广泛的用在Web开发、运维自动化、测试自动化、数据挖掘等多个行业和领域。一项专业调查显示,75%的受访者将Python视为他们的主要开发语言,反之,其他25%受访者则将其视为辅助开发语言。将Python作为主要开发语言的开发者数量逐年递增,这表明Python正在成为越来越多开发者的开发语言选择。

目前,国内不少大企业都已经使用Python如豆瓣、搜狐、金山、腾讯、盛大、网易、百度、阿里、淘宝、热酷、土豆、新浪、果壳等;国外的谷歌、NASA、YouTube、Facebook、工业光魔、红帽等都在应用Python完成各种各样的任务。

3、从市场需求与薪资看Python发展

从最新Python招聘岗位需求来看,Python工程师的岗位需求量巨大,并且岗位需求量还在呈现上涨的趋势。全国Python岗位需求量接近10W个,北京岗位需求量居首位为20890个,占比21.17%;上海Python工程师岗位需求量居第二位为12843个,占比13.02%;其次是深圳、杭州、广州等一线城市合计占比16.53%,下图为全国主要城市Python工程师岗位需求量:

python应用在很多领域:

①web开发

豆瓣、知乎、拉勾网等都是用的Python,web开发在国内的发展也非常好,因为Python的web开发框架是最大的一个优势,如果你用Python搭建一个网站只需要几行的代码就可以搞定,非常简洁;

② 数据挖分析

Python所拥有的完整的生态环境十分有利于进行数据分析处理,比如,"大数据"分析所需要的分布式计算、数据可视化、数据库操作等,都可以通过Python中的十分成熟的模块完成;

③ 自动化测试

Python在自动化测试方面占着一大半天,有丰富的第三方库,满足接口测试、单元测试、web自动化和APP自动化、性能测试…几乎涵盖了所有的测试方面;

测试的工作是枯燥和重复的,在过去,每次产品更新,都要重复测试一遍,效率低而且容易出错。Python 提供了很多自动化测试的框架,如 Selenium、Pytest 等,避免了大量的重复工作,Python 自动化测试也变得越来越流行。

④ 网络爬虫

最早用Python做网络爬虫的是谷歌,Python做爬虫非常容易上手,市场占有率比较大,现在公司基本做爬虫的都是用Python来做的;

⑤ 人工智能

人工智能的发展潜力和钱途就不说了吧,这个都是大家都知道的东西,但是目前的话,人工智能方面的工作对学历要求比较高,但肯定是最具有发展潜力的方向了;

⑥ 自动化运维

最开始一批学习Python的人,就是运维和测试的在职人员,因为Python对于他们的工作起到很大的作用,因为使用Python脚本进行批量化的文件部署和运行调整都成了Linux服务器上很不错的选择;

1.6 python 开发环境

  • IDLE:Python内置IDE (随python安装包提供)

  • PyCharm :由著名的JetBrains公司开发,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工 具,比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。

  • Vim: 最新7.3版编译时可以加入python支持,提供python代码自动提示支持

  • Visual Studio + Python Tools for Visual Studio

  • ipython

1.7 python的版本介绍

1991年发布Python的第一个版本。此时Python已经具有了类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统。

1991-1994年,Python增加了lambda,map,filter and reduce

1999年,Python的web框架之祖——Zope 1发布。

2000年,加入了内存回收机制,构成了现在Python语言框架的基础

2004年,web框架 Django 诞生

2006年,Python 2.5

2008年,Python 2.6

2010年,Python 2.7

2008年,Python 3.0

2009年,Python 3.1

2011年,Python 3.2

2012年,Python 3.3

2014年,Python 3.4

2015年,Python 3.5

2016年,Python 3.6

2018年,Python 3.7

2019年,Python 3.8

2020年,Python 3.9

2021年,Python 3.10

Python 2.x 与 3.x 的区别

1.性能

Py3.0运行 pystone benchmark的速度比Py2.5慢30%。Guido认为Py3.0有极大的优化空间,在字符串和整形操作上可以取得很好的优化结果。

Py3.1性能比Py2.5慢15%,还有很大的提升空间。

2.编码

Py3.X源码文件默认使用utf-8编码

3.语法

1)去除了<>,全部改用**!=**

2)去除``,全部改用repr()

3)关键词加入as 和with,还有True,False,None

4)整型除法返回浮点数,要得到整型结果,请使用**//**

5)加入nonlocal语句。使用noclocal x可以直接指派外围(非全局)变量

6)去除print语句,加入**print()函数实现相同的功能。同样的还有 exec语句,已经改为exec()**函数

7)改变了顺序操作符的行为,例如x<y,当x和y类型不匹配时抛出TypeError而不是返回随即的 bool值

8)输入函数改变了,删除了raw_input,用input代替

9)去除元组参数解包。不能def(a, (b, c)):pass这样定义函数了

10)新式的8进制字变量,相应地修改了**oct()**函数。

11)增加了 2进制字面量和**bin()**函数

12)扩展的可迭代解包。在Py3.X 里,a, b, *rest = seq和 *rest, a = seq都是合法的,只要求两点:rest是list对象和seq是可迭代的。

13)新的super(),可以不再给super()传参数

14)新的metaclass语法

15)支持class decorator。用法与函数decorator一样

4. 字符串和字节串

现在字符串只有str一种类型,但它跟2.x版本的unicode几乎一样。

5.数据类型

1)Py3.X去除了long类型,现在只有一种整型——int,但它的行为就像2.X版本的long

2)新增了bytes类型,对应于2.X版本的八位串

3)dict的.keys()、.items 和.values()方法返回迭代器,而之前的iterkeys()等函数都被废弃。同时去掉的还有dict.has_key(),用in替代

6.面向对象

1)引入抽象基类(Abstraact Base Classes,ABCs)。

2)容器类和迭代器类被ABCs化,所以cellections模块里的类型比Py2.5多了很多

3)迭代器的next()方法改名为__next__(),并增加内置函数next(),用以调用迭代器的__next__()方法

4)增加了@abstractmethod和 @abstractproperty两个 decorator,编写抽象方法(属性)更加方便

7.异常

1)所以异常都从 BaseException继承,并删除了StardardError

2)去除了异常类的序列行为和.message属性

3)用 raise Exception(args)代替 raise Exception, args语法

4)捕获异常的语法改变,引入了as关键字来标识异常实例

5)异常链,因为__context__在3.0a1版本中没有实现

8.模块变动

1)移除了cPickle模块,可以使用pickle模块代替。最终我们将会有一个透明高效的模块。

2)移除了imageop模块

3)移除了 audiodev, Bastion, bsddb185, exceptions, linuxaudiodev, md5, MimeWriter, mimify, popen2,
rexec, sets, sha, stringold, strop, sunaudiodev, timing和xmllib模块

4)移除了bsddb模块

5)移除了new模块

6)os.tmpnam()和os.tmpfile()函数被移动到tmpfile模块下

7)tokenize模块现在使用bytes工作。主要的入口点不再是generate_tokens,而是 tokenize.tokenize()

9.其它

1)xrange() 改名为range(),要想使用range()获得一个list,必须显式调用

2)bytes对象不能hash,也不支持 b.lower()、b.strip()和b.split()方法,但对于后两者可以使用 b.strip(b’\n\t\r \f’)和b.split(b’ ')来达到相同目的

3)zip()、map()和filter()都返回迭代器。而apply()、 callable()、coerce()、 execfile()、reduce()和reload()函数都被去除了现在可以使用hasattr()来替换 callable(). hasattr()的语法如:hasattr(string, ‘name’)

4)string.letters和相关的.lowercase和.uppercase被去除,改用string.ascii_letters 等

5)如果x < y的不能比较,抛出TypeError异常。2.x版本是返回伪随机布尔值的

6)__getslice__系列成员被废弃。a[i:j]根据上下文转换为a.getitem(slice(I, j))或 __setitem__和__delitem__调用

7)file类被废弃

1.8 python的安装

因为Python是跨平台的,它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序,放到Linux上也是能够运行的。

官方网站:http://www.python.org

ftp 官方下载地址:https://www.python.org/ftp/python/

注意:在官方 ftp 下载地址中,可以选择下载任意一个需要的版本

image-20220402154622506
1.8.1 Windows环境下的安装

​ 提前下载好对应版本的python安装包,以下示例以python3.9.1为例

  • 打开安装包,切记要勾选add python 3.9 to PATH

    可选择自动安装(Install Now)或点击自定义安装(Customize installation)(以下步骤是以自定义安装为例)

image-20220318194339653
  • 默认全部勾选,点击next
image-20220318194434696
  • 选择自己要安装的目录,勾选install for all users,然后点击install进行安装
image-20220318194535992
  • 安装完成后,一定要点击disable path length limit,禁用系统的path长度自动限制,可以避免很多麻烦,完成后点击close即可

检查安装是否成功

运行----CMD进入黑窗口,输入python,能够使用exit()成功退出即可

image-20220318194936465

遇到的问题

  1. 已经安装过就不用安装,如果出现以下界面表示已经安装过

    image-20220402141859199
  2. 缺少环境变量

    方法一:卸载重装(选择环境变量)

    image-20220318194339653

    方法二:此电脑—高级环境设置-----环境变量配置环境变量

    image-20220318211624771 image-20220318211718730 image-20220318212022434
  3. 丢失 … .dll文件 ----- 百度自行下载补丁文件

1.8.2 Linux环境下的安装

广泛使用两个不兼容的 Python 版本: Python 2.x 和 Python 3.x。RHEL 8 提供以下 Python 版本。

版本要安装的软件包命令示例从现在开始可用生命周期
Python 3.6python3python3, pip3RHEL 8.0full RHEL 8
Python 2.7python2python2, pip2RHEL 8.0较短
Python 3.8python38python3.8, pip3.8RHEL 8.2较短
Python 3.9python39python3.9, pip3.9RHEL 8.4较短

安装

[root@kittod ~]# sudo yum install python3

验证

[root@kittod ~]# python3 --version
Python 3.6.8

指定版本

​ 查看安装路径:

which python3

whereis python

type python

切换版本方式一

alias python=python3

alias python=python2.7

验证指定版本

[root@kittod ~]# python

切换版本方式二

可以将未指定版本的 python 命令直接配置为特定的 Python 版本。

  • 要将未指定版本的 python 命令配置为 Python 3.6,请使用:

    # alternatives --set python /usr/bin/python3
    
  • 要将未指定版本的 python 命令配置为 Python 3.8,请使用:

    # alternatives --set python /usr/bin/python3.8
    

验证指定版本

[root@kittod ~]# alternatives --set python /usr/bin/python3
[root@kittod ~]# python
Python 3.6.8 (default, Aug 18 2020, 08:33:21) 
[GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
[root@kittod ~]# alternatives --set python /usr/bin/python3.8
[root@kittod ~]# python
Python 3.8.3 (default, Aug 18 2020, 08:56:04) 
[GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
1.8.3 MAC环境下的安装

首先MAC是自带Python环境的,我们可以先了解一下Mac自带的版本

选择前往文件夹,输入下面地址:

/System/Library/Frameworks/Python.framework/Version

img img

就可以看到这里有多个python版本,而在Current目录下存放的是系统当前的python版本。

img

Mac既然自带了python,当然肯定配置好了python的全局命令,我们直接在终端运行:

1 python

img

当前Mac自带的python版本为 2.7.10

安装步骤

主要就这几个步骤而已,不会太困难的(或许有些你本来就装好了)

Step 1 安装Xcode

Step 2 安装套件管理工具Homebrew

Step 3 安装Python

Step 4 设定路径$PATH(不跟系统Python 打架)

Step 5 完成!确认安装结果 

Step 1 安装Xcode

可以到App Store搜寻Xcode并安装安装好了之后就把Xcode打开~第一次开启的时候会需要同意他的License Agreement之类的东西。然后到terminal输入来安装Xcode command line tool:

xcode-select --install 

STEP 2 安装Homebrew

可以参考官网(https://brew.sh/index_zh-cn.html)或者直接粘贴:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"  

如果是权限问题 需要先执行

sudo -i 

再执行

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 

STEP 3 安装Python

接下来要正式进入安装Python的步骤了!
首先,输入

python --version 

会有如下结果:

img

这是Mac系统要使用的Python,不去动它。所以现在我们要用homebrew来安装自己使用的Python。

利用homebrew搜索Python

 brew search python 

会有如下结果:

img

开始安装:

 brew install python 

python2安装成功

brew install python3 

python3安装成功

查看安装的目录

 open /usr/local/Cellar/ 
img

STEP 4 设定路径$PATH (不和系统Python干扰)

echo $PATH 

如图所示

img
 open /usr/local/bin 
img

​ brew其实就在/usr/local/bin里面,所以现在的问题就是,系统在/usr/bin里面也有一份Python,现在我们在/usr/local/Cellar里面也装了Python,这样在terminal打上python指令时,谁会被开启呢?因为路径有顺序,所以它会先找到系统的Python,现在就要来解决这个问题

sudo emacs /etc/paths 

sudo让我们取得管理员权限,用emacs这个程序编辑路径档案,terminal会要求输入密码

img

如果不是这个顺序,调整成这个顺序。

control + k:把一行字剪下来
control + y:把字粘贴
control + x + s:存盘
control + x + c:关掉emacs

这时,重启terminal 会看到变化,再输入一次

echo $PATH 

STEP 5 安装完成,确认结果

 python3 
img

如图所示带便安装成功。

如果想使用系统的Python,就输入

 /usr/bin/python 
img
1.8.4 python 解释器

当我们编写Python代码时,我们得到的是一个包含Python代码的以.py为扩展名的文本文件。要运行代码,就需要Python解释器去执行.py文件。

由于整个Python语言从规范到解释器都是开源的,所以理论上,只要水平够高,任何人都可以编写Python解释器来执行Python代码(当然难度很大)。事实上,确实存在多种Python解释器。

  • CPython

​ 当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

​ CPython是使用最广的Python解释器。教程的所有代码也都在CPython下执行。

  • IPython

​ IPython是基于CPython之上的一个交互式解释器,也就是说,IPython只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

​ CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

  • PyPy

​ PyPy是另一个Python解释器,它的目标是执行速度。PyPy采用JIT技术,对Python代码进行动态编译(注意不是解释),所以可以显著提高Python代码的执行速度。

绝大部分Python代码都可以在PyPy下运行,但是PyPy和CPython有一些是不同的,这就导致相同的Python代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到PyPy下执行,就需要了解PyPy和CPython的不同点(https://doc.pypy.org/en/latest/cpython_differences.html)

  • Jython

    Jython是运行在Java平台上的Python解释器,可以直接把Python代码编译成Java字节码执行。

  • IronPython

​ IronPython和Jython类似,只不过IronPython是运行在微软.Net平台上的Python解释器,可以直接把Python代码编译成.Net的字节码。

小结

Python的解释器很多,但使用最广泛的还是CPython。如果要和Java或.Net平台交互,最好的办法不是用Jython或IronPython,而是通过网络调用来交互,确保各程序之间的独立性。

1.9 第一个python程序

在写代码之前,请千万不要用“复制”-“粘贴”把代码从页面粘贴到你自己的电脑上。写程序也讲究一个感觉,你需要一个字母一个字母地把代码自己敲进去,在敲代码的过程中,初学者经常会敲错代码:拼写不对,大小写不对,混用中英文标点,混用空格和Tab键,所以,你需要仔细地检查、对照,才能以最快的速度掌握如何写程序。

1.9.1 编写第一个程序

在你的计算机上创建一个文件夹,之后在里面创建一个文件,注意,该文件最好以英文命名,后缀名是.py(.py是python的缩写,所有的python文件的后缀名是py,就相当于java的文件是以.java为后缀名的一样)。

然后使用sublime打开这个文件。

在里面输入如下的代码:

print("Hello World")

启动cmd命令提示符窗口,将路径切换到这个文件所在的目录。

在cmd下,cd 路径 表示切换到对应的路径下 , 如果文件不在C盘,需要切换盘符,则需要执行:盘符号: 如我需要切换到g盘的demo文件夹:

 C:\Users\Administrator>g:  
 G:\>cd G:\qiku\python\demo  
 G:\qiku\python\demo>  

使用python + 文件名称执行刚刚写的代码:

python demo.py   # 注意加上空格
1.9.2 终端中编写代码

python默认携带了终端,该终端中也可以写代码,主要用来测试代码。

这样就可以不使用任何编辑器了,但是这样一般都是来测试或者书写少数代码的,大量代码的编写不建议使用。

cmd中输入python命令,进入终端:

直接在终端中可以输入代码:

print("终端中也可以写代码哦~~~")
1.9.3 程序运行原理解释

我们在hellowold.py代码中,编写了内容print(“…”),print在英文中表示打印的意思,这里的代码更加符合我们人类的语言习惯。但是计算器作为机器,它自己的逼格决定了它是读不懂人类的语言的。

所以,我们需要一个python开发环境,开发环境中有一个专门用来将符合人类语言的源代码翻译成机器能认识的语言的翻译器:解释器(PVM)。有了解释器,我们只要按照规范的语言编写了程序,就可以让计算机来按照我们期望的方式执行程序了,整个完整的源代码->解释->运行的过程如下:

1.10 注释

我们只要写过代码,都知道在编写代码的是时候是需要注释的,注释就是计算机不去识别,主要用来我们人自己看的,这样做的好处就是:对代码作用和功能进行了描述,用于提高代码的可读性。

​ 注释在编程中主要分为单行注释和多行注释

1.10.1 单行注释

​ Python其实最开始只有单行注释,所以单行注释是以#开头。如:

# 这个是单行注释
print("Hello World");
1.10.2 多行注释

python中多行注释,第一种就是我们可以使用单行字符串的形式来进行多行注释,第二种就是多行字符串使用三个单引号来包含一段字符。

如下面使用单行注释模拟多行注释:

print("Hello World");
#
# 这个是多行注释
# 当然是模拟的,其实就是多个单行注释
#

也可以使用三个引号来包裹一段文字来形成注释,这个是python真正的多行注释:

'''
	python最开始的时候是没有多行注释的,
	后面可以通过使用三个单引号,中间部分就是多行注释
'''
print("hello world");

注意不要使用中文符号 不要使用单引号和双引号混合使用(文档注释)

第二章 基本数据类型

2.1 变量和标识符

2.1.1 变量是什么

变量(variable):在程序运行时,可以发生变化的量,被称之为变量。

同样,有的人称变量为:具有名称的内存空间,这种说法当然也是正确的,只是站在了不同角度看待变量而已。

2.1.2 python如何定义变量

在开发中使用变量,而不是直接使用值,主要的目的是为了让程序运算过程中,更加灵活。

如直接使用数字10参与了十万行代码,现在因为有些原因,数字10需要修改为11。此时,如果直接修改10,工程量太大,软件的维护成本过高。

而如果直接定义变量a=10,就可以在开发过程中,直接使用a替代数字10,将来变化了值,直接在定义的地方修改即可。

python它是一门弱数据类型语言,和JavaScript、PHP等编程语言非常的像。

跟java、c、C++不一样,这些语言都是强数据类型语言,它们定义变量的格式:

数据类型 变量名称 = 变量/值;

如Java中:

int age = 10;
double price = 3.14;
String name = "hahaha";

每个类型所需要的内存大小不一样

  • 1字节 的是 byte 、boolean
  • 2字节 的是 short 、char
  • 4字节 的是 int 、float
  • 8字节 的是 long 、double

而弱数据类型语言,一般格式如下:

var 变量名称 = 变量/值;

什么是强数据类型语言、弱数据类型语言?

强数据类型语言:当定义变量的类型后,不允许存储其他数据类型

int a = 10;
a = 20;
a = "hahaha"  //报错 

弱数据类型语言:变量类型不固定,值是什么类型,变量就会自动变成对应类型

image-20221022103359426

python连var关键字都省略了(python中没有var关键字),直接

变量名称 = 变量 

全局函数:

Print(字符串) 在控制台上输出这个字符串,标准输出函数

Type(变量) 返回变量的数据类型

字符串 “” ‘’ “”” ””” ‘’’ ’’’

2.1.3 变量的命名规范

变量的命名规范

1> 变量名称只能由有效字符(大小写字母,数字,下划线)组成(name+ 报错语法错误)

2> 不能以数字开头

3> 不能是关键字或者是保留字

4> 变量命名尽量有意义(name age sex )

小驼峰法 userAddress(除了第一个单词,其他单词首字母大写)

下划线法(官方推荐)

2.1.4 标识符的命名规范

所谓的标识符就是对变量、常量、函数、类等对象起的名字。

首先必须说明的是,Python语言在任何场景都严格区分大小写!

Python对于标识符的命名有如下规定:

  • 第一个字符必须是字母表中的字母或下划线’_’

    例如:a, Ak, _set_id, green等都是可以的,但是例如:$abc, &_a, ~bashrc, 123abc等是不可以的!

    注意:中文也是支持的

    image-20221022104701456

    虽然支持中文标识符,但是没有人会这么干,我也不建议大家这么做

  • 标识符的其他的部分由字母、数字和下划线组成

  • 标识符对大小写敏感

2.1.5 python常量

python和ES6之前的js一样,没有定义常量的方式,常量是通过变量来模拟的。

常量的命名规范:所有单词的字母统统大写

这是一种非强制约束实现。

COUNTRY = "中国";
MAX_VALUE = 10000;

2.2 python的关键字

关键字:在编程语言中具有特殊含义的单词或者词组

保留字:目前版本中还没有使用的关键字,未来版本中可能要使用,暂时保留,不建议使用

查看python的该键字

import keyword

Keyword.kwlist

image-20221022105114628
>>> if = 1
SyntaxError: invalid syntax
>>> print(and)
SyntaxError: invalid syntax
>>> def = "hi"
SyntaxError: invalid syntax

2.3 数据类型

python是弱数据类型语言,所以没有数据类型(这句话是错误的),只是说明不用去声明数据类型python中数据类型分为基本数据类型和复合数据类型(引用数据类型)

2.3.1基本数据类型
  • 数值型

    整数 int

    浮点数 float

    复数(虚数) 一般用不到,除非做一些复杂的数学模型整数

  • 布尔类型(bool)

    a = True
    type(a)
    

    判断条件成不成立?(可以举例子,是男是女?今天下雨?灯亮还是不亮?)一般就两种情况 True或者Flase (python里面要注意大小写,Java里就是true和flase)

    image-20221022105358469

    name ‘true’ is not defined -------- 未定义

  • 字符串(在Java强数据类型语言中是对象,而在python中是数据类型,这是特点)

    在python中单引号、双引号、三引号 引起来的均为字符串

    字符串可以包含各种语言

    print('包含中文的str')
    包含中文的str
    

    对于单个字符的编码(ASCII),Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

    >>> ord('A')
    65
    >>> ord('中')
    20013
    >>> chr(66)
    'B'
    >>> chr(25991)
    '文'
    

    如果字符串内部既包含'又包含"怎么办?可以用转义字符\来标识,比如:

    print("I'm ok")
    I'm ok
    print("I'm "ok"")
    File "<stdin>", line 1
        print("I'm "ok"")
                    ^
    SyntaxError: invalid syntax   语法错误
    
    
    print('I\'m \"OK\"!')
    

    表示的字符串内容是:

    I'm "OK"!
    

    转义字符\可以转义很多字符,比如\n表示换行,\t表示制表符,字符\本身也要转义,所以\\表示的字符就是\,可以在Python的交互式命令行用print()打印字符串看看:

    >>> print('I\'m ok.')
    I'm ok.
    >>> print('I\'m learning\nPython.')
    I'm learning
    Python.
    >>> print('\\')
    \
    

    如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''...'''的格式表示多行内容,可以自己试试:

    >>> print('''line1
    ... line2
    ... line3''')
    line1
    line2
    line3
    

    注意:必须是三引号,不能是双引号,在命令行里先输入print(“‘ 内容 ,当输入完结束符 ’”) 后,执行该语句并打印结果

    | 转义 | 字符 描述 |
    | --------------- | ------------------------------------- |
    | (在行尾时) | 续行符 |
    | \ | 反斜杠 |
    | | 单引号 |
    | " | 双引号 |
    | \b | 退格(Backspace) |
    | \000 | |
    | \n | 换行 |
    | \r | 回车 |
    | \f | 换页 |
    | \yyy | 以\数字0开头后跟三位八进制数 |
    | \xyy | 以\字母x开头后跟至少2位十六进制数 |

    2.3.2复合数据类型(引用数据类型)

    (学习完面向对象,可以知道万物皆对象)

    字典、列表、元组、集合

    通过type()判断数据类型

案例:::可以写一段代码,输入一个数,判断这个数的类型

#输入一个数,判断这个数的类型  print()输出  input()
num = input("请输入一个数:")  
print(num)  #什么都不输入 就不打印

#输入一个数,判断这个数的类型  print()输出  input()
num = input("请输入一个数:")  
print(type(num))    //返回值是什么?标准输出函数返回的是字符串,那么如何转换为整型?  

第三个全局函数:

input() ---------- 标准的输入函数,默认返回结果是字符串

2.4 数据类型转换

  • 字符串转换为整型------------------- int(字符串)

    #输入一个数,判断这个数的类型  print()输出  input()
    num = input("请输入一个数:")  
    print(type(num))    //返回值是什么?标准输出函数返回的是字符串,那么如何转换为整型?  #将字符串转换为Int
    temp = int(num)
    print(type(temp))
    print(temp)
    
    #输入一个数,判断这个数的类型  print()输出  input()
    num = input("请输入一个数:")  
    print(type(num))    //返回值是什么?标准输出函数返回的是字符串,那么如何转换为整型?  print(num + 10) //报错
    #将字符串转换为Int
    temp = int(num)
    print(type(temp))
    print(temp)
    print(temp + 10) 
    

    代码可以改为

    #输入一个数,判断这个数的类型  print()输出  input()
    num = input("请输入一个数:")  
    print(type(num))    //返回值是什么?标准输出函数返回的是字符串,那么如何转换为整型?  #print(num + 10) //报错
    #将字符串转换为Int
    num = int(num)
    print(type(num))
    print(num)
    print(num + 10) 
    

    代码进行优化

    num = int(input("请输入一个数:") )
    print(type(num))
    

    前提条件,传入的参数值能转成字符串,比如“hahhah”,会报错

    如果传入20.3小数怎么转?

  • float(字符串)-------------将字符串转换为浮点数

    num = float(input("请输入一个数:") )
    print(type(num))
    

    如果现在有种情况:a = 10 拼接字符串 和 10 怎么办?

    num = float(input("请输入一个数:") )
    print(type(num))
    a = 10
    print("此时a=" + a)   //报错 字符串只能连接字符串
    
  • str(其他类型) ------ 将其他类型的数据转换为字符串

    num = float(input("请输入一个数:") )
    print(type(num))
    a = 10
    print(str(a))
    print("此时a=" + a)   
    

2.5 常见的运算符

2.5.1 算术运算符

算术就是数学直接的一些运算规则

运算符含义备注
+加法运算
-减法运算
*乘法运算
/除法运算在c++ C Java等强数据类型语言是整除运算
%取余运算求模或者求余数
//整除运算整除(只要整数部分)
**幂次方运算
 a = 10 
 b = 3
 a + b
 a - b
 a * b
 a / b    #3.33333  在java中结果3 因为是强数据类型语言,ab都是int,python是弱数据类型语言
 a % b    # 1      判断是否整除
 a // b   #3  
 2 ** 3   # 8
 a ** b   
#将摄氏度温度转换为华氏度
cel = int(input("请输入摄氏温度:"))
fagrenheit = (9 / 5) * cel + 32
print("%s摄氏度是%s华氏度"%(cel,fagrenheit))
2.5.2 关系运算符

两个数之间的关系

注意:结果是一个boolean值

运算符含义备注
>大于
<小于
>=大于等于
<=小于等于
==等于
!=不等

== 比较两个变量的值

比较两个变量的地址 -----is 关键字

a = 10
b = 2
a > b

练习:输入两个数,比较两个数关系

num1 = int(input("请输入第一个数:"))
num2 = int(input("请输入第二个数:"))

print(num1 > num2)
print(num1 < num2)
print(num1 == num2)
print(num1 != num2)
print(num1 >= num2)
print(num1 <= num2)

print('%d>%d结果是%s'%(num1,num2,(num1>num2))) #占位符% 总共有三个
2.5.3 逻辑运算符

多个表达式之间的关联关系,运算结果是一个boolean值

例如:咱班有姓王的并且是男生? 有两个条件

运算符含义备注
and(&或者&& 其他语言) 多个条件必须同时满足,则结果是true
or多个条件,至少一个为真,则为真
not取反 ,一般会和In关键字一起使用,表示意义相反

In 关键字 ---------- 判断某个值是否在容器中

and 举例

a = 3
b = 4
c = 3
a > b and b > c
#结果Flase
a > b 
b> c

or 举例

a > b or b > c

and举例

a = [1,2,3,4,"hhahha","good n"]  #容器
2 in a   #2这个变量在a容器中
"xixixi" in a
2 not in a  #2不在a容器中
2.5.4 赋值运算符

Java或者C 有自加自减运算 i++ ++i i-- --i 比较麻烦,python里面没有自加自减

运算符含义备注
=等于赋值
+=加等a += 值 等价于 a = a + 值
-=减等
*=乘等
/=除等
**=幂次方等
//=整除等

注意: Python没有自加自减运算

a = 10  #将10赋值给a
b = 2
a = b #将b赋值给a
2.5.5 身份运算符
运算符含义备注
is判断两个标识符是不是引用自一个对象x is y,类似id(x)==id(y),如果引用的是同一个对象则返回True,否则返回False
is not判断两个标识符是不是引用自不同对象x is not y

注意is与比较运算符“==”的区别,两者有根本上的区别:

is用于判断两个变量的引用是否为同一个对象,而==用于判断变量引用的对象的值是否相等!

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b == a
True
>>> b = a[:]
>>> b is a
False
>>> b == a
True

这里介绍一个常用的内置函数:id(),用它可以查看某个变量或者对象的内存地址,两个相同内存地址的对 象被认为是同一个对象。

>>> a = 1
>>> b = 2
>>> id(a)
1383969856
>>> id(b)
1383969888
>>> c = 2
>>> id(c)
1383969888
2.5.6 成员运算符

in 与 not in是Python独有的运算符(全部都是小写字母),用于判断对象是否某个集合的元素之一,非常好用,并且运行速度很快。返回的结果是布尔值类型的True或者False。

2.5.7 位运算

按位运算符是把数字看作二进制来进行计算的。Python中的按位运算法则如下: 下表中变量 a 为 60,b 为 13,二进制格式如下:

a = 0b 0011 1100
b = 0b 0000 1101
­­­­­­­­­­­­­­­­­
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a = 1100 0011
image-20221022110829311
2.5.8 三目运算符

python中的三目运算符不像其他语言一般的表示方法:判定条件?为真时的结果:为假时的结果。

例如,C语言是这么写的: result = 5>3?1:0

在python中的格式为:为真时的结果 if 判定条件 else 为假时的结果

例如: True if 5>3 else False

a = int(input())
b = int(input())
max = a if a > b else b
print(max)

2.6 图形化设计

Turtle是Python内嵌的绘制线、圆以及其他形状(包括)文本的图形模块

image-20221022111550619

常用的方法:

imoprt turtle #导入库文件

showturtle() #显示当前的位置和方向

write(string) #绘制一个文本字符串

forward(int) #向箭头方向移动int个像素并绘制直线

right(int) #转向int度数

color(string) #设置颜色

goto(int,int) #移到某个位置

penup() #抬起笔

pendown() #放下笔

circle(int) #绘制半径为int的圆

done() #绘制完毕

示例:

绘制三角形

import turtle as t
t.pensize(10)
t.right(60)
t.forward(100)
t.right(120)
t.forward(100)
t.right(120)
t.forward(100)
t.done()

绘制五角星

import turtle as t
t.pensize(10)
t.color("red")
t.right(72)
t.forward(100)
t.right(144)
t.forward(100)
t.right(144)
t.forward(100)
t.right(144)
t.forward(100)
t.right(144)
t.forward(100)
t.done()

绘制五环

import turtle # 画布中心坐标为(0,0)
turtle.pensize(10) # 画笔宽度
turtle.color("blue")
turtle.penup()
turtle.goto(-110, -25)
turtle.pendown()
turtle.circle(45)
turtle.color("black")
turtle.penup()
turtle.goto(0, -25)
turtle.pendown()
turtle.circle(45)
turtle.color("red")
turtle.penup()
turtle.goto(110, -25)
turtle.pendown()
turtle.circle(45)
turtle.color("yellow")
turtle.penup()
turtle.goto(-55, -75)
turtle.pendown()
turtle.circle(45)
turtle.color("green")
turtle.penup()
turtle.goto(55, -75)
turtle.pendown()
turtle.circle(45)
turtle.done()

2.7 作业

  • 可以选择性布置《python编程练习题》中的第一部分内容(1-18)

第三章 程序控制流程

3.1 顺序流程

程序设计的一般思路:
变量初始化(名称、个数、初始值)
输入 ( input()
处理 (算法)
输出 (print())
原则:自顶向下、逐步细化,由上到下逐条执行,即书写顺序就是执行顺序,清晰第一、效率第二

例1:输入一个十进制数,转换为二进制、八进制、十六进制数

num = int(input("输入数字:"))
print("十进制数为:", num)
print("转换为二进制为:", bin(num))
print("转换为八进制为:", oct(num))
print("转换为十六进制为:", hex(num))

例2:输入一个华氏温度,要求输出摄氏温度,公式为;C=5/9*(F-32)

F = int(input("请输入华氏温度:"))
C = 5 / 9 * (F - 32)
print("摄氏温度为:%.2f" % C)

例3:从键盘读入两个整数a,b合并生成一个新的四位数c, 例如a=54,b=12.则c的值应该是5142

a = int(input("请输入一个2位数:"))
b = int(input("请输入一个2位数:"))
c = a // 10 * 1000 + b // 10 * 100 + a % 10 * 10 + b % 10
print(c)

例4:财务人员给员工发工资时经常遇到这样一个问题,即根据每个人的工资额(单位:元)计算出各种面值的钞票的张数,且要求总张数最少

gz = int(input("请输入本月工资:"))
print("100元:", gz // 100, "张")
gz = gz % 100
print("50元:", gz // 50, "张")
gz = gz % 50
print("20元:", gz // 20, "张")
gz = gz % 20
print("10元:", gz // 10, "张")
gz = gz % 10
print("5元:", gz // 5, "张")
print("1元:", gz % 5, "张")

例5:输入三角形的三边长,求三角形面积。

已知三角形的三边长a,b,c,则该三角形的面积公式为:img

中s = (a+b+c)/2 , 注:三角形的三条边长可任意输入吗?

import math

a = int(input())
b = int(input())
c = int(input())
s = (a + b + c) / 2
area = math.sqrt(s * (s - a) * (s - b) * (s - c))  # area=(s*(s-a)*(s-b)*(s-c))**0.5
print(area)

3.2 选择结构

3.2.1 单分支
  • 单分支

流程图:

image-20230201105702147

语法结构:

 if 条件:
       #缩进,Python强缩进语言
       #执行条件满足的代码

要注意缩进和空格(table)的区别,不能混合使用,要缩进都缩进,要空格都空格,不然层级关系不对称

比如,输入用户年龄,根据年龄打印不同的内容,在Python程序中,用if语句实现:

age = 20
if age >= 18:
    print('your age is', age)
    print('adult')
根据Python的缩进规则,如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。
3.2.2 双分支
  • 双分支

流程图:

image-20230201105827319

语法结构:

 if 条件:
     #执行条件满足的代码
 else:
     #执行条件不满足的代码

age = 3
if age >= 18:
    print('your age is', age)
    print('adult')
else:
    print('your age is', age)
    print('teenager')

注意不要少写了冒号:

3.2.3 多分支

多分支(三分支)

流程图:

image-20230201110204127

语法结构:

if 条件1:
    #执行条件1满足的代码 
elif 条件2:
    #执行条件2满足的代码
    …………
elif 条件n
    #执行条件n满足的代码 else:
     #前面所有条件都不满足的情况

【else】-----可写可不写,根据实际情况

age = 3
if age >= 18:
    print('adult')
elif age >= 6:
    print('teenager')
else:
    print('kid')

if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elifelse,所以,请测试并解释为什么下面的程序打印的是teenager

age = 20
if age >= 6:
    print('teenager')
elif age >= 18:
    print('adult')
else:
    print('kid')

if判断条件还可以简写,比如写:

if x:
    print('True')

只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

注:再议 input

最后看一个有问题的条件判断。很多同学会用input()读取用户的输入,这样可以自己输入,程序运行得更有意思:

birth = input('birth: ')
if birth < 2000:
    print('00前')
else:
    print('00后')

输入1982,结果报错:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()

这是因为input()返回的数据类型是strstr不能直接和整数比较,必须先把str转换成整数。Python提供了int()函数来完成这件事情:

s = input('birth: ')
birth = int(s)
if birth < 2000:
    print('00前')
else:
    print('00后')

再次运行,就可以得到正确地结果。但是,如果输入abc呢?又会得到一个错误信息:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

原来int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了。

如何检查并捕获程序运行期的错误呢?后面的错误和调试会讲到。

练习

1.小明身高1.75,体重80.5kg。请根据BMI公式(体重除以身高的平方)帮小明计算他的BMI指数,并根据BMI指数:

  • 低于18.5:过轻
  • 18.5-25:正常
  • 25-28:过重
  • 28-32:肥胖
  • 高于32:严重肥胖

if-elif判断并打印结果

2.输入一个月,判断季节(让用户输入一个月份,判断这个月是哪个季节?假定3到4月是春季,5到8月是夏季,9到10是秋季,11、12、1、2月是冬季)

a = int (input("请输入一个月份:"))
if a == 3 or a == 4:
	print("%s月是春季"%a)
elif 5 <= a <=8:
	print("%s月是夏季"%a)
elif 9 <= a <=10:
	print("%s月是秋季"%a)
else:
	print("%s月是冬季"%a)

3.3 while循环

例如:输出1+2+3+4的值


#输出1+2+3+4的值
num = 1 + 2 + 3 + 4
# print("1+2+3+4的值为"+ str(num))
# print("1+2+3+4的值为%s"%num)
# print("1+2+3+4的值为",num)
print("1+2+3+4的值为{}".format(num))

补充:拼接字符串的四种方法:

  1. +str()

  2. %s

  3. 使用,

  4. 使用{} 补充:

    print("{} + {} = {}".format(num1,num2,num3))  #format是字符串里面的一个方法
    
打印1~100#打印1~100之间的数
index = 1
while index <= 100:
	print(index)
	index += 1

while 循环:

 while 条件:
 	#缩进
 	执行循环体

练习(也可以布置为作业)

1 .计算100以内所有奇数的和

# sum = 0
# n = 1
# while  n < 100:
# 	sum = sum + n
# 	n += 2
# print(sum)
sum = 0
n = 99
while n > 0:
  sum = sum + n
  n = n - 2
print(sum)

2.打印

*
**
***
****
*****
******

打印每一行的*

layer = int(input("请输入要打印的层数"))
index = 1
while index <= layer:
    #每层*的个数
    j = 1
    while j <= index:
       print("*",end="")#help(print)------end换行
       j += 1
    print()
    index += 1
  1. 九九乘法表

具有行和列(外层循环控制行,内层控制列)

i = 1
while i <= 9:
    j = 1
    while j <= i:
       print("%s X %s = %s " %(i,j,(i*j)),end = "")
       j += 1
    print()
    i += 1

优化格式:

i = 1
while i <= 9:
    j = 1
    while j <= i:
       res = i * j
       if res < 10: #结果小于10打印两个空格
          print("%s X %s = %s  " %(i,j,(i*j)),end = "")
       else:
            print("%s X %s = %s " %(i,j,(i*j)),end = "")
       j += 1
    print()
    i += 1
  1. 判断一个数是否为质数(素数)

质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数。最小的质数是2,它也是唯一的偶数质数。最前面的质数依次排列为:2,3,5,7,11等

num = eval(input('请输入一个数字:'))  
if num <= 1:  
    print('这不是质数')  
elif num == 2:  
    print('这是一个质数!')  
else:  
    i=2  
    while i < num:  
        if num%i == 0:  
            print('这不是一个质数')  
            break  
        i += 1  
    else:  
        print ('这是一个质数!')

5.猜单词游戏

import random
WORDS =("python","jumble","easy","difficult","answer","continue","phone","position","game")
print("欢迎来到猜单词游戏,请把乱序的字母组成正确的单词")
iscontinue = "y"
while iscontinue == "y" or iscontinue == "Y":
  words = random.choice(WORDS)
  right = words
  print(words)
  newWords = ""
  while words:
    position = random.randrange(len(words))
    #  print(position)
    newWords += words[position]
    # print(newWords)
    words = words[:position] + words[(position + 1):]
  print("乱序后的单词:", newWords)
  guess = input("\n请你猜单词:")
  '''if guess == words:
    print("恭喜你,猜对了")
  else :
    print("抱歉,你猜错了")
   '''
  while guess != right and guess != "":
    print("抱歉,你猜错了")
    guess = input("\n请你继续猜单词:")
  if guess == right:
    print("恭喜你,猜对了")
  iscontinue = input("\n\n是否继续(Y/N):")
  1. 实现两个数的交换(用户输入两个整数,存储到两个变量中,交换变量存储的值)
num1 = int(input("请输入一个整数"))
num2 = int(input("请输入另一个个整数"))
print("交换前,num1=%s , num2=%s" %(num1,num2))
#方法一:使用临时变量
temp = num1
num1 = num2
num2 = temp
print("交换后,num1=%s , num2=%s" %(num1,num2))
#方法二:求和法(第一种方法产生了一个中间量temp,浪费空间)
num1 = num1 + num2  # 20+50=70
num2 = num1 - num2  # 70-20=50
num1 = num1 -num2   # 70-50=20
print("交换后,num1=%s , num2=%s" %(num1,num2))
#方法三:异或交换法 (python独有的)引入了寄存器的说法(计算机组成原理)
num1,num2 = num2,num1
print("交换后,num1=%s , num2=%s" %(num1,num2))

3.4 for循环

for 循环 加强循环(foreach),一般使用for in 结构 用来遍历容器的

列表:存储多个数据的一个容器

ls = [1,2,3,4] 变量 = [ 元素1,元素2,元素3] 回顾 -----逻辑运算符 in ---- 1 in ls

遍历ls容器?

for 变量 in 容器:

	#该变量表示的是每一次容器迭代的元素

	#缩进

	print(变量) 
for i in ls:
   print(i)

结论:for in 循环最开始主要用来迭代容器( 可迭代的对象(可迭代对象在python中是一种数据类型))

那么如果除了可迭代容器外,如何for循环常规的数据运算呢?

例如:1—100的和怎么用for循环实现?

引入一个新的全局函数range()

range 翻译 -------范围,区间的意思

range(参数) ----表示从0~参数的一个区间范围内的整数,前闭后开

打印0~9
for i in range(10):
   print(i)

range(参数1,参数2) ------表示参数1到参数2 的范围,前闭后开

打印10~19
for i in range(10,20):
   print(i)

range(参数1,参数2,step) -----表示参数1到参数2 的范围,数据的变化由step决定,默认的step的值为1

for i in range(0,20,2):
   print(i)
反着打印:
for i in range(20,1,-1):
   print(i)

3.5 break和continue关键字

for i in range(101):
   if i == 50:
      break #终止循环
   print(i)
for i in range(101):
   if i == 50:
      continue #跳过本次循环继续循环
   print(i)

pass关键字可以使用在任何地方(函数,循环等),它的作用是目前不知道代码怎么实现,暂时为了保证语法能够正常通过,从而保证语法的完整性

for i in range(101):
   if i == 50:
      pass  #如果没有pass就会报错
   print(i)

一个完整的循环结构:

for 变量 in range(范围):

​ #循环体

【else:

循环正常结束后需要执行的代码】

for i in range(101):
   if i == 50:
      break #终止循环
   print(i)
else:
   print("break了就不进来了")
print(“循环结束了”)

---------当循环内部结束了,那么代码就不会进入到else
for i in range(101):
   if i == 50:
      continue
   print(i)
else:
   print("break了就不进来了")
print(“循环结束了”)


---------------循环正常进行结束后,会进入else

3.6 循环加强案例练习

  1. 求0~100以内偶数的和
sum = 0
for i in range(0,101):
   #判断i是不是偶数
   if i % 2 == 0:
      #说明是偶数
      sum += i
 print("0~100的偶数和是", sum)
  1. 求0~100以内的奇数和
#100以内奇数的和
sum = 0
for i in range(0,100):
  if i%2==1:
    sum += i
print(sum)
  1. 一个自然数与3的和是5的倍数,与3的差是6的倍数,这个自然数最小是多少?
index = 0
while True:
    if (index + 3) % 5 == 0 and (index - 3) % 6 == 0:
        print(index)
        break
    index += 1

  1. 在400~500之间求一个数,他被2除余1,被5除余3,被8除余1,这个数是多少?
for i in range(400, 501):
    if i % 2 == 1 and i % 5 == 3 and i % 8 == 1:
        print(i)
        break

5.打印三角形(等腰三角形)

         *            1行    3个空格   1个*
        ***            2     2        3
       *****           3     1        5
      *******          4     0        7
layer = int(input("请输入打印的行数:"))
#每一行
for i in range(1,layer+1):
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j变量冲突或者矛盾吗
     print("*",end="")
  print("")

6.打印菱形

         *            1行    3个空格   1个*
        ***            2     2        3
       *****           3     1        5
      *******          4     0        7
       *****           5     1        5
        ***            6     2        3       
         *             7     3        1         第二种方法:下半部分的*的个数  总行数*2 -当前行数*2 + 1

(菱形行数数偶数行)分为一个正三角形和一个倒三角形

layer = int(input("请输入打印的行数:"))
while layer % 2 == 0:
   layer = int(input("对不起,请输入奇数行:"))
#上半部分 分为两个等腰三角形
for i in range(1,layer // 2 + 2):#上半部分多打印一行   layer=5
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     print("*",end="")
  print("")
#下半部分
for i in range(layer //2 , 0,-1):
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     print("*",end="")
  print("")

7.打印空心菱形

       *
      * *
     *   *
    *     *
     *   *          
      * *          
       *  
layer = int(input("请输入打印的行数:"))
while layer % 2 == 0:
   layer = int(input("对不起,请输入奇数行:"))
#上半部分
for i in range(1,layer // 2 + 2):  #上半部分多打印一行   layer=5
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1:
         print("*",end="")
     else:
           print (" ",end="")
  print("")
#下半部分
for i in range(layer //2 , 0,-1):
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
    #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1:
         print("*",end="")
     else:
           print (" ",end="")
  print("")
       *
      * *
     *   *
    *******
     *   *          
      * *          
       *  
layer = int(input("请输入打印的行数:"))
while layer % 2 == 0:
   layer = int(input("对不起,请输入奇数行:"))
#上半部分
for i in range(1,layer // 2 + 2): #上半部分多打印一行   layer=5
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1 or i == layer // 2 + 1:  #i == layer // 2 + 1  上半部分最后一行
         print("*",end="")
     else:
           print (" ",end="")
  print("")
#下半部分
for i in range(layer //2 , 0,-1):  
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
    #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1:
         print("*",end="")
     else:
           print (" ",end="")
  print("")
        *
       ***
      * * *
     *  *  *
    *********
     *  *  *
      * * *
       *** 
        *
layer = int(input("请输入打印的行数:"))
while layer % 2 == 0:
   layer = int(input("对不起,请输入奇数行:"))
#上半部分
for i in range(1,layer // 2 + 2): #上半部分多打印一行   layer=5
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1 or i == layer // 2 + 1 or j == star_num // 2:  #i == layer // 2 + 1  上半部分最后一行  j == star_num // 2  中间这一列
         print("*",end="")
     else:
           print (" ",end="")
  print("")
#下半部分
for i in range(layer //2 , 0,-1):  
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
    #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1 or j == star_num // 2:
         print("*",end="")
     else:
           print (" ",end="")
  print("")
        *
       ***
      * * *
     *  *  *
    *********
        *  
        * 
        *
        *
layer = int(input("请输入打印的行数:"))
while layer % 2 == 0:
   layer = int(input("对不起,请输入奇数行:"))
#上半部分
for i in range(1,layer // 2 + 2): #上半部分多打印一行   layer=5
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
     #判断是不是第一个和最后一个
     if j == 0 or j == star_num -1 or i == layer // 2 + 1 or j == star_num // 2:  #i == layer // 2 + 1  上半部分最后一行  j == star_num // 2  中间这一列
         print("*",end="")
     else:
           print (" ",end="")
  print("")
#下半部分
for i in range(layer //2 , 0,-1):  
  #计算出空格的个数
  space_num = layer - i;
  for j in range(0,space_num):
    print (" ",end="")
    
   #计算*个数
  star_num = 2 * i -1
  for j in range(0,star_num): #j矛盾吗
    #判断是不是第一个和最后一个
     if j == star_num // 2:
         print("*",end="")
     else:
           print (" ",end="")
  print("")

3.7 作业

  • 可以选择性布置《python编程练习题》中的第二部分内容(19-36)和第三部分内容(37-54)

  • 1:计算下列多项式之和

sum = 0
for i in range(1, 101):
    sum = sum + 1 / i
print("sum=%.6f" % sum)          #sum=5.187378
  • 2:计算下列多项式之和

sum = 1
f = -1
for i in range(2, 101):
    sum = sum + 1 / i * f
    f = -f
print("sum=%.6f" % sum)        #sum=0.688172
  • 3:计算下列多项式之和

sum = 0
t = 0
for i in range(1, 101):
    t = t + i
    sum = sum + 1 / t
print("sum=%.6f" % sum)   #sum=1.980198
  • 4:计算a+aa+aaa+a…a前6项之和(a=2)
a = 2
sum = 0
for i in range(6):
    sum = sum + a
    a = a * 10 + 2
print("sum=", sum)    #sum= 246912
  • 5:用泰勒级数计算e的近似值,直到最后一项小于1e-6为止,e=1+1/1!+ 1/2! +1/3!+……1/n!
sum = 1
t = 1
i = 1
while 1 / t > 1e-6:
    t = t * i
    sum = sum + 1 / t
    i += 1
print("sum=%.6f" % sum)  #sum=2.718282
  • 6:计算π的公式为,img计算π的近似值.
pi = 1
f = -1
i = 3
while 1 / i > 1e-6:
    pi = pi + 1 / i * f
    i = i + 2
    f = -f
print("pi=%.6f" % (pi * 4))   #pi=3.141591

第四章 内置容器

4.1 容器的概述

  1. 什么是容器,为什么要学习容器?

​ 容器:可以存放多个元素的一种数据类型

​ 补充变量的无法存储大量数据的缺陷

  1. Python提供的容器

​ 列表

​ 集合

​ 元组

​ 字典

4.2 列表—list

线性表(常见的线性表-----数组 栈 队列 链表) ---- 基于链表的数据结构实现的

image-20221029183949909

:先进后出,后进先出

队列:先进先出,后进后出

链表(比较特殊的数据结构):(只有一个6byte的空间,但是要存8byte的数组,肯定是不够的,所以是有很多零碎的内存块,科学家就使用线将各种零碎的内存块连接起来)

单向链表:节约内存

没有头节点,只有尾节点

双向链表

一般有三个节点,第一个节点存上一个节点的地址(头指针),中间的存储数据,最后一个存下一个节点的内存(尾指针)

image-20221029184012752

Python中的列表就是基于双向链表实现的(查询慢,增删快)

  • 定义

    由弱数据类型语言决定,直接赋值给变量

    ​ 如: ls = [ 2,3,4,5 ]

    使用全局函数list对列表进行定义

     ls = list()
    
      ls = list([1,2,3,4,5,""])
    
      #容器中的每一个值,我们叫做元素
    
  • 如何访问元素

    ​ 使用下标来访问,注意下标是从0开始的(外国人喜欢从0开始计数) ls[0] 如果下标超过范围会报异常, 可以用下标来访问值,也可以修改值,另外下标也可以是负数

    求列表的长度?

    全局函数len(容器) -----------返回容器的长度

  • 如何遍历容器

     #For循环
         For i in ls:
            Print(i)
    
     #While循环
         Index = 0
         While index < len(ls):
              Print(ls[index])
              Index += 1
    
    
    
  • 常见的方法

    方法名含义
    append()向列表尾部追加元素
    insert(index, object)向指定的位置追加元素
    sort()列表排序(只能排Int)字母按照ASCII值进行排序,类型不能混淆
    index()查找元素第一次在列表中出现的位置,如果没有这个元素则报错
    reverse()将列表元素顺序翻转
    remove()通过元素来移除元素,如果元素不存在则抛出异常
    count()统计元素在列表中的个数
    clear()清除元素
    copy()浅拷贝对象(拷贝) 不等价于 = (引用传递),在堆内存中进行对象拷贝
    extend()合并列表
    pop()与append()相反,删除列表最后一个元素,并返回值这个元素,要删除指定位置的元素,用pop(i)方法,其中i是索引位置

    补充:

    要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

    >>> classmates[1] = 'Sarah'
    >>> classmates
    ['Michael', 'Sarah', 'Tracy']
    

    list里面的元素的数据类型也可以不同,比如:

    >>> L = ['Apple', 123, True]
    

    list元素也可以是另一个list,比如:

    >>> s = ['python', 'java', ['asp', 'php'], 'scheme']
    >>> len(s)
    4
    

    要注意s只有4个元素,其中s[2]又是一个list,如果拆开写就更容易理解了:

    >>> p = ['asp', 'php']
    >>> s = ['python', 'java', p, 'scheme']
    

    要拿到'php'可以写p[1]或者s[2][1],因此s可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。

    如果一个list中一个元素也没有,就是一个空的list,它的长度为0:

    >>> L = []
    >>> len(L)
    0
    
  • 练习:

    L = [
         ['Apple', 'Google', 'Microsoft'],
      ['Java', 'Python', 'C', 'PHP'],
        ['Adam', 'Bart', 'Lisa']
    ]
    
    # 打印Apple:
    print(L[0][0])
    # 打印Google:
    print(L[0][1])
    # 打印Microsoft:
    print(L[0][2])
    

    内存模型

    什么是内存?存储数据的地方,电脑是有内存条的,内存条是用来存储数据的

    至于怎么存储?不知道大家对计算机底层有没有了解?计算机底层是二进制运算的,0和1两个数据

    二进制逢二进一

    大家知道物理里面有二极管?二极管的特性-----单向导通(一个半导体材料)电流从正向是一个导体,电流正常通过了,从负极就是一个绝缘体,那么是不是可以模拟0和1,导通是0,绝缘体是1

    image-20221029184105527

    一般会把内存分为四个区域,第一个是栈stack(栈的内存不会太大,一般会存储变量或者函数调用等)

    另一个区域是堆heap (存储的是对象)

    ls = [ ] 实则是在栈中有个对象是ls ,ls的存储地址是0x456f(十六进制)

    image-20221029184126449

    内存模型:

    栈(stack):

    ​ 先进后出,后进先出

    堆(heap):

    队列:先进先出,后进后出

4.3 集合—set(哈希结构)

  • 建一个集合的方法

    方法含义
    s= set()使用全局函数set()创建一个集合
    s = set({1,3,4,5})创建集合并赋值
    s = {}如果使用空的{}来创建一个对象,该对象是一个字典,不是一个集合
    s = {元素}{}至少要有一个元素,此时才是集合
  • 集合底层是基于hash表实现的

    所以集合是无序的、不能重复的(也就意味着集合中的元素都是唯一的,无序并不是顺序,涉及到hash的底层,我们后面再说)

s[0] ? ---------不可以,会报错,因为是无序的

  • 常见的方法

    方法含义
    Clear
    remove
    copy
    add添加元素,如果元素重复,不能添加,涉及到hash算法
    difference差集
    intersection交集
    union并集
    update更新集合,合并集合
    discard移除元素,但是元素如果不存在,则不做任何操作

4.4 元组—tuple

元组是有序的 所以可以通过下标来获取t[0],下标也可以为负数但不能赋值成另外的元素Python元组中允许存在重复元素。 Python元组是一种有序的、不可变的序列类型,与列表类似,但是元组一旦创建后,其内容就不能再进行修改

不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。

  • 元组的创建:

​ 弱数据类型创建 t = (1,2,3,4)

​ tuple全局函数创建 t = tuple() t = tuple((元素……))

  • 通过下标来访问元素

  • 元组的特点:

元组是一个不可变类型,元组的元素一旦定义下来,则无法改变

注意:

​ 虽然元组不可变,如果元组内部元素是可变类型,那么该元组就可变

f = (1,2,[1,2,3,4],4)

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个tuple定义的时候有3个元素,分别是'a''b'和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?

我们先看看定义的时候tuple包含的3个元素:

image-20230109103143683

当我们把list的元素'A''B'修改为'X''Y'后,tuple变为:

image-20230109103457018

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

  • 常见方法:

    方法名说明
    Index
    count

    面试题 : a = (1) type a --------- int类型

    ​ a = (1,) type a----------元组类型

4.5字典—dict

  • dict全称dictionary,在其他语言中也称为map
  • 键值对形式存在(当作二维表,多行两列的表格)
  • 定义:

​ d = {“name”:“zhangsan”,“age”:16,“gender”:“男”}

​ dd = dict()

​ dd = dict({“name”:“zhangsan”,“age”:16,“gender”:“男”})


  • 如何访问值

​ 通过key来访问对应的值 d[“name”]

字典对象[key] 返回key对应的值,如果没有抛出异常

​ 字典对象[key] = 新值

​ 字典对象新[key] = 新值 增加新的键值对

  • 常见的方法

    方法名说明
    clear
    copy
    get和字典对象[key]类似,通过key值获取值,注意,如果没有该键,则返回none
    keys返回所有的键
    values返回所有的值
    setdefault设置一个默认值
    items返回键值对
    pop(key)通过键来移除键值对,如果没有,则抛出异常
    popitem移除键值对(按照LIFO后进先出的顺序)
  • ​ 字典的遍历:

for k in d:

    print(k,d.get(k))

for k in d.keys():

    print(k,d[k])

for k,v in d.items():

     print(k,v)

4.6 常见的几种排序算法

4.6.1 冒泡排序
冒泡排序

原理:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。 [1]
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 [1]
  3. 针对所有的元素重复以上的步骤,除了最后一个。 [1]
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
arr = [3, 5, 15,36, 38,44,26, 27, 47]
for i in range(0, len(arr) - 1):
    for j in range(0, len(arr) - 1 - i):
        if arr[j] >= arr[j + 1]:
            arr[j], arr[j + 1] = arr[j + 1], arr[j]
print(arr)

4.6.2 选择排序
选择

原理:

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

arr = [8, 3, 2, 6, 1, 4, 9, 7]
for i in range(0, len(arr)):
    for j in range(i + 1, len(arr)):
        if arr[i] >= arr[j]:
            arr[i], arr[j] = arr[j], arr[i]
print(arr)

4.6.3 插入排序
插入

原理:

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 [1] 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动

arr = [8, 3, 2, 6, 1, 4, 9, 7]
for i in range(1, len(arr)):
    for j in range(i, 0, -1):
        if arr[j] <= arr[j - 1]:
            arr[j], arr[j - 1] = arr[j - 1], arr[j]
print(arr)

4.6.4 计数排序
计数

原理:

计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。

arr = [7, 3, 2, 0, 1, 2, 3, 6]
max_num = arr[0]
min_num = arr[0]
for num in arr:
    if num > max_num:
        max_num = num
    elif num < min_num:
        min_num = num
# print(max_num)
# print(min_num)
# 计数列表的长度
len_arr1 = max_num - min_num + 1
# 偏移量
offset = min_num
# 初始化计数列表 元素全为0
arr1 = [0] * len_arr1
# 排序后的列表
arr2 = [0] * len(arr)
# 计数
for num in arr:  # 7,3,2,0,1,2,3,6
    #print(num)
    arr1[num - offset] += 1  # 0 0 0 0 0 0 0
#print(arr1)
index = 0
for i in range(0, len_arr1):
    for j in range(0, arr1[i]):
        print(i + offset, end=" ")
        arr2[index] = i + offset
        index += 1
print()
#print(arr2)

4.7 查找算法—二分查找

原理:

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

lst = [11, 22, 33, 44, 55, 66, 77, 88, 99, 123, 234, 345, 456, 567, 678, 789]
n = 567
left = 0
right = len(lst)-1
count = 1
while left <= right :
    middle = (left +right)
    if n > lst[middle] :
        left = middle + 1
    elif n < lst[middle] :
        right = middle -1
    else :
        #print(count)
        print("存在")
        print(middle)
        break
    count += 1
else :
    print("不存在")

4.8 作业

1. 已知列表x=list(range(9)),那么执行语句del x[:2]之后,x的值为()
A.[1,3,5,7,9]  
B.[1,3,5,7]
C.[0,1,3,5,7]
D.[2,3,4,5,6,7,8]

2. len(range(1,10))的值是()
A.8    B.9    C.10    D.11

3. 表达式",".join(list)中list是列表类型,以下选项中对其功能的描述正确的是()
A.将逗号字符串增加到列表list中
B.在列表list每个元素后增加一个逗号
C.将列表所有元素连接成一个字符串,每个元素后增加一个逗号
D.将列表所有元素连接成一个字符串,元素之间增加一个逗号

4. 表达式[1,2,3]*3的执行结果为()
A.[1,2,3,1,2,3,1,2,3]
B.{[1,2,3],[1,2,3],[1,2,3]}
C.[1,2,3],[1,2,3],[1,2,3]
D.([1,1,1,2,2,2,3,3,3])

5. 假设列表对象aList的值为[3,4,5,6,7,9,11,13,15,17],那么切片aList[3:7]得到的值是()
A.[5,6,7,9,11]
B.[6,7,9,11]
C.[5,6,7,9,]
D.[7,9,11,13]

6. 已知x=list(range(20)),那么表达式x[-1]的值为()。
A.0B.1C.20D.19

7. 切片操作list(range(6))[::2]执行结果为()
A.[0,1]B.[0,2,4,6]C.[0,1,2]D.[0,2,4]

8. 已知x=[3,7,5],那么执行语句x=x.sort(reverse=True)之后,x的值为()
A.[3,5,7]B.[7,5,3]C.NoneD	.[3,7,5]

9. 下面程序输出是什么?
txt=["a","b","c","d","e"]
stop_words=["d","i"]
t=[x for x in txt if x not in stop_words]
print(t)
A.["a","b","c","d","e"]
B.["a","b","c","d"]
C.["a","b","c"]
D.["a","b","c",”e”]

10. 下面程序的输出是:
	ls=["abcd","ab","cd","cdab"]
	n,m=0,0
	for line in ls:
     if "ab" and "cd" in line:
          n+=1
          m+=1
     elif "ab" in line:
          n+=1
     elif "cd" in line:
          m+=1
print (n,m)
A:3 3B:3 4C:4 3D:4 4

11. 以下程序的输出结果是( ):
ls=[11,22,33,44]
for i in ls:
     if i=="33":
        print("找到!i=",i)
        break
else
      print("未找到...")
A: 未找到...
B: 未找到... 未找到... 找到!I=33
C:未找到... 未找到... 未找到... 未找到...
D:找到!I=33

12. 以下程序的输出结果是( ):
k=0
for i in range (4):
      for j in range(i+1):
           k+=j
           if j>1:
              break
print(k)
A:5   B:6   C:7   D:8

第五章 函数

5.1 函数概述

在Python中,定义一个函数要使用**def**(define function)语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用**return**语句返回。

5.2 python定义和调用函数

def 函数名称([参数列表]):

​ 函数体

​ [return 函数返回值] #可以通过return返回返回值

我们以自定义一个求绝对值的my_abs函数为例:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

    
 print(my_abs(-99)) 

注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

如果没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return

在Python交互环境中定义函数时,注意Python会出现...的提示。函数定义结束后需要按两次回车

>>> def my_abs(x):                                      
...     if x >= 0:                                      
...         return x                                    
...     else:                                           
...         return -x                                   
...                                                     
>>> my_abs(-9)                                          
9                                                       
>>> 

1.如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

pass还可以用在其他语句里,比如:

if age >= 18:
    pass

缺少了pass,代码运行就会有语法错误

2.参数检查

调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError

>>> my_abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: my_abs() takes 1 positional argument but 2 were given

但是如果参数类型不对,Python解释器就无法帮我们检查。试试my_abs和内置函数abs的差别:

>>> my_abs('A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in my_abs
TypeError: unorderable types: str() >= int()
>>> abs('A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'

当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样。所以,这个函数定义不够完善。

让我们修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误:

>>> my_abs('A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in my_abs
TypeError: bad operand type

错误和异常处理将在后续讲到。

  1. 返回多个值

函数可以返回多个值吗?答案是肯定的。

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的坐标:

import math

def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny

import math语句表示导入math包,并允许后续代码引用math包里的sincos等函数。

然后,我们就可以同时获得返回值:

>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0

但其实这只是一种假象,Python函数返回的仍然是单一值:

>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
(151.96152422706632, 70.0)

原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

4.小结

定义函数时,需要确定函数名和参数个数;

如果有必要,可以先对参数的数据类型做检查;

函数体内部可以用return随时返回函数结果;

函数执行完毕也没有return语句时,自动return None

函数可以同时返回多个值,但其实就是一个tuple。

函数的调用

函数的调用,函数是一个功能,定义完成后,需要调用,函数定义完成后,并不会自己调用,加载到内存中,等待调用者来调用

要调用一个函数,需要知道函数的名称和参数,比如求绝对值的函数abs,只有一个参数。可以直接从Python的官方网站查看文档:

http://docs.python.org/3/library/functions.html#abs

也可以在交互式命令行通过**help(abs)**查看abs函数的帮助信息。

调用----------函数名称([实参列表])

调用abs函数:

>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34

调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个:

>>> abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)

如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型:

>>> abs('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'

max函数max()可以接收任意多个参数,并返回最大的那个

>>> max(1, 2)
2
>>> max(2, 3, 1, -5)
3

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

String内置模块

Import string

这个模块提供了大量对于字符串操作的属性和方法

re模块(正则表达式)

re.sub(“”,””,字符串) 类似与replace

5.3 函数的分类

  • 以函数是否存在参数:

    ​ 有参函数

    无参函数

  • 以是否有返回值

    有返回值

    无返回值

  • 定义者来分类

    系统

        Python官方有定义好的,内置到cpython解释器内部的
    

    第三方

    ​ (公司、组织、个人)

    ​ 自定义的

5.4 函数的参数

  • 函数参数(位置参数、默认值参数、命名参数、可变参数、万能参数)
计算圆的周长

 def get_circle_cal(r, pi):
 return 2 * pi * r

if __name__ == '__main__':

 r = float(input("请输入圆的半径:"))

cal = get_circle_cal(r, 3.14)

print("半径是为{}的圆的周长是{}".format(r, cal))

注意:python并不是从main入口的

代码是脚本,从上而下执行

Python的main函数的目的:写在main函数的代码,并不会导入到其他模块中

5.4.1 位置参数

我们先写一个计算x2的函数:

def power(x):
    return x * x

对于power(x)函数,参数x就是一个位置参数。

当我们调用power函数时,必须传入有且仅有的一个参数x

>>> power(5)
25
>>> power(15)
225

现在,如果我们要计算x3怎么办?可以再定义一个power3函数,但是如果要计算x4、x5……怎么办?我们不可能定义无限多个函数。

你也许想到了,可以把power(x)修改为power(x, n),用来计算xn:

def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

对于这个修改后的power(x, n)函数,可以计算任意n次方:

>>> power(5, 2)
25
>>> power(5, 3)
125

修改后的power(x, n)函数有两个参数:xn,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数xn

5.4.2 默认值参数

当参数中有值的时候,参数就是默认值参数。也就说说,如果在调用的时候,给这个参数 赋值了,则按照赋值情况算,如果没有赋值,也不会报错,按照默认值算

新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:

>>> power(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'

Python的错误信息很明确:调用函数power()缺少了一个位置参数n

这个时候,默认参数就排上用场了。由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2:

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

这样,当我们调用power(5)时,相当于调用power(5, 2)

>>> power(5)
25
>>> power(5, 2)
25

而对于n > 2的其他情况,就必须明确地传入n,比如power(5, 3)

从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面);

二是如何设置默认参数:

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

使用默认参数有什么好处?最大的好处是能降低调用函数的难度。

举个例子,我们写个一年级小学生注册的函数,需要传入namegender两个参数:

def enroll(name, gender):
    print('name:', name)
    print('gender:', gender)

这样,调用enroll()函数只需要传入两个参数:

>>> enroll('Sarah', 'F')
name: Sarah
gender: F

如果要继续传入年龄、城市等信息怎么办?这样会使得调用函数的复杂度大大增加。

我们可以把年龄和城市设为默认参数:

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)

这样,大多数学生注册时不需要提供年龄和城市,只提供必须的两个参数:

>>> enroll('Sarah', 'F')
name: Sarah
gender: F
age: 6
city: Beijing

只有与默认参数不符的学生才需要提供额外的信息:

enroll('Bob', 'M', 7)
enroll('Adam', 'M', city='Tianjin')

可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

有多个默认参数时,调用的时候,既可以按顺序提供默认参数,比如调用enroll('Bob', 'M', 7),意思是,除了namegender这两个参数外,最后1个参数应用在参数age上,city参数由于没有提供,仍然使用默认值。

**也可以不按顺序提供部分默认参数。**当不按顺序提供部分默认参数时,需要把参数名写上。比如调用enroll('Adam', 'M', city='Tianjin'),意思是,city参数用传进去的值,其他默认参数继续使用默认值。

默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:

先定义一个函数,传入一个list,添加一个END再返回:

def add_end(L=[]):
    L.append('END')
    return L

当你正常调用时,结果似乎不错:

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

当你使用默认参数调用时,一开始结果也是对的:

>>> add_end()
['END']

但是,再次调用add_end()时,结果就不对了:

>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list。

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

定义默认参数要牢记一点:默认参数必须指向不变对象!

要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

现在,无论调用多少次,都不会有问题:

>>> add_end()
['END']
>>> add_end()
['END']

为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

如果一个函数中存在大量的参数时,在调用的时候一个一个传递,会很麻烦,那怎么办?

Def show(a,b,c,d,e)print()

可以写成

Def show(a,b,*args)print()
5.4.3 可变参数

可变参数,*变量来表示可变参数,表示可以传递实际参数,也可以不传递参数,args以元组的形式将剩余参数封装起来

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。

要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

但是调用的时候,需要先组装出一个list或tuple:

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

如果利用可变参数,调用函数的方式可以简化成这样:

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

所以,我们把函数的参数改为可变参数:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

定义可变参数和定义一个list或tuple参数相比,**仅仅在参数前面加了一个*号。**在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

>>> calc(1, 2)
5
>>> calc()
0

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14

这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

5.4.4 关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

函数person除了必选参数nameage外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数:

>>> person('Michael', 30)
name: Michael age: 30 other: {}

也可以传入任意个数的关键字参数:

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

当然,上面复杂的调用可以用简化的写法:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

当参数出现**时,参数可以接收键值对存在的参数

  def show_info(x,**kwargs):

   print(x)
5.4.5 命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

仍以person()函数为例,我们希望检查是否有cityjob参数:

def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        pass
    if 'job' in kw:
        # 有job参数
        pass
    print('name:', name, 'age:', age, 'other:', kw)

但是调用者仍可以传入不受限制的关键字参数:

>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数

调用方式如下:

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

由于调用时缺少参数名cityjob,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。

命名关键字参数可以有缺省值,从而简化调用:

def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)

由于命名关键字参数city具有默认值,调用时,可不传入city参数:

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

def person(name, age, city, job):
    # 缺少 *,city和job被视为位置参数
    pass
5.4.6 参数组合
def test(a,b,*args,**kwargs):

   print()

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

比如定义一个函数,包含上述若干种参数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

最神奇的是通过一个tuple和dict,你也可以调用上述函数:

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差

5.4.7 小结

Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

默认参数一定要用不可变对象,如果是可变对象,程序运行时可能会有逻辑错误!

要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kw是Python的习惯写法当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值

定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

5.5 局部变量和全局变量

  • 全局变量

    ​ 在python中,定义在py文件的变量,叫做全局变量

    ​ 特点:代码运行时,代码始终有效(不是手动回收手动写成None)

    name = "zhangsan"  #全局变量
    name = None
    
  • 局部变量(local 本地变量):

    定义在函数中的变量叫做局部变量,当函数被垃圾机制回收的时候,该变量也会被回收

    局部变量作用域也只有在函数内有效,当外界访问局部变量时,会报错,找不到

    name = "zhangsan"  #全局变量
    age = 18
    
    # name = None
    
    def show(msg):
    	gender = "男"
    	print(msg)
    	print(name)
    	print(age)
    	print(gender)
    
    #print(gender)  #会报错
    show("生活如此多娇~~~")
    
  • 在函数外面不能访问到函数内部定义的变量(局部变量)

  • 反过来说,在函数内部可以访问本地变量(全局变量)

注意::::函数中不能修改(操作)全局变量,如果一定要在函数中操作全局变量,用global(全局的意思)关键字来声明,不建议在函数中修改全局变量

# 在python中定义在py文件中的变量,叫做全局变量,因为python是解释性语言
name = "zhangsan"  # 全局变量
age = 18


# name = None

def show(msg):
    # 如果一定要在函数中操作全局变量,使用gglobal关键字
    gender = "男"
    global age
    print(msg)
    print(name)
    # 函数中增加了全局变量
    # age += 1   #会报错  函数内部不能操作全局变量
    age += 1
    print(age)
    print(gender)


# print(gender)  #会报错
show("生活如此多娇~~~")

5.6 函数的运行原理

  • 为什么在函数外不能使用局部变量呢?

    ​ 函数本质就是对象(存储在堆中)

    函数的调用的过程:函数调用本质就是压栈,函数调用成功后就会弹栈

    函数名称代指函数本身

    image-20221029192252458

注意:在弱数据类型语言中,万物皆对象,函数也是对象,函数的参数类型任意(基本数据类型+对象)

5.7 值传递和引用传递

  • 值传递

    def add(x,y):
    	return x+y
    x = float(input("请输入第一个数:"))
    y = float(input("请输入第二个数:"))
    print(add(x,y))
    
    image-20221029192342247

    它们之间冲突吗?不冲突,add(x,y)中x和y是局部变量(形参),后面我们输入的x和y是实参,实参的值会传递给形参

  • ​ 引用传递(传对象)

    # 引用传递
    def info(fn, msg):
        fn()
        print(msg)
    
    
    def print_msg():
        print("我自己定义的函数")
    
    
    print(print_msg)  # 代表的是内存地址,指向的是一个函数prit_msg,代表的是函数的本身
    print(print_msg())  # 代表函数的返回值,没有返回值,返回的是None
    info(print_msg, "哈哈哈")
    

5.8 匿名函数

### 5.8 匿名函数—lambda表达式

匿名函数  ----- lambda表达式

匿名函数:没有名称的函数,称为匿名函数

javaScript中定义函数



​```javascript
 function 函数(参数){

     //代码块

     return 返回值;

}

匿名函数

   function (参数){

代码块

}

Python 中

  def 函数名称(参数):

          函数体

Python在早期是不支持匿名函数的,python是通过换行和缩进来体现块

在lambda表达式出现的时候可以支持匿名函数

最早是在C++中出现的,目的是为了简化操作,在其他语言–>箭头函数(前端)就是lambda表达式

lambda: 代码 如果函数需要返回值,则不需要写返回值

在python中lambda代指的是一个没有名字的函数(匿名函数),一般lambda,也就是匿名函数,一般表示简单的函数(一行或者两行)

  1. 匿名函数与普通函数的对比
def sum_func(a, b, c):
    return a + b + c


sum_lambda = lambda a, b, c: a + b + c

print(sum_func(1, 100, 10000))
print(sum_lambda(1, 100, 10000))

运行结果:

10101
10101

可以看到,lambda适用于多个参数、一个返回值的情况,可以用一个变量来接收,变量是一个函数对象,执行这个函数对象的结果与执行一个普通函数的结果一样。

**lambda函数比普通函数更简洁,且没有声明函数名,**上面的代码是用一个变量来接收lambda函数返回的函数对象,并不是lambda函数的名字。

  1. 匿名函数的多种形式

无参数

lambda_a = lambda: 100
print(lambda_a())

一个参数

lambda_b = lambda num: num * 10
print(lambda_b(5))

多个参数

lambda_c = lambda a, b, c, d: a + b + c + d
print(lambda_c(1, 2, 3, 4))

表达式分支

lambda_d = lambda x: x if x % 2 == 0 else x + 1
print(lambda_d(6))
print(lambda_d(7))

运行结果:

100
50
10
6
8

可以看到,lambda的参数可以0个到多个,并且返回的表达式可以是一个复杂的表达式,只要最后的值是一个值就行了。

3 . lambda作为一个参数传递

def sub_func(a, b, func):
    print('a =', a)
    print('b =', b)
    print('a - b =', func(a, b))


sub_func(100, 1, lambda a, b: a - b)

运行结果:

a = 100
b = 1
a - b = 99

上面的代码中,sub_func中需要传入一个函数,然后这个函数在sub_func里面执行,这时候我们就可以使用lambda函数,因为lambda就是一个函数对象。

  1. lambda作为函数的返回值
def run_func(a, b):
    return lambda c: a + b + c


return_func = run_func(1, 10000)
print(return_func)
print(return_func(100))

运行结果:

<function run_func.<locals>.<lambda> at 0x00000254E4C94158>
10101

匿名函数可以作为一个函数的返回值,在上面的代码中,run_func返回的是一个匿名函数,返回的是一个函数对象,当我们执行这个函数时,可以得到lambda函数的结果。

注意:其中的a,b两个参数是run_func中的参数,但我们执行返回的函数return_func时,已经不在run_func的作用域内了,而lambda函数仍然能使用a,b参数。说明lambda函数会将它的运行环境保存一份,一直保留到它自己执行的时候使用。

匿名函数的优点:简化代码,优化内存

缺点:降低了代码的可读性,增加了代码的维护成本

5.9 偏函数

在python中,我们有时候需要调用某个函数,如果想将该函数中的某个参数设置一个固定的值,那么就可以使用偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

functools import functools

from functools import partial----------只导入了partial

partial(函数名称,参数=固定的值)

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:

int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')
12345

int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
    return int(x, base)

这样,我们转换二进制就非常方便了:

>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

>>> int2('1000000', base=10)
1000000

最后,创建偏函数时,实际上可以接收函数对象*args**kw这3个参数,当传入:

int2 = functools.partial(int, base=2)

实际上固定了int()函数的关键字参数base,也就是:

int2('10010')

相当于:

kw = { 'base': 2 }
int('10010', **kw)
int2('10010', **kw)

当传入:

max2 = functools.partial(max, 10)

实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)

相当于:

args = (10, 5, 6, 7)
max(*args)
# 上面写法等同于下面写法
args = (5, 6, 7)
max2(*args)

结果为10

小结

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

5.10 函数递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

前提条件:

1.函数自己调用自己

2.要有终止条件

注意:如果递归中没有结束条件,会形成死循环(python不会,会报错)

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

image-20230201110910499

所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

上面就是一个递归函数。可以试试:

>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

>>> fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归****了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

def fact(n):
    return fact_iter(n, 1)

可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1num * product在函数调用前就会被计算,不影响函数调用。

fact(5)对应的fact_iter(5, 1)的调用如下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

小结

使用递归函数的优点是逻辑简单清晰缺点是过深的调用会导致栈溢出。

针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

5.11 全局函数

http://docs.python.org/3/library/functions.html#abs

abs()delattr()hash()memoryview()set()
all()dict()help()min()setattr()
any()dir()hex()next()slice()
ascii()divmod()id()object()sorted()
bin()enumerate()input()oct()staticmethod()
bool()eval()int()open()str()
breakpoint()exec()isinstance()ord()sum()
bytearray()filter()issubclass()pow()super()
bytes()float()iter()print()tuple()
callable()format()len()property()type()
chr()frozenset()list()range()vars()
classmethod()getattr()locals()repr()zip()
compile()globals()map()reversed()
complex()hasattr()max()round(

全局函数

round() ---------- 在奇数上是标准的四舍五入,在偶数上是以5为分界线,例如:round(5.5) = 6 round(4.5) = 4

第六章 字符串对象和切片操作

6.1 字符串对象

具有特殊含义的字符组成的串,什么是字符?------ 肉眼可以识别的各种符号

计算机里面有个字节的概念,肉眼是无法识别的,底层是0,1表示的,比如图片

  1. python三种字符串的定义方式

​ 由弱数据类型语言的特性决定的:

​ 单引号

​ 双引号

​ 三引号(允许换行)

​ str

​ s = str(“字符串”)

6.2 字符串对象常见方法

字符串常见方法

方法名说明
capitalize()大写,队长,统领的意思格式化字符串,将字符串的首字母大写
center(width,[fillchar])设置字符串按照长度居中,fillchar默认是空格,是可选参数
count()统计字符或字符串的出现次数
endswith()判断字符串是否以XXX结尾
startswith()判断字符串是否以XXX开头
index()查找字符或者字符串在字符串第一次出现的位置,如果不存在则抛出异常
rindex从右向左找,查找字符或者字符串在字符串中最后一个的位置
find查找字符或者字符串在字符串第一次出现的位置,如果不存在返回-1
rfind从右向左找,查找字符或者字符串在字符串中最后一个的位置,如果不存在返回-1
encode(对应的编码)python3提供将字符串转换为字节的方法(网络传输的时候需要将电信息存储到磁盘上,存储到字符串上是需要二进制数据,在python2中涉及到字符转换非常的麻烦,他不支持中文)(转换的时候需要指定编码集utf-8,转换成功后是字节,type,bxxxx是字节数据)如果字节想转换为字符串?--------decode(对应的的编码)((dir(t) t是字节) 不是字符串的方法) -------字节的方法
format格式化字符串 n =2 nn=3 print(“n={},nn={}”.format(n,nn))
islower判断是否都是小写字母
isupper判断是否都是大写字母
istitle判断字符串是否是标题(看首字母是否是大写,大写首字母就是标题,否则不是)
isspace判断是不是空格位(不常用,了解就行)
isdigit判断是否为数字(将字符串转换为整数,如果不是数字不能转int)
isalnum判断是否含有有效符号(*$%等)
isalpha判断是否都是字母
title将字符串转换为标题格式
lower将字符串转换为小写
upper将字符串转换为大写
split(“符号”)按照指定的符号将字符串进行切割(ls = s.split(" ") -----以空格分割),返回一个列表
join按照特定的符号将一个可迭代对象拼接成字符串(" ".jion(ls) ------空格拼,不是列表的方法)
strip清除字符串两侧的空格(java里面有trim()),不能清除字符串中间的空格,例如注册信息的时候,容易打空格,后台接收到的字符串含有很多空格“name ” ,如果用户以"name"登录的时候会登录失败
lstrip清除字符串左侧的空格
rstrip清除字符串右侧的空格
replace(“原字符串”,“新字符串”)替换对应的字符串
ljust(长度参数)左对齐
rjust(长度参数)右对齐

6.3 切片操作

Python提供用来分割可迭代对象(容器)

一个完整的切片表达式包含两个“:”,用于分隔三个参数(start_index、end_index、step)

切片操作基本表达式:object[start_index : end_index : step]

step:正负数均可,其绝对值大小决定了切取数据时的“步长”,而正负号决定了****“切取方向,正表示“从左往右”取值,负表示“从右往左”取值。当step省略时,默认为1,即从左往右以增量1取值。“切取方向非常重要!”“切取方向非常重要!”“切取方向非常重要!”,重要的事情说三遍!

start_index:表示起始索引(包含该索引本身);该参数省略时,表示从对象“端点”开始取值,至于是从“起点”还是从“终点”开始,则由step参数的正负决定,step为正从“起点”开始,为负从“终点”开始。

end_index:表示终止索引(不包含该索引本身);该参数省略时,表示一直取到数据”端点“,至于是到”起点“还是到”终点“,同样由step参数的正负决定,step为正时直到”终点“,为负时直到”起点“

​ 对象[start:] 从start位置开始切割字符串,切到末尾,包含strat位置

​ 对象[start:end] 从start位置开始切割字符串,切到end位置为止,包含strat位置,不包含end位置

​ 对象[start:end:step] 从start位置开始切割字符串,切到end位置为止,step为步长,默认值为1,当步长为负数的时候,会反向切割

以下示例均以列表a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]为例:

 >>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
image-20221029184440955

**1.**切取单个值

>>> a[0]

0
>>> a[-4]

6

**2.**切取完整对象

>>> a[:] # 从左往右

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> a[::] # 从左往右

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> a[::-1] # 从右往左

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

3.start_indexend_index全为正(+****)索引的情况

>>> a[1:6] # step=1,从左往右取值,start_index=1到end_index=6同样表示从左往右取值。

[1, 2, 3, 4, 5]

>>>a[1:6:-1] # step=-1,决定了从右往左取值,而start_index=1到end_index=6决定了从左往右取值,两者矛盾。

>>> [] # 输出为空列表,说明没取到数据。

>>>a[6:1] # step=1,决定了从左往右取值,而start_index=6到end_index=1决定了从右往左取值,两者矛盾。

>>> [] # 同样输出为空列表。

>>>a[:6] # step=1,从左往右取值,从“起点”开始一直取到end_index=6。

>>> [0, 1, 2, 3, 4, 5]

>>>a[:6:-1] # step=-1,从右往左取值,从“终点”开始一直取到end_index=6。

>>> [9, 8, 7]

>>>a[6:] # step=1,从左往右取值,从start_index=6开始,一直取到“终点”。

>>> [6, 7, 8, 9]

>>>a[6::-1] # step=-1,从右往左取值,从start_index=6开始,一直取到“起点”。

>>> [6, 5, 4, 3, 2, 1, 0]

4.start_indexend_index全为负(-****)索引的情况

>>>a[-1:-6] # step=1,从左往右取值,而start_index=-1到end_index=-6决定了从右往左取值,两者矛盾。
>>> []

>>>a[-1:-6:-1] # step=-1,从右往左取值,start_index=-1到end_index=-6同样是从右往左取值。
>>> [9, 8, 7, 6, 5]

>>>a[-6:-1] # step=1,从左往右取值,而start_index=-6到end_index=-1同样是从左往右取值。
>>> [4, 5, 6, 7, 8]

>>>a[:-6] # step=1,从左往右取值,从“起点”开始一直取到end_index=-6。
>>> [0, 1, 2, 3]

>>>a[:-6:-1] # step=-1,从右往左取值,从“终点”开始一直取到end_index=-6。
>>> [9, 8, 7, 6, 5]

>>>a[-6:] # step=1,从左往右取值,从start_index=-6开始,一直取到“终点”。
>>> [4, 5, 6, 7, 8, 9]

>>>a[-6::-1] # step=-1,从右往左取值,从start_index=-6开始,一直取到“起点”。
>>> [4, 3, 2, 1, 0]

5.start_indexend_index正(+)负(-****)混合索引的情况

>>>a[1:-6] # start_index=1在end_index=-6的左边,因此从左往右取值,而step=1同样决定了从左往右取值。
>>> [1, 2, 3]

>>>a[1:-6:-1] # start_index=1在end_index=-6的左边,因此从左往右取值,但step=-则决定了从右往左取值,两者矛盾。
>>> []

>>>a[-1:6] # start_index=-1在end_index=6的右边,因此从右往左取值,但step=1则决定了从左往右取值,两者矛盾。
>>> []

>>>a[-1:6:-1] # start_index=-1在end_index=6的右边,因此从右往左取值,而step=-1同样决定了从右往左取值。
>>> [9, 8, 7]

**6.**连续切片操作

>>>a[:8][2:5][-1:]
>>> [4]

相当于:
 a[:8]=[0, 1, 2, 3, 4, 5, 6, 7]
 a[:8][2:5]= [2, 3, 4]
 a[:8][2:5][-1:] = 4
 理论上可无限次连续切片操作,只要上一次返回的依然是非空可切片对象。

**7.**切片操作的三个参数可以用表达式

>>>a[2+1:3*2:7%3] # 即:a[2+1:3*2:7%3] = a[3:6:1]
>>> [3, 4, 5]

**8.**其他对象的切片操作

前面的切片操作说明都以list为例进行说明,但实际上可进行的切片操作的数据类型还有很多,包括元组、字符串等等。

>>> (0, 1, 2, 3, 4, 5)[:3] # 元组的切片操作
>>> (0, 1, 2)

>>>'ABCDEFG'[::2] # 字符串的切片操作
>>>'ACEG'

>>>for i in range(1,100)[2::3][-10:]: # 利用range函数生成1-99的整数,然后取3的倍数,再取最后十个。
    print(i, end=' ')
>>> 72 75 78 81 84 87 90 93 96 99

注:Python常用切片操作

以列表:a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]为说明对象

**1.**取偶数位置

>>>b = a[::2]

[0, 2, 4, 6, 8]

**2.**取奇数位置

>>>b = a[1::2]

[1, 3, 5, 7, 9]
image-20221029184521690

面试题:

  1. 如果使用切片切割数据,如果超越下标会不会报错----不会,会返回一个空列表

  2. 在python中怎样将列表方向输出

list.reverse() 循环方向输出 切片[: :-1]

6.4 作业

  1. 去掉字符串中所有的空格
  2. 获取字符串中汉字的个数
  3. 将字母全部转换为大写和小写
  4. 根据标点符号对字符串进行分行
  5. 去掉字符串数组中每个字符串的空格(循环)
  6. 随意输入心中想到的一个书名,然后输出它的字符串长度(len属性)
  7. 接收用户输入的字符串,将其中的字符进行排序,并以逆序输出,例如:acdefb - abcdef - fedcba
  8. 用户输入一句英文,将其中的单词以反序输出 例如:hello c sharp — sharp c hello
  9. 用户输入一句话,找出所有”呵“的位置
  10. 有个字符串数组,存储了10个书名,书名有长有短,现在将他们统一处理,若长度大于10,则 截取长度为8的子串,将统一处理后的结果输出
  11. 用户输入一句话,找出所有”呵呵“的位置
  12. 如何判断一个字符串是否是另一个字符串的子串
  13. 如何验证一个字符串中的每一个字符均在另一个字符串中出现
  14. 如何生成无数字的全字母的字符串
  15. 如何随机生成带数字和字母的字符串
  16. 如何判定一个字符串中既有数字又有字

第七章 内置模块

  1. 什么是模块?

import XXX ------其中 XXX就是模块 ,自己定义的XX.py文件本质就是模块

打开python的安装包,lib文件下全是模块

image-20221029193052926
  1. 模块的分类
  • 通过模块的创建者

    系统内置模块

    uuid、os、math、random均是python官方提供的cpython解释器提供的模块

    第三方模块

    程序员、组织、公司 创建的第三方模块,第三方模块需要使用,首先需要安装模块

    在线安装(简单方便,这种情况必须有网)(cmd-----pip install modle_name(如果有问题 python -m pip install modle-name))

    pip install requests(requests是使用爬虫的,不用安装,只是给大家演示一下) ------安装requests模块,一般会出现进度条,因为我以及安装了,所以会出现“already”,如下图:

    image-20221029193124629

    离线安装(1,先下载离线安装包xxx.zip 2.解压安装包 3.安装中setup.py 4.cmd:python install setup.py )

    自定义模块

    ​ xxx.py

  1. 模块的导入问题

    import 关键字导入 --------- import math

    image-20221029193203970

    import 模块名称 as alias(别名)

    import hashlib as h (给hashlib起别名)

    image-20221029193252104

from 包 import 模块名称 python3强烈推荐这种方法

image-20221029193252104

7.1 math模块

math主要的作用是数学运算

方法说明
ceil(天花板的意思) 向上取整
floor向下取整 注意:四舍五入(全局函数中的round)
e属性 自然常数
fabs求绝对值 等价于全局函数abs()
fmod求模
isnan判断是不是一个数字(是数字返回flase nan—not a number)
isfinite判断是不是无限
pi圆周率
pow()幂次方
sqrt根号 开平方根

7.2 random模块

该模块主要用来产生随机数(伪随机数,计算机产生不了真正的随机数,是依靠算法来计算的)

方法说明
randint()产生随机整数[m,n]
random()产生一个0~1内的随机数[0,1) random.random() 可以乘10取整得到0·9的整数
uniform(a,b)产生基于正态分布的随机数(一般用不到)
randrange(a)产生一个范围的随机数
choice()在序列(有序的,set是无序的)中随机筛选一个元素 ls=[1,2,3,4,5,6,7,8,9] random.choice(ls) s = {1,2,3,4,5,6,7} random.choice(s)-----报错

其他的方法,自己调取帮助文档help()自己学习,并且总结

7.3 OS模块

操作的是系统的文件系统

os.system(“cls”)-----清屏

方法说明
chdir(path)修改当前工作目录 os.chdir(“c:\”)------os.chdir(“…”) ,一般不会更改
curdir获取当前目录 属性 注意返回的是相对路径 (绝对路径os.path.abspath(os.curdir))
chmod()修改权限 主要用在linux,help(os.chmod)(不做演示)
close关闭文件路径(不做演示)
cpu_count()返回cpu的核 对应线程数(2核4线程)
getcwd()获取当前路径,返回的是绝对路径 ,相当于linux的pwd 如: os.getcwd()
getpid()获取当前进程的进程编号(任务管理器—详细信息)
getppid()获取当前进程的父进程的进程编号
kill()通过进程编号杀死进程(明白就行)
linesep对应系统下的换行符
listdir()返回对应目录下的所有文件及文件夹(隐藏文件也可以调取出来),返回的是列表os.listdir(“C:\test”)
makedirs()创建目录,支持创建多层目录(文件夹)os.makedirs(“a/b/c/d”)
mkdir创建目录,只支持一层创建,不能创建多层
open创建文件,等价于全局函数open (IO流详细讲)
pathsep获取环境变量的分隔符 windows ; linux :
sep路径的分割符 windows \ linux /
remove(文件名或者路径)删除文件 os.remove(b.text)
removedirs()移除指定目录,当前目录如果不是空的,不能移除
system执行终端命令

7.4 os.path模块

先引入Import os 模块,在os模块里面有个很重要的模块path,要注意path是一个子模块,可以通过help(os.path)查看帮助文档

那么此模块的导入方式:先导入os模块,使用dir(os.path);其次可以直接import os.path导入模块;import os.path as p ----------dir§;from os import path

方法说明
abspath(相对路径)返回路径对应的绝对路径(完整的路径) path.abspath(“.”)
altsep查看python中的各种符号
basename文件名称,shell编程里面也有 path.basename(“路径”)
dirname文件所在的目录,shell编程里面也有
exists判断文件或者目录是否存在(特别有用,使用爬虫爬取数据的时候需要判断是否有这个文件或者文件夹)
getctime创建时间(不做演示)
getmtime修改时间(不做演示)
getsize获取文件的大小,单位是字节
isdir判断path是不是目录(文件夹)
isfile判断path是不是文件
isabs判断是不是绝对路径(不演示)
islink判断是不是连接(不演示)
ismount判断是不是挂载文件(Linux下要用的)(不演示)
join (p1,p2)拼接路径 name=“123.txt” url=“C:/a/b/c” url +“/”+name path.jion(url,name)
sep路径分隔符 url + path.sep +name
split分割或拆分当前路径 os.path.split(“C://desktop”)
realpath返回真实路径 和abspath一样

作业:使用os和os.path以及函数的递归完成:

给出一个路径,遍历当前路径所有的文件及文件夹

打印输出所有的文件(遇到文件输出路径,遇到文件夹继续进文件夹)

import os
from os import path


# 定义一个函数(方法)
def scanner_file(url):
    # os.listdir 输入当前路径下所有的文件和文件夹
    files = os.listdir(url)
    print(files)
    # print(files) 验证
    # 进行路径拼接  三种方式
    for f in files:
    #real_path = url +”\\” +f
    # real_path = url +os.sep +f
        real_path = path.join(url, f)
    # print(real_path) 得到路径
    # 判断该路径是不是文件或者目录
        if path.isfile(real_path):
            print(path.abspath(real_path))
    # 是一个目录
        elif path.isdir(real_path):
        # 此时是一个文件夹
            scanner_file(real_path)
        else:
            print("其他情况")
            pass


scanner_file("C:\\test")

7.5 sys模块

方法说明
api_version获取当前python的内部版本号
argv接收脚本参数的,注意第一个参数是脚本名称(javaz中main函数中有个args) import sys print(sys.argv) ----- python xxx.py 返回的是[“xxx.py”] python xxx.py 1 2 3 hahaha
copyright输出cpython的版权信息
sys.exit()退出系统
getdefaultencoding()获取默认编码, 默认是utf-8(python3),python2的编码是根据系统一致
getfilesystemencoding获取文件系统的默认编码,默认是utf-8
getrecursionlimit获取python对于递归的限制层数
setrecursionlimit(num)重新设置递归的限制层数,注意能不用不要用,以免造成其他的问题
getrefcount(对象)获取对象的引用计数,是垃圾回收机制中的引用计数 例如:ls=[1,2,3,4] sys.getrefcouont(ls) ----结果是2 (默认有一个引用计数 加上ls) a = ls sys.getrefcouont(ls) ----结果是3 b=ls 结果是4
getwindowsversion()返回窗口的版本信息
verson获取版本信息
import os
from os import path
import sys

# 定义一个函数(方法)
def scanner_file(url):
    # os.listdir 输入当前路径下所有的文件和文件夹
    files = os.listdir(url)
    print(files)
    # print(files) 验证
    # 进行路径拼接  三种方式
    for f in files:
        # real_path = url +”\\” +f
        # real_path = url +os.sep +f
        real_path = path.join(url, f)
        # print(real_path) 得到路径
        # 判断该路径是不是文件或者目录
        if path.isfile(real_path):
            print(path.abspath(real_path))
        # 是一个目录
        elif path.isdir(real_path):
            # 此时是一个文件夹
            scanner_file(real_path)
        else:
            print("其他情况")
            pass


ls = sys.argv
if len(ls) < 2:
    print("对不起,这个脚本需要输入参数,参数是需要遍历的磁盘路径")
else:
    scanner_file(sys.argv[1])

     
 
#运行:python  xxx.py D://

python的垃圾回收原理:

​ 引用计数为主,以标记清除和分代收集为辅

java:以标记清除为主,以引用计数和分代收集为辅

7.6 UUID模块

uuid模块 -----是一种特殊的技术,在文件上传、文件备份会经常使用

------------- 获取的是永不重负的字符串

import uuid

print(uuid.uuid4().hex)

7.7 时间日期模块

7.7.1 time模块

Python提供的一个 time模块来格式化时间,在python爬虫等应用中相当有用

print(dir(time))
方法说明
asctime()获取当前时间
ctime()获取当前时间
localtime()获取本地时间 返回的是对象,方便自己完成格式化 ltiem = time.localtime() ltiem.tm_year print(“%s-%s-%s %s:%s:%s” % (ltiem.tm_year, ltiem.tm_mon, ltiem.tm_mday, ltiem.tm_hour, ltiem.tm_min, ltiem.tm_sec))
sleep(1)表示休眠时间,单位是秒
time()获取当前系统的时间戳,单位是秒(计算机从1970年0时0分0秒到现在秒数)
strftime()将时间对象格式化为字符串 f—format help(time.strftime) time.strftime(“%Y-%m-%d”)
strptime()将一个特定格式的时间字符串转换成时间对象 help(time.strptime) s=“2019-08-03 07:35:35” type(s) time.strptime(s,“%Y-%m-%d %H:%M:%S”)
import time

t = time.time()
print("当前时间戳为:", t)
localtime = time.localtime()
print("本地时间为 :", localtime)  # 时间元组,附表查看
localtime = time.asctime(time.localtime())
print("本地时间为 :", localtime)
# 格式化时间
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

print(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))
7.7.2 datetime模块

对time模块的补充,用于时间的处理

dir(time)

datetime子模块:

  • 常用类:date 类、time 类、datetime 类、timedelta 类
import datetime as dt

x = dt.date(2021, 10, 1)  # 年、月、日
y = dt.time(7, 15, 30, 10)  # 时、分、秒、微秒
z = dt.datetime(2021, 10, 1, 7, 15, 30)  # date类和time类的结合,参数:年、月、日、时、分、秒
d = dt.timedelta(3, 2, 1, 6)  # 日、秒、微秒、毫秒,分钟,小时,周星期
print(x)
print(y)
print(z)
print(d)
from datetime import datetime

print(dir(datetime))
print(datetime.now())

7.2.3 calendar模块

calendar(日历) 模块 (不讲,当兴趣自己学习)

  • 常用方法:
import calendar

print("calendar.calendar()返回某年的日历:")
print(calendar.calendar(2023))

print("calendar.month()返回某年某月的日历:")
print(calendar.month(2023, 11))

print("calendar.isleap()返回是否是闰年:")
print(calendar.isleap(2020), calendar.isleap(2021))
print("calendar.leapdays()返回两年之间的闰年总数:")
print(calendar.leapdays(2000, 2021))

print("calendar.weekday()返回某年某月某日是周几:")
print(calendar.weekday(2023, 11, 23))

7.8 加密模块

有了解过加密吗?什么地方用到过加密?加密是计算机最重要的计数之一,无论是学习哪一方面,都需要学习加密

加密算法的分类(须牢记):

  • 以算法是否可逆

​ 可逆算法

是不是使用用一个密钥:

​ 对称加密

​ 解密和加密用的是同一密钥(压缩文件—输入密码(可以暴力破解,百度) 并不是说是右击文件夹对文件夹进行隐藏)

​ 例如:DES算法

​ 不对称加密

​ 加密和解密使用的是一对密钥(公钥、私钥),网络中大部分使用的就是不对称加密,https协议(使用的证书认证,也就是CA认证)(HTTP协议不安全,各种钓鱼网站等信息不安全)

​ 例如: RSA

不可逆算法 (hash算法)

​ 特点:不可逆、结果是唯一的

​ Md5

7.8.1 hashlib

import hashlib

dir(hashlib)

哈希算法

1、 注意:hashlib所有的hash操作起来是一样的,就是你学会一个其它的用法都是一样的,只改变名称就可以,但是在Java里就不一样了,每个算法不一样

cmd窗口:md5 = hashlib.md5()

​ md5

2、 使用步骤:

创建算法对象(md5 sha256),返回一个算法对象

注意:调用MD5的时候一定要给参数:

例如:md5 = hashlib.md5(“12345”) #这个错误不是其他错误

​ md5 = hashlib.md5(“12345”.encode(“utf-8”)) #需要接收字节数据不能是字符串

​ print(md5.hexdigest())

如果不做盐值混淆,直接调用hexdigest() md5.hexdigest()

哈希算法的特点:结果唯一、不可逆,哈希算法是无法反向解密的,安全性特别强,因为结果唯一,可以使用碰撞破解,先把MD5的值存下来,下一次遇到的话就可以破解了

https://cmd5.com/ 这个网站可以对MD5密码解密

在注册账号的时候,需要输入密码和账号存储到数据库中,密码可以铭文存储到数据库吗?不可以,运维人员一定可以看的得到所有人的密码和账号,这样就很不安全,使用密码校验的时候使用的密文校验

3、 盐值混淆

Hash容易碰撞破解,一般建议使用盐值混淆

Md5.update(salt)

import hashlib
md5 = hashlib.md5("12345".encode("utf-8"))

md5.update("!@@@@&%hhh".encode("utf-8"))

print(md5.hexdigest())
image-20221029193555449
7.8.2 hmac模块

hmac也是一个哈希加密库,而且用到了对称加密

参数:第一个参数是要加密的字符串,第二个参数是盐值 ,第三个参数是加密算法

import hmac
print(hmac.new("123456".encode("utf-8"), "hahhah".encode("utf-8"), "md5").hexdigest())

练习:完成用户的登录注册

# 完成登录注册案例
import sys
import hashlib

# 用来存储所有的用户信息
users = []
slat = "hahha!@@@@%$^"


# def password_md5(slat,password):

def password_md5(password):
    md5 = hashlib.md5(password.encode("utf-8"))
    # 盐值混淆
    md5.update(slat.encode("utf-8"))
    return md5.hexdigest()


def main():
    print("~*" * 20)
    print("\t\t 1.用户注册")
    print("\t\t 2.用户登录")
    print("\t\t 3.退出系统")
    print("~*" * 20)
    choice = input("请输入您要操作的选项:")
    return choice


def register():
    username = input("请输入你的用户名称:")
    password = input("请输入你的用户密码:")
    # 保存前要校验数据
    if username == None or username.strip() == " ":
        print("用户名不能为空")
        return
    if password == None or password.strip() == " " or len(password) < 3:
        print("密码长度不能小于3位")
        return

    # 判断用户是否存在?怎么判断?用户名?密码?用户名+密码

    #  for i in users:

    #  	if i.get("username") ==username:

    #  print("对不起,该用户已经存在,请请重新输入")

    # return
    if exits_user(username):
        print("对不起,该用户已经存在,请请重新输入")
        return
    # 组建成一个字典对象
    user = {}
    user["username"] = username
    # user["password"] = password
    # print(user)
    # 密码加密留作业
    user["password"] = password_md5(password)
    users.append(user)
    # 验证
    print(users)


def exits_user(username):
    for i in users:
        if i.get("username") == username:
            # print("对不起,该用户已经存在,请请重新输入")
            return True
    return False


def is_login(username, password):
    for i in users:
        if i.get("username") == username and i.get("password") == password:
            print("登录成功")
            return True
    return False


def login():
    username = input("输入用户名:")
    password = input("输入用户密码:")
    # 加密密码
    password = password_md5(password)

    # for i in users:

    # 	if i.get("username") == username and i.get("password")==password:

    # print("登录成功")

    # return

    if is_login(username, password):
        print("恭喜你登录成功")
    else:
        print("对不起,登录失败,请重新登录")


while True:
    # 独立的界面

    # print("~*"*20)

    # print("\t\t 1.用户注册")

    # print("\t\t 2.用户登录")

    # print("\t\t 3.退出系统")

    # print("~*"*20)

    # choice = input("请输入您要操作的选项:")

    choice = main()
    if choice == "1":
        print("用户注册")
        # 注册本质是什么?将数据存储下来,第一选择数据库(不学)通过IO技术将数据直接持久化磁盘   存储到内存中(容器 选择哪个容器最合适?list+字典)
        register()
    elif choice == "2":
        print("用户登录")
        login()
    else:
        print("程序正常退出")
        sys.exit()

第二部分 面向对象

第八章 IO及对象序列化

8.1 IO流

到目前为止,我们做的一切操作,都是在内存里进行的。如果一旦断电或发生意外,那么你的工作成果将瞬间消失。你有没有一种人生缺少了点什么的感觉?是的,我们还缺少将数据在本地文件系统进行持久化的能力,白话讲就是文件的读写能力。很久以前,我刚开始学习编程的时候,很长一段时间都觉得写的代码毫无用处,直到我学会了对本地文件进行读写之后,才感觉自己真的能写点有用的东西了

8.1.1 什么是IO stream
  • 什么是IO流

Input output Stream 的缩写

IO流主要是计算机的输入和输出操作

常见的IO操作,一般说的是内存与磁盘之间的输入输出(侠义)

IO流操作是一种持久化操作,将数据持久化在磁盘上(例如淘宝账号和密码是使用数据库持久化,下一次登录不需要输入密码)

  • Python操作IO流

​ 掌握open函数即可

open函数主要作用是打开本地的一个文件

  • open函数的解析(help(open))

    第一个参数file 代表要打开或者创建文件的名称或者路径

    第二个参数表示打开的模式(默认的是字符输入流)

    其他参数

Open的简单使用

from os import path

print(path.abspath("."))
# 在当前路径下建立一个 文件(里面写上一句话)
# open("xxx.txt")
# open("路径//xxx.txt")
# 返回的是空,io流打开是一个指针,open函数的第三个参数
f = open('xxx.txt', "r", encoding='utf-8')
print(dir(f))
# 一般会把读到的数据保存在变量中
Msg = f.read()
print(Msg)
# 关闭IO流,操作完成一定要关闭,不然文件删除不了
f.close()

8.1.2 流的分类
  • IO流的分类:

  • 根据数据流动(站在内存的角度上来说)的方向

    ​ 输入流

    ​ 输出流

    例子:把数据保存在硬盘里是输入还是输出流?----输出流

    根据数据的类型

    字节流(存储图片、视频等)

    字符流

8.2 字符流

f = open("xxx.txt", mode="w")

num1 = f.write("哈哈哈哈哈哈,嘿嘿嘿")

# 会返回字符的个数

# 这个时候打开xxx.txt 没有这一句话,没有关闭,所以没有保存

# 会把写入的数据保存到缓存区,缓存区满了才会写入

# 第一种方式 关闭流 关闭的时候会自动调用flus 刷新缓存区
print(num1)
f.close()
f = open("xxx.txt", mode="w")

f.write("呵呵呵呵呵呵,嘿嘿嘿")

f.write("呵呵呵呵呵呵,嘿嘿嘿")

f.write("呵呵呵呵呵呵,嘿嘿嘿")

f.flush()

f = open("xxx.txt", mode="w")

f.write("嘻嘻嘻嘻嘻嘻嘻,嘿嘿嘿")

f.close()  # 会覆盖原来的内容

不覆盖的方式

f = open("xxx.txt", mode="a")

f.write("呜呜呜呜呜呜,嘿嘿嘿")

f.close()

注意:本质mode=“rt” 或者"wt" ------------ t值得是text 字符流

f1 = open("xxx.txt", mode="rt")

c1=f1.read()
print(c1)

f1.close()

f2 = open("xxx.txt", mode="wt")

c2=f2.write("你好你好你好,覆盖了数据")
print(c2)

f2.close()

f3 = open("xxx.txt", mode="wt", encoding="utf-8")

f3.write("你好你好")

f3.close()

f = open("xxx.txt", mode="rt",encoding="utf-8")

f.read(2)
# 读取大文件的时候 可以指定位置读取,然后利用循环
while True:
    msg = f.read(2,)
    print(msg)
    if msg == "":
        break
    else:
        continue
# 继续读
# 这种方式一般用不到

8.3 字节流

字符流一般不会设计内存不够用的情况,即使500w字也不会有很大的内存,一个字符一个字节,1024个字=1k

help(open)

b ------------ binary mode

视频、图片、音频、可执行文件都是二进制数据,需要使用字节流

mode=“b” --------------表示字节流操作IO流

'''
f = open("xxx.jpg", "r")

f.read()  # - -------------会出错
'''

f = open("xxx.jpg", "rb")
#print(f.read())

ff = open("xxxbak.jpg", "wb")  #xxxbak.jpg自动创建

print(ff.write(f.read()))

f.close()

ff.close()

注意:字节流操作大数据的时候,不建议一次性读取

字节可以操作任何数据,字符只能操作字符数据

f = open("xxx.txt","rt")  #读取字符流

f.read()

f = open("xxx.txt","rb")  # 读取字节流

f.read()

字节数据备份

def copy_file(src, dest):
    f = open(src, "rb")
    f2 = open(dest, "wb")

    f2.write(f.read())


if __name__ == '__main__':
    copy_file("a.mp4", "C:\\南京python\\a.mp4")

def copy_file(src, dest):     #带参数
    f = open(src, "rb")
    f2 = open(dest, "wb")
    # 不推荐
    # f2.write(f.read())
    while True:
        data = f.read(1024 * 1024)  # 以M为单位读取
        # 字符流读到最后会返回空""
        if data == b"":
            print("数据读取 完成")
            break
        else:
            f2.write(data)

    f.close()
    f2.close()


if __name__ == '__main__':
    copy_file("b.mp4", "C:\\南京python\\b.mp4")

def copy_file():      #不带参数
    src = input("请输入要备份的数据的路径:")
    dest = input("请输入要保存的路径:")
    f = open(src, "rb")
    f2 = open(dest, "wb")
    # 不推荐
    # f2.write(f.read())
    while True:
        data = f.read(1024 * 1024)  # 以M为单位读取
        # 字符流读到最后会返回空""
        if data == b"":
            print("数据读取 完成")
            break
        else:
            f2.write(data)

    f.close()
    f2.close()


if __name__ == '__main__':
    copy_file()

运行过程:python xxx.py

​ 请输入要备份的数据的路径:c:\user\wx\desktop\xxx.mp4

请输入保存备份的路径:c:\user\wx\xxx.mp4

问题:python xxx.py

​ 请输入要备份的数据的路径:c:\user\wx\desktop\xxx.mp4

请输入保存备份的路径:c:\user\wx

from os import path


def copy_file():
    src = input("请输入要备份的数据的路径:")
    dest = input("请输入要保存的路径:")
    f = open(src, "rb")
    filename = src[src.rfind("\\") + 1:]
    print(filename)
    f2 = open(path.join(dest, filename), "wb")
    # 不推荐
    # f2.write(f.read())
    while True:
        # 以M为单位读取
        data = f.read(1024 * 1024)
        # 字符流读到最后会返回空 字节流b""
        if data == b"":
            print("数据读取 完成")
            break
        else:
            f2.write(data)
    f.close()
    f2.close()


if __name__ == '__main__':
    copy_file()

会出现视频的名称相同,会覆盖掉相同名称的视频,所以备份的名称最好不同

使用uuid

from os import path
import uuid


def copy_file():
    src = input("请输入要备份的数据的路径:")
    dest = input("请输入要保存的路径:")
    f = open(src, "rb")
    filename = src[src.rfind("\\") + 1:]
    random_uuid = uuid.uuid4().hex
    filename = random_uuid + filename
    print(filename)
    f2 = open(path.join(dest, filename), "wb")
    # 不推荐
    # f2.write(f.read())
    while True:
        # 以M为单位读取
        data = f.read(1024 * 1024)
        # 字符流读到最后会返回空 字节流b""
        if data == b"":
            print("数据读取 完成")
            break
        else:
            f2.write(data)
    f.close()
    f2.close()


if __name__ == '__main__':
    copy_file()

注意:也可以再加上时间戳(自己练习)

作业1:结合之前学习的遍历磁盘的代码+IO操作,完成磁盘的数据备份

8.4 对象序列化

8.4.1 什么是对象序列化

对象序列化,什么是对象序列化?

列表、字典、集合等是对象,对象都是抽象的,是我们想象的虚拟对象,需要将对象持久化,将它保存到磁盘等,所以需要序列化,

对象序列化:将内存中像对象这种抽象的概念转化为真正的字符或者字节的数据

8.4.2 pickle模块

可以将对象转换为字节数据

import pickle

dir(pickle)

dumps ---------------将对象序列化为字节数据

import pickle

ls = [1, 2, 3, 4, 5, 6, 7]
data = pickle.dumps(ls)
f = open("C:\\test\\ls.dat", "wb")
f.write(data)
f.close()

loads ------------- 将字节数据反序列化为对象

import pickle

f = open("C:\\test\\ls.dat", "rb")
show = f.read()
print(pickle.loads(show))

load ---------------- 将一个file对象反序列化为对象

import pickle

print(pickle.load(open("C:\\test\\ls.dat", "rb")))

8.4.3 json模块

可以将对象转换为字符对象 (不讲)

import json
d = {"username":"wangxiu","age":18}
json.dumps(d)

s = json.dumps(d)

f = open("c://a.txt","w")

f.write(s)

f.close()



f = open("c://a.txt","r")

ss = f.read()


s= json.loads(ss)

print(type(s))

print(type(ss))


注意:json这个模块一般用来序列化字典对象,或者转换json数据,并不是只能用来序列化字典,其他也可以,但是取得时候比较麻烦(python2 中只能操作字典)

import json
import pickle

ls = [1,2,3,4,5,6]

#pickle.dumps(ls)

s = json.dumps(ls)

f = open("c:\\aa.txt","w")

f.write(s)

f.close()

ss = json.loads(open("c:\\aa.txt").read())
print(ss)

作业2:使用对象序列化将用户登录、注册,完成对象序列化

第九章 面向对象

9.1 OOP(面向对象思想)概述

什么是编程思想:人们利用计算机来解决实际问题的一种思维方式,其中Python是同时支持面向对象和面向过程的编程语言

面向过程的编程思想

  • 传统的面向过程的编程思想总结起来就八个字——自顶向下,逐步细化
  • 将要实现的功能描述为一个从开始到结束按部就班的连续的“步骤”,依次逐步完成这些步骤,如果某一个步骤的难度较大,又可以将该步骤再次细化为若干个子步骤,以此类推,一直到结尾并得到我们想要的结果
  • 程序的主体是函数,是一个封装起来的模块,可以实现特定的功能,从而实现代码的重用与模块化编程
  • 例:报名学习,可以分成哪些步骤? 开始 → 学员提出报名,提供相关材料 → 学生缴纳学费,获得缴费凭证 → 教师凭缴费凭证进行分配班级 → 班级增加学生信息 → 结束,所谓面向过程,就是将上面分析好的步骤,依次执行即可

面向对象编程思想

  • 面向对象就是在编程的时候尽可能的去模拟现实世界,现实世界中,任何一个操作辑的实现都需要一个实体来完成,实体就是动作的支配者,没有实体,就没有动作的发生

  • 思考:上例报名过程中,有哪些动词:提出、提供、缴纳、获得、分配、增加,那么有动词就一定有实现这个动作的实体

  • 分析:

    • 第一步:分析哪些动作是由哪些实体发出的:

      • 学生:提出报名
      • 学生:提供相关资料
      • 学生:缴费
      • 机构:收费
      • 教师:分配教室
      • 班级:增加学生信息
      • 整个过程中,一共有四个实体:学生、机构、教师、班级
    • 第二步:定义这些实体,为其增加相应的属性和功能

      • 属性就是实体固有的某些特征特性信息,本质就是以前的变量,如:

        实体属性
        手机价格、品牌、操作系统、颜色、尺寸、配置等
        身高、体重、姓名、性别、年龄、学历、电话、籍贯、毕业院校等
      • 功能就是就是实体可以完成的动作,本质就是函数或方法

    • 报名的实体与属性:

    • 第三步:让实体去执行相应的功能或动作
      • 学生:提出报名
      • 学生:提供相关资料
      • 教师:登记学生信息
      • 学生:缴费
      • 机构:收费
      • 教师:分配教室
      • 班级:增加学生信息

面向过程向面向对象思想迁移

  • 以前写代码:首先考虑实现什么功能,然后调用函数,之后按部就班的执行
  • 以后写代码:首先考虑应该由什么样的主体去实现什么样的功能,再把该主体的属性和功能统一的进行封装,最后才去实现各个实体的功能
  • 注意:面向对象并不是一种技术,而是一种思想,是一种解决问题的思维方式
  • 面向对象的核心思想是:对调用该功能的主体进行封装,在使用的过程中,先得到对应的主体,再使用主体去实现相关的功能

面试题:面向过程和面向对象的区别?

  • 都可以实现代码重用和模块化编程,面向对象的模块化更深,数据也更封闭和安全
  • 面向对象的思维方式更加贴近现实生活,更容易解决大型的复杂的业务逻辑
  • 从前期开发的角度来看,面向对象比面向过程要更复杂,但是从维护和扩展的角度来看,面向对象要远比面向过程简单!
  • 面向过程的代码执行效率较高

9.2 面向对象的专业术语

oo组成:

  • OOA:面向对象分析
  • OOD:面向对象设计
  • OOP:面向对象编程
image-20230201134258732

9.3 类与对象

对象(object)

  • 概念:是一个抽象概念,对象是事物存在的实体,如:一个人
  • 每一个现实业务逻辑的一个动作实体就对应着OOP编程中的一个对象
  • 对象分为2部分
    • 静态部分:属性,客观存在,不可忽视,如:人的性别
    • 动态部分:行为,对象执行的动作,如:人跑步
  • 对象使用属性(property)保存数据,对象使用方法(method)管理数据
image-20230201134323142

Python中,采用类(class)来生产对象,用类来规定对象的属性和方法即要得到对象,必须先有类

为什么要引入类的概念?

  • 类本来就是对现实世界的一种模拟,在现实生活中,任何一个实体都有一个类别
  • 类就是具有相同或相似属性和动作的一组实体的集合
  • 所以,在Python中,对象是指现实中的一个具体的实体,由于实体都有一个类别,所以OOP中的对象也都有一个类

一个对象具有的特征信息,是由其所属的类来决定的,但每个对象又可以具有不同的特征特性信息

** 如,我自己(人类)这个对象,名字叫梅兰芳,性别男,会写代码,会教书;另一个对象(人类)可能叫赵薇,性别女,会演戏,会唱歌**

image-20230201134355163 image-20230201134419055
9.3.1 类的定义

类名的命名规范:遵循大驼峰法

  • 旧式类:不由任意内置类型派生出的类,称之为经典类
class 类名: 
    '''类的帮助信息'''
    代码
  • 新式类
class  类名():
    '''类的帮助信息'''
	代码
  • 上述是一个类,只不过里面什么都没有
  • 类名不区分大小写,遵守一般的标识符的命名规则(以字母、数字和下划线构成,并且不能以数字开头)
  • 类为了和方法名相区分,应使用驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线
  • 基本架构示例:
class Person():
    # 属性
    # 方法(函数)
    def eat(self):
        print('我喜欢吃零食')
    def drink(self):
        print('我喜欢喝可乐')
class Geese:
	'''大雁类'''
	pass
  • 类的本质也是一个特殊的对象
    • python中一切皆为对象,类是一个特殊的对象即类对象
    • 程序运行时,类也会被加载到内存
    • 类对象在内存中只有一份,可以创建出多个对象实例

类的实例化

  • 定义完类之后并不会创建一个实例,比如类是一个汽车设计图,告诉你怎么制造,但设计图本身不是一辆汽车,你不能开走它,它只能用来制造真正的汽车,而且可以制造很多辆汽车

  • 类的实例化就是把抽象的事务具体为现实世界中的实体,即通过类得到对象

  • 类只是对象的一种规范,类本身基本上什么都做不了,必须利用类得到对象,这个过程就叫作类的实例化

  • 格式:

对象名 = 类名()
  • 在其它编程语言中,类的实例化一般是通过new关键字实例化生成的,但是在Python中,不需要new关键字,只需要类名+( )括号就代表类的实例

  • 例1:把Person类实例化为为对象p1

# 1、定义一个类
class Person():
    # 定义相关方法
    def eat(self):
        print('我喜欢吃零食')

    def drink(self):
        print('我喜欢喝可乐')


# 2、实例化对象
p1 = Person()
# 3、调用类中的方法
p1.eat()
p1.drink()
  • 例2:将Geese类实例化
class Geese:
    '''大雁类'''
    pass

wildGoose = Geese()
print(wildGoose)
  • 每一个对象 都有自己 独立的内存空间,保存各自不同的属性,多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部

类中的self关键字

  • self是Python内置的关键字之一,其指向了类实例对象本身,用于访问类中的属性和方法,在方法调用时会自动传递实际参数self
# 1、定义一个类
class Person():
    # 定义一个方法
    def speak(self):
        print(self)   #打印地址1
        print('Nice to meet you!')

# 2、类的实例化(生成对象)
p1 = Person()
print(p1)    # 这里打印的地址和地址1一样
p1.speak()

p2 = Person()
print(p2)
p2.speak()

总结:类中的self就是谁实例化了对象,self就指向谁

9.3.2 类的属性

创建类的属性:

  • 概念:定义在类中并且在函数体外的属性变量,类属性可以在类的所有实例之间共享值,是公用属性,一般记录这个类的相关特征

  • 类属性可以通过类名或实例名访问

image-20230201134440613
class Geese:
    '''大雁类'''
    neck = '脖子较长'
    wing = '翅膀振动频率高'
    leg = '腿位于身体中心支点'


geese=Geese()   
print(Geese.neck)   # 通过类名访问
print(Geese.wing)
print(geese.leg)   # 通过对象名访问
  • 类属性变更,所有对象的属性都变化,相当于模板变化了
class Geese:
    '''大雁类'''
    neck = '脖子老长了'    # 类属性变化
    wing = '翅膀振动频率高'
    leg = '腿位于身体中心支点'


geese1 = Geese()
geese2 = Geese()

print(geese1.neck)
print(geese2.neck)
  • 应用场景:可以进行计数,控制或包含各个对象
  • 注意:不能使用 对象.类属性=值 的赋值语句,这只会添加一个属性,而不会影响类属性
9.3.3 类的方法

类中得变量和方法都叫做类成员

创建_ _ init _ _()方法 (初始化方法)

  • 在Python中,__xxx__()的函数叫做魔术方法,指的是具有特殊功能的函数
  • 思考:人的姓名、年龄等信息都是与生俱来的属性,可不可以在生产过程中就赋予这些属性呢?
  • 类创建后,可以手动创建一个_ _ init _ _()方法,该方法是一个特殊方法,类似java的构造方法,在创建一个对象时默认被调用,不需要手动调用
  • _ _ init _ _()方法必须包含一个self参数,且必须是第一个参数,其指向实例本身,没有写self或位置不对,则会报错
  • _ _ init _ _()方法开头结尾的双下划线(中间没有空格),是一种特殊约定,旨在区分默认方法和普通方法
class Geese:
    '''大雁类'''
    def __init__(self):
        print('我是大雁类')

wildGoose = Geese()
  • _ _ init _ _()方法中可以自定义一些参数,参数间使用逗号隔开,如:
class Geese:
    '''大雁类'''

    def __init__(self, beak, wing, claw):
        print('我是大雁类')
        print(beak)
        print(wing)
        print(claw)

beak1 = '喙的长度与头部长度几乎相等'  # 喙(huì)
wing1 = '翅膀长而尖'
claw1 = '爪子为蹼状'
wildGoose = Geese(beak1, wing1, claw1)
# 1、定义一个类
class Person():
    # 初始化实例对象属性
    def __init__(self, name, age):
        # 赋予name属性、age属性给实例化对象本身
        # self.实例化对象属性 = 参数
        self.name = name
        self.age = age

# 2、实例化对象并传入初始化属性值
p1 = Person('孙悟空', 500)
# 3、调用p1对象自身属性name与age
print(p1.name)
print(p1.age)

创建类的实例方法

  • 类的成员主要由实例方法和数据成员组成

  • 实例方法本质是类中定义的函数

  • 格式:

    def  方法名(self , 其它参数):
    	方法体
    
    • 方法名:一般使用小写字母开头
    • self:必要参数,表示类的实例
    • 其它参数:多个参数使用逗号隔开
    • 方法体:实现具体的功能
  • 实例方法与函数的区别:python函数实现的是某个独立的功能,实例方法是实现类中的一个行为,是类的一部分

  • 使用格式:实例化对象.方法名(实际参数)

  • 例:创建大雁飞行的方法

class Geese:
    '''大雁类'''
    def __init__(self, beak, wing, claw):
        print('我是大雁类,我有以下特征:')
        print(beak)
        print(wing)
        print(claw)

    def fly(self, state):
        print(state)

beak1 = '喙的长度与头部长度几乎相等'  # 喙(huì)
wing1 = '翅膀长而尖'
claw1 = '爪子为蹼状'
wildGoose = Geese(beak1, wing1, claw1)
wildGoose.fly('飞行的时候,队形为人字形或一字型')

注意:创建实例方法时,也可以设置默认值,但默认值参数必须为最后一个参数如:def fly(self, state=’我会飞行‘):

创建类的静态方法

  • 作用:在开发时,如果需要在类中封装一个方法,这个方法
    • 既 不需要访问实例属性或者调用实例方法
    • 也 不需要访问类属性或者调用类方法
    • 一般打印提示、帮助等信息
  • 格式:@staticmethod,其用于修饰类中的方法,使其可以再不创建类实例的情况下进行调用,优点是执行效率高,该方法一般被成为静态方法。静态方法不可以引用类中的属性或方法,其参数列表也不需要约定的默认参数self
  • 例:
# 开发一款游戏
class Game(object):
    # 开始游戏,打印游戏功能菜单
    @staticmethod     # 装饰器声明静态
    def menu():
        print('1、开始游戏')
        print('2、游戏暂停')
        print('3、退出游戏')


# 开始游戏、打印菜单
Game.menu()   # 不需要生成实例对象,直接类名访问
9.3.4 对象的创建和调用

对象的属性

属性概念:

  • Python中,任何一个对象都应该由两部分组成:属性 + 方法
  • 属性即是特征,比如:人的姓名、年龄、身高、体重…都是对象的属性
  • 对象属性既可以在类外面创建和获取,也能在类里面创建和获取

创建实例属性(默认的对象属性)

  • 概念:定义在类方法中的属性,只作用于当前对象
class Geese:
    '''大雁类'''

    def __init__(self):
        self.neck = '脖子贼长'   # 对象属性


geese1 = Geese()
geese2 = Geese()
geese1.neck = '脖子再长有我的长'
print(geese1.neck)
print(geese2.neck)  # 值不变

在类的外面添加属性和获取属性

  • 格式:对象名.属性 = 属性值
# 1、定义一个Person类
class Person():
    pass


# 2、实例化Person类,生成p1对象
p1 = Person()
# 3、为p1对象添加属性
p1.name = '老王'  # 外部添加属性
p1.age = 18
p1.address = '西安市雁塔区电子城街道融鑫路3号'

p2 = Person()  # 注意:对象属性私有
print(p2.name) # 报错

在类的内部获取外部定义的属性

# 1、定义一个Person类
class Person():
    def speak(self):
        print(f'我的名字:{self.name},我的年龄:{self.age},我的住址:{self.address}')


# 2、实例化Person类,生成p1对象
p1 = Person()
# 3、添加属性
p1.name = '孙悟空'
p1.age = 500
p1.address = '花果山水帘洞'
# 4、调用speak方法
p1.speak()

魔术方法

在Python中,_ _xxx_ _()的函数叫做魔法方法,指的是具有特殊功能的函数,如之前讲过的_ _ init _ _()

_ _ str _ _()方法

  • 当使用print输出对象的时候,默认打印对象的内存地址
  • 如果类定义了_ _ str _ _()方法,那么就会打印在这个方法中 return 的数据
  • 例:
# 1、定义一个类
class Car():
    # 首先定义一个__init__方法,用于初始化实例对象属性
    def __init__(self, brand, model, color):
        self.brand = brand
        self.model = model
        self.color = color

    # 定义一个__str__内置魔术方法,用于输出小汽车的相关信息
    def __str__(self):
        return f'汽车品牌:{self.brand},汽车型号:{self.model},汽车颜色:{self.color}'


# 2、实例化对象c1
c1 = Car('奔驰', 'S600', '黑色')
print(c1)
  • _ _ str _ _()魔术方法是在类的外部,使用print(对象)时,自动被调用的

  • 在类的内部定义_ _ str _ _()方法时,必须使用return返回一个字符串类型的数据

_ _ del _ _()方法

  • 又称为删除方法或析构方法
  • 当删除对象时,python解释器也会默认调用_ _ del _ _()方法。
  • 例:
class Person():
    # 构造函数__init__
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 析构方法__del__
    def __del__(self):
        print(f'{self}对象已经被删除')


# 实例化对象
p1 = Person('白骨精', 100)
# 删除对象p1
del p1
  • _ _ del _ _()方法主要用于关闭文件操作、关闭数据库连接等

9.4 面向对象的案例讲解

"""
定义一个用户类,类名称是user
class User: 旧时类
class User(object): 新式类
两种定义类的方式是一样的  都是继承object类
"""
#class User:


class User(object):
    #重写__int__初始化方法,该方法用来初始化属,在构建方法的时候,这个方法会自动触发,用来初始化属性
    def __init__(self):
        #name = “”  这是一个局部变量
        #self类似指针 this
        #self.name类的属性
        print("构建对象的时候触发了")
        self.name = "张三"
        self.age = 18
        self.gender = "男"
        self.tel = "13124564456"

    def study(self):
        print("我爱学习")

if __name__ == "__main__":
    #创建类 ,创建了一个user的对象
    print("马上要触发了")
    u = User()
    print("触发结束了")
    print(u.name)
    print(u.age)
    #直接修改值
    u.age += 1
    print(u.age)
    #调用方法
    u.study()
class User():
    #重写__int__初始化方法,该方法用来初始化属
    #因为在构建对象的时候,才能确定对象的属性值,我们就可以将这些值进行传递
    def __init__(self,name,age,gender,tel):
        #name = “”  这是一个局部变量
        #self类似指针 this
        #self.name类的属性,表示用户名称,name式将来构建对象的值
        self.name = name
        self.age = age
        self.gender = gender
        self.tel = tel

    def study(self):
        print("我爱学习")
        print("我是"+ self.name + ",我爱学习")
    def __str__(self):
        #tostring方法一致
        return "[name = " + self.name + ",age = " + str(self.age) + ",gender = " + self.gender + ",tel = " + self.tel + "]"
if __name__ == "__main__":
    #创建类 ,创建了一个user的对象
    # u = User()
    # print(u.name)
    # print(u.age)  #这样会报错
    #传递属性的值,用来初始化属性
    u = User("张三",17,"男","120")
    #打印<__main__.User object at 0x000001920985AFD0> 所以正常打印的对象输出的是内存的地址
    # 在列表、字典这些对象打印的时候时候直接输出的就是对象的内容,因为这些对象重写了__str__(sef)
    print(u)

    u.study()

定义学员信息类,包含姓名、成绩属性,定义成绩打印方法(90分及以上显示优秀,80分及以上显示良好,70分及以上显示中等,60分及以上显示合格,60分以下显示不及格)

image-20230201134510065
# 1、定义学员信息类
class Student():
    # 2、定义学员对象属性
    def __init__(self, name, score):
        self.name = name  # 所有对象的名字来自哪里
        self.score = score

    # 3、定义一个方法,用于打印学员的成绩等级
    def print_grade(self):
        if self.score >= 90:  # 所有对象的成绩判断
            print(f'学员姓名:{self.name},学员成绩:{self.score},优秀')
        elif self.score >= 80:
            print(f'学员姓名:{self.name},学员成绩:{self.score},良好')
        elif self.score >= 70:
            print(f'学员姓名:{self.name},学员成绩:{self.score},中等')
        elif self.score >= 60:
            print(f'学员姓名:{self.name},学员成绩:{self.score},及格')
        else:
            print(f'学员姓名:{self.name},学员成绩:{self.score},不及格')


# 4、实例化对象
name1 = input('请输入姓名:')
score1 = int(input('请输入成绩:'))
tom = Student(name1, score1)
tom.print_grade()

小明体重75.0公斤,小明每次跑步会减掉0.50公斤,小明每次吃东西体重增加1公斤

  • 对象:小明

  • 属性:姓名、体重

  • 方法:跑步、吃东西

    image-20230201134533822
# 1、定义Person类
class Person():
    # 2、初始化对象属性,name和weight
    def __init__(self, name, weight):
        self.name = name
        self.weight = weight

    # 3、定义一个__str__方法打印对象的信息
    def __str__(self):  # str方法返回return信息
        return f'姓名:{self.name},目前体重:{self.weight}KG'

    # 4、定义一个run方法代表跑步
    def run(self):
        self.weight -= 0.5

    # 5、定义一个eat方法代表吃饭
    def eat(self):
        self.weight += 1


# 6、实例化对象
xiaoming = Person('小明', 75.0)
print(xiaoming)

# 7、吃饭
xiaoming.eat()
print(xiaoming)

# 8、减肥跑步
xiaoming.run()
print(xiaoming)

编写Prime类实现输出大于m且紧随m的k个素数

class Prime:
    def __init__(self, start_value, number):
        self.start_value = start_value
        self.number = number

    def prime_number(self):
        count = 0
        i = self.start_value + 1
        while count < self.number:
            for j in range(2, i):
                if i % j == 0:
                    break
            else:
                count = count + 1
                print(i, end=" ")
            i = i + 1


m = int(input("请输入起始值m:"))
k = int(input("请输入需要的素数个数k:"))
prime1 = Prime(m, k)
prime1.prime_number()

作业:

1.定义一个圆类(Circle),求圆的面积和周长

2.定义一个三角形类(Strange),属性是三边的长度,求三角形的面积和周长

3.创建一个学员类,并设计三个字段用于表示学生的成绩(语文、数学、英语):然后定义一个列表表示一个班的学生(10人),依次输入每个学生的信息和成绩,输入的同时将学员的每科成绩划分等级(100-90:A 89-80:B 79-70:C 69-60:D 60以下:E)最后输出所有学员的信息

4.编一个关于求某门功课总分和平均分的程序

  • 每个学生的信息包括姓名和某门功课的成绩

  • 假设5个学生

  • 类和对象的处理要合理

5.设计一个游戏角色类

字段:角色名、血量、魔法、状态

方法:释放技能 被伤害

要求:设计合理

9.5 面向对象三大特征—封装

9.5.1 基本封装方式

广义上的封装指的是将细节封起来,提供一个公开的,好用的方式让大家使用的过程

面向对象的封装特指:将属性和方法,封装到一个类或多个类,需要使用的时候,通过对象调用对应的属性或者方法

class User(object):
    def __init__(self,username,age,gender,email):
        self.username = username
        self.age = age
        self.gender = gender
        self.email = email


u1 = User("张三",18,"男","123@qq.com")
print(u1.username)
print(u1.email)
#对象可以直接操作属性  __属性实则不能在外界被访问
u1.username = "李四"
print(u1.username)

真正意义的封装特性:将类的属性封装(隐藏)起来,提供公开的方法(getter和setter方法)来访问行为

将属性私有化(只有类内部可以访问,外界无法访问),提供公开的方法(getter和setter方法)来访问

python中要私有化属性 :在属性前加上两个下划线 __username

class User(object):
    def __init__(self, username, age, gender, email):
        """
        私有化属性
        :param username:
        :param age:
        :param gender:
        :param email:
        """
        self.__username = username
        self.__age = age
        self.__gender = gender
        self.__email = email


u1 = User("张三", 18, "男", "123@qq.com")
u1.username = "李四"  # 对象可以直接操作属性
print(u1.username)
print(u1.__username)  # __属性实则不能在外界被访问
print(u1.__email)  # __属性实则不能在外界被访问

image-20221111151254402

外界不能访问,内部可以访问

class User(object):
    def __init__(self,username,age,gender,email):
        """
        私有化属性
        :param username:
        :param age:
        :param gender:
        :param email:
        """
        self.__username = username
        self.__age = age
        self.__gender = gender
        self.__email = email

    def __str__(self):
        return "name = " + self.__username      #__属性内部可以访问


u1 = User("张三",18,"男","123@qq.com")
print(u1)
print(u1.__username)  # __属性实则不能在外界被访问
print(u1.__email)
#对象可以直接操作属性  
u1.username = "李四"
print(u1.username)

如果外界想要访问:

class User(object):
    def __init__(self,username,age,gender,email):
        """
        私有化属性
        :param username:
        :param age:
        :param gender:
        :param email:
        """
        self.__username = username
        self.__age = age
        self.__gender = gender
        self.__email = email

    def __str__(self):
        return "name = " + self.__username

    #提供公开的get方法访问
    def get_name(self):
        return self.__username

    #提供公共的set方法修改
    def set_name(self , name):
        self.__username = name
u1 = User("张三",18,"男","123@qq.com")
print(u1)
#调用私有化属性的get方法来访问属性
print(u1.get_name())
u1.set_name("李四")
print(u1)

以后,所有的python定义的类,都要讲属性私有化,提供公开的方法

9.5.2 property函数封装

封装的其他写法:

property()的使用:

class User(object):
    def __init__(self,username,age,gender,email):
        """
        私有化属性
        :param username:
        :param age:
        :param gender:
        :param email:
        """
        self.__username = username
        self.__age = age
        self.__gender = gender
        self.__email = email

    def __str__(self):
        return "name = " + self.__username

    #提供公开的get方法访问
    def get_username(self):
        return self.__username

    #提供公共的set方法修改
    def set_username(self, username):
        self.__username = username

    def get_age(self):
        return self.__age

    def set_age(self, age):
        self.__age = age

    def get_gender(self):
        return self.__gender

    def set_gender(self, gender):
        self.__gender = gender

    def get_email(self):
        return self.__email

    def set_email(self, email):
        self.__email = email

    #获取值相当于调用了property中的第一个参数,设置值相当于调用第二个此参数 进一步的伪装代码
    username = property(get_username, set_username)


if __name__ == '__main__':
    u1 = User("张三",18,"男","123@qq.com")
    # print(u1.get_username())
    # u1.set_username("李四")
    # print(u1.get_username())
    print(u1.username)
    u1.username = "哈哈哈"
    print(u1)

可以使用断点调试:image-20221111151445252

image-20221111151522534

image-20221111151539299image-20221111151656484

image-20221111151539299image-20221111151656484

image-20221111151722273

调用了set

image-20221111151634598
9.5.3 @property装饰器封装

装饰器:使用装饰器@property完成get方法的装饰(注意:函数名称建议就是私有前的名称在set方法上@xxx.setter

class User(object):
    def __init__(self,username,age,gender,email):
        """
        私有化属性
        :param username:
        :param age:
        :param gender:
        :param email:
        """
        self.__username = username
        self.__age = age
        self.__gender = gender
        self.__email = email

    def __str__(self):
        return "name = " + self.__username

    @property
    def username(self):
        return self.__username

    @username.setter
    def username(self, username):
        self.__username = username

    def get_age(self):
        return self.__age

    def set_age(self, age):
        self.__age = age

    def get_gender(self):
        return self.__gender

    def set_gender(self, gender):
        self.__gender = gender

    def get_email(self):
        return self.__email

    def set_email(self, email):
        self.__email = email


if __name__ == '__main__':
    u1 = User("张三",18,"男","123@qq.com")
    # print(u1.get_username())
    # u1.set_username("李四")
    # print(u1.get_username())
    print(u1.username)
    u1.username = "哈哈哈"
    print(u1)

9.6 面向对象三大特征—继承

9.6.1 继承的基本概念
  • 类是用来描述现实世界中同一组事务的共有特性的抽象模型,但是类也有上下级和范围之分,比如:生物 => 动物 => 哺乳动物 => 灵长型动物 => 人类 => 黄种人
  • 从哲学上说,就是共性与个性之间的关系,比如:白马和马!所以,我们在OOP代码中,也一样要体现出类与类之间的共性与个性关系,这里就需要通过类的继承来体现。简单来说,如果一个类A使用了另一个类B的成员(属性和方法),我们就可以说A类继承了B类,同时这也体现了OOP中代码重用的特性

与继承相关的几个概念

  • 继承:一个类从另一个已有的类获得其成员的相关特性,就叫作继承
  • 派生:从一个已有的类产生一个新的类,称为派生,继承和派生其实就是从不同的方向来描述的相同的概念而已,本质上是一样的
  • 父类:也叫作基类,就是指已有被继承的类
  • 子类:也叫作派生类或扩展类
  • 扩展:在子类中增加一些自己特有的特性,就叫作扩展,没有扩展,继承也就没有意义了
  • 单继承:一个类只能继承自一个其他的类,不能继承多个类,单继承也是大多数面向对象语言的特性
  • 多继承:一个类同时继承了多个父类, (C++、Python等语言都支持多继承)
9.6.2 python实现继承

类与类之间也存在一种继承的关系

class Son(RichMan):
#表示继承,RichMan是son类的父类(超类)
#son类是RichMan的子类
class RichMan(object):

    def __init__(self):
        self.money = 10000
        self.company = "阿里巴巴"

    def tell_motto(self):
        print("我对钱没有兴趣")


class Son(RichMan):
    """
    继承了RichMan
    """
    pass


if __name__ == '__main__':
    s = Son()
    print(s.money)
    print(s.company)
    

子类能继承所有的父类里的属性和方法吗?不能

注意:继承父类不是所有的方法和属性都能被继承,只能继承公开的方法和属性,私有属性和方法都不能被继承

class RichMan(object):

    def __init__(self):
        self.money = 10000
        self.company = "阿里巴巴"
        self.__house = "北京"

    def tell_motto(self):
        print("我对钱没有兴趣")

    def __speak_english(self):
        print("讲英文贼溜")


class Son(RichMan):
    """
    继承了RichMan
    """
    pass


if __name__ == '__main__':
    s = Son()
    print(s.money)
    print(s.company)
    #print(s.__house)
    s.tell_motto()
    s.__speak_english__()
9.6.3 方法重写和覆盖

继承也可以重写方法

方法重写(覆盖)(override):在继承的基础上,如果子类方法父类的方法不能满足自己使用的时候,那么就重写这个方法

注意:重写时方法的名称和参时必须一样,但并不是将父类的方法覆盖了,在编程语言中都有一个“就近原则“,先找自己的方法,找到了就不会再找父类的方法

class RichMan(object):

    def __init__(self):
        self.money = 10000
        self.company = "阿里巴巴"
        self.__house = "北京"

    def tell_motto(self):
        print("我对钱没有兴趣")

    def __speak_english(self):
        print("讲英文贼溜")

    def working(self):
        print("A是一个好员工")

class Son(RichMan):
    """
    继承了RichMan
    """
    #方法重写
    def working(self):
        print("还是B工作比较认真")


if __name__ == '__main__':
    s = Son()
    print(s.money)
    print(s.company)
    #print(s.__house)
    s.tell_motto()
    #s.__speak_english__()
    s.working()

面试题:python中有函数重载吗?有方法重写吗?

结论:python中没有函数重载

重载(overload):函数名称相同,而参数的个数或者类型不同,这样的一些函数构成了函数重载(Java中很重要的概念)

但是可以实现,通过装饰器实现函数重载

总结:继承的本质就是为了实现代码的复用

Python多继承:

python支持多继承class Son(RichMan,object,xxx,xxxx):

那么如果A类继承B和C类,B和C分别有一个h(),那么A继承的是哪个父类的该方法

python3中继承按照继承顺序来找(广度优先)

9.6.4 super关键字
  • super关键字:是一个指针,默认指向父类
class RichMan(object):

    def __init__(self):
        self.money = 10000
        self.company = "阿里巴巴"
        self.__house = "北京"

    def tell_motto(self):
        print("我对钱没有兴趣")

    def __speak_english(self):
        print("讲英文贼溜")

    def working(self):
        print("A是一个好员工")


class Son(RichMan,object):
    """
    继承了RichMan
    """
    #方法重写
    def working(self):
        print("还是B工作比较认真")

    def test(self):
        self.working()
        #super指向父类,有参数需要传递参数
        super().working()

if __name__ == '__main__':
    s = Son()
    # print(s.money)
    # print(s.company)
    #print(s.__house)
    # s.tell_motto()
    #s.__speak_english__()
    # s.working()
    s.test()
class User(object):

    def __init__(self):
        print("这是一个父类")

class Admin(User):

    def __init__(self):
        #想要调取父类
        #super().__init__()
        print("这是一个子类")

a1 = Admin()   #会打印出什么?
9.6.5 重载和重写的区别

方法重写(覆盖)(override):在继承的基础上,如果子类方法父类的方法不能满足自己使用的时候,那么就重写这个方法

注意:重写时方法的名称和参时必须一样,但并不是将父类的方法覆盖了,在编程语言中都有一个“就近原则“,先找自己的方法,找到了就不会再找父类的方法

重载(overload):函数名称相同,而参数的个数或者类型不同,这样的一些函数构成了函数重载(Java中很重要的概念)

但是可以实现,通过装饰器实现函数重载

9.7 多态

多态(python中不重要):指的就是对象的多种状态,多态是在继承的基础上而言的,父类引用指向子类实例的现象,把这种现象叫做多态

ArrayList a1 = new ArrayList();

List ls = new ArrayList();  //面向对象的语言中,运行父类引用指向子类对象实例

注意:在弱数据类型语言中,因为不用声明类型,所以天生支持多态

第十章 异常处理

10.1 异常概述

异常:相对于正常而言,就是不正常的情况,程序开发过程中错误和BUG都市补充正常的情况(错误并不是异常,异常并不等价于错误,异常值得是软件在运行的过程中,因为一些原因(如:使用者操作不当)引起的程序错误,导致软件奔溃的这种现象,叫做异常)

异常发生的后果:引发程序崩溃

10.2 为什么需要异常

因为错误发生的时候,原订的执行流程就无法继续,但对于用户来讲,他们不能因为这样的错误就终止程序的使用,所以提供给程序设计者异常机制,让设计者能知道为什么出现错误,出现了应该做些什么。

10.3 python异常处理方案

处理异常(容错):包容出现的不正常的错误,保证程序的正常执行

处理异常的方式:

try-except语句块:异常捕获处理

image-20221111152111210

代码出现异常,并不影响后面代码的执行的话,应该让代码继续向下执行

可以在可能出现异常的代码前加上try语句块

try:

​ #有可能触发异常的代码

except:

​ #处理异常

try:
    num = int(input("请输入一个数字:"))
    print("哈哈哈哈哈")
    result = num + 10
except:
    print("发现了异常")

print("{}+10={}".format(num, result))
image-20221111152149113 image-20221111152219660

问题:看不出来是什么错误

except Exception as e:
image-20221111152249584

建议使用:

except ValueError as e:

常见的异常:

import builtins

dir(builtins)

异常的继承关系:

BaseException(所有异常的父类)

Exception(BaseException的子类,常见异常的父类)

assertions:断言测试(感兴趣自己学习,不讲)

10.4 抓捕异常语法解析

捕获指定异常

  • 原因:有些情况下,我们需要针对性的捕获异常,并执行相应的代码

  • 格式:

try:
    可能遇到异常的代码
except (需要捕获的异常名  [as 新名称]):
    捕获到对应的错误以后,执行的代码
  • 需要捕获的异常名 [as 新名称]:表示指明需要捕获的异常,由于名字长可以使用短名称利用as命名别名,也可以省略

  • 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常

  • 一般try下方只放一行尝试执行的代码

  • 例:

try:
    f = open('test123.txt', 'r')
except FileNotFoundError as e:
    print(e)

同时捕获多个异常

  • 格式:
try:
    print(name)
    #print(10/0)
except (NameError, ZeroDivisionError) as e:
    print(e)
  • 多个异常名使用逗号隔开

同时捕获多个异常

  • 无论我们在except后面定义多少个异常类型,实际应用中,也可能会出现无法捕获的未知异常。这个时候,我们考虑使用Exception异常类型捕获可能遇到的所有未知异常
  • 例1:
try:
    可能遇到的错误代码
except Exception as e:
    print(e)
  • 例2:打印一个未定义变量,使用Exception异常类进行捕获
try:
    print(name)
except Exception as e:
    print(e)
# 可以间接查看异常解释

异常捕获中else语句

作用:else语句表示如果没有异常则要执行的代码

格式:

try:
	可能遇到异常的代码
except
	应对代码
else:
	没有发现异常时执行的语句

例:

try:
    print(1)
except Exception as e:
    print(e)
else:
    print('哈哈,真开森,没有遇到任何异常')

10.5 finally关键字

finally关键字:表示必须要执行的代码(释放资源、释放连接、释放内存(IO)等)

try:
    int(input("请输入一个数:"))
except Exception as e:
    print("出现了异常")
else:
    print("没有出现异常")
finally:
    print("不管出不出现异常,我都要执行")


print("剩余的代码")

问题:

def demo(msg):
    try:
        int(input(msg))
        print("hello worls")
        return "A"
    except Exception as e:
        print("处理异常")
        return "B"
    finally:
        print("释放资源")
        return "C"
    return "D"


res =demo(input(">>>>>:"))
print(res)

如果现在控制台输入123,会打印出什么?

image-20221111152340927

函数中,如果在return后面存在finally,那么代码并不会直接返回,而是需要执行finally,再执行返回,所以finally会在return之前执行

10.6 自定义异常类

之前学习的异常都是系统提供的异常,有时候需要自定义异常,首先需要定义一个类,之后让这个类继承Exception\BaseException,当然建议使用Exception

class MyException(Exception):
    def __init__(self,msg):
        Exception.__init__(self,msg)

举例:

class MyException(Exception):
    def __init__(self,msg):
        Exception.__init__(self,msg)

def login(username,password):
    if username == None or username.strip() == " ":
        # 抛出异常---使用raise关键字(自定义异常和系统自带异常都可以抛出)
        raise MyException("对不起,用户名不能为空")
    if password == None or password.strip() == " ":
        # 抛出异常---使用raise关键字(自定义异常和系统自带异常都可以抛出)
        raise MyException("对不起,密码不能为空")
if __name__ == '__main__':
    #login(None,None)
    #抓捕异常
    try:
        login(None, None)
    except Exception as e:
        print("抓捕异常,信息是----》",e)

注意:人为抛出异常:作用----提示错误,向上层传递错误信息

第三部分 python的高级编程

第十一章 python的高级编程

11.1 包和模块

什么是包(package)?

xxx.py是模块,包在python中用来包裹模块的文件夹,在python中文件夹可以当作包使用,但是包并不是文件夹,在python中一个文件夹中存在image-20221111152620934这个模块文件的话,这个文件夹就是包

image-20221111152641576一般用来初始化文件

什么是模块(module)?

xxx.py是模块,保存要执行或者使用的代码

常见的导包方式:

import package.module

import package.module as 别名

from package import module

from package1,package2… import module

from package import* 通配所有(有时候使用一些第三方模块的时候,使用可能会导入失败,只有在属性声明过的模块,才支持这个通配导入方式)

创建包的方式:

image-20221111152726198

11.2. is和==

is和==都是用来判断变量的

is判断的是两个变量的内存地址

==判断的是两个变量的值

a = 10

b = 20

a == b

False

a is b

False

a = 1024

b = 1024

a == b

True

a is b

False

id(a)

id(b)

练习:

a  = 10

b = 10

a == b  ------?    True

a  is  b  ------?    True

python 内存中有一个数据区,数据区存的是常量,固定不变的值,缓存数据等,python中有一个小整型缓存区(-5 ----256 ),当在这个范围内就不开辟新空间存储数字了

除了小整型缓存区还有字符串缓存区,但是字符串不能包含特殊的符号

s = "hello"

ss = "hello"

s is ss

True

s = "Hello world"

ss = "Hello world"

s == ss

True

s is ss

Flase

11.3 深拷贝和浅拷贝

引用赋值(基本数据类型)

主要操作栈内存的复制

a = 10

b = a

a

10

b

10

浅拷贝

拷贝对象,而对象是存储在堆中的,拷贝堆内存,需要得到第二个一样的对象,通过复制拷贝对象效率最高

ls = [1,2,3,4,5,6]

ls

ls1 = ls   #赋值

ls.append("7")

ls

ls1   #ls和ls1都改变了

ls2 = ls.copy(ls)  #浅拷贝

python为了提供对象拷贝,专门提供了一个copy模块

import copy

dir copy

copy.copy() ---------- 浅拷贝,相当于列表中的copy()

ls = [1,2,3,[1,2,3],100]

ls2 = ls.copy()

id(ls)

id(ls2)

ls.append(30)

ls

ls2

#目前没有问题  浅拷贝

ls[3]

ls[3].append(40)

ls

ls2

#ls2也发生了改变    浅拷贝并不是把对象完整的拷贝,而是仅仅拷贝了对象的第一层

深拷贝------使用的是递归的方式完成的拷贝,这样两个对象之间将没有任何关系

copy.deepcopy() ----------深拷贝

ls

ls1

ls2

ls1 = copy.deepcopy(ls)

ls[3].insert(0,"哈哈哈")

ls

ls2

ls1

#ls1没有哈哈哈

总结:

需要将当前对象拷贝的时候,一般建议拷贝为浅拷贝(只拷贝一层,效率高,内存占有少),如果需要完全分离,使用深拷贝

特殊情况:不可变类型(元组、字符串、数值)浅拷贝和深拷贝后永远只有一份

t = (1,2,3,4)

tt = copy.copy(t)

ttt = copy.deepcopy(t)

t is tt 

True

t is ttt

True

#元组是一个不可变类型,不可能备份,除了元组,字符串也是不可变类型

元组里面的元素可以是列表(可变),会是什么样的情况?(自学)

11.4 生成器

11.4.1 列表推导式

列表推导式可以快速得到一个需要的列表

想生成一个0~100的列表:[x for x in range(101)]

ls = [x for x in range(101)]

[i for i in range(101) if i % 2 ==0] -----------打印0~100内所有偶数的列表

[i*j for i in range(10) for j in range(10)]

问题:假设该列表的元素特别多,空间复杂度会非常大,引起大量无用元素占用空间,太浪费空间

11.4.2 列表生成器

只要把[]换成()

(i for i in range(10)) --------会返回一个对象

res = (i for i in range(10))

next(res)

0

next(res)

1

next(res)

2

next(res)相当于一个指针,不会回退,直到抛出异常位置

res

for i in res:

	print(res)

生成器是一个算法,通过next()计算值,所以占内存小,没调用一次next(),返回下一个值,直到抛出异常

res

dir(res)

image-20221111152831715等价于nex(res)

函数转换为列表生成器

当列表生成时,需要大量的代码来完成时,不可能使用列表推导式,一般使用函数来完成(如:斐波那契数列:从第三个元素开始,每个元素是前两个元素的和)

ls = []
def fibonacci(num):
    first, second = 1, 1
    index = 0
    while index < num:
        ls.append(first)
        first, second = second, first + second
        index += 1


if __name__ == '__main__':
    fibonacci(10)
    print(ls)

数据一直存储再列表里,也是会造成内存占用过大的问题,函数转换为列表生成器,通过yield关键字,整个函数的返回值是一个生成器

ls = []


def fibonacci(num):
    first, second = 1, 1
    index = 0
    while index < num:
        ls.append(first)
        first, second = second, first + second
        index += 1


def fibonacci2(num):
    first, second = 1, 1
    index = 0
    while index < num:
        first, second = second, first+second
        yield first  #和return相似 会返回值
        index += 1


if __name__ == '__main__':
    #fibonacci(10)
    #fibonacci(1000) #占用内存
    # print(ls)
    res = fibonacci2(10)  #没有任何输出结果
    print(res)  #打印生成器对象
    print(next(res))
    print(next(res))
    print(next(res))

yield关键字的作用:

具有return的功能,能够返回一个函数的值

当一个函数出现yield,那么这个函数被调用执行,而且返回值是一个生成器next它的返回值,不断地返回被调用yield的值

yield值之后,会记住当前位置,下一次next函数调用生成器的时候,会在记住的位置继续执行

def test_yield(num):
    print("---------start-----")
    index = 0
    while index < num :
        print("-------1---------")
        return "哈哈哈"
        print("---------2--------")
        index += 1

        
if __name__ == '__main__':

    test_yield(10)

image-20221111152852741image-20221111152908254

def test_yield(num):
    print("---------start-----")
    index = 0
    while index < num :
        print("-------1---------")
        yield "哈哈哈"
        print("---------2--------")
        index += 1


if __name__ == '__main__':

    test_yield(10)
image-20221111155024911

yield关键字函数直接调用时,函数本身不会调用,而是将函数转换为生成器返回

result = test_yield(10)
print(result)
image-20221111155047077

第一次使用next()调用结果:

result = test_yield(10)
print(result)
print(next(result))

image-20221111152943021image-20221111153002476

第二次调用next()

result = test_yield(10)
print(result)
print(next(result))
print("-------第二次调用-------")
print(next(result))
image-20221111155131320

yield不仅具有return返回的功能,而且能记住上一次的位置,执行的时候从上一次结束的地方开始

res = fibonacci2(10)
for i in res:
    print(i)
image-20221111155157786

11.5 迭代器

迭代(Iterable)是访问容器元素的一种方式

可迭代的对象有哪些?可以for in循环遍历

判断是否可迭代:可以使用isinstance()判断一个对象是否是可迭代对象

s = "this is string"

from collections.abc import Iterable

help(isinstance)

isinstance(s,Iterable)

True

a = 10

isinstance(a,Iterable)

False

ls = []

isinstance(ls,Iterable)

True

迭代器:在python中,能够被全局函数next调用,并且返回下一个值得对象,就是迭代器

from collections.abc import Iterator

ls = [1,23,4,5]

isinstance(ls,iterator)

False

结论:凡是可以作用于for循环的对象都是iterable类型;凡是可作用于next()函数的对象都是iterator类型;可迭代对象不一定是迭代器,迭代器都是可迭代对象

怎样转为迭代器?

help(iter)

ls = [1,2,3,4]

iter(ls)

ls_s = iter(ls)

isinstance(ls,iteratable)

isinstance(ls,iterator)

next(ls)

next(ls)

11.6 闭包(closure)

闭包是一种现象,是弱数据类型编程语言所特有的现象。JavaScript(js)中最为广泛

概念:能够在函数内部调用其他函数变量的现象就叫做闭包(函数的变量是局部变量,作用域只有函数本身),闭包就是函数包裹函数的现象就是闭包

def outer():
    """
    这个函数就是一个闭包
    :return:
    """
    print("这是以一个闭包")
    a = 10
    def inner():
        print("这是里面函数的代码")
        b = 20
        print(a + b)
    #return inner() #没有返回值相当于返回的是None
    return inner

res = outer()
print(res)

输出结果是什么?

image-20221111153051263

可以debug运行看代码的执行过程

image-20221111153106291

有没有发现什么问题呢?闭包这样写有没有问题?

函数的调用本质是压栈的过程,res = outer()调用outer()函数的时候是一个压栈的过程,当执行完print(res)之后弹栈,局部变量应该被回收了,但是Inner()函数里面需要使用a变量,如果回收了,会报错,所以没有回收

结论:闭包让外出函数常驻内存,导致垃圾不能够及时释放,但是它让局部变量变成了全局变量,所以尽量不要使用闭包,闭包在js里用的比较广泛,在Python里比较少,会在装饰器里面用

11.7 装饰器的使用

装饰器是一种功能,功能就是在运行原来的功能基础上,加上一些其他的功能,比如权限的验证、日志记录等,不修改原来的代码,进行功能的扩展。比如java中的动态代理,python的注解装饰器,其实python的装饰器,是修改了代码

需求:初创公司有N个业务部门,1个基本平台部门,基础平台负责提供底层的功能,如:数据库操作,redis调用,监控API等功能。业务部门使用功能时,只需要调用基础平台提供的功能即可。如下:

#基础功能
def f1():
    print("f1")


def f2():
    print("f2")


def f3():
    print("f3")


def f4():
    print("f4")


#业务部门A调用平台提供的功能
f1()
f2()
f3()
f4()



#业务部门B调用平台提供的功能
f1()
f2()
f3()
f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码的时候没有关注验证相关的问题,即:基础平台的提供功能可以被任何人使用。现在需要堆基础平台的而所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先验证

老大把工作交给了lowB,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证,这样一来基础平台就不需要做任何修改了,有充足的时间去干其他的事情了…

老大又把工作交给了lowBB,他是这么做的:

#基础功能
def f1():
    #验证1
    #验证2
    #验证3
    print("f1")


def f2():
    # 验证1
    # 验证2
    # 验证3
    print("f2")


def f3():
    # 验证1
    # 验证2
    # 验证3
    print("f3")


def f4():
    # 验证1
    # 验证2
    # 验证3
    print("f4")


#业务部门A调用平台提供的功能
f1()
f2()
f3()
f4()



#业务部门B调用平台提供的功能
f1()
f2()
f3()
f4()

因为修改底层的代码需要很长的时间,过了一个周lowBB被开除了…因为花费的时间太长了,还没有完成工作

老大把工作交给了LowBBB,他是这么做的:

只对基础平台的代码进行了重构,其他业务部门无须做任何修改

#基础功能
def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass


def f1():
    check_login()
    print("f1")


def f2():
    check_login()
    print("f2")


def f3():
    check_login()
    print("f3")


def f4():
    check_login()
    print("f4")


#业务部门A调用平台提供的功能
f1()
f2()
f3()
f4()



#业务部门B调用平台提供的功能
f1()
f2()
f3()
f4()

老大看了lowBBB的实现,最近露出了一丝欣慰的笑,语重心长的和他聊了天:

老大说:

写代码要遵从开放封闭原则(OCP),虽然这个原则是用的面向对象开发,但是也适用于函数式编程,简单的来说,它规定已经实现的功能代码不允许被修改,但可以扩展,即:

封闭:已经实现的功能代码块

开放:对扩展开发

​ OCP原则:即开放关闭原则

​ 对已有代码的修改关闭

​ 对已有代码的增加开发

如果将开放封闭原则应用在上述需求中,那么不允许在函数f1,f2,f3,f3的内部进行修改代码,老大给了lowBBB一个实现方案:

#基础功能
def w1(func):
    def inner():
         # 验证1
         # 验证2
         # 验证3
         func()
    return inner


@w1
def f1():
    print("f1")


@w1
def f2():

    print("f2")


@w1
def f3():

    print("f3")


@w1
def f4():

    print("f4")


#业务部门A调用平台提供的功能
f1()
f2()
f3()
f4()



#业务部门B调用平台提供的功能
f1()
f2()
f3()
f4()

对于上述代码,也是仅仅对基础平台的代码进行了修改,就可以实现在其他人调用函数f1,f2,f3,f4之前都进行验证,并且其他业务部门无需做任何操作

def login():
    print("用户开始登录了")
    print("用户登录成功了")
    
    
def reg():
    print("用户开始注册了")
    print("用户注册成功了")


login()
reg()

这样写,假如我们忘记加入日志了(银行管理系统)请在如上代码里面加上日志记录

def login():
    with open("log.log","w") as f:
        f.write("xxxxxx")
    print("用户开始登录了")
    print("用户登录成功了")


def reg():
    print("用户开始注册了")
    print("用户注册成功了")


login()
reg()
image-20221111153137295

这样写不合适,违背了OCP原则

def outer(fn):
    def record():
        print("开始记录日志")
        fn()
        print("日志记录结束")
    return record


@outer
def login():
    #在稳定运行的代码上,不应该修改代码,违背了OCP原则
    # with open("log.log","w") as f:
    #     f.write("xxxxxx")
    print("用户开始登录了")
    print("用户登录成功了")


def reg():
    print("用户开始注册了")
    print("用户注册成功了")


login()
reg()
image-20221111153153843

​ 装饰器就是一个闭包函数,它能够@闭包名称装饰一个原有的函数,使得原有的函数功能更加完善

如何定义装饰器:

​ 1.定义一个闭包函数,闭包有一个默认的参数,是一个引用,该引用就是需要装饰的函数

​ 2. 需要在里面函数调用引用(函数fn),在调用之前写的代码就会被装饰在原函数之前,调用之后的代码,会被装饰在函数之后

注意:装饰器会自动调用函数

def test(fn):
    print("test")


def a():
    print("a")

不会打印出任何结果

def test(fn):
    print("test")

@test
def a():
    print("a")
image-20221111153243696
def test(fn):
    print("test")
    fn()

@test
def a():
    print("a")
image-20221111153258781

为什么装饰器一定要用闭包?上述是不是没有闭包也是可以,但是如果没有参数可以不用闭包,但是有参数呢?

def record(fn):
    def inner():
        print("开始记录日志")
        fn()
        print("日志记录结束")
    return inner


@record
def login(name,password):

    print("{}---{}用户开始登录了".format(name,password))
    print("用户登录成功了")


login("张三","123456")

会报错

image-20221111153348120

因为fn()就是Login(),需要参数

def record(fn):
    #如果装饰的函数存在了参数,请在闭包函数中传递参数
    def inner(name,password):
        print("开始记录日志")
        fn(name,password)
        print("日志记录结束")
    return inner


@record
def login(name,password):

    print("{}---{}用户开始登录了".format(name,password))
    print("用户登录成功了")


login("张三","123456")

执行过程(运行原理)

image-20221111153436461
def record(fn):
    print("装饰器开始工作了")
    #如果装饰的函数存在了参数,请在闭包函数中传递参数
    def inner(name,password):
        print("开始记录日志")
        fn(name,password)
        print("日志记录结束")
    print("装饰器返回了")
    return inner


@record
def login(name,password):

    print("{}---{}用户开始登录了".format(name,password))
    print("用户登录成功了")


login("张三","123456")
image-20221111153453274

可以有多个装饰器,一个装饰器可以装饰多个函数,一个函数也可以用多个装饰器装饰(自己研究)

装饰器的示例:(作业)

1.无参函数

2.有参函数

3.其他参数的函数

4.有返回值

11.8 正则表达式

正则表达式也叫做匹配模式,它由一组具有特定含义的字符串组成,通常用于匹配和替换字符串,它是一个独立的技术

import re

dir(re)

重要的方法:compile findall diniter match search split sub

>>> import re
>>> dir(re)
['A', 'ASCII', 'DEBUG', 'DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'Match', 'Pattern', 'RegexFlag', 'S', 'Scanner', 'T', 'TEMPLATE'
, 'U', 'UNICODE', 'VERBOSE', 'X', '_MAXCACHE', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__'
, '__spec__', '__version__', '_cache', '_compile', '_compile_repl', '_expand', '_locale', '_pickle', '_special_chars_map', '_subx', 'compile', 'copyre
g', 'enum', 'error', 'escape', 'findall', 'finditer', 'fullmatch', 'functools', 'match', 'purge', 'search', 'split', 'sre_compile', 'sre_parse', 'sub'
, 'subn', 'template']

match ----- 从头开始匹配

>>> s = "今天天气真好"
>>> type(s)
<class 'str'>
>>> re.match("今天",s)
<re.Match object; span=(0, 2), match='今天'>
>>> res = re.match("今天",s)
>>> res
<re.Match object; span=(0, 2), match='今天'>
>>> dir(res)
['__class__', '__class_getitem__', '__copy__', '__deepcopy__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute_
_', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__r
epr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'end', 'endpos', 'expand', 'group', 'groupdict', 'groups', 'lastgroup', 'lastindex
', 'pos', 're', 'regs', 'span', 'start', 'string']
>>> help(res.group)
Help on built-in function group:

group(...) method of re.Match instance
    group([group1, ...]) -> str or tuple.
    Return subgroup(s) of the match by indices or names.
    For 0 returns the entire match.

>>> res
<re.Match object; span=(0, 2), match='今天'>
>>> res.group()
'今天'
>>> re.match("天气",s) #返回的是空 没有匹配上

11.8.1 元字符

\d ------------- 匹配数字

. ------------匹配任意符号(除了换行符\n)

\w --------------匹配所有有效符号(大小写字母,数字下,下划线,各国语言符号)

\s --------------- 匹配空白位(如:空格、\t(四个空格))

^ ---------------- 以xxx开头,例如以1开头:^1

$ ----------------- 以xx结尾

[] --------------- 列举 [0123456789]等价于\d [a-z] 表示a-z的26个小写字母 [A-Za-z0-9] 表示有效字符 [A-Za-z]大小写字母

>>> re.match(".","1")
<re.Match object; span=(0, 1), match='1'>
>>> re.match(".","A")
<re.Match object; span=(0, 1), match='A'>
>>> re.match(".","a")
<re.Match object; span=(0, 1), match='a'>
>>> re.match(".","+")
<re.Match object; span=(0, 1), match='+'>
>>> re.match("\d","ahahhashhshsh")
>>> re.match("\d","5")
<re.Match object; span=(0, 1), match='5'>
>>> re.match("\d","57667hhhhh")
<re.Match object; span=(0, 1), match='5'>
>>> re.match("\w","57667hhhhh")
<re.Match object; span=(0, 1), match='5'>
>>> re.match("\w","*")
>>> re.match("^1.","1")
>>> re.match("^1.*","1")
<re.Match object; span=(0, 1), match='1'>
>>> re.match(".","/n")
<re.Match object; span=(0, 1), match='/'>
>>> re.match(".","\n")
>>> re.match("...","gagahshh")
<re.Match object; span=(0, 3), match='gag'>
>>> re.match("\s","gagahshh")
>>> re.match("\s"," gagahshh")
<re.Match object; span=(0, 1), match=' '>
>>> re.match("\s","   gagahshh")
<re.Match object; span=(0, 1), match=' '>
>>> re.match("\s","\t   gagahshh")
<re.Match object; span=(0, 1), match='\t'>
>>> re.match("^1","123h")
<re.Match object; span=(0, 1), match='1'>
>>> re.match("^1","23h")
>>> re.match("^1.*","23h")
>>> re.match("^1.*","23h")
>>> re.match("^1.*","123h")
<re.Match object; span=(0, 4), match='123h'>
>>> re.match("$h","123h")
>>> re.match("$1","123h1")
>>> re.match("^1\d$1","121")
>>> re.match("^1\d1$","121")
<re.Match object; span=(0, 3), match='121'>
>>> re.match("1$","121")
>>> re.match("h$","123h")
>>> re.match("^1h$","123h")
>>> re.match("^1\d*h$","123h")
<re.Match object; span=(0, 4), match='123h'>
>>> re.findall("h$","123h")
['h']
>>> help(re.findall)
Help on function findall in module re:

findall(pattern, string, flags=0)
    Return a list of all non-overlapping matches in the string.

    If one or more capturing groups are present in the pattern, return
    a list of groups; this will be a list of tuples if the pattern
    has more than one group.

    Empty matches are included in the result.

>>> re.match("[0123456789]","")
>>> re.match("[0123456789]","1234")
<re.Match object; span=(0, 1), match='1'>
>>> re.match("我今年\d岁了","我今年18岁了")

11.8.2 反义符

\D ------------------- 不是数字

\W --------------------- 特殊符号

\S -------------------------非空白位

>>> re.match("\D","7")
>>> re.match("\D","a")
<re.Match object; span=(0, 1), match='a'>
>>> re.match("\W","6")
>>> re.match("\W","^")
<re.Match object; span=(0, 1), match='^'>
>>> re.match("\S"," ")
>>> re.match("\s"," ")
<re.Match object; span=(0, 1), match=' '>
>>> re.match("\S","a")
<re.Match object; span=(0, 1), match='a'>

11.8.3 转义符

在python的字符串中\是有特殊含义的,要表示一个正常的\,需要两个\表示

s = "\t hello \n world"

print(s)

s = "hello \n world" #想要输出hello \n world

在正则中,\是有特殊含义的,要表示一个正常的\,需要两个\表示

path = "c:\\a\\b\\c"

print(path)

c:\a\b\c

#要匹配出上述路径

re.match("c:\\a\\b\\c",path)

#报错

re.match("c:\\\\\a\\\\\b\\\\\\c",path)

#需要四个\ 正则转义一次,字符转义一次

re.match(r"c:\\a\\b\\c",path)  #字符串前加r 只需要两个/

注意:建议大家以后在写正则的时候,一定要在正则表达式前面加上r

11.8.4 位数

s = "我今年18岁了,明年就19岁了"

s

re.findall(r"\d",s)

.*----------------------表示匹配任意位(可以是0位,可以是1位)

re.findall(r".*",s)

±-------------------- 表示至少有一位(至少1位,可以有n位)

re.findall(r"\d+",s)

? ---------------0位或者1位

{n,} --------------至少n位

{n,m} ---------------- 表示n-m的区间范围

re.findall("1[356789]]\d{9}","123456789")  #第一位是1 第二位是356789

re.findall("1[356789]]\d{9,}","123456789")

re.findall("1[356789]]\d{9,11}","123456789")


补充:[^] -------------------- 列举反义 注意[]和[]的区别

11.8.5 分组(group)

在正则表达式中,使用圆括号()将正则包裹起来,会形成正则匹配后的二次筛选

re.match(r".*\d+.*",s)

re.match(r".*(\d)+.*",s)

res = re.match(r".*(\d)+.*",s)

res

res.group(1)

 res = re.match(r".*?(\d+).*",s)

res>>> s
'我今年18岁了,明年我就19岁了'
>>> re.match(r".*\d+.*",s)
<re.Match object; span=(0, 16), match='我今年18岁了,明年我就19岁了'>
>>> re.match(r".*(\d)+.*",s)
<re.Match object; span=(0, 16), match='我今年18岁了,明年我就19岁了'>
>>> res = re.match(r".*(\d)+.*",s)
>>> res
<re.Match object; span=(0, 16), match='我今年18岁了,明年我就19岁了'>
>>> res.group(1)
'9'
>>> res.group(0)
'我今年18岁了,明年我就19岁了'
>>> help(res.group)
Help on built-in function group:

group(...) method of re.Match instance
    group([group1, ...]) -> str or tuple.
    Return subgroup(s) of the match by indices or names.
    For 0 returns the entire match.

>>> res = re.match(r".*?(\d+).*",s)
>>> res
<re.Match object; span=(0, 16), match='我今年18岁了,明年我就19岁了'>
>>> res.group(1)
'18'
>>> res = re.match(r"(\d{4})\-(\d{7})","0295-7172345")
>>> res
<re.Match object; span=(0, 12), match='0295-7172345'>
>>> res.group()
'0295-7172345'
>>> res.group(0)
'0295-7172345'
>>> res.group(1)
'0295'
>>> res.group(2)
'7172345'

res.group(1)

16

res.group()
#表示全部

分组最大的好处就是能够形成二次筛选

re.findall(r".*?(\d)+.*",s)

["16"]

实例:

 ss = """
... <div>
... <ul>
... <li><a href="#">友情连接1</a></li>
... <li><a href="#">友情连接2</a></li>
... <li><a href="#">友情连接3</a></li>
... <li><a href="#">友情连接4</a></li>
... <li><a href="#">友情连接5</a></li>
... </ul>
... </div>
... """

ss

'\n<div>\n<ul>\n<li><a href="#">友情连接1</a></li>\n<li><a href="#">友情连接2</a></li>\n<li><a href="#">友情连接3</a></li>\n<li><a href="#">友情连接4</a></li>\n<li><a href="#">友情连接5</a></li>\n</ul>\n</div>\n'

#把\n换掉

re.sub("\n|\r|\s","",s)

sss = re.sub("\n|\r|\s","",ss)

sss

'<div><ul><li><ahref="#">友情连接1</a></li><li><ahref="#">友情连接2</a></li><li><ahref="#">友情连接3</a></li><li><ahref="#">友情连接4</a></li><li><ahref="#">友情连接5</a></li></ul></div>'

re.sub(r"</?.+?>","",sss)

'友情连接1友情连接2友情连接3友情连接4友情连接5'

r"</?.*?>" ---------------- 匹配HTML标签

第十二章 爬虫(选修)

爬虫(spider),又叫网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本。

关于爬虫背景(自己学习)

通用爬虫就是检索引擎,例如百度,其实百度本身就是一个爬虫,它将很多信息爬取下来,比如连接image-20221111153725636等等,还有快照image-20221111153759147image-20221111153824420

爬虫目的:采集数据

爬虫的分类:

​ 通用网络爬虫(检索引擎(百度、Google)必须遵循robots协议)

​ 聚焦网络爬虫(针对我们需要的某一个战点或者几个站点进行采集的程序)

​ 增量式网络爬虫(属于爬虫的方式)

​ 累计式爬虫

​ 深层网络爬虫(暗网,自己感兴趣可以自己学这种爬虫方式)

12.1 第一个爬虫程序

#导入网络库
import urllib.request

#urlopen方法打开一个连接
url = "http://www.sina.com.cn"
#发送请求,返回响应头
response = urllib.request.urlopen(url)
#读取数据
data = response.read()
print(data)

返回的是一个字节数据

image-20221111154006316
#导入网络库
import urllib.request

#urlopen方法打开一个连接
url = "http://www.sina.com.cn"
#发送请求,返回响应头
response = urllib.request.urlopen(url)
#读取数据 注意返回的是字节数据
data = response.read()
print(data)

with open("sina.html","wb") as f:
    f.write(data)
    print("新浪采集完成")
image-20221111154045287
#导入网络库
import urllib.request

#urlopen方法打开一个连接
url = "http://www.sina.com.cn"
#发送请求
response = urllib.request.urlopen(url)
#读取数据 注意返回的是字节数据
data = response.read()
print(data)

# with open("sina.html","wb") as f:
#     f.write(data)
#     print("新浪采集完成")

#如果需要堆数据进行操作,建议转换字符串操作
html = data.decode("utf-8") #字符集和页面保持一致,查看网页源代码,meta=“utf-8”
# with open("sine2.html","w") as f:
with open("sine2.html","w",encoding="utf-8") as f:
    f.write(html)
    print("新浪采集完成")

12.2 fidder的使用

爬虫的本质就是使用网络请求获取数据,筛选需要的数据,将数据存储下来

https://www.telerik.com/download/fiddler

image-20221111154224096!

选择:I Agree

image-20221111154240781

选择安装的路径

image-20221111154332200

选择install 进行安装

image-20221111154404364

点击close,安装完后

打开软件,打开浏览器,百度页面,会出现很多请求

image-20221111154436535

remove all 清除

image-20221111154507311

打开pycharm运行代码

然后到fiddler中看到如下:

image-20221111154533107

Accept-Encoding: identity 期望编码

User-Agent: Python-urllib/3.9 用户代理对象

Connection: close

Host: www.sina.com.cn

网页百度页面:查看源代码

image-20221111154611084切换到手机模式

image-20221111154632311再刷新页面就出现了手机端

image-20221111154708048 image-20221111154730301

这是goolge提供的抓包工具,只能抓网页,不能抓pycharm,所以用fiddler

百度就是通过User-Agent来判断是客户端还是PC端

image-20221111155607047

很多网站都是存在着反爬得措施

想要爬取数据,要反反爬

​ 1.请求头伪造(必须)

​ 2.联系多次采集,一定注意:模拟人得行为 time.sleep(random.randint(1,5))

​ 3.IP地址代理(推荐收费)

12.3 请求头伪造

import urllib.request
resp = urllib.request.urlopen("https://www.baidu.com")

data = resp.read()
print(data)

with open("baidu.html","wb") as f:
     f.write(data)
import urllib.request
from urllib import request

"""
 在爬虫开发是,因为urllib库默认的User-Agent: Python-urllib/3.9,这样web服务器直到你是一个爬虫
 为了防止web服务器阻止爬虫,所以一般要求伪造请求头
"""
#header对象以键值对存在
headers = {
     "User - Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36"
}
url = "http://www.baidu.com"
#创建一个请求头对象
req = request.Request(url=url,headers=headers)
#注意:此时传递的是一个请求头对象
resp = request.urlopen(req)
data = resp.read()
print(data)

with open("baidu.html","wb") as f:
     f.write(data)
image-20221111155628946

不建议这样伪造,因为和浏览器一样,有时候一秒钟请求次数过多,也会被当作爬虫拦截

百度可以搜索User-Agent

import random
from urllib import request
us = [
    "User-Agent:Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
    "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
]
headers = {
    "User-Agent":random.choice(us)
}
url = "http://www.qq.com/"
#真正的请求头对象
req = request.Request(url=url,headers=headers)
resp = request.urlopen(req)

data = resp.read()
with open("qq.html","wb") as f:
    f.write(data)

爬取的数据出现乱码

查看腾讯的网页源代码,查看编码

image-20221111155650782

爬出的时候默认的是utf-8

import random
from urllib import request
us = [
    "User-Agent:Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
    "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
]
headers = {
    "User-Agent":random.choice(us)
}
url = "http://www.qq.com/"
#真正的请求头对象
req = request.Request(url=url,headers=headers)
resp = request.urlopen(req)

data = resp.read()
html = data.decode("gb2312",erros="ignore")
print(html)
# with open("qq.html","wb") as f:
#     f.write(data)

编码还是有问题,可以先转二进制,再转字符串(先不讲)

erros="ignore"   在转码的过程中,出现一些特殊的错误,先忽略不计
html = data.decode("utf-8")   这样写不可以,不一定编码都是utf-8

需要导入安装一个新的库

python -m pip install chardet

image-20221111155722844
import random
from urllib import request

import chardet
us = [
    "User-Agent:Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
    "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
]
headers = {
    "User-Agent":random.choice(us)
}
url = "http://www.sina.com.cn"
#真正的请求头对象
req = request.Request(url=url,headers=headers)
resp = request.urlopen(req)

data = resp.read()
#先判断数据的默认编码是什么
#返回的是一个字典数据
res = chardet.detect(data)
print(res)
# html = data.decode("utf-8")
# print(html)
# with open("qq.html","wb") as f:
#     f.write(data)
image-20221111155756816
import random
from urllib import request

import chardet
us = [
    "User-Agent:Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
    "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"
]
headers = {
    "User-Agent":random.choice(us)
}
url = "http://www.sina.com.cn"
#真正的请求头对象
req = request.Request(url=url,headers=headers)
resp = request.urlopen(req)

data = resp.read()
#先判断数据的默认编码是什么
#返回的是一个字典数据
res = chardet.detect(data)
#print(res)
charset = res.get("encoding")

html = data.decode(charset)
# print(html)
with open("sine3.html","w",encoding=charset) as f:
     f.write(html)

12.4 get和post请求

请求类型:get/post

GET请求一般用于我们向服务器获取数据,比如说,我们用百度搜索 爬虫:https://www.baidu.com/s?wd=爬虫(https://www.baidu.com/s?wd=%E7%88%AC%E8%99%AB)

我们可以看到在请求部分里,http://www.baidu.com/s? 之后出现一个长长的字符串,其中就包含我们要查询的关键词“爬虫”,于是我们可以尝试用默认的GET方式来发送请求。

百度地址栏:https://www.baidu.com/s?wd=请求

https://www.baidu.com/s?wd=%E8%AF%B7%E6%B1%82

from urllib import request
from  urllib import parse
url = "http://www.baidu.com/s?"
wd = input("请输入您要检索的关键字:")
params = {
    "wd":wd
}
ps = parse.urlencode(params)
print(ps)
image-20221111155816304
from urllib import request
from  urllib import parse
url = "https://www.baidu.com/s?"
wd = input("请输入您要检索的关键字:")
params = {
    "wd":wd
}
params = parse.urlencode(params)
#print(params)
url = url + params
print(url)
resp = request.urlopen(url)
#print(resp.read())
with open("get.html","wb") as f:
    f.write(resp.read())

这样不合适,需要加上请求头

配置https:

image-20221111155850627
from urllib import request
from urllib import parse

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36'
}
url = "http://www.baidu.com/s?"
wd = input("请输入您要检索的关键字:")
params = {
    "wd": wd
}

params = parse.urlencode(params)
# print(params)
#get请求最大得特定就是,如果有参数,将参数拼接到路径上来。如果存在中文问题,那么需要将中文编写拼接
url = url + params
# print(url)
req = request.Request(url=url,headers=headers)
resp = request.urlopen(req)
data = resp.read()
print(data)
with open("get.html","wb") as f:
    f.write(data)

练习:爬取图片(百度)

Request请求对象里有data参数,这就是用在POST里的,我们要传送的数据就是这个参数data,data是一个字典,里面要有匹配键值对。

以下以有道词典翻译网站为例为模拟POST请求

Pycharm中建一个文本文件,ctrl+r进行查找匹配,选择正则

image-20221111155926633 image-20221111155955771
# 导入库
import urllib.request
import urllib
url = "http://fanyi.youdao.com/translate?"
# chrome 的 User-Agent,包含在 header里
header = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36'
}

word = input("请输入需要翻译的词条:")

from_data = {
    "i":word,
    "from":"AUTO",
    "to":"AUTO",
    "smartresult":"dict",
    "doctype":"json",
    "version":"2.1",
    "keyfrom":"fanyi.wed"
}
data = urllib.parse.urlencode(from_data)
data = data.encode(encoding="utf-8")  # str转bytes

request = urllib.request.Request(url, data = data, headers = header)

response = urllib.request.urlopen(request)

html = response.read().decode(encoding = "utf-8").strip()

print(html)

12.5 requests库

requests底层是封装了urllib库

安装和配置:

推荐在线安装:pip命令

​ pip install requests(如果有问题:python -m pip install requests)

其他的安装方式,在模块的时候讲过

安装完成后可以进入Python测试终端测试是否安装成功:image-20221111160103980

requests的常用方法:

自己调取帮助文档学习

  • get请求
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#检测新浪的编码
print(chardet.detect(response.content))
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
print(response.encoding)
#检测新浪的编码
print(chardet.detect(response.content))

ISO-8859-1 这是ladin1编码,只支持ASCII编码表上的编码

import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
print(response.encoding)
#检测新浪的编码
print(chardet.detect(response.content))
print(response.text)
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
print(response.text)
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
print(response.text)
with open("sina4.html","w",encoding=charset) as f:
    #以字符形式将数据保存下来
    f.write(response.text)

如果知道编码格式,可以直接将代码改为:

import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
# print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
# charset = chardet.detect(response.content).get("encoding")
# print(charset)
# response.encoding = charset
# print(response.text)
response.encoding = "utf-8"
with open("sina4.html","w",encoding="utf-8") as f:
    #以字符形式将数据保存下来
    f.write(response.text)

不太建议这样写,还是需要去检测网站的编码

要求:将新浪的图片存下来(用到正则)

import re
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
# print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
# print(response.text)
# response.encoding = "utf-8"
# with open("sina4.html","w",encoding="utf-8") as f:
#     #以字符形式将数据保存下来
#     f.write(response.text)
html = response.text
#进行数据筛选,需要选择页面中所有的图片
images = re.findall(r"src=\"(.*?jpg|png|gif|jepg)\"",html)
print(images)
print(len(images))

图片路径是有问题的,所以我们需要下载图片,复制sina4.html中的图片路径到浏览器,在复制出来之后,图片的路径为:http://n.sinaimg.cn/finance/blackcat/pc/heimao.gif

import re
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
# print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
# print(response.text)
# response.encoding = "utf-8"
# with open("sina4.html","w",encoding="utf-8") as f:
#     #以字符形式将数据保存下来
#     f.write(response.text)
html = response.text
#进行数据筛选,需要选择页面中所有的图片
images = re.findall(r"src=\"(.*?jpg|png|gif|jepg)\"",html)
# print(images)
# print(len(images))
#循环下载这些图片
#enumerate可以传递一个容器
for index,item, in enumerate(images):
    print("开始从{}下载图片了".format(item))
    resp = requests.get(item)
    with open("image/"+str(index)+".jpg","wb") as f:
        #jpg并不会真的将所有图片改为jpg
        #因为是字节数据,所以使用resp.content
        f.write(resp.content)

会报错,图片路径:

)

import re
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
# print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
# print(response.text)
# response.encoding = "utf-8"
# with open("sina4.html","w",encoding="utf-8") as f:
#     #以字符形式将数据保存下来
#     f.write(response.text)
html = response.text
#进行数据筛选,需要选择页面中所有的图片
images = re.findall(r"src=\"(.*?jpg|png|gif|jepg)\"",html)
# print(images)
# print(len(images))
#循环下载这些图片
#enumerate可以传递一个容器
for index,item, in enumerate(images):
    print("开始从{}下载图片了".format(item))
    real_url = "http:" + item
    print("开始从{}下载图片了".format(real_url))
    resp = requests.get(real_url)
    with open("image/"+str(index)+".jpg","wb") as f:
        #jpg并不会真的将所有图片改为jpg
        #因为是字节数据,所以使用resp.content
        f.write(resp.content)
image-20221111160739452

但是还是有问题:有些图片的路径已经有http:

import re
import requests
import chardet
url = "http://www.sina.com.cn"
#requests库,如果需要发生get请求,直接调用get方法
#返回对象是响应头对象
response = requests.get(url = url)
#显示响应头对应的默认编码
# print(response.encoding)
#检测新浪的编码
# print(chardet.detect(response.content))
charset = chardet.detect(response.content).get("encoding")
print(charset)
response.encoding = charset
# print(response.text)
# response.encoding = "utf-8"
# with open("sina4.html","w",encoding="utf-8") as f:
#     #以字符形式将数据保存下来
#     f.write(response.text)
html = response.text
#进行数据筛选,需要选择页面中所有的图片
images = re.findall(r"src=\"(.*?jpg|png|gif|jepg)\"",html)
# print(images)
# print(len(images))
#循环下载这些图片
#enumerate可以传递一个容器
for index,item, in enumerate(images):
    print("开始从{}下载图片了".format(item))
    if not item.startswith("http"):
      real_url = "http:" + item
    print("开始从{}下载图片了".format(real_url))
    resp = requests.get(real_url)
    with open("image/"+str(index)+".jpg","wb") as f:
        #jpg并不会真的将所有图片改为jpg
        #因为是字节数据,所以使用resp.content
        f.write(resp.content)

post请求就是把get方法换成post

我们一直在手动伪造请求头,有没有哪个库可以提供这些请求头

fake-useragent

pip install fake-useragent(有问题:python -m pip install fake-useragent)

image-20221111160820398

pycharm中安装:

image-20221111160844611
import re
import requests
from fake_useragent import UserAgent
ua = UserAgent()
print(ua)
#ie浏览器的useragent

print(ua.ie)

#chrome浏览器

print(ua.chrome)
import re
import requests
from fake_useragent import UserAgent
# ua = UserAgent()
# print(ua.chrome)
# print(ua.ie)
# #随机
# print(ua.random)
headers = {
    "User-Agent":UserAgent().random
}
kw = input("请输入你要搜索的关键字:")
params ={
    "wd" : kw
}
url = "http://www.baidu.com/s?"
reponse = requests.get(url=url,params=params,headers=headers)
print(reponse.text)
image-20221111160948014
  • post请求(有四个参数)

    image-20221111161659021
image-20221111161728552

因为是post请求,直接访问,会报错

image-20221111161755147

如上访问,实则属于get访问,因为是地址栏访问

Pycharm中建一个文本文件,ctrl+r进行查找匹配,选择正则

image-20221111161819946 image-20221111161839744
import requests
from fake_useragent import UserAgent

url = "http://fanyi.youdao.com/translate?"
# chrome 的 User-Agent,包含在 header里
header = {
    "User-Agent":UserAgent().random
}

word = input("请输入需要翻译的词条:")

from_data = {
    "i":word,
    "from": "AUTO",
    "to": "AUTO",
    "smartresult": "dict",
    "client": "fanyideskweb",
    "doctype": "json",
    "version": "2.1",
    "keyfrom": "fanyi.web",
    "action": "FY_BY_REALTlME"
}
response = requests.post(url=url,data=from_data,headers=header)

print(response.text)

其他参数:

查看有道源代码------ctrl+f查找js,找到

image-20221111161901903

打开js文件,下载,复制到pycharm中格式化代码:

image-20221111161929875

找到js文件中所对应的参数:

image-20221111161955079

我们需要知道salt,sign,lts,bv这四个参数:

 {ts: r, bv: t, salt: i, sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")}

把它看作一个字典

r =  {ts: r, bv: t, salt: i, sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")}
var t = n.md5(navigator.appVersion), r = "" + (new Date).getTime(), i = r + parseInt(10 * Math.random(), 10);

salt:r.salt ===>i

i = r + parseInt(10 * Math.random(), 10)

salt = r + parseInt(10 * Math.random(), 10) #获取1-10的随机数*10,然后转成整数parseInt

r是什么?

r = “” + (new Date).getTime() 等价于python中的time.time()

看一下r有没有赋给某个变量

image-20221111162036394

看一下ts:

{ts: r, bv: t, salt: i, sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")}

所以ts的值就出来了

ts = 时间戳 time.time()

还剩余salt和bv,明显是md5加密的值,关键要看那些值被加密?

sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")

e:是我们要翻译的数据 i 就是盐值

sign = hashlib.md5("fanyideskweb"+ word + salt + "Tbh5E8=q6U3EXe+&L[4c@")

bv的值:

var t = n.md5(navigator.appVersion), r = "" + (new Date).getTime(), i = r + parseInt(10 * Math.random(), 10);
return {ts: r, bv: t, salt: i, sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")}

n.md5(navigator.appVersion)

创建一个html文件:

image-20221111162129456
bv = hashlib.md5("5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36")
import time
import hashlib
import random
import requests
from fake_useragent import UserAgent

url = "http://fanyi.youdao.com/translate?"
# chrome 的 User-Agent,包含在 header里
header = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36'
}

word = input("请输入需要翻译的词条:")
#python中获取时间戳是基于秒为单位的所以要化作毫秒
lts =   time.time()*1000
salt =  lts + random.randint(1,10)
sign = hashlib.md5("fanyideskweb"+ word + salt + "Tbh5E8=q6U3EXe+&L[4c@")
bv = hashlib.md5("5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36")
from_data = {
    "i": word,
    "from": "AUTO",
    "to": "AUTO",
    "smartresult": "dict",
    "client": "fanyideskweb",
    "salt": salt,
    "sign": sign,
    "lts":lts,
    "bv":bv,
    "doctype": "json",
    "version": "2.1",
    "keyfrom": "fanyi.web",
    "action": "FY_BY_REALTlME",
}
response = requests.post(url=url,data=from_data,headers=header)

print(response.text)
image-20221111162208667
import time
import hashlib
import random
import requests
from fake_useragent import UserAgent

url = "http://fanyi.youdao.com/translate?"
# chrome 的 User-Agent,包含在 header里
header = {
    "User-Agent":UserAgent().random
}

word = input("请输入需要翻译的词条:")
#python中获取时间戳是基于秒为单位的所以要化作毫秒
lts =   time.time()*1000
salt =  lts + random.randint(1,10)
sign = hashlib.md5(("fanyideskweb"+ word + str(salt) + "Tbh5E8=q6U3EXe+&L[4c@").encode("utf-8")).hexdigest()
bv = hashlib.md5("5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36".encode("utf-8")).hexdigest()
from_data = {
    "i": word,
    "from": "AUTO",
    "to": "AUTO",
    "smartresult": "dict",
    "client": "fanyideskweb",
    "salt": salt,
    "sign": sign,
    "lts":lts,
    "bv":bv,
    "doctype": "json",
    "version": "2.1",
    "keyfrom": "fanyi.web",
    "action": "FY_BY_REALTlME",
}
response = requests.post(url=url,data=from_data,headers=header)

print(response.text)

作业:破解百度翻译

12.6 xpath的入门使用

我们爬取数据之后,需要筛选解析数据,之前用的是正则,但是正则比较难,今天来学习一个新的解析数据的方法xpath

xpath,全称是XMLPath,即xml路径语言,它是一门在xml文档中查找信息的语言

例如:json数据

{”id“:1,”name“:”张三“}

xml数据:可扩展性标记语言

<user>

	<id>1</id>

	<name>zhangsa</name>

</user>

html 超文本标记语言,标签都是固定的

<html>

	<head>
	    
	</head>

 <body>

	<a href=""></a>

	<img src=""/>

</body>

</html>

xpath的常用规则:

nodename-------------------选取此节点的所有子节点

/ ------------------------ 根节点

// --------------------从当前节点选取子孙节点

. --------------------- 选取当前节点

… ----------------- — 选取当前节点父节点

@ --------------------- 选取属性

python使用xpath

xpath是独立的用来操作xml的语言,任何语言都可以使用。在python中使用xpath,需要下载安装lxml库,该模块就是Python对应xpath的封装库

pip install lxml

image-20221111162240053

如果下载失败,在百度搜索pip豆瓣,找相应的镜像,阿里淘宝都可以

image-20221111162302251 image-20221111162329936

-m pip install lxml -i “https://pypi.doubanio.com/simple/”

from lxml import etree

content = """
<div class = "containner">
    <ul class = "first">
        <li><a href = "#">内容1</a></li>
        <li><a href = "#">内容2</a></li>
        <li class = "active"><a href = "#">内容3</a></li>
        <li><a href = "#">内容4</a></li>
        <li><a href = "#">内容5</a></li>
        <li><a href = "#">内容6</a></li>
        <li><a href = "#">内容7</a></li>
        <li><a href = "#">内容8</a></li>
    </ul>
</div>          
"""
#转换成xpat对象
html = etree.HTML(content)
print(html)
image-20221111162451732
from lxml import etree

content = """
<div class = "containner">
    <ul class = "first">
        <li><a href = "#">内容1</a></li>
        <li><a href = "#">内容2</a></li>
        <li class = "active"><a href = "#">内容3</a></li>
        <li><a href = "#">内容4</a></li>
        <li><a href = "#">内容5</a></li>
        <li><a href = "#">内容6</a></li>
        <li><a href = "#">内容7</a></li>
        <li><a href = "#">内容8</a></li>
    </ul>
</div>          
"""
#转换成xpat对象
#etree.HTMLParser()解析器 方便后续操作
html = etree.HTML(content,etree.HTMLParser())
print(html)
#获取li标签下a标签的内容
res = html.xpath("//li/a/text()")
print(res)
#获取a标签href属性值
res = html.xpath("//li/a/@href")
print(res)

如果要加上属性值:

res = html.xpath("//li/a[@href='http://www.taobao.com']")

打印内容:

res = html.xpath("//li/a[@href='http://www.taobao.com']/text()")

推荐大家使用xpathhelper工具

第十三章 连接数据库(选修)

MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。

此外,MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。

13.1 安装MySQL

可以直接从MySQL官方网站下载最新的Community Server 5.6.x版本。MySQL是跨平台的,选择对应的平台下载安装文件,安装即可。

安装时,MySQL会提示输入root用户的口令,请务必记清楚。如果怕记不住,就把口令设置为password

在Windows上,安装时请选择UTF-8编码,以便正确地处理中文。

在Mac或Linux上,需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf

[client]
default-character-set = utf8

[mysqld]
default-storage-engine = INNODB
character-set-server = utf8
collation-server = utf8_general_ci

重启MySQL后,可以通过MySQL的客户端命令行检查编码:

$ mysql -u root -p
Enter password: 
Welcome to the MySQL monitor...
...

mysql> show variables like '%char%';
+--------------------------+--------------------------------------------------------+
| Variable_name            | Value                                                  |
+--------------------------+--------------------------------------------------------+
| character_set_client     | utf8                                                   |
| character_set_connection | utf8                                                   |
| character_set_database   | utf8                                                   |
| character_set_filesystem | binary                                                 |
| character_set_results    | utf8                                                   |
| character_set_server     | utf8                                                   |
| character_set_system     | utf8                                                   |
| character_sets_dir       | /usr/local/mysql-5.1.65-osx10.6-x86_64/share/charsets/ |
+--------------------------+--------------------------------------------------------+
8 rows in set (0.00 sec)

看到utf8字样就表示编码设置正确。

:如果MySQL的版本≥5.5.3,可以把编码设置为utf8mb4utf8mb4utf8完全兼容,但它支持最新的Unicode标准,可以显示emoji字符。

13.2 安装MySQL驱动

由于MySQL服务器以独立的进程运行,并通过网络对外服务,所以,需要支持Python的MySQL驱动来连接到MySQL服务器。MySQL官方提供了mysql-connector-python驱动,但是安装的时候需要给pip命令加上参数--allow-external

$ pip install mysql-connector-python --allow-external mysql-connector-python

如果上面的命令安装失败,可以试试另一个驱动:

$ pip install mysql-connector
python -m pip install -U pip mysql-connector

我们演示如何连接到MySQL服务器的test数据库:

# 导入MySQL驱动:
>>> import mysql.connector
# 注意把password设为你的root口令:
>>> conn = mysql.connector.connect(user='root', password='password', database='test')
>>> cursor = conn.cursor()
# 创建user表:
>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 插入一行记录,注意MySQL的占位符是%s:
>>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
>>> cursor.rowcount
1
# 提交事务:
>>> conn.commit()
>>> cursor.close()
# 运行查询:
>>> cursor = conn.cursor()
>>> cursor.execute('select * from user where id = %s', ('1',))
>>> values = cursor.fetchall()
>>> values
[('1', 'Michael')]
# 关闭Cursor和Connection:
>>> cursor.close()
True
>>> conn.close()

由于Python的DB-API定义都是通用的,所以,操作MySQL的数据库代码和SQLite类似。

小结:

  • 执行INSERT等操作后要调用commit()提交事务;
  • MySQL的SQL占位符是%s

13.3 pycharm 使用数据库

PymySQL安装很方便,由于它是用纯Python实现的

import pymysql
conn = pymysql.connect(
        host="localhost",
        user="root",
        password="111",
        db="test",
        charset="utf8",
        port=3306,)
# 创建游标
cursor = conn.cursor()
cursor.execute('select * from user where id = %s', ('1',))
values = cursor.fetchall() # 获取结果
print(values)
# 关闭Cursor和Connection:
cursor.close()
conn.close()

第十七章 人脸识别(选修)

opencv是一个强大的图像处理和计算机视觉库,实现了很多实用算法,值得学习和深究下

python -m pip install opencv-python

cv2.imread()

imread(img_path,flag) 读取图片,返回图片对象
    img_path: 图片的路径,即使路径错误也不会报错,但打印返回的图片对象为None
    flag:cv2.IMREAD_COLOR,读取彩色图片,图片透明性会被忽略,为默认参数,也可以传入1
          cv2.IMREAD_GRAYSCALE,按灰度模式读取图像,也可以传入0
          cv2.IMREAD_UNCHANGED,读取图像,包括其alpha通道,也可以传入-1

cv2.imshow()

imshow(window_name,img):显示图片,窗口自适应图片大小
    window_name: 指定窗口的名字
    img:显示的图片对象
    可以指定多个窗口名称,显示多个图片
    
waitKey(millseconds)  键盘绑定事件,阻塞监听键盘按键,返回一个数字(不同按键对应的数字不同)
    millseconds: 传入时间毫秒数,在该时间内等待键盘事件;传入0时,会一直等待键盘事件
    
destroyAllWindows(window_name) 
    window_name: 需要关闭的窗口名字,不传入时关闭所有窗口

cv2.imwrite()

imwrite(img_path_name,img)
    img_path_name:保存的文件名
    img:文件对象

案例:

import cv2
img = cv2.imread(r"image1.jpg")
# print(img.shape)
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,img_threshold = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY)
cv2.imshow("img",img)
cv2.imshow("thre",img_threshold)
key = cv2.waitKey(0)
if key==27: #按esc键时,关闭所有窗口
print(key)
cv2.destroyAllWindows()
cv2.imwrite(r"thre.jpg",img_threshold)

第四部分 项目(选修)

项目一:pygame制作跳跃小球

初识

Python Pygame 是一款专门为开发和设计 2D 电子游戏而生的软件包,它支 Windows、Linux、Mac OS 等操作系统,具有良好的跨平台性。

Pygame 由 Pete Shinners 于 2000 年开发而成,是一款免费、开源的的软件包,因此您可以放心地使用它来开发游戏,不用担心有任何费用产生。

Pygame 在 SDL(Simple DirectMedia Layer,使用 C语言编写的多媒体开发库)的基础上开发而成,它提供了诸多操作模块,比如图像模块(image)、声音模块(mixer)、输入/输出(鼠标、键盘、显示屏)模块等。相比于开发 3D 游戏而言,Pygame 更擅长开发 2D 游戏,比如于飞机大战、贪吃蛇、扫雷等游戏。

安装

官网:www.pygame.org

命令:pip install pygame

加载模块并初始化:小球开始

import sys
import pygame

pygame.init()      # 初始化pygame

创建窗口

pygame.display模块

  • 作用:创建游戏的主窗口
  • 常用内置方法
方法作用
pygame.display.init( )初始化display模块
pygame.display.quit( )结束display模块
pygame.display.get_init( )判断是否被初始化
pygame.display.set_mode( )初始化一个准备显示的界面
pygame.display.get_surface()获取当前Surface对象
pygame.display.flip( )更新显示
pygame.display.update( )更新部分内容显示到屏幕
  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size) # 显示窗口
  • 上例运行,会出现一个一闪而过的黑色窗口,这是因为执行完后会自动关闭,若想让窗口一直显示,需要使用while True 让程序一直执行
  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
while True:
    screen = pygame.display.set_mode(size)  # 显示窗口
  • 上例代码,对显示窗口增加了死循环可以达到窗口一直显示,但无法实现手动关闭,所以需要更改算法,利用事件检测来编写
  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size) # 显示窗口

# 执行死循环,确保窗口一直显示,直到手动关闭
while  True:
  # 检查事件,显示窗口是一个事件,点击关闭也是一个事件
    for event in pygame.event.get():
        if event.type==pygame.QUIT: # 如果点击关闭则退出
            pygame.quit()  
            sys.exit()
  • 上例代码中,添加了轮询事件检测,pygame.event.get()可以获取事件队列,使用for…in遍历事件,在根据type属性判断事件类型是否属于关闭窗口事件

添加小球

image模块

  • 作用:pyagme的图像传输的模块

  • 常用方法:

方法作用
pygame.image.load( )从文件加载新图片
pygame.image.save( )将图片保存到磁盘
pygame.image.get_extended( )检测是否支持载入扩展的图像格式
pygame.image.tostring( )将图像转换为字符串描述
pygame.image.fromstring()将字符串描述转换为图像
  • 支持的图片格式:JPG、PNG、GIF、BMP、PCX等
  • 注意:当一个图像被成功载入后,将转换为 Surface 对象,Surface 对象允许在其上边画线、设置像素、捕获区域等
  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size)  # 显示窗口
color = (0, 0, 0)  # 设置颜色

ball = pygame.image.load('ball.png')  # 加载小球图片,不显示

# 执行死循环,确保窗口一直显示,直到手动关闭
while True:
    # 检查事件,显示窗口是一个事件,点击关闭也是一个事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果点击关闭则退出
            pygame.quit()
            sys.exit()

处理Surface对象

image.load( )方法加载图片后会返回一个Surface对象,其表示图片,可以对该对象进行涂画、变形、复制、等操作,其实屏幕也是一个Surface对象

常用方法:

方法作用
pygame.Surface.blit( )将图像绘制到另一个图像上
pygame.Surface.convert( )转换图像的像素格式
pygame.Surface.fill( )使用颜色填充Surface
pygame.Surface.get_rect获取Surface的矩形区域
  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size)  # 显示窗口,screen是一个Surface对象
color = (0, 0, 0)  # 设置颜色

ball = pygame.image.load('ball.png')  # 加载小球图片
ballrect = ball.get_rect()  # 获取矩形区域,其实就是小球所占的区域,只不过没有内容

# 执行死循环,确保窗口一直显示,直到手动关闭
while True:
    # 检查事件,显示窗口是一个事件,点击关闭也是一个事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果点击关闭则退出
            pygame.quit()
            sys.exit()

    screen.fill(color)  # 填充颜色
    screen.blit(ball, ballrect)  # 把ball图片填充到窗口上
    pygame.display.flip()  # 更新全部显示

移动小球

小球ballrect拥有move()方法,可用于移动,move(x,y)表示x轴和y轴的移动距离,左上角坐标为(0,0),可以设置move(5,5)

小球续:

import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size)  # 显示窗口,screen是一个Surface对象
color = (0, 0, 0)  # 设置颜色

ball = pygame.image.load('ball.png')  # 加载小球图片
ballrect = ball.get_rect()  # 获取矩形区域

# 执行死循环,确保窗口一直显示,直到手动关闭
while True:
    # 检查事件,显示窗口是一个事件,点击关闭也是一个事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果点击关闭则退出
            pygame.quit()
            sys.exit()

    ballrect = ballrect.move(5, 5)  #  小球循环移动
    screen.fill(color)  # 填充颜色
    screen.blit(ball, ballrect)  # 把ball图片填充到窗口上
    pygame.display.flip()  # 更新全部显示

上例代码发现小球移动太快,这是因为上述代码的运行时间太短了,导致肉眼观察出现错觉,因此需要添加一个时钟用来控制程序运行事件

pygame.time模块

  • 作用:pygame中监控时间的模块
  • 常用方法
方法作用
pygame.time.get_ticks( )获取以毫秒为单位的时间
pygame.time.wait( )暂停程序一段时间
pygame.time.set_timer( )在事件队列上重复创建一个事件
pygame.time.Clock( )创建一个对象来帮助跟踪时间

注:Pygame中的时间以毫秒(1/1000秒)表示

  • 小球续:
import sys
import pygame

pygame.init()  # 初始化pygame
size = width, height = 640, 480  # 设置窗口大小
screen = pygame.display.set_mode(size)  # 显示窗口,screen是一个Surface对象
color = (0, 0, 0)  # 设置颜色

ball = pygame.image.load('ball.png')  # 加载小球图片
ballrect = ball.get_rect()  # 获取矩形区域

clock = pygame.time.Clock()  # 创建时钟
# 执行死循环,确保窗口一直显示,直到手动关闭
while True:
    clock.tick(60)  # 更新时钟,每秒执行60次,即程序将永远不会超过每秒60帧
    # 检查事件,显示窗口是一个事件,点击关闭也是一个事件
    for event in pygame.event.get():
        if event.type == pygame.QUIT:  # 如果点击关闭则退出
            pygame.quit()
            sys.exit()

    ballrect = ballrect.move(5, 5)  # 小球循环移动
    screen.fill(color)  # 填充颜色
    screen.blit(ball, ballrect)  # 把ball图片填充到窗口上
    pygame.display.flip()  # 更新全部显示

碰撞检测

上例中小球移动后消失,表示移动到窗体之外了,所以需要增加碰撞检测,即小球与窗体任意一边的边缘发生碰撞则更改小球的移动方向

小球终:

import sys
import pygame

pygame.init()                        # 初始化pygame
size = width, height = 640, 480      # 设置窗口
screen = pygame.display.set_mode(size)   # 显示窗口
color = (0, 0, 0)                   # 设置颜色
ball = pygame.image.load('ball.png')   # 加载图片
ballrect = ball.get_rect()          # 获取矩形区域
 
speed = [5, 5]                      # 设置移动的x轴、y轴
clock = pygame.time.Clock()         # 设置时钟

while True:                        # 执行死循环,却窗口一直显示
    clock.tick(60)                 # 每秒执行60次
    # 检查事件
    for event in pygame.event.get():  
        if event.type == pygame.QUIT:  # 如果单机窗口关闭,则退出
            pygame.quit()              # 退出pygame
            sys.exit()
    ballrect = ballrect.move(speed)    # 移动小球
    # 左右边缘碰撞检测
    if ballrect.left < 0 or ballrect.right > width:
        speed[0] = -speed[0]
    # 上下边缘碰撞检测
    if ballrect.top < 0 or ballrect.bottom > height:
        speed[1] = -speed[1]
    screen.fill(color)                # 填充颜色
    screen.blit(ball, ballrect)       # 将图片显示到窗口上
    pygame.display.flip()             # 更新全部显示

项目二:爬虫

基本概念

一个完整的数据分析案例:

从数据采集到可视化,整体方案制定:

1、需求分析

2、数据采集(爬虫)

3、数据清洗

4、数据入库

5、数据分析

6、数据可视化及报表

项目三:词云图

jieba包分词

概念

  • 在我们的日常生活中,如果想对英文文章进行分词是十分简单的,因为单词之间是有空格进行连接,例如: l Iove China.,但对于中文来说却十分麻烦,如:

    我 爱 中国 或者 我爱 中 国等,所以需要使用jieba实现中文分词

  • jieba(结巴)是百度工程师Sun Junyi开发的一个开源库,是一种基于Python的中文分词工具,是一种优秀的中文分词第三方库,jieba还可以做关键词抽取、词频统计等工作

安装

  • 新建python项目
  • 打开命令行
  • pip install jieba --use-pep517

jieba语法基本结构

  • 结构1:jieba.cut(sentence, cut_all=False, HMM=True, use_paddle=False)
    • sentence:分词的内容
    • cut_all:是否使用全模式,默认情况下为False,不使用
    • HMM:是否使用隐马尔可夫链,默认下是开启的,会对词典中没有出现过的词语自动识别并对其进行组合,关闭后不会对陌生词进行识别。
  • use_paddle:是否使用paddle模式,默认情况下是关闭的
    • 结构2:jieba.lcut( )同上,只是将结果转为list对象返回(推荐)
  • 小结:

jieba常用的三种模式:

  • 精确模式,试图将句子最精确地切开,不存在冗余,适合文本分析;

  • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义以及存在冗余;

  • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词

  • 例1:新建jiebamod.py文件

import jieba

str1 = jieba.lcut("中国是一个伟大的国家")  # 精确模式
# 结果:['中国', '是', '一个', '伟大', '的', '国家']
str2 = jieba.lcut("中国是一个伟大的国家", cut_all=True)  # 全模式
# 结果:['中国', '国是', '一个', '伟大', '的', '国家']
str3 = jieba.lcut_for_search("中华人民共和国是伟大的")  # 搜索引擎模式
# 结果:['中华', '华人', '人民', '共和', '共和国', '中华人民共和国', '是', '伟大', '的']
print(str1, "\n", str2, "\n", str3)
  • 例2:对qingchun.txt进行分词,数据来源于文本文件
import jieba

txt = open(r"D:\python\wordcloud\qingchun.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt)
for w in words:
    if w == "\n":
        print()
    else:
        print(w, end="\t")
  • 例3:对雪中悍刀行的演职员表进行分词,来自文件
import jieba

txt = open(r"d:\python\wordcloud\xz.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt)
print(words)

自定义词典分词

  • 原因:上例中某些词汇已经被人们接受,但结巴词库中并没有该词,所以分词后有误,可以自定义词典,添加新词,完成分词
  • 字典格式:
    • 新建文本文件
    • 一个词占一行
    • 每一行分三部分:词语、词频(可省略)、词性(可省略),空格隔开,顺序不可颠倒
    • 文件若使用路径或二进制打开则字符编码必须位UTF-8
  • 例1:上列3中新建字典后重新分词
    • 新建字典文件:xzd.txt
    • 输入新词,注意格式
    • 重新分词:
import jieba

txt = open(r"d:\python\wordcloud\xz.txt", "r", encoding="utf-8").read()
jieba.load_userdict("xzd.txt")  # 使用字典
words = jieba.lcut(txt)
print(words)
  • 例2:通过增加字典内容,进行分词

    • 添加前:
    import jieba
    
    txt = open(r"d:\python\wordcloud\jsj.txt", "r", encoding="utf-8").read()
    words = jieba.lcut(txt)
    print(words)
    
    • 添加后:
import jieba

txt = open(r"d:\python\wordcloud\jsj.txt", "r", encoding="utf-8").read()
jieba.add_word('大数据')  # 手动添加新词到词典
jieba.add_word('云计算')
jieba.add_word('区块链')
words = jieba.lcut(txt)
print(words)

统计词频:

  • 例1:统计雪中悍刀行的前10词频
import jieba

txt1 = open(r"d:\python\wordcloud\xue.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt1)
counts = {}
for w in words:
    if len(w) == 1:
        continue
    else:
        counts[w] = counts.get(w, 0) + 1
items = list(counts.items())
items.sort(key=lambda x: x[1], reverse=True)
for i in range(10):
    w, count = items[i]
    print("{0:<10}{1:>5}".format(w, count))
    
# 上述显示结果包含理论的前10词汇,若需要只统计演员表还需在做处理
  • 例2: 统计雪中悍刀行演员的前10词频
import jieba

txt1 = open(r"d:\python\wordcloud\xue.txt", "r", encoding="utf-8").read()
txt2 = open(r"d:\python\wordcloud\xz.txt", "r", encoding="utf-8").read()
jieba.load_userdict("xzd.txt")  # 使用字典
yy = jieba.lcut(txt2)
words = jieba.lcut(txt1)
counts = {}
for w in words:
    if len(w) == 1:
        continue
    elif w in yy:
        counts[w] = counts.get(w, 0) + 1
items = list(counts.items())
items.sort(key=lambda x: x[1], reverse=True)
for i in range(10):
    w, count = items[i]
    print("{0:<10}{1:>5}".format(w, count))

wordcloud

wordcloud是优秀的词云展示第三方库,所谓词云就是通过形成“关键词云层”或“关键词渲染”,对网络文本中出现频率较高的“关键词”的视觉上的突出,词云图过滤掉大量的文本信息,让读者快速提取文本的重要内容,而且通过不同文本的词云比对,达到数据分析的目的

安装所需库

  • 打开命令行
  • 安装数学计算库:pip install numpy
  • 安装图像处理标准库:pip install pillow
  • 安装绘图库:pip install matplotlib
  • 安装wordcloud(关键)
  • 命令安装:pip install wordcloud (若报错,需要下载vs组件)
    • 可以下载离线安装包安装:https://www.lfd.uci.edu/~gohlke/pythonlibs/#wordcloud
    • 安装:pip install 将下好的文件拖动到此处

制作步骤

  • 读取文本:open()
  • 分词
    • 中文分词
    • 以空格分割文本:变量名=" ".join(变量名)
  • 词云
    • 调用对象:w = wordcloud.WordCloud(参数)
    • 设置字体、布局参数
    • 生成词云:w.generate(txt) (向WordCloud对象w中加载文本txt)
    • 根据词频生成词云:generate_from_frequencies( )
  • 输出到文件
    • w.to_file(“文件名”) (导出到图像文件输出,格式为.png或者.jpg)
    • 通过matplotlib绘图库输出

wordcloud.WordCloud()函数

  • 参数介绍

  • 例1:将“悯农”诗词英文版的文本进行词云制作
import wordcloud

txt = """Hoeing in the paddy under the mid-day sun,
        Sweat dripping to the soil underneath the plant.
        Who knows that the meals in our plates,
        Every morsel of them comes with toil and pains."""  # 悯农古诗英文版
w = wordcloud.WordCloud()  # 创建对象
w.generate(txt)  # 加载文本
w.to_file('cy1.png')  # 输出文件

'''注意:注意同一段文本每次运行得到的词云都有可能不一样(颜色,位置会变),
   因为这里并没有固定的配置参数,但是字号是不变的,因为对于同一段文本来说,
   词频统计得到的结果是一样的。并且最新的结果会覆盖掉上一次的结果。'''
  • 例2:
import wordcloud

txt = """Hoeing in the paddy under the mid-day sun,
        Sweat dripping to the soil underneath the plant.
        Who knows that the meals in our plates,
        Every morsel of them comes with toil and pains."""
w = wordcloud.WordCloud(width=4000, height=2000, max_font_size=400)
w.generate(txt)
w.to_file('cy1.png')

文本为汉字的处理

  • 汉字格式:utf-8
  • 汉字字体必须设置:C:\Windows\Fonts
  • 例3:某小说词云图
import jieba
import wordcloud
txt = open(r"d:\python\wordcloud\xz.txt", "r", encoding="utf-8").read()
jieba.load_userdict("xzd.txt")
words = jieba.lcut(txt)
words = ' '.join(words)
w=wordcloud.WordCloud(
                        font_path="C:/Windows/Fonts/msyh.ttc", #为中文必须设置字体,必须为电脑有的字体
# msyh.ttc为微软雅黑
                        background_color="White",  #设置背景为白色
                        width=1500,                #设置画布高度
                        height=1000,               #设置画布宽度
                        max_words=50,              #设置最大词语个数
                        max_font_size=200,         #设置最大字号
                        scale=4,                   #设置画布放大倍数
                        random_state=50            #设置随机配色方案有多少种
                    ).generate(words)
image=w.to_image()
image.show()          #显示图像

注意:背景颜色可以更改,如:紫色 purple 粉红色 pink 蓝色 blue 红色 red 黑 色 black 黄 色yellow 青 色 cyan 金 色 glod 棕色 brown 白色 white

带有遮罩图形的词云图

  • 例1:制作西安政府工作报告带有模板的词云图
import jieba
import numpy
from PIL import Image
import wordcloud
txt = open(r"d:\python\wordcloud\zfbg.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt)
words = ' '.join(words)
image=numpy.array(Image.open('4.jpg'))                 #加载背景模板
w=wordcloud.WordCloud(
                        font_path="C:/Windows/Fonts/msyh.ttc", #为中文必须设置字体,必须为电脑有的字体
# msyh.ttc为微软雅黑
                        background_color="White",  #设置背景为白色
                        width=1500,                #设置画布高度
                        height=1000,               #设置画布宽度
                        max_words=200,              #设置最大词语个数
                        max_font_size=200,         #设置最大字号
                        scale=4,                   #设置画布放大倍数
                        random_state=50,            #设置随机配色方案有多少种
                        mask=image                #增加背景模板
                    ).generate(words)
image=w.to_image()
image.show()          #显示图像

注意:可以使用参数:colormap='cool’更换字体配色方案,配色方案有:autumn bone cool copper flag gray hot jet pink prism spring summer winter

colormap是matlab的内置函数,内涵多种颜色组合的条形渐变图

  • 例2:使用绘图方式显示
import jieba
import numpy
from PIL import Image
import wordcloud
import matplotlib.pyplot as plt
txt = open(r"d:\python\wordcloud\qingchun.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt)
words = ' '.join(words)
image=numpy.array(Image.open('5.jpg'))
w=wordcloud.WordCloud(
                        font_path="C:/Windows/Fonts/msyh.ttc",
                        background_color="White",  #设置背景为白色
                        width=1500,                #设置画布高度
                        height=1000,               #设置画布宽度
                        max_words=200,              #设置最大词语个数
                        max_font_size=70,         #设置最大字号
                        scale=4,                   #设置画布放大倍数
                        random_state=50,            #设置随机配色方案有多少种
                        mask=image  ,               #增加背景模板
                        colormap='copper'         #更改字体配色方案
                    ).generate(words)
plt.imshow(w)  #绘图
plt.show() #显示图像

项目四:学生信息管理系统

需求分析

使用面向对象编程思想完成学员管理系统的开发,具体如下:

  • 系统要求:学员数据存储在文件中
  • 系统功能:添加学员、删除学员、修改学员信息、查询学员信息、显示所有学员信息、保存学员信息及退出系统等功能

角色分析

在面向对象编程思想中,必须找到要具体实现操作的实体。

分析:

  • 通过系统实现添加学员操作

  • 通过系统实现删除学员操作

注意:

  • 了方便维护代码,一般一个角色一个程序文件
  • 项目要有主程序入口,习惯为main.py

创建项目

创建一个student项目名的项目

创建学生信息的文件:student.py

创建信息管理的文件:studentManager.py

创建主入口文件:main.py

如图:

71

student.py学员信息存储代码分析

需求:学员(主体) => 属性(姓名、年龄、电话)

注意:添加__str__魔法方法,方便查看学员对象信息

# 定义一个Student类
class Student():
    # 定义魔术方法,用于初始化属性信息
    def __init__(self, name, age, mobile):
        self.name = name
        self.age = age
        self.mobile = mobile
    # 定义魔术方法,用于打印输出学员信息
    def __str__(self):
        return f'{self.name}, {self.age}, {self.mobile}'

studentManager.py信息管理代码分析:

需求:

  • 存储数据的文件名:student.data
    • 加载文件数据
    • 修改数据后保存到文件
  • 存储数据的形式:列表存储学员对象
  • 系统功能:
    • 添加学员信息
    • 删除学员信息
    • 修改学员信息
    • 查询学员信息
    • 显示所有学员信息
    • 保存学员信息
    • 退出系统

编写程序代码,先搭建框架,具体功能回头填充

class StudentManager(object):
    # 定义一个__init__魔术方法,用于初始化数据
    def __init__(self):
        # 初始化一个student_list属性,用于将来保存所有学员对象信息
        self.student_list = []

    # 定义load_student()方法
    def load_student(self):
        pass      # 需填充

    # 定义静态show_help()方法
    @staticmethod
    def show_help():
        print('-' * 40)
        print('openlab信息管理系统V2.0')
        print('1.添加学员信息')
        print('2.删除学员信息')
        print('3.修改学员信息')
        print('4.查询学员信息')
        print('5.显示所有学员信息')
        print('6.保存学员信息')
        print('7.退出系统')
        print('-' * 40)

    def add_student(self):
        pass    # 需填充

    def del_student(self):
        pass    # 需填充

    def mod_student(self):
        pass    # 需填充

    def show_student(self):
        pass    # 需填充

    def show_all(self):
        pass    # 需填充

    def save_student(self):
        pass    # 需填充

    # 定义一个run()方法,专门用于实现对管理系统中各个功能调用
    def run(self):
        # 1、调用一个学员加载方法,用于加载文件中的所有学员信息,加载完成后,把得到的所有学员信息保存在student_list属性中
        self.load_student()
        # 2、显示帮助信息,提示用户输入要实现的功能编号
        while True:
            # 显示帮助信息
            self.show_help()
            # 提示用户输入要操作功能编号
            user_num = int(input('请输入要操作功能的编号:'))
            if user_num == 1:
                self.add_student()
            elif user_num == 2:
                self.del_student()
            elif user_num == 3:
                self.mod_student()
            elif user_num == 4:
                self.show_student()
            elif user_num == 5:
                self.show_all()
            elif user_num == 6:
                self.save_student()
            elif user_num == 7:
                print('感谢您使用openlab信息管理系统V2.0,欢迎下次使用!')
                break
            else:
                print('信息输入错误,请重新输入...')

编写add_student()学员添加方法实现

  • 需求:用户输入学员姓名、年龄、手机号,将学员添加到系统。
  • 步骤:
    • 用户输入姓名、年龄、手机号
    • 创建该学员对象(真正添加到列表中的是对象)
    • 将该学员对象添加到列表[] => append()
  • 代码:
from student import Student  # 第一行添加
	...    
    ...
    ...
    def add_student(self):
        # 提示用户输入学员信息
        name = input('请输入学员的姓名:')
        age = int(input('请输入学员的年龄:'))
        mobile = input('请输入学员的电话:')
        # 使用Student类实例化对象
        student = Student(name, age, mobile)
        # 调用student_list属性,追加student对象信息
        self.student_list.append(student)
        print('学员信息已添加成功')

编写del_student()学员删除方法实现

  • 需求:用户输入目标学员姓名,如果学员存在则删除该学员
  • 步骤:
    • 用户输入目标学员姓名
    • 遍历学员数据列表,如果用户输入的学员姓名存在则删除,否则则提示该学员不存在
  • 代码:
	def del_student(self):
        # 提示用户输入要删除的学员姓名
        name = input('请输入要删除的学员姓名:')
        # 对student_list属性(本质列表)进行遍历
        for i in self.student_list:
            if i.name == name:
                # 找到了要删除的学员,删除
                self.student_list.remove(i)
                print(f'学员{name}信息删除成功')
                break
        else:
            print('您要删除的学员不存在...')

编写mod_student()学员修改方法实现

  • 代码:
	def mod_student(self):
        # 提示用户输入要修改的学员姓名
        name = input('请输入要修改的学员姓名:')
        # 对student_list属性进行遍历,判断要修改的学员姓名是否存在
        for i in self.student_list:
            if i.name == name:
                i.name = input('请输入修改后的学员姓名:')
                i.age = int(input('请输入修改后的学员年龄:'))
                i.mobile = input('请输入修改后的学员电话:')
                print(f'学员信息修改成功,修改后信息如下 => 学员姓名:{i.name},学员年龄:{i.age},学员电话:{i.mobile}')
                break
        else:
            print('您要修改的学员信息不存在...')

编写show_student()学员查询方法实现

  • 代码:
	def show_student(self):
        # 提示用户输入要查询的学员姓名
        name = input('请输入要查询的学员姓名:')
        # 对student_list属性进行遍历
        for i in self.student_list:
            if i.name == name:
                print(i)
                break
        else:
            print('您要查找的学员信息不存在...')

编写show_all()方法查询所有学员实现

  • 代码:
	def show_all(self):
        # 直接对列表进行遍历
        for i in self.student_list:
            print(i)

编写save_student()方法学员信息保存功能实现

  • 需求:将所有学员信息都保存到存储数据的文件(student.data)
    • 步骤:打开文件 -->读写文件 --> 关闭文件
  • 代码:
# 把self.student_list转换为字符串保存到student.data文件中
    def save_student(self):
        # 打开文件
        f = open('student.data', 'w', encoding='utf-8')
        # 把列表中的对象转换为字典
        new_list = [i.__dict__ for i in self.student_list]
        # 文件读写(写入)
        f.write(str(new_list))
        # 关闭文件
        f.close()
        # 提示用户数据已经保存成功了
        print('学员信息保存成功')

编写load_student()方法学员加载功能实现

  • 需求:每次进入系统后,修改文件里面的数据
  • 步骤:读取文件、读取数据、转换数据类型为列表并转换列表内的字典i[‘name’]为对象i.name、存储学员数据到学员列表self.student_list
  • 代码:
 # 定义load_student()方法
    def load_student(self):
        # 捕获异常
        try:
            f = open('student.data', 'r', encoding='utf-8')
        except:
            f = open('student.data', 'w', encoding='utf-8')
        else:
            # 如果文件存在,没有异常,则执行else语句
            content = f.read()
            # 把字符串转换为原数据类型[{}, {}, {}]
            data = eval(content)
            # 把列表中的所有字典 => 转换为对象
            self.student_list = [Student(i['name'], i['age'], i['mobile']) for i in data]

        finally:
            f.close()

studentManager.py文件最终代码:

from student import Student


class StudentManager(object):
    # 定义一个__init__魔术方法,用于初始化数据
    def __init__(self):
        # 初始化一个student_list属性,用于将来保存所有学员对象信息
        self.student_list = []

    # 定义load_student()方法
    def load_student(self):
        # 捕获异常
        try:
            f = open('student.data', 'r', encoding='utf-8')
        except:
            f = open('student.data', 'w', encoding='utf-8')
        else:
            # 如果文件存在,没有异常,则执行else语句
            content = f.read()
            # 把字符串转换为原数据类型[{}, {}, {}]
            data = eval(content)
            # 把列表中的所有字典 => 转换为对象
            self.student_list = [Student(i['name'], i['age'], i['mobile']) for i in data]

        finally:
            f.close()

    # 定义静态show_help()方法
    @staticmethod
    def show_help():
        print('-' * 40)
        print('openlab信息管理系统V2.0')
        print('1.添加学员信息')
        print('2.删除学员信息')
        print('3.修改学员信息')
        print('4.查询学员信息')
        print('5.显示所有学员信息')
        # V2.0新增功能
        print('6.保存学员信息')
        print('7.退出系统')
        print('-' * 40)

    def add_student(self):
        # 提示用户输入学员信息
        name = input('请输入学员的姓名:')
        age = int(input('请输入学员的年龄:'))
        mobile = input('请输入学员的电话:')
        # 使用Student类实例化对象
        student = Student(name, age, mobile)
        # 调用student_list属性,追加student对象信息
        self.student_list.append(student)
        print('学员信息已添加成功')

    def del_student(self):
        # 提示用户输入要删除的学员姓名
        name = input('请输入要删除的学员姓名:')
        # 对student_list属性(本质列表)进行遍历
        for i in self.student_list:
            if i.name == name:
                # 找到了要删除的学员,删除
                self.student_list.remove(i)
                print(f'学员{name}信息删除成功')
                break
        else:
            print('您要删除的学员不存在...')

    def mod_student(self):
        # 提示用户输入要修改的学员姓名
        name = input('请输入要修改的学员姓名:')
        # 对student_list属性进行遍历,判断要修改的学员姓名是否存在
        for i in self.student_list:
            if i.name == name:
                i.name = input('请输入修改后的学员姓名:')
                i.age = int(input('请输入修改后的学员年龄:'))
                i.mobile = input('请输入修改后的学员电话:')
                print(f'学员信息修改成功,修改后信息如下 => 学员姓名:{i.name},学员年龄:{i.age},学员电话:{i.mobile}')
                break
        else:
            print('您要修改的学员信息不存在...')

    def show_student(self):
        # 提示用户输入要查询的学员姓名
        name = input('请输入要查询的学员姓名:')
        # 对student_list属性进行遍历
        for i in self.student_list:
            if i.name == name:
                print(i)
                break
        else:
            print('您要查找的学员信息不存在...')

    def show_all(self):
        # 直接对列表进行遍历
        for i in self.student_list:
            print(i)

    def save_student(self):
        # 打开文件
        f = open('student.data', 'w', encoding='utf-8')
        # 把列表中的对象转换为字典
        new_list = [i.__dict__ for i in self.student_list]
        # 文件读写(写入)
        f.write(str(new_list))
        # 关闭文件
        f.close()
        # 提示用户数据已经保存成功了
        print('学员信息保存成功')

    # 定义一个run()方法,专门用于实现对管理系统中各个功能调用
    def run(self):
utumn  bone  cool  copper  flag  gray  hot   jet   pink  prism  spring  summer  winter**        
>
> 
>
> **colormap是matlab的内置函数,内涵多种颜色组合的条形渐变图**

- **2:使用绘图方式显示**

```python
import jieba
import numpy
from PIL import Image
import wordcloud
import matplotlib.pyplot as plt
txt = open(r"d:\python\wordcloud\qingchun.txt", "r", encoding="utf-8").read()
words = jieba.lcut(txt)
words = ' '.join(words)
image=numpy.array(Image.open('5.jpg'))
w=wordcloud.WordCloud(
                        font_path="C:/Windows/Fonts/msyh.ttc",
                        background_color="White",  #设置背景为白色
                        width=1500,                #设置画布高度
                        height=1000,               #设置画布宽度
                        max_words=200,              #设置最大词语个数
                        max_font_size=70,         #设置最大字号
                        scale=4,                   #设置画布放大倍数
                        random_state=50,            #设置随机配色方案有多少种
                        mask=image  ,               #增加背景模板
                        colormap='copper'         #更改字体配色方案
                    ).generate(words)
plt.imshow(w)  #绘图
plt.show() #显示图像

项目四:学生信息管理系统

需求分析

使用面向对象编程思想完成学员管理系统的开发,具体如下:

  • 系统要求:学员数据存储在文件中
  • 系统功能:添加学员、删除学员、修改学员信息、查询学员信息、显示所有学员信息、保存学员信息及退出系统等功能

角色分析

在面向对象编程思想中,必须找到要具体实现操作的实体。

分析:

  • 通过系统实现添加学员操作

  • 通过系统实现删除学员操作

注意:

  • 了方便维护代码,一般一个角色一个程序文件
  • 项目要有主程序入口,习惯为main.py

创建项目

创建一个student项目名的项目

创建学生信息的文件:student.py

创建信息管理的文件:studentManager.py

创建主入口文件:main.py

如图:

71

student.py学员信息存储代码分析

需求:学员(主体) => 属性(姓名、年龄、电话)

注意:添加__str__魔法方法,方便查看学员对象信息

# 定义一个Student类
class Student():
    # 定义魔术方法,用于初始
  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值