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的表现形式:

  1. 英⽂ b'alex' 英⽂的表现形式和字符串没什么两样

  2. 中⽂ 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的表现形式:

  1. 英⽂ b'alex' 英⽂的表现形式和字符串没什么两样

  2. 中⽂ 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 总结

综上: 在实参的⾓角度来看. 分为三种:

  1. 位置参数

  2. 关键字参数

  3. 混合参数, 位置参数必须在关键字参数前面

位置参数:

  位置参数,按照位置来赋值,到目前为止,我们编写的函数都是这种

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)

注意:必须先声明在位置参数,才能声明关键字参数

综上:在形参的角度来看

  1. 位置参数

  2. 默认值参数(大多数传进来的参数都是一样的, 一般用默认参数

 

第十章

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不存在了已经..

我们给存放名字和值的关系的空间起一个名字叫: 命名空间. 我们的变量在存储的时候就 是存储在这片空间中的.

命名空间分类:

  1. 全局命名空间--> 我们直接在py文件中, 函数外声明的变量都属于全局命名空间

  2. 局部命名空间--> 在函数中声明的变量会放在局部命名空间

  3. 内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间  

加载顺序:

  1. 内置命名空间

  2. 全局命名空间

  3. 局部命名空间(函数被执行的时候)

取值顺序:

  1. 局部命名空间

  2. 全局命名空间

  3. 内置命名空间

a = 10
def func():  
    a = 20   
    print(a)

func()  # 20

作用域: 作用域就是作用范围, 按照生效范围来看分为 全局作用域 和 局部作用域

   全局作用域: 包含内置命名空间和全局命名空间. 在整个文件的任何位置都可以使用(遵循 从上到下逐⾏执行).

   局部作用域: 在函数内部可以使用.

作⽤域命名空间:

  1. 全局作⽤用域: 全局命名空间 + 内置命名空间

  2. 局部作⽤用域: 局部命名空间

我们可以通过globals()函数来查看全局作⽤用域中的内容,也可以通过locals()来查看局部作⽤用域中的变量量和函数信息

a = 10
def func():   
    a = 40   
    b = 20   
    print("哈哈")   
    print(a, b)        
    print(globals())    # 打印全局作用域中的内容   
    print(locals())     # 打印局部作用域中的内容
func()

四. 函数的嵌套

  1. 只要遇见了()就是函数的调用. 如果没有()就不是函数的调用

  2. 函数的执行顺序

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中有三种方式来创建生成器:

  1. 通过生成器函数

  2. 通过生成器推导式

  3. 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 循环模式

刚才我们看到的就是循环模式,那么有同学会问到,什么叫' 加工的变量'? 这个也比较简单,接下来我们做几道题:

  1. 将10以内所有整数的平方写入列表。

l1 = [i*i for i in range(1,11)]
print(l1)
  1. 100以内所有的偶数写入列表.

l1 = [i for i in range(2,101,2)]
print(l1)
  1. 从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])

通过我给大家的演示,大家做几道题:

  1. 三十以内可以被三整除的数。

    multiples = [i for i in range(30) if i % 3 is 0]
    print(multiples)
    
  2. 过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

    l = ['wusir', 'laonanhai', 'aa', 'b', 'taibai']
    # print([i.upper() for i in l if len(i) > 3])
    
  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)

生成器表达式和列表推导式的区别:

  1. 列表推导式比较耗内存,所有数据一次性加载到内存。而生成器表达式遵循迭代器协议,逐个产生元素。

  2. 得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器

  3. 列表推导式一目了然,生成器表达式只是一个内存地址。

无论是生成器表达式,还是列表推导式,他只是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,这个就是闭包。

闭包的定义:

  1. 闭包是嵌套在函数中的函数。

  2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。

# 例一:
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. 可以保存一些非全局变量但是不易被销毁、改变的数据。

  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多大.

1alex18+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文件中,这个文件就称之为模块,模块,就是一些列常用功能的集合体。

为什么要使用模块?

  1. 从文件级别组织程序,更方便管理, 随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用

  2. 拿来主义,提升开发效率 同样的原理,我们也可以下载别人写好的模块然后导入到自己的项目中使用,这种拿来主义,可以极大地提升我们的开发效率,避免重复造轮子.

    人们常说的脚本是什么?

如果你在终端上编写的代码运行完后,退出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...的方式有好处也有坏处

好处:使用起来方便了

坏处:容易与当前执行文件中的名字冲突

示例演示:

  1. 执行文件有与模块同名的变量或者函数名,会有覆盖效果。

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路径中包含的模块

模块的查找顺序

  1. 在第一次导入某个模块时(比如meet),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用(ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看)

  2. 如果没有,解释器则会查找同名的内置模块

  3. 如果还没有找到就从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模块 :(重点

  1. 不同语言都遵循的一种数据转化格式,即不同语言都使用的特殊字符串。(比如Python的一个列表[1, 2, 3]利用json转化成特殊的字符串,然后在编码成bytes发送给php的开发者,php的开发者就可以解码成特殊的字符串,然后在反解成原数组(列表): [1, 2, 3])

  2. json序列化只支持部分Python数据结构:dict,list, tuple,str,int, float,True,False,None

pickle模块:

  1. 只能是Python语言遵循的一种数据转化格式,只能在python语言中使用。

  2. 支持Python所有的数据类型包括实例化对象。

shelve模块:类似于字典的操作方式去操作特殊的字符串(不讲,可以课下了解)。

当然序列化模块中使用最多的的就是json模块,那么接下来,我们讲一下json与pickle模块。

1.1 json模块

json模块是将满足条件的数据结构转化成特殊的字符串,并且也可以反序列化还原回去。

上面介绍我已经说过了,序列化模块总共只有两种用法,要不就是用于网络传输的中间环节,要不就是文件存储的中间环节,所以json模块总共就有两对四个方法:

用于网络传输:dumps、loads

用于文件写读:dump、load

dumps、loads

  1. 将字典类型转换成字符串类型

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转换完的字符串类型的字典中的字符串是由""表示的
  1. 将字符串类型的字典转换成字典类型

import json
dic2 = json.loads(str_dic)  #反序列化:将一个字符串格式的字典转换成一个字典
#注意,要用json的loads功能处理的字符串类型的字典中的字符串必须由""表示
print(type(dic2),dic2)  #<class 'dict'> {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
  1. 还支持列表类型

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

  1. 将对象转换成字符串写入到文件当中

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字符串的文件。
  1. 将文件中的字符串类型的字典转换成字典

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的特征以及使用要点:

  1. bytes类型数据 ---> 通过hashlib算法 ---> 固定长度的字符串

  2. 不同的bytes类型数据转化成的结果一定不同。

  3. 相同的bytes类型数据转化成的结果一定相同。

  4. 此转化过程不可逆。

那么刚才我们也说了,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


@auth
def article():
   print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问文章页面\033[0m')


@auth
def diary():
   print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问日记页面\033[0m')


@auth
def comment():
   print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问评论页面\033[0m')


@auth
def enshrine():
   print(f'\033[1;32;0m 欢迎{status_dic["username"]}访问收藏页面\033[0m')


def login_out():
   status_dic['username'] = None
   status_dic['status'] = False
   print('\033[1;32;0m 注销成功 \033[0m')


def exit_program():
   global flag
   flag = False
   return flag


choice_dict = {
   1: login,
   2: register,
   3: article,
   4: diary,
   5: comment,
   6: enshrine,
   7: login_out,
   8: exit_program,
}


while flag:
   print('''
  欢迎来到博客园首页
  1:请登录
  2:请注册
  3:文章页面
  4:日记页面
  5:评论页面
  6:收藏页面
  7:注销
  8:退出程序''')

   choice = input('请输入您选择的序号:').strip()
   if choice.isdigit():
       choice = int(choice)
       if 0 < choice <= len(choice_dict):
           choice_dict[choice]()
       else:
           print('\033[1;31;0m 您输入的超出范围,请重新输入 \033[0m')

   else:
       print('\033[1;31;0m 您您输入的选项有非法字符,请重新输入 \033[0m')

此时我们是将所有的代码都写到了一个py文件中,

如果代码量多且都在一个py文件中,那么对于代码结构不清晰,不规范,运行起来效率也会非常低。

所以我们接下来一步一步的修改:

  1. 程序配置.

你项目中所有的有关文件的操作出现几处,都是直接写的register相对路径,

如果说这个register注册表路径改变了,或者你改变了register注册表的名称,那么相应的这几处都需要一一更改,这样其实你就是把代码写死了,

那么怎么解决?

我要统一相同的路径,也就是统一相同的变量,在文件的最上面写一个变量指向register注册表的路径,代码中如果需要这个路径时,直接引用即可。

  1. 划分文件

一个项目的函数不能只是这些,我们只是举个例子,这个小作业函数都已经这么多了,那么要是一个具体的实际的项目,函数会非常多,所以我们应该将这些函数进行分类, 然后 分文件而治。在这里我划分了以下几个文件:

 

settings.py: 配置文件,

就是放置一些项目中需要的静态参数,比如文件路径,数据库配置,软件的默认设置等等

类似于我们作业中的这个:

common.py:公共组件文件,

这里面放置一些我们常用的公共组件函数,并不是我们核心逻辑的函数,而更像是服务于整个程序中的公用的插件,程序中需要即调用。比如我们程序中的装饰器auth,有些函数是需要这个装饰器认证的,但是有一些是不需要这个装饰器认证的,它既是何处需要何处调用即可。比如还有密码加密功能,序列化功能,日志功能等这些功能都可以放在这里。

src.py:这个文件主要存放的就是核心逻辑功能,你看你需要进行选择的这些核心功能函数,都应该放在这个文件中。

start.py:项目启动文件。你的项目需要有专门的文件启动,而不是在你的核心逻辑部分进行启动的,有人对这个可能不太理解,

为什么还要设置一个单独的启动文件呢?

  #### 目的就是放在显眼的位置,方便开启。

你想想你的项目这么多py文件,如果src文件也有很多,那么到底哪个文件启动整个项目,你还得一个一个去寻找,太麻烦了,这样我把它单独拿出来,就是方便开启整个项目。

那么我们写的项目开启整个项目的代码就是下面这段:

你把这些放置到一个文件中也可以,但是没有必要,我们只需要一个命令或者一个开启指令就行,就好比我们开启电视只需要让人很快的找到那个按钮即可,对于按钮后面的一些复杂的线路板,我们并不关心,所以我们要将上面这个段代码整合成一个函数,

开启项目的''按钮''就是此函数的执行即可。

这个按钮要放到启动文件start.py里面。

除了以上这几个py文件之外还有几个文件,也是非常重要的:

类似于register文件:这个文件文件名不固定,register只是我们项目中用到的注册表,但是这种文件就是存储数据的文件,类似于文本数据库,那么我们一些项目中的数据有的是从数据库中获取的,有些数据就是这种文本数据库中获取的,总之,你的项目中有时会遇到将一些数据存储在文件中,与程序交互的情况,所以我们要单独设置这样的文件。

log文件:log文件顾名思义就是存储log日志的文件。日志我们一会就会讲到,日志主要是供开发人员使用。比如你项目中出现一些bug问题,

比如开发人员对服务器做的一些操作都会记录到日志中,以便开发者浏览,查询。

至此,我们将这个作业原来的两个文件,合理的划分成了6个文件,但是还是有问题的,如果我们的项目很大,你的每一个部分相应的你一个文件存不下的,比如你的src主逻辑文件,函数很多,你是不是得分成:src1.py src2.py?

你的文本数据库register这个只是一个注册表,如果你还有个人信息表,记录表呢? 如果是这样,你的整个项目也是非常凌乱的:

3. 划分具体目录

上面看着就非常乱了,那么如何整改呢?

所以我们这可以整多个文件夹,分别管理不同的物品,那么标准版本的目录结构就来了:

为什么要设计项目目录结构?

"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。对于这种风格上的规范,一直都存在两种态度:

  1. 一类同学认为,这种个人风格问题"无关紧要"。理由是能让程序work就好,风格问题根本不是问题。

  2. 另一类同学认为,规范化能更好的控制程序结构,让程序具有更高的可读性。

我是比较偏向于后者的,因为我是前一类同学思想行为下的直接受害者。我曾经维护过一个非常不好读的项目,其实现的逻辑并不复杂,但是却耗费了我非常长的时间去理解它想表达的意思。

从此我个人对于提高项目可读性、可维护性的要求就很高了。"项目目录结构"其实也是属于"可读性和可维护性"的范畴,我们设计一个层次清晰的目录结构,就是为了达到以下两点:

  1. 可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。

  2. 可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

所以,我认为,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。

上面那个图片就是较好的目录结构。

二. 按照项目目录结构,规范博客园系

  1. 配置start.py文件

我们首先要配置启动文件,启动文件很简答就是将项目的启动执行放置start.py文件中,运行start.py文件可以成功启动项目即可。 那么项目的启动就是这个指令run() 我们把这个run()放置此文件中不就行了?

这样你能执行这个项目么?肯定是不可以呀,你的starts.py根本就找不到run这个变量,肯定是会报错的。

NameError: name 'run' is not defined 本文件肯定是找不到run这个变量也就是函数名的,不过这个难不倒我们,我们刚学了模块, 另个一文件的内容我们可以引用过来。但是你发现import run 或者 from src import run 都是报错的。为什么呢? 骚年,遇到报错不要慌!

我们说过你的模块之所以可以引用,那是因为你的模块肯定在这个三个地方:内存,内置,sys.path里面,那么core在内存中肯定是没有的,也不是内置,而且sys.path也不可能有,因为sys.path只会将你当前的目录(bin)加载到内存,所以你刚才那么引用肯定是有问题的,那么如何解决?内存,内置你是左右不了的,你只能将core的路径添加到sys.path中,这样就可以了。

import sys
sys.path.append(r'D:\lnh.python\py project\teaching_show\blog\core')
from src import run
run()

这样虽然解决了,但是你不觉得有问题么?你现在从这个start文件需要引用src文件,那么你需要手动的将src的工作目录添加到sys.path中,那么有没有可能你会引用到其他的文件?比如你的项目中可能需要引用conf,lib等其他py文件,那么在每次引用之前,或者是开启项目时,全部把他们添加到sys.path中么?

sys.path.append(r'D:\lnh.python\py project\teaching_show\blog\core')
sys.path.append(r'D:\lnh.python\py project\teaching_show\blog\conf')
sys.path.append(r'D:\lnh.python\py project\teaching_show\blog\db')
sys.path.append(r'D:\lnh.python\py project\teaching_show\blog\lib')

这样是不是太麻烦了? 我们应该怎么做?我们应该把项目的工作路径添加到sys.path中,用一个例子说明:你想找张三,李四,王五,赵六等人,这些人全部都在一栋楼比如在汇德商厦,那么我就告诉你汇德商厦的位置:北京昌平区沙河镇汇德商厦。 你到了汇德商厦你在找具体这些人就可以了。所以我们只要将这个blog项目的工作目录添加到sys.path中,这样无论这个项目中的任意一个文件引用项目中哪个文件,就都可以找到了。所以:

import sys
sys.path.append(r'D:\lnh.python\py project\teaching_show\blog')
from core.src import run
run()

上面还是差一点点,你这样写你的blog的路径就写死了,你的项目不可能只在你的电脑上,项目是共同开发的,你的项目肯定会出现在别人电脑上,那么你的路径就是问题了,在你的电脑上你的blog项目的路径是上面所写的,如果移植到别人电脑上,他的路径不可能与你的路径相同, 这样就会报错了,所以我们这个路径要动态获取,不能写死,所以这样就解决了:

import sys
import os
# sys.path.append(r'D:\lnh.python\py project\teaching_show\blog')
print(os.path.dirname(__file__))
# 获取本文件的绝对路径 # D:/lnh.python/py project/teaching_show/blog/bin
print(os.path.dirname(os.path.dirname(__file__)))
# 获取父级目录也就是blog的绝对路径 # D:/lnh.python/py project/teaching_show/blog
BATH_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BATH_DIR)
from core.src import run
run()

那么还差一个小问题,这个starts文件可以当做脚本文件进行直接启动,如果是作为模块,被别人引用的话,按照这么写,也是可以启动整个程序的,这样合理么?这样是不合理的,作为启动文件,是不可以被别人引用启动的,所以我们此时要想到 name了:

import sys
import os
# sys.path.append(r'D:\lnh.python\py project\teaching_show\blog')
# print(os.path.dirname(__file__))
# 获取本文件的绝对路径 # D:/lnh.python/py project/teaching_show/blog/bin
# print(os.path.dirname(os.path.dirname(__file__)))
# 获取父级目录也就是blog的绝对路径 # D:/lnh.python/py project/teaching_show/blog
BATH_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BATH_DIR)
from core.src import run

if __name__ == '__main__':
   run()

这样,我们的starts启动文件就已经配置成功了。以后只要我们通过starts文件启动整个程序,它会先将整个项目的工作目录添加到sys.path中,然后在启动程序,这样我整个项目里面的任何的py文件想引用项目中的其他py文件,都是你可以的了。

  1. 配置settings.py文件。

接下来,我们就会将我们项目中的静态路径,数据库的连接设置等等文件放置在settings文件中。

我们看一下,你的主逻辑src中有这样几个变量:

status_dic = {
   'username': None,
   'status': False,
}
flag = True
register_path = r'D:\lnh.python\py project\teaching_show\blog\register'

我们是不是应该把这几个变量都放置在settings文件中呢?不是!setttings文件叫做配置文件,其实也叫做配置静态文件

什么叫静态? 静态就是一般不会轻易改变的,但是对于上面的代码status_dic ,flag这两个变量,由于在使用这个系统时会时长变化,所以不建议将这个两个变量放置在settings配置文件中,只需要将register_path放置进去就可以。

register_path = r'D:\lnh.python\py project\teaching_show\blog\register'

但是你将这个变量放置在settings.py之后,你的程序启动起来是有问题,为什么?

with open(register_path, encoding='utf-8') as f1:
NameError: name 'register_path' is not defined

因为主逻辑src中找不到register_path这个路径了,所以会报错,那么我们解决方式就是在src主逻辑中引用settings.py文件中的register_path就可以了。

这里引发一个问题:为什么你这样写就可以直接引用settings文件呢?我们在starts文件中已经说了,刚已启动blog文件时,我们手动将blog的路径添加到sys.path中了,这就意味着,我在整个项目中的任何py文件,都可以引用到blog项目目录下面的任何目录:bin,conf,core,db,lib,log这几个,所以,刚才我们引用settings文件才是可以的。

  1. 配置common.py文件

接下来,我们要配置我们的公共组件文件,在我们这个项目中,装饰器就是公共组件的工具,我们要把装饰器这个工具配置到common.py文件中。先把装饰器代码剪切到common.py文件中。这样直接粘过来,是有各种问题的:

所以我们要在common.py文件中引入src文件的这两个变量。

可是你的src文件中使用了auth装饰器,此时你的auth装饰器已经移动位置了,所以你要在src文件中引用auth装饰器,这样才可以使用上

OK,这样你就算是将你之前写的模拟博客园登录的作业按照规范化目录结构合理的完善完成了,最后还有一个关于readme文档的书写。

关于readme的内容

每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

它需要说明以下几个事项:

  1. 软件定位,软件的基本功能。

  2. 运行代码的方法: 安装环境、启动命令等。

  3. 简要的使用说明。

  4. 代码目录结构说明,更详细点可以说明软件的基本原理。

  5. 常见问题说明。

在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。

 

 

 

第十九章

 

一.re模块

1.什么是正则?

 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说:正则就是用来描述一类事物的规则。(在Python中)它内嵌在Python中,并通过 re 模块实现。正则表达式模式被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。

元字符匹配内容
\w 匹配字母(包含中文)或数字或下划线
\W 匹配非字母(包含中文)或数字或下划线
\s 匹配任意的空白符
\S 匹配任意非空白符
\d 匹配数字
\D 匹配非数字
\A 从字符串开头匹配
\z 匹配字符串的结束,如果是换行,只匹配到换行前的结果
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开始
$ 匹配字符串的结尾
. 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。
[...] 匹配字符组中的字符
... 匹配除了字符组中的字符的所有字符
* 匹配0个或者多个左边的字符。
+ 匹配一个或者多个左边的字符。
匹配0个或者1个左边的字符,非贪婪方式。
{n} 精准匹配n个前面的表达式。
{n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
ab 匹配a或者b
() 匹配括号内的表达式,也表示一个组

------------------------------------------------匹配模式----------------------------------------------------

我们现在配合着正则表达式来进行测试

1.字符串的常用操作:一对一匹配

s1 = 'meet郭宝元'
print(s1.find('宝元'))

2.正则匹配

\w 匹配中文,字母,数字,下划线

import re
name = "宝元-meet_123 "
print(re.findall("\w",name))
#结果
['宝', '元', 'm', 'e', 'e', 't', '_', '1', '2', '3']

\W 不匹配中文,字母,数字,下划线

import re
name = "宝元-meet_123 "
print(re.findall("\W",name))
# 结果
['-',' ']

\s 匹配任意的空白符

import re
name = "宝元-meet_123 "
print(re.findall("\s",name))
# 结果
[' ']

\S 匹配不是任意的空白符

import re
name = "宝元-meet_123 "
print(re.findall("\s",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3']

\d 匹配数字

import re
name = "宝元-meet_123 "
print(re.findall("\d",name))
# 结果
['1', '2', '3']

\D 匹配非数字

import re
name = "宝元-meet_123 "
print(re.findall("\D",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', ' ']

\A 与 ^ 从字符串开头匹配

import re
name = "宝元-meet_123 "
print(re.findall("\A宝元",name))
# 结果
['宝元']

import re
name = "宝元-meet_123 "
print(re.findall("\A宝元",name))
# 结果
['宝元']

\Z 与 \z 与 $ 字符串结尾匹配

import re
name = "宝元-meet_123 "
print(re.findall("123 \Z",name))
# 结果
['123 ']

import re
name = "宝元-meet_123 "
print(re.findall("123 \Z",name))
# 结果
['123 ']

import re
name = "宝元-meet_123 "
print(re.findall("123 $",name))
# 结果
['123 ']

\n 与 \t 匹配换行符合制表符

import re
name = "宝元-meet_123\t \n"
print(re.findall("\n",name))
# 结果
['\n']

import re
name = "宝元-meet_123\t \n"
print(re.findall("\t",name))
# 结果
['\t']

------------------------------------------------匹配方式----------------------------------------------------

. 匹配任意字符(换行符除外)

import re
name = "宝元-meet_123\t \n"
print(re.findall(".",name))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3', '\t', ' ']

. 匹配任意字符

import re
name = "宝元-meet_123\t \n"
print(re.findall(".",name,re.DOTALL))
# 结果
['宝', '元', '-', 'm', 'e', 'e', 't', '_', '1', '2', '3', '\t', '\n']

? 匹配?前元素0个或1个

import re
name = "m-e-me-meet-meet_123\t \n"
print(re.findall("me?",name))
# 结果
['m', 'me', 'me', 'me']

* 匹配 * 前面元素0个或多个 [贪婪匹配]

import re
name = "m-e-me-meet-meet_123\t \n"
print(re.findall("*",name))
# 结果
['m', 'me', 'mee', 'mee']

+ 匹配 +前面元素1个或多个 [贪婪匹配]

import re
name = "m-e-me-meet-meet_123\t \n"
print(re.findall("me+",name))
# 结果
['me', 'mee', 'mee']

{n,m} 匹配n到m个元素

import re
name = "m-e-me-meet-meet_123\t \n"
print(re.findall("e{1,2}",name))
# 结果
['e', 'e', 'ee', 'ee']

.* 任意内容0个或多个

import re
name = "m-e-me-meet-meet_123\t \n"
print(re.findall(".*",name))
# 结果
['m-e-me-meet-meet_123', '']

.*? 任意内容0个或1个

import re
name = "m-e-me-meet-meet_123"
print(re.findall("m.*?e",name))
# 结果
['m-e', 'me', 'mee', 'mee']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("m.?e",name))
# 结果
['m-e', 'me', 'mee', 'mee']

[] 获取括号中的内容

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[1-9]",name))
# 结果
['1', '2', '3']
# []中的-是什么至什么不会匹配-

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[a-z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[A-z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't', '_']
# 是按照ascii码表位进行匹配的

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[a-zA-Z]",name))
# 结果
['m', 'e', 'm', 'e', 'm', 'e', 'e', 't', 'm', 'e', 'e', 't']

import re
name = "m-e-me-meet-meet_123"
print(re.findall("[^A-z]",name))
# 结果
['-', '-', '-', '-', '1', '2', '3']
# [^A-z] 有上尖号就是取反,获取不是字母和特定的几个字符

如果想要匹配到-,就需要进行如下操作(-号放到最前面)
import re
name = "m-e-me-meet-meet_123"
print(re.findall("[-+*/]",name))
# 结果
['-', '-', '-', '-']

练习

有如下字符串:'alex_sb ale123_sb wu12sir_sb wusir_sb ritian_sb' 的 alex wusir '

找到所有带_sb的内容

() 分组 定制一个匹配规则

import re
print(re.findall('(.*?)_sb', 'alex_sb wusir_sb 日天_sb'))
# 结果
['alex', ' wusir', ' 日天']

# 应用举例:
print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>')
# 结果
['http://www.baidu.com']

| 匹配 左边或者右边

import re
print(re.findall('alex|宝元|wusir', 'alex宝元wusiraleeeex宝宝元odlb'))
# 结果
['alex', '宝元', 'wusir', '宝元']

import re
print(re.findall('compan(day|morrow)','Work harder today than yesterday, and the day after tomorrow will be better'))
# 结果
['day', 'morrow']

import re
print(re.findall('compan(?:day|morrow)','Work harder today than yesterday, and the day after tomorrow will be better'))
# 结果
['today', 'tomorrow']
# 分组() 中加入?: 表示将整体匹配出来而不只是()里面的内容。

------------------------------------------------常用方法----------------------------------------------------

findall 全部找到返回一个列表

import re
print(re.findall("alex","alexdsb,alex_sb,alexnb,al_ex"))
# 结果
['alex', 'alex', 'alex']

search 从字符串中任意位置进行匹配查找到一个就停止了,返回的是一个对象. 获取匹配的内容必须使用.group()进行获取

import re
print(re.search("sb|nb","alexdsb,alex_sb,alexnb,al_ex").group())
# 结果
sb

match 从字符串开始位置进行匹配

import re
print(re.match('meet', 'meet alex wusir 日天').group())
# 结果
meet

import re
print(re.match('alex', 'meet alex wusir 日天'))
# 结果
None

split 分隔 可按照任意分隔符进行分隔

import re
print(re.split('[ ::,;;,]','alex wusir,日天,太白;女神;肖锋:吴超'))
# 结果
['alex', 'wusir', '日天', '太白', '女神', '肖锋', '吴超']

sub 替换

import re
print(re.sub('barry', 'meet', 'barry是最好的讲师,barry就是一个普通老师,请不要将barry当男神对待。'))
# 结果
meet是最好的讲师,meet就是一个普通老师,请不要将meet当男神对待。

compile 定义匹配规则

import re
obj = re.compile('\d{2}')
print(obj.findall("alex12345"))
# 结果
['12', '34']

import re
['12', '34']
obj = re.compile('\d{2}')
print(obj.search("alex12345").group())
# 结果
12

finditer 返回一个迭代器

import re
g = re.finditer('al',"alex_alsb,al22,aladf")
print(next(g).group())
print([i.group() for i in g])
# 结果
al
['al','al','al']

给分组起名字

import re
ret = re.search("<(?P<tag_name>\w+)>\w+</\w+>","<h1>hello</h1>")
print(ret.group("tag_name"))
print(ret.group())
# 结果
h1
<h1>hello</h1>

import re
ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
print(ret.group(1))
print(ret.group())

相关练习:

1 "1-2*(60+(-40.35/5)-(-4*3))"
1.1 匹配所有的整数
print(re.findall('\d+',"1-2*(60+(-40.35/5)-(-4*3))"))
1.2 匹配所有的数字(包含小数)
print(re.findall(r'\d+\.?\d*|\d*\.?\d+', "1-2*(60+(-40.35/5)-(-4*3))"))
1.3 匹配所有的数字(包含小数包含负号)
print(re.findall(r'-?\d+\.?\d*|\d*\.?\d+', "1-2*(60+(-40.35/5)-(-4*3))"))

2,匹配一段你文本中的每行的邮箱
http://blog.csdn.net/make164492212/article/details/51656638 匹配所有邮箱

3,匹配一段你文本中的每行的时间字符串 这样的形式:'1995-04-27'

s1 = '''
时间就是1995-04-27,2005-04-27
1999-04-27 老男孩教育创始人
老男孩老师 alex 1980-04-27:1980-04-27
2018-12-08
'''
print(re.findall('\d{4}-\d{2}-\d{2}', s1))

4 匹配 一个浮点数
print(re.findall('\d+\.\d*','1.17'))

5 匹配qq号:腾讯从10000开始:
print(re.findall('[1-9][0-9]{4,}', '2413545136'))

s1 = '''
<div id="cnblogs_post_body" class="blogpost-body"><h3><span style="font-family: 楷体;">python基础篇</span></h3>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6847032.html" target="_blank">python 基础知识</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6627631.html" target="_blank">python 初始python</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/articles/7087609.html" target="_blank">python 字符编码</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6752157.html" target="_blank">python 类型及变量</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6847663.html" target="_blank">python 字符串详解</a></strong></span></p>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6850347.html" target="_blank">python 列表详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6850496.html" target="_blank">python 数字元祖</a></strong></span></p>
<p><span style="font-family: 楷体;">&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6851820.html" target="_blank">python 字典详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<strong><a href="http://www.cnblogs.com/guobaoyuan/p/6852131.html" target="_blank">python 集合详解</a></strong></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087614.html" target="_blank">python 数据类型</a>&nbsp;</strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/6752169.html" target="_blank">python文件操作</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/p/8149209.html" target="_blank">python 闭包</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6705714.html" target="_blank">python 函数详解</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087616.html" target="_blank">python 函数、装饰器、内置函数</a></strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/7087629.html" target="_blank">python 迭代器 生成器</a>&nbsp;&nbsp;</strong></span></p>
<p><span style="font-family: 楷体;"><strong>&nbsp; &nbsp;<a href="http://www.cnblogs.com/guobaoyuan/articles/6757215.html" target="_blank">python匿名函数、内置函数</a></strong></span></p>
</div>
'''
1,找到所有的span标签的内容
ret = re.findall('<span(.*?)>', s1)
print(ret)

2,找到所有a标签对应的url
print(re.findall('<a href="(.*?)".*?</a>',s1))

第二十章

 

一.模块和包

包 是程序中一种组织文件的形式.

只要文件夹下含有init.py文件就 是一个包

包是干什么的呢?

为了能够充分的将某个功能进行重用 我们使用了模块,但是慢慢的模块就会越来越多.我们想提高程序的结构性可维护性,就使用包将模块进行统一管理

包能够管理多个模块,我们想要使用包里的模块怎么办呢?

使用import 和from xx import xx 现有如下结构

bake            

   ├── __init__.py      

   ├── api              

       ├── __init__.py

       ├── policy.py

       └── versions.py

 ├── cmd            

   ├── __init__.py

   └── manage.py

 └── db                

     ├── __init__.py

     └── models.py

我们在bake同级创建一个test.py进行导入policy.py 我们使用模块的import的时候只能将api添加到sys.path的路劲中,我们来看看包使用import导入

import bake.api.policy
bake.api.policy.get()

导入的太长了下边使用的时候还需要在重复写一遍,我们可以使用as起别名

import bake.api.policy as p
p.get()

这样的操作只支持包,普通的文件夹无效,有人一定在想我把bake拿过来然后一层一层的打开那拿工具就可以了

import bake
bake.api.policy.get()

不好使,这样导入是只将policy导入了,有人想怎么将api包下的模块全部导入不要急,先说单独导入的方式

咱们能够使用import进行导入,在来看看from的导入方式

from bake.api import policy
policy.get()
from bake import api
print(api.versions.name)

还是不好使,通过这两个我们能够感觉都导入的时候指定要导入的内容,不能再导入后在进行开箱子

我们现在说了单独导入一个模块,现在来说道说道怎么导入某个包下的所有模块,想要导入某个包下的所有的模块 我们就需要在包中的init.py做点手脚

bake包下的__init__.py
from . import api

.是当前路径,因为from的时候不能空着

api包下的__init__.py
from . import policy

我们将包下的init配置好,然后在test.py进行导入

import bake
bake.api.policy.get()

又好使了,这是为什么呢?我们import导入bake这个包,因为bake是一个文件夹不能进行任何操作,就让init.py代替它 去将api这包中的模块导入,api也是一个文件夹不能操作就需要让api下边的init.py去找api下边的两个模块

这个和公司的上下级关系一样,打比方现在test.py就是一个ceo要和policy这个小员工谈话,ceo先把这个想法人事经理,人事经理就是 bake这个包,人事经理通知人事让人事查找一下policy在那个部门,人事查到后通知部门的负责人,部门的负责人在通知部门的主管,主管告诉policy这个员工, 说ceo要找你,部门的主管带着policy去找人事,人事带着policy,人事然后在带着policy去找ceo.最后成功的和ceo进行了一番交流

如果在传达的时候中间一个环节忘记传递了,policy就不知道ceo在找他,ceo等了好久不来ceo就生气报错了

使用的时候需要注意: 有的同学,想在policy文件中导入versions就是直接使用import,在policy文件使用没有问题,很美,很高兴.但是在test.py执行的时候就会报错 因为我们在test.py中执行的import versions 相当于在test.py文件进行查找,肯定不会找到,我们需要在policy文件中向sys.path添加了当前的路劲就可以了 具体操作如下:

import os
import sys
sys.path.insert(os.path.dirname(__file__)

file获取的是当前文件的路径,这样我们就能在test中正常使用了,我们使用from也能够将某个包下所有的模块全都导入 比如我们现在想将cmd包下的所有的模块导入需要在bake包下的init.py进行设置

from . import *

我们需要在api包下设置init.py

from . import policy
from . import versions

我们需要在db包下设置init.py

from . import models

我们需要在cmd包下设置init.py

from . import manage

test.py调用如下:

from bake.api import *
print(versions.name)
policy.get()

from bake.db import *
models.register_models(123)

from bake.cmd import *
print(manage.name)

在使用import有个注意点,python2中如果import包,没有init.py文件就会报错 python3 import没有init.py文件的包不会报错 from 包 import 包或者模块(在import后边不能在进行.操作)

路径:

绝对路径:从最外层(bake)包.查找的就是绝对路径 相对路径:.就是相对路径, ..是上一级目录 例如:我们在bake/api/version.py中想要导入bake/cmd/manage.py

# 绝对路径:
from bake.cmd import manage
manage.main()

#相对路径:
from ..cmd import manage
manage.main()

注意在使用相对路径的时候一定要在于bake同级的文件中测试 我们需要在和bake同级的test.py中测试

from bake.cmd import manage

二 . logging模块

我们来说一下这个logging模块,这个模块的功能是记录我们软件的各种状态,你们现在和我一起找到红蜘蛛的那个图标,然后右键找一找是不是有个错误日志.其实每个软件都是有错误日志的,开发人员可以通过错误日志中的内容对他的程序进行修改

这只是一种应用场景,有的还会将日志用于交易记录.比如你给我转账应该做记录吧,

我们使用的信用卡,每消费的一笔都会记录,我们来看看这个日志怎么用?

我们先来看一下函数式简单配置

import logging  
logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING

(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG),

默认的日志格式为日志级别:Logger名称:用户输出消息。

我们自己用函数写的这个可以正常使用但是不够灵活,我们看看这个灵活的

灵活配置日志级别,日志格式,输出位置:

import logging  
logging.basicConfig(level=logging.DEBUG,  
                  format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',  
                  datefmt='%a, %d %b %Y %H:%M:%S',  
                  filename='/tmp/test.log',  
                  filemode='w')  

logging.debug('debug message')  
logging.info('info message')  
logging.warning('warning message')  
logging.error('error message')  
logging.critical('critical message')

basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有:

  • filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。

  • filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。

  • format:指定handler使用的日志显示格式。

  • datefmt:指定日期时间格式。

  • level:设置记录日志的级别

  • stream:用指定的stream创建StreamHandler。可以指定输出到

  • sys.stderr,sys.stdout或者文件(f=open(‘test.log’,’w’)),默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

format参数中可能用到的格式化串

  • %(name)s Logger的名字

  • %(levelno)s 数字形式的日志级别

  • %(levelname)s 文本形式的日志级别

  • %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有

  • %(filename)s 调用日志输出函数的模块的文件名

  • %(module)s 调用日志输出函数的模块名

  • %(funcName)s 调用日志输出函数的函数名

  • %(lineno)d 调用日志输出函数的语句所在的代码行

  • %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示

  • %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数

  • %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒

  • %(thread)d 线程ID。可能没有

  • %(threadName)s 线程名。可能没有

  • %(process)d 进程ID。可能没有

  • %(message)s用户输出的消息

logger对象配置

import logging

logger = logging.getLogger()
# 创建一个handler,用于写入日志文件
fh = logging.FileHandler('test.log',encoding='utf-8')

# 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

fh.setLevel(logging.DEBUG)

fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象
logger.addHandler(ch)

logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger critical message')

logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。另外,可以通过:logger.setLevel(logging.Debug)设置级别,当然,也可以通过

fh.setLevel(logging.Debug)单对文件流设置某个级别。

 

第二十一章

一.面向对象初识

1.1 面向过程编程vs函数式编程

我们在没有学习函数的时候,写的代码都是面向过程式编程

# 面向过程编程 测量对象的元素的个数。
s1 = 'fjdsklafsjda'
count = 0
for i in s1:
   count += 1


l1 = [1,2,3,4]
count = 0
for i in l1:
   count += 1

在我们学习函数后就是在面向函数编程

def func(s):
   count = 0
   for i in s:
       count += 1
   return count
func('fdsafdsa')
func([1,2,3,4])

通过对比可知:函数编程较之面向过程编程最明显的两个特点:

1,减少重复的代码。

2,增强代码的可读性。

二.面向对象初识

2.1 函数式编程vs面向对象编程

函数式编程

# 函数式编程

# auth 认证相关
def login():
   pass

def regisgter():
   pass

# account 账户相关
def func1():
   pass

def func2():
   pass


# 购物车相关
def shopping(username,money):
   pass
def check_paidgoods(username,money):
   pass
def check_unpaidgoods(username,money):
   pass
def save(username,money):
   pass

面向对象编程

class LoginHandler:
   def login(self):
       pass

   def regisgter(self):
       pass

class Account:
   def func1(self):
       pass

   def func2(self):
       pass

class ShoppingCar:
   def shopping(username,money):
       pass
   def check_paidgoods(username,money):
       pass
   def check_unpaidgoods(username,money):
       pass
   def save(username,money):
       pass

通过对比可以看出面向对象第一个优点:

面向对象编程:是一类相似功能函数的集合,使你的代码更清晰化,更合理化。

说第二个优点之前,先看看什么是面向对象。

面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。

问题来了,那什么是类?什么又是对象?

类:就是具有相同属性和功能的一类事物

对象:就是类的具体表现形式

具体一些:先解释解释什么是⻋? 有轱辘, 有⽅向盘, 有发动机, 会跑的是⻋. 好. 在解释⼀个. 什么是⼈. 有名字, 年龄, 爱好, 会唱歌跳舞思考的是⼈.那么广义上 车,人就是类:但是具体的我的车,你这个人这是一个对象。

猫,是一类,你们家养的 大橘。

狗,是一类,隔壁家养的那只二哈就是对象。

⾯向对象思维, 要⾃⼰建立对象. ⾃⼰建立场景. 你是就是⾯向对象世界中的上帝. 你想让⻋⼲嘛就⼲嘛. 你想让⼈⼲嘛⼈就能⼲嘛。

再说第二个优点:面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来(慢慢体会)。

2.2 类的结构

class Human:
   """
  此类主要是构建人类
  """
   mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
   dic = {}
   l1 = []
   def work(self): # 第二部分:方法 函数 动态属性
       print('人类会工作')
class 是关键字与def用法相同,定义一个类。
Human是此类的类名,类名使用驼峰(CamelCase)命名风格,首字母大写,私有类可用一个下划线开头。
类的结构从大方向来说就分为两部分:
静态变量。
动态方法。

三. 从类名的角度研究类

3.1 类名操作静态属性

第一种,查看类中的所有内容:类名.dict方式。

class Human:
   """
  此类主要是构建人类
  """
   mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
   dic = {}
   l1 = []
   def work(self): # 第二部分:方法 函数 动态属性
       # print(self)
       print('人类会工作')

print(Human.__dict__)
以下了解:
print(Human.__dict__['mind'])
Human.__dict__['mind'] = '无脑'  # 错误
#通过这种方式只能查询,不能增删改.
print(Human.__dict__)

第二种:万能的点.

class Human:
   """
  此类主要是构建人类
  """
   mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
   dic = {}
   l1 = []
   def work(self): # 第二部分:方法 函数 动态属性
       # print(self)
       print('人类会工作')
print(Human.mind)  # 查
Human.mind = '无脑'  # 改
print(Human.mind)
del Human.mind  # 删
Human.walk = '直立行走'
print(Human.walk)
# 通过万能的点 可以增删改查类中的单个属性

  对以上两种做一个总结:如果想查询类中的所有内容,通过 第一种dict方法,如果只是操作单个属性则用万能的点的方式。

2.2 类名操作动态方法

  前提:除了两个特殊方法:属性,类方法之外,一般不会通过类名操作一个类中的方法。

class Human:
   """
  此类主要是构建人类
  """
   mind = '有思想'  # 第一部分:静态属性 属性 静态变量 静态字段
   dic = {}
   l1 = []
   def work(self): # 第二部分:方法 函数 动态属性
       # print(self)
       print('人类会工作')
   def tools(self):
       print('人类会使用工具')

Human.work(111)
Human.tools(111)
下面可以做,但不用。
Human.__dict__['work'](111)

四. 从对象的角度研究类

4.1 什么是对象

对象是从类中出来的,只要是类名加上(),这就是一个实例化过程,这个就会实例化一个对象。

执行下列代码会发生什么事情?

class Human:
   mind = '有思想'

   def work(self):
       print('人类会工作')

   def tools(self):
       print('人类会使用工具')
obj = Human() # 实例化对象
print(obj)  # <__main__.Human object at 0x00000191508AA828>

其实实例化一个对象总共发生了三件事:

  1,在内存中开辟了一个对象空间。

  2,自动执行类中的init方法,并将这个对象空间(内存地址)传给了init方法的第一个位置参数self。

  3,在init 方法中通过self给对象空间添加属性。

示例:

class Human:
   mind = '有思想'
   language = '使用语言'
   def __init__(self,name,sex,age,hobby):
       # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
       self.n = name
       self.s = sex
       self.a = age
       self.h = hobby

obj = Human('meet','男',18,'运动')

3.2 对象操作对象空间属性

对象查询对象中所有属性。 对象.dict

class Human:

   mind = '有思想'
   language = '实用语言'
   def __init__(self,name,sex,age,hobby):
       # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
       self.n = name
       self.s = sex
       self.a = age
       self.h = hobby

obj = Human('meet','男',18,'运动')
print(obj.__dict__)  # {'n': 'meet', 'h': '运动', 's': '男', 'a': 18}

对象操作对象中的单个属性。 万能的点 .

class Human:

   mind = '有思想'
   language = '实用语言'
   def __init__(self,name,sex,age,hobby):
       # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。
       self.n = name
       self.s = sex
       self.a = age
       self.h = hobby

obj = Human('meet','男',18,'运动')
obj.job = 'IT'  # 增
del obj.n  # 删
obj.s = '女' # 改
print(obj.s)  # 查
print(obj.__dict__)

4.3 对象查看类中的属性

class Human:

   mind = '有思想'
   language = '实用语言'
   def __init__(self,name,sex,age,hobby):
       self.n = name
       self.s = sex
       self.a = age
       self.h = hobby

obj = Human('meet','男',18,'运动')
print(obj.mind)
print(obj.language)
obj.a = 666
print(obj.a)

4.4 对象操作类中的方法

class Human:

   mind = '有思想'
   language = '实用语言'
   def __init__(self,name,sex,age,hobby):
       self.n = name
       self.s = sex
       self.a = age
       self.h = hobby

   def work(self):
       print(self)
       print('人类会工作')

   def tools(self):
       print('人类会使用工具')

obj = Human('meet','男',18,'运动')
obj.work()
obj.tools()

  类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self.

4.5 self 是什么?

self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以咱们把类中的方法的第一个参数约定俗成设置成self, 代表这个就是对象.这个self可以进行改变但是不建议大家进行修改

传参分为隐式传参和显示传参,self这种方式就是隐式传参

4.6 一个类可以实例化多个对象

obj1= Human('小胖','男',20,'美女')
obj2= Human('相爷','男',18,'肥女')
print(obj1,obj2)
print(obj1.__dict__)
print(obj2.__dict__)

我们每实例化一个对象都开辟一个空间,并且这写空间之间是独立的.

第二十二章

 

一.Python 类的空间问题

1.1 何处可以添加对象属性

class A:
   def __init__(self,name):
       self.name = name

   def func(self,sex):
       self.sex = sex
# 类外面可以:
obj = A('meet')
obj.age = 18
print(obj.__dict__)  # {'name': 'meet', 'age': 18}

# 类内部也可以:
obj = A('meet') # __init__方法可以。
obj.func('男')  # func 方法也可以。

总结:对象的属性不仅可以在init里面添加,还可以在类的其他方法或者类的外面添加。

1.2 何处可以添加类的静态属性

class A:
   def __init__(self,name):
       self.name = name

   def func(self,sex):
       self.sex = sex

   def func1(self):
       A.bbb = 'ccc'

# 类的外部可以添加

A.aaa = 'baoyuan'
print(A.__dict__)

# 类的内部也可以添加。

A.func1(111)
print(A.__dict__)

总结:类的属性不仅可以在类内部添加,还可以在类的外部添加。

1.3 对象如何找到类的属性

之前咱们都学习过,实例化一个对象,可以通过点的方式找到类中的属性,那么他为什么可以找到类中的属性呢?

通过图解说明:

对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....

类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........

上面的顺序都是单向不可逆,类名不可能找到对象的属性。

二. 类与类之间的关系

⼤千世界, 万物之间皆有规则和规律. 我们的类和对象是对⼤千世界中的所有事物进⾏归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在⾯向对象的世界中. 类与类中存在以下关系:我们会使用这个关系就行,不用扣这写名词

  1. 依赖关系

  2. 组合关系

  3. 继承关系(类的三大特性之一:继承。)

2.1 依赖关系

⾸先, 我们设计⼀个场景. 夏天到了大象很热,大象想到冰箱中. 注意. 在这个场景中, 其实是存在了两种事物的. ⼀个是⼤象, ⼤象负责整个事件的掌控者, 还有⼀个是冰箱, 冰箱负责被⼤象操纵. 通过这个我们要分出主次, 大象就是主 冰箱就是次

⾸先, 写出两个类, ⼀个是⼤象类, ⼀个是冰箱类

class Elphant:
   def __init__(self, name):
       self.name = name

   def open(self):
       '''
      开⻔
      '''
       pass

   def close(self):
       '''
      关⻔
      '''
       pass


class Refrigerator:

   def open_door(self):
       print("冰箱⻔被打开了")

   def close_door(self):
       print("冰箱⻔被关上了")

  冰箱的功能非常简单, 只要会开⻔, 关⻔就⾏了. 但是⼤象就没那么简单了. 想想. ⼤象开⻔和关⻔的时候是不是要先找个冰箱啊. 然后呢? 打开冰箱⻔. 是不是打开刚才找到的那个冰箱⻔. 然后装⾃⼰. 最后呢? 关冰箱⻔, 注意, 关的是刚才那个冰箱吧. 也就是说. 开⻔和关⻔⽤的是同⼀个冰箱. 并且. ⼤象有更换冰箱的权利, 想进那个冰箱就进那个冰箱. 这时, ⼤象类和冰箱类的关系并没有那么的紧密. 因为⼤象可以指定任何⼀个冰箱. 接下来. 我们把代码完善⼀下

class Elphant:
   def __init__(self, name):
       self.name = name

   def open(self,obj1):
       '''
      开⻔

      '''
       print('大象要开门了,默念三声,开')
       obj1.open_door()

   def close(self):
       '''
      关⻔
      '''
       print('大象要关门了,默念三声,关')


class Refrigerator:

   def open_door(self):
       print("冰箱⻔被打开了")

   def close_door(self):
       print("冰箱⻔被关上了")


elphant1 = Elphant('大象')
haier = Refrigerator()
elphant1.open(haier)

通过上边的代码可以发现,将一个类名或对象当做参数传递给另一个函数被使用就是依赖关系

2.2 组合关系

这个最简单. 也是最常⽤的⼀种关系. 比如. ⼤家都有男女朋友. 男⼈关联着女朋友. 女⼈关联着男朋友. 这种关系可以是互相的, 也可以是单⽅⾯的.

定义类

class Boy:
   def __init__(self,name,girlFriend=None):
       self.name = name
       self.girlFriend = girlFriend

   def have_a_diner(self):
       if self.girlFriend:
           print('%s 和 %s 一起晚饭'%(self.name,self.girlFriend.name))
       else:
           print('单身狗,吃什么饭')


class Girl:
   def __init__(self,name):
       self.name = name

实例(创建)日天对象

b = Boy('日天')
b.have_a_diner() # 此时是单身狗

# 突然有一天,日天牛逼了
b.girlFriend = '如花'
b.have_a_diner()  #共进晚餐

实例(创建)wusir对象

# wusir 生下来就有女朋友 服不服
gg = Girl('小花')
bb = Boy('wusir', gg)
bb.have_a_diner()

# 结果嫌他有点娘,不硬,分了
bb.girlFriend = None
bb.have_a_diner()

我们了解了依赖关系和组合关系,现在来对比一下

依赖关系,将一个类或对象传递给另一个类的方法中

组合关系,将一个类的对象传递到另一个类的对象属性中

像这样的关系有很多很多. 比如. 学校和老师之间的关系.

学校和老师示例:

# 老师属于学校,必须有学校才可以工作
class School:

   def __init__(self,name,address):
       self.name = name
       self.address = address


class Teacher:

   def __init__(self,name,school):
       self.name = name
       self.school = school

s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')

t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)

print(t1.school.name)
print(t2.school.name)
print(t3.school.name)

但是学校也是依赖于老师的,所以老师学校应该互相依赖。

class School:

   def __init__(self,name,address):
       self.name = name
       self.address = address
       self.teacher_list = []

   def append_teacher(self,teacher):
       self.teacher_list.append(teacher)

class Teacher:

   def __init__(self,name,school):
       self.name = name
       self.school = school

s1 = School('北京校区','美丽的沙河')
s2 = School('上海校区','上海迪士尼旁边')
s3 = School('深圳校区','南山区')

t1 = Teacher('武大',s1)
t2 = Teacher('海峰',s2)
t3 = Teacher('日天',s3)

s1.append_teacher(t1)
s1.append_teacher(t2)
s1.append_teacher(t3)

# print(s1.teacher_list)
# for teacher in s1.teacher_list:
#     print(teacher.name)

组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。

咱们设计一个游戏人物类,让实例化几个对象让这几个游戏人物实现互殴的效果。

class Gamerole:
   def __init__(self,name,ad,hp):
       self.name = name
       self.ad = ad
       self.hp = hp
   def attack(self,p1):
       p1.hp -= self.ad
       print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
gailun = Gamerole('盖伦',10,200)
yasuo= Gamerole('亚索',50,80)

#盖伦攻击亚索
gailun.attack(yasuo)
# 亚索攻击盖伦
yasuo.attack(gailun)

但是这样互相攻击没有意思,一般游戏类的的对战方式要借助武器,武器是一个类,武器类包含的对象很多:刀枪棍剑斧钺钩叉等等,所以咱们要写一个武器类。

class Gamerole:
   def __init__(self,name,ad,hp):
       self.name = name
       self.ad = ad
       self.hp = hp
   def attack(self,p1):
       p1.hp -= self.ad
       print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))

class Weapon:
   def __init__(self,name,ad):
       self.name = name
       self.ad = ad
   def weapon_attack(self,p1,p2):
       p2.hp = p2.hp - self.ad - p1.ad
       print('%s 利用 %s 攻击了%s,%s还剩%s血' %(p1.name,self.name,p2.name,p2.name,p2.hp))

接下来借助武器攻击对方:

pillow = Weapon('绣花枕头',2)
pillow.weapon_attack(meet,panky)
# 但是上面这么做不好,利用武器攻击也是人类是动作的发起者,所以不能是pillow武器对象,而是人类利用武器攻击对方

所以,对代码进行修改。

class Gamerole:
   def __init__(self,name,ad,hp):
       self.name = name
       self.ad = ad
       self.hp = hp
   def attack(self,p1):
       p1.hp -= self.ad
       print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))

   def equip_weapon(self,wea):
       self.wea = wea  # 组合:给一个对象封装一个属性改属性是另一个类的对象
class Weapon:
   def __init__(self,name,ad):
       self.name = name
       self.ad = ad
   def weapon_attack(self,p1,p2):
       p2.hp = p2.hp - self.ad - p1.ad
       print('%s 利用 %s 攻击了%s,%s还剩%s血'
             %(p1.name,self.name,p2.name,p2.name,p2.hp))


# 实例化三个人物对象:
meet = Gamerole('太白',10,200)
panky = Gamerole('金莲',20,50)
pillow = Weapon('绣花枕头',2)

# 给人物装备武器对象。
meet.equip_weapon(pillow)

# 开始攻击
meet.wea.weapon_attack(meet,panky)

上面就是组合,只要是人物.equip_weapon这个方法,那么人物就封装了一个武器对象,再利用武器对象调用其类中的weapon_attack方法

 

第二十三章

Python 面向对象继承

一 什么是面向对象的继承

比较官方的说法就是:

继承(英语:inheritance)是面向对象软件技术当中的一个概念。

如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。

字面意思就是:子承父业,合法继承家产,就是如果你是独生子,而且你也很孝顺,不出意外,你会继承你父母所有家产,他们的所有财产都会由你使用(败家子儿除外)。

那么用一个例子来看一下继承:

class Person:
   def __init__(self,name,sex,age):
       self.name = name
       self.age = age
       self.sex = sex

class Cat:
   def __init__(self,name,sex,age):
       self.name = name
       self.age = age
       self.sex = sex

class Dog:
   def __init__(self,name,sex,age):
       self.name = name
       self.age = age
       self.sex = sex

# 继承的用法:
class Aniaml(object):
   def __init__(self,name,sex,age):
           self.name = name
           self.age = age
           self.sex = sex


class Person(Aniaml):
   pass

class Cat(Aniaml):
   pass

class Dog(Aniaml):
   pass

继承的有点也是显而易见的:

1,增加了类的耦合性(耦合性不宜多,宜精)。

2,减少了重复代码。

3,使得代码更加规范化,合理化。

二 继承的分类

就向上面的例子:

Aminal 叫做父类,基类,超类。 Person Cat Dog: 子类,派生类。 继承:可以分单继承,多继承

这里需要补充一下python中类的种类(继承需要):

在python2x版本中存在两种类.:   ⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.   ⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。 python3x版本中只有一种类: python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object

三 单继承

3.1 类名,对象执行父类方法

class Aniaml(object):
   type_name = '动物类'

   def __init__(self,name,sex,age):
           self.name = name
           self.age = age
           self.sex = sex

   def eat(self):
       print(self)
       print('吃东西')


class Person(Aniaml):
   pass


class Cat(Aniaml):
   pass


class Dog(Aniaml):
   pass

# 类名:
print(Person.type_name)  # 可以调用父类的属性,方法。
Person.eat(111)
print(Person.type_name)

# 对象:
# 实例化对象
p1 = Person('春哥','男',18)
print(p1.__dict__)
# 对象执行类的父类的属性,方法。
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()

3.2 执行顺序

class Aniaml(object):
   type_name = '动物类'
   def __init__(self,name,sex,age):
           self.name = name
           self.age = age
           self.sex = sex

   def eat(self):
       print(self)
       print('吃东西')

class Person(Aniaml):

   def eat(self):
       print('%s 吃饭'%self.name)

class Cat(Aniaml):
   pass

class Dog(Aniaml):
   pass

p1 = Person('barry','男',18)
# 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。
p1.eat()
# 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。

3.3同时执行类以及父类方法

方法一:

如果想执行父类的func方法,这个方法并且子类中夜用,那么就在子类的方法中写上:

父类.func(对象,其他参数)

举例说明:

class Aniaml(object):
   type_name = '动物类'
   def __init__(self,name,sex,age):
           self.name = name
           self.age = age
           self.sex = sex

   def eat(self):
       print('吃东西')

class Person(Aniaml):
   def __init__(self,name,sex,age,mind):
       '''
      self = p1
      name = '春哥'
      sex = 'laddboy'
      age = 18
      mind = '有思想'
      '''
       # Aniaml.__init__(self,name,sex,age) # 方法一
       self.mind = mind

   def eat(self):
       super().eat()
       print('%s 吃饭'%self.name)
class Cat(Aniaml):
   pass

class Dog(Aniaml):
   pass

# 方法一: Aniaml.__init__(self,name,sex,age)
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)

# 对于方法一如果不理解:
# def func(self):
#     print(self)
# self = 3
# func(self)

方法二:

利用super,super().func(参数)

class Aniaml(object):
   type_name = '动物类'
   def __init__(self,name,sex,age):
           self.name = name
           self.age = age
           self.sex = sex

   def eat(self):
       print('吃东西')

class Person(Aniaml):
   def __init__(self,name,sex,age,mind):
       '''
      self = p1
      name = '春哥'
      sex = 'laddboy'
      age = 18
      mind = '有思想'
      '''
       # super(Person,self).__init__(name,sex,age) # 方法二
       super().__init__(name,sex,age)  # 方法二
       self.mind = mind

   def eat(self):
       super().eat()
       print('%s 吃饭'%self.name)
class Cat(Aniaml):
   pass

class Dog(Aniaml):
   pass
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)

单继承的课堂练习

# 1
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)

class Foo(Base):
   pass
obj = Foo(123)
obj.func1() # 123 运⾏的是Base中的func1  

# 2      
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)
class Foo(Base):
   def func1(self):
       print("Foo. func1", self.num)
obj = Foo(123)
obj.func1() # Foo. func1 123 运⾏的是Foo中的func1      

# 3        
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)
class Foo(Base):
   def func1(self):
       print("Foo. func1", self.num)
obj = Foo(123)
obj.func1() # Foo. func1 123 运⾏的是Foo中的func1    
# 4
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)
       self.func2()
   def func2(self):
       print("Base.func2")
class Foo(Base):
   def func2(self):
   print("Foo.func2")
obj = Foo(123)
obj.func1() # 123 Foo.func2 func1是Base中的 func2是⼦类中的
# 再来
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)
       self.func2()
   def func2(self):
       print(111, self.num)
class Foo(Base):
   def func2(self):
       print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
   obj.func2() # 111 1 | 111 2 | 222 3

# 再来
class Base:
   def __init__(self, num):
       self.num = num
   def func1(self):
       print(self.num)
       self.func2()
   def func2(self):
       print(111, self.num)
class Foo(Base):
   def func2(self):
       print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
obj.func1() # 那笔来吧. 好好算

四 多继承

class ShenXian: # 神仙
   def fei(self):
       print("神仙都会⻜")
class Monkey: # 猴
   def chitao(self):
       print("猴⼦喜欢吃桃⼦")
class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是⼀只猴
   pass
sxz = SunWukong() # 孙悟空
sxz.chitao() # 会吃桃⼦
sxz.fei() # 会⻜

  此时, 孙悟空是⼀只猴⼦, 同时也是⼀个神仙. 那孙悟空继承了这两个类. 孙悟空⾃然就可以执⾏这两个类中的⽅法. 多继承⽤起来简单. 也很好理解. 但是多继承中, 存在着这样⼀个问题. 当两个⽗类中出现了重名⽅法的时候. 这时该怎么办呢? 这时就涉及到如何查找⽗类⽅法的这么⼀个问题.

即MRO(method resolution order) 问题. 在python中这是⼀个很复杂的问题. 因为在不同的python版本中使⽤的是不同的算法来完成MRO的.

4.1经典类的多继承

虽然在python3中已经不存在经典类了. 但是经典类的MRO最好还是学⼀学.

这是⼀种树形结构遍历的⼀个最直接的案例. 在python的继承体系中. 我们可以把类与类继承关系化成⼀个树形结构的图. 来, 上代码:

class A:
   pass
class B(A):
   pass
class C(A):
   pass
class D(B, C):
   pass
class E:
   pass
class F(D, E):
   pass
class G(F, D):
   pass
class H:
   pass
class Foo(H, G):
   pass

对付这种mro画图就可以:

继承关系图已经有了. 那如何进⾏查找呢?

记住⼀个原则. 在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.

图中每个圈都是准备要送鸡蛋的住址. 箭头和⿊线表⽰线路. 那送鸡蛋的顺序告诉你入⼝在最下⾯R. 并且必须从左往右送. 那怎么送呢?

如图. 肯定是按照123456这样的顺序来送. 那这样的顺序就叫 深度优先遍历.

⽽如果是142356呢? 这种被称为 ⼴度优先遍历.

好了. 深度优先就说这么多. 那么上⾯那个图怎么找的呢?

MRO是什么呢? 很简单. 记住. 从头开始. 从左往右. ⼀条路跑到头, 然后回头. 继续⼀条 路跑到头. 就是经典类的MRO算法.

类的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C. 你猜对了么?

4.2新式类的多继承

4.2.1 mro序列

MRO是一个有序列表L,在类被创建时就计算出来。 通用计算公式为:

mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )
(其中Child继承自Base1, Base2)

如果继承至一个基类:class B(A) 这时B的mro序列为

mro( B ) = mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]

如果继承至多个基类:class B(A1, A2, A3 …) 这时B的mro序列

mro(B) = mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...

计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。

4.2.2. 表头和表尾

表头:   列表的第一个元素

表尾:   列表中表头以外的元素集合(可以为空)

示例   列表:[A, B, C]   表头是A,表尾是B和C

4.2.3. 列表之间的+操作

+操作:

[A] + [B] = [A, B] (以下的计算中默认省略) ---------------------

merge操作示例:

如计算merge( [E,O], [C,E,F,O], [C] )
有三个列表 :  ①        ②      ③

1 merge不为空,取出第一个列表列表①的表头E,进行判断                              
   各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
2 取出列表②的表头C,进行判断
   C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
   merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 进行下一次新的merge操作 ......
---------------------

计算mro(A)方式:

mro(A) = mro( A(B,C) )

原式= [A] + merge( mro(B),mro(C),[B,C] )

  mro(B) = mro( B(D,E) )
         = [B] + merge( mro(D), mro(E), [D,E] )  # 多继承
         = [B] + merge( [D,O] , [E,O] , [D,E] )  # 单继承mro(D(O))=[D,O]
         = [B,D] + merge( [O] , [E,O]  ,  [E] )  # 拿出并删除D
         = [B,D,E] + merge([O] ,  [O])
         = [B,D,E,O]

  mro(C) = mro( C(E,F) )
         = [C] + merge( mro(E), mro(F), [E,F] )
         = [C] + merge( [E,O] , [F,O] , [E,F] )
         = [C,E] + merge( [O] , [F,O]  ,  [F] )  # 跳过O,拿出并删除
         = [C,E,F] + merge([O] ,  [O])
         = [C,E,F,O]

原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
    = [A,B] + merge( [D,E,O], [C,E,F,O],   [C])
    = [A,B,D] + merge( [E,O], [C,E,F,O],   [C])  # 跳过E
    = [A,B,D,C] + merge([E,O],  [E,F,O])
    = [A,B,D,C,E] + merge([O],    [F,O])  # 跳过O
    = [A,B,D,C,E,F] + merge([O],    [O])
    = [A,B,D,C,E,F,O]
---------------------

结果OK. 那既然python提供了. 为什么我们还要如此⿇烦的计算MRO呢? 因为笔 试.......你在笔试的时候, 是没有电脑的. 所以这个算法要知道. 并且简单的计算要会. 真是项⽬ 开发的时候很少有⼈这么去写代码.

这个说完了. 那C3到底怎么看更容易呢? 其实很简单. C3是把我们多个类产⽣的共同继 承留到最后去找. 所以. 我们也可以从图上来看到相关的规律. 这个要⼤家⾃⼰多写多画图就 能感觉到了. 但是如果没有所谓的共同继承关系. 那⼏乎就当成是深度遍历就可以了

 

 

 

第二十四章

 

 

Python面向对象三大特性

一.封装

把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装.

在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.

封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。

所以,在使用面向对象的封装特性时,需要:

  • 将内容封装到某处

  • 从某处调用被封装的内容

第一步:将内容封装到某处

self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1

当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2

所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。

第二步:从某处调用被封装的内容

调用被封装的内容时,有两种情况:

  • 通过对象直接调用

  • 通过self间接调用

1、通过对象直接调用被封装的内容

上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名

class Foo:
    def __init__(self, name, age):
        self.name = name
        self.age = age
obj1 = Foo('wupeiqi', 18)
print obj1.name    # 直接调用obj1对象的name属性
print obj1.age     # 直接调用obj1对象的age属性
obj2 = Foo('alex', 73)
print obj2.name    # 直接调用obj2对象的name属性
print obj2.age     # 直接调用obj2对象的age属性

2、通过self间接调用被封装的内容

执行类中的方法时,需要通过self间接调用被封装的内容

class Foo:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def detail(self):
        print self.name
        print self.age

obj1 = Foo('wupeiqi', 18)
obj1.detail()  # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18

obj2 = Foo('alex', 73)
obj2.detail()  # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78

综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容

二.继承

⼦类可以⾃动拥有⽗类中 除了私有属性外的其他所有内容.

在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系.

那么什么情况可以使⽤继承呢?

单纯的从代码层⾯上来看.

两个类具有相同的功能 或者 特征 的时候. 可以采⽤继承的形式. 提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码.

如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类.

三.多态

同⼀个对象, 多种形态.

这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说.

比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型.

但是我们可以通过程序让a = "alex", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但 是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。

多态,同一个对象,多种形态。 python默认支持多态

鸭子类型

python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
对于代码上的解释其实很简答:
class A:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
class B:
    def f1(self):
        print('in A f1')
    def f2(self):
        print('in A f2')
obj = A()
obj.f1()
obj.f2()
obj2 = B()
obj2.f1()
obj2.f2()
# A 和 B两个类完全没有耦合性,但是在某种意义上他们却统一了一个标准。
# 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互成为鸭子类型。
# 这样的例子比比皆是:str  tuple list 都有 index方法,这就是统一了规范。
# str bytes 等等 这就是互称为鸭子类型。

四.类的约束

⾸先, 你要清楚. 约束是对类的约束.

用一个例子说话:

公司让小明给他们的网站完善一个支付功能,小明写了两个类,如下:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(200)

但是上面这样写不太放方便,也不合理,老大说让他整改,统一一下付款的方式,小明开始加班整理:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)
class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)
def pay(obj,money):  # 这个函数就是统一支付规则,这个叫做: 归一化设计。
    obj.pay(money)
a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

写了半年的接口,小明终于接了大项目了,结果公司没品位,招了一个野生的程序员春哥接替小明的工作,老大给春哥安排了任务,让他写一个微信支付的功能:

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay:  # 野生程序员一般不会看别人怎么写,自己才是最好,结果......
    def fuqian(self,money):
        print('使用微信支付%s元' % money)

def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

结果春哥,受惩罚了,限期整改,那么春哥,发奋图强,python的相关资料,重新梳理的代码:

class Payment: 
  """ 此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
   """
    def pay(self,money):pass

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)


def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

但是,这样还会有问题,如果再来野生程序员,他不看其他的支付方式,也不知道为什么继承的类中要定义一个没有意义的方法,所以他会是会我行我素:

class Payment: 
  """ 此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
   """
    def pay(self,money):pass

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)


def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

所以此时我们要用到对类的约束,

对类的约束有两种:

1.提取⽗类. 然后在⽗类中定义好⽅法. 在这个⽅法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.

2.使⽤元类来描述⽗类. 在元类中给出⼀个抽象⽅法. 这样⼦类就不得不给出抽象⽅法的具体实现. 也可以起到约束的效果.

先用第一种方式解决:

class Payment:
    """
    此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
    """
    def pay(self,money):
        raise Exception("你没有实现pay方法")

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)

def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)

第二种方式:引入抽象类的概念处理

from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass  # 抽象方法

class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)

class Wechatpay(Payment):
    # def pay(self,money):
    #     print('使用微信支付了%s元'%money)
    def recharge(self):pass

def pay(a,money):
    a.pay(money)

a = Alipay()
a.pay(100)
pay(a,100)    # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100)   # 到用的时候才会报错



# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta,
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范

总结: 约束. 其实就是⽗类对⼦类进⾏约束. ⼦类必须要写xxx⽅法. 在python中约束的⽅式和⽅法有两种:

1. 使⽤抽象类和抽象⽅法, 由于该⽅案来源是java和c#. 所以使⽤频率还是很少的

2. 使⽤⼈为抛出异常的⽅案. 并且尽量抛出的是NotImplementError. 这样比较专业, ⽽且错误比较明确.(推荐)

五.super

super是严格按照类的继承顺序执行!!!

class A:
    def f1(self):
        print('in A f1')
    def f2(self):

        print('in A f2')
class Foo(A):
    def f1(self):
        super().f2()
        print('in A Foo')
obj = Foo()
obj.f1()
super可以下一个类的其他方法
super()严格按照类的mro顺序执行
class A:
    def f1(self):
        print('in A')

class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')

class Bar(A):
    def f1(self):
        print('in Bar')

class Info(Foo,Bar):
    def f1(self):
        super().f1()
        print('in Info f1')

obj = Info()
obj.f1()

'''
in Bar
in Foo
in Info f1
'''
print(Info.mro())  # [<class '__main__.Info'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <class '__main__.A'>, <class 'object'>]

 

 

 

 

 

 

 

第二十五章

 

Python面向对象之类成员

一.细分类的组成成员

之前咱们讲过类大致分两块区域,如下图所示:

每个区域详细划分又可以分为:

class A:

    company_name = '老男孩教育'  # 静态变量(静态字段)
    __iphone = '1353333xxxx'  # 私有静态变量(私有静态字段)


    def __init__(self,name,age): #特殊方法

        self.name = name  #对象属性(普通字段)
        self.__age = age  # 私有对象属性(私有普通字段)

    def func1(self):  # 普通方法
        pass

    def __func(self): #私有方法
        print(666)


    @classmethod  # 类方法
    def class_func(cls):
        """ 定义类方法,至少有一个cls参数 """
        print('类方法')

    @staticmethod  #静态方法
    def static_func():
        """ 定义静态方法 ,无默认参数"""
        print('静态方法')

    @property  # 属性
    def prop(self):
        pass

二. 类的私有成员

对于每一个类的成员而言都有两种形式:

  • 公有成员,在任何地方都能访问

  • 私有成员,只有在类的内部才能方法

私有成员和公有成员的访问限制不同

静态字段(静态属性)

  • 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问

  • 私有静态字段:仅类内部可以访问;

2.1 公有静态属性(字段)

class C:
    name = "公有静态字段"
    def func(self):
        print C.name
class D(C):
    def show(self):
        print C.name
C.name         # 类访问
obj = C()
obj.func()     # 类内部可以访问
obj_son = D()
obj_son.show() # 派生类中可以访问
公有静态字段

2.2 私有静态属性(字段)

class C:
    __name = "私有静态字段"
    def func(self):
        print C.__name
class D(C):
    def show(self):
        print C.__name
C.__name       # 不可在外部访问
obj = C()
obj.__name  # 不可在外部访问
obj.func()     # 类内部可以访问   
obj_son = D()
obj_son.show() #不可在派生类中可以访问  
私有静态字段

普通字段(对象属性)

  • 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问

  • 私有普通字段:仅类内部可以访问;

2.3 公有普通字段

class C:

    def __init__(self):
        self.foo = "公有字段"

    def func(self):
        print self.foo  # 类内部访问

class D(C):

    def show(self):
        print self.foo # 派生类中访问

obj = C()

obj.foo     # 通过对象访问
obj.func()  # 类内部访问

obj_son = D();
obj_son.show()  # 派生类中访问

2.4 私有对象属性

class C:

    def __init__(self):
        self.__foo = "私有字段"

    def func(self):
        print self.foo  # 类内部访问

class D(C):

    def show(self):
        print self.foo # 派生类中访问

obj = C()

obj.__foo     # 通过对象访问    ==> 错误
obj.func()  # 类内部访问        ==> 正确

obj_son = D();
obj_son.show()  # 派生类中访问  ==> 错误

私有普通字段

方法:

公有方法:对象可以访问;类内部可以访问;派生类中可以访问 私有方法:仅类内部可以访问;

2.5 公有方法

class C:

    def __init__(self):
        pass

    def add(self):
        print('in C')

class D(C):

    def show(self):
        print('in D')

    def func(self):
        self.show()
obj = D()
obj.show()  # 通过对象访问   
obj.func()  # 类内部访问    
obj.add()  # 派生类中访问

2.6 私有方法

class C:

    def __init__(self):
        pass

    def __add(self):
        print('in C')

class D(C):

    def __show(self):
        print('in D')

    def func(self):
        self.__show()
obj = D()
obj.__show()  # 通过不能对象访问
obj.func()  # 类内部可以访问
obj.__add()  # 派生类中不能访问

总结:

对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.

ps:非要访问私有成员的话,可以通过 对象.类_属性名,但是绝对不允许!!!

为什么可以通过.类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上类名.

三. 类的其他成员

这里的其他成员主要就是类方法:

方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。

实例方法

定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);

调用:只能由实例对象调用。

类方法

定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);

调用:实例对象和类对象都可以调用。

静态方法

定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;

调用:实例对象和类对象都可以调用。

双下方法(后面会讲到)

 定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加爽下划线 方法名的具有特殊意义的方法,双下方法主要是python源码程序员使用的,

    我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。

 调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:init

3.1 类方法

使用装饰器@classmethod。

原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。

如下场景:

假设我有一个学生类和一个班级类,想要实现的功能为: 执行班级人数增加的操作、获得班级的总人数; 学生类继承自班级类,每实例化一个学生,班级人数都能增加; 最后,我想定义一些学生,获得班级中的总人数。

思考:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。

class Student:

    __num = 0
    def __init__(self,name,age):
        self.name = name
        self.age= age
        Student.addNum()  # 写在__new__方法中比较合适,但是现在还没有学,暂且放到这里

    @classmethod
    def addNum(cls):
        cls.__num += 1

    @classmethod
    def getNum(cls):
        return cls.__num



a = Student('宝元', 18)
b = Student('武sir', 36)
c = Student('alex', 73)
print(Student.getNum())

3.2 静态方法

使用装饰器@staticmethod

静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。

譬如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数。

import time

class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())


print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)

如上,使用了静态方法(函数),然而方法体中并没使用(也不能使用)类或实例的属性(或方法)。若要获得当前时间的字符串时,并不一定需要实例化对象,此时对于静态方法而言,所在类更像是一种名称空间。

其实,我们也可以在类外面写一个同样的函数来做这些事,但是这样做就打乱了逻辑关系,也会导致以后代码维护困难。

3.3 属性

什么是特性property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
  体质指数(BMI)=体重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86

例一代码

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People('meet',100,1.85)
print(p1.bmi)

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Foo:
    @property
    def AAA(self):
        print('get的时候运行我啊')

    @AAA.setter
    def AAA(self,value):
        print('set的时候运行我啊')

    @AAA.deleter
    def AAA(self):
        print('delete的时候运行我啊')

#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

或者:
class Foo:
    def get_AAA(self):
        print('get的时候运行我啊')

    def set_AAA(self,value):
        print('set的时候运行我啊')

    def delete_AAA(self):
        print('delete的时候运行我啊')
    AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应

f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

商品示例:

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deltter
    def price(self, value):
        del self.original_price

obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

四. isinstace 与 issubclass

class A:
    pass

class B(A):
    pass

obj = B()


print(isinstance(obj,B))
print(isinstance(obj,A))

4.1 isinstance(a,b)

判断a是否是b类(或者b类的派生类)实例化的对象

class A:
    pass

class B(A):
    pass

class C(B):
    pass

print(issubclass(B,A))
print(issubclass(C,A))

4.2 issubclass(a,b)

判断a类是否是b类(或者b的派生类)的派生类

思考:那么 list str tuple dict等这些类与 Iterble类 的关系是什么?

from collections import Iterable

print(isinstance([1,2,3], list))  # True
print(isinstance([1,2,3], Iterable))  # True
print(issubclass(list,Iterable))  # True

# 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类。

 

 

 

第二十六章

 

Python 反射及双下方法

一.反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个可以实现自省的函数

下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

1.1 对象的反射

class Foo:
    f = '类的静态变量'
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hi(self):
        print('hi,%s'%self.name)

obj=Foo('egon',73)

#检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))

#获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()

print(getattr(obj,'aaaaaaaa','不存在啊')) #报错

#设置属性
setattr(obj,'sb',True)
setattr(obj,'show_name',lambda self:self.name+'sb')
print(obj.__dict__)
print(obj.show_name(obj))

#删除属性
delattr(obj,'age')
delattr(obj,'show_name')
delattr(obj,'show_name111')#不存在,则报错

print(obj.__dict__)

对实例化对象的示例

1.2 对类的反射

class Foo(object):
    staticField = "old boy"
    def __init__(self):
        self.name = 'wupeiqi'
    def func(self):
        return 'func'
    @staticmethod
    def bar():
        return 'bar'
print ( getattr(Foo, 'staticField'))
print (getattr(Foo, 'func'))
print (getattr(Foo, 'bar'))

1.3 当前模块的反射

import sys
def s1():
    print ('s1')
def s2():
    print ('s2')
this_module = sys.modules[__name__]
hasattr(this_module, 's1')
getattr(this_module, 's2')

1.4 其他模块的反射

#一个模块中的代码
def test():
    print('from the test')
"""
程序目录:
    module_test.py
    index.py
当前文件:
    index.py
"""
# 另一个模块中的代码
import module_test as obj
#obj.test()
print(hasattr(obj,'test'))
getattr(obj,'test')()

1.5 反射的应用

了解了反射的四个函数。那么反射到底有什么用呢?它的应用场景是什么呢?

现在让我们打开浏览器,访问一个网站,你单击登录就跳转到登录界面,你单击注册就跳转到注册界面,等等,其实你单击的其实是一个个的链接,每一个链接都会有一个函数或者方法来处理。

没学反射之前的解决方式

class User:
    def login(self):
        print('欢迎来到登录页面')
    def register(self):
        print('欢迎来到注册页面')
    def save(self):
        print('欢迎来到存储页面')

while 1:
    choose = input('>>>').strip()
    if choose == 'login':
        obj = User()
        obj.login()

    elif choose == 'register':
        obj = User()
        obj.register()

    elif choose == 'save':
        obj = User()
        obj.save()

学了反射之后解决方式

class User:
    def login(self):
        print('欢迎来到登录页面')

    def register(self):
        print('欢迎来到注册页面')

    def save(self):
        print('欢迎来到存储页面')

user = User()
while 1:
    choose = input('>>>').strip()
    if hasattr(user,choose):
        func = getattr(user,choose)
        func()
    else:
        print('输入错误。。。。')

这样就可以明确的感觉到反射的好处

二. 双下方法

定义:双下方法是特殊方法,他是解释器提供的 由双下划线加方法名加双下划线 方法名的具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。

调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:init

3.1 len

class B:
    def __len__(self):
        print(666)

b = B()
len(b) # len 一个对象就会触发 __len__方法。

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __len__(self):
        return len(self.__dict__)
a = A()
print(len(a))

3.2 hash

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __hash__(self):
        return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))

3.3 str

如果一个类中定义了str方法,那么在打印 对象 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __str__(self):
        return '宝元'
a = A()
print(a)
print('%s' % a)

3.4 repr

如果一个类中定义了repr方法,那么在repr(对象) 时,默认输出该方法的返回值。

class A:
    def __init__(self):
        pass
    def __repr__(self):
        return '宝元'
a = A()
print(repr(a))
print('%r'%a)

3.5 call

对象后面加括号,触发执行。

注:构造方法new的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class Foo:

    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__

3.6 eq

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __eq__(self,obj):
        if  self.a == obj.a and self.b == obj.b:
            return True
a = A()
b = A()
print(a == b)

3.7 del

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

3.8 new

class A:
    def __init__(self):
        self.x = 1
        print('in init function')
    def __new__(cls, *args, **kwargs):
        print('in new function')
        return object.__new__(A, *args, **kwargs)
a = A()
print(a.x)

使用new实现单例模式

class A:
    __instance = None
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            obj = object.__new__(cls)
            cls.__instance = obj
        return cls.__instance

单例模式

单例模式具体分析:

单例模式是一种常用的软件设计模式。

在它的核心结构中只包含一个被称为单例类的特殊类。

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

【采用单例模式动机、原因】 对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。

如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。

如何保证一个类只有一个实例并且这个实例易于被访问呢?

定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

【单例模式优缺点】

【优点】

一、实例控制 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性 因为类控制了实例化过程,所以类可以灵活更改实例化过程。

【缺点】

一、开销 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期 不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用

3.9 item系列

class Foo:
    def __init__(self,name):
        self.name=name
        
    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        self.__dict__.pop(item)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-09-02 18:20  xiongsheng  阅读(743)  评论(0编辑  收藏  举报