Python的主要应用领域
第一章
一. Python 简介
1.1 Python的出生
python的创始人为吉多·范罗苏姆(Guido van Rossum)。
1989年的圣诞节期间,吉多·范罗苏姆(中文名字:龟叔)为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承。
(龟叔:2005年加入谷歌至2012年,2013年加入Dropbox直到现在,依然掌握着Python发展的核心方向,被称为仁慈的独裁者)。
1.2 Python的今世
2019年6月的TIOBE排行榜,Python占据第三的位置, Python崇尚优美、清晰、简单,是一个优秀并广泛使用的语言。
Python整体呈上升趋势,反映出Python应用越来越广泛并且也逐渐得到业内的认可!
1.2.1 Python的主要应用领域:
-
云计算: 云计算最火的语言, 典型应用OpenStack
-
WEB开发: 众多优秀的WEB框架,众多大型网站均为Python开发,Youtube, Dropbox, 豆瓣。。。典型WEB框架有Django
-
科学运算、人工智能: 典型库NumPy, SciPy, Matplotlib, Enthought librarys,pandas
-
系统运维: 运维人员必备语言
-
爬虫:通过代码来模拟人进行页面访问,对信息进行批量的获取
-
金融:量化交易,金融分析,在金融工程领域,Python不但在用,且用的最多,而且重要性逐年提高。原因:作为动态语言的Python,语言结构清晰简单,库丰富,成熟稳定,科学计算和统计分析 生产效率远远高于c,c++,java,尤其擅长策略回测'
-
图形GUI: PyQT, WxPython,TkInter
1.2.2 Python在哪些公司被使用:
-
谷歌:Google App Engine 、code.google.com 、Google earth 、谷歌爬虫、
-
Google广告等项目都在大量使用Python开发
-
CIA: 美国中情局网站就是用Python开发的
-
NASA: 美国航天局(NASA)大量使用Python进行数据分析和运算
-
YouTube:世界上最大的视频网站YouTube就是用Python开发的
-
Dropbox:美国最大的在线云存储网站,全部用Python实现,每天网站处理10亿个文件的上传和下载
-
Instagram:美国最大的图片分享社交网站,每天超过3千万张照片被分享,全部用python开发
-
Facebook:大量的基础库均通过Python实现的
-
Redhat: 世界上最流行的Linux发行版本中的yum包管理工具就是用python开发的
-
豆瓣: 公司几乎所有的业务均是通过Python开发的
-
知乎: 国内最大的问答社区,通过Python开发(国外Quora)
-
春雨医生:国内知名的在线医疗网站是用Python开发的
-
除上面之外,还有搜狐、金山、腾讯、盛大、网易、百度、阿里、淘宝 、土豆、新浪、果壳等公司都在使用Python完成各种各样的任务。
1.3 Python的历史
-
1989年,为了打发圣诞节假期,Guido开始写Python语言的编译器。
-
Python这个名字,来自Guido所挚爱的电视剧Monty Python’s Flying Circus。他希望这个新的叫做Python的语言,能符合他的理想:创造一种C和shell之间,功能全面,易学易用,可拓展的语言。
-
1991年,第一个Python编译器诞生。它是用C语言实现的,并能够调用C语言的库文件。从一出生,Python已经具有了:类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统。
-
Granddaddy of Python web frameworks, Zope 1 was released in 1999
-
Python 1.0 - January 1994 增加了 lambda, map, filter and reduce.
-
Python 2.0 - October 16, 2000,加入了内存回收机制,构成了现在Python语言框架的基础
-
Python 2.4 - November 30, 2004, 同年目前最流行的WEB框架Django 诞生
-
Python 2.5 - September 19, 2006
-
Python 2.6 - October 1, 2008
-
Python 2.7 - July 3, 2010
-
In November 2014, it was announced that Python 2.7 would be supported until 2020, and reaffirmed that there would be no 2.8 release as users were expected to move to Python 3.4+ as soon as possible
-
Python 3.0 - December 3, 2008
-
Python 3.1 - June 27, 2009
-
Python 3.2 - February 20, 2011
-
Python 3.3 - September 29, 2012
-
Python 3.4 - March 16, 2014
-
Python 3.5 - September 13, 2015
-
Python 3.6 - December 16,2016
1.4 Python是什么编程语言
编程语言主要从以下几个角度为进行分类,编译型和解释型、静态语言和动态语言、
强类型定义语言和弱类型定义语言,我们主要通过编译型和解释性来划分Python是什么编程语言
1.4.1 编译型与解释型
编译器 是把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时
计算机可以直接以机器语言来运行此程序,速度很快;
而解释器则是只在执行程序时,才一条一条的解释成机器语言给计算机来执行,
所以运行速度是不如编译后的程序运行的快的.
为什么会有编译型好解释型语言是因为计算机不能直接认识并执行我们写的语句,它只能认识机器语言(是二进制 的形式)
编译型 优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
解释型 优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
1.4.2 动态语言和静态语言
通常我们所说的动态语言、静态语言是指动态类型语言和静态类型语言。
(1)动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,
在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,
在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如
VBScript也多少属于动态类型语言。
(2)静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,
也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,
其他的静态类型语言还有C#、JAVA等。
1.4.3 强类型定义语言和弱类型定义语言
(1)强类型定义语言:强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,
如果不经过强制转换,那么它就永远是这个数据类型了。
举个例子:如果你定义了一个整型变量a,那么程序根本不可能将a当作字符串类型处理。
强类型定义语言是类型安全的语言。
(2)弱类型定义语言:数据类型可以被忽略的语言。它与强类型定义语言相反,
一个变量可以赋不同数据类型的值。
强类型定义语言在速度上可能略逊色于弱类型定义语言,但是强类型定义语言带来的严谨性能够有效的避
免许多错误。另外,“这门语言是不是动态语言”与“这门语言是否类型安全”之间是完全没有联系的!
例如:Python是动态语言,是强类型定义语言(类型安全的语言); VBScript是动态语言,是弱类型定义语
言(类型不安全的语言); JAVA是静态语言,是强类型定义语言(类型安全的语言)。
通过上面这些介绍,我们可以得出. Python是一门动态解释性的强类型定义语言。
1.5 Python的种类
-
Cpython
Python的官方版本,使用C语言实现,使用最为广泛,CPython实现会将源文件(py文件)
转换成字节码文件(pyc文件),然后运行在Python虚拟机上。
-
Jyhton
Python的Java实现,Jython会将Python代码动态编译成Java字节码,然后在JVM上运行。
-
IronPython
Python的C#实现,IronPython将Python代码编译成C#字节码,然后在CLR上运行。(与Jython类似)
-
PyPy(特殊)
Python实现的Python,将Python的字节码字节码再编译成机器码。
看了这么多内容,感觉好厉害已近迫不及待的想要动手去试试了,我不得不说,还是需要在稍等一下.我们需要安装一下Python解释器
二.环境安装
2.1打开官网:http://www.python.org,点击Downloads下载,如下图
2.2下拉页面,选择对应版本:Python3.6.3,如下图
向下查找,找到Python 3.6.3
2.3 根据系统选择对应的安装包,如下图
根据当前系统的选择对用的,红色的是windows32位,绿色的是windows64位
2.4下载完成,如下图
下载完成后找到下载的目录,然后用鼠标左键双击
2.5 安装
2.6 执行下一步
2.7 勾选安装
2.8 安装进度
2.9 安装成功
2.10 验证是否配置成功
打开电脑的终端(黑窗口),输入 python回车进去python解释器,返回的结果入下图:
2.11 退出终端中的python
在终端中 >>> 输入exit() 如下图:
三.Python入门
3.1 输出
使用Windows系统使用Notepad++创建一个文本,文本中的内容如下:
print("hello world")
写完后一定要记得保存,保存完后我们打开我们的终端,Windows系统的找到图下的两个键:
使用Mac系统的电脑首先打开 启动台 — 其他 — 终端 — 输入 vim 文件名字 然后按键盘i 进行输入然后退出的时候按一下esc键 发现文件的左下角有个: 然后在这个位置输入wq! 回车就行了,具体的请看图下实例
我们现在都打开了终端后再终端中输入以下命令:
python 文件名.py
注意点:python和文件名是有空格的,再次确定一下大家的现在是不是都是这个样子的:
print() 括号里面的内容就是要输出的内容,如果是文字和字母需要用英文的引号引起来,数字直接写就可以啦
python2写法:
print 'hello world'
python3写法:
print('hello world')
我们学习的python3版本,python2版本是什么样也要知道
我们现在成功的将英文的打印出来了,现在我们使用这种方式打印一下中文的内容吧!
print("你好 世界")
然后将文件保存了,保存后我们在来终端上运行一次.是不是都出现报错了,出现报错这个是正常的,我们只需要在加上一句话就可以了.
#conding:utf-8
这局话必须要加在文件的第一行,这个是告诉解释器当文件中出现中文的时候使用utf-8的编码进行查看
3.2 变量
什么是变量?
变量就是把程序运行的中间结果临时存在内存中,以便后续代码使用
这个知识点用在哪呢? 变量在程序中经常会被使用
变量如何使用?
name = "meet"
这样就是在定义一个变量,我们具体说一下定义变量
name 是一个变量名
= 是一个赋值操作 赋值就是将值交给name
'Meet' 是一个值
变量的作用:昵称,就是代指内存中某个地址中的内容
我们定义变量的时候,可以任意起名字吗? 应该是有规则的吧,我们来看看变量定义的规则
3.2.1 变量的定义规则
-
变量名由字母,数字,下划线组成
-
变量名禁止使用数字开头
-
禁止使用Python中的关键字以及内置函数的名字
关键字如下:
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
-
不建议使用中文和拼音
-
变量名要具有意义
-
变量名区分大小写
-
推荐写法:
-
驼峰体: AgeOfOldboy = 56
-
下划线:age_of_oldboy = 56
-
这是两种写法,你们觉得那种的比较清晰.是不是第二种看着比较清晰,第一种猛的一看还以为是AngelaBaby呢
我们现在定义一个变量知道了,看看定义多个变量
3.2.2 变量的赋值
name1 = 'Meet' name2 = 'Guo'
此时的name1的值是Meet,name2的值是Guo,我们来看看下边的这个变量
现在我们在内存中开辟了两个空间,分别给不同的内存地址贴上不同的标签,我们来看看下边的代码
name1 = 'Meet' name2 = name1
现在的这个name1是meet,name2是name1 也就是name2现在用的是name1的值,name2的值就是meet
我们在来看一下变量的小高级使用:
3.2.3 变量的小高级
age1 = 18 age2 = age1 age1 = 12 age3 = age2 print(age1,age2,age3)
我们先来分析一下这个结果是怎样的,在运行看结果是否和你想的一样
如果结果和你想的不一致,你需要注意了.咱们代码的运行是从上至下的执行,并且在内存中只能用一个名字相同的标签.
age = 12 age = 12 + 1 age1 = age + 1 print("我今年:",age1) name1 = 'alex' name2 = name1 name1 = 'wusir' name3 = name2 打印name1,name2name3分别对应的值是什么
通过上边的小高级和练习我们可以得出一个宗旨,代码是从上向下执行,并且便签只能有一份,相当于一个变量名只能代指一个值
3.3常量
常量就是将变量名大写,尽量保持不更改的一种量 这个常量有是干什么的呢 其实我生活中就有一些定死了的量 比如生日,身份证等 咱们代码中,也应该有一些这样的东西,咱们从设计之初就是不让任何人改变的,所以这个东西就是常量。
3.4注释
在你写代码时,有些代码写的比较经典,或者过于复杂,你一眼看不出来,怎么办,比如在文言文中,有些出处或者偏难怪的文字,你是不是不理解? 那么怎么办呢?是不是就得有简单说明。
注释就是做这个的,我们来看一下怎么用
# 这个就是给这一行进行注释,注释后的代码不会执行 ''' 这种的是多行注释, 什么是多行注释啊,其实很简单就是咱们注释的内容可以 进行换行 '''
看个示例:
# print('hello') 这样就是对这个代码进行注释,并且这个代码不会执行 ''' print(1) print(2) 这种就是多行注释,这样的注释也不会执行 '''
3.5 基础数据类型:
什么是数据类型?
我们人类可以很容易的分清数字与字符的区别,但是计算机并不能呀,计算机虽然很强大,但从某种角度上看又很傻,除非你明确的告诉它,1是数字,“汉”是文字,否则它是分不清1和‘汉’的区别的,因此,在每个编程语言里都会有一个叫数据类型的东西,其实就是对常用的各种数据进行了明确的划分,你想让计算机进行数值运算,你就传数字给它,你想让他处理文字,就传字符串类型给他。Python中常用的数据类型有多种,今天我们暂只讲3种, 数字、字符串、布尔类型
int是整型;也就是我们从小就接触的数学中的整数,整型在计算机中运于计算和比较
在32位机器上int的范围是: -231~231-1,即-2147483648~2147483647
在64位机器上int的范围是: -263~263-1,即-9223372036854775808~9223372036854775807
目前这些数字已经够你用了吧. 注意这些是整数.
3.5.1 整型
num1 = 2 num2 = 3 print(num1 + num2) # 结果: 5 # 整型就和我们学的数学一样可以进行加减乘除
str是字符串;也就是我们从小就接触的中文,如果我们在程序中直接把中文写入,程序是不能分辨这是个什么鬼东西.所以我们要遵循一种规则让计算机能够识别,这个规则就是只要是用引号引起来就是字符串
在Python中,凡是用引号引起来的,全是字符 .
字符 可以用单引号,双引号,或者三引号引起来,没有什么区别,只是一些特殊的格式需要不用的引号 比如:
msg = "My name is Alex , I'm 22 years old!" 这个就需要单双引号配合。 msg = """ 今天我想写首小诗, 来歌颂我的同桌, 你看他那乌黑的短发, 好像一只炸毛鸡。 """ 想写多个内容还需要使用换行的时候,就需要三引号。
数字类型可以做运算,字符串可以吗??
3.5.2 字符串
字符串加法
#字符串的拼接 s1 = 'a' s2 = 'bc' print(s1 + s2)
字符串乘法
str*int name = '坚强' print(name*8)
1.5.3 布尔值
bool是布尔值,布尔值中一共分为俩个状态
True 就是真
False 就是假
例如我们想让计算机帮我们计算一下两个数的比较大小
print(3>7) 结果: False print(7>3) 结果: True
我们现在简单的认识了这三个基础数据类型,我们在学习一下用户输入
3.6 输入
咱们在登录一些网页和QQ的时候是不是常常会让咱们输入账户或者密码的情况,
这样的情况在程序中会大量的存在,接下来咱们自己实现一个登录时候要输入的功能
s = input('请输入你喜欢的东西')
input就是固定格式,他是输入的意思 括号里边的内容是提示是给用户看的提示语句,用户输入的内容让input接收了然后赋值给了字符串s
3.6.1 输入用户名
#!/usr/bin/env python #-*- coding:utf-8 -*- #将用户输入的内容赋值给name变量 python2的写法 name = raw_input("请输入用户名:") print name python3的写法 name = input("请输入用户名:") print(name)
需要注意的是input程序交互获取到的内容都是字符串,我们看下面的简单示例:
num = input('请输入数字:') print(num + 5) 结果: 请输入数字:6 Traceback (most recent call last): File "D:/python_object/test.py", line 350, in <module> print(num + 5) TypeError: must be str, not int
报错了,提示我们类型错误,数字和字符串是不能相加的.可是我们输入的明明是数字啊.其实我们看着输入的是 数字,但是input获取到的都是字符串
这个知识点用在哪里?
在工作中如果需要用户输入一些个人信息比如账号,密码,性别,爱好等,即是你需要将你输入的信息传递给程序当中,那么就需要用到用户交互input。
注意
在Python2 中使用raw_input 获取的都是字符串 在Python3 中使用input 获取的都是字符串
3.7 流程控制
你在生活中是不是经常遇到各种选择,比如玩色子,猜大小,比如选择走那条路回家?Python程序中也会遇到这种情况,这就用到了if语句。
这个功能就是Python中的流程控制语句if。 那么如何使用这个功能呢?(下面就是)
if 条件: #引号是将条件与结果分开。 结果# 四个空格,或者一个tab键,这个是告诉程序满足这个条件的结果。切记空格和tab键不能混合使用
3.7.1 判断条件 if
age = input("输入年龄:") if int(age) >= 18: print("成年了,能干成年人的事了")
3.7.2 二选一 if else
age = input('请输入您的年龄:') if int(age) > 18: print('你可以去网吧尽情的嗨皮了') else: print('你现在还不行啊!')
以上这种写法是让用户输入内容,然后进行比较,如果一个条件成立那就执行print,为了明确的区分改执行哪些部分,当条件成立后执行缩进的代码
然后程序就退出了.下边的else就不会在执行了
3.7.3 多个选项进行单选或不选 if elif
num = input('请输入要比较的数字:') if int(num) >= 22: print('可以扯证了') elif int(num) >= 18: print('成年了,可以干一些成年的事了') elif int(num) < 18: print('还是小屁孩')
以上写法就是我们做的单选题一样,三个分支判断中只要有个成立,其余都不在执行
3.7.4 多个选项进行单选(必选) if elif else
num = input("请输入要比较的数字:") if num > 60: print("大了") elif num < 60: print("小了") else: print("猜对了")
3.7.5 嵌套if
name = input('请输入名字:') if name == 'meet': age = input('请输入年龄:') if int(age) == 18: print('输入全部正确') else: print('年龄输入错误!') else: print('名字输入错误!')
这样写法很常用,这样我们就可以明确的知道用户输入的名字和年龄那个是错误的,并且还做一个判断如果名字正确后在进行判断密码
第二章
Python 运算符
一、Pycharm使用
1.1 下载Pycahrm
首先要下载Pycharm这个软件,官网的下载地址是: http://www.jetbrains.com/pycharm/download/#section=windows
选择左边的点击进行下载,左边的是专业版右边是社区版
1.2 下载中
出现这个页面就是正在下载,稍微喝杯茶等等
1.3 找到文件
1.4 安装
选择Next 点击
1.5 选择要安装到那个目录
1.6 选择配置
我是64位机器,我选择了64,如果是32的就选择32 [不管64还是32剩下的都选择]
1.7 点击安装
1.8 安装中
1.9 安装成功
出现这个界面的直接点击Finish关闭就可以了. 我们现在切换到桌面
1.10 使用Pycharm
找到这个图标然后双击
1.11 首次使用Pychram
1.12 用户许可证
1.13 激活Pycharm
激活详情http://idea.lanyus.com/页面
点击选择的内容生成激活码,然后将激活码复制到code选项中
1.14 个性化设置
这个直接关闭就可以了
1.15 启动成功
如果你的激活码没问题的话,会在个性化,主题设置完毕之后,经过短暂的加载(加载速度取决于电脑性能)进入如图页面,到这一步,PyCharm安装完成了
1.16 创建文件
1.17 PyCharm选择解释器
File -- Settings -- Project -- Project Interpreter,这里会显示当前系统默认的解释器,如果要添加别的解释器,点击工具图标,Add local -- Existing environment,点击三点图标,在打开的本地文件目录中选择解释器文件的.exe文件。就行了,如果你没有选择, PyCharm会自动选择当前环境默认的解释器
1.18 PyCharm创建py文件
鼠标放到大纲然后右键鼠标,出现一个New然后鼠标向右滑动选择python file点击左键
直接写文件的名字就可以了,写完后回车就搞定了.
二、while循环
while 循环 在生活中,我们遇到过循环的事情吧?比如循环听歌。在程序中,也是存才的,这就是流程控制语句 while
基本循环
while 条件: # 循环体 # 如果条件为真,那么循环则执行 # 如果条件为假,那么循环不执行
条件如果为真就会一直执行下去 也就人们常说的死循环,我们想要停止就点那个红色的方块,如果点击的x的话,程序并没有停止,还在继续运行着
我们可以使用while循环进行内容循环,我们怎么能够让程序停止运行?
刚刚说到,死循环是因为条件一直都为真的时候,如果想让while循环停止最简单的方式就是将条件修改成假的,看下面示例
flage = True str_num = input("请输入要比较的数字:") print("进入循环") while flage: if "3" > str_num: print("在执行循环") else: print("要终止循环") flage = False print("退出循环")
我们现在知道可以通过变量的形式改变while循环,我们还可以通过计数的方式来控制循环执行循环的次数,先来看一下
使用while计数
count = 0 while True: count = count + 1 print(count)
这样下去我就会执行下去,但是我想到100就停了
控制while循环的次数
count = 0 while count < 100: count = count + 1 print(count)
while 关键后边的是条件,这样就可以通过条件成功的控制住循环的次数,我们现在知道通过修改while后边的内容来终止循环,这是咱们自己想的办法,python这个编程语言中是不是应该也得有个终止循环的关键字什么的吧,我们来找一下试试
break关键字
我们除了可以使用条件能够让循环停止,其实Python还给我们提供了一个break关键字来停止循环
num = 1 while num <6: print(num) num+=1 break print("end")
当程序执行到break的时候就结束了.break就是结束当前这个while循环的 break以下的代码都不执行
continue关键字
continue 用于退出当前循环,继续下一次循环
num = 1 while num <6: print(num) num+=1 continue print("end")
注意:break是终止循环,continue是跳出本次循环,继续下次循环
while else使用
# 循环一 while True: if 3 > 2: print('你好') break else: print('不好') # 循环二 while True: if 3 > 2: print('你好') print('不好') # 大家看到的这个是不是感觉效果是一样的啊,其实不然 # 当上边的代码执行到break的时候else缩进后的内容不会执行
这个执行的效果是因为
循环一执行了循环也执行了if条件打印了你好然后碰到break循环结束了
循环二执行了循环也执行了if条件打印了你好,但是没有break 就继续重复执行了
循环一将3>2改成3<2这个条件就不成立,然后执行了else里打印了不好
while else 练习
首先让用户输入序号选择格式如下:
0.退出 1.开始登录 如果用户选择序号0 就提示用户退出成功 如果用户选择序号1就让用户输入用户名密码然后进行判断,正确就终止循环,错误重新输入
三.格式化输出
现在有个需要我们录入我们身边好友的信息,格式如下:
------------ info of Alex Li ---------- Name : Alex Li Age : 22 job : Teacher Hobbie: girl ------------- end ----------------
我们现在能想到的办法就是用一下方法:
name = input('请输入姓名:') age = input('请输入年龄:') job = input('请输入职业:') hobby = input('请输入爱好:') a = '------------ info of Alex Li ----------' b = 'Name:' c = 'Age:' d = 'Job:' e = 'Hobby:' f = '------------- end ----------------' print(a+ '\n'+ b+ name+ '\n'+ c+ age+ '\n'+ d+ job+ '\n'+ e+ hobby+ '\n'+ f) # 运行结果 ------------ info of Alex Li ---------- Name:meet Age:18 Job:it Hobby:3 ------------- end ----------------
这样写完全没有问题,但是会不会比较繁琐呢,有些大佬肯定会想这不都实现了吗,还逼叨逼什么啊,那是没有体验过格式化输出有多霸道,我们现在来体验下霸道的姿势
3.1 %s
name = input('请输入姓名:') age = input('请输入年龄:') job = input('请输入职业:') hobby = input('请输入爱好:') msg = ''' ------------ info of Alex Li ---------- Name : %s Age : %s job : %s Hobbie: %s ------------- end ---------------- ''' print(msg%(name,age,job,hobby))
% 是一个占位, 回想下我们小时候给朋友占位子的场景,是的这个就是占位.那s又是什么呢? s代码的字符串类型;
3.2 %d|%
name = input('>>>') s1 = '1234%d'%int(name) s2 = '1234%i'%int(name) print(s1) print(s2) 结果: >>>89 123489 123489 # %d和%i这种格式化只能用数字来填补占位
3.3 %%
num = input('>>>') s= '目前学习进度:%s%%'%num print(s) 结果: >>>80 目前学习进度:80% # 如果我们字符串中想要显示单独的%就需要用来个%%来转义,不然程序会认为那是一个占位
四.运算符
计算机可以进行的运算有很多种,可不只加减乘除这么简单,运算按种类可分为算数运算、比较运算、逻辑运算、赋值运算、成员运算、身份运算、位运算.
今天我们暂只学习 算数运算、比较运算、逻辑运算、赋值运算、成员运算
4.1 算数运算
以下假设变量:a=10,b=20
4.2 比较运算
以下假设变量:a=10,b=20
4.3 赋值运算
以下假设变量:a=10,b=20
4.4 逻辑运算
针对逻辑运算的进一步研究:
在没有()的情况下not 优先级高于 and,and优先级高于or,即优先级关系为( )>not>and>or,同一优先级从左往右计算。
例题:
判断下列逻辑语句的True,False。
x or y , x为真,值就是x,x为假,值是y;
x and y, x为真,值是y,x为假,值是x。
3>4 or 4<3 and 1==1 1 < 2 and 3 < 4 or 1>2 2 > 1 and 3 < 4 or 4 > 5 and 2 < 1 not 2 > 1 and 3 < 4 or 4 > 5 and 2 > 1 and 9 > 8 or 7 < 6
例题:求出下列逻辑语句的值。
8 or 4 0 and 3 0 or 4 and 3 or 7 or 9 and 6
4.5 成员运算
in not in :
判断子元素是否在原字符串(字典,列表,集合)中:
例如:
#print('喜欢' in 'dkfljadklf喜欢hfjdkas') #print('a' in 'bcvd') #print('y' not in 'ofkjdslaf')
五.编码
咱们的电脑,存储和发送文件,发送的是什么?电脑里面是不是有成千上万个二极管,亮的代表是1,不亮的代表是0,这样实际上电脑的存储和发送是不是都是010101啊
我们发送的内容都是010101010这样写的内容比较多就不知道是什么了,所以我们想要明确的区分出来发送的内容就需要
在某个地方进行分段.计算机中设定的就是8位一断句
5.1 ASCII
计算机: 储存文件,或者是传输文件,实际上是010101010 计算机创建初期,美国,是7位一段,但是发明者说为了拓展,留出一位,这样就是8位一段句。8位有多少种可能 ?256 密码本: ascii 00000001 01000001 01000010 01000011 ABC
随着计算机的发展. 以及普及率的提高. 流⾏到欧洲和亚洲. 这时ASCII码就不合适了. 比如: 中⽂汉字有几万个. 而ASCII 多也就256个位置. 所以ASCII不行了. 怎么办呢? 这时, 不同的国家就提出了不同的编码用来适用于各自的语言环境. 比如, 中国的GBK, GB2312, BIG5, ISO-8859-1等等. 这时各个国家都可以使用计算机了.
5.2 GBK
GBK, 国标码占用2个字节. 对应ASCII码 GBK直接兼容. 因为计算机底层是用英文写的. 你不支持英文肯定不行. 而英文已经使用了ASCII码. 所以GBK要兼容ASCII. 这里GBK国标码. 前⾯的ASCII码部分. 由于使⽤两个字节. 所以对于ASCII码⽽言. 前9位都是0
字母A:0100 0001 # ASCII 字母A:0000 0000 0100 0001 # 国标码
随着全球化的普及,发展到欧洲,亚洲等国家,发现这些根本不够用,所以创建了万国码。 因为全球语言很多,ascii不足以存储这么多对应关系,创建了一个超级密码本:万国码unicode
8 位 == 1个字节. hello h一个字符,e一个字符,he就不是一个字符. 中国:中是一个字符,国是一个字符.
5.3 unicode
创建之初,16位,2个字节,表示一个字符. 英文: a b c 六个字节 一个英文2个字节 中文 中国 四个字节 一个中文用2个字节
但是这种也不行,这种最多有65535种可能,可是中国文字有9万多,所以改成 32位,4个字节,表示一个字符.
a 01000001 01000010 01000011 00000001 b 01000001 01000010 01100011 00000001 中 01001001 01000010 01100011 00000001 浪费资源.
5.4 UTF-8
对Unicode进行升级: utf-8 utf-8 用最少用8位数,去表示一个字符. 英文: 8位,1个字节表示. 欧洲文字: 16位,两个字节表示一个字符. 中文,亚洲文字: 24位,三个字节表示.
utf-16 用最少用16位数。
gbk: 国标,只能中国人自己用, 一个中文用16位,两个字节表示。
5.5 单位转化
8bit = 1byte 1024byte = 1KB 1024KB = 1MB 1024MB = 1GB 1024GB = 1TB 1024TB = 1PB 1024TB = 1EB 1024EB = 1ZB 1024ZB = 1YB 1024YB = 1NB 1024NB = 1DB 常⽤到TB就够了
第三章
Python基础数据类型一
一. 整型(int)
整型在Python中的关键字用int来表示; 整型在计算机中是用于计算和比较的
在python3中所有的整数都是int类型. 但在 python2中如果 数据量比较大. 会使用long类型.
在python3中不存在long类型 整数可以进行的操作:
1.1 整数的加
a = 10 b = 20 print(a + b) 结果: 30
1.2 整数的减
a = 10 b = 20 print(b - a) 结果 10
1.3 整数的乘
a = 10 b = 20 print(a * b) 结果: 200
1.4 整数的除
a = 10 b = 20 print(b / a) 结果: 2.0 # 注意点:当我们使用Python3的时候我们除法获取到时浮点数,也就是小数,但是使用Python2的时候使用除法获取的就是整数
1.5 整数的整除
a = 10 b = 20 print(b // a) 结果: 2
1.6 整数的取余
a = 5 b = 2 print(a % b) 结果: 1
1.7 整数的次方(幂)
a = 5 b = 2 print(a ** b) 结果: 25
注意点: 在Python2中存在long(长整型) 但是在 Python3中long(长整型)不存在
我们常说的数字都是10进制的,数字表示的形式有很多种,我们今天就简单的说一下 十进制数 和 二进制数
他们之间是如何转换,我们先来看看十进制转换二进制,使用一种整除法,例如十进制的数是15我们想要知道15的二进制是多少就除以2获取他的余数然后从下向上将这些余数拼到一起,就是用以下的方法
我们现在知道怎么将十进制的数转换成二进制,那就再来看看怎样将二进制的数转换成十进制.
我们使用110001来举例,现在想要获取到这个二进制的数然后转换成十进制 要进行如下操作
接下来的计算我们就从右向左计算,用最右侧的数乘以2的0次方,依次向左推 1*2**0 + 0*2**1 + 0*2**2 + 0*2**3 + 1*2**4 + 1*2**5 换算下来就是 1 + 0 + 0 + 0 + 16 + 32 = 49
我们使用这种办法就能将二进制的数转换成十进制的数
二. 布尔值(bool)
布尔值(bool) 判断真假
python语法 人类语言 计算语言 True 真 1 False 假 0
2.1 字符串转换数字
n = int('22') print(n) 结果:22 #注意点: 字符串中的内容必须是阿拉伯数字才能强行转换
2.2 数字转换字符串
n = str(22) print(n) 结果: 2 看到的效果是像是数字,实际上就是字符串,我们可以使用type查看类型
2.3 数字转换布尔值
n = bool(4) # bool(-1) 结果也是True print(n) 结果: True 注意点: 使用bool转换的时候只要是非0的都为True
2.4 布尔值转换数字
n = int(True) # int(False) 结果是0 print(n) 结果: 1
2.5 字符串转换布尔值
n = bool(' ') print(n) 结果: True 注意点: 只要字符串中的内容不为空, 所得到的结果就是True
2.6 布尔值转换字符串
n = str(True) print(n) 结果: True 这是一个字符串的True
三.字符串(str)
字符串, 用于存储一些少量的数据,在Pyhton当中只要是用引号引起来的就是字符串,字符串中的每个单独的字母汉字都是一个元素.
在第一天的时候咱们简单的认识了下字符串,今天我们好好的来认识一下这个让你又喜又优的字符串
我们来回忆下字符串是不是可以进行加减乘除
3.1 字符串加
a = '世界' b = '你好' print(a + b) 结果: 世界你好
通过刚刚我们的测试发现字符串也是可以进行加法操作的,字符串相加其实有个专业的名词 --- 字符串拼接;相加的规则就是必须都是字符串才能相加
字符串中是不能进行减法和除法操作的,通过这句话就能得知字符串是可以进行乘法操作的,我们立马来看一下:
3.2 字符串乘
a = '坚强' print(a * 8) 结果: 坚强坚强坚强坚强坚强坚强坚强坚强
字符串的乘法 就是 将多个字符串拼接到一起, 乘法的规则:只能是字符串和数字相乘
我们回顾完第一天的字符的内容,那就来看看咱们今天要讲的内容是什么??
3.3 索引(下标)
大家在上学的时候就会发现在一个班有的同学的名字是一样的,最后大家为了好区分就有了外号,我们每个人都有个学号,其实学校就是为了防止重名找错人,
学号就是一种能够保证唯一且准确的手段,这种手段在计算机中存在,这种手段叫做索引,也有人称之为下标.
图上就是对"meet"字符串进行索引排号,其实图上有一点大家不难看出m对应的是数字0,如果让我们人来给排号.下意识的会从1开始.
因为我们从小的时候就是从1开始数数的,但是在计算机中数数确实要从0开始,其实这个点也是对程序的入门.
以后在碰到这种类似的问题我们就知道是从0开始计数,废话不多说我们来看看,我说的到底行不行.
name = 'meet' # 索引 0123 print(name[0]) 结果: m
细心的老铁们会发现这[ ]是干啥的,这个是一个查找,我不知道字符串中第一个是什么,但是我知道第一个的索引,我就可以通过这个方式来查看
比方:我不知道这个学生叫什么,但是我知道他的学号.我也可以通过学号找到这个同学.
图上这个是从左到右的一种排序,其实还有一种从右像左的排序,看下图:
这个查看和上边的一样,只不过是索引排序的方式不一样,他这次的排列序号是从右向左并且还是以-1开始,这里的-1其实就是表示我们的倒数第一个
-2表示倒数第二个,这样排列,如果我们的字符串比较长的时候想要获取最后一个就完全可以使用-1,看以下示例:
name = 'meet' # 索引 -4-3-2-1 print(name[-1]) 结果: t
告诉大家一个小技巧,刚开始的我们不能明确的区分这个字符串中的每个索引的时候,我们就先把每个字母对应的索引写下来,这样就方便我们查找
上边我们说完了索引,你们就以为完事了是吗?不是滴,咱们还有来个内容没有进行讲解,分别是切片和步长
3.4 切片
切片又是什么呢?我们先来看一个示例:
name = 'meet' # 索引 0123 print(name[0:3]) 结果: mee
[第一个位置是开始:第二个位置是终止]中间必须使用分号,这样的写法就是从索引0开始获取到索引3结束
这个结果是不是和大家想的有点出入啊,大家肯定认为获取到的内容是meet,但是为什么是mee呢,因为 终止的索引是不包含的获取的是这个区间的内容
想想咱们在买肉的时候,有一个块整肉,这块整肉就后边有点肥,不想要是不是就让老板给切掉了,我们就买前边的部分啊.在生活中这种神操作就是切片
我也知道你们刚接过这些东西,使用起来不是很熟悉,尤其是切片这部分,在悄悄的告诉你们个小技巧,下次当看到切片中终止位置的时候把这个数减一就ok啦
3.5 步长
步长又是什么呢?先看示例:
name = 'meet' # 索引 0123 print(name[0:3:1]) 结果: mee
发现我在中括号中最后一个位置写了一个东西,也没什么变化,是的没有变化,因为我们不写的时候他默认就是1,我们换个数字在来看看
name = 'meet' # 索引 0123 print(name[0:3:2]) 结果: me
这又是啥情况呢?想想我开始告诉大家的 中括号里第一个参数是起始位置,第二参数是终止位置,第三个参数现在告诉大家是步长(每次走几步)
当步长设置为2的时候,咱们只需要用起始位置0加上步长2,结果也就2然后在把索引为2的找到,2在加上步长2就是4,当要查找索引4是发现终止索引就是3,
所有不会进行查找.最终的结果就是me.
3.6 字符串方法详解
3.6.1 全部大写
name = 'alex' new_name = name.upper() print(new_name) # 把这个字符串全部变成大写
3.6.2 全部小写
name = 'ALEX' new_name = name.lower() print(new_name) # 把这个字符串全部变成小写
应用场景:
# 字符串大小写做验证码 y_z_m = 'O98k' y_z_m_input = input("请输入验证码(O98k)") user = input('请输入账号:') pwd = input('请输入密码:') if y_z_m == y_z_m_input: if user == 'alex' and pwd == '8520': print('登陆成功!') else: print('登录失败') else: print('验证码错误!')
3.6.3 以什么开头
name = 'alex' new_name = name.startswith('a') if new_name: print('是以a开头') # 判断这是不是字符串是不是以a开头的 name = 'alex' new_name = name.startswith('e',2,5) if new_name: print('是以e开头') # 我们可以指定从哪个位置开始哪个位置结束
3.6.4 以什么结尾
name = 'alex' new_name = name.endswith('x') if new_name: print('是以x结尾') # 判断这是不是字符串是不是以x结尾的 name = 'alex' new_name = name.endswith('x',2,5) if new_name: print('是以x结尾') # 判断这是不是字符串是不是以x结尾的
3.6.5 统计出现的次数
name = 'alexdasx' new_name = name.count('a') print(new_name) # 统计name这个字符串中a出现的次数
3.6.6 字符串替换
name = 'alexdasx' new_name = name.replace('sx','sb',1) print(new_name) # 替换字符串中的内容以外 我们可以指定要替换的次数
3.6.7 去除头尾两边的空格 换行符/制表符
name = ' alexdasx ' new_name = name.strip() # 默认去除的头尾两端的空格,换行符,制表符 也可以自己指定 print(new_name) # 去除头尾俩遍的空格和换行符
3.6.8 分割
name = 'alexdasx' new_name = name.split("x") # 默认以空格,换行符,制表符分隔 print(new_name) # 也可以通过x将字符串name进行切割 name = 'alexdasx' new_name = name.split("x",maxsplit=1) # 默认以空格,换行符,制表符分隔 print(new_name) # 切割后的内容是一个列表
3.6.9 字符串格式化
name = 'alexdasx{}' new_name = name.format('说的对') print(new_name) # 字符串格式化 # 可以按照顺序去填充,也可以按照索引去填充,也可以按照关键字填充
练习
将 name 变量对应的值中所有的"l"替换为 "p",并输出结果 将name变量对应的值中的第一个"l"替换成"p",并输出结果 将 name 变量对应的值根据所有的"l"进行分割,并输出结果。 将name变量对应的值根据第一个"l"分割,并输出结果。 请输出 name 变量对应的值的第2个字符? 请输出 name 变量对应的值的前3个字符? 请输出 name 变量对应的值的后2个字符?
3.6.10 is系列
判断是不是十进制的数字,返回的是布尔值
name = 'alexdasx' new_name = name.isdecimal() print(new_name) # 判断是不是十进制的数字,返回结果是布尔值
判断是不是数字和字母以及中文,返回的是布尔值
name = 'alex7dasx' new_name = name.isalnum() print(new_name) # 判断是不是数字和字母以及中文,返回结果是布尔值
判断是不是字母和汉字,返回的是布尔值
name = 'alexdasx' new_name = name.isalpha() print(new_name) # 判断是不是纯字母和汉字,返回的是布尔值
练习
输⼊⼀个字符串,要求判断在这个字符串中⼤写字⺟,⼩写字⺟,数字, 其它字符共出现了多少次,并输出
3.6.11 获取长度
目前我们写的内容还比较少,如果有一个很长很长的字符串,要是一个一个数是不是很累呢,Python给咱们提供了一个方法就是获取长度的.
长度获取示例:
name = "Meet" print(len(name)) 结果: 4 这里需要大家清楚一点的就是,len是一个公共的方法,不是字符串的方法只有用字符串.的方式使用的才是字符串的方法
我们现在能够轻松的获取到到长度,现在我有这么一个字符串 name = "Meet",请使用while循环获取到字符串中每个元素并打印,效果图入下:
3.6.12 使用while循环打印单个元素
name = "Meet" count = 0 while count < len(name): print(name[count]) count = count + 1
以上这个写法是没有问题的,你们来看下边这个会不会简单些
3.6.13 for循环
name = "Meet" for i in name: print(i) for i in "Meet": print(i)
上边的这个东西叫做for循环, for是关键字 i是一个变量 后边的name是要被循环的内容,他会把这个name指向的字符串里的每个元素打印出来
for循环是可以循环的数据结构:
-
字符串(str)
-
列 表(list)
-
元 祖(tuple)
-
字 典(dict)
-
集 合(set)
唯独不能进行循环的就是 整型(int)和布尔值(bool)
第四章
Python基础数据类型二
一.列表
列表是python的基础数据类型之一 ,其他编程语言也有类似的数据类型.
比如js中的数 组, java中的数组等等. 它是以[ ]括起来, 每个元素用' , '隔开而且可以存放各种数据类型:
列表是python中的基础数据类型之一,其他语言中也有类似于列表的数据类型,比如js中叫数组,他是以[]括起来,每个元素以逗号隔开,而且他里面可以存放各种数据类型比如:
li = [‘alex’,123,Ture,(1,2,3,’wusir’),[1,2,3,’小明’,]]
列表相比于字符串,不仅可以储存不同的数据类型,而且可以储存大量数据,
32位python的限制是 536870912 个元素,
64位python的限制是 1152921504606846975 个元素。而且列表是有序的,有索引值,可切片,方便取值。
lst = [1,2,3,"alex",[1,2,3]]
我们定义了一个列表,我们现在来看看内存中是怎样存储的
lst我们就可以想象成一个书包,书包中存放了衣服,电脑,书,钱包,钱包中放的是身份证,公交卡,家门钥匙
1.1 列表的索引
列表和字符串一样也拥有索引:
lst = ['刘德华','周润发','周杰伦','向华强'] print(lst[0]) # 列表中第一个元素 print(lst[1]) # 列表中第二个元素 print(lst[2]) # 列表中第三个元素
注意:列表是可以进行修改的,这里和字符串不一样
lst[3] = '王健林' print(lst)
字符串修改
s = '王思聪' s[0] = '李' print(s) 结果: Traceback (most recent call last): File "D:/python_object/path2/test.py", line 1076, in <module> s[0] = '李' TypeError: 'str' object does not support item assignment
1.2 列表的切片
lst = ["麻花藤", "王剑林", "马芸", "周鸿医", "向华强"] print(lst[0:3]) # ['麻花藤', '王剑林', '马芸'] print(lst[:3]) # ['麻花藤', '王剑林', '马芸'] print(lst[1::2]) # ['王剑林', '周鸿医'] 也有步长 print(lst[2::-1]) # ['马芸', '王剑林', '麻花藤'] 也可以倒着取 print(lst[-1:-3:-2]) # 倒着带步长
练习
li = [1, 3, 2, "a", 4, "b", 5,"c"] 通过对li列表的切片形成新的列表l1,l1 = [1,3,2] 通过对li列表的切片形成新的列表l2,l2 = ["a",4,"b"] 通过对li列表的切片形成新的列表l3,l3 = ["1,2,4,5] 通过对li列表的切片形成新的列表l4,l4 = [3,"a","b"] 通过对li列表的切片形成新的列表l5,l5 = ["c"] 通过对li列表的切片形成新的列表l6,l6 = ["b","a",3]
1.3 列表的增删改查
1.3.1 增
注意 list和str是不一样的. lst可以发生改变. 所以直接就在原来的对象上进行了操作
追加模式
lst = ["麻花藤", "林俊杰", "周润发", "周芷若"] print(lst) lst.append("wusir") print(lst)
练习
输入用户信息,添加到列表中
lst = [] while True: content = input("请输入你要录入的员工信息, 输入Q退出:") if content.upper() == 'Q': break lst.append(content) print(lst)
插入模式
lst = ["麻花藤", "张德忠", "孔德福"] lst.insert(1, "刘德华") # 在1的位置插入刘德华. 原来的元素向后移动一位 print(lst)
迭代添加
# 迭代添加 lst = ["王志文", "张一山", "苦海无涯"] lst.extend(["麻花藤", "麻花不疼"]) print(lst)
1.3.2 删除
pop 通过下标删除元素(默认删除最后一个) lst = ["麻花藤", "王剑林林", "李李嘉诚", "王富贵"] print(lst) lst.pop() deleted = lst.pop() print('被删除的',deleted) print(lst) el = lst.pop(2) # 删除下标位2的元素 print(el) # 被删除的元素 print(lst)
remove 通过元素删除
lst = ["麻花藤", "王剑林", "李嘉诚", "王富贵"] lst.remove('王剑林') print(lst) 结果: ['麻花藤', '李嘉诚', '王富贵'] lst.remove('哈哈') # 删除不存在的元素 结果: Traceback (most recent call last): File "D:/python_object/path2/test.py", line 1115, in <module> lst.remove('哈哈') # 删除不存在的元素 ValueError: list.remove(x): x not in list
clear 清空
lst = ["麻花藤", "王剑林", "李嘉诚", "王富贵"] lst.clear() print(lst) 结果: []
练习
写代码,有如下列表,按照要求实现每一个功能 li = ["alex", "WuSir", "ritian", "barry", "wenzhou"] 请删除列表中的元素"ritian",并输出添加后的列表 请删除列表中的第2个元素,并输出删除的元素和删除元素后的列表 请删除列表中的第2至4个元素,并输出删除元素后的列表
1.3.3 修改
索引切片修改
# 修改 lst = ["太白", "太黑", "五色", "银王", "⽇天"] lst[1] = "太污" # 把1号元素修改成太污 print(lst) lst[1:4:3] = ["麻花藤", "哇靠"] # 切片修改也OK. 如果步长不是1, 要注意元素的数 print(lst) lst[1:4] = ["我是哪个村的村长王富贵"] # 如果切片没有步长或者步长是1. 则不用关心个数 print(lst)
1.3.4 查询
列表是一个可迭代对象,所以可以进行for循环 lst = ["麻花藤", "王剑林", "李嘉诚", "王富贵"] for i in lst: print(i) 结果: 麻花藤 王剑林 李嘉诚 王富贵
练习
li = ["alex", "WuSir", "ritian", "barry", "wenzhou"] 将列表li中第三个元素修改成'taibai' 将列表li中第四个元素修改成'女神' 将列表li中前三个元素修改成'alex1,alex2,alex3'
1.4 列表的嵌套
注意:采用降维操作,一层一层的看就好
lst = [1,'太白','wusir',['麻花疼',['可口可乐'],'王健林']] # 找到wusir print(lst[2]) # 找到太白和wusir print(lst[1:3]) # 找到太白的白字 print(lst[1][1]) # 将wusir拿到,然后首字母大写 在扔回去 s = lst[2] s = s.capitalize() lst[2] = s print(lst) # 简写 lst[2] = lst[2].capitalize() print(lst) # 把太白换成太黑 lst[1] = lst[1].replace('白','黑') # 把麻花疼换成麻花不疼 lst[3][0] = lst[3][0].replace('疼','不疼') print(lst) # 在可口可乐后边添加一个雪碧 lst[3][1].append('雪碧') print(lst)
练习
写代码,有如下列表,按照要求实现每一个功能。 lis = [2, 3, "k", ["qwe", 20, ["k1", ["tt", 3, "1"]], 89], "ab", "adv"] 将列表lis中的"tt"变成大写(用两种方式)。 将列表中的数字3变成字符串"100"(用两种方式)。 将列表中的字符串"1"变成数字101(用两种方式)。
这个知识点用在什么地方:
你需要存储大量的数据,且需要这些数据有序的时候。制定一些特殊的数据群体:按顺序,按规则,自定制设计数据
二.元祖
1.对于容器型数据类型list,无论谁都可以对其增删改查,那么有一些重要的数据放在list中是不安全的,所以需要一种容器类的数据类型存放重要的数据,创建之初只能查看而不能增删改,这种数据类型就是元祖。
元祖:俗称不可变的列表,又被成为只读列表,元祖也是python的基本数据类型之一,
用小括号括起来,里面可以放任何数据类型的数据,查询可以,循环也可以,切片也可以.但就是不能改.在python中关键字是tuple
tu = ('我','怎么','这么','可爱') tu1 = tu[0] # 记性下标 print(tu1) for i in tu: print(i) # 进行for循环 tu2 = tu[0:3] print(tu2) # 进行切片 结果: Traceback (most recent call last): File "D:/python_object/path2/test.py", line 1286, in <module> tu[0] = '你' NameError: name 'tu' is not defined
关于不可变, 注意: 这里元组的不可变的意思是子元素不可变. 而子元素内部的子元素是可以变, 这取决于子元素是否是可变对象.
元组中如果只有一个元素. 一定要添加一个逗号, 否则就不是元组
tu = ('meet') print(type(tu)) #type是查看数据类型 结果: <class:str> tu = ('meet',) print(type(tu)) #type是查看数据类型 结果: <class:tuple>
这个知识点如何使用
1.可遍历
2.可切片
3.有len,count,index方法
2.1 元祖嵌套
tu = ('今天姐姐不在家','姐夫和小姨子在客厅聊天',('姐夫问小姨子税后多少钱','小姨子低声说道说和姐夫还提钱')) tu1 = tu[0] tu2 = tu[1] tu3 = tu[2][0] tu4 = tu[2][1] print(tu1) print(tu2) print(tu3) print(tu4) 结果: 今天姐姐不在家 姐夫和小姨子在客厅聊天 姐夫问小姨子税后多少钱 小姨子低声说道说和姐夫还提钱
在哪里使用
就是将一些非常重要的不可让人改动的数据放在元祖中,只供查看。后期你们写项目的时候会有配置文件,配置文件中的不想让人修改的单个变量使用常量,如果是多个不想让人修改的就是用元组来存储
三.range
翻译过来就是范围,那我们我来先看下.
range(0,5,1) 参数第一个是范围的起始位置 参数第二个是范围的结束位置 参数第三个是步长 print(range(0,5)) # 结果: range(0, 5) #一个范围 # 我们可以通过list方法来转换这个范围来查看一下 l = list(range(0,5)) print(l) # 结果: [0, 1, 2, 3, 4] l = list(range(0,5,2)) print(l) # 结果: [0, 2, 4] # 这个结果就会发现和我之前用步长获取的内容是相似的,是的他就是步长
练习
利用for循环和range打印出下面列表的索引。 li = ["alex", "WuSir", "ritian", "barry", "wenzhou"] 利用for循环和range找出100以内所有的偶数并将这些偶数插入到一个新列表中 利用for循环和range 找出50以内能被3整除的数,并将这些数插入到一个新列表中 利用for循环和range从100~1,倒序打印 利用for循环和range,将1-30的数字一次添加到一个列表中,并循环这个列表,将能被3整除的数改成*。
第五章
Python基础数据类型三
一.字典
列表可以存储大量的数据类型,但是只能按照顺序存储,数据与数据之间关联性不强。
所以咱们需要引入一种容器型的数据类型,解决上面的问题,这就需要dict字典。
字典(dict)是python中唯⼀的⼀个映射类型.他是以{ }括起来的键值对组成.
在dict中key是 唯⼀的. 在保存的时候, 根据key来计算出⼀个内存地址. 然后将key-value保存在这个地址中.
这种算法被称为hash算法, 所以, 切记, 在dict中存储的key-value中的key必须是可hash的
可以改变的都是不可哈希的, 那么可哈希就意味着不可变. 这个是为了能准确的计算内存地址⽽规定的.
已知的可哈希(不可变)的数据类型: int, str, tuple, bool 不可哈希(可变)的数据类型: list, dict, set
语法:{'key1':1,'key2':2}
注意: key必须是不可变(可哈希)的. value(值)没有要求.可以保存任意类型的数据
/
# 合法 dic = {123: 456, True: 999, "id": 1, "name": 'sylar', "age": 18, "stu": ['帅 哥', '美⼥'], (1, 2, 3): '麻花藤'} print(dic[123]) print(dic[True]) print(dic['id']) print(dic['stu']) print(dic[(1, 2, 3)]) # 不合法 # dic = {[1, 2, 3]: '周杰伦'} # list是可变的. 不能作为key # dic = {{1: 2}: "哈哈哈"} # dict是可变的. 不能作为key dic = {{1, 2, 3}: '呵呵呵'} # set是可变的, 不能作为key
注意:dict保存的数据不是按照我们添加进去的顺序保存的. 是按照hash表的顺序保存的. ⽽hash表 不是连续的. 所以不能进⾏切片⼯作. 它只能通过key来获取dict中的数据
1.1 字典增删改查
1.1.1 增
dic = {} dic['name'] = '汪峰' dic['age'] = 18 print(dic) 结果: {'name': '汪峰', 'age': 18} # 如果dict中没有出现这个key,就会将key-value组合添加到这个字典中 # 如果dict中没有出现过这个key-value. 可以通过setdefault设置默认值 s1 = dic.setdefault('王菲') print(s1) print(dic) 结果: None # 返回的是添加进去的值 {'王菲': None} # 我们使用setdefault这个方法 里边放的这个内容是我们字典的健,这样我们添加出来的结果 就是值是一个None dic.setdefault('王菲',歌手) # 这样就是不会进行添加操作了,因为王菲在dic这个字典中存在 # 总结: 当setdefault中第一个参数存在这个字典中就就不进行添加操作,否则就添加 dic1 = {} s2 = dic1.setdefault('王菲','歌手') print(s2) print(dic1) 结果: 歌手 {'王菲': '歌手'}
1.1.2删
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} s = dic.pop('哈啥给') # pop删除有返回值,返回的是被删的值 print(s) print(dic) # 打印删除后的字典 dic.popitem() # 随机删除 python3.6是删除最后一个 print(dic) dic.clear() # 清空
1.1.3改
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} dic['哈啥给'] = '剑姬' # 当哈哈给是字典中的健这样写就是修改对应的值,如果不存在就是添加 print(dic) dic.update({'key':'v','哈啥给':'剑姬'}) # 当update中的字典里没有dic中键值对就添加到dic字典中,如果有就修改里边的对应的值 print(dic)
1.1.4 查
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} s = dic['大宝剑'] #通过健来查看,如果这个健不在这个字典中.就会报错 print(s) s1 = dic.get('剑圣') #通过健来查看,如果这个健不在这个字典中.就会返回None print(s1) s2 = dic.get('剑姬','没有还查你是不是傻') # 我们可以在get查找的时候自己定义返回的结果 print(s2)
练习
dic = {'k1': "v1", "k2": "v2", "k3": [11,22,33]} 请在字典中添加一个键值对,"k4": "v4",输出添加后的字典 请在修改字典中 "k1" 对应的值为 "alex",输出修改后的字典 请在k3对应的值中追加一个元素 44,输出修改后的字典 请在k3对应的值的第 1 个位置插入个元素 18,输出修改后的字典
1.2 字典其他操作
1.2.1 获取字典所有的键
key_list = dic.keys() print(key_list) 结果: dict_keys(['剑圣', '哈啥给', '大宝剑']) # 一个高仿列表,存放的都是字典中的key
1.2.2 获取字典所有的值
value_list = dic.values() print(value_list) 结果: dict_values(['易', '剑豪', '盖伦']) #一个高仿列表,存放都是字典中的value
1.2.3 获取字典的键值对
key_value_list = dic.items() print(key_value_list) 结果: dict_items([('剑圣', '易'), ('哈啥给', '剑豪'), ('大宝剑', '盖伦')]) # 一个高仿列表,存放是多个元祖,元祖中第一个是字典中的键,第二个是字典中的值
练习
循环打印字典的值
循环打印字典的键
循环打印元祖形式的键值对
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} for i in dic: print(i) for i in dic.keys(): print(i)
循环打印字典中的键
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} for i in dic: print(dic[i]) for i in dic.values(): print(i)
循环打印字典中的值和值
dic = {'剑圣':'易','哈啥给':'剑豪','大宝剑':'盖伦'} for i in dic.items(): print(i)
循环打印元祖形式的键值对
1.3 解构
a,b = 1,2 print(a,b) 结果: 1 2 a,b = ('你好','世界') print(a,b) 结果: 你好 世界 a,b = ['你好','大飞哥'] print(a,b) 结果: 你好 世界 a,b = {'汪峰':'北京北京','王菲':'天后'} print(a,b) 结果: 汪峰 王菲
解构可以将内容分别赋值到变量当中,我们使用解构就能够快速的将值使用
1.3.1 循环字典获取键和值
for k,v in dic.items(): print('这是键',k) print('这是值',v) 结果: 这是键 剑圣 这是值 易 这是键 哈啥给 这是值 剑豪 这是键 大宝剑 这是值 盖伦
1.4 字典的嵌套
dic = { 'name':'汪峰', 'age':48, 'wife':[{'name':'国际章','age':38}], 'children':['第一个熊孩子','第二个熊孩子'] }
获取汪峰的妻子名字
d1 = dic['wife'][0]['name'] print(d1)
获取汪峰的孩子们
d2 = dic['children'] print(d2)
获取汪峰的第一个孩子
d3 = dic['children'][0] print(d3)
练习
dic1 = { 'name':['alex',2,3,5], 'job':'teacher', 'oldboy':{'alex':['python1','python2',100]} } 1,将name对应的列表追加⼀个元素’wusir’。 2,将name对应的列表中的alex⾸字⺟⼤写。 3,oldboy对应的字典加⼀个键值对’⽼男孩’,’linux’。 4,将oldboy对应的字典中的alex对应的列表中的python2删除
第六章
Python基础数据类型补充
一.数据补充
我们前期的时候和大家说了,有些方法我们放在数据类型补充的这一天中,我们今天将前面没有讲解的内容,都一起讲解了,就现从str开始
str:
1.1 首字母大写
name = "meet" name.capitalize()
1.2 每个单词的首字母大写
name = "meet" name.title()
1.3 大小写反转
name = "meet" name.swapcase()
1.4 统计
name = "meet" name.count()
1.5 查找
name = "meet" name.find()
1.6 居中
name = "meet" name.index()
list:
1.1 反转
lst = [1,2,3,4,5] lst.reverse()
1.2 排序
lst = [1,2,3,4,5] lst.sort() # 升序 lst.sort(reverse=True) # 降序
1.3 查找
lst = [1,2,3,4,5] lst.finde(3) # 存在返回索引,不存在就返回-1 lst.index(3) # 存在就返回索引,不存在就报错
1.4 统计
lst = [1,23,4,5,6,] lst.count(23) # 统计23出现的次数
dict:
1.1 随机删除
dic = {"key":"value","key2":"value2"} dic.popitem() # 随机删除
1.2 批量创建字典
dic = {} dic1 = dic.fromkeys("abc",[1,2]) print(dic1)
formkeys这个是个坑,坑点就在于值是可变数据类型的时候,当第一个键对应的值进行修改了以后,其余的键对应的值也都跟着进行改变了. 如何避免坑,就是批量创建字典的时候值不能使用可变的数据类型.
最后我们对我们学习的这些数据类型进行一个总结,我们按照有序,无序,可变,不可变,取值方式来总结
-
有序:
-
数字
-
字符串
-
列表
-
元组
-
-
无序:
-
字典
-
集合
-
-
可变数据类型:
-
列表
-
字典
-
集合
-
-
不可变数据类型:
-
字符串
-
数字
-
元组
-
-
取值顺序:
-
直接取值 — 数字,布尔值,集合
-
顺序取值(可以索引) — 列表,元组,字符串
-
通过键取值 — 字典
-
类型转换
元组 => 列表 list(tuple) 列表 => 元组 tuple(list) list=>str str.join(list) str=>list str.split() 转换成False的数据: 0,'',None,[],(),{},set() ==> False
二.以后要遇到的坑
2.1 循环添加
lst = [1,2,3,4,5,6] for i in lst: lst.append(7) # 这样写法就会一直持续添加7 print(lst) print(lst)
2.2 列表循环删除
列表: 循环删除列表中的每⼀个元素
li = [11, 22, 33, 44] for e in li: li.remove(e) print(li) 结果: [22, 44]
分析原因: for的运⾏过程. 会有⼀个指针来记录当前循环的元素是哪⼀个, ⼀开始这个指针指向第0 个.
然后获取到第0个元素. 紧接着删除第0个. 这个时候. 原来是第⼀个的元素会⾃动的变成 第0个.
然后指针向后移动⼀次, 指向1元素. 这时原来的1已经变成了0, 也就不会被删除了.
⽤pop删除试试看:
li = [11, 22, 33, 44] for i in range(0, len(li)): del li[i] print(li) 结果: 报错 # i= 0, 1, 2 删除的时候li[0] 被删除之后. 后⾯⼀个就变成了第0个. # 以此类推. 当i = 2的时候. list中只有⼀个元素. 但是这个时候删除的是第2个 肯定报错啊
经过分析发现. 循环删除都不⾏. 不论是⽤del还是⽤remove. 都不能实现. 那么pop呢?
for el in li: li.pop() # pop也不⾏ print(li) 结果: [11, 22]
2.3 列表循环删除成功
只有这样才是可以的:
for i in range(0, len(li)): # 循环len(li)次, 然后从后往前删除 li.pop() print(li) 或者. ⽤另⼀个列表来记录你要删除的内容. 然后循环删除 li = [11, 22, 33, 44] del_li = [] for e in li: del_li.append(e) for e in del_li: li.remove(e) print(li)
注意: 由于删除元素会导致元素的索引改变, 所以容易出现问题. 尽量不要再循环中直接去删 除元素. 可以把要删除的元素添加到另⼀个集合中然后再批量删除.
2.4 字典的坑
dict中的fromkey(),再次重提 可以帮我们通过list来创建⼀个dict
dic = dict.fromkeys(["jay", "JJ"], ["周杰伦", "麻花藤"]) print(dic) 结果: {'jay': ['周杰伦', '麻花藤'], 'JJ': ['周杰伦', '麻花藤']}
代码中只是更改了jay那个列表. 但是由于jay和JJ⽤的是同⼀个列表. 所以. 前⾯那个改了. 后面那个也会跟着改
2.5 字典在循环时不能修改
dict中的元素在迭代过程中是不允许进⾏删除的
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} # 删除key中带有'k'的元素 for k in dic: if 'k' in k: del dic[k] # dictionary changed size during iteration, 在循环迭 代的时候不允许进⾏删除操作 print(dic)
那怎么办呢? 把要删除的元素暂时先保存在⼀个list中, 然后循环list, 再删除
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} dic_del_list = [] # 删除key中带有'k'的元素 for k in dic: if 'k' in k: dic_del_list.append(k) for el in dic_del_list: del dic[el] print(dic)
一.二次编码
编码回顾:
ASCII : 最早的编码. ⾥⾯有英⽂⼤写字⺟, ⼩写字⺟, 数字, ⼀些特殊字符. 没有中⽂, 8个01代码, 8个bit, 1个byte
GBK: 中⽂国标码, ⾥⾯包含了ASCII编码和中⽂常⽤编码. 16个bit, 2个byte
UNICODE: 万国码, ⾥⾯包含了全世界所有国家⽂字的编码. 32个bit, 4个byte, 包含了 ASCII
UTF-8: 可变⻓度的万国码. 是unicode的⼀种实现. 最⼩字符占8位 1.英⽂: 8bit 1byte 2.欧洲⽂字:16bit 2byte 3.中⽂:24bit 3byte
综上, 除了ASCII码以外, 其他信息不能直接转换. 在python3的内存中. 在程序运⾏阶段. 使⽤的是unicode编码. 因为unicode是万国码. 什么内容都可以进⾏显⽰. 那么在数据传输和存储的时候由于unicode比较浪费空间和资源. 需要把 unicode转存成UTF-8或者GBK进⾏存储. 怎么转换呢. 在python中可以把⽂字信息进⾏编码. 编码之后的内容就可以进⾏传输了. 编码之后的数据是bytes类型的数据.其实啊. 还是原来的 数据只是经过编码之后表现形式发⽣了改变⽽已.
bytes的表现形式:
-
英⽂ b'alex' 英⽂的表现形式和字符串没什么两样
-
中⽂ b'\xe4\xb8\xad' 这是⼀个汉字的UTF-8的bytes表现形式
1.1 编码
s = "alex" print(s.encode("utf-8")) # 将字符串编码成UTF-8 print(s.encode("GBK")) # 将字符串编码成GBK 结果: b'alex' b'alex' s = "中" print(s.encode("UTF-8")) # 中⽂编码成UTF-8 print(s.encode("GBK")) # 中⽂编码成GBK 结果: b'\xe4\xb8\xad' b'\xd6\xd0'
记住: 英⽂编码之后的结果和源字符串⼀致. 中⽂编码之后的结果根据编码的不同. 编码结果也不同. 我们能看到. ⼀个中⽂的UTF-8编码是3个字节. ⼀个GBK的中⽂编码是2个字节. 编码之后的类型就是bytes类型. 在⽹络传输和存储的时候我们python是保存和存储的bytes 类型. 那么在对⽅接收的时候. 也是接收的bytes类型的数据. 我们可以使⽤decode()来进⾏解 码操作. 把bytes类型的数据还原回我们熟悉的字符串:
s = "我叫李嘉诚" print(s.encode("utf-8")) # b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a' print(b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a'.decod e("utf-8")) # 解码
1.2 解码
编码和解码的时候都需要制定编码格式.
s = "我是⽂字" bs = s.encode("GBK") # 我们这样可以获取到GBK的⽂字 # 把GBK转换成UTF-8 # ⾸先要把GBK转换成unicode. 也就是需要解码 s = bs.decode("GBK") # 解码 # 然后需要进⾏重新编码成UTF-8 bss = s.encode("UTF-8") # 重新编码 print(bss)
第七章
Python基础数据类型补充
一.数据补充
我们前期的时候和大家说了,有些方法我们放在数据类型补充的这一天中,我们今天将前面没有讲解的内容,都一起讲解了,就现从str开始
str:
1.1 首字母大写
name = "meet" name.capitalize()
1.2 每个单词的首字母大写
name = "meet" name.title()
1.3 大小写反转
name = "meet" name.swapcase()
1.4 统计
name = "meet" name.count()
1.5 查找
name = "meet" name.find()
1.6 居中
name = "meet" name.index()
list:
1.1 反转
lst = [1,2,3,4,5] lst.reverse()
1.2 排序
lst = [1,2,3,4,5] lst.sort() # 升序 lst.sort(reverse=True) # 降序
1.3 查找
lst = [1,2,3,4,5] lst.finde(3) # 存在返回索引,不存在就返回-1 lst.index(3) # 存在就返回索引,不存在就报错
1.4 统计
lst = [1,23,4,5,6,] lst.count(23) # 统计23出现的次数
dict:
1.1 随机删除
dic = {"key":"value","key2":"value2"} dic.popitem() # 随机删除
1.2 批量创建字典
dic = {} dic1 = dic.fromkeys("abc",[1,2]) print(dic1)
formkeys这个是个坑,坑点就在于值是可变数据类型的时候,当第一个键对应的值进行修改了以后,其余的键对应的值也都跟着进行改变了. 如何避免坑,就是批量创建字典的时候值不能使用可变的数据类型.
最后我们对我们学习的这些数据类型进行一个总结,我们按照有序,无序,可变,不可变,取值方式来总结
-
有序:
-
数字
-
字符串
-
列表
-
元组
-
-
无序:
-
字典
-
集合
-
-
可变数据类型:
-
列表
-
字典
-
集合
-
-
不可变数据类型:
-
字符串
-
数字
-
元组
-
-
取值顺序:
-
直接取值 — 数字,布尔值,集合
-
顺序取值(可以索引) — 列表,元组,字符串
-
通过键取值 — 字典
-
类型转换
元组 => 列表 list(tuple) 列表 => 元组 tuple(list) list=>str str.join(list) str=>list str.split() 转换成False的数据: 0,'',None,[],(),{},set() ==> False
二.以后要遇到的坑
2.1 循环添加
lst = [1,2,3,4,5,6] for i in lst: lst.append(7) # 这样写法就会一直持续添加7 print(lst) print(lst)
2.2 列表循环删除
列表: 循环删除列表中的每⼀个元素
li = [11, 22, 33, 44] for e in li: li.remove(e) print(li) 结果: [22, 44]
分析原因: for的运⾏过程. 会有⼀个指针来记录当前循环的元素是哪⼀个, ⼀开始这个指针指向第0 个.
然后获取到第0个元素. 紧接着删除第0个. 这个时候. 原来是第⼀个的元素会⾃动的变成 第0个.
然后指针向后移动⼀次, 指向1元素. 这时原来的1已经变成了0, 也就不会被删除了.
⽤pop删除试试看:
li = [11, 22, 33, 44] for i in range(0, len(li)): del li[i] print(li) 结果: 报错 # i= 0, 1, 2 删除的时候li[0] 被删除之后. 后⾯⼀个就变成了第0个. # 以此类推. 当i = 2的时候. list中只有⼀个元素. 但是这个时候删除的是第2个 肯定报错啊
经过分析发现. 循环删除都不⾏. 不论是⽤del还是⽤remove. 都不能实现. 那么pop呢?
for el in li: li.pop() # pop也不⾏ print(li) 结果: [11, 22]
2.3 列表循环删除成功
只有这样才是可以的:
for i in range(0, len(li)): # 循环len(li)次, 然后从后往前删除 li.pop() print(li) 或者. ⽤另⼀个列表来记录你要删除的内容. 然后循环删除 li = [11, 22, 33, 44] del_li = [] for e in li: del_li.append(e) for e in del_li: li.remove(e) print(li)
注意: 由于删除元素会导致元素的索引改变, 所以容易出现问题. 尽量不要再循环中直接去删 除元素. 可以把要删除的元素添加到另⼀个集合中然后再批量删除.
2.4 字典的坑
dict中的fromkey(),再次重提 可以帮我们通过list来创建⼀个dict
dic = dict.fromkeys(["jay", "JJ"], ["周杰伦", "麻花藤"]) print(dic) 结果: {'jay': ['周杰伦', '麻花藤'], 'JJ': ['周杰伦', '麻花藤']}
代码中只是更改了jay那个列表. 但是由于jay和JJ⽤的是同⼀个列表. 所以. 前⾯那个改了. 后面那个也会跟着改
2.5 字典在循环时不能修改
dict中的元素在迭代过程中是不允许进⾏删除的
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} # 删除key中带有'k'的元素 for k in dic: if 'k' in k: del dic[k] # dictionary changed size during iteration, 在循环迭 代的时候不允许进⾏删除操作 print(dic)
那怎么办呢? 把要删除的元素暂时先保存在⼀个list中, 然后循环list, 再删除
dic = {'k1': 'alex', 'k2': 'wusir', 's1': '⾦⽼板'} dic_del_list = [] # 删除key中带有'k'的元素 for k in dic: if 'k' in k: dic_del_list.append(k) for el in dic_del_list: del dic[el] print(dic)
一.二次编码
编码回顾:
ASCII : 最早的编码. ⾥⾯有英⽂⼤写字⺟, ⼩写字⺟, 数字, ⼀些特殊字符. 没有中⽂, 8个01代码, 8个bit, 1个byte
GBK: 中⽂国标码, ⾥⾯包含了ASCII编码和中⽂常⽤编码. 16个bit, 2个byte
UNICODE: 万国码, ⾥⾯包含了全世界所有国家⽂字的编码. 32个bit, 4个byte, 包含了 ASCII
UTF-8: 可变⻓度的万国码. 是unicode的⼀种实现. 最⼩字符占8位 1.英⽂: 8bit 1byte 2.欧洲⽂字:16bit 2byte 3.中⽂:24bit 3byte
综上, 除了ASCII码以外, 其他信息不能直接转换. 在python3的内存中. 在程序运⾏阶段. 使⽤的是unicode编码. 因为unicode是万国码. 什么内容都可以进⾏显⽰. 那么在数据传输和存储的时候由于unicode比较浪费空间和资源. 需要把 unicode转存成UTF-8或者GBK进⾏存储. 怎么转换呢. 在python中可以把⽂字信息进⾏编码. 编码之后的内容就可以进⾏传输了. 编码之后的数据是bytes类型的数据.其实啊. 还是原来的 数据只是经过编码之后表现形式发⽣了改变⽽已.
bytes的表现形式:
-
英⽂ b'alex' 英⽂的表现形式和字符串没什么两样
-
中⽂ b'\xe4\xb8\xad' 这是⼀个汉字的UTF-8的bytes表现形式
1.1 编码
s = "alex" print(s.encode("utf-8")) # 将字符串编码成UTF-8 print(s.encode("GBK")) # 将字符串编码成GBK 结果: b'alex' b'alex' s = "中" print(s.encode("UTF-8")) # 中⽂编码成UTF-8 print(s.encode("GBK")) # 中⽂编码成GBK 结果: b'\xe4\xb8\xad' b'\xd6\xd0'
记住: 英⽂编码之后的结果和源字符串⼀致. 中⽂编码之后的结果根据编码的不同. 编码结果也不同. 我们能看到. ⼀个中⽂的UTF-8编码是3个字节. ⼀个GBK的中⽂编码是2个字节. 编码之后的类型就是bytes类型. 在⽹络传输和存储的时候我们python是保存和存储的bytes 类型. 那么在对⽅接收的时候. 也是接收的bytes类型的数据. 我们可以使⽤decode()来进⾏解 码操作. 把bytes类型的数据还原回我们熟悉的字符串:
s = "我叫李嘉诚" print(s.encode("utf-8")) # b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a' print(b'\xe6\x88\x91\xe5\x8f\xab\xe6\x9d\x8e\xe5\x98\x89\xe8\xaf\x9a'.decod e("utf-8")) # 解码
1.2 解码
编码和解码的时候都需要制定编码格式.
s = "我是⽂字" bs = s.encode("GBK") # 我们这样可以获取到GBK的⽂字 # 把GBK转换成UTF-8 # ⾸先要把GBK转换成unicode. 也就是需要解码 s = bs.decode("GBK") # 解码 # 然后需要进⾏重新编码成UTF-8 bss = s.encode("UTF-8") # 重新编码 print(bss)
Python基础-文件操作
一.只读
有如下文件,但是没有相应的软件打开,想不想看?
美女 模特 空姐 护士 联系方式 .txt 让你开发一个软件,可以打开此文件,你需要什么参数? 文件路径: D:\美女模特空姐护士联系方式 .txt 编码:utf-8,gbk,gb2312.... 模式:只读,只写,追加,写读,读写....
使用Python来读写文件是非常简单的操作,我们使用open()来打开一个文件,获取到文件句柄,然后通过文件句柄就可以进行各种各样的操作了
根据打开方式的不同能够执行的操作会有相应的差异.
打开文件的方式:
r,w,a
r+,w+,a+
rb, wb, ab
r+b,w+b,a+b
1.1 r模式
我们先来看看这个 只读操作 ,听名字应该也知道只能进行读不能进行别的操作
f = open('美女模特空姐护士联系方式.txt',mode='r',encoding='utf-8')
content = f.read()
print(content)
f.close()
结果:
标题很好
open中第一个参数放入的是要打开的文件名字,第二个参数是要对这个文件进行的操作,第三参数是用什么编码方式打开文件中的内容
f 可写成任意变量等,它被称作:文件句柄,文件操作符,或者文件操作对象等。 open 是Python调用的操作系统(windows,linux,等)的功能,而windows的默认编码方式为gbk,linux默认编码方式为utf-8,所以你的文件用什么编码保存的,就用什么方法打开,一般都是用utf-8。 mode为打开方式:常见的有r,w,a,r+,w+,a+.rb,wb,ab,等,默认不写是r。 流程就是打开文件,产生一个文件句柄,对文件句柄进行相应操作,关闭文件。
1.2 rb 模式
我们试了r只读,再看看rb只读字节的模式
f = open('护士少妇萝莉',mode='rb')
content = f.read()
print(content)
f.close()
结果:
b'\xe6\xa0\x87\xe9\xa2\x98\xe5\xbe\x88\xe5\xa5\xbd'
rb 读出来的数据是bytes类型,在rb模式下,不能encoding字符集
rb的作用:在读取非文本文件的时候,比如要读取mp3,图像,视频等信息的时候就需要用到rb,因为这种数据是没办法直接显示出来的
这个字节的模式是用于传输和存储
好了,我们动手试一试.读文件吧,你们如果是在windows系统中用记事本创建的文件,在打开的时候会有问题,
是因为windows系统中记事本中写的内容编码是GBK我们要不将文件中编码改成utf-8 要不在open中第三个参数 中
修改成utf-8
二.路径
2.1 相对路劲
以上代码中文件名使用的是相对路劲,什么是相对路劲呢???
就是相对于某个文件来说,进行路劲查找,以上代码是相对于运行的文件.
如果相对还是太理解,来看下这列子:
你朋友要来找你,知道你在那个楼那一层但是不知道那个一个屋,现在你朋友来到这个楼里相对他知道的这一层然后开始找你在那个房间
这种操作就是相对路劲,例子中是通过这个楼中的这一层开始寻找,也就是相对于这个楼的某一层
2.2 绝对路径
绝对路径是从咱们电脑的磁盘开始查找,例如想要知道你的家乡是哪的,你身份证上写的住址就是绝对路径
绝对路径:从根目录下开始一直到文件名。
相对路径:同一个文件夹下面的文件,直接写文件名就可以。
我们在使用绝对路径的时候因为有这样程序是不能识别的,解决方法:
open('C:\Users\Meet') #这样程序是不识别的
解决方法一:
open('C:\\Users\\Meet') #这样就成功的将\进行转义 两个\\代表一个\
解决方法二:
open(r'C:\Users\Meet') #这样相比上边的还要省事,在字符串的前面加个小r也是转义的意思
我更推荐大家使用相对路劲,因为我把这个程序的整个文件发给你的时候,就可以运行,如果使用绝对路径还需要额外的拷贝外部文件给你
三.读操作
3.1 read()
read()是将文件中所有的内容都读取
f = open('path1/小娃娃.txt',mode='r',encoding='utf-8')
msg = f.read()
f.close()
print(msg)
结果:
高圆圆
刘亦菲
张柏芝
杨紫
王菲
read()可以指定我们想要读取的内容数量
f = open('path1/小娃娃.txt',mode='r',encoding='utf-8')
msg = f.read(3) #读取三个字符
msg1 = f.read() #后边在读就会继续向后读取
f.close()
print(msg)
print(msg1)
结果:
高圆圆
刘亦菲
张柏芝
杨紫
王菲
上边现在使用的是r模式这样读取的就是文字,如果使用rb模式读取出来的就是字节
f = open('path1/小娃娃.txt',mode='rb')
msg = f.read(3)
msg1 = f.read()
f.close()
print(msg)
print(msg1)
结果:
b'\xe9\xab\x98'
b'\xe5\x9c\x86\xe5\x9c\x86\r\n\xe5\x88\x98\xe4\xba\xa6\xe8\x8f\xb2\r\n\xe5\xbc\xa0\xe6\x9f\x8f\xe8\x8a\x9d\r\n\xe6\x9d\xa8\xe7\xb4\xab\r\n\xe7\x8e\x8b\xe8\x8f\xb2'
read()的弊端就是当文件很大的时候,将文件中的内容全部读取,存放在内存中这样会导致内存奔溃
3.2 readline()
readline()读取每次只读取一行,注意点:readline()读取出来的数据在后面都有一个\n
f = open('path1/小娃娃.txt',mode='r',encoding='utf-8')
msg1 = f.readline()
msg2 = f.readline()
msg3 = f.readline()
msg4 = f.readline()
f.close()
print(msg1)
print(msg2)
print(msg3)
print(msg4)
结果:
高圆圆
刘亦菲
张柏芝
杨紫
结果这里每个一内容中间都有一个空行是因为咱们读取的内容后边都带有一个\n print()的时候会将这个\n执行
f = open('path1/小娃娃.txt',mode='r',encoding='utf-8') msg1 = f.readline().strip() msg2 = f.readline().strip() msg3 = f.readline().strip() msg4 = f.readline().strip() f.close() print(msg1) print(msg2) print(msg3) print(msg4) 结果: 高圆圆 刘亦菲 张柏芝 杨紫
解决这个问题只需要在我们读取出来的文件后边加一个strip()就OK了
3.3 readlines()
readlines() 将每一行形成一个元素,放到一个列表中,将所以的内容全部读出来,如果文件很大,占内存,容易崩盘。
f = open('log',encoding='utf-8') print(f.readlines()) f.close() # 结果['666666\n', 'fkja l;\n', 'fdkslfaj\n', 'dfsflj\n', 'df;asdlf\n', '\n', ]
如果有个较大的文件我们进行读取不推荐使用以下方法:
f = open('../path1/弟子规',mode='r',encoding='utf-8') # 此处的../是返回到父目录 print(f.read()) #这样就是将文件一次性全部读取到内存中,内存容易奔溃
推荐使用的是这种方法:
f = open('../path1/弟子规',mode='r',encoding='utf-8') for line in f: print(line) #这种方式就是在一行一行的进行读取,它就执行了下边的功能 print(f.readline()) print(f.readline()) print(f.readline()) print(f.readline()) f.close()
注意点:读完的文件句柄一定要关闭
四.写模式
4.1 覆盖写
在写文件的时候我们要养成一个写完文件就刷新的习惯. 刷新flush()
f = open('../path1/小娃娃.txt',mode='w',encoding='utf-8') f.write('太白很白') f.flush() f.close() 结果: 当我选择使用w模式的时候,在打开文件的时候就就会把文件中的所有内容都清空,然后在操作
注意点:如果文件不存在使用w模式会创建文件,文件存在w模式是覆盖写,在打开文件时会把文件中所有的内容清空.
f1 = open('../path1/小娃娃.txt',mode='r',encoding='utf-8') msg = f1.read() print(msg) # 这个是先查看小娃娃文件中有哪些内容 f = open('../path1/小娃娃.txt',mode='w',encoding='utf-8') f.write('太白很白') f.flush() f.close() # 这个是对小娃娃文件进行覆盖写操作 f1 = open('../path1/小娃娃.txt',mode='r',encoding='utf-8') msg = f1.read() print(msg) # 查看覆盖写后的内容
尝试读一读
f1 = open('../path1/小娃娃.txt',mode='w',encoding='utf-8') msg = f1.read() print(msg) 结果: Traceback (most recent call last): File "D:/python_object/path2/test.py", line 563, in <module> msg = f1.read() io.UnsupportedOperation: not readable #模式是w,不可以执行读操作
wb模式下,不可以指定打开文件的编辑,但是写文件的时候必须将字符串转换成utf-8的bytes数据
f = open('../path1/小娃娃.txt',mode='wb') msg = '你好'.encode('utf-8') f.write(msg) f.flush() # 刷新 f.close()
4.2 追加
只要是a或者ab,a+都是在文件的末尾写入,不论光标在任何位置.
在追加模式下,我们写入的内容后追加在文件的末尾
a模式如果文件不存在就会创建一个新文件
f1 = open('../path1/小娃娃.txt',mode='a',encoding='utf-8') msg = f1.write('这支烟灭了以后')
ab这个模式,自己试一下 没有什么太大的差别
五.读写模式
对于读写模式,必须是先读后写,因为光标默认在开头位置,当读完了以后再进行写入.我们以后使用频率最高的模式就是r+
5.1 r+模式
看下正确的操作:
f1 = open('../path1/小娃娃.txt',mode='r+',encoding='utf-8') msg = f1.read() f1.write('这支烟灭了以后') f1.flush() f1.close() print(msg) 结果: 正常的读取之后,写在结尾
看下错误的操作:
f1 = open('../path1/小娃娃.txt',mode='r+',encoding='utf-8') f1.write('小鬼') msg = f1.read() f1.flush() f1.close() print(msg) 结果: 这样写会把小鬼写在开头,并且读出来的是小鬼之后的内容
r+模式一定要记住是先读后写
5.2 r+模式坑
深坑请注意:在r+模式下. 如果读取了内容. 不论读取内容多少. 光标显示的是多少. 再写入或者操作
文件的时候都是在结尾进行的操作.
六.写读模式
先将所有的内容清空,然后写入.最后读取.但是读取的内容是空的,不常用
f1 = open('../path1/小娃娃.txt',mode='w+',encoding='utf-8') f1.write('小鬼') msg = f1.read() f1.flush() f1.close() print(msg)
有人说,先读在写不就行了.w+模式下 其实和w模式一样,把文件清空了,在写的内容.所以很少人用
追加读(a+,a+b)
a+模式下,不论是先读还是后读,都是读不到数据的
f = open('../path1/小娃娃.txt',mode='a+',encoding='utf-8') f.write('阿***') f.flush() msg = f.read() f.close() print(msg)
还有几个带b的模式,其实就是对字节的一些操作,就不多叙述了.
七.其他相关操作
7.1 seek()
seek(n)光标移动到n位置,注意: 移动单位是byte,所有如果是utf-8的中文部分要是3的倍数
通常我们使用seek都是移动到开头或者结尾
移动到开头:seek(0,0) 可以看做成seek(0)
seek(6)这种如果是单数并且不是0的就是按照字节来移动光标
移动到结尾:seek(0,2) seek的第二个参数表示的是从哪个位置进行偏移,默认是0,表示开头,1表示当前位置,2表示结尾
f = open("小娃娃", mode="r+", encoding="utf-8") f.seek(0) # 光标移动到开头 content = f.read() # 读取内容, 此时光标移动到结尾 print(content) f.seek(0) # 再次将光标移动到开头 f.seek(0, 2) # 将光标移动到结尾 content2 = f.read() # 读取内容. 什么都没有 print(content2) f.seek(0) # 移动到开头 f.write("张国荣") # 写入信息. 此时光标在9 中文3 * 3个 = 9 f.flush() f.close() tell()
7.2 tell()
使用tell()可以帮我们获取当前光标在什么位置
f = open("小娃娃", mode="r+", encoding="utf-8") f.seek(0) # 光标移动到开头 content = f.read() # 读取内容, 此时光标移动到结尾 print(content) f.seek(0) # 再次将光标移动到开头 f.seek(0, 2) # 将光标移动到结尾 content2 = f.read() # 读取内容. 什么都没有 print(content2) f.seek(0) # 移动到开头 f.write("张国荣") # 写入信息. 此时光标在9 中⽂文3 * 3个 = 9 print(f.tell()) # 光标位置9 f.flush() f.close()
7.3 修改文件
文件修改:只能将文件中的内容读取到内存中,将信息修改完毕, 然后将源文件删除, 将新文件的名字改成老文件 的名字.
import os with open("../path1/小娃娃", mode="r", encoding="utf-8") as f1,\ open("../path1/小娃娃_new", mode="w", encoding="UTF-8") as f2: content = f1.read() new_content = content.replace("冰糖葫芦", "⼤白梨") f2.write(new_content) os.remove("../path1/小娃娃") # 删除源文件 os.rename("../path1/小娃娃_new", "小娃娃") # 重命名新文件
弊端: ⼀次将所有内容进行读取. 内存溢出. 解决方案: 一行一行的读取和操作
import os with open("小娃娃", mode="r", encoding="utf-8") as f1,\ open("小娃娃_new", mode="w", encoding="UTF-8") as f2: for line in f1: new_line = line.replace("大白梨", "冰糖葫芦") f2.write(new_line) os.remove("小娃娃") # 删除源⽂文件 os.rename("小娃娃_new", "小娃娃") # 重命名新文件
第九章
Python 初始函数
一.函数介绍
现在我们自己来实现一个len,但是不能使用len
a = "alexdsb" count = 0 for i in a: count += 1 print(count)
二.函数定义
def 是python中关键字主要用来定义函数的
len这个是函数的名字
()这个括号是个神奇的东西,咱们后边会详细盘它
: 冒号是表示咱们这个语句写完了
函数体就是有4个空格的缩进
def len(): a = "alexdsb" count = 0 for i in a: count += 1 print(count)
我们来看一下函数的定义在内存空间发生了什么:
内存开辟了一个空间,但是里边存放是代码.
三.函数的调用
使用函数名加小括号就可以调用了
写法:函数名() 这个时候函数的函数体会被执行
def len(): a = "alexdsb" count = 0 for i in a: count += 1 print(count) len() # 函数的调用
当我们调用执行的时候,才会执行func这个空间里的代码,执行的时候在开辟空间,这次是在func里边开辟的空间
现在就实现了求一次长度,我想要多求几次我就只需要
len() len() len()
直接调用定义好的函数就可以了 大家都练习一下
当我们执行完函数后,函数里开辟的空间就销毁了,
我们如果想用函数里的值就需要从函数中传递出来
四. 函数的返回值
def yue(): print("掏出手机") print("打开默默") print("约一个妹子") yue()
使用return来返回结果
def yue(): print("约你") print("约我") print("约他") return yue()
返回值返回给了函数调用者
yue ()就是函数的调用者 那就是返回给它
def yue(): print("约你") print("约我") print("约他") return ret = yue() print(ret)
返回的结果是None, 想想我们有的时候操作列表的方法 打印的结果就是None, 我们所有用的字符串,列表的方法都是函数.
函数中遇到return, 此函数就立马结束了.不在继续执行
def yue(): print("约你") print("约我") print("约他") return print("大家一起约") yue()
我们现在给return后边写一个返回值,我们试试返回个东西
def yue(): print("约你") print("约我") print("约他") return "美女一枚" yue()
我们现在返回了一个字符串,我们来看一下
def yue(): print("约你") print("约我") print("约他") return '美女一枚' girl = yue() print(girl) 结果: 约你 约我 约他 美女一枚
肯定好多朋友还是不明白上边怎么就打印美女一枚了,莫慌看下图:
1.定义了一个函数yue 2.调用函数 3.执行函数里的约你 4.执行函数里的约我 5.执行函数里的约他 6.返回给调用者一个字符串,这个调用者就是yue() 7.将返回的字符串通过一个等号赋值给白变量girl 8.打印变量girl的值
函数的返回值可以有多个结果:
def yue(): print("约你") print("约我") print("约他") return "美女一枚", "萝莉一枚" girl = yue() print(type(girl)) # tuple
当函数的返回值是多个的时候,返回的就是一个元组
4.1 总结
1.遇到return,此函数结束,return下面的代码将不会在执行
2.return 返回值
关于返回值:
如果return什么都不写或者干脆就没写return,返回的结果就是None
如果return后面写了一个值,返回给调用者这个值
如果return后面写了多个结果,返回给调用者一个tuple(元祖),
调用者可以直接使用解构获取多个变量
##
参数,也就是函数括号里的内容 函数在调用的时候指定一个具体的变量的值 就是参数.写法如下:
def 函数名(参数): 函数体
def yue(chat): print("拿出⼿手机") print("打开"+chat) print("找个漂亮的妹⼦子") print("约不约") yue("陌陌") yue("微信") yue("探探") 结果: 拿出手机 打开陌陌 找个漂亮的妹⼦ 约不约 拿出⼿机 打开微信 找个漂亮的妹⼦ 约不约 拿出手机 打开探探 找个漂亮的妹⼦ 约不约 搞定了. 我们在调用yue的时候给chat一个值. 然后再执行函数体.
如果我们再定函数的时候写了形参,在调用函数的时候没有传递值,调用的时候右边括号会发黄,所以我们必须要传递参数,参数要一一对应,不能多不能少.
5.1 参数
1.形参
写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx
2.实参
在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx
3.传参
从调用函数的时候将值传递到定义函数的过程叫做传参
def yue(chat): # chat 形参 print("拿出手机") print("打开"+chat) print("找个漂亮的妹子") print("约不约") yue("陌陌") # "陌陌"在这里就是实参 len("字符串") # "字符串"在这里就是实参 print("麻花藤") # "麻花藤"就是实参
5.2 参数的分类
5.2.1 位置参数
约到现在,有没有想过这么一个问题,啥样的都约么? 哪里的都约么? 不一定吧.比如我现在在北京,我很寂寞,我喜欢小姐姐
alex 在泰国,很寂寞 人妖就行了 .需求是不一样的 而我们现在的函数没有这些功能 那怎么办呢? 很简单 多来几个参数就好了
def yue(chat,addr,age): # chat 形参 print("拿出手机") print("打开"+chat) print("找个" + addr +"附近漂亮的" + str(age) + "岁妹子") print("约不约") yue("陌陌","北京",18) # 实参 结果: 拿出手机 打开陌陌 找个北京附近漂亮的18岁妹子 约不约
上述代码分析: 在访问yue()的时候,我们按照位置的顺序分别把"陌陌","北京",18赋值给了chat,addr,age,在传参过程中.系统会按照位置把实参赋值到形参.
形参就是一个变量名,实参就是值 传参就是在赋值
def func(addr,age): addr = "北京" age = 18 # 从实参到形参的过程中,函数体内部帮我做了变量的赋值 print(addr) print(age) func("北京",18)
练习
编写函数,给函数传递两个参数a,b a,b相加 返回a参数和b参数相加的和
def f(a,b): c = a+b return c num_sum = f(5,8) print(num_sum) 结果: 13
编写函数,给函数传递两个参数a,b 比较a,b的大小 返回a,b中最大的那个数
def f(a,b): if a>b: return a else: return b num_sum = f(5,8) print(num_sum) 结果:8
比较大小的这个写法有点麻烦,我们在这里学一个三元运算符
def f(a,b): c = a if a > b else b #当a>b就把a赋值给c,否则就把b赋值给c return c msg = f(5,7) print(msg) 结果: 7
5.2.2 关键字参数
位置参数好不好呢? 如果是少量的参数还算OK, 没有问题. 但是如果函数在定义的时候参数非常多怎么办? 程序员必须记住, 我有哪些参数, 而且还有记住每个参数的位置, 否则函数就不能正常调用了. 那则么办呢? python提出了一种叫做关键字参数. 我们不需要记住每个参数的位置. 只要记住每个参数的名字就可以了
def yue(chat, address, age): print("拿出手机") print("打开"+chat) print("找个"+address+"附近漂亮的"+str(age)+"岁妹子") print("约不约") yue(chat="微信", age=18, address="北京") # 关键字参数. 结果: 拿出手机 打开微信 找个北京附近漂亮的18岁妹子 约不约
搞定, 这样就不需要记住繁琐的参数位置了.
5.2.3 混合参数
可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.
# 混合参数 yue("微信", age=18, address="上海") # 正确.第一个位置赋值给chat, 后面的参数开始指定关键字. yue(age="18", "微信", address="广州") # 错误, 最开始使用了关键字参数, 那么后面的 微信的位置就串了, 容易出现混乱
注意: 在使用混合参数的时候, 关键字参数必须在位置参数后面
5.2.4 总结
综上: 在实参的⾓角度来看. 分为三种:
-
位置参数
-
关键字参数
-
混合参数, 位置参数必须在关键字参数前面
位置参数:
位置参数,按照位置来赋值,到目前为止,我们编写的函数都是这种
def yue(chat, address, age): print("拿出手机") print("打开"+chat) print("找个"+address+"附近漂亮的"+str(age)+"岁妹子") print("约不约")
默认值参数:
在函数声明的时候, 就可以给出函数参数的默认值. 在调用的时候可以 给出具体的值, 也可以不给值, 使⽤用默认值. 比如, 我们录入咱们班学生的基本信息. 通过调查发现. 我们班大部分学生都是男生. 这个时 候就可以给出⼀一个sex='男'的默认值.
def stu_info(name, age, sex='男'): print("录入学生信息") print(name, age, sex) print("录入完毕") stu_info("张强", 18)
注意:必须先声明在位置参数,才能声明关键字参数
综上:在形参的角度来看
-
位置参数
-
默认值参数(大多数传进来的参数都是一样的, 一般用默认参数
第十章
Python 函数进阶
一. 函数参数-动态参数
形参的第三种:动态参数
参数
在参数位置用*表示接受任意参数
def eat(*args): print('我想吃',args) eat('大米饭','中米饭','小米饭') # 收到的结果是一个tuple元祖
动态接收参数的时候要注意: 动态参数必须在位置参数后面
def eat(*args,a,b): print('我想吃',args,a,b) eat('大米饭','中米饭','小米饭') 结果: TypeError: eat() missing 2 required keyword-only arguments: 'a' and 'b' # eat函数在调用的时候发现缺少俩个位置参数没有进行传递
通过上述代码发现一个问题就是,我们明明给了多个参数,为什么还会提示参数未传递呢?
原因就是因为这个在搞鬼 把所有的位置参数都给接受了,所有会报错.我们尝试着把a,b放在的前面试试
def eat(a,b,*args): print('我想吃',args,a,b) eat('大米饭','中米饭','小米饭') 结果: 我想吃 ('小米饭',) 大米饭 中米饭
动态接收参数的时候要注意:动态参数必须在位置参数后面
那默认值参数呢?
def eat(a,b,c='白菜',*args): print('我想吃',a,b,c,args) eat('豆腐','粉条','猪肉','大葱') 结果: 我想吃 豆腐 粉条 猪肉 ('大葱',) # 我们定义好的白菜没有生效,被猪肉给覆盖了
我们发现默认值参数写在动态参数前面,默认值的参数是不会生效的
def eat(a,b,*args,c='白菜'): print('我想吃',a,b,args,c) eat('猪肉','粉条','豆腐','大葱') 结果: 我想吃 猪肉 粉条 ('豆腐', '大葱') 白菜 # 这样默认参数就生效了
这个时候如果你不给出关键字传参,那么你的默认值是永远都生效的
注意: 形参的顺序: 位置参数 , 动态参数 , 默认参数
1.2 动态接收关键字参数
在python中可以动态的位置参数,但是*这种情况只能接收位置参数无法接收关键字参数,在python中使用**来接收动态关键字参数
def func(**kwargs): print(kwargs) func(a=1, b=2, c=3) 结果: {'a': 1, 'b': 2, 'c': 3}
动态关键字参数最后获取的是一个dict字典形式
顺序的问题, 在函数调用的时候, 如果先给出关键字参数, 则整个参数列表会报错.
def func(a,b,c,d): print(a,b,c,d) func(1,2,c=3,4) 结果: File "D:/python_object/path2/test.py", line 806 func(1,2,c=3,4) ^ SyntaxError: positional argument follows keyword argument
关键参数必须要放在位置参数后边,由于实参是这个顺序,所以形参接收的时候也是这个顺序.也就是说位置参数必须在关键字参数前面.动态接收关键字参数也要在后面
最终顺序:
位置参数 > *args(动态位置参数) > 默认值参数 > **kwargs(动态默认参数)
这四种参数可以任意的使用
如果想接收所有的参数:
def func(*args,**kwargs): print(args,kwargs) func(1,23,5,a=1,b=6)
动态参数还可以这样传参:
lst = [1,4,7] # 方法一 def func(*args): print(args) func(lst[0],lst[1],lst[2]) # 方法二 def func(*args): print(args) func(*lst) # 在实参的位置上用*将lst(可迭代对象)按照顺序打散 # 在形参的位置上用*把收到的参数组合成一个元祖
字典也可以进行打散,不过需要**
dic = {'a':1,'b':2} def func(**kwargs): print(kwargs) func(**dic)
二. 函数的注释
def eat(food,drink): ''' 这里描述这个函数是做什么的.例如这函数eat就是吃 :param food: food这个参数是什么意思 :param drink: drink这个参数是什么意思 :return: 执行完这个函数想要返回给调用者什么东西 ''' print(food,drink) eat('麻辣烫','肯德基')
在外部查看函数的注释 函数名.*doc*
print(eat.__doc__) #函数名.__doc__ 结果: 这里描述这个函数是做什么的.例如这函数eat就是吃 :param food: food这个参数是什么意思 :param drink: drink这个参数是什么意思 :return: 执行完这个函数想要返回给调用者什么东西
三 .名称空间
在python解释器开始执行之后, 就会在内存中开辟一个空间, 每当遇到一个变量的时候, 就把变量名和值之间的关系记录下来, 但是当遇到函数定义的时候, 解释器只是把函数名读入内存, 表示这个函数存在了, 至于函数内部的变量和逻辑, 解释器是不关心的. 也就是说一开始的时候函数只是加载进来, 仅此而已, 只有当函数被调用和访问的时候, 解释器才会根据函数内部声明的变量来进行开辟变量的内部空间. 随着函数执行完毕, 这些函数内部变量占用的空间也会随着函数执行完毕而被清空.
def fun(): a = 10 print(a) fun() print(a) # a不存在了已经..
我们给存放名字和值的关系的空间起一个名字叫: 命名空间. 我们的变量在存储的时候就 是存储在这片空间中的.
命名空间分类:
-
全局命名空间--> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间
-
局部命名空间--> 在函数中声明的变量会放在局部命名空间
-
内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
加载顺序:
-
内置命名空间
-
全局命名空间
-
局部命名空间(函数被执行的时候)
取值顺序:
-
局部命名空间
-
全局命名空间
-
内置命名空间
a = 10 def func(): a = 20 print(a) func() # 20
作用域: 作用域就是作用范围, 按照生效范围来看分为 全局作用域 和 局部作用域
全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行).
局部作用域: 在函数内部可以使用.
作⽤域命名空间:
-
全局作⽤用域: 全局命名空间 + 内置命名空间
-
局部作⽤用域: 局部命名空间
我们可以通过globals()函数来查看全局作⽤用域中的内容,也可以通过locals()来查看局部作⽤用域中的变量量和函数信息
a = 10 def func(): a = 40 b = 20 print("哈哈") print(a, b) print(globals()) # 打印全局作用域中的内容 print(locals()) # 打印局部作用域中的内容 func()
四. 函数的嵌套
-
只要遇见了()就是函数的调用. 如果没有()就不是函数的调用
-
函数的执行顺序
def fun1(): print(111) def fun2(): print(222) fun1() fun2() print(111)
def fun2(): print(222) def fun3(): print(666) print(444) fun3() print(888) print(33) fun2() print(555)
五 .gloabal、nonlocal
首先在全局声明一个变量, 然后再局部调用这个变量, 并改变这 个变量的值
a = 100 def func(): global a # 加了个global表示不再局部创建这个变量了. 而是直接使用全局的a a = 28 print(a) func() print(a)
global表示. 不再使用局部作用域中的内容了. 而改用全局作用域中的变量
5.1global 宗旨
在函数内部修改全局的变量,如果全局中不存在就创建一个变量
lst = ["麻花藤", "刘嘉玲", "詹姆斯"] def func(): lst.append("⻢云") # 对于可变数据类型可以直接进⾏访问 print(lst) func() print(lst)
5.2 nonlocal宗旨
nonlocal 只修改上一层变量,如果上一层中没有变量就往上找一层,只会找到函数的最外层,不会找到全局进行修改
a = 10 def func1(): a = 20 def func2(): nonlocal a a = 30 print(a) func2() print(a) func1() 结果: 加了nonlocal 30 30 不加nonlocal 30 20
再看, 如果嵌套了很多层, 会是一种什么效果:
a = 1 def fun_1(): a = 2 def fun_2(): nonlocal a a = 3 def fun_3(): a = 4 print(a) print(a) fun_3() print(a) print(a) fun_2() print(a) print(a) fun_1() print(a)
这样的程序如果能分析明白. 那么作用域, global, nonlocal就没问题了
第十一章
一. 函数名的运用
函数名是一个变量, 但它是一个特殊的变量, 与括号配合可以执行函数的变量
1.1.函数名的内存地址
def func(): print("呵呵") print(func) 结果: <function func at 0x1101e4ea0>
1.2 函数名可以赋值给其他变量
def func(): print("呵呵") print(func) a = func # 把函数当成一个值赋值给另一个变量 a() # 函数调用 func()
1.3. 函数名可以当做容器类的元素
def func1(): print("呵呵") def func2(): print("呵呵") def func3(): print("呵呵") def func4(): print("呵呵") lst = [func1, func2, func3] for i in lst: i()
1.4.函数名可以当做函数的参数
def func(): print("吃了么") def func2(fn): print("我是func2") fn() # 执行传递过来的fn print("我是func2") func2(func) # 把函数func当成参数传递给func2的参数fn.
1.5. 函数名可以作为函数的返回值
def func_1(): print("这里是函数1") def func_2(): print("这里是函数2") print("这里是函数1") return func_2 fn = func_1() # 执行函数1. 函数1返回的是函数2, 这时fn指向的就是上面函数2 fn() # 执行func_2函数
二.f-string字符串格式化
f-strings 是python3.6开始加入标准库的格式化输出新的写法,这个格式化输出比之前的%s 或者 format 效率高并且更加简化,非常的好用 。
2.1 简单举例
他的结构就是F(f)+ str的形式,在字符串中想替换的位置用{}展位,与format类似,但是用在字符串后面写入替换的内容,而他可以直接识别。碉堡了。
name = '宝元' age = 18 sex = '男' msg = F'姓名:{name},性别:{age},年龄:{sex}' # 大写字母也可以 msg = f'姓名:{name},性别:{age},年龄:{sex}' print(msg) ''' 输出结果: 姓名:宝元,性别:18,年龄:男 '''
2.2 任意表达式
他可以加任意的表达式,非常方便:
print(f'{3*21}') # 63 name = 'barry' print(f"全部大写:{name.upper()}") # 全部大写:BARRY # 字典也可以 teacher = {'name': '宝元', 'age': 18} msg = f"The teacher is {teacher['name']}, aged {teacher['age']}" print(msg) # The comedian is 宝元, aged 18 # 列表也行 l1 = ['宝元', 18] msg = f'姓名:{l1[0]},年龄:{l1[1]}.' print(msg) # 姓名:宝元,年龄:18.
2.3 也可以插入表达式
可以用函数完成相应的功能,然后将返回值返回到字符串相应的位置
def sum_a_b(a,b): return a + b a = 1 b = 2 print('求和的结果为' + f'{sum_a_b(a,b)}')
2.4 多行f
xname = 'barry' age = 18 ajd = 'handsome' # speaker = f'''Hi {name}. # You are {age} years old. # You are a {ajd} guy!''' speaker = f'Hi {name}.'\ f'You are {age} years old.'\ f'You are a {ajd} guy!' print(speaker)
2.5 其他细节
print(f"{{73}}") # {73} print(f"{{{73}}}") # {73} print(f"{{{{73}}}}") # {{73}} m = 21 # ! , : { } ;这些标点不能出现在{} 这里面。 # print(f'{;12}') # 报错 # 所以使用lambda 表达式会出现一些问题。 # 解决方式:可将lambda嵌套在圆括号里面解决此问题。 x = 5 print(f'{(lambda x: x*2) (x)}') # 10
三 .迭代器
3.1 可迭代对象
3.1.1 可迭代对象定义
什么是对象?Python中一切皆对象,之前我们讲过的一个变量,一个列表,一个字符串,文件句柄,函数名等等都可称作一个对象,
其实一个对象就是一个实例,就是一个实实在在的东西。
那么什么叫迭代? 更新迭代 ,迭代就是一个重复的过程,但是不能是单纯的重复(如果只是单纯的重复那么他与循环没有什么区别)每次重复都是基于上一次的结果而来。比如 你使用过得app,微信,抖音等,隔一段时间就会基于上一次做一些更新,那么这就是迭代。
可迭代对象从字面意思来说就是一个可以重复取值的实实在在的东西。
str list tuple dic set range 文件句柄等,那么int,bool这些为什么不能称为可迭代对象呢?虽然在字面意思这些看着不符合,但是我们要有一定的判断标准或者规则去判断该对象是不是可迭代对象。
在python中,但凡内部含有iter方法的对象,都是可迭代对象。
3.1.2 查看对象内部方法
该对象内部含有什么方法除了看源码还有什么其他的解决方式么?当然有了, 可以通过dir() 去判断一个对象具有什么方法
s1 = 'alex' print(dir(s1))
dir()会返回一个列表,这个列表中含有该对象的以字符串的形式所有方法名。这样我们就可以判断python中的一个对象是不是可迭代对象了:
s1 = 'alex' i = 100 print('__iter__' in dir(i)) # False print('__iter__' in dir(s1)) # True
3.1.3 小结:
从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。
从专业角度来说:但凡内部含有iter方法的对象,都是可迭代对象。
可迭代对象可以通过判断该对象是否有iter方法来判断。
可迭代对象的优点:
可以直观的查看里面的数据。
可迭代对象的缺点:
1.占用内存。
2.可迭代对象不能迭代取值(除去索引,key以外)。
那么这个缺点有人就提出质疑了,即使抛去索引,key以外,这些我可以通过for循环进行取值呀!对,他们都可以通过for循环进行取值,其实for循环在底层做了一个小小的转化,就是先将可迭代对象转化成迭代器,然后在进行取值的。那么接下来,我们就看看迭代器是个什么鬼。
3.2 迭代器
3.2.1 迭代器的定义
从字面意思来说迭代器,是一个可以迭代取值的工具,器:在这里当做工具比较合适。
从专业角度来说:迭代器是这样的对象:实现了无参数的next方法,返回序列中的下一个元素,如果没有元素了,那么抛出StopIteration异常.python中的迭代器还实现了iter方法,因此迭代器也可以迭代。 出自《流畅的python》
那么对于上面的解释有一些超前,和难以理解,不用过于纠结,我们简单来说:在python中,内部含有Iter方法并且含有next方法的对象就是迭代器。
3.2.2 如何判断该对象是否是迭代器
ok,那么我们有了这个定义,我们就可以判断一些对象是不是迭代器或者可迭代对象了了,请判断这些对象:str list tuple dict set range 文件句柄 哪个是迭代器,哪个是可迭代对象:
o1 = 'alex' o2 = [1, 2, 3] o3 = (1, 2, 3) o4 = {'name': '太白','age': 18} o5 = {1, 2, 3} f = open('file',encoding='utf-8', mode='w') print('__iter__' in dir(o1)) # True print('__iter__' in dir(o2)) # True print('__iter__' in dir(o3)) # True print('__iter__' in dir(o4)) # True print('__iter__' in dir(o5)) # True print('__iter__' in dir(f)) # True print('__next__' in dir(o1)) # False print('__next__' in dir(o2)) # False print('__next__' in dir(o3)) # False print('__next__' in dir(o4)) # False print('__next__' in dir(o5)) # False print('__next__' in dir(f)) # True f.close()
通过以上代码可以验证,之前我们学过的这些对象,只有文件句柄是迭代器,剩下的那些数据类型都是可迭代对象。
3.2.3 可迭代对象如何转化成迭代器:
l1 = [1, 2, 3, 4, 5, 6] obj = l1.__iter__() # <list_iterator object at 0x000002057FE1A3C8> # 或 obj = iter(l1) print(obj) # <list_iterator object at 0x102cc67f0>
3.2.4 迭代器取值:
可迭代对象是不可以一直迭代取值的(除去用索引,切片以及Key),但是转化成迭代器就可以了,迭代器是利用next()进行取值:
l1 = [1, 2, 3,] obj = l1.__iter__() # 或者 iter(l1) # print(obj) # <list_iterator object at 0x000002057FE1A3C8> ret = obj.__next__() print(ret) ret = obj.__next__() print(ret) ret = obj.__next__() print(ret) ret = obj.__next__() # StopIteration print(ret) # 迭代器利用next取值:一个next取对应的一个值,如果迭代器里面的值取完了,还要next, # 那么就报StopIteration的错误。
3.2.5 while模拟for的内部循环机制:
刚才我们提到了,for循环的循环对象一定要是可迭代对象,但是这不意味着可迭代对象就可以取值,因为for循环的内部机制是:将可迭代对象转换成迭代器,然后利用next进行取值,最后利用异常处理处理StopIteration抛出的异常。
l1 = [1, 2, 3, 4, 5, 6] # 1 将可迭代对象转化成迭代器 obj = iter(l1) # 2,利用while循环,next进行取值 while 1: # 3,利用异常处理终止循环 try: print(next(obj)) except StopIteration: break
3.2.6 小结:
从字面意思来说:迭代器就是可以迭代取值的工具。
从专业角度来说:在python中,内部含有Iter方法并且含有next方法的对象就是迭代器。
迭代器的优点:
节省内存。 迭代器在内存中相当于只占一个数据的空间:因为每次取值都上一条数据会在内存释放,加载当前的此条数据。
惰性机制。 next一次,取一个值,绝不过多取值。
有一个迭代器模式可以很好的解释上面这两条:迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式。
迭代器的缺点:
不能直观的查看里面的数据。
取值时不走回头路,只能一直向下取值。
l1 = [1, 2, 3, 4, 5, 6] obj = iter(l1) for i in range(2): print(next(obj)) for i in range(2): print(next(obj))
3.3 可迭代对象与迭代器对比
我们今天比较深入的了解了可迭代对象与迭代器,接下来我们说一下这两者之间比较与应用:
可迭代对象:
是一个私有的方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不能直接通过循环迭代取值的这么一个数据集。
应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。
迭代器:
是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。
应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。
第十二章
一.生成器
1.1 初识生成器
生成器的本质就是迭代器,在python社区中,大多数时候都把迭代器和生成器是做同一个概念。
唯一的不同就是:迭代器都是Python给你提供的已经写好的工具或者通过数据转化得来的,(比如文件句柄,iter([1,2,3])。
生成器是需要我们自己用python代码构建的工具。最大的区别也就如此了。
1.2 生成器的构建方式
在python中有三种方式来创建生成器:
-
通过生成器函数
-
通过生成器推导式
-
python内置函数或者模块提供(其实1,3两种本质上差不多,都是通过函数的形式生成,只不过1是自己写的生成器函数,3是python提供的生成器函数而已)
1.3 生成器函数
我们先来研究通过生成器函数构建生成器。
首先,我们先看一个很简单的函数:
def func(): print(11) return 22 ret = func() print(ret) # 运行结果: 11 22
将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数
def func(): print(11) yield 22
def func(): print(11) yield 22 ret = func() print(ret) # 运行结果: <generator object func at 0x000001A575163888>
为什么在函数中添加了yield在调用函数的时候就发现结果不是我们预想的结果呢,
是因为当我们调用函数的时候函数体里的代码会进行执行当执行到yield的关键字的时候,发现我们是想声明一个生成器.程序就会返回一个生成器给咱们
生成器的本质就是迭代器.
迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器
def func(): print("111") yield 222 gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器 ret = gener.__next__() # 这个时候函数才会执⾏ print(ret) # 并且yield会将func生产出来的数据 222 给了 ret。 结果: 111 222
并且我的生成器函数中可以写多个yield。
def func(): print("111") yield 222 print("333") yield 444 gener = func() ret = gener.__next__() print(ret) ret2 = gener.__next__() print(ret2) ret3 = gener.__next__() # 最后⼀个yield执⾏完毕. 再次__next__()程序报错 print(ret3)
当程序运行完最后一个yield,那么后面继续运行next()程序会报错,
一个yield对应一个next,next超过yield数量,就会报错,与迭代器一样。
yield与return的区别:
return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。
yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。
举例:
我们来看一下这个需求:老男孩向楼下卖包子的老板订购了10000个包子.包子铺老板非常实在,一下就全部都做出来了
def eat(): lst = [] for i in range(1,10000): lst.append('包子'+str(i)) return lst e = eat() print(e)
这样做没有问题,但是我们由于学生没有那么多,只吃了2000个左右,剩下的8000个,就只能占着一定的空间,放在一边了。如果包子铺老板效率够高,我吃一个包子,你做一个包子,那么这就不会占用太多空间存储了,完美。
def eat(): for i in range(1,10000): yield '包子'+str(i) e = eat() for i in range(200): next(e)
这两者的区别:
第一种是直接把包子全部做出来,占用内存。
第二种是吃一个生产一个,非常的节省内存,而且还可以保留上次的位置。
def eat(): for i in range(1,10000): yield '包子'+str(i) e = eat() for i in range(200): next(e) for i in range(300): next(e) # 多次next包子的号码是按照顺序记录的。
1.4 send 方法(了解,不讲)
接下来我们再来认识一个新的东西,send方法
# next只能获取yield生成的值,但是不能传递值。 def gen(name): print(f'{name} ready to eat') while 1: food = yield print(f'{name} start to eat {food}') dog = gen('alex') next(dog) next(dog) next(dog) # 而使用send这个方法是可以的。 def gen(name): print(f'{name} ready to eat') while 1: food = yield 222 print(f'{name} start to eat {food}') dog = gen('alex') next(dog) # 第一次必须用next让指针停留在第一个yield后面 # 与next一样,可以获取到yield的值 ret = dog.send('骨头') print(ret) def gen(name): print(f'{name} ready to eat') while 1: food = yield print(f'{name} start to eat {food}') dog = gen('alex') next(dog) # 还可以给上一个yield发送值 dog.send('骨头') dog.send('狗粮') dog.send('香肠')
send和next()区别:
相同点:
send 和 next()都可以让生成器对应的yield向下执行一次。
都可以获取到yield生成的值。
不同点:
第一次获取yield值只能用next不能用send(可以用send(None))。
send可以给上一个yield置传递值。
1.5 yield from
在python3中提供一种可以直接把可迭代对象中的每一个数据作为生成器的结果进行返回
# 对比yield 与 yield from def func(): lst = ['卫龙','老冰棍','北冰洋','牛羊配'] yield lst g = func() print(g) print(next(g)) # 只是返回一个列表 def func(): lst = ['卫龙','老冰棍','北冰洋','牛羊配'] yield from lst g = func() print(g) # 他会将这个可迭代对象(列表)的每个元素当成迭代器的每个结果进行返回。 print(next(g)) print(next(g)) print(next(g)) print(next(g)) ''' yield from ['卫龙','老冰棍','北冰洋','牛羊配'] 等同于: yield '卫龙' yield '老冰棍' yield '北冰洋' yield '牛羊配'
1.6 yield from 小坑
def func(): lst1 = ['卫龙', '老冰棍', '北冰洋', '牛羊配'] lst2 = ['馒头', '花卷', '豆包', '大饼'] yield from lst1 yield from lst2 g = func() for i in g: print(i)
返回的结果是将第一个列表的元素全部返回后,在返回第二个列表
二. 推导式
列表推导式,生成器表达式以及其他推导式,我认为推导式就是构建比较有规律的列表,生成器,字典等一种简便的方式。
2.1列表推导式
这里让学生自己做一下,首先我们先看一下这样的代码,给出一个列表,通过循环,想列表中添加1~10:
li = [] for i in range(10): li.append(i) print(li)
那么按照上面的要求我们用列表推导式写一下:
ls = [i for i in range(10)] print(ls)
列表推导式进行一个分类:
列表推导式分为两种模式:
1.循环模式:[变量(加工的变量) for 变量 in iterable]
2.筛选模式: [变量(加工的变量) for 变量 in iterable if 条件]
当然还有多层循环的,这个我们一会就会讲到,那么我们先来看循环模式。
2.1.1 循环模式
刚才我们看到的就是循环模式,那么有同学会问到,什么叫' 加工的变量'? 这个也比较简单,接下来我们做几道题:
-
将10以内所有整数的平方写入列表。
l1 = [i*i for i in range(1,11)] print(l1)
-
100以内所有的偶数写入列表.
l1 = [i for i in range(2,101,2)] print(l1)
-
从python1期到python24期写入列表lst
lst = [f'python{i}' % i for i in range(1,25)] print(lst)
上面那个格式化输出的变量f'python{i}',就是加工的变量。
上面做的那三个就是循环模式,比较简单,接下来我们研究筛选模式。
2.1.2 筛选模式
筛选模式就是在上面的基础上加上一个判断条件,将满足条件的变量留到列表中。
带着同学们做一个题:
将这个列表中大于3的元素留下来。
l1 = [4, 3, 2, 6, 5, 5, 7, 8] print([i for i in l1 if i > 3])
通过我给大家的演示,大家做几道题:
-
三十以内可以被三整除的数。
multiples = [i for i in range(30) if i % 3 is 0] print(multiples)
-
过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母
l = ['wusir', 'laonanhai', 'aa', 'b', 'taibai'] # print([i.upper() for i in l if len(i) > 3])
-
找到嵌套列表中名字含有两个‘e’的所有名字(有难度)
names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] print([name for lst in names for name in lst if name.count('e') >= 2]) # 注意遍历顺序,这是实现的关键
列表推导式基本上讲完了,当然今天会做一些有关列表推导式的题,让大家更加深入的了解。
2.1.3 生成器表达式
生成器表达式和列表推导式的语法上一模一样,只是把[]换成()就行了。比如将十以内所有数的平方放到一个生成器表达式中
gen = (i**2 for i in range(10)) print(gen) # 结果: <generator object <genexpr> at 0x0000026046CAEBF8>
生成器表达式也可以进行筛选
# 获取1-100内能被3整除的数 gen = (i for i in range(1,100) if i % 3 == 0) for num in gen: print(num)
生成器表达式和列表推导式的区别:
-
列表推导式比较耗内存,所有数据一次性加载到内存。而生成器表达式遵循迭代器协议,逐个产生元素。
-
得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器
-
列表推导式一目了然,生成器表达式只是一个内存地址。
无论是生成器表达式,还是列表推导式,他只是Python给你提供了一个相对简单的构造方式,因为使用推导式非常简单,所以大多数都会为之着迷,这个一定要慎重,推导式只能构建相对复杂的并且有规律的对象,对于没有什么规律,而且嵌套层数比较多(for循环超过三层)这样就不建议大家用推导式构建。
生成器的惰性机制: 生成器只有在访问的时候才取值,说白了.你找他要才给你值.不找他要.他是不会执行的.
2.1.4 其他相关的推导式(了解)
字典推导式
根据名字应该也能猜到,推到出来的是字典
lst1 = ['jay','jj','meet'] lst2 = ['周杰伦','林俊杰','郭宝元'] dic = {lst1[i]:lst2[i] for i in range(len(lst1))} print(dic)
集合推导式
集合推导式可以帮我们直接生成一个集合,
集合的特点;无序,不重复 所以集合推导式自带去重功能
lst = [1,2,3,-1,-3,-7,9] s = {abs(i) for i in lst} print(s)
三. 内置函数Ⅰ
函数就是以功能为导向,一个函数封装一个功能,
Python将一些常用的功能(比如len)给我们封装成了一个一个的函数,供我们使用,他们不仅效率高(底层都是用C语言写的),而且是拿来即用,避免重复早轮子,那么这些函数就称为内置函数,
到目前为止python给我们提供的内置函数一共是68个,
内置函数进行分类:
eval:执行字符串类型的代码,并返回最终结果。
eval('2 + 2') # 4 n=81 eval("n + 4") # 85 eval('print(666)') # 666
exec:执行字符串类型的代码。
s = ''' for i in [1,2,3]: print(i) ''' exec(s)
以上两个内置函数很强大 工作中禁止使用
hash:获取一个对象(可哈希对象:int,str,Bool,tuple)的哈希值。
print(hash(12322)) print(hash('123')) print(hash('arg')) print(hash('alex')) print(hash(True)) print(hash(False)) print(hash((1,2,3))) ''' -2996001552409009098 -4637515981888139739 1 2528502973977326415 '''
help:函数用于查看函数或模块用途的详细说明。
print(help(list)) print(help(str.split))
callable:函数用于检查一个对象是否是可调用的。如果返回True,仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功。
name = 'alex' def func(): pass print(callable(name)) # False print(callable(func)) # True
int:函数用于将一个字符串或数字转换为整型。
print(int()) # 0 print(int('12')) # 12 print(int(3.6)) # 3 print(int('0100',base=2)) # 将2进制的 0100 转化成十进制。结果为 4
float:函数用于将整数和字符串转换成浮点数。
complex:函数用于创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。。
print(float(3)) # 3.0 print(complex(1,2)) # (1+2j)
bin:将十进制转换成二进制并返回。
oct:将十进制转化成八进制字符串并返回。
hex:将十进制转化成十六进制字符串并返回。
print(bin(10),type(bin(10))) # 0b1010 <class 'str'> print(oct(10),type(oct(10))) # 0o12 <class 'str'> print(hex(10),type(hex(10))) # 0xa <class 'str'>
divmod:计算除数与被除数的结果,返回一个包含商和余数的元组(a // b, a % b)。
round:保留浮点数的小数位数,默认保留整数。
pow:求xy次幂。(三个参数为xy的结果对z取余)
print(divmod(7,2)) # (3, 1) print(round(7/3,2)) # 2.33 print(round(7/3)) # 2 print(round(3.32567,3)) # 3.326 print(pow(2,3)) # 两个参数为2**3次幂 print(pow(2,3,3)) # 三个参数为2**3次幂,对3取余。
bytes:用于不同编码之间的转化。
# s = '你好' # bs = s.encode('utf-8') # print(bs) # s1 = bs.decode('utf-8') # print(s1) # bs = bytes(s,encoding='utf-8') # print(bs) # b = '你好'.encode('gbk') # b1 = b.decode('gbk') # print(b1.encode('utf-8'))
ord:输入字符找当前字符编码的位置
chr:输入当前编码的位置数字找出其对应的字符
# ord 输入字符找该字符编码的位置 # print(ord('a')) # print(ord('中')) # chr 输入位置数字找出其对应的字符 # print(chr(97)) # print(chr(20013))
repr:返回一个对象的string形式(原形毕露)。
# %r 原封不动的写出来 # name = 'taibai' # print('我叫%r'%name) # repr 原形毕露 print(repr('{"name":"alex"}')) print('{"name":"alex"}')
all:可迭代对象中,全都是True才是True
any:可迭代对象中,有一个True 就是True
# all 可迭代对象中,全都是True才是True # any 可迭代对象中,有一个True 就是True # print(all([1,2,True,0])) # print(any([1,'',0]))
一. 匿名函数
匿名函数 lambda,也叫一句话函数。
现在有一个需求:你们写一个函数,此函数接收两个int参数,返回和值。
def func(a,b):
return a+b
print(func(3,4))
那么接下来我们用匿名函数完成上面的需求:
func = lambda a,b: a+b
print(func(3, 4)) # 7
我们分析一下上面的代码:
语法:
函数名 = lambda 参数:返回值
1)此函数不是没有名字,他是有名字的,他的名字就叫做lambda
2)lambda 是定义匿名函数的关键字,相当于函数的def.
3)lambda 后面直接加形参,形参加多少都可以,只要用逗号隔开就行。
func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs
print(func(3, 4,c=666,name='alex'))
# {'name': 'alex'}
# 所有类型的形参都可以加,但是一般使用匿名函数只是加位置参数,其他的用不到。
4)返回值在冒号之后设置,返回值和正常的函数一样,可以是任意数据类型。(但是想要返回多个元素要以容器的形式返回)
5)匿名函数不管多复杂.只能写一行.且逻辑结束后直接返回数据
接下来做几个匿名函数的小题:
写匿名函数:接收一个可切片的数据,返回索引为0与2的对应的元素(元组形式)。
func = lambda x:(x[0],x[2])
print(func('afafasd'))
写匿名函数:接收两个int参数,将较大的数据返回。
func = lambda x,y: x if x > y else y
print(func(3,100))
二. 内置函数Ⅱ
昨天,我们已经比较重要的内置函数讲完了,那么今天我们要讲的是最重要的内置函数和高阶函数,这些内置函数是面试与工作中经常用到的,所以,今天的这些内置函数,我们一定要全部记住,并且熟练使用。
内置函数:
str() 将字节转换成字符串
byte_str = bytes("你好",encoding="utf")
print(byte_str)
print(str(byte_str,encoding="utf-8"))
list() 将可迭代对象转换成列表
print(list("alex"))
tuple() 将可迭代对象转换成元组
print(tuple([1,2,3,4]))
dict() 将元组和列表转换成字典
print(dict([(1,2),(3,4)])) print(dict(((1,2),(3,4))))
set() 将可迭代对象转换成一个集合
print(set("alex"))
print() 屏幕输出。
''' 源码分析 def print(self, *args, sep=' ', end='\n', file=None): # known special case of print """ print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) file: 默认是输出到屏幕,如果设置为文件句柄,输出到文件 sep: 打印多个值之间的分隔符,默认为空格 end: 每一次打印的结尾,默认为换行符 flush: 立即把内容输出到流文件,不作缓存 """ ''' print(111,222,333,sep='*') # 111*222*333 print(111,end='') print(222) #两行的结果 111222 f = open('log','w',encoding='utf-8') print('写入文件',fle=f,flush=True)
sum() 求和
sum求和必须是可迭代对象,对象中的元素必须都为整型,字符串类型不能使用
print(sum([1,2,3])) print(sum([1,2,3],100)) 100是起始值,就是从100开始进行求和
abs() 返回绝对值
i = -5 print(abs(i)) # 5
dir() 查看当前对象具有什么方法
print(dir(list))
zip() 拉链方法。函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,
然后返回由这些元祖组成的内容,如果各个迭代器的元素个数不一致,则按照长度最短的返回,
lst1 = [1,2,3] lst2 = ['a','b','c','d'] lst3 = (11,12,13,14,15) for i in zip(lst1,lst2,lst3): print(i) 结果: (1, 'a', 11) (2, 'b', 12) (3, 'c', 13)
format() 格式转换
# 对齐方式: print(format(122,">20")) print(format(122,"<20")) print(format(122,"^20")) # 进制转换: 将十进制转换成二进制 print(format(12,"b")) print(format(12,"08b")) 将十进制转换成八进制 print(format(12,"o")) print(format(12,"08o")) 将二进制转换成十进制 print(format(0b11001,"d")) 将十进制转换成十六进制 print(format(17,"x")) print(format(17,"08x"))
reversed() 将一个序列翻转, 返回翻转序列的迭代器 reversed 示例:
l = reversed('你好') # l 获取到的是一个生成器 print(list(l)) ret = reversed([1, 4, 3, 7, 9]) print(list(ret)) # [9, 7, 3, 4, 1]
高阶函数:
filter筛选过滤
语法: filter(function,iterable) function: 用来筛选的函数,在filter中会自动的把iterable中的元素传递给function,然后根据function返回的True或者False来判断是否保留此项数据 iterable:可迭代对象 lst = [{'id':1,'name':'alex','age':18}, {'id':1,'name':'wusir','age':17}, {'id':1,'name':'taibai','age':16},] ls = filter(lambda e:e['age'] > 16,lst) print(list(ls)) 结果: [{'id': 1, 'name': 'alex', 'age': 18}, {'id': 1, 'name': 'wusir', 'age': 17}]
map映射
映射函数 语法: map(function,iterable) 可以对可迭代对象中的每一个元素进映射,分别取执行function 计算列表中每个元素的平方,返回新列表 lst = [1,2,3,4,5] def func(s): return s*s mp = map(func,lst) print(mp) print(list(mp)) 改写成lambda lst = [1,2,3,4,5] print(list(map(lambda s:s*s,lst))) 计算两个列表中相同位置的数据的和 lst1 = [1, 2, 3, 4, 5] lst2 = [2, 4, 6, 8, 10] print(list(map(lambda x, y: x+y, lst1, lst2))) 结果: [3, 6, 9, 12, 15]
sorted排序函数
语法:sorted(iterable,key=None,reverse=False) iterable : 可迭代对象 key: 排序规则(排序函数),在sorted内部会将可迭代对象中的每一个元素传递给这个函数的参数.根据函数运算的结果进行排序 reverse :是否是倒序,True 倒序 False 正序 lst = [1,3,2,5,4] lst2 = sorted(lst) print(lst) #原列表不会改变 print(lst2) #返回的新列表是经过排序的 lst3 = sorted(lst,reverse=True) print(lst3) #倒叙 结果: [1, 3, 2, 5, 4] [1, 2, 3, 4, 5] [5, 4, 3, 2, 1] 字典使用sorted排序 dic = {1:'a',3:'c',2:'b'} print(sorted(dic)) # 字典排序返回的就是排序后的key 结果: [1,2,3] 和函数组合使用 # 定义一个列表,然后根据一元素的长度排序 lst = ['天龙八部','西游记','红楼梦','三国演义'] # 计算字符串的长度 def func(s): return len(s) print(sorted(lst,key=func)) # 结果: # ['西游记', '红楼梦', '天龙八部', '三国演义'] 和lambda组合使用 lst = ['天龙八部','西游记','红楼梦','三国演义'] print(sorted(lst,key=lambda s:len(s))) 结果: ['西游记', '红楼梦', '天龙八部', '三国演义'] lst = [{'id':1,'name':'alex','age':18}, {'id':2,'name':'wusir','age':17}, {'id':3,'name':'taibai','age':16},] # 按照年龄对学生信息进行排序 print(sorted(lst,key=lambda e:e['age'])) 结果: [{'id': 3, 'name': 'taibai', 'age': 16}, {'id': 2, 'name': 'wusir', 'age': 17}, {'id': 1, 'name': 'alex', 'age': 18}]
max() 最大值与最小值用法相同
min() 求最小值
print(min([1,2,3])) # 返回此序列最小值 ret = min([1,2,-5,],key=abs) # 按照绝对值的大小,返回此序列最小值 print(ret) # 加key是可以加函数名,min自动会获取传入函数中的参数的每个元素,然后通过你设定的返回值比较大小,返回最小的传入的那个参数。 print(min(1,2,-5,6,-3,key=lambda x:abs(x))) # 可以设置很多参数比较大小 dic = {'a':3,'b':2,'c':1} print(min(dic,key=lambda x:dic[x])) # x为dic的key,lambda的返回值(即dic的值进行比较)返回最小的值对应的键
reduce 累计算
from functools import reduce def func(x,y): return x + y # reduce 的使用方式: # reduce(函数名,可迭代对象) # 这两个参数必须都要有,缺一个不行 ret = reduce(func,[3,4,5,6,7]) print(ret) # 结果 25 reduce的作用是先把列表中的前俩个元素取出计算出一个值然后临时保存着, 接下来用这个临时保存的值和列表中第三个元素进行计算,求出一个新的值将最开始 临时保存的值覆盖掉,然后在用这个新的临时值和列表中第四个元素计算.依次类推 注意:我们放进去的可迭代对象没有更改 以上这个例子我们使用sum就可以完全的实现了.我现在有[1,2,3,4]想让列表中的数变成1234,就要用到reduce了. 普通函数版 from functools import reduce def func(x,y): return x * 10 + y # 第一次的时候 x是1 y是2 x乘以10就是10,然后加上y也就是2最终结果是12然后临时存储起来了 # 第二次的时候x是临时存储的值12 x乘以10就是 120 然后加上y也就是3最终结果是123临时存储起来了 # 第三次的时候x是临时存储的值123 x乘以10就是 1230 然后加上y也就是4最终结果是1234然后返回了 l = reduce(func,[1,2,3,4]) print(l) 匿名函数版 l = reduce(lambda x,y:x*10+y,[1,2,3,4]) print(l) 在Python2.x版本中recude是直接 import就可以的, Python3.x版本中需要从functools这个包中导入 龟叔本打算将 lambda 和 reduce 都从全局名字空间都移除, 舆论说龟叔不喜欢lambda 和 reduce 最后lambda没删除是因为和一个人给龟叔写了好多封,进行交流然后把lambda保住了.
参考资料:
https://www.processon.com/view/link/5b4ee15be4b0edb750de96ac
三.闭包
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) print(make_averager(120000))
series列表是一个全局变量,只要是全局作用域的任何地方,都可能对这个列表进行改变。
series = [] def make_averager(new_value): series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) print(make_averager(110000)) series.append(666) # 如果对数据进行相应改变,那么你的平均收盘价就会出现很大的问题。 print(make_averager(120000))
def make_averager(new_value): series = [] series.append(new_value) total = sum(series) return total / len(series) print(make_averager(100000)) # 100000.0 print(make_averager(110000)) # 110000.0 print(make_averager(120000)) # 120000.0
在嵌套函数中使用外部非全局变量就是闭包
我们用闭包的思想改一下这个代码。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() print(avg(100000)) print(avg(110000)) print(avg(120000))
大家仔细看一下这个代码,我是在函数中嵌套了一个函数。那么avg 这个变量接收的实际是averager函数名,也就是其对应的内存地址,我执行了三次avg 也就是执行了三次averager这个函数。
上面被红色方框框起来的区域就是闭包,被蓝色圈起来的那个变量应该是make_averager()函数的局部变量,它应该是随着make_averager()函数的执行结束之后而消失。但是他没有,是因为此区域形成了闭包,series变量就变成了一个叫自由变量的东西,averager函数的作用域会延伸到包含自由变量series的绑定。也就是说,每次我调用avg对应的averager函数 时,都可以引用到这个自用变量series,这个就是闭包。
闭包的定义:
-
闭包是嵌套在函数中的函数。
-
闭包必须是内层函数对外层函数的变量(非全局变量)的引用。
# 例一: def wrapper(): a = 1 def inner(): print(a) return inner ret = wrapper() # 例二: a = 2 def wrapper(): def inner(): print(a) return inner ret = wrapper() # 例三: def wrapper(a,b): def inner(): print(a) print(b) return inner a = 2 b = 3 ret = wrapper(a,b)
有一些函数的属性是可以获取到此函数是否拥有自由变量的,如果此函数拥有自由变量,那么就可以侧面证明其是否是闭包函数了(了解):
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total/len(series) return averager avg = make_averager() # 函数名.__code__.co_freevars 查看函数的自由变量 print(avg.__code__.co_freevars) # ('series',) 当然还有一些参数,仅供了解: # 函数名.__code__.co_freevars 查看函数的自由变量 print(avg.__code__.co_freevars) # ('series',) # 函数名.__code__.co_varnames 查看函数的局部变量 print(avg.__code__.co_varnames) # ('new_value', 'total') # 函数名.__closure__ 获取具体的自由变量对象,也就是cell对象。 # (<cell at 0x0000020070CB7618: int object at 0x000000005CA08090>,) # cell_contents 自由变量具体的值 print(avg.__closure__[0].cell_contents) # []
闭包的作用:保存局部信息不被销毁,保证数据的安全性。
闭包的作用: 1.保护数据的安全性 2.装饰器
闭包的应用:
-
可以保存一些非全局变量但是不易被销毁、改变的数据。
-
装饰器
什么是闭包? 1.在嵌套函数内,使用(非本层变量)和非全局变量就是闭包
闭包的理解 : 一种特殊的函数,由两个函数嵌套组成 外函数和内函数 ,外函数返回值是内函数的引用
第十四章
Python 装饰器
一.装饰器
1. 开放封闭原则
1.对扩展是开放的
2.对修改是封闭的
什么是装饰器?
什么是装饰? 装饰就是添加新的,
它就是在原来的基础上,添加了一个新的功能。
装饰器 是以功能为导向的,就是一个函数。
被装饰的对象:其实也是一个函数。
所以装饰器最终最完美的定义就是:在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。
2. 初识装饰器
def index(): print('欢迎访问博客园主页')
版本1:
需求分析: 可以利用time模块,有一个time.time()功能。
import time print(time.time())
此方法返回的是格林尼治时间,是此时此刻距离1970年1月1日0点0分0秒的时间秒数。也叫时间戳
他是一直变化的。所以要是计算index的执行效率就是在执行前后计算这个时间戳的时间,然后求差值即可。
import time def index(): print('欢迎访问博客园主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}')
由于index函数只有一行代码,执行效率太快了,所以我们利用time模块的一个sleep模拟一下
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}')
版本1分析:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园首页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') start_time = time.time() home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}')
函数就是以功能为导向,减少重复代码
版本2:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def inner(): start_time = time.time() index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') inner()
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def inner(): start_time = time.time() index() home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') timer()
版本3:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timmer(func): # func == index 函数 start_time = time.time() func() # index() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') timmer(index)
这样我将index函数的函数名作为参数传递给timmer函数,然后在timmer函数里面执行index函数,这样就变成动态传参了。
对比着开放封闭原则说: 首先,index函数除了完成了自己之前的功能,还增加了一个测试执行效率的功能, 所以也符合开放原则。
其次,index函数源码 没有改变 没有,但是执行方式改变了,所以不符合封闭原则。
版本4:实现真正的开放封闭原则:装饰器。
这个也很简单,就是我们昨天讲过的闭包,只要你把那个闭包的执行过程整清楚,那么这个你想不会都难。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页')
你将上面的inner函数在套一层最外面的函数timer,然后将里面的inner函数名作为最外面的函数的返回值,这样简单的装饰器就写好了,一点新知识都没有加,这个如果不会就得多抄几遍,抄的时候要理解一下代码。
def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # f = timer(index) # f()
我们分析一下,代码,代码执行到这一行:f = timer(index) 先执行谁?看见一个等号先要执行等号右边, timer(index) 执行timer函数将index函数名传给了func形参。内层函数inner执行么?不执行,inner函数返回 给f变量。所以我们执行f() 就相当于执行inner闭包函数。 f(),这样既测试效率又执行了原函数,有没有问题?当然有啦!!版本4你要解决原函数执行方式不改变的问题,怎么做? 所以你可以把 f 换成 index变量就完美了! index = timer(index) index()带着同学们将这个流程在执行一遍,特别要注意 函数外面的index实际是inner函数的内存地址而不是index函数。让学生们抄一遍,理解一下,这个timer就是最简单版本装饰器,在不改变原index函数的源码以及调用方式前提下,为其增加了额外的功能,测试执行效率。
3. 带返回值的装饰器
你现在这个代码,完成了最初版的装饰器,但是还是不够完善,因为你被装饰的函数index可能会有返回值,如果有返回值,你的装饰器也应该不影响,开放封闭原则嘛。但是你现在设置一下试试:
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner index = timer(index) print(index()) # None
加上装饰器之后,他的返回值为None,为什么?因为你现在的index不是函数名index,这index实际是inner函数名。所以index() 等同于inner() 你的 '访问成功'返回值应该返回给谁?应该返回给index,这样才做到开放封闭,实际返回给了谁?实际返回给了func,所以你要更改一下你的装饰器代码,让其返回给外面的index函数名。 所以:你应该这么做:
def timer(func): # func = index def inner(): start_time = time.time() ret = func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return ret return inner index = timer(index) # inner print(index()) # print(inner())
借助于内层函数inner,你将func的返回值,返回给了inner函数的调用者也就是函数外面的index,这样就实现了开放封闭原则,index返回值,确实返回给了'index'。
让同学们;练习一下。
4. 被装饰函数带参数的装饰器
到目前为止,你的被装饰函数还是没有传参呢?按照我们的开放封闭原则,加不加装饰器都不能影响你被装饰函数的使用。所以我们看一下。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timer(func): # func = index def inner(): start_time = time.time() func() end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # 要想timer装饰home函数怎么做? home = timer(home) home('太白')
上面那么做,显然报错了,为什么? 你的home这个变量是谁?是inner,home('太白')实际是inner('太白')但是你的'太白'这个实参应该传给谁? 应该传给home函数,实际传给了谁?实际传给了inner,所以我们要通过更改装饰器的代码,让其将实参'太白'传给home.
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(name): start_time = time.time() func(name) # home(name) == home('太白') end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner # 要想timer装饰home函数怎么做? home = timer(home) home('太白')
这样你就实现了,还有一个小小的问题,现在被装饰函数的形参只是有一个形参,如果要是多个怎么办?有人说多少个我就写多少个不就行了,那不行呀,你这个装饰器可以装饰N多个不同的函数,这些函数的参数是不统一的。所以你要有一种可以接受不定数参数的形参接受他们。这样,你就要想到*args,**kwargs。
import time def index(): time.sleep(2) # 模拟一下网络延迟以及代码的效率 print('欢迎访问博客园主页') return '访问成功' def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(*args,**kwargs): # 函数定义时,*代表聚合:所以你的args = ('太白',18) start_time = time.time() func(*args,**kwargs) # 函数的执行时,*代表打散:所以*args --> *('太白',18)--> func('太白',18) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner home = timer(home) home('太白',18)
这样利用*的打散与聚合的原理,将这些实参通过inner函数的中间完美的传递到给了相应的形参。
好将上面的代码在敲一遍。
5. 标准版装饰器
代码优化:语法糖
根据我的学习,我们知道了,如果想要各给一个函数加一个装饰器应该是这样:
def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner home = timer(home) home('太白',18)
如果你想给home加上装饰器,每次执行home之前你要写上一句:home = timer(home)这样你在执行home函数 home('太白',18) 才是真生的添加了额外的功能。但是每次写这一句也是很麻烦。所以,Python给我们提供了一个简化机制,用一个很简单的符号去代替这一句话。
def timer(func): # func = home def inner(*args,**kwargs): start_time = time.time() func(*args,**kwargs) end_time = time.time() print(f'此函数的执行效率为{end_time-start_time}') return inner @timer # home = timer(home) def home(name,age): time.sleep(3) # 模拟一下网络延迟以及代码的效率 print(name,age) print(f'欢迎访问{name}主页') home('太白',18)
你看此时我调整了一下位置,你要是不把装饰器放在上面,timer是找不到的。home函数如果想要加上装饰器那么你就在home函数上面加上@home,就等同于那句话 home = timer(home)。这么做没有什么特殊意义,就是让其更简单化,比如你在影视片中见过野战军的作战时由于不方便说话,用一些简单的手势代表一些话语,就是这个意思。
至此标准版的装饰器就是这个样子:
def wrapper(func): def inner(*args,**kwargs): '''执行被装饰函数之前的操作''' ret = func '''执行被装饰函数之后的操作''' return ret return inner
这个就是标准的装饰器,完全符合代码开放封闭原则。这几行代码一定要背过,会用。
此时我们要利用这个装饰器完成一个需求:简单版模拟博客园登录。 此时带着学生们看一下博客园,说一下需求: 博客园登陆之后有几个页面,diary,comment,home,如果我要访问这几个页面,必须验证我是否已登录。 如果已经成功登录,那么这几个页面我都可以无阻力访问。如果没有登录,任何一个页面都不可以访问,我必须先登录,登录成功之后,才可以访问这个页面。我们用成功执行函数模拟作为成功访问这个页面,现在写三个函数,写一个装饰器,实现上述功能。
login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth def diary(): print('欢迎访问日记页面') @auth def comment(): print('欢迎访问评论页面') @auth def home(): print('欢迎访问博客园主页') diary() comment() home()
第十五章
一. 带参数的装饰器
我们看,装饰器其实就是一个闭包函数,再说简单点就是两层的函数。那么是函数,就应该具有函数传参功能。
login_status = { 'username': None, 'status': False, } def auth(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner
你看我上面的装饰器,不要打开,他可以不可在套一层:
def auth(x): def auth2(func): def inner(*args,**kwargs): if login_status['status']: ret = func() return ret username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth
举例说明:抖音:绑定的是微信账号密码。 皮皮虾:绑定的是qq的账号密码。 你现在要完成的就是你的装饰器要分情况去判断账号和密码,不同的函数用的账号和密码来源不同。 但是你之前写的装饰器只能接受一个参数就是函数名,所以你写一个可以接受参数的装饰器。
def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if 微信: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif 'qq': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner @auth2 def jitter(): print('记录美好生活') @auth2 def pipefish(): print('期待你的内涵神评论')
解决方式:
def auth(x): def auth2(func): def inner(*args, **kwargs): if login_status['status']: ret = func() return ret if x == 'wechat': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret elif x == 'qq': username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == '太白' and password == '123': login_status['status'] = True ret = func() return ret return inner return auth2 @auth('wechat') def jitter(): print('记录美好生活') @auth('qq') def pipefish(): print('期待你的内涵神评论')
@auth('wechat') :分两步:
第一步先执行auth('wechat')函数,得到返回值auth2
第二步@与auth2结合,形成装饰器@auth2 然后在依次执行。
这样就是带参数的装饰器,参数可以传入多个,一般带参数的装饰器在以后的工作中都是给你提供的, 你会用就行,但是自己也一定要会写,面试经常会遇到。
二.装饰器装饰多个函数
我们现在知道标准装饰器和带参数的装饰器,我们来看看多个装饰器装饰一个函数:
def wrapper1(func): def inner1(*args,**kwargs): print("这是装饰器一开始") func(*args,**kwargs) print("这是装饰器一结束") return inner1 def wrapper2(func): def inner2(*args,**kwargs): print("这是装饰器二开始") func(*args,**kwargs) print("这是装饰器二结束") return inner2 def wrapper3(func): def inner3(*args,**kwargs): print("这是装饰器三开始") func(*args,**kwargs) print("这是装饰器三结束") return inner3 @wrapper1 @wrapper2 @wrapper3 def func(): print("这是被装饰的函数") func()
大家来推断一下,这个的打印结果
这是装饰器一开始 这是装饰器二开始 这是装饰器三开始 这是被装饰的函数 这是装饰器三结束 这是装饰器二结束 这是装饰器一结束
这个结果和我们想象的是不是不一样啊,这是为什么呢?
是因为@语法糖要检测下面的代码是不是函数,只有@语法糖检测到函数的时候才会进行执行,也就是被装饰的函数正上方的@语法糖先执行,然后在执行被装饰的函数上面的第二个装饰器,以此类推!
三.递归
什么是递归,我们通过名字先来分析一波,递类似于传递,我给你个东西你们一直向下传递,归就是将我给你们传递过去的东西,你们在传到我的手上.这是我们生活上递归
程序中的递归有点不太一样,程序中的递归就是不断调用自己本身
我们说到了调用,你们能想到什么??函数对吧,递归就是用函数实现的,我们来写一个递归
def func(): print(1) func() func()
这个就是递归,你们肯定有人在写函数的时候碰到过这个问题,现在和大家说一下它为什么会报错,是因为这样一直执行下去的话就是一个死循环了,Python这个语言当初设定递归的时候就不是让大家这么用的,Python中使用递归要满足两个条件才是有意义的递归
-
不断的调用自己本身
-
有明确的终止条件
我们用一个例子来说一下递归,你们问我alex多大,我不告诉你alex多大,但是alex比wusir大2岁,你们问我wusir多大,我不告诉,但是wusir比我大两岁,你们我多大,我今年18岁.你们现在知道alex多大吗?我们就可以画个表来推算alex多大.
1 | alex | 18+2+2 |
---|---|---|
2 | wusir | 18+2 |
3 | baoyuan | 18 |
Alex22岁是咱们人计算的,用程序怎么来计算呢?
def age(n): if n == 1: return 18 else: return age(n-1)+2 print(age(3))
我们想知道alex多大,一共问了三次,age(3)就是咱们询问的次数,if n==1 这是一个结束条件,是因为最后一次我告诉你们我多大了,知道我多大了就需要计算wusir多大,知道wusir多大就需要计算alex多大.在问的时候就是递一共递了3次,知道我18岁的时候要就wusir和alex多大的时候就是归.
到现在为止你们还是不太明白这个是怎么实现的,我们来对递归进行一个拆解
def age1(n): if n == 1: return 18 else: return age2(n-1)+2 def age2(n): if n == 1: return 18 else: return age3(n - 1) + 2 def age3(n): if n == 1: return 18 print(age1(3))
看着还是很迷糊,莫慌看以上代码的执行流程图:
图中红色箭头是递的过程,蓝色箭头是归的过程
这个你们大家自己敲一下,体会体会
递归什么时候使用呢?我们之前做了一道题,
li = [1, 3, 4, "alex", [3, 7, 8, "TaiBai"], 5, "RiTiAn"]
这个还有印象吗,我们稍微改改需求
li = [1, 3, 4, "alex", [3, 7, 8, "TaiBai"], 5, "RiTiAn",[4,5,6,[7,[11,12,34,5],10,8,9]]]
将以上列表中的每个元素打印出来,我们之前比较少使用的for实现,使用for它有点不兼容,我们现在使用递归实现一下
def func(lst): for i in lst: if type(i) == list: func(i) else: print(i) func(li) li = [1, 3, 4, "alex", [3, 7, 8, "TaiBai"], 5, "RiTiAn",[4,5,6,[7,[11,12,34,["alex","wusir","baoyuan"],5],10,8,9]]]
我们将结构修改成这样的,用刚刚写的代码也能实现.
第十六章
一.自定义模块
1. 模块的定义与分类
模块是什么?
一个函数封装一个功能,你使用的软件可能就是由n多个函数组成的(先不考虑面向对象)。
比如抖音这个软件,不可能将所有程序都写入一个文件,所以咱们应该将文件划分,这样其组织结构要好并且代码不冗余。假如分了10个文件,每个文件里面可能都有相同的功能(函数),怎么办?所以将这些相同的功能封装到一个文件中,那么这个存储着很多常用的功能的py文件,
就是模块。 模块就是文件,存放一堆常用的函数,
我们说一个函数就是一个功能,那么把一些常用的函数放在一个py文件中,这个文件就称之为模块,模块,就是一些列常用功能的集合体。
为什么要使用模块?
-
从文件级别组织程序,更方便管理, 随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用
-
拿来主义,提升开发效率 同样的原理,我们也可以下载别人写好的模块然后导入到自己的项目中使用,这种拿来主义,可以极大地提升我们的开发效率,避免重复造轮子.
人们常说的脚本是什么?
如果你在终端上编写的代码运行完后,退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,
因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
所以,脚本就是一个python文件,比如你之前写的购物车,模拟博客园登录系统的文件等等。
模块的分类
Python语言中,模块分为三类。
第一类:内置模块,也叫做标准库。此类模块就是python解释器给你提供的,比如我们之前见过的time模块,os模块。标准库的模块非常多(200多个,每个模块又有很多功能),我们这几天就讲常用的十几种,后面课程中还会陆续的讲到。
第二类:第三方模块,第三方库。一些python大神写的非常好用的模块,必须通过pip install 指令安装的模块,比如BeautfulSoup, Django,等等。大概有6000多个。
第三类:自定义模块。我们自己在项目中定义的一些模块。
这几天,我们先学第一类和第三类模块,第二类模块会在我们并发编程开始逐渐的接触学习。
今天,我们先讲第三类,自定义模块。
我们先定义一个模块,定义一个模块其实很简单就是写一个文件,里面写一些代码(变量,函数)即可。此文件的名字为meet.py,文件内容如下:
print('from the meet.py') name = '郭宝元' def read1(): print('meet模块:',name) def read2(): print('meet模块') read1() def change(): global name name = "宝浪"
2. import
2.1 import 使用
import 翻译过来是一个导入的意思。
这里一定要给同学强调哪个文件执行文件,和哪个文件是被执行模块。
模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块import很多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下 import meet 只在第一次导入时才执行meet.py内代码,此处的显式效果是只打印一次'from the meet.py',当然其他的顶级代码也都被执行了,只不过没有显示效果.
import meet import meet import meet import meet import meet 执行结果:只是打印一次: from the meet.py
重复导入会直接引用内存中已经加载好的结果
2.2 第一次导入模块执行三件事
1.创建一个以模块名命名的名称空间。
2.执行这个名称空间(即导入的模块)里面的代码。
3.通过此模块名. 的方式引用该模块里面的内容(变量,函数名,类名等)。 这个名字和变量名没什么区别,都是‘第一类的’,且使用meet名字的方式可以访问meet.py文件中定义的名字,meet.名字与test.py中的名字来自两个完全不同的地方。
2.3 被导入模块有独立的名称空间
每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突。
示例:
当前是test.py import meet name = 'alex' print(name) print(meet.name) ''' 运行结果: from the meet.py alex 郭宝元 ''' def read1(): print(666) meet.read1() ''' 运行结果: from the meet.py meet模块: 郭宝元 ''' name = '日天' meet.change() print(name) print(meet.name) ''' 运行结果: from the meet.py 日天 宝浪 '''
让同学们将上面的代码练习一下。
2.4 为模块起别名
别名其实就是一个外号,我们小的时候,都喜欢给学生们起外号对吧。
1. 好处可以将很长的模块名改成很短,方便使用.
import tbjx as t t.read1()
2. 有利于代码的扩展和优化。
#mysql.py def sqlparse(): print('from mysql sqlparse') #oracle.py def sqlparse(): print('from oracle sqlparse') #test.py db_type=input('>>: ') if db_type == 'mysql': import mysql as db elif db_type == 'oracle': import oracle as db db.sqlparse()
2.5 导入多个模块
我们以后再开发过程中,免不了会在一个文件中,导入多个模块,推荐写法是一个一个导入。
import os,sys,json # 这样写可以但是不推荐 推荐写法 import os import sys import json
多行导入:易于阅读 易于编辑 易于搜索 易于维护。
3 from ... import ...
3.1 from ... import ... 使用
from ... import ... 的使用示例。 from meet import name, read1 print(name) read1() ''' 执行结果: from the meet.py 太白金星 meet模块: 郭宝元 '''
3.2 from...import... 与import对比
唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:tbjx.
from...import...的方式有好处也有坏处
好处:使用起来方便了
坏处:容易与当前执行文件中的名字冲突
示例演示:
-
执行文件有与模块同名的变量或者函数名,会有覆盖效果。
name = 'oldboy' from meet import name, read1, read2 print(name) ''' 执行结果: 郭宝元 ''' ---------------------------------------- from meet import name, read1, read2 name = 'oldboy' print(name) ''' 执行结果: oldboy ''' ---------------------------------------- def read1(): print(666) from meet import name, read1, read2 read1() ''' 执行结果: meet模块: 郭宝元 ''' ---------------------------------------- from meet import name, read1, read2 def read1(): print(666) read1() ''' 执行结果: meet模块: 666 '''
2. 当前位置直接使用read1和read2就好了,执行时,仍然以meet.py文件全局名称空间
#测试一:导入的函数read1,执行时仍然回到meet.py中寻找全局变量 'alex' #test.py from meet import read1 name = 'alex' read1() ''' 执行结果: from the meet.py meet->read1->name = '郭宝元' ''' #测试二:导入的函数read2,执行时需要调用read1(),仍然回到meet.py中找 #read1() #test.py from meet import read2 def read1(): print('==========') read2() ''' 执行结果: from the meet.py meet模块 meet模块: 郭宝元 '''
from … import也支持as
通过这种方式引用模块也可以对模块进行改名。
from meet import read1 as read read()
3.4 一行导入多个
from tbjx import read1,read2,name
3.5 from ... import *
from meet import * 把meet中所有的不是以下划线(_)开头的名字都导入到当前位置
大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差,在交互式环境中导入时没有问题。
可以使用all来控制*(用来发布新版本),在meet.py中新增一行
__all__=['name','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字
3.6 模块循环导入问题(了解)
模块循环/嵌套导入抛出异常的根本原因是由于在python中模块被导入一次之后,就不会重新导入,只会在第一次导入时执行模块内代码
在我们的项目中应该尽量避免出现循环/嵌套导入,如果出现多个模块都需要共享的数据,可以将共享的数据集中存放到某一个地方在程序出现了循环/嵌套导入后的异常分析、解决方法如下(了解,以后尽量避免)
示范文件内容如下
#创建一个m1.py print('正在导入m1') from m2 import y x='m1 #创建一个m2.py print('正在导入m2') from m1 import x y='m2' #创建一个run.py import m1 #测试一 执行run.py会抛出异常 正在导入m1 正在导入m2 Traceback (most recent call last): File "/python项目/run.py", line 1, in <module> import m1 File "/python项目/m1.py", line 2, in <module> from m2 import y File "/python项目/m2.py", line 2, in <module> from m1 import x ImportError: cannot import name 'x' #测试一结果分析 先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1" --->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错 #测试二:执行文件不等于导入文件,比如执行m1.py不等于导入了m1 正在导入m1 正在导入m2 Traceback (most recent call last): 正在导入m1 File "/python项目/m1.py", line 2, in <module> from m2 import y File "/python项目/m2.py", line 2, in <module> from m1 import x File "/python项目/m1.py", line 2, in <module> from m2 import y ImportError: cannot import name 'y' #测试二分析 执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错 # 解决方法: 方法一:导入语句放到最后 #m1.py print('正在导入m1') x='m1' from m2 import y #m2.py print('正在导入m2') y='m2' from m1 import x 方法二:导入语句放到函数中 #m1.py print('正在导入m1') def f1(): from m2 import y print(x,y) x = 'm1' # f1() #m2.py print('正在导入m2') def f2(): from m1 import x print(x,y) y = 'm2' #run.py import m1 m1.f1()
4. py文件的两种功能
编写好的一个python文件可以有两种用途: 一:脚本,一个文件就是整个程序,用来被执行(比如你之前写的模拟博客园登录那个作业等) 二:模块,文件中存放着一堆功能,用来被导入使用 python为我们内置了全局变量name, 当文件被当做脚本执行时:name 等于'main' 当文件被当做模块导入时:name等于模块名 作用:用来控制.py文件在不同的应用场景下执行不同的逻辑(或者是在模块文件中测试代码)
if __name__ == '__main__': print('from the meet.py') __all__ = ['name', 'read1',] name = '郭宝元' def read1(): print('meet模块:',name) def read2(): print('meet模块') read1() def change(): global name name = '宝浪' if __name__ == '__main__': # 在模块文件中测试read1()函数 # 此模块被导入时 __name__ == meet 所以不执行 read1()
5. 模块的搜索路径
当你引用一个模块时,不见得每次都可以import到:
当咱们导入同一个目录下的模块的时候就能够使用import成功,不是同一个目录下的导入就会报错
上面的示例可以得知,引用模块也是按照一定规则进行引用的。
Python中引用模块是按照一定的规则以及顺序去寻找的,这个查询顺序为:先从内存中已经加载的模块进行寻找找不到再从内置模块中寻找,内置模块如果也没有,最后去sys.path中路径包含的模块中寻找。它只会按照这个顺序从这些指定的地方去寻找,如果最终都没有找到,那么就会报错。
内存中已经加载的模块->内置模块->sys.path路径中包含的模块
模块的查找顺序
-
在第一次导入某个模块时(比如meet),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用(ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看)
-
如果没有,解释器则会查找同名的内置模块
-
如果还没有找到就从sys.path给出的目录列表中依次寻找meet.py文件。
需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错
#在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。 > > > import sys > > > sys.path.append('/a/b/c/d') > > > sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索 > > > 注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理, #首先制作归档文件:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目录结构的具体位置 sys.path.append('module.zip/lib/python') #windows下的路径不加r开头,会语法错误 sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\a') #至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。 #需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。
第十七章
一. 模块一
1. 序列化模块
我们今天学习下序列化,什么是序列化呢? 序列化的本质就是将一种数据结构(如字典、列表)等转换成一个特殊的序列(字符串或者bytes)的过程就叫做序列化。那么有同学就会问了,为什么要转化成这个序列,我们不是学过么?
dic = {'name': '郭宝元'} ret = str(dic) print(ret,type(ret))
首先你要看清楚!我说的是一个特殊的序列,而不是我们常用的str这种字符串。
为什么要有序列化模块?
其次,将这个数据结构转化成这个特殊的序列有什么用呢? 这个才是序列化的关键所在,这个特殊的序列大有用处。举例说明:
比如,你的程序中需要一个字典类型的数据存放你的个人信息:
dic = {'username':'宝元', 'password': 123,'login_status': True}
你的程序中有一些地方都需要使用这个dic数据,登录时会用到,注册时也会用到。那么我们之前就是将这个dic写在全局里,但是这样是不合理的,应该是将这数据写入一个地方存储(还没有学数据库)先存放在一个文件中,那么程序中哪里需要这个数据了,你就读取文件取出你需要的信息即可。那么有没有什么问题? 你将这个字典直接写入文件是不可以的,必须转化成字符串的形式,而且你读取出来也是字符串形式的字典(可以用代码展示)。
那么你拿到一个str(dic)有什么用?他是根本转化不成dic的(不能用eval很危险),所以很不方便。那么这时候序列化模块就起到作用了,如果你写入文件中的字符串是一个序列化后的特殊的字符串,那么当你从文件中读取出来,是可以转化回原数据结构的。这个就很牛逼了。
下面说的是json序列化,pickle序列化有所不同。
json序列化除了可以解决写入文件的问题,还可以解决网络传输的问题,比如你将一个list数据结构通过网络传给另个开发者,那么你不可以直接传输,之前我们说过,你要想传输出去必须用bytes类型。但是bytes类型只能与字符串类型互相转化,它不能与其他数据结构直接转化,所以,你只能将list ---> 字符串 ---> bytes 然后发送,对方收到之后,在decode() 解码成原字符串。此时这个字符串不能是我们之前学过的str那种字符串,因为它不能反解,必须要是这个特殊的字符串,他可以反解成list 这样开发者之间就可以借助网络互传数据了,不仅仅是开发者之间,你要借助网络爬取数据这些数据多半是这种特殊的字符串,你接受到之后,在反解成你需要的数据类型。
对于这个序列化模块我们做一个小小总结:
序列化模块就是将一个常见的数据结构转化成一个特殊的序列,并且这个特殊的序列还可以反解回去。它的主要用途:文件读写数据,网络传输数据。
Python中这种序列化模块有三种:
json模块 :(重点)
-
不同语言都遵循的一种数据转化格式,即不同语言都使用的特殊字符串。(比如Python的一个列表[1, 2, 3]利用json转化成特殊的字符串,然后在编码成bytes发送给php的开发者,php的开发者就可以解码成特殊的字符串,然后在反解成原数组(列表): [1, 2, 3])
-
json序列化只支持部分Python数据结构:dict,list, tuple,str,int, float,True,False,None
pickle模块:
-
只能是Python语言遵循的一种数据转化格式,只能在python语言中使用。
-
支持Python所有的数据类型包括实例化对象。
shelve模块:类似于字典的操作方式去操作特殊的字符串(不讲,可以课下了解)。
当然序列化模块中使用最多的的就是json模块,那么接下来,我们讲一下json与pickle模块。
1.1 json模块
json模块是将满足条件的数据结构转化成特殊的字符串,并且也可以反序列化还原回去。
上面介绍我已经说过了,序列化模块总共只有两种用法,要不就是用于网络传输的中间环节,要不就是文件存储的中间环节,所以json模块总共就有两对四个方法:
用于网络传输:dumps、loads
用于文件写读:dump、load
dumps、loads
-
将字典类型转换成字符串类型
import json dic = {'k1':'v1','k2':'v2','k3':'v3'} str_dic = json.dumps(dic) #序列化:将一个字典转换成一个字符串 print(type(str_dic),str_dic) #<class 'str'> {"k3": "v3", "k1": "v1", "k2": "v2"} #注意,json转换完的字符串类型的字典中的字符串是由""表示的
-
将字符串类型的字典转换成字典类型
import json dic2 = json.loads(str_dic) #反序列化:将一个字符串格式的字典转换成一个字典 #注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示 print(type(dic2),dic2) #<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
-
还支持列表类型
list_dic = [1,['a','b','c'],3,{'k1':'v1','k2':'v2'}] str_dic = json.dumps(list_dic) #也可以处理嵌套的数据类型 print(type(str_dic),str_dic) #<class 'str'> [1, ["a", "b", "c"], 3, {"k1": "v1", "k2": "v2"}] list_dic2 = json.loads(str_dic) print(type(list_dic2),list_dic2) #<class 'list'> [1, ['a', 'b', 'c'], 3, {'k1': 'v1', 'k2': 'v2'}]
dump、load
-
将对象转换成字符串写入到文件当中
import json f = open('json_file.json','w') dic = {'k1':'v1','k2':'v2','k3':'v3'} json.dump(dic,f) #dump方法接收一个文件句柄,直接将字典转换成json字符串写入文件 f.close() # json文件也是文件,就是专门存储json字符串的文件。
-
将文件中的字符串类型的字典转换成字典
import json f = open('json_file.json') dic2 = json.load(f) #load方法接收一个文件句柄,直接将文件中的json字符串转换成数据结构返回 f.close() print(type(dic2),dic2)
其他参数说明
ensure_ascii
:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。
separators
:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(,,:);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。
sort_keys
:将数据根据keys的值进行排序。 剩下的自己看源码研究
json序列化存储多个数据到同一个文件中
对于json序列化,存储多个数据到一个文件中是有问题的,默认一个json文件只能存储一个json数据,但是也可以解决,举例说明:
对于json 存储多个数据到文件中 dic1 = {'name':'oldboy1'} dic2 = {'name':'oldboy2'} dic3 = {'name':'oldboy3'} f = open('序列化',encoding='utf-8',mode='a') json.dump(dic1,f) json.dump(dic2,f) json.dump(dic3,f) f.close() f = open('序列化',encoding='utf-8') ret = json.load(f) ret1 = json.load(f) ret2 = json.load(f) print(ret)
上边的代码会报错,解决方法:
dic1 = {'name':'oldboy1'} dic2 = {'name':'oldboy2'} dic3 = {'name':'oldboy3'} f = open('序列化',encoding='utf-8',mode='a') str1 = json.dumps(dic1) f.write(str1+'\n') str2 = json.dumps(dic2) f.write(str2+'\n') str3 = json.dumps(dic3) f.write(str3+'\n') f.close() f = open('序列化',encoding='utf-8') for line in f: print(json.loads(line))
1.2 pickle模块
pickle模块是将Python所有的数据结构以及对象等转化成bytes类型,然后还可以反序列化还原回去。
刚才也跟大家提到了pickle模块,pickle模块是只能Python语言识别的序列化模块。如果把序列化模块比喻成全世界公认的一种交流语言,也就是标准的话,json就是像是英语,全世界(python,java,php,C,等等)都遵循这个标准。而pickle就是中文,只有中国人(python)作为第一交流语言。
既然只是Python语言使用,那么它支持Python所有的数据类型包括后面我们要讲的实例化对象等,它能将这些所有的数据结构序列化成特殊的bytes,然后还可以反序列化还原。使用上与json几乎差不多,也是两对四个方法。
用于网络传输:dumps、loads
用于文件写读:dump、load
dumps、loads
import pickle dic = {'k1':'v1','k2':'v2','k3':'v3'} str_dic = pickle.dumps(dic) print(str_dic) # bytes类型 dic2 = pickle.loads(str_dic) print(dic2) #字典 # 还可以序列化对象 import pickle def func(): print(666) ret = pickle.dumps(func) print(ret,type(ret)) # b'\x80\x03c__main__\nfunc\nq\x00.' <class 'bytes'> f1 = pickle.loads(ret) # f1得到 func函数的内存地址 f1() # 执行func函数
dump、load
dic = {(1,2):'oldboy',1:True,'set':{1,2,3}} f = open('pick序列化',mode='wb') pickle.dump(dic,f) f.close() with open('pick序列化',mode='wb') as f1: pickle.dump(dic,f1)
pickle序列化存储多个数据到一个文件中
dic1 = {'name':'oldboy1'} dic2 = {'name':'oldboy2'} dic3 = {'name':'oldboy3'} f = open('pick多数据',mode='wb') pickle.dump(dic1,f) pickle.dump(dic2,f) pickle.dump(dic3,f) f.close() f = open('pick多数据',mode='rb') while True: try: print(pickle.load(f)) except EOFError: break f.close()
这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?这里我们要说明一下,json是一种所有的语言都可以识别的数据结构。如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码也可以拿来用。但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了~所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块,但如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle。
2. os模块
os模块是与操作系统交互的一个接口,它提供的功能多与工作目录,路径,文件等相关。接下来这些方法我会带着大家演示一遍,重点的一些方法最好记住,剩下的记好笔记,以后需要时随时查阅即可。
讲这些方法前先给大家普及一下专用名词:
目录指的是:文件夹 当前目录,工作目录,父级目录:指的都是一个,就是本文件所在的文件夹。
接下来带着学生讲解下面的这些方法:按照星的等级划分,三颗星是需要记住的
当前执行这个python文件的工作目录相关的工作路径
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 ** os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd ** os.curdir 返回当前目录: ('.') ** os.pardir 获取当前目录的父目录字符串名:('..') **
文件夹相关
os.makedirs('dirname1/dirname2') 可生成多层递归目录 *** os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 *** os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname *** os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname *** os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 **
文件相关
os.remove() 删除一个文件 *** os.rename("oldname","newname") 重命名文件/目录 *** os.stat('path/filename') 获取文件/目录信息 **
路径相关
os.path.abspath(path) 返回path规范化的绝对路径 *** os.path.split(path) 将path分割成目录和文件名二元组返回 *** os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 ** os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值,即os.path.split(path)的第二个元素。 ** os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False *** os.path.isabs(path) 如果path是绝对路径,返回True ** os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False *** os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False *** os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 *** os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间 ** os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 ** os.path.getsize(path) 返回path的大小 ***
操作系统相关(了解)
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/" * os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n" os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为: * os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix' * # 和执行系统命令相关 os.system("bash command") 运行shell命令,直接显示 ** os.popen("bash command).read() 运行shell命令,获取执行结果 ** os.environ 获取系统环境变量 **
os.stat('path/filename') 获取文件/目录信息 的结构说明(了解)
stat 结构: st_mode: inode 保护模式 st_ino: inode 节点号。 st_dev: inode 驻留的设备。 st_nlink: inode 的链接数。 st_uid: 所有者的用户ID。 st_gid: 所有者的组ID。 st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。 st_atime: 上次访问的时间。 st_mtime: 最后一次修改的时间。 st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。
3. sys模块
sys模块是与python解释器交互的一个接口,这个模块功能不是很多,练习一遍就行。
sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1) sys.version 获取Python解释程序的版本信息 sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 *** sys.platform 返回操作系统平台名称
4. hashlib模块
此模块有人称为摘要算法,也叫做加密算法,或者是哈希算法,散列算法等等,这么多title不用大家记,那么有同学就问他到底是干啥的? 简单来说就是做加密和校验使用,它的工作原理给大家简单描述一下:它通过一个函数,把任意长度的数据按照一定规则转换为一个固定长度的数据串(通常用16进制的字符串表示)。
比如:之前我们在一个文件中存储用户的用户名和密码是这样的形式:
宝元|123456
有什么问题?你的密码是明文的,如果有人可以窃取到这个文件,那么你的密码就会泄露了。所以,一般我们存储密码时都是以密文存储,比如:
宝元|4665ace0eb5d3d6a2822a7c455587e47
那么即使是他窃取到这个文件,他也不会轻易的破解出你的密码,这样就会保证了数据的安全。
hashlib模块就可以完成的就是这个功能。
hashlib的特征以及使用要点:
-
bytes类型数据 ---> 通过hashlib算法 ---> 固定长度的字符串
-
不同的bytes类型数据转化成的结果一定不同。
-
相同的bytes类型数据转化成的结果一定相同。
-
此转化过程不可逆。
那么刚才我们也说了,hashlib的主要用途有两个:
密码的加密。
文件一致性校验。
hashlib模块就相当于一个算法的集合,这里面包含着很多的算法,算法越高,转化成的结果越复杂,安全程度越高,相应的效率就会越低。
普通加密:
我们以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
import hashlib md5 = hashlib.md5() md5.update('123456'.encode('utf-8')) # 必须是bytes类型才能够进行加密 print(md5.hexdigest()) # 计算结果如下: 'e10adc3949ba59abbe56e057f20f883e' # 验证:相同的bytes数据转化的结果一定相同 import hashlib md5 = hashlib.md5() md5.update('123456'.encode('utf-8')) print(md5.hexdigest()) # 计算结果如下: 'e10adc3949ba59abbe56e057f20f883e' # 验证:不相同的bytes数据转化的结果一定不相同 import hashlib md5 = hashlib.md5() md5.update('12345'.encode('utf-8')) print(md5.hexdigest()) # 计算结果如下: '827ccb0eea8a706c4c34a16891f84e7b'
上面就是普通的md5加密,非常简单,几行代码就可以了,但是这种加密级别是最低的,相对来说不很安全。虽然说hashlib加密是不可逆的加密方式,但也是可以破解的,那么他是如何做的呢?你看网上好多MD5解密软件,他们使用撞库的方式。他们会把常用的一些密码比如:123456,111111,以及他们的md5的值做成对应关系,类似于字典,
dic = {'e10adc3949ba59abbe56e057f20f883e': 123456}
循环他们那定义的字典中的键和咱们生成的密文进行比较,比较成功后通过你的密文获取对应的密码。
所以针对刚才说的情况,我们有更安全的加密方式:加盐。
加盐加密
固定的盐
什么叫加盐?加盐这个词儿来自于国外,外国人起名字我认为很随意,这个名字来源于烧烤,俗称BBQ。我们烧烤的时候,一般在快熟的时候,都会给肉串上面撒盐,增加味道,那么这个撒盐的工序,外国人认为比较复杂,所以就将比较复杂的加密方式称之为加盐。
其实代码非常简单:
ret = hashlib.md5('xx教育'.encode('utf-8')) # xx教育就是固定的盐 ret.update('a'.encode('utf-8')) print(ret.hexdigest())
上面的xx教育就是固定的盐,比如你在一家公司,公司会将你们所有的密码在md5之前增加一个固定的盐,这样提高了密码的安全性。但是如果黑客通过手段窃取到你这个固定的盐之后,也是可以破解出来的。所以,我们还可以加动态的盐。
动态的盐
username = '宝元666' ret = hashlib.md5(username[::2].encode('utf-8')) # 针对于每个账户,每个账户的盐都不一样 ret.update('a'.encode('utf-8')) print(ret.hexdigest())
这样,安全性能就大大提高了。
那么我们之前说了hahslib模块是一个算法集合,他里面包含很多种加密算法,刚才我们说的MD5算法是比较常用的一种加密算法,一般的企业用MD5就够用了。但是对安全要求比较高的企业,比如金融行业,MD5加密的方式就不够了,得需要加密方式更高的,比如sha系列,sha1,sha224,sha512等等,数字越大,加密的方法越复杂,安全性越高,但是效率就会越慢。
ret = hashlib.sha1() ret.update('guobaoyuan'.encode('utf-8')) print(ret.hexdigest()) #也可加盐 ret = hashlib.sha384(b'asfdsa') ret.update('guobaoyuan'.encode('utf-8')) print(ret.hexdigest()) # 也可以加动态的盐 ret = hashlib.sha384(b'asfdsa'[::2]) ret.update('guobaoyuan'.encode('utf-8')) print(ret.hexdigest())
不过一般我们用到MD5加密就可以了。
4.42 文件的一致性校验
hashlib模块除了可以用于密码加密之外,还有一个常用的功能,那就是文件的一致性校验。
linux讲究:一切皆文件,我们普通的文件,是文件,视频,音频,图片,以及应用程序等都是文件。我们都从网上下载过资源,比如我们刚开学时让大家从网上下载Python解释器,当时你可能没有注意过,其实你下载的时候都是带一个MD5或者shax值的,为什么? 我们的网络世界是很不安全的,经常会遇到病毒,木马等,有些你是看不到的可能就植入了你的电脑中,那么他们是怎么来的? 都是通过网络传入来的,就是你在网上下载一些资源的时候,趁虚而入,当然大部分被我们的浏览器或者杀毒软件拦截了,但是还有一部分偷偷的进入你的磁盘中了。那么我们自己如何验证我们下载的资源是否有病毒呢?这就需要文件的一致性校验了。在我们下载一个软件时,往往都带有一个MD5或者shax值,当我们下载完成这个应用程序时你要是对比大小根本看不出什么问题,你应该对比他们的md5值,如果两个md5值相同,就证明这个应用程序是安全的,如果你下载的这个文件的MD5值与服务端给你提供的不同,那么就证明你这个应用程序肯定是植入病毒了(文件损坏的几率很低),那么你就应该赶紧删除,不应该安装此应用程序。
我们之前说过,md5计算的就是bytes类型的数据的转换值,同一个bytes数据用同样的加密方式转化成的结果一定相同,如果不同的bytes数据(即使一个数据只是删除了一个空格)那么用同样的加密方式转化成的结果一定是不同的。所以,hashlib也是验证文件一致性的重要工具。
我们在安装python解释器的时候,在安装python解释器的时候计算本地的md5值是否一致,一致安装,不一致的删除.
我将文件校验写在一个函数中
low版文件校验:
def func(file): with open(file,mode='rb') as f1: ret = hashlib.md5() ret.update(f1.read()) return ret.hexdigest() print(func('hashlib_file1'))
这样就可以计算此文件的MD5值,从而进行文件校验。但是这样写有一个问题,有什么问题?如果文件过大,全部读取出来直接就会撑爆内存的,所以我们要分段读取,那么分段读取怎么做呢?
hashlib还可以这样玩:
import hashlib # 直接 update md5obj = hashlib.md5() md5obj.update('宝元 is a old driver'.encode('utf-8')) print(md5obj.hexdigest()) # da525c66739e6baa8729332f8bae8e0f # 分段update md5obj = hashlib.md5() md5obj.update('宝元 '.encode('utf-8')) md5obj.update('is '.encode('utf-8')) md5obj.update('a '.encode('utf-8')) md5obj.update('old '.encode('utf-8')) md5obj.update('driver'.encode('utf-8')) print(md5obj.hexdigest()) # da525c66739e6baa8729332f8bae8e0f # 结果相同
我们现在知道可以进行分段update后,我们就可以迭代的获取文件中的内容,现在来做一个高大上版文件校验
高大上版文件校验
校验Pyhton解释器的Md5值是否相同
import hashlib def file_check(file_path): with open(file_path,mode='rb') as f1: sha256 = hashlib.md5() while 1: content = f1.read(1024) if content: sha256.update(content) else: return sha256.hexdigest() print(file_check('python-3.6.6-amd64.exe'))
5. collections模块
在内置数据类型(dict、list、set、tuple)的基础上,collections模块还提供了几个额外的数据类型:Counter、deque、defaultdict、namedtuple和OrderedDict等。
1.namedtuple: 生成可以使用名字来访问元素内容的tuple
2.deque: 双端队列,可以快速的从另外一侧追加和推出对象
3.Counter: 计数器,主要用来计数
4.OrderedDict: 有序字典
5.defaultdict: 带有默认值的字典
namedtuple
我们知道tuple可以表示不变数据,例如,一个点的二维坐标就可以表示成:
p = (1, 2)
但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。
这时,namedtuple就派上了用场:
from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) p = Point(1, 2) print(p)
结果:Point(x=1, y=2)
类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:
namedtuple('名称', [属性list]): Circle = namedtuple('Circle', ['x', 'y', 'r'])
deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
from collections import deque q = deque(['a', 'b', 'c']) q.append('x') q.appendleft('y') q deque(['y', 'a', 'b', 'c', 'x'])
deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
OrderedDict
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。
如果要保持Key的顺序,可以用OrderedDict:
from collections import OrderedDict d = dict([('a', 1), ('b', 2), ('c', 3)]) # 另一种定义字典的方式 print(d) # 结果: {'a': 1, 'c': 3, 'b': 2} od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) print(od) # 结果: OrderedDict([('a', 1), ('b', 2), ('c', 3)])
注意,OrderedDict的Key会按照插入的顺序排列,不是Key本身排序:
>>> od = OrderedDict() >>> od['z'] = 1 >>> od['y'] = 2 >>> od['x'] = 3 >>> od.keys() # 按照插入的Key的顺序返回 ['z', 'y', 'x']
defaultdict
有如下值集合 [11,22,33,44,55,66,77,88,99,90...],将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。
即: {'k1': 大于66 , 'k2': 小于66}
li = [11,22,33,44,55,77,88,99,90] result = {} for row in li: if row > 66: if 'key1' not in result: result['key1'] = [] result['key1'].append(row) else: if 'key2' not in result: result['key2'] = [] result['key2'].append(row) print(result) from collections import defaultdict values = [11, 22, 33,44,55,66,77,88,99,90] my_dict = defaultdict(list) for value in values: if value>66: my_dict['k1'].append(value) else: my_dict['k2'].append(value)
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:
from collections import defaultdict dd = defaultdict(lambda: 'N/A') dd['key1'] = 'abc' # key1存在 print(dd['key1']) dd['key2'] # key2不存在,返回默认值 print(dd['key2'])
Counter
Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。
c = Counter('abcdeabcdabcaba') print c 输出:Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})
一. 软件的开发规范
什么是开发规范?为什么要有开发规范呢?
你现在包括之前写的一些程序,所谓的'项目',都是在一个py文件下完成的,代码量撑死也就几百行,
真正的后端开发的项目,系统等,少则几万行代码,多则十几万,几十万行代码,
你全都放在一个py文件中行么?当然你可以说,只要能实现功能即可。
软件开发,规范你的项目目录结构,代码规范,遵循PEP8规范等等,让你更加清晰滴,合理滴开发。
博客园系统的作业举例,
将我们之前在一个py文件中的所有代码,整合成规范的开发。
目录结构(简化版):
py文件的具体代码如下:
status_dic = {
'username': None,
'status': False,
}
flag = True
def login():
i = 0
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
while i < 3:
username = input('请输入用户名:').strip()
password = input('请输入密码:').strip()
if username in dic and dic[username] == password:
print('登录成功')
return True
else:
print('用户名密码错误,请重新登录')
i += 1
def register():
with open('register', encoding='utf-8') as f1:
dic = {i.strip().split('|')[0]: i.strip().split('|')[1] for i in f1}
while 1:
print('\033[1;45m 欢迎来到注册页面 \033[0m')
username = input('请输入用户名:').strip()
if not username.isalnum():
print('\033[1;31;0m 用户名有非法字符,请重新输入 \033[0m')
continue
if username in dic:
print('\033[1;31;0m 用户名已经存在,请重新输入 \033[0m')
continue
password = input('请输入密码:').strip()
if 6 <= len(password) <= 14:
with open('register', encoding='utf-8', mode='a') as f1:
f1.write(f'\n{username}|{password}')
status_dic['username'] = str(username)
status_dic['status'] = True
print('\033[1;32;0m 恭喜您,注册成功!已帮您成功登录~ \033[0m')
return True
else:
print('\033[1;31;0m 密码长度超出范围,请重新输入 \033[0m')
def auth(func):
def inner(*args, **kwargs):
if status_dic['status']:
ret = func(*args, **kwargs)
return ret
else:
print('\033[1;31;0m 请先进行登录 \033[0m')
if login():
ret = func(*args, **kwargs)
return ret
return inner