0%

模块

module:一个 .py 文件就是个 module

lib:抽象概念,和另外两个不zhidao是一类,只要你喜欢,什么都是 lib,就算只有个 hello world

package:就是个带 init.py 的文件夹,并不在乎里内面有什么,不过一般来讲会包含一些 packages/modules

scrapy、flask、Django、numpy、scipy、NLTK、jieba一般都被认为是 lib,因为关注点并不是代码是怎么组织容的。

  1. Python 程序由模块组成。一个模块对应 python 源文件,一般后缀名是:.py。
  2. 模块由语句组成。运行 Python 程序时,按照模块中语句的顺序依次执行。
  3. 语句是 Python 程序的构造单元,用于创建对象、变量赋值、调用函数、控制语句等。

1.python模块是:

python模块:包含并且有组织的代码片段为模块。

表现形式为:写的代码保存为文件。这个文件就是一个模块。sample.py 其中文件名smaple为模块名字。

关系图:

img

2.python包是:

包是一个有层次的文件目录结构,它定义了由n个模块或n个子包组成的python应用程序执行环境。通俗一点:包是一个包含init.py 文件的目录,该目录下一定得有这个init.py文件和其它e68a84e79fa5e9819331333365656638模块或子包。

常见问题:

引入某一特定路径下的模块

使用sys.path.append(yourmodulepath)

将一个路径加入到python系统路径下,避免每次通过代码指定路径

利用系统环境变量 export PYTHONPATH=$PYTHONPATH:yourmodulepath,

直接将这个路径链接到类似/Library/Python/2.7/site-packages目录下

好的建议:

经常使用if name == ‘main‘,保证写包既可以import又可以独立运行,用于test。

多次import不会多次执行模块,只会执行一次。可以使用reload来强制运行模块,但不提倡。

常见的包结构如下:

package_a├── init.py├── module_a1.py└── module_a2.pypackage_b├── init.py├── module_b1.py└── module_b2.py

main.py

如果main.py想要引用packagea中的模块modulea1,可以使用:

from package_a import module_a1

import package_a.module_a1

如果packagea中的modulea1需要引用packageb,那么默认情况下,python是找不到packageb。我们可以使用sys.path.append(‘../‘),可以在packagea中的init.py添加这句话,然后该包下得所有module都添加* import __init_即可。

关系图:

img

3.库(library)

库的概念是具有相关功能模块的集合。这也是Python的一大特色之一,即具有强大的标准库、第三方库以及自定义模块。

1.2 标准库模块(standard library)

与函数类似,模块也分为标准库模块和用户自定义模块。
Python 标准库提供了操作系统功能、网络通信、文本处理、文件处理、数学运算等基 本的功能。比如:random(随机数)、math(数学运算)、time(时间处理)、file(文件处理)、 os(和操作系统交互)、sys(和解释器交互)等。

另外,Python 还提供了海量的第三方模块,使用方式和标准库类似。功能覆盖了我们 能想象到的所有领域,比如:科学计算、WEB 开发、大数据、人工智能、图形系统等。

模块化编程有如下几个重要优势:
\1. 便于将一个任务分解成多个模块,实现团队协同开发,完成大规模程序 2. 实现代码复用。一个模块实现后,可以被反复调用。
\3. 可维护性增强。

1.4 模块化编程的流程

模块化编程的一般流程:
\1. 设计 API,进行功能描述

\2. 编码实现 API 中描述的功能。
\3. 在模块中编写测试代码,并消除全局代码。
\4. 使用私有函数实现不被外部客户端调用的模块函数。

API(Application Programming Interface 应用程序编程接口)是用于描述模 块中提供的函数和类的功能描述和使用方式描述。

模块化编程中,首先设计的就是模块的 API(即要实现的功能描述),然后开始编 码实现 API 中描述的功能。最后,在其他模块中导入本模块进行调用。

我们可以通过help(模块名)查看模块的API。一般使用时先导入模块 然后通过help函数查看

1
2
import math
help(math)

设计计算薪水模块的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"""
本模块用于计算公司员工的薪资
"""
company = "北京尚学堂"

def yearSalary(monthSalary):

"""根据传入的月薪,计算出年薪"""

pass

def daySalary(monthSalary):
"""根据传入的月薪,计算出每天的薪资"""

pass

如上模块只有功能描述和规范,需要编码人员按照要求实现编码。 我们可以通过doc可以获得模块的文档字符串的内容。

test.py 的源代码:

1
2
3
4
5
import salary

print(salary.__doc__)

print(salary.yearSalary.__doc__)

运行结果:

本模块用于计算公司员工的薪资

根据传入的月薪,计算出年薪

模块的创建和测试代码

每个模块都有一个名称,通过特殊变量name可以获取模块的名称。在正常情况下,模块名字对应源文件名。 仅有一个例外,就是当一个模块被作为程序入口时(主 程序、交互式提示符下),它的name的值为“main”。我们可以根据这个特 点,将模块源代码文件中的测试代码进行独立的处理。例如:

1
2
3
 import math

math.__name__ #输出'math'
1
2
3
4
5
6
7
8
9
10
company = "北京尚学堂"
def yearSalary(monthSalary):
"""根据传入的月薪,计算出年薪"""
return monthSalary*12
def daySalary(monthSalary):
"""根据传入的月薪,计算出每天的薪资"""
return monthSalary/22.5 #国家规定每个月的平均工作日是 22.5
if __name__ =="__main__": #测试代码
print(yearSalary(3000))
print(daySalary(3000))

1.7 模块文档字符串和 API 设计

我们可以在模块的第一行增加一个文档字符串,用于描述模块的相关功能。然后,通过 doc可以获得文档字符串的内容。

2. 模块的导入

模块化设计的好处之一就是“代码复用性高”。写好的模块可以被反复调用,重复使用。 模块的导入就是“在本模块中使用其他模块”。

2.1 import 语句导入

import 语句的基本语法格式如下: import 模块名

import 模块 1,模块 2… import 模块名 as 模块别名

#导入一个模块 #导入多个模块

#导入模块并使用新名字

import 加载的模块分为四个通用类别:
a.使用 python 编写的代码(.py 文件); b.已被编译为共享库或 DLL 的 C 或 C++扩展; c.包好一组模块的包
d.使用 C 编写并链接到 python 解释器的内置模块;

我们一般通过 import 语句实现模块的导入和使用,import 本质上是使用了内置函数 import()。

当我们通过 import 导入一个模块时,python 解释器进行执行,最终会生成一个对象, 这个对象就代表了被加载的模块。

我们可以看到 math 模块被加载后,实际会生成一个 module 类的对象,该对象被 math 变量引用。我们可以通过 math 变量引用模块中所有的内容。

我们通过 import 导入多个模块,本质上也是生成多个 module 类的对象而已。

有时候,我们也需要给模块起个别名,本质上,这个别名仅仅是新创建一个变量引用加 载的模块对象而已。

2.2 from…import 导入

Python 中可以使用 from…import 导入模块中的成员。基本语法格式如下: from 模块名 import 成员 1,成员 2,…

如果希望导入一个模块中的所有成员,则可以采用如下方式: from 模块名 import *

【注】尽量避免“from 模块名 import ”这种写法。 它表示导入模块中所有的不 是以下划线(_)开头的名字都导入到当前位置。 但你不知道你导入什么名字,很有可能 会覆盖掉你之前已经定义的名字。而且可读性极其的差。一般生产环境中尽量避免使用, 学习时没有关系。

2.3 import 语句和 from…import 语句的区别

import 导入的是模块。from…import 导入的是模块中的一个函数/一个类。

如果进行类比的话,import 导入的是“文件”,我们要使用该“文件”下的内容,必 须前面加“文件名称”。from…import 导入的是文件下的“内容”,我们直接使用这 些“内容”即可,前面再也不需要加“文件名称”了。

2.4 import()动态导入

import 语句本质上就是调用内置函数import(),我们可以通过它实现动态导入。给 import()动态传递不同的的参数值,就能导入不同的模块。

1
2
3
s = "math"
m = __import__(s) #导入后生成的模块对象的引用给变量 m
print(m.pi)

注意:一般不建议我们自行使用import()导入,其行为在 python2 和 python3 中 有差异,会导致意外错误。如果需要动态导入可以使用 importlib 模块。

1
2
3
import importlib
a = importlib.import_module("math")
print(a.pi)

2.5 模块的加载问题

当导入一个模块时, 模块中的代码都会被执行。不过,如果再次导入这个模块, 则不会再次执行。

Python 的设计者为什么这么设计?因为,导入模块更多的时候需要的是定义模块 中的变量、函数、对象等。这些并不需要反复定义和执行。“只导入一次 import-only-once”就成了一种优化。

一个模块无论导入多少次,这个模块在整个解释器进程内有且仅有一个实例对象。

重新加载 有时候我们确实需要重新加载一个模块,这时候可以使用:**importlib.reload()**

方法:

1
2
3
4
5
import test02
import test02
print("####")
import importlib
importlib.reload(test02)

3. 包 package 的使用

3.1 包(package)的概念和结构

1
当一个项目中有很多个模块时,需要再进行组织。我们将功能类似的模块放到一起, 形成了“包”。本质上,“包”就是一个必须有__init__.py 的文件夹。典型结构如下:

包下面可以包含“模块(module)”,也可以再包含“子包(subpackage)”。就像文件 夹下面可以有文件,也可以有子文件夹一样。

1
,a 是上层的包,下面有一个子包:aa。可以看到每个包里面都有__init__.py 文件。

3.2 pycharm 中创建包

在 pycharm 开发环境中创建包,非常简单。在要创建包的地方单击右键:New–>Python package 即可。pycharm 会自动帮助我们生成带有init.py 文件的包。

3.3 导入包操作和本质

上一节中的包结构,我们需要导入 module_AA.py。方式如下:

  1. import a.aa.module_AA

    在使用时,必须加完整名称来引用,比如:a.aa.module_AA.fun_AA()

  2. from a.aa import module_AA

    在使用时,直接可以使用模块名。 比如:module_AA.fun_AA()

  3. from a.aa.module_AA import fun_AA 直接导入函数

    在使用时,直接可以使用函数名。 比如:fun_AA()

    【注】
    \1. from package import item 这种语法中,item 可以是包、模块,也可以是函数、

    类、变量。
    \2. import item1.item2 这种语法中,item 必须是包或模块,不能是其他。

1
2
3
4
5
6
导入包的本质其实是“导入了包的__init__.py”文件。也就是说,”import pack1”意味 着执行了包 pack1 下面的__init__.py 文件。 这样,可以在__init__.py 中批量导入我们需要 的模块,而不再需要一个个导入。

__init__.py 的三个核心作用:
1. 作为包的标识,不能删除。
2. 用来实现模糊导入
3. 导入包实质是执行__init__.py 文件,可以在__init__.py 文件中做这个包的初始化、以及 需要统一执行代码、批量导入。

可以说“包的本质还是模块”。

3.4 用*导入包

import * 这样的语句理论上是希望文件系统找出包中所有的子模块,然后导入它们。 这可能会花长时间等。Python 解决方案是提供一个明确的包索引。

这个索引由 init.py 定义 all 变量,该变量为一列表,如上例 a 包下的 init.py 中,可定义 all = [“module_A”,”module_A2”]

这意味着, from sound.effects import * 会从对应的包中导入以上两个子模块;

【注】尽管提供 import * 的方法,仍不建议在生产代码中使用这种写法。

3.5 包内引用

如果是子包内的引用,可以按相对位置引入子模块 以 aa 包下的 module_AA 中导入 a 包下内容为例:

from .. import module_A #..表示上级目录 .表示同级目录

from . import module_A2 #.表示同级目录

3.6 sys.path 和模块搜索路径

当我们导入某个模块文件时, Python 解释器去哪里找这个文件呢?只有找到这个文 件才能读取、装载运行该模块文件。它一般按照如下路径寻找模块文件(按照顺序寻找,找 到即停不继续往下寻找):

\1. 内置模块
\2. 当前目录
\3. 程序的主目录
\4. pythonpath 目录(如果已经设置了 pythonpath 环境变量) 5. 标准链接库目录
\6. 第三方库目录(site-packages 目录)
\7. .pth 文件的内容(如果存在的话)
\8. sys.path.append()临时添加的目录

当任何一个 python 程序启动时,就将上面这些搜索路径(除内置模块以外的路径)进行收集, 放到 sys 模块的 path 属性中(sys.path)。

使用 sys.path 查看和临时修改搜索路径

1
2
3
import sys 
sys.path.append("d:/")
print(sys.path)

4. 模块发布和安装

4.1 模块的本地发布**

当我们完成了某个模块开发后,可以将他对外发布,其他开发者也可以以“第三方扩展 库”的方式使用我们的模块。我们按照如下步骤即可实现模块的发布:

1.为模块文件创建如下结构的文件夹(一般,文件夹的名字和模块的名字一样):

2.在文件夹中创建一个名为『setup.py』的文件

\3. 构建一个发布文件。通过终端,cd 到模块文件夹 c 下面,再键入命令:

python setup.py sdist

4.3 上传模块到 PyPI

将自己开发好的模块上传到 PyPI 网站上,将成为公开的资源,可以让全球用户自由使 用。按照如下步骤做,很容易就实现上传模块操作。

·管理你的模块

4.4 让别人使用你的模块

模块发布完成后,其他人只需要使用 pip 就可以安装你的模块文件。比如:

pip install package-name

5. 库(Library)

Python 中库是借用其他编程语言的概念,没有特别具体的定义。模块和包侧重于代码 组织,有明确的定义。

一般情况,库强调的是功能性,而不是代码组织。我们通常将某个功能的“模块的集合”, 称为库。

5.1 标准库(Standard Library)

Python 拥有一个强大的标准库。Python 语言的核心只包含数字、字符串、列表、字典、 文件等常见类型和函数,而由 Python 标准库提供了系统管理、网络通信、文本处理、数据 库接口、图形系统、XML 处理等额外的功能。

Python 标准库的主要功能有:
\1. 文本处理,包含文本格式化、正则表达式匹配、文本差异计算与合并、Unicode 支 持,二进制数据处理等功能
\2. 文件处理,包含文件操作、创建临时文件、文件压缩与归档、操作配置文件等功能

\3. 操作系统功能,包含线程与进程支持、IO 复用、日期与时间处理、调用系统函数、 日志(logging)等功能
\4. 网络通信,包含网络套接字,SSL 加密通信、异步网络通信等功能
\5. 网络协议,支持 HTTP,FTP,SMTP,POP,IMAP,NNTP,XMLRPC 等多种网 络协议,并提供了编写网络服务器的框架

\6. W3C 格式支持,包含 HTML,SGML,XML 的处理
\7. 其它功能,包括国际化支持、数学运算、HASH、Tkinter 等

目前学过的有:random、math、time、file、os、sys 等模块。可以通过 random 模 块实现随机数处理、math 模块实现数学相关的运算、time 模块实现时间的处理、file 模块 实现对文件的操作、OS 模块实现和操作系统的交互、sys 模块实现和解释器的交互。

5.2 第三方扩展库的介绍

强大的标准库奠定了 python 发展的基石,丰富和不断扩展的第三方库是 python 壮大 的保证。我们可以进入 PyPI 官网:

常用第三方库大汇总

分类 库名称 说明
环境管理 P 非常简单的交互式 python 版本管理工具
Pyenv 简单的 Python 版本管理工具
Vex 可以在虚拟环境中执行命令
Virtualenv virtualenvwrapp 创建独立 Python 环境的工具

尚学堂·百战程序员 www.itbaizhan.cn

er
包管理 pip Python 包和依赖关系管理工具
pip-tools 保证 Python 包依赖关系更新的一组工具
Pipenv Python 官方推荐的新一代包管理工具
Poetry 可完全取代 setup.py 的包管理工具
包仓库 warehouse 下一代 PyPI
Devpi PyPI 服务和打包/测试/分发工具
分发 (打包为可执行文件 以便分发) PyInstaller 将 Python 程序转成独立的执行文件(跨平台)
Nuitka 将脚本、模块、包编译成可执行文件或扩展模块
py2app 将 Python 脚本变为独立软件包(Mac OS X)
py2exe 将 Python 脚本变为独立软件包(Windows)
pynsist 一个用来创建 Windows 安装程序的工具,可 以在安装程序中打包 Python 本身
构建工具 (将源码编译成软件) Buildout 构建系统,从多个组件来创建,组装和部署应用
BitBake 针对嵌入式 Linux 的类似 make 的构建工具
Fabricate 对任何语言自动找到依赖关系的构建工具
交互式 Python 解 析器 IPython 功能丰富的工具,非常有效的使用交互式 Python
bpython 界面丰富的 Python 解析器
Ptpython 高级交互式 Python 解析器,构建 于 python-prompt-toolkit 之上
文件管理 Aiofiles 基于 asyncio,提供文件异步操作
Imghdr (Python 标准库)检测图片类型
Mimetypes (Python 标准库)将文件名映射为 MIME 类型
path.py 对 os.path 进行封装的模块
Pathlib (Python3.4+ 标准库)跨平台的、面向对象的 路径操作库
Unipath 用面向对象的方式操作文件和目录
Watchdog 管理文件系统事件的 API 和 shell 工具
日期和时间 Arrow 更好的 Python 日期时间操作类库
Chronyk 解析手写格式的时间和日期
Dateutil Python datetime 模块的扩展
PyTime 一个简单易用的 Python 模块,用于通过字符 串来操作日期/时间
when.py 提供用户友好的函数来帮助用户进行常用的日 期和时间操作
文本处理 chardet 字符编码检测器,兼容 Python2 和 Python3

尚学堂·百战程序员 www.itbaizhan.cn

Difflib (Python 标准库)帮助我们进行差异化比较
Fuzzywuzzy 模糊字符串匹配
Levenshtein 快速计算编辑距离以及字符串的相似度
Pypinyin 汉字拼音转换工具 Python 版
Shortuuid 一个生成器库,用以生成简洁的,明白的,URL 安全的 UUID
simplejson Python 的 JSON 编码、解码器
Unidecode Unicode 文本的 ASCII 转换形式
Xpinyin 一个用于把汉字转换为拼音的库
Pygment 通用语法高亮工具
Phonenumbers 解析,格式化,储存,验证电话号码
Sqlparse 一个无验证的 SQL 解析器
特殊文本格式处理 Tablib 一个用来处理中表格数据的模块
Pyexcel 用来读写,操作 Excel 文件的库
python-docx 读取,查询以及修改 word 文件
PDFMiner 一个用于从 PDF 文档中抽取信息的工具
Python-Markdo wn2 纯 Python 实现的 Markdown 解析器
Csvkit 用于转换和操作 CSV 的工具
自然语言处理 NLTK 一个先进的平台,用以构建处理人类语言数据的 Python 程序
Jieba 中文分词工具
langid.py 独立的语言识别系统
SnowNLP 一个用来处理中文文本的库
Thulac 清华大学自然语言处理与社会人文计算实验室 研制推出的一套中文词法分析工具包
下载器 you-get 一个 YouTube/Youku/Niconico 视频下载器
图像处理 pillow 最常用的图像处理库
imgSeek 一个使用视觉相似性搜索一组图片集合的项目
face_recognition 简单易用的 python 人脸识别
python-qrcode 一个纯 Python 实现的二维码生成器
OCR Pyocr Tesseract 和 Cuneiform 的 一 个 封 装 (wrapper)
pytesseract Google Tesseract OCR 的 另 一 个 封 装

尚学堂·百战程序员 www.itbaizhan.cn

(wrapper)
音频处理 Audiolazy Python 的数字信号处理包
Dejavu 音频指纹提取和识别
id3reader 一个用来读取 MP3 元数据的 Python 模块
TimeSide 开源 web 音频处理框架
Tinytag 一个用来读取 MP3, OGG, FLAC 以及 Wave 文件音乐元数据的库
Mingus 一个高级音乐理论和曲谱包,支持 MIDI 文件 和回放功能
视频和 GIF 处理 Moviepy 一个用来进行基于脚本的视频编辑模块,适用于 多种格式,包括动图 GIFs
scikit-video SciPy 视频处理常用程序
地理位置 GeoDjango 世界级地理图形 web 框架
GeoIP MaxMind GeoIP Legacy 数 据 库 的 Python API
Geopy Python 地址编码工具箱
HTTP requests 人性化的 HTTP 请求库
httplib2 全面的 HTTP 客户端库
urllib3 一个具有线程安全连接池,支持文件 post,清 晰友好的 HTTP 库
Python 实现的 数据库 pickleDB 一个简单,轻量级键值储存数据库
PipelineDB 流式 SQL 数据库
TinyDB 一个微型的,面向文档型数据库
web 框架 Django Python 界最流行的 web 框架
Flask 一个 Python 微型框架
Tornado 一个 web 框架和异步网络库
CMS 内容管理系统 odoo-cms 一个开源的,企业级 CMS,基于 odoo
djedi-cms 一个轻量级但却非常强大的 Django CMS ,考 虑到了插件,内联编辑以及性能
Opps 一个为杂志,报纸网站以及大流量门户网站设计 的 CMS 平台,基于 Django
电子商务和支付系 统 django-oscar 一个用于 Django 的开源的电子商务框架
django-shop 一个基于 Django 的店铺系统
Shoop 一个基于 Django 的开源电子商务平台
Alipay Python 支付宝 API
Merchant 一个可以接收来自多种支付平台支付的

尚学堂·百战程序员 www.itbaizhan.cn

Django 应用
游戏开发 Cocos2d 用来开发 2D 游戏
Panda3D 由迪士尼开发的 3D 游戏引擎,并由卡内基梅 陇娱乐技术中心负责维护。使用 C++ 编写, 针 对 Python 进行了完全的封装
Pygame Pygame 是一组 Python 模块,用来编写游戏
RenPy 一个视觉小说(visual novel)引擎
计算机视觉库 OpenCV 开源计算机视觉库
Pyocr Tesseract 和 Cuneiform 的包装库
SimpleCV 一个用来创建计算机视觉应用的开源框架
机器学习 人工智能 TensorFlow 谷歌开源的最受欢迎的深度学习框架
keras 以 tensorflow/theano/CNTK 为 后 端 的 深 度 学习封装库,快速上手神经网络
Hebel GPU 加速的深度学习库
Pytorch 一个具有张量和动态神经网络,并有强大 GPU 加速能力的深度学习框架
scikit-learn 基于 SciPy 构建的机器学习 Python 模块
NuPIC 智能计算 Numenta 平台
科学计算和数据分 析 NumPy 使用 Python 进行科学计算的基础包
Pandas 提供高性能,易用的数据结构和数据分析工具
SciPy 用于数学,科学和工程的开源软件构成的生态系 统
PyMC 马尔科夫链蒙特卡洛采样工具
代码分析和调试 code2flow 把你的 Python 和 JavaScript 代码转换为流 程图
Pycallgraph 这个库可以把你的 Python 应用的流程(调用 图)进行可视化
Pylint 一个完全可定制的源码分析器
autopep8 自动格式化 Python 代码,以使其符合 PEP8 规范
Wdb 一个奇异的 web 调试器,通过 WebSockets 工作
Lineprofiler 逐行性能分析
Memory Profiler 监控 Python 代码的内存使用
图形用户界面 Pyglet 一个 Python 的跨平台窗口及多媒体库

尚学堂·百战程序员 www.itbaizhan.cn

PyQt 跨平台用户界面框架 Qt 的 Python 绑定 ,支 持 Qtv4 和 Qtv5
Tkinter Tkinter 是 Python GUI 的一个事实标准库
wxPython wxPython 是 wxWidgets C++ 类 库 和 Python 语言混合的产物
网络爬虫和 HTML 分析 Scrapy 一个快速高级的屏幕爬取及网页采集框架
Cola 一个分布式爬虫框架
Grab 站点爬取框架
Pyspider 一个强大的爬虫系统
html2text 将 HTML 转换为 Markdown 格式文本
python-goose HTML 内容/文章提取器
硬件编程 Ino 操作 Arduino 的命令行工具
Pyro Python 机器人编程库
PyUserInput 跨平台的,控制鼠标和键盘的模块
Pingo Pingo 为类似 RaspberryPi,pcDuino,Intel Galileo 等设备提供统一的 API

5.4 安装第三方扩展库的 2 种方式

第三方库有数十万种之多,以 pillow 库为例讲解第三方扩展库的安装。pillow 是 Python 平台事实上的图像处理标准库,本节以安装 pillow 为例,给大家介绍第三方库的两 种常用的安装方法。

尚学堂·百战程序员 www.itbaizhan.cn

第一种方式:命令行下远程安装

以安装第三方 pillow 图像库为例,在命令行提示符下输入:pip 安装完成后,我们就可以开始使用。

第二种方式:Pycharm 中直接安装到项目中

在 Pycharm 中,依次点击:file–>setting–>Project 本项目名–>Project Interpreter

文件操作(IO技术)

一个完整的程序一般都包括数据的存储和读取;我们在前面写的程序数据都没有进行实 际的存储,因此 python 解释器执行完数据就消失了。实际开发中,我们经常需要从外部存 储介质(硬盘、光盘、U 盘等)读取数据,或者将程序产生的数据存储到文件中,实现“持 久化”保存。

很多软件系统是将数据存储的数据库中;数据库实际也是基于文件 形式存储的

文本文件和二进制文件

按文件中数据组织形式,我们把文件分为文本文件和二进制文件两大类。

  1. 文本文件

文本文件存储的是普通“字符”文本,python 默认为 unicode 字符集(两个字节表示 一个字符,最多可以表示:65536 个),可以使用记事本程序打开。但是,像 word 软件 编辑的文档不是文本文件。

  1. 二进制文件

二进制文件把数据内容用“字节”进行存储,无法用记事本打开。必须使用专用的软件 解码。常见的有:MP4 视频文件、MP3 音频文件、JPG 图片、doc 文档等等。

文件操作相关模块概述

名称 说明
io 模块 文件流的输入和输出操作 input output
os 模块 基本操作系统功能,包括文件操作
glob 模块 查找符合特定规则的文件路径名
fnmatch 模块 使用模式来匹配文件路径名
fileinput 模块 处理多个输入文件
filecmp 模块 用于文件的比较
cvs 模块 用于 csv 文件处理
pickle 和 cPickle 用于序列化和反序列化
xml 包 用于 XML 数据处理
bz2、gzip、zipfile、zlib、tarfile 用于处理压缩和解压缩文件(分别对应不同的算法)

创建文件对象 open()

open()函数用于创建文件对象,基本语法格式如下: open(文件名[,打开方式])

如果只是文件名,代表在当前目录下的文件。文件名可以录入全路径,比如:D:\a\b.txt。

为了减少“\”的输入,可以使用原始字符串:r “d:\b.txt”。

示例如下:

1
f = open(r"d:\b.txt","w")

打开方式有如下几种:

模式

1
2
3
4
5
6
7
8
9
 r 读 read 模式

w 写 write 模式。如果文件不存在则创建;如果文件存在,则重写新内容;

a 追加 append 模式。如果文件不存在则创建;如果文件存在,则在文件末尾追加内容

b 二进制 binary 模式(可与其他模式组合使用)

+ 读、写模式(可与其他模式组合使用)

文本文件对象和二进制文件对象的创建: 如果我们没有增加模式“b”,则默认创建的是文本文件对象,处理的基本单元是“字符”。如果是二进制模式“b”,则创建的是二进制文件对象,处理的基本单元是“字节”。

文本文件的写入

基本的文件写入操作

文本文件的写入一般就是三个步骤:

  1. 创建文件对象
  2. 写入数据
  3. 关闭文件对象
1
2
3
4
f = open(r"a.txt","a") 
s = "itbaizhan\nsxt\n"
f.write(s)
f.close()

常用编码介绍

在操作文本文件时,经常会操作中文,这时候就经常会碰到乱码问题。

ASCII

全称为 American Standard Code for Information Interchange,美国信 息交换标准代码,这是世界上最早最通用的单字节编码系统,主要用来显示现代 英语及其他西欧语言。

ASCII 码用 7 位表示,只能表示 128 个字符。只定义了 27=128 个字符,用 7bit 即可完全编码,而一字节 8bit 的容量是 256,所以一字节 ASCII 的编码最 高位总是 0。

031 表示控制字符如回车、退格、删除等;32126 表示打印字符即可以 通过键盘输入并且能显示出来的字符;其中 4857 为 0 到 9 十个阿拉伯数字, 6590 为 26 个大写英文字母,97~122 号为 26 个小写英文字母,其余为一 些标点符号、运算符号等,具体可以参考 ASCII 标准表(大家自行百度,不在此 赘述)。

ISO8859-1

ISO-8859-1 又称 Latin-1,是一个 8 位单字节字符集,它把 ASCII 的最高 位也利用起来,并兼容了 ASCII,新增的空间是 128,但它并没有完全用完。

在 ASCII 编码之上又增加了西欧语言、希腊语、泰语、阿拉伯语、希伯来语 对应的文字符号,它是向下兼容 ASCII 编码

GB2312,GBK,GB18030

·GB2312

GB2312 全称为信息交换用汉字编码字符集,是中国于 1980 年发布,主要 用于计算机系统中的汉字处理。GB2312 主要收录了 6763 个汉字、682 个符号。

GB2312 覆盖了汉字的大部分使用率,但不能处理像古汉语等特殊的罕用字, 所以后来出现了像 GBK、GB18030 这种编码。

GB2312 完全兼容 ISO8859-1。

·GBK

全称为 Chinese Internal Code Specification,即汉字内码扩展规范,于 1995 年制定。 它主要是扩展了 GB2312,在它的基础上又加了更多的汉字,它一共收录了 21003 个汉字

·GB18030

现在最新的内码字集于 2000 年发布,并于 2001 年强制执行,包含了中国大部分少数 民族的语言字符,收录汉字数超过 70000 余个。

它主要采用单字节、双字节、四字节对字符编码,它是向下兼容 GB2312 和 GBK 的, 虽然是我国的强制使用标准,但在实际生产中很少用到,用得最多的反而是 GBK 和 GB2312

Unicode

Unicode 编码设计成了固定两个字节,所有的字符都用 16 位(2^16=65536) 表示,包括之前只占 8 位的英文字符等,所以会造成空间的浪费,UNICODE 在 很长的一段时间内都没有得到推广应用。

6

北京尚学堂·百战程序员 高淇的 python400 集:从零打好内功,直达手写神经网络 Unicode 完全重新设计,不兼容 iso8859-1,也不兼容任何其他编码。

UTF-8

对于英文字母,unicode 也需要两个字节来表示。所以 unicode 不便 于传输和存储。因此而产生了 UTF 编码,UTF-8 全称是(8-bit Unicode Transformation Format)。

UTF 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符, 不过,UTF 编码是不定长编码,每一个字符的长度从 1-4 个字节不等。其中, 英文字母都是用一个字节表示,而汉字使用三个字节。

【老鸟建议】一般项目都会使用 UTF-8。unicode 中虽然汉字是两个字节, UTF-8 中汉字是 3 个字节。但是互联网中一个网页也包含了大量的英文字母, 这些英文字母只占用 1 个字节,整体占用空间,UTF-8 仍然由于 Unicode。

中文乱码问题

windows 操作系统默认的编码是 GBK,Linux 操作系统默认的编码是 UTF-8。当我们 用 open()时,调用的是操作系统打开的文件,默认的编码是 GBK。

1
我们在文件编辑区单击右键,选择 FileEncoding,选择 GBK 即可:

write()/writelines()写入数据

write(a):把字符串 a 写入到文件中

writelines(b):把字符串列表写入文件中,不添加换行符

1
2
3
4
f = open(r"d:\bb.txt","w",encoding="utf-8") 
s = ["高淇\n","高老三\n","高老四\n"]
f.writelines(s)
f.close()

close()关闭文件流

由于文件底层是由操作系统控制,所以我们打开的文件对象必须显式调用 close()方法 关闭文件对象。当调用 close()方法时,首先会把缓冲区数据写入文件(也可以直接调用 flush() 方法),再关闭文件,释放文件对象。

为了确保打开的文件对象正常关闭,一般结合异常机制的 finally 或者 with 关键字实现 无论何种情况都能关闭打开的文件对象。

结合异常机制 finally 确保关闭文件对象

1
2
3
4
5
6
7
8
try:
f = open(r"my01.txt","a")
str = "gaoqi"
f.write(str)
except BaseException as e:
print(e)
finally:
f.close()

with 语句(上下文管理器)

with 关键字(上下文管理器)可以自动管理上下文资源,不论什么原因跳出 with 块,都能 确保文件正确的关闭,并且可以在代码块执行完毕后自动还原进入该代码块时的现场。

使用 with 管理文件写入操作

1
2
3
s = ["高淇\n","高老三\n","高老五\n"]
with open(r"d:\bb.txt","w") as f:
f.writelines(s)

文本文件的读取

文件的读取一般使用如下三个方法: 1. read([size])

从文件中读取 size 个字符,并作为结果返回。如果没有 size 参数,则读取整个文件。

读取到文件末尾,会返回空字符串。 2. readline()

读取一行内容作为结果返回。读取到文件末尾,会返回空字符串。 3. readlines()

文本文件中,每一行作为一个字符串存入列表中,返回该列表

读取一个文件前 4 个字符

1
2
with open(r"bb","r",encoding="utf-8") as f: 
print(f.read(4))

文件较小,一次将文件内容读入到程序中

1
2
with open(r"d:\bb.txt","r") as f: 
print(f.read())

按行读取一个文件

1
2
3
4
5
6
7
with open(r"bb.txt","r") as f: 
while True:
fragment = f.readline()
if not fragment:
break
else:
print(fragment,end="")

使用迭代器(每次返回一行)读取文本文件

1
2
3
with open(r"d:\bb.txt","r") as f: 
for a in f:
print(a,end="")

为文本文件每一行的末尾增加行号

1
2
3
4
5
with open("e.txt","r",encoding="utf-8") as f: 
lines = f.readlines()
lines = [ line.rstrip()+" #"+str(index+1)+"\n" for index,line in enumerate(lines)] #推导式生成列表
with open("e.txt","w",encoding="utf-8") as f:
f.writelines(lines)
1
2
3
4
5
6
7
8
执行前文件内容:
我 love u!
尚学堂
百战程序员
执行程序后文件内容:
我 love u! #1
尚学堂 #2
百战程序员 #3

二进制文件的读取和写入

二进制文件的处理流程和文本文件流程一致。首先还是要创建文件对象,不过,我们需要指 定二进制模式,从而创建出二进制文件对象。例如:

1
2
3
f = open(r"d:\a.txt", 'wb') #可写的、重写模式的二进制文件对象
f = open(r"d:\a.txt", 'ab') #可写的、追加模式的二进制文件对象
f = open(r"d:\a.txt", 'rb') #可读的二进制文件对象

创建好二进制文件对象后,仍然可以使用 write()、read()实现文件的读写操作。

读取图片文件,实现文件的拷贝

1
2
3
4
5
with open('aa.gif', 'rb') as f:
with open('aa_copy.gif', 'wb') as w:
for line in f.readlines():
w.write(line)
print('图片拷贝完成!')

文件对象的常用属性和方法

文件对象封装了文件相关的操作。在前面我们学习了通过文件对象对文件进行读写操作。本 节我们详细列出文件对象的常用属性和方法,并进行说明。

文件对象的属性
属性 说明

name 返回文件的名字
mode 返回文件的打开模式
closed 若文件被关闭则返回 True

文件对象的打开模式

模式 说明
r 读模式
w 写模式
a 追加模式
b 二进制模式(可与其他模式组合)
+ 读写模式(可以其他模式组合)

文件对象的常用方法

方法名 说明
read([size]) 从文件中读取 size 个字节或字符的内容返回。若省略[size],则读 取到文件末尾,即一次读取文件所有内容
readline() 从文本文件中读取一行内容
readlines() 把文本文件中每一行都作为独立的字符串对象,并将这些对象放入 列表返回
write(str) 将字符串 str 内容写入文件
writelines(s) 将字符串列表 s 写入文件文件,不添加换行符
seek(offset ,whence]) 把文件指针移动到新的位置,offset 表示相对于 whence 的多少个 字节的偏移量; offset: off 为正往结束方向移动,为负往开始方向移动 whence 不同的值代表不同含义: 0: 从文件头开始计算(默认值) 1:从当前位置开始计算 2:从文件尾开始计算
tell() 返回文件指针的当前位置
truncate([size]) 不论指针在什么位置,只留下指针前 size 个字节的内容,其余全 部删除; 如果没有传入 size,则当指针当前位置到文件末尾内容全部删除
flush() 把缓冲区的内容写入文件,但不关闭文件
close() 把缓冲区内容写入文件,同时关闭文件,释放文件对象相关资源

文件任意位置操作

seek()移动文件指针示例

1
2
3
4
5
6
7
with open("e.txt","r",encoding="utf-8") as f: 
print("文件名是:{0}".format(f.name))
print(f.tell())
print("读取的内容:{0}".format(str(f.readline())))
print(f.tell())
f.seek(0,0)
print("读取的内容:{0}".format(str(f.readline())))

使用 pickle 序列化

Python 中,一切皆对象,对象本质上就是一个“存储数据的内存块”。有时候,我们 需要将“内存块的数据”保存到硬盘上,或者通过网络传输到其他的计算机上。这时候,就 需要“对象的序列化和反序列化”。 对象的序列化机制广泛的应用在分布式、并行系统上。

序列化指的是:将对象转化成“串行化”数据形式,存储到硬盘或通过网络传输到其他 地方。反序列化是指相反的过程,将读取到的“串行化数据”转化成对象。

我们可以使用 pickle 模块中的函数,实现序列化和反序列操作。

序列化我们使用:

1
2
3

pickle.dump(obj, file) obj 就是要被序列化的对象,file 指的是存储的文件
pickle.load(file) 从 file 读取数据,反序列化成对象

1.序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程

2.对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。

3.序列化机制的核心作用就是对象状态的保存与重建。

4.反序列化就是客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。

5.序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态。

6.序列化算法一般会按步骤做如下事情:

(1)将对象实例相关的类元数据输出。
(2)递归地输出类的超类描述直到不再有超类。
(3)类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
(4)从上至下递归输出实例的数据

7.序列化的好处:

一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),

二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

对象序列化到文件中,反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
with open(r"d:\data.dat","wb") as f:#序列化
a1="nihao"
a2="223"
pickle.dump(a1,f)
pickle.dump(a2,f)
#反序列化
with open(r"d:\data.dat","rb") as f:
a1 = pickle.load(f)
a2 = pickle.load(f)
print(a1);print(a2)
#打印出:nihao
223

CSV 文件的操作

csv(Comma Separated Values)是逗号分隔符文本格式,常用于数据交换、Excel 文件和数据库数据的导入和导出。与 Excel 文件不同,CSV 文件中:

值没有类型,所有值都是字符串 不能指定字体颜色等样式 不能指定单元格的宽高,不能合并单元格,没有多个工作表

不能嵌入图像图表

csv.reader 对象和 csv 文件读取

1
2
3
4
5
6
7
8
9
import csv
with open(r"d:\a.csv") as a:
a_csv = csv.reader(a) #创建 csv 对象,它是一个包含所有数据的列表,每一行为一个元素
headers = next(a_csv) #获得列表对象,包含标题行的信息
print(headers)
for row in a_csv:#循环打印各行内容
print(row)
#['高老三', '19', '测试工程师', '20000']
['高老五', '20', '人工智能开发', '50000']

csv.writer 对象和 csv 文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import csv
headers = ["工号","姓名","年龄","地址","月薪"]
rows = [("1001","高淇",18,"西三旗 1 号院","50000"),("1002","高八",19,"西三旗 1 号院","30000")]
with open(r"d:\b.csv","w") as b:
b_csv = csv.writer(b) #创建 csv 对象
b_csv.writerow(headers) #写入一行(标题)
b_csv.writerows(rows) #写入多行(数据)
```
执行结果:
工号,姓名,年龄,地址,月薪
1001,高淇,18,西三旗 1 号院,50000
1002,高八,19,西三旗 1 号院,30000

```

os 和 os.path 模块

os 模块可以帮助我们直接对操作系统进行操作。我们可以直接调用操作系统的可执行 文件、命令,直接操作文件、目录等等。在系统运维的核心基础。

**os 模块-调用操作系统命令

** ·os.system 可以帮助我们直接调用系统的命令

1
2
3
4
5
6
import os 
os.system("notepad.exe")#os.system 调用 windows 系统的记事本程序
import os
os.system("ping www.baidu.com")#os.system 调用 windows 系统中 ping 命令
import os
os.startfile(r"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe")#运行安装好的微信

【注】Linux 是命令行操作更容易,我们可以通过 os.system 可以更加容易的调用相关的命 令;

【注】控制台输出中文可能会有乱码问题,可以在 file–>setting 中设置

os.startfile:直接调用可执行文件

os 模块-文件和目录操作

我们可以通过前面讲的文件对象实现对于文件内容的读写操作。如果,还需要对文件和 目录做其他操作,可以使用 os 和 os.path 模块。

os 模块下常用操作文件的方法

方法名 描述
remove(path) 删除指定的文件
rename(src,dest) 重命名文件或目录
stat(path) 返回文件的所有属性
listdir(path) 返回 path 目录下的文件和目录列表

os 模块下关于目录操作的相关方法,汇总如下:

方法名 描述
mkdir(path) 创建目录
makedirs(path1/path2/path3/… ) 创建多级目录
rmdir(path) 删除目录
removedirs(path1/path2…) 删除多级目录
getcwd() 返回当前工作目录:current work dir
chdir(path) 把 path 设为当前工作目录
walk() 遍历目录树
sep 当前操作系统所使用的路径分隔符

os 模块:创建、删除目录、获取文件信息等

1
2
3
4
5
6
7
8
9
10
11
#coding=utf-8
#测试 os 模块中,关于文件和目录的操作 import os
#############获取文件和文件夹相关的信息################ print(os.name) #windows->nt linux 和 unix->posix print(os.sep) #windows->\ linux 和 unix->/ print(repr(os.linesep)) #windows->\r\n linux-->\n\
print(os.stat("my02.py"))
##############关于工作目录的操作############### #print(os.getcwd())
#os.chdir("d:") #改变当前的工作目录为:d:盘根目录 #os.mkdir("书籍") ################创建目录、创建多级目录、删除############# #os.mkdir("书籍")
#os.rmdir("书籍") #相对路径都是相对于当前的工作目录

#os.rename("电影","movie")
dirs = os.listdir("movie")
print(dirs)

os.path 模块

os.path 模块提供了目录相关(路径判断、路径切分、路径连接、文件夹遍历)的操作

方法 描述
isabs(path) 判断 path 是否绝对路径
isdir(path) 判断 path 是否为目录
isfile(path) 判断 path 是否为文件
exists(path) 判断指定路径的文件是否存在
getsize(filename) 返回文件的大小
abspath(path) 返回绝对路径
dirname(p) 返回目录的路径
getatime(filename) 返回文件的最后访问时间
getmtime(filename) 返回文件的最后修改时间
walk(top,func,arg) 递归方式遍历目录
join(path,*paths) 连接多个 path
split(path) 对路径进行分割,以列表形式返回
splitext(path) 从路径中分割文件的扩展名

walk()递归遍历所有文件和目录

os.walk()方法:

返回一个 3 个元素的元组,(dirpath, dirnames, filenames),

dirpath:要列出指定目录的路径 dirnames:目录下的所有文件夹 filenames:目录下的所有文件

shutil 模块(拷贝和压缩)

shutil 模块是 python 标准库中提供的,主要用来做文件和文件夹的拷贝、移动、删除等;还可以做 文件和文件夹的压缩、解压缩操作。

os 模块提供了对目录或文件的一般操作。shutil 模块作为补充,提供了移动、复制、压缩、解压等操 作,这些 os 模块都没有提供。

我们日常传输数据想防止hacker的攻击,需要加密算法,常见的:对称加密,非对称加密,对称加密相对于交易双方有相同钥匙,但是任意一方被黑,两方都有危险,而非对称加密,最经典的rsa解决了这个问题。

rsa算法原理:

首先找出两个质数 p和q

计算 n = p * q

φ(n) = (p-1) * (q-1) 这个函数又叫欧拉函数

公钥e 1 < e < φ(n) 在这个范围取出一个整数,并且 e 和 φ(n) 互质

私钥d (e * d ) / φ(n) = x ······ 1 (x是几无所谓,余数必须是1,比如 φ(n)是20 e是3 那么d就是7)

加密 m^e / n =x ······ c (这几个x都代表任意数的商,在这里面没有直接意义,可以忽略不看,这三个x也都不相等)

解密 c^d / n =x ······ m (可以从数学上证明,经过这一系列的计算,得到结果的余数一定是m)
————————————————
版权声明:本文为CSDN博主「狱蝶阿一」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42510528/java/article/details/80765361

一、RSA加密简介(ssh典型用此算法)

  RSA加密是一种非对称加密。可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。

  

二、RSA加密、签名区别

  加密和签名都是为了安全性考虑,但略有不同。常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改。这里举2个例子说明。

第一个场景:战场上,B要给A传递一条消息,内容为某一指令。

RSA的加密过程如下:

(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。

(2)A传递自己的公钥给B,B用A的公钥对消息进行加密。

(3)A接收到B加密的消息,利用A自己的私钥对消息进行解密。

  在这个过程中,只有2次传递过程,第一次是A传递公钥给B,第二次是B传递加密消息给A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密,防止了消息内容的泄露。

所以.ssh文件中私钥千万别动,那是你唯一救命稻草。但是公钥随便给别人。

第二个场景:A收到B发的消息后,需要进行回复“收到”。

RSA签名的过程如下:

(1)A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。

(2)A用自己的私钥对消息加签,形成签名,并将加签的消息和消息本身一起传递给B。

(3)B收到消息后,在获取A的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是A回复的。

  在这个过程中,只有2次传递过程,第一次是A传递加签的消息和消息本身给B,第二次是B获取A的公钥,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给B,防止了消息内容的篡改。

  但是,综合两个场景你会发现,第一个场景虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给A。第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥验签来获得,并不能防止泄露。所以在实际应用中,要根据情况使用,也可以同时使用加密和签名,比如A和B都有一套自己的公钥和私钥,当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。

  总结:公钥加密、私钥解密、私钥签名、公钥验签。

三、RSA加密、签名的方法,代码例子如下:

java实现此算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  1 import java.io.ByteArrayOutputStream;//都是javase6以后的包
2 import java.security.KeyFactory;
3 import java.security.KeyPair;
4 import java.security.KeyPairGenerator;
5 import java.security.PrivateKey;
6 import java.security.PublicKey;
7 import java.security.Signature;
8 import java.security.spec.PKCS8EncodedKeySpec;
9 import java.security.spec.X509EncodedKeySpec;
10 import javax.crypto.Cipher;
11 import org.apache.commons.codec.binary.Base64;
12
13 public class TestRSA {
14
15 /**
16 * RSA最大加密明文大小
17 */
18 private static final int MAX_ENCRYPT_BLOCK = 117;
19
20 /**
21 * RSA最大解密密文大小
22 */
23 private static final int MAX_DECRYPT_BLOCK = 128;
24
25 /**
26 * 获取密钥对
27 *
28 * @return 密钥对
29 */
30 public static KeyPair getKeyPair() throws Exception {
31 KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
32 generator.initialize(1024);
33 return generator.generateKeyPair();
34 }
35
36 /**
37 * 获取私钥
38 *
39 * @param privateKey 私钥字符串
40 * @return
41 */
42 public static PrivateKey getPrivateKey(String privateKey) throws Exception {
43 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
44 byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
45 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
46 return keyFactory.generatePrivate(keySpec);
47 }
48
49 /**
50 * 获取公钥
51 *
52 * @param publicKey 公钥字符串
53 * @return
54 */
55 public static PublicKey getPublicKey(String publicKey) throws Exception {
56 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
57 byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
58 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
59 return keyFactory.generatePublic(keySpec);
60 }
61
62 /**
63 * RSA加密
64 *
65 * @param data 待加密数据
66 * @param publicKey 公钥
67 * @return
68 */
69 public static String encrypt(String data, PublicKey publicKey) throws Exception {
70 Cipher cipher = Cipher.getInstance("RSA");
71 cipher.init(Cipher.ENCRYPT_MODE, publicKey);
72 int inputLen = data.getBytes().length;
73 ByteArrayOutputStream out = new ByteArrayOutputStream();
74 int offset = 0;
75 byte[] cache;
76 int i = 0;
77 // 对数据分段加密
78 while (inputLen - offset > 0) {
79 if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
80 cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
81 } else {
82 cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
83 }
84 out.write(cache, 0, cache.length);
85 i++;
86 offset = i * MAX_ENCRYPT_BLOCK;
87 }
88 byte[] encryptedData = out.toByteArray();
89 out.close();
90 // 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
91 // 加密后的字符串
92 return new String(Base64.encodeBase64String(encryptedData));
93 }
94
95 /**
96 * RSA解密
97 *
98 * @param data 待解密数据
99 * @param privateKey 私钥
100 * @return
101 */
102 public static String decrypt(String data, PrivateKey privateKey) throws Exception {
103 Cipher cipher = Cipher.getInstance("RSA");
104 cipher.init(Cipher.DECRYPT_MODE, privateKey);
105 byte[] dataBytes = Base64.decodeBase64(data);
106 int inputLen = dataBytes.length;
107 ByteArrayOutputStream out = new ByteArrayOutputStream();
108 int offset = 0;
109 byte[] cache;
110 int i = 0;
111 // 对数据分段解密
112 while (inputLen - offset > 0) {
113 if (inputLen - offset > MAX_DECRYPT_BLOCK) {
114 cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
115 } else {
116 cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
117 }
118 out.write(cache, 0, cache.length);
119 i++;
120 offset = i * MAX_DECRYPT_BLOCK;
121 }
122 byte[] decryptedData = out.toByteArray();
123 out.close();
124 // 解密后的内容
125 return new String(decryptedData, "UTF-8");
126 }
127
128 /**
129 * 签名
130 *
131 * @param data 待签名数据
132 * @param privateKey 私钥
133 * @return 签名
134 */
135 public static String sign(String data, PrivateKey privateKey) throws Exception {
136 byte[] keyBytes = privateKey.getEncoded();
137 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
138 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
139 PrivateKey key = keyFactory.generatePrivate(keySpec);
140 Signature signature = Signature.getInstance("MD5withRSA");
141 signature.initSign(key);
142 signature.update(data.getBytes());
143 return new String(Base64.encodeBase64(signature.sign()));
144 }
145
146 /**
147 * 验签
148 *
149 * @param srcData 原始字符串
150 * @param publicKey 公钥
151 * @param sign 签名
152 * @return 是否验签通过
153 */
154 public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
155 byte[] keyBytes = publicKey.getEncoded();
156 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
157 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
158 PublicKey key = keyFactory.generatePublic(keySpec);
159 Signature signature = Signature.getInstance("MD5withRSA");
160 signature.initVerify(key);
161 signature.update(srcData.getBytes());
162 return signature.verify(Base64.decodeBase64(sign.getBytes()));
163 }
164
165 public static void main(String[] args) {
166 try {
167 // 生成密钥对
168 KeyPair keyPair = getKeyPair();
169 String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
170 String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
171 System.out.println("私钥:" + privateKey);
172 System.out.println("公钥:" + publicKey);
173 // RSA加密
174 String data = "待加密的文字内容";
175 String encryptData = encrypt(data, getPublicKey(publicKey));
176 System.out.println("加密后内容:" + encryptData);
177 // RSA解密
178 String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
179 System.out.println("解密后内容:" + decryptData);
180
181 // RSA签名
182 String sign = sign(data, getPrivateKey(privateKey));
183 // RSA验签
184 boolean result = verify(data, getPublicKey(publicKey), sign);
185 System.out.print("验签结果:" + result);
186 } catch (Exception e) {
187 e.printStackTrace();
188 System.out.print("加解密异常");
189 }
190 }
191 }

  PS:RSA加密对明文的长度有所限制,规定需加密的明文最大长度=密钥长度-11(单位是字节,即byte),所以在加密和解密的过程中需要分块进行。而密钥默认是1024位,即1024位/8位-11=128-11=117字节。所以默认加密前的明文最大长度117字节,解密密文最大长度为128字。那么为啥两者相差11字节呢?是因为RSA加密使用到了填充模式(padding),即内容不足117字节时会自动填满,用到填充模式自然会占用一定的字节,而且这部分字节也是参与加密的。

  密钥长度的设置就是上面例子的第32行。可自行调整,当然非对称加密随着密钥变长,安全性上升的同时性能也会有所下降。

改编自:https://www.cnblogs.com/pcheng/p/9629621.html,https://blog.csdn.net/weixin_42510528/java/article/details/80765361

异常和错误

软件程序在运行过程中,非常可能遇到刚刚提到的这些问题,我们称之为 异常,英文是:Exception,意思是例外。

异常机制本质

异常指程序运行过程中出现的非正常现象,例如用户输入错误、除数为零、需 要处理的文件不存在、数组下标越界等。

所谓异常处理,就是指程序在出现问题时依然可以正确的执行剩余的程序,而 不会因为异常而终止程序执行。

python 中,引进了很多用来描述和处理异常的类,称为异常类。异常类定义中 包含了该类异常的信息和对异常进行处理的方法。

python 中内建异常类的继承层次:

1
2
3
BaseException:所以异常的父类
有4子类:KeyBoardInterrupt, Exception, SystemExit,GeneratorExit
Exception内子类:NameError, ValueError, AttributeError等

python 中一切都是对象,异常也采用对象的方式来处理。处理过程:

  1. 抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给解释器。

  2. 捕获异常:解释器得到该异常后,寻找相应的代码来处理该异常。

异常解决的关键:定位

当发生异常时,解释器会报相关的错误信息,并会在控制台打印出相关错误信息。我们 只需按照从上到下的顺序即可追溯(Trackback)错误发生的过程,最终定位引起错误的那一 行代码。

try… 一个except 结构

try…except 是最常见的异常处理结构。结构如下:

1
2
3
4
5
6
7
try:

被监控的可能引发异常的语句块

except BaseException [as e]:

异常处理语句块

try 块包含着可能引发异常的代码,except 块则用来捕捉和处理发生的异常。执行的时 候,如果 try 块中没有引发异常,则跳过 ecept 块继续执行后续代码;执行的时候,如果 try块中发生了异常,则跳过 try 块中的后续代码,跳到相应的 except 块中处理异常;异常处理 完后,继续执行后续代码。

例子

1
2
3
4
5
6
7
8
try:
print("step1")
a=3/0
print("step2")
except BaseException as e:
print("step3")
print("e")
print("step4")

结果:

1
2
3
4
step1
step3
division by zero
step4

例子2

1
2
3
4
5
6
7
8
try:
print("step1")
a=3/2
print("step2")
except BaseException as e:
print("step3")
print("e")
print("step4")

#结果

step1
step2
step4

try…多个 except 结构

上面的结构可以捕获所有的异常,工作中也很常见。但是,从经典理论考虑,一般建议 尽量捕获可能出现的多个异常(按照先子类后父类的顺序),并且针对性的写出异常处理代 码。为了避免遗漏可能出现的异常,可以在最后增加 BaseException。结构如下:

1
2
3
4
5
6
7
8
try:
被监控的可能引发异常的语句块
except Exception1:
处理 Exception1 的语句块

except Exception2:
处理 Exception2 的语句块
...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try:
a=input("输入一个被除数:")
b=input("输入一个除数:")
c=float(a)/float(b)
print(c)
except ZeroDicisionError:
print("除数不为0")
except TypeError:
print("异常:除数和被除数都应该为数值类型")
except NameError:
print("异常:变量不存在")
except BaseException as e:
print(e)
print(type(e))
```
请输入被除数:10
请输入除数:0
异常:除数不能为 0
```

try…except…else 结构

try…except…else 结构增加了“else 块”。如果 try 块中没有抛出异常,则执行 else 块。如果

try 块中抛出异常,则执行 except 块,不执行 else 块。

1
2
3
发生异常的执行情况(执行 except 块,没有执行 else):

没有发生异常的执行情况(执行完 try 块后,执行 else):

try…except…finally 结构

try…except…finally 结构中,finally 块无论是否发生异常都会被执行;通常用来释放 try 块中申请的资源

finally 中的语句,无论是否发生异常都执行

读取文件,finally 中保证关闭文件资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try:
f = open("d:/a.txt",'r')
content = f.readline()
print(content)
except BaseException as e:
print(e)
finally:
f.close() #释放资源。此处也可能会发生异常。若发生异常,则程序终止,不会继续往下执行
print("step4")
```
Traceback (most recent call last):
[Errno 2] No such file or directory: 'd:/a.txt'
File "C:/PycharmProjects/mypro_exception/my01.py", line 8, in <module>
f.close() #释放资源。此处也可能会发生异常。若发生异常,则程序终止,不会继续往下执行
NameError: name 'f' is not defined Process finished with exit code 1
```

需要把f.close也try except

return 语句和异常处理问题

由于 return 有两种作用:结束方法运行、返回值。我们一般不把 return 放到异常处理结构

中,而是放到方法最后。

一般不要将 return 语句放到 try、except、else、finally 块中,会发生一些意想不到的错误。建议放到方法最后。

常见异常的解决

Python 中的异常都派生自 BaseException 类

\1. SyntaxError:语法错误

1
2
int a =3 
SyntaxError: invalid syntax

\2. NameError:尝试访问一个没有申明的变量

1
2
print(a)
NameError: name 'a' is not defined

\3. ZeroDivisionError:除数为0错误(零除错误)

1
2
a = 3/0
ZeroDivisionError: division by zero

\4. ValueError:数值错误

1
2
float("gaoqi")
ValueError: could not convert string to float: 'gaoqi'

\5. TypeError:类型错误

1
2
123+"abc"
TypeError: unsupported operand type(s) for +: 'int' and 'str'

\6. AttributeError:访问对象的不存在的属性

1
2
a.sayhi()
AttributeError: 'int' object has no attribute 'sayhi'

\7. IndexError:索引越界异常

1
2
a[10]
IndexError: list index out of range

\8. KeyError:字典的关键字不存在

1
a['salary'] KeyError: 'salary'
异常名称 说明
ArithmeticError 所有数值计算错误的基类
AssertionError 断言语句失败
AttributeError 对象没有这个属性
BaseException 所有异常的基类
DeprecationWarning 关于被弃用的特征的警告
EnvironmentError 操作系统错误的基类
EOFError 没有内建输入,到达 EOF 标记
Exception 常规错误的基类
FloatingPointError 浮点计算错误
FutureWarning 关于构造将来语义会有改变的警告
GeneratorExit 生成器(generator)发生异常来通知退出
ImportError 导入模块/对象失败
IndentationError 缩进错误
IndexError 序列中没有此索引(index)
IOError 输入/输出操作失败
KeyboardInterrupt 用户中断执行(通常是输入^C)
KeyError 映射中没有这个键
LookupError 无效数据查询的基类
MemoryError 内存溢出错误(对于 Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
NotImplementedError 尚未实现的方法
OSError 操作系统错误
OverflowError 数值运算超出最大限制
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
StandardError 所有的内建标准异常的基类
StopIteration 迭代器没有更多的值
SyntaxError Python 语法错误
SyntaxWarning 可疑的语法的警告
SystemError 一般的解释器系统错误
SystemExit 解释器请求退出
TabError Tab 和空格混用
TypeError 对类型无效的操作
UnboundLocalError 访问未初始化的本地变量
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeError Unicode 相关的错误
UnicodeTranslateError Unicode 转换时错误
UserWarning 用户代码生成的警告
ValueError 传入无效的参数
Warning 警告的基类
WindowsError 系统调用失败
ZeroDivisionError 除(或取模)零 (所有数据类型)

with 上下文管理

finally 块由于是否发生异常都会执行,通常我们放释放资源的代码。其实,我们可以通过 with 上下文管理,更方便的实现释放资源的操作。 with 上下文管理的语法结构如下:

1
2
with context_expr [ as var]: 
语句块

with 上下文管理可以自动管理资源,在 with 代码块执行完毕后自动还原进入该代码之前的 现场或上下文。不论何种原因跳出 with 块,不论是否有异常,总能保证资源正常释放。极 大的简化了工作,在文件操作、网络通信相关的场合非常常用。

With不是取代try…except…finally的,而是作为补充,方便文件管理,网络通信时的开发

1
2
3
with open("d:/bb.txt") as f: 
for line in f:
print(line)

trackback 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#coding=utf-8
import traceback
try:
print("step1")
num = 1/0
except:
traceback.print_exc()
```
step1
Traceback (most recent call last):
File "/Users/mengxiaowang/Documents/a.py", line 4, in <module>
num = 1/0
ZeroDivisionError: division by zero
```

使用 traceback 将异常信息写入日志文件

1
2
3
4
5
6
7
import traceback
try:
print("step1")
num = 1/0
except:
with open("d:/a.log","a") as f:
traceback.print_exc(file=f)

自定义异常类

程序开发中,有时候我们也需要自己定义异常类。自定义异常类一般都是运行时异常,通常继承 Exception 或其子类即可。命名一般以 Error、Exception 为后缀。

自定义异常由 raise 语句主动抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
classAgeError(Exception): #继承Exception 
def __init__(self,errorInfo):
Exception.__init__(self)
self.errorInfo = errorInfo
def __str__(self):
return str(self.errorInfo)+",年龄错误!应该在 1-150 之间"
############测试代码################
if __name__ == "__main__": #如果为 True,则模块是作为独立文件运行, 可以执行测试代码
age = int(input("输入一个年龄:"))
if age<1 or age>150:
raise AgeError(age)
else:
print("正常的年龄:",age)
```
输入一个年龄:160
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/mypro_exception/my10.py", line 16, in <module>
raise AgeError(age)
__main__.AgeError: 200,年龄错误!应该在 1-150 之间
```

if name == ‘main‘:的作用

简单说:

在当前执行的程序下(例如当前程序为test.py),如果导入其他模块(other_test.py),则运行程序时other_test.py的if name == ‘main‘ 语句判断失败,将不会运行下面的方法。

也就是说导入的模块的if name == ‘main‘ 语句下的方法是不会执行的。只会执行当前的if name == ‘main‘ 下的方法。
一个python文件通常有两种使用方法,第一是作为脚本直接执行,第二是 import 到其他的 python 脚本中被调用(模块重用)执行。因此 if name == ‘main’: 的作用就是控制这两种情况执行代码的过程,在 if name == ‘main’: 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而 import 到其他脚本中是不会被执行的。举例说明如下:

  • 直接执行

img

直接执行 test.py,结果如下图,可以成功 print 两行字符串。即,if name==”main“: 语句之前和之后的代码都被执行。

img

  • import 执行

然后在同一文件夹新建名称为 import_test.py 的脚本,输入如下代码:

img

执行 import_test.py 脚本,输出结果如下:

img

只输出了第一行字符串。即,if name==”main“: 之前的语句被执行,之后的没有被执行。

if name == ‘main‘:的运行原理

每个python模块(python文件,也就是此处的 test.py 和 import_test.py)都包含内置的变量 name,当该模块被直接执行的时候,name 等于文件名(包含后缀 .py );如果该模块 import 到其他模块中,则该模块的 name 等于模块名称(不包含后缀.py)。

main” 始终指当前执行模块的名称(包含后缀.py)。进而当模块被直接执行name == ‘main’ 结果为真。

为了进一步说明,我们在 test.py 脚本的 if name==”main“: 之前加入 print(name),即将 name 打印出来。文件内容和结果如下:

img

img

可以看出,此时变量name的值为”main“。

再执行 import_test.py,执行结果如下:

img

img

此时,test.py中的name变量值为 test,不满足 name==”main“ 的条件,因此,无法执行其后的代码。

python面向对象

类定义数据类型的属性(数据)和方法(行为),也就是说,“类将行为和状态打 包在一起”。

对象是类的具体实体,一般称为“类的实例”。“方法代码是共享的,属性数据不共享”。

定义类的语法格式如下: class 类名:

类体

要点如下:

  1. 类名必须符合“标识符”的规则;一般规定,首字母大写,多个单词使用“驼峰原则”。

    1. 类体中我们可以定义属性和方法。
    2. . 属性用来描述数据,方法(即函数)用来描述这些数据相关的操作。

init构造方法和new方法

类是抽象的,也称之为“对象的模板”。我们需要通过类这个模板,创建类的实例对象,然 后才能使用类定义的功能。

我们前面说过一个 Python 对象包含三个部分:id(identity 识别码)、type(对象类型)、 value(对象的值)。

现在,我们可以更进一步的说,一个 Python 对象包含如下部分: 1. id(identity 识别码)

  1. type(对象类型) 3. value(对象的值):(1) 属性(attribute) (2) 方法(method)

创建对象,我们需要定义构造函数init()方法。构造方法用于执行“实例对象的初始化工 作”,即对象创建后,初始化当前对象的相关属性,无返回值。

1
_init_()

的要点如下:
\1. 名称固定,必须为:

1
__init__()

\2. 第一个参数固定,必须为:self。 self 指的就是刚刚创建好的实例对象。
\3. 构造函数通常用来初始化实例对象的实例属性,如下代码就是初始化实例属性:name 和 score。

1
2
3
def __init__(self,name,score):
self.name = name #实例属性
self.score = score

\4. 通过“类名(参数列表)”来调用构造函数。调用后,将创建好的对象返回给相应的变量。 比如:s1 = Student(‘张三’, 80)

\5.

1
__init__()方法:初始化创建好的对象,初始化指的是:“给实例属性赋值”

\6.

1
__new__()方法: 用于创建对象,但我们一般无需重定义该方法。

\7.

1
如果我们不定义__init__方法,系统会提供一个默认的__init__方法。如果我们定义了带参 的__init__方法,系统不创建默认的__init__方法。

注:
\1. Python中的self相当于C++中的self指针,JAVA和C#中的this关键字。Python中, self 必须为构造函数的第一个参数,名字可以任意修改。但一般遵守惯例,都叫做 self。

实例属性和实例方法

实例属性instance(其实跟java差不多)

实例属性是从属于实例对象的属性,也称为“实例变量”。他的使用有如下几个要点:

1
2
3
4
5
6
7
8
9
10
\1. 实例属性一般在__init__()方法中通过如下代码定义:

self.实例属性名 = 初始值
\2. 在本类的其他实例方法中,也是通过 self 进行访问:

self.实例属性名
\3. 创建实例对象后,通过实例对象访问:

obj01 = 类名() #创建对象,调用__init__()初始化属性
obj01.实例属性名 = 值 #可以给已有属性赋值,也可以新加属性

实例方法

实例方法是从属于实例对象的方法。实例方法的定义格式如下:

1
2
3
4
def 方法名(self [, 形参列表]):
函数体
方法的调用格式如下:
对象.方法名([实参列表])

要点:
\1. 定义实例方法时,第一个参数必须为 self。和前面一样,self 指当前的实例对象。 2. 调用实例方法时,不需要也不能给 self 传参。self 由解释器自动传参。

· 函数和方法的区别

  1. 都是用来完成一个功能的语句块,本质一样。

  2. 方法调用时,通过对象来调用。方法从属于特定实例对象,普通函数没有这个特点。

  3. 直观上看,方法定义时需要传递 self,函数不需要。

· 实例对象的方法调用本质:

a = Student()

a.say_score()

解释器翻译:

Student.say_score(a)

· 其他操作:

1
2
3
4
1. dir(obj)可以获得对象的所有属性、方法
2.obj.__dict__ 对象的属性字典
3.pass 空语句
4.isinstance(对象,类型) 判断“对象”是不是“指定类型”

类对象、类属性、类方法、静态方法

类对象

我们在前面讲的类定义格式中,“class 类名:”。实际上,当解释器执行 class 语句时, 就会创建一个类对象。

1
2
3
4
5
6
7
class Student:
pass #空语句
print(type(Student))
print(id(Student))
Stu2 = Student
s1 = Stu2()
print(s1)

执行结果如下:
<class ‘type’>

51686328
<main.Student object at 0x0000000002B5FDD8>

我们可以看到实际上生成了一个变量名就是类名“Student”的对象。我们通过赋值给新变 量 Stu2,也能实现相关的调用。说明,确实创建了“类对象”。

【注】pass 为空语句。就是表示什么都不做,只是作为一个占位符存在。当你写代码时, 遇到暂时不知道往方法或者类中加入什么时,可以先用 pass 占位,后期再补上。

类属性

类属性是从属于“类对象”的属性,也称为“类变量”。由于,类属性从属于类对象,可以 被所有实例对象共享。

类属性的定义方式:
class 类名:

类变量名= 初始值 在类中或者类的外面,我们可以通过:“类名.类变量名”来读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student:
company = "SXT" #类属性
count = 0 #类属性
def __init__(self,name,score):
self.name = name
self.score = score #实例属性
Student.count = Student.count+1
def say_score(self): #实例方法
print("我的公司是:",Student.company)
print(self.name,'的分数是:',self.score)
s1 = Student('张三',80) #s1 是实例对象,自动调用__init__()方法
s1.say_score()
print('一共创建{0}个 Student 对象'.format(Student.count))
```
执行结果:
我的公司是: SXT
张三 的分数是: 80
一共创建 1 个 Student 对象
```

类方法

类方法是从属于“类对象”的方法。类方法通过装饰器@classmethod 来定义,格式如下: @classmethod

def 类方法名(cls [,形参列表]) : 函数体

要点如下:
\1. @classmethod 必须位于方法上面一行
\2. 第一个 cls 必须有;cls 指的就是“类对象”本身;
\3. 调用类方法格式:“类名.类方法名(参数列表)”。 参数列表中,不需要也不能给 cls 传 值。
\4. 类方法中访问实例属性和实例方法会导致错误
\5. 子类继承父类方法时,传入 cls 是子类对象,而非父类对象

1
2
3
4
5
6
class Student:
company = "SXT" #类属性
@classmethod
def printCompany(cls):
print(cls.company)
Student.printCompany()

静态方法

Python 中允许定义与“类对象”无关的方法,称为“静态方法”。

“静态方法”和在模块中定义普通函数没有区别,只不过“静态方法”放到了“类的名字空 间里面”,需要通过“类调用”。

静态方法通过装饰器@staticmethod 来定义,格式如下: @staticmethod

def 静态方法名([形参列表]) : 函数体

要点如下:

  1. @staticmethod 必须位于方法上面一行

  2. 调用静态方法格式:“类名.静态方法名(参数列表)”。

  3. 静态方法中访问实例属性和实例方法会导致错误

1
2
3
4
5
6
7
class Student:
company = "SXT" # 类属性
@staticmethod
def add(a, b): # 静态方法
print("{0}+{1}={2}".format(a,b,(a+b)))
return a+b
Student.add(20,30)

del方法(析构函数)和垃圾回收机制

del方法称为“析构方法”,用于实现对象被销毁时所需的操作。比如:释放对象 占用的资源,例如:打开的文件资源、网络连接等。

Python 实现自动的垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器 调用del方法。

我们也可以通过 del 语句删除对象,从而保证调用del方法。 系统会自动提供del方法,一般不需要自定义析构方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#析构函数
class Person:
def __del__(self):
print("销毁对象:{0}".format(self))
p1 = Person()
p2 = Person()
del p2
print("end")
```
运算结果:
销毁对象:<__main__.Person object at 0x02175610> 程序结束
销毁对象:<__main__.Person object at 0x021755D0>

```

call方法和可调用对象

定义了call方法的对象,称为“可调用对象”,即该对象可以像函数一样被调用。

1
2
3
4
5
6
7
8
9
10
class SalaryAccount: '''工资计算类'''
def __call__(self, salary):
yearSalary = salary*12
daySalary = salary//30
hourSalary = daySalary//8
return
dict(monthSalary=salary,yearSalary=yearSalary,daySalary=daySalary ,hourSalary=hourSalary)
s = SalaryAccount()
print(s(5000)) #可以像调用函数一样调用对象的__call__方法
#{'monthSalary': 5000, 'yearSalary': 60000, 'daySalary': 166, 'hourSalary': 20}

方法没有重载

在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可。方法签名包含 3 个部分:方法名、参数数量、参数类型。

Python 中,方法的的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由 可变参数控制。因此,Python 中是没有方法的重载的。定义一个方法即可有多种调用方式, 相当于实现了其他语言中的方法的重载。

如果我们在类体中定义了多个重名的方法,只有最后一个方法有效。 建议:不要使用重名的方法!Python 中方法没有重载。

方法的动态性

Python 是动态语言,我们可以动态的为类添加新的方法,或者动态的修改类的已有的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:

def work(self):
print("努力上班!")
def play_game(self):
print("{0}玩游戏".format(self))
def work2(s):
print("好好工作,努力上班!")
Person.play = play_game
Person.work = work2
p = Person()
p.play()
p.work()
```
<__main__.Person object at 0x7fb0405fdee0>玩游戏
好好工作,努力上班!
```

我们可以看到,Person 动态的新增了 play_game 方法,以及用 work2 替换了 work 方法。

私有属性和私有方法(实现封装)

Python 对于类的成员没有严格的访问控制限制,这与其他面向对象语言有区别。关于私有 属性和私有方法,有如下要点:

  1. 通常我们约定,两个下划线开头的属性是私有的(private)。其他为公共的(public)。

  2. 类内部可以访问私有属性(方法)

  3. 类外部不能直接访问私有属性(方法)

  4. 类外部可以通过“_类名__私有属性(方法)名”访问私有属性(方法)

【注】方法本质上也是属性!只不过是可以通过()执行而已。所以,此处讲的私有属性和公有属性,也同时讲解了私有方法和公有方法的用法。如下测试中,同时也包含了私有方法和 公有方法的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#测试私有属性、私有方法
class Employee:
__company = "google" #私有类属性. 通过 dir 可以查到_Employee__company
def __init__(self,name,age):
self.name = name
self.__age = age #私有实例属性
def say_company(self):
print("我的公司是:",Employee.__company) #类内部可以直接访问私有属性
print(self.name,"的年龄是:",self.__age)
self.__work()
def __work(self): #私有实例方法 通过 dir 可以查到 _Employee__work
print("工作!好好工作,好好赚钱,娶个媳妇!")
p1 = Employee("高淇",32)
print(p1.name)
print(dir(p1)) #
p1.say_company()
print(p1._Employee__age) #通过这种方式可以直接访问到私有属性 。通过 dir 可以查到属性:_Employee__age
#print(p1.__age) #直接访问私有属性,报错
#p1.__sleep() #直接访问私有方法,报错
```
执行结果:
高淇
['_Person__age', '_Person__leg_num', '_Person__sleep', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'say_age']
腿的数目: 2
高淇 的年龄是: 18
睡觉
18
从打印的 Person 对象所有属性我们可以看出。私有属性“__age”在实际存储时是按照 “_Person__age”这个属性来存储的。这也就是为什么我们不能直接使用“__age”而可以 使用“_Person__age”的根本原因。
```

@property 装饰器

@property 可以将一个方法的调用方式变成“属性调用”

@property 主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直 接通过:

emp1.salary = 30000 如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为 1-10000 的数字。这时候,我们就需要通过 getter、setter 方法来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Employee:
def __init__(self,name,salary):
self.name = name
self.__salary = salary
@property #相当于 salary 属性的 getter 方法
def salary(self):
print("月薪为{0},年薪为 {1}".format(self.__salary,(12*self.__salary)))
return self.__salary;
@salary.setter
def salary(self,salary): #相当于 salary 属性的 setter 方法
if(0<salary<1000000):
self.__salary = salary
else:
print("薪水录入错误!只能在 0-1000000 之间")
emp1 = Employee("高淇",100)
print(emp1.salary)
emp1.salary = -200

运行结果:
月薪为 100,年薪为 1200

100
月薪为 100,年薪为 1200
100
薪水录入错误!只能在 0-1000000 之间

属性和方法命名总结

1
2
3
· _xxx:保护成员,不能用“from module import * ”导入,只有类对象和子类对象能访 问这些成员。(类似java 的proetected)
· __xxx__:系统定义的特殊成员
· __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。(但,在类外 部可以通过“对象名. _类名__xxx”这种特殊方式访问。Python 不存在严格意义的私有成员)(类似java的private)

注:再次强调,方法和属性都遵循上面的规则。

私有属性是无法被继承的。由此可知,在 Python 中私有属性为假私有属性。那为什么不从语法上保证 private 字段的私密性呢?用最简单的一句话来说:We are all consenting adults here。正如Python 程序员的观点:开放要比封闭好。

综上所述:

Python 编译器无法严格保证 private 字段的私密性。

只有当子类不受自己控制的时候,才可以考虑使用 private 属性来避免名称冲突。所以python还是不如java啊。

类编码风格

\1. 类名首字母大写,多个单词之间采用驼峰原则。
\2. 实例名、模块名采用小写,多个单词之间采用下划线隔开。
\3. 每个类,应紧跟“文档字符串”,说明这个类的作用。
\4. 可以用空行组织代码,但不能滥用。在类中,使用一个空行隔开方法;模块中,使用两 个空行隔开多个类。

面向对象进阶

面向对象三大特征介绍

Python 是面向对象的语言,也支持面向对象编程的三大特性:继承、封装(隐藏)、多态。

·封装(隐藏)

隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只 对外暴露“相关调用方法”。

通过前面学习的“私有属性、私有方法”的方式,实现“封装”。Python 追求简洁的 语法,没有严格的语法级别的“访问控制符”,更多的是依靠程序员自觉实现。

·继承

继承可以让子类具有父类的特性,提高了代码的重用性。

从设计上是一种增量进化,原有父类设计不变的情况下,可以增加新的功能,或者改进 已有的算法。

·多态

多态是指同一个方法调用由于对象不同会产生不同的行为。

继承

继承是面向对象程序设计的重要特征,也是实现“代码复用”的重要手段。

如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作 难度。已有的类,我们称为“父类或者基类”,新的类,我们称为“子类或者派生类”。

语法格式

Python 支持多重继承,一个子类可以继承多个父类。继承的语法格式如下:

class 子类类名(父类 1[,父类 2,…]):

​ 类体

1
2
3
如果在类定义中没有指定父类,则默认父类是 object 类。也就是说,object 是所有类的父 类,里面定义了一些所有类共有的默认实现,比如:__new__()。

定义子类时,必须在其构造函数中调用父类的构造函数。调用格式如下: 父类名.__init__(self, 参数列表)
1
2
3
4
class Student(Person):
def __init__(self,name,age,score):
self.score = score
Person.__init__(self,name,age) #构造函数中包含调用父类构造函数。根据需要,不是必须。 子类并不会自动调用父类的__init__(),我们必须显式的调用它

类成员的继承和重写

\1. 成员继承:子类继承了父类除构造方法之外的所有成员。
\2. 方法重写:子类可以重新定义父类中的方法,这样就会覆盖父类的方法,也称为“重写”

查看类的继承层次结构

1
通过类的方法 mro()或者类的属性__mro__可以输出这个类的继承层次结构。
1
2
3
4
5
6
class A:pass 
class B(A):pass
class C(B):pass
print(C.mro())
#[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
#c继承B,继承A,继承object

object 根类

object 类是所有类的父类,因此所有的类都有 object 类的属性和方法。我们显然有必要深 入研究一下 object 类的结构。对于我们继续深入学习 Python 很有好处。

dir()查看对象属性

为了深入学习对象,我们先学习内置函数 dir(),他可以让我们方便的看到指定对象所有的 属性。

1
2
3
4
5
6
7
8
9
10
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def say_age(self):
print(self.name,"的年龄是:",self.age)
obj = object()
print(dir(obj))
s2 = Person("高淇",18)
print(dir(s2))
1
2
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'say_age']

从上面我们可以发现这样几个要点:

  1. Person 对象增加了六个属性:
1
__dict__ __module__ __weakref__ age name say_age

\2. object 的所有属性,Person 类作为 object 的子类,显然包含了所有的属性。
\3. 我们打印 age、name、say_age,发现 say_age 虽然是方法,实际上也是属性。只不过, 这个属性的类型是“method”而已。

age <class ‘int’>
name <class ‘str’>

say_age <class ‘method’>

重写str()方法

1
2
3
4
object 有一个__str__()方法,用于返回一个对于“对象的描述”,对应于内置函数 str() 经常用于 print()方法,帮助我们查看对象的信息。__str__()可以重写。
def __str__(self):
'''将对象转化成一个字符串,一般用于 print 方法'''
return "名字是:{0},年龄是{1}".format(self.name,self.__age)

多重继承

Python 支持多重继承,一个子类可以有多个“直接父类”。这样,就具备了“多个父 类”的特点。但是由于,这样会被“类的整体层次”搞的异常复杂,尽量避免使用。class C(B,A):

MRO()

Python 支持多继承,如果父类中有相同名字的方法,在子类没有指定父类名时,解释器将 “从左向右”按顺序搜索。
MRO(Method Resolution Order):方法解析顺序。 我们可以通过 mro()方法获得 “类的层次结构”,方法解析顺序也是按照这个“类的层次结构”寻找的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#多重继承
class A:
def aa(self):
print("aa")
def say(self):
print("say AAA!")
class B:
def bb(self):
print("bb")
def say(self):
print("say BBB!")
class C(B,A):
def cc(self):
print("cc")
c = C()
print(C.mro()) #打印类的层次结构
c.say() #解释器寻找方法是“从左到右”的方式寻找,此时会执行 B 类中的 say()

[<class ‘main.C’>, <class ‘main.B’>, <class ‘main.A’>, <class ‘object’>]
say BBB!

super()获得父类定义

在子类中,如果想要获得父类的方法时,我们可以通过 super()来做。super()代表父类的定义,不是父类对象。

1
2
3
def say(self):
#A.say(self) 调用父类的 say 方法
super().say() #通过 super()调用父类的方法

多态

多态(polymorphism)是指同一个方法调用由于对象不同可能会产生不同的行为。在现实 生活中,我们有很多例子。比如:同样是调用人的休息方法,张三的休息是睡觉,李四的休 息是玩游戏,高淇老师是敲代码。同样是吃饭的方法,中国人用筷子吃饭,英国人用刀叉吃 饭,印度人用手吃饭。

关于多态要注意以下 2 点:
\1. 多态是方法的多态,属性没有多态。
\2. 多态的存在有 2 个必要条件:继承、方法重写。

特殊方法和运算符重载

Python 的运算符实际上是通过调用对象的特殊方法实现的。

1
2
3
4
5
6
7
8
a = 20 
b = 30
c = a+b
d = a.__add__(b)
print("c=",c)
print("d=",d)
#运算结果: c= 50
d= 50
1
2
3
4
5
6
7
8
9
10
11
常见的特殊方法统计如下:

__init__ 构造方法 对象创建:p = Person()
__del__ 析构方法 对象回收
__repr__,__str__ 打印,转换 rint(a)
__call__ 函数调用 a()
__getattr__ 点号运算 a.xxx
__setattr__ 属性赋值 a.xxx = value
__getitem__ 索引运算 a[key]
__setitem__ 索引赋值 a[key]=value
__len__ 长度 len(a)
1
2
3
4
5
6
7
8
运算符+ __add__ 加法
运算符- __sub__ 减法
<,<=,== __lt__,__le__,__eq__ 比较运算符
>,>=,!= __gt__,__ge__,__ne__ 比较运算符
|,^,& __or__,__xor__,__and__ 或、异或、与
<<,>> __lshift__,__rshift__ 左移、右移
*,/,%,// __mul__,__truediv__,__mod__,_ _floordiv__ 乘、浮点除、模运算 (取余)、整数除
** __pow__ 指数运算

我们可以重写上面的特殊方法,即实现了“运算符的重载”。

特殊属性

Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊用法。这 里我们列出常见的特殊属性:

1
2
3
4
5
6
7
8
9
10
11
12
obj.__dict__对象的属性字典
obj.__class__对象所属的类
class.__bases__类的基类元组(多继承)
class.__base__类的基类
class.__mro__类层次结构
class.__subclasses__()子类列表
print(dir(c))
print(c.__dict__)
print(c.__class__)
print(C.__bases__)
print(C.mro())
print(A.__subclasses__())

对象的浅拷贝和深拷贝

·变量的赋值操作

只是形成两个变量,实际还是指向同一个对象。

·浅拷贝

Python 拷贝一般都是浅拷贝。拷贝时,对象包含的子对象内容不拷贝。因此,源对象 和拷贝对象会引用同一个子对象。
·深拷贝

使用 copy 模块的 deepcopy 函数,递归拷贝对象中包含的子对象。源对象和拷贝对象 所有的子对象也不同。

1
2
3
import copy
m2 = copy.copy(m) #m2 是新拷贝的另一个手机对象
m3 = copy.deepcopy(m)

组合

“is-a”关系,我们可以使用“继承”。从而实现子类拥有的父类的方法和属性。“is-a” 关系指的是类似这样的关系:狗是动物,dog is animal。狗类就应该继承动物类。

“has-a”关系,我们可以使用“组合”,也能实现一个类拥有另一个类的方法和属性。” has-a”关系指的是这样的关系:手机拥有 CPU。 MobilePhone has a CPU。self.cpu=cpu

设计模式_工厂模式实现

设计模式是面向对象语言特有的内容,是我们在面临某一类问题时候固定的做法,设计 模式有很多种,比较流行的是:GOF(Goup Of Four)23 种设计模式。当然,我们没有 必要全部学习,学习几个常用的即可。

对于初学者,我们学习两个最常用的模式:工厂模式和单例模式。

工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进 行统一的管理和控制。

设计模式_单例模式实现

单例模式(Singleton Pattern)的核心作用是确保一个类只有一个实例,并且提供一 个访问该实例的全局访问点。

单例模式只生成一个实例对象,减少了对系统资源的开销。当一个对象的产生需要比较 多的资源,如读取配置文件、产生其他依赖对象时,可以产生一个“单例对象”,然后永久 驻留内存中,从而极大的降低开销。

1
单例模式有多种实现的方式,我们这里推荐重写__new__()的方法。

python基础入门

序列

python 中常用的序列结构有:

字符串、列表、元组、字典、集合

列表简介

列表:用于存储任意数目、任意类型的数据集合。

列表是内置可变序列,是包含多个元素的有序连续的内存空间。列表定义的标准语法格式: a = [10,20,30,40]

其中,10,20,30,40 这些称为:列表 a 的元素。 列表中的元素可以各不相同,可以是任意类型。比如:

a = [10,20,’abc’,True] 列表对象的常用方法汇总如下,方便大家学习和查阅。

方法 要点 描述
list.append(x) 增加元素 将元素 x 增加到列表 list 尾部
list.extend(aList) 增加元素 将列表 alist 所有元素加到列表 list 尾部
list.insert(index,x) 增加元素 在列表 list 指定位置 index 处插入元素 x
list.remove(x) 删除元素 在列表 list 中删除首次出现的指定元素 x
list.pop([index]) 删除元素 删除并返回列表 list 指定为止 index 处的元素,默认是 最后一个元素
list.clear() 删除所有元素 删除列表所有元素,并不是删除列表对象
list.index(x) 访问元素 返回第一个 x 的索引位置,若不存在 x 元素抛出异常
list.count(x) 计数 返回指定元素 x 在列表 list 中出现的次数
len(list) 列表长度 返回列表中包含元素的个数
list.reverse() 翻转列表 所有元素原地翻转
list.sort() 排序 所有元素原地排序
list.copy() 浅拷贝 返回列表对象的浅拷贝

Python 的列表大小可变,根据需要随时增加或缩小。

字符串和列表都是序列类型,一个字符串是一个字符序列,一个列表是任何元素的序列。我 们前面学习的很多字符串的方法,在列表中也有类似的用法,几乎一模一样。

列表的创建

基本语法[]创建

list()创建: 使用 list()可以将任何可迭代的数据转化成列表。

1
2
3
>>> a = list("gaoqi,sxt")
>>> a
['g', 'a', 'o', 'q', 'i', ',', 's', 'x', 't']

range()创建整数列表

range()可以帮助我们非常方便的创建整数列表,这在开发中及其有用。语法格式为: range([start,] end [,step])

start 参数:可选,表示起始数字。默认是 0
end 参数:必选,表示结尾数字。
step 参数:可选,表示步长,默认为 1
python3 中 range()返回的是一个 range 对象,而不是列表。我们需要通过 list()方法将其 转换成列表对象。

1
2
>>> list(range(3,15,2))
[3, 5, 7, 9, 11, 13]

推导式生成列表

使用列表推导式可以非常方便的创建列表,在开发中经常使用。

1
2
3
 a = [x*2 for x in range(100) if x%9==0] #通过 if 过滤元素 
>>> a
[0, 18, 36, 54, 72, 90, 108, 126, 144, 162, 180, 198]

列表元素的增加和删除

当列表增加和删除元素时,列表会自动进行内存管理,大大减少了程序员的负担。但这 个特点涉及列表元素的大量移动,效率较低。除非必要,我们一般只在列表的尾部添加元素 或删除元素,这会大大提高列表的操作效率。

append()方法

原地修改列表对象,是真正的列表尾部添加新的元素,速度最快,推荐使用。 a.append(80)

+运算符操作

并不是真正的尾部添加元素,而是创建新的列表对象;将原列表的元素和新列表的元素依次 复制到新的列表对象中。这样,会涉及大量的复制操作,对于操作大量元素不建议使用。a = a+[50]

extend()方法

将目标列表的所有元素添加到本列表的尾部,属于原地操作,不创建新的列表对象。

1
2
3
4
5
6
>>> a = [20,40]
>>> id(a)
46016072
>>> a.extend([50,60])
>>> id(a)
46016072

insert()插入元素

使用 insert()方法可以将指定的元素插入到列表对象的任意制定位置。这样会让插入位置后 面所有的元素进行移动,会影响处理速度。涉及大量元素时,尽量避免使用。类似发生这种 移动的函数还有:remove()、pop()、del(),它们在删除非尾部元素时也会发生操作位置后 面元素的移动。

a.insert(2,100)#在index2处加入100

乘法扩展

使用乘法扩展列表,生成一个新列表,新列表元素时原列表元素的多次重复。

1
2
3
4
5
b = a*3 
>>> a
['sxt', 100]
>>> b
['sxt', 100, 'sxt', 100, 'sxt', 100]

列表元素的删除 del 删除

删除列表指定位置的元素。del a[1]

pop()方法

pop()删除并返回指定位置元素,如果未指定位置则默认操作列表最后一个元素。

a.pop(1)#返回index 1的元素

remove()方法

删除首次出现的指定元素,若不存在该元素抛出异常。a.remove(20)

列表元素访问和计数

我们可以通过索引直接访问元素。索引的区间在[0, 列表长度-1]这个范围。超过这个范围则 会抛出异常。

index()获得指定元素在列表中首次出现的索引

index()可以获取指定元素首次出现的索引位置。语法是:index(value,[start,[end]])。其中, start 和 end 指定了搜索的范围。

1
>>> a.index(30,5,7) #从索引位置 5 到 7 这个区间,第一次出现 30 元素的位置

count()获得指定元素在列表中出现的次数

count()可以返回指定元素在列表中出现的次数。 a.count(20)

len()返回列表长度

len()返回列表长度,即列表中包含元素的个数。 len(a)

成员资格判断

判断列表中是否存在指定的元素,我们可以使用 count()方法,返回 0 则表示不存在,返回 大于 0 则表示存在。但是,一般我们会使用更加简洁的 in 关键字来判断,直接返回 True 或 False。100 not in a

  1. 切片操作

    我们在前面学习字符串时,学习过字符串的切片操作,对于列表的切片操作和字符串类似。 切片是 Python 序列及其重要的操作,适用于列表、元组、字符串等等。切片的格式如下:

    切片 slice 操作可以让我们快速提取子列表或修改。标准格式为: [起始偏移量 start:终止偏移量 end[:步长 step]]

    注:当步长省略时顺便可以省略第二个冒号

    操作和说明 示例 结果
    [:] 提取整个列表 [10,20,30][:] [10,20,30]
    [start:]从 start 索引开始到 结尾 [10,20,30][1:] [20,30]
    [:end]从头开始知道 end-1 [10,20,30][:2] [10,20]
    [start:end]从 start 到 end-1 [10,20,30,40][1:3] [20,30]
    [start:end:step] 从 start 提 取到 end-1,步长是 step [10,20,30,40,50,60,70][1:6: 2] [20, 40, 60]
示例 说明 结果
[10,20,30,40,50,60,70][-3:] 倒数三个 [50,60,70]
10,20,30,40,50,60,70][-5:-3] 倒数第五个到倒数 第三个(包头不包尾) [30,40]
[10,20,30,40,50,60,70][::-1] 步长为负,从右到左 反向提取 [70, 60, 50, 40, 30, 20, 10]

切片操作时,起始偏移量和终止偏移量不在[0,字符串长度-1]这个范围,也不会报错。起始 偏移量小于 0 则会当做 0,终止偏移量大于“长度-1”会被当成”长度-1”

列表的遍历

for obj in listObj: print(obj)

复制列表所有的元素到新列表对象

我们可以通过如下简单方式,实现列表元素内容的复制: list1 = [30,40,50]

list2 = [] + list1

*列表排序 *

修改原列表,不建新列表的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = [20,10,30,40]
>>> id(a)
46017416
>>> a.sort() #默认是升序排列
>>> a
[10, 20, 30, 40]
>>> a = [10,20,30,40]
>>> a.sort(reverse=True) #降序
>>> a
[40, 30, 20, 10]
>>> import random
>>> random.shuffle(a) #打乱排序
>>> a
[20, 40, 30, 10]

建新列表排序

我们也可以通过内置函数 sorted()进行排序,这个方法返回新列表,不对原列表做修改。

a = sorted(a)

c = sorted(a,reverse=True) #降序

reversed()返回迭代器

内置函数 reversed()也支持进行逆序排列,与列表对象 reverse()方法不同的是,内置函数 reversed()不对原列表做任何修改,只是返回一个逆序排列的迭代器对象。

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = [20,10,30,40]
>>> c = reversed(a)
>>> c
<list_reverseiterator object at 0x0000000002BCCEB8>
>>> list(c)
[40, 30, 10, 20]
>>> list(c)
[]
···
我们打印输出 c 发现提示是:list_reverseiterator。也就是一个迭代对象。同时,我们使用 list(c)进行输出,发现只能使用一次。第一次输出了元素,第二次为空。那是因为迭代对象 在第一次时已经遍历结束了,第二次不能再使用。

···

列表相关的其他内置函数汇总 max 和 min

用于返回列表中最大和最小值。max(a)

sum

对数值型列表的所有元素进行求和操作,对非数值型列表运算则会报错。

元组 tuple

列表属于可变序列,可以任意修改列表中的元素。元组属于不可变序列,不能修改元组中的 元素。因此,元组没有增加元素、修改元素、删除元素相关的方法。

因此,我们只需要学习元组的创建和删除,元组中元素的访问和计数即可。元组支持如 下操作:

\1. 索引访问
\2. 切片操作
\3. 连接操作
\4. 成员关系操作
\5. 比较运算操作
\6. 计数:元组长度 len()、最大值 max()、最小值 min()、求和 sum()等。

元组的创建

\1. 通过()创建元组。小括号可以省略。

如果元组只有一个元素,则必须后面加逗号。这是因为解释器会把(1)解释为整数 1,(1,) 解释为元组。

a = (10,20,30) 或者 a = 10,20,30

\2. 通过 tuple()创建元组 tuple(可迭代的对象)

tuple()可以接收列表、字符串、其他序列类型、迭代器等生成元组。 list()可以接收元组、字符串、其他序列类型、迭代器等生成列表。

元组的元素访问和计数

\1. 元组的元素不能修改

\2. 元组的元素访问和列表一样,只不过返回的仍然是元组对象。

\3. 列表关于排序的方法 list.sorted()是修改原列表对象,元组没有该方法。如果要对元组排 序,只能使用内置函数 sorted(tupleObj),并生成新的列表对象。 sorted(a)

zip

zip(列表 1,列表 2,…)将多个列表对应位置的元素组合成为元组,并返回这个 zip 对象。

1
2
3
4
5
6
>>> a = [10,20,30]
>>> b = [40,50,60]
>>> c = [70,80,90]
>>> d = zip(a,b,c)
>>> list(d)
[(10, 40, 70), (20, 50, 80), (30, 60, 90)]

生成器推导式创建元组

从形式上看,生成器推导式与列表推导式类似,只是生成器推导式使用小括号。列表推 导式直接生成列表对象,生成器推导式生成的不是列表也不是元组,而是一个生成器对象。

我们可以通过生成器对象,转化成列表或者元组。也可以使用生成器对象的next() 方法进行遍历,或者直接作为迭代器对象来使用。不管什么方式使用,元素访问结束后,如 果需要重新访问其中的元素,必须重新创建该生成器对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> s = (x*2 for x in range(5))
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
(0, 2, 4, 6, 8)
>>> list(s) #只能访问一次元素。第二次就为空了。需要再生成一次
[]
>>> s
<generator object <genexpr> at 0x0000000002BDEB48>
>>> tuple(s)
()
>>> s = (x*2 for x in range(5))
>>> s.__next__()
0
>>> s.__next__()
2
>>> s.__next__()
4

元组总结

\1. 元组的核心特点是:不可变序列。
\2. 元组的访问和处理速度比列表快。
\3. 与整数和字符串一样,元组可以作为字典的键,列表则永远不能作为字典的键使用。

字典介绍

字典是“键值对”的无序可变序列,字典中的每个元素都是一个“键值对”,包含:“键 对象”和“值对象”。可以通过“键对象”实现快速获取、删除、更新对应的“值对象”。

列表中我们通过“下标数字”找到对应的对象。字典中通过“键对象”找到对应的“值 对象”。“键”是任意的不可变数据,比如:整数、浮点数、字符串、元组。但是:列表、 字典、集合这些可变对象,不能作为“键”。并且“键”不可重复。

“值”可以是任意的数据,并且可重复。

字典的创建

\1. 我们可以通过{}、dict()来创建字典对象。

\2. 通过 zip()创建字典对象

1
2
3
>>> k = ['name','age','job']
>>> v = ['gaoqi',18,'techer']
>>> d = dict(zip(k,v))

\3. 通过 fromkeys 创建值为空的字典

1
2
3
>>> a = dict.fromkeys(['name','age','job']) 
>>> a
{'name': None, 'age': None, 'job': None}

字典元素的访问

\1. 通过 [键] 获得“值”。若键不存在,则抛出异常。 a[‘name’]

\2. 通过 get()方法获得“值”。推荐使用。优点是:指定键不存在,返回 None;也可以设 定指定键不存在时默认返回的对象。推荐使用 get()获取“值对象”。a.get(‘name’)

\3. 列出所有的键值对

1
2
>>> a.items()
dict_items([('name', 'gaoqi'), ('age', 18), ('job', 'programmer')])

\4. 列出所有的键,列出所有的值

1
2
3
4
>>> a.keys()
dict_keys(['name', 'age', 'job'])
>>> a.values()
dict_values(['gaoqi', 18, 'programmer'])

\5. len() 键值对的个数
\6. 检测一个“键”是否在字典中 用in

字典元素添加、修改、删除

\1. 给字典新增“键值对”。如果“键”已经存在,则覆盖旧的键值对;如果“键”不存在, 则新增“键值对”。a[‘age’]=16

\2. 使用 update()将新字典中所有键值对全部添加到旧字典对象上。如果 key 有重复,则直 接覆盖。

1
2
3
>>> a = {'name':'gaoqi','age':18,'job':'programmer'} 
>>> b = {'name':'gaoxixi','money':1000,'sex':'男的'}
>>> a.update(b)

\3. 字典中元素的删除,可以使用 del()方法;或者 clear()删除所有键值对;pop()删除指定 键值对,并返回对应的“值对象”;

1
2
3
4
>>> del(a['name'])
>>> a
{'age': 18, 'job': 'programmer'}
>>> b = a.pop('age')

\4. popitem() :随机删除和返回该键值对。字典是“无序可变序列”,因此没有第一个元 素、最后一个元素的概念;popitem 弹出随机的项,因为字典并没有”最后的元素”或者其 他有关顺序的概念。若想一个接一个地移除并处理项,这个方法就非常有效(因为不用首先获取键的列表)。

a.popitem()

序列解包

序列解包可以用于元组、列表、字典。序列解包可以让我们方便的对多个变量赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> x,y,z=(20,30,10)
>>> x
20
>>> y
30
>>> z
10
>>> (a,b,c)=(9,8,10)
>>> a
9
>>> [a,b,c]=[10,20,30]
>>> a
10
>>> b
20

序列解包用于字典时,默认是对“键”进行操作; 如果需要对键值对操作,则需要使用 items();如果需要对“值”进行操作,则需要使用 values();

1
2
3
4
5
6
7
8
9
10
>>> s = {'name':'gaoqi','age':18,'job':'teacher'}
>>> name,age,job=s #默认对键进行操作
>>> name
'name'
>>> name,age,job=s.items()
>>> name
('name', 'gaoqi')
>>> name,age,job=s.values()
>>> name
'gaoqi'

字典核心底层原理(重要)

字典对象的核心是散列表。散列表是一个稀疏数组(总是有空白元素的数组),数组的 每个单元叫做 bucket。每个 bucket 有两部分:一个是键对象的引用,一个是值对象的引 用。
由于,所有 bucket 结构和大小一致,我们可以通过偏移量来读取指定 bucket。

扩容

python 会根据散列表的拥挤程度扩容。“扩容”指的是:创造更大的数组,将原有内容 拷贝到新数组中。

接近 2/3 时,数组就会扩容。

用法总结:
\1. 键必须可散列

(1) 数字、字符串、元组,都是可散列的。

(2) 自定义对象需要支持下面三点:

  1. 1 支持 hash()函数
  2. 2 支持通过eq()方法检测相等性。
  3. 3 若 a==b 为真,则 hash(a)==hash(b)也为真。

\2. 字典在内存中开销巨大,典型的空间换时间。
\3. 键查询速度很快
\4. 往字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字 典的同时进行字典的修改。

集合

集合创建和删除

\1. 使用{}创建集合对象,并使用 add()方法添加元素

\2. 使用 set(),将列表、元组等可迭代对象转成集合。如果原来数据存在重复数据,则只保 留一个。

\3. remove()删除指定元素;clear()清空整个集合

集合相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = {1,3,'sxt'}
>>> b = {'he','it','sxt'}
>>> a|b #并集
{1, 3, 'sxt', 'he', 'it'}
>>> a&b #交集
{'sxt'}
>>> a-b #差集
{1, 3}
>>> a.union(b) #并集
{1, 3, 'sxt', 'he', 'it'}
>>> a.intersection(b) #交集
{'sxt'}
>>> a.difference(b) #差集
{1, 3}

控制语句

三元条件运算符

Python 提供了三元运算符,用来在某些简单双分支赋值情况。三元条件运算符语法格式如 下:

条件为真时的值 if (条件表达式) else 条件为假时的值

1
print( num if int(num)<10 else "数字太大")

可迭代对象

Python 包含以下几种可迭代对象:

  1. ​ 序列。包含:字符串、列表、元组
    1. 字典
    2. . 迭代器对象(iterator)
      \4. 生成器函数(generator)
      \5. 文件对象
1
2
3
4
5
6

for x in print(x)
for x in print(x)
d.keys():#遍历字典所有的 key
d.values():#遍历字典所有的 value
for x in d.items():#遍历字典所有的"键值对"
1
【操作】利用嵌套循环打印九九乘法表
1
2
3
4
for m in range(1,10):
for n in range(1,m+1):
print("{0}*{1}={2}".format(m,n,(m*n)),end="\t")
print()

使用 zip()并行迭代

我们可以通过 zip()函数对多个序列进行并行迭代,zip()函数在最短序列“用完”时就会停止。

1
2
3
4
5
names = ("高淇","高老二","高老三","高老四") 
ages = (18,16,20,25)
jobs = ("老师","程序员","公务员")
for name,age,job in zip(names,ages,jobs):
print("{0}--{1}--{2}".format(name,age,job))

推导式创建序列

推导式是从一个或者多个迭代器快速创建序列的一种方法。它可以将循环和条件判断结合, 从而避免冗长的代码。推导式是典型的 Python 风格,会使用它代表你已经超过 Python 初 学者的水平。

列表推导式

列表推导式生成列表对象,语法如下:
[表达式 for item in 可迭代对象 ]

或者:{表达式 for item in 可迭代对象 if 条件判断}

1
2
3
4
5
6
7
8
9
10
11
12
13

>>> [x for x in range(1,5)]
[1, 2, 3, 4]
>>> [x*2 for x in range(1,5)]
[2, 4, 6, 8]
>>> [x*2 for x in range(1,20) if x%5==0 ]
[10, 20, 30]
>>> [a for a in "abcdefg"]
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> cells = [(row,col) for row in range(1,10) for col in range(1,10)]
#可以使用两个循环
>>> for cell in cells:
print(cell)

字典推导式

row in range(1,10) for col in range(1,10)]

#可以使用两

字典的推导式生成字典对象,格式如下:
{key_expression : value_expression for 表达式 in 可迭代对象}

类似于列表推导式,字典推导也可以增加 if 条件判断、多个 for 循环。

统计文本中字符出现的次数:

1
2
3
4
\>>> my_text = ' i love you, i love sxt, i love gaoqi'
\>>> char_count = {c:my_text.count(c) for c in my_text}
\>>> char_count
{' ': 9, 'i': 4, 'l': 3, 'o': 5, 'v': 3, 'e': 3, 'y': 1, 'u': 1, ',': 2, 's': 1, 'x': 1, 't': 1, 'g': 1, 'a': 1, 'q': 1}

集合推导式

集合推导式生成集合,和列表推导式的语法格式类似:
{表达式 for item in 可迭代对象 }

或者:{表达式 for item in 可迭代对象 if 条件判断}

>>> {x for x in range(1,100) if x%9==0} {99, 36, 72, 9, 45, 81, 18, 54, 90, 27, 63}

生成器推导式(生成元组)

很多同学可能会问:“都有推导式,元组有没有?”,能不能用小括号呢?

(x for x in range(1,100) if x%9==0)
<generator object at 0x0000000002BD3048>

我们发现提示的是“一个生成器对象”。显然,元组是没有推导式的。

一个生成器只能运行一次。第一次迭代可以得到数据,第二次迭代发现数据已经没有了。

1
2
3
4
5
6
7
8
9
>>> gnt = (x for x in range(1,100) if x%9==0)
>>> for x in gnt:

print(x,end=' ')
9 18 27 36 45 54 63 72 81 90 99

>>> for x in gnt:

print(x,end=' ')

函数

Python 中函数分为如下几类: 1. 内置函数

我们前面使用的 str()、list()、len()等这些都是内置函数,我们可以拿来直接使用。

  1. 标准库函数

我们可以通过 import 语句导入库,然后使用其中定义的函数

  1. 第三方库函数

Python 社区也提供了很多高质量的库。下载安装这些库后,也是通过 import 语句导 入,然后可以使用这些第三方库的函数

  1. 用户自定义函数

用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数。今天我们学习的 就是如何自定义函数。

文档字符串(函数的注释)

程序的可读性最重要,一般建议在函数体开始的部分附上函数定义说明,这就是“文档字符 串”,也有人成为“函数的注释”。我们通过三个单引号或者三个双引号来实现,中间可以

北京尚学堂·百战程序员 实战 系统 好教育

加入多行文字进行说明。

函数也是对象,内存底层分析

Python 中,“一切都是对象”。实际上,执行 def 定义函数后,系统就创建了相应的函数 对象。

变量的作用域(全局变量和局部变量)

变量起作用的范围称为变量的作用域,不同作用域内同名变量之间互不影响。变量分为:全局变量、局部变量。

全局变量:

\1. 在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块 结束。

  1. 全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用。
  2. 全局变量一般做常量使用。
  3. 函数内要改变全局变量的值,使用 global 声明一下

局部变量:
\1. 在函数体中(包含形式参数)声明的变量。
\2. 局部变量的引用比全局变量快,优先考虑使用。
\3. 如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 100 #全局变量
def f1():
global a #如果要在函数内改变全局变量的值,增加 global 关键字声明
print(a) #打印全局变量 a 的值,
a = 300
print(a)#局部变量a
print(locals())
print(globals())
f1()
print(a)

```结果
100
300
{}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/mengxiaowang/Documents/a.py', 'a': 300, 'f1': <function f1 at 0x7ff5602dc430>}
300
```

另一种输出,因为local变global后没有了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 100 #全局变量
def f1():
a = 300
print(locals())#{'a': 300}
print(globals())#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <function a at 0x7f9388270830>}
f1()
print(a)
#输出
```
{'a': 300}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/mengxiaowang/Documents/a.py', 'a': 100, 'f1': <function f1 at 0x7fc050745430>}
100

```

局部变量和全局变量效率测试

局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。 在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运 行速度。

参数的传递

函数的参数传递本质上就是:从实参到形参的赋值操作。 Python 中“一切皆对象”, 所有的赋值操作都是“引用的赋值”。所以,Python 中参数的传递都是“引用传递”,不 是“值传递”。具体操作时分为两类:

\1. 对“可变对象”进行“写操作”,直接作用于原对象本身。

\2. 对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填 充这块空间。(起到其他语言的“值传递”效果,但不是“值传递”)

可变对象有: 字典、列表、集合、自定义的对象等

不可变对象有: 数字、字符串、元组、function 等

浅拷贝和深拷贝

为了更深入的了解参数传递的底层原理,我们需要讲解一下“浅拷贝和深拷贝”。我们可以 使用内置函数:copy(浅拷贝)、deepcopy(深拷贝)。

浅拷贝:不拷贝子对象的内容,只是拷贝子对象的引用。 深拷贝:会连子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import copy
def testCopy(): '''测试浅拷贝'''
a = [10, 20, [5, 6]]
b = copy.copy(a)
print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("浅拷贝......")
print("a", a)
print("b", b)
def testDeepCopy(): '''测试深拷贝'''
a = [10, 20, [5, 6]]
b = copy.deepcopy(a)
print("a", a)
print("b", b)
b.append(30)
b[2].append(7)
print("深拷贝......")
print("a", a)
print("b", b)
testCopy()
print("*************")
testDeepCopy()
1
2
3
4
5
6
7
8
9
10
11
12
13
结果

a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
浅拷贝......
a [10, 20, [5, 6, 7]]
b [10, 20, [5, 6, 7], 30]
*************
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷贝......
a [10, 20, [5, 6]]
b [10, 20, [5, 6, 7], 30]

传递不可变对象包含的子对象是可变的情况

#传递不可变对象时。不可变对象里面包含的子对象是可变的。则 方法内修改了这个可变对象,源对象也发生了变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = (10,20,[5,6]) 
print("a:",id(a))
def test01(m):
print("m:",id(m))
m[2][0] = 888
print(m)
print("m:",id(m))
test01(a)
print(a)
运行结果:
a: 41611632
m: 41611632
(10, 20, [888, 6])
m: 41611632
(10, 20, [888, 6])

id相对于c中地址符&

*参数的几种类型 *

位置参数

函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为: “位置参数”。

默认值参数

我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。 默认值参数放到位置参数后面。def f1(a,b,c=10,d=20): #默认值参数必须位于普通位置参数后面

命名参数

我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”。f1(8,9,19) #位置参数 f1(c=10,a=20,b=30) #命名参数

强制命名参数

在带星号的“可变参数”后面增加新的参数,必须在调用的时候“强制命名参数”。

1
2
3
4
5
def f1(*a,b,c): 
print(a,b,c)
#f1(2,3,4) #会报错。由于 a 是可变参数,将 2,3,4 全部收集。造成 b 和 c 没有赋值。
f1(2,b=3,c=4)
执行结果: (2,) 3 4

lambda 表达式和匿名函数

lambda 表达式可以用来声明匿名函数。lambda 函数是一种简单的、在同一行中定义函数 的方法。lambda 函数实际生成了一个函数对象。
lambda 表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数 的返回值。

lambda 表达式的基本语法如下:
lambda arg1,arg2,arg3… : <表达式>

arg1/arg2/arg3 为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。

g = [lambda a:a2,lambda b:b3,lambda c:c*4]

eval()函数

功能:将字符串 str 当成有效的表达式来求值并返回计算结果。

语法: eval(source[, globals[, locals]]) -> value

参数:
source:一个 Python 表达式或函数 compile()返回的代码对象 globals:可选。必须是 dictionary locals:可选。任意映射对象

1
2
3
dict1 = dict(a=100,b=200)
d = eval("a+b",dict1)
print(d)#eval 函数会将字符串当做语句来执行,因此会被注入安全隐患。比如:字符串中含有删除文 件的语句。那就麻烦大了。因此,使用时候,要慎重!!!

递归函数

递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类 似于大家中学数学学习过的“数学归纳法”。 每个递归函数必须包含两个部分:
\1. 终止条件

表示递归什么时候结束。一般用于返回值,不再调用自己。 2. 递归步骤

把第 n 步的值和第 n-1 步相关联。

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨 慎使用。

嵌套函数(内部函数)

嵌套函数: 在函数内部定义的函数!

一般在什么情况下使用嵌套函数?

  1. 封装 - 数据隐藏外部无法访问“嵌套函数”。
  2. 贯彻 DRY(Don’t Repeat Yourself) 原则嵌套函数,可以让我们在函数内部避免重复代码。
  3. 闭包

nonlocal 关键字

nonlocal 用来声明外层的局部变量。 global 用来声明全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a=100
def outer():
b=10
def inner():
nonlocal b#声明外部函数的局部变量
print("inner b:",b)
b=20
global a#声明全局变量
a=1000
inner()
print("outer b:",b)
outer()
print("a:",a)
#结果
inner b: 10
outer b: 20
a: 1000

LEGB 规则

Python 在查找“名称”时,是按照 LEGB 规则查找的: Local–>Enclosed–>Global–>Built in

Local 指的就是函数或者类的方法内部
Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包) Global 指的是模块中的全局变量
Built in 指的是 Python 为自己保留的特殊名称。

如果某个 name 映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域 (enclosed)进行搜索,如果闭包作用域也没有找到,Python 就会到全局(global)命名空 间中进行查找,最后会在内建(built-in)命名空间搜索 (如果一个名称在所有命名空间 中都没有找到,就会产生一个 NameError)。

python基础入门

python入门

1.注释

  1. (1) 行注释

    每行注释前加#号。当解释器看到#,则忽略这一行#后面的内容

  2. (2) 段注释

    使用三个连续单引号(‘’’)。当解释看到’’’,则会扫描到下一个’’’,然后忽略他们

之间的内容。

2.行连接符

使用\行连接符

一行程序长度是没有限制的,但是为了可读性更强,通常将一行比较长的程序分为多行。这 是,我们可以使用\行连接符,把它放在行结束的地方。Python 解释器仍然将它们解释为同 一行。

>>> a = [10,20,30,40,\ 50,60,70,\

​ 80,90,100]

3.对象

Python 中,一切皆对象。每个对象由:标识(identity)、类型(type)、value(值)

组成。

  1. 标识用于唯一标识对象,通常对应于对象在计算机内存中的地址。使用内置函数 id(obj) 可返回对象 obj 的标识。

  2. 类型用于表示对象存储的“数据”的类型。类型可以限制对象的取值范围以及可执行的 操作。可以使用 type(obj)获得对象的所属类型。

  3. 值表示对象所存储的数据的信息。使用 print(obj)可以直接打印出值。

对象的本质就是:一个内存块,拥有特定的值,支持特定类型的相关操作。

源码:

>>> a = 3
>>> a
3
>>> id(3) 1531372336
>>> type(3) <class ‘int’>
>>> b = “我爱你” >>> id(a) 1531372336

>>> type(a) <class ‘int’> >>> print(a) 3

>>> id(b) 46806816 >>> type(b) <class ‘str’>

引用

在 Python 中,变量也称为:对象的引用。因为,变量存储的就是对象的地址。 变量通过地址引用了“对象”。

变量位于:栈内存(压栈出栈等细节,后续再介绍)。 对象位于:堆内存。

·Python 是动态类型语言

变量不需要显式声明类型。根据变量引用的对象,Python 解释器自动确定数据类型。

·Python 是强类型语言

每个对象都有数据类型,只支持该类型支持的操作。

4.python标识符规则

类型 规则 例子
模块和包名 全小写字母,尽量简单。若多个单词之间用 下划线 math, os, sys
函数名 全小写字母,多个单词之间用下划线隔开 phone, my_name
类名 首字母大写,采用驼峰原则。多个单词时, 每个单词第一个字母大写,其余部分小写 MyPhone 、 MyClass 、 Phone
常量名 全大写字母,多个单词使用下划线隔开 SPEED、MAX_SPEED

5.变量声明赋值

变量的声明和赋值用于将一个变量绑定到一个对象上,格式如下: 变量名 = 表达式

最简单的表达式就是字面量。比如:a = 123 。 运行过程中,解释器先运行右边的表达式, 生成一个代表表达式运算结果的对象;然后,将这个对象地址赋值给左边的变量。

变量在使用前必须先被初始化(先被赋值),否则出现NameError

可以通过 del 语句删除不在使用的变量。

如果对象没有变量引用,就会被垃圾回收器回收,清空内存空间。

1
2
3
4
5
6
>>> a=123
>>> del a
>>> x
Traceback (most recent call last):
File "<pyshell#20>", line 1, in <module> x
NameError: name 'x' is not defined

链式赋值

链式赋值用于同一个对象赋值给多个变量。 x=y=123 相当于:x=123; y=123

系列解包赋值

系列数据赋值给对应相同个数的变量(个数必须保持一致) >>> a,b,c=4,5,6 相当于:a=4;b=5;c=6

使用系列解包赋值实现变量交换

1
2
3
4
>>> a,b=1,2 
>>> a,b=b,a
>>> print(a,b)
21

Python 不支持常量,即没有语法规则限制改变一个常量的值。我们只能约定常量的命名规 则,以及在程序的逻辑上不对常量的值作出修改。(bug,其实可以改,所以说还是java好啊)

6.数据类型

使用 int()实现类型转换::其他类型转int

python有自动转型,小转大,int+float自动转float

最大整数是googol,也就是Google最初的名字,googol = 10**100

类似于 int(),我们也可以使用 float()将其他类型转化成浮点数。

round(value)可以返回四舍五入的值 注:但不会改变原有值,而是产生新的值

运算符+、-、,/、//、*和%和赋值符=结合可以构成“增强型赋值运算符”。

注意:“+=”中间不能加空格!

python 中可以通过 time.time() 获得当前时刻,返回的值是以秒为单位,带微秒 (1/1000 毫秒)精度的浮点值。

1
2
3
4
5
6
7
>>> import time
>>> b = int(time.time())
>>> totalMinutes = b/60
>>> totalMinutes = b//60
>>> totalHours = totalMinutes//60
>>> totalDays = totalHours//24
>>> totalYears = totalDays//365

Python2 中没有布尔值,直接用数字 0 表示 False,用数字 1 表示 True。(类似c)
Python3 中,把 True 和 False 定义成了关键字,但他们的本质还是 1 和 0,甚至可以和数 字相加。

同一运算符用于比较两个对象的存储单元,实际比较的是对象的地址。

is;is 是判断两个标识符是不是引用同一个对象

is not:is not 是判断两个标识符是不是引用不同对象

is 与 == 区别:
is 用于判断两个变量引用对象是否为同一个,既比较对象的地址。

== 用于判断引用变量引用对象的值是否相等,默认调用对象的 eq()方法。

Python 仅仅对比较小的整数对象进行缓存(范围为[-5, 256])缓存起来,而并非是所有整数对 象。需要注意的是,这仅仅是在命令行中执行,而在 Pycharm 或者保存为文件执行,结果是不一样 的,这是因为解释器做了一部分优化(范围是[-5,任意正整数])。

·总结
1、is 比较两个对象的 id 值是否相等,是否指向同一个内存地址;
2、== 比较的是两个对象的内容是否相等,值是否相等;
3、小整数对象[-5,256]在全局解释器范围内被放入缓存供重复使用;
4、is 运算符比 == 效率高,在变量和 None 进行比较时,应该使用 is。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = 1000
>>> b = 1000
>>> a == b
True
>>> a is b
False
>>> id(a) 46764560
>>> id(b) 46765216
>>> c = 10
>>> d = 10
>>> c is d
True#和整数缓存有关,类似java
>>> id(c) 1388831648
>>> id(d) 1388831648

与 C 和 JAVA 不一样,Python 不支持自增(++)和自减(–)

7。字符串

字符串的本质是:字符序列。Python 的字符串是不可变的,我们无法对原字符串做任 何修改。但,可以将字符串的一部分复制到新创建的字符串,达到“看起来修改”的效果。

Python 不支持单字符类型,单字符也是作为一个字符串使用的。

Python3 直接支持 Unicode,可以表示世界上任何书面语言的字符。Python3 的字符 默认就是 16 位 Unicode 编码,ASCII 码是 Unicode 编码的子集。

使用内置函数 ord()可以把字符转换成对应的 Unicode 码; 使用内置函数 chr()可以把十进制数字转换成对应的字符。

连续三个单引号或三个双引号,可以帮助我们创建多行字符串。例如:

resume = ‘’’ name=”gaoqi”
company=”sxt” age=18
lover=”Tom”‘’’

>>> print(resume)

name=”gaoqi”

company=”sxt” age=18 lover=”Tom”

转义字符: 我们可以使用“\+特殊字符”,实现某些难以用字符表示的效果

我们前面调用 print 时,会自动打印一个换行符。有时,我们不想换行,不想自动添加换行 符。我们可以自己通过参数 end = “任意字符串”。实现末尾添加任何内容

1
2
3
4
5
print("sxt",end=' ') 
print("sxt",end='##')
print("sxt")
运行结果:
sxt sxt##sxt

我们可以使用 input()从控制台读取键盘输入的内容。

当我们调用 print()函数时,解释器自动调用了 str()将非字符串的对象转成了字符串。

字符串的本质就是字符序列,我们可以通过在字符串后面添加[],在[]里面指定偏移量, 可以提取该位置的单个字符。

正向搜索:
最左侧第一个字符,偏移量是 0,第二个偏移量是 1,以此类推。直到 len(str)-1

为止。 反向搜索:

最右侧第一个字符,偏移量是-1,倒数第二个偏移量是-2,以此类推,直到-len(str) 为止。

字符串不可改变。但是,我们确实有时候需要替换某些字符。这时,只能通过创建新的字符 串来实现。

使用replace

1
2
a="alfjoashoa"
a=a.replace("a","替换字")

切片 slice 操作可以让我们快速的提取子字符串。标准格式为: [起始偏移量 start:终止偏移量 end:步长 step]

典型操作(三个量为正数的情况)如下:

操作和说明 示例 结果
[:] 提取整个字符串 “abcdef”[:] “abcdef”
[start:]从 start 索引开始到结尾 “abcdef”[2:] “cdef”
[:end]从头开始知道 end-1 “abcdef”[:2] “ab”
[start:end]从 start 到 end-1 “abcdef”[2:4] “cd”
[start​:end:step]从 start 提取到 end-1,步长是 step “abcdef”[1:5:2] “bd”
1
其他操作(三个量为负数)的情况:
示例 说明 结果
“abcdefghijklmnopqrstuv wxyz”[-3:] 倒数三个 “xyz”
“abcdefghijklmnopqrstuv wxyz”[-8:-3] 倒数第八个到倒数第 三个(包头不包尾) ‘stuvw’
“abcdefghijklmnopqrstuv wxyz”[::-1] 步长为负,从右到左 反向提取 ‘zyxwvutsrqpon mlkjihgfedcba’

split()分割和 join()合并

split()可以基于指定分隔符将字符串分隔成多个子字符串(存储到列表中)。如果不指定分隔 符,则默认使用空白字符(换行符/空格/制表符)。示例代码如下:

1
2
3
4
5
>>> a = "to be or not to be" 
>>> a.split()
['to', 'be', 'or', 'not', 'to', 'be']
>>> a.split('be')
['to ', ' or not to ', '']

join()的作用和 split()作用刚好相反,用于将一系列子字符串连接起来。示例代码如下:

1
2
3
>>> a = ['sxt','sxt100','sxt200']
>>> '*'.join(a)
'sxt*sxt100*sxt200'

拼接字符串要点:

使用字符串拼接符+,会生成新的字符串对象,因此不推荐使用+来拼接字符串。推荐 使用 join 函数,因为 join 函数在拼接字符串之前会计算所有字符串的长度,然后逐一拷贝, 仅新建一次对象。

字符串驻留:仅保存一份相同且不可变字符串的方法,不同的值被存放在字符串驻留池中。 Python 支持字符串驻留机制,对于符合标识符规则的字符串(仅包含下划线(_)、字母 和数字)会启用字符串驻留机制驻留机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = "abd_33"
>>> b = "abd_33"
>>> a is b
True
>>> c = "dd#"
>>> d = "dd#"
>>> c is d
False
>>> str1 = "aa"
>>> str2 = "bb"
>>> str1+str2 is "aabb"
False
>>> str1+str2 == "aabb"
True

字符串比较和同一性

我们可以直接使用==,!=对字符串进行比较,是否含有相同的字符。
我们使用 is / not is,判断两个对象是否同一个对象。比较的是对象的地址,即 id(obj1)是 否和 id(obj2)相等。

字符串常用方法汇总

常用查找方法

1
2
3
4
5
6
7
8
a="akhfahglwaejiorhsdlhgkld"
len(a)#返回长度
a.startswith('a')#以指定字符串开头
a.endswith('d')#以指定字符串结尾
a.find('k')#第一次出现指定字符串的位置
a.rfind('d')#最后一次出现指定字符串的位置
a.count('ad')#指定字符串出现几次
a.isalnum()#所有字符全是字母数字

去除首尾信息

我们可以通过 strip()去除字符串首尾指定信息。通过 lstrip()去除字符串左边指定信息, rstrip()去除字符串右边指定信息。

1
2
3
4
5
6
7
8
>>> "*s*x*t*".strip("*") 
's*x*t'
>>> "*s*x*t*".lstrip("*")
's*x*t*'
>>> "*s*x*t*".rstrip("*")
'*s*x*t'
>>> " sxt ".strip()
'sxt'

大小写转换

a.capitalize() 产生新的字符串,首字母 大写
a.title() 产生新的字符串,每个单 词都首字母大写
a.upper() 产生新的字符串,所有字 符全转成大写
a.lower() 产生新的字符串,所有字 符全转成小写
a.swapcase() 产生新的,所有字母大小 写转换

格式排版

center()、ljust()、rjust()这三个函数用于对字符串实现排版。示例如下: >>> a=”SXT”
>>> a.center(10,”*”)
SXT*

>>> a.center(10)
‘ SXT ‘
>>> a.ljust(10,”*”) ‘SXT***

其他方法

\1. isalnum() 是否为字母或数字
\2. isalpha() 检测字符串是否只由字母组成(含汉字)。 3. isdigit() 检测字符串是否只由数字组成。
\4. isspace() 检测是否为空白符
\5. isupper() 是否为大写字母
\6. islower() 是否为小写字母

字符串的格式化

format()基本用法

Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的 功能。
基本语法是通过 {} 和 : 来代替以前的 % 。

format 函数可以接受不限个参数,位置可以不按顺序。

1
2
3
4
5
6
7
8
9
10
11
>>> a = "名字是:{0},年龄是:{1}" 
>>> a.format("高淇",18)
'名字是:高淇,年龄是:18'
>>> a.format("高希希",6)
'名字是:高希希,年龄是:6'
>>> b = "名字是:{0},年龄是{1}。{0}是个好小伙"
>>> b.format("高淇",18)
'名字是:高淇,年龄是 18。高淇是个好小伙'
>>> c = "名字是{name},年龄是{age}"
>>> c.format(age=19,name='高淇')
'名字是高淇,年龄是 19'#我们可以通过{索引}/{参数名},直接映射参数值,实现对字符串的格式化,非常方便。

填充与对齐

填充常跟对齐一起使用 ^、<、>分别是居中、左对齐、右对齐,后面带宽度

:号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充

1
2
>>> "{:*>8}".format("245")
'*****245'

浮点数通过 f,整数通过 d 进行需要的格式化。案例如下:

1
2
3
4
>>> a = "我是{0},我的存款有{1:.2f}"

>>> a.format("高淇",3888.234342)
'我是高淇,我的存款有 3888.23'
数字 格式 输出 描述
3.1415926 {:.2f} 3.14 保留小数点后两位
3.1415926 {:+.2f} 3.14 带符号保留小数点后两位
2.71828 {:.0f} 3 不带小数
5 {:0>2d} 05 数字补零 (填充左边, 宽度为 2)
5 {:x<4d} 5xxx 数字补 x (填充右边, 宽度为 4)
10 {:x<4d} 10xx 数字补 x (填充右边, 宽度为 4)
1000000 {:,} 1,000,000 以逗号分隔的数字格式
0.25 {:.2%} 25.00% 百分比格式
1000000000 {:.2e} 1.00E+09 指数记法
13 {:10d} 13 右对齐 (默认, 宽度为 10)
13 {:<10d} 13 左对齐 (宽度为 10)
13 {:^10d} 13 中间对齐 (宽度为 10)

可变字符串

在 Python 中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,智 能创建新的字符串对象。但是,经常我们确实需要原地修改字符串,可以使用 io.StringIO 对象或 array 模块。

1
2
3
4
5
6
7
8
9
10
11
12
>>> import io
>>> s = "hello, sxt"
>>> sio = io.StringIO(s)
>>> sio
<_io.StringIO object at 0x02F462B0> >>> sio.getvalue()
'hello, sxt'
>>> sio.seek(7)
7
>>> sio.write("g")
1
>>> sio.getvalue()
'hello, gxt'

教你如何ssh免密操作和无需输入ip及用户名,已经scp如何使用

如何获取本地ip

1
2
3
4
ifconfig | grep inet
#如果没有ifconfig命令
linux中
sudo apt install net-tools

教你玩转ssh

ssh, secure shell,远程登录服务器,机器的一种命令。

此教程仅适合mac,unix,linux系统,windows系统请使用PuTTy 等软件进行操作。

1.先在本机生成rsa密钥对

1
ssh-keygen -t rsa

2.然后一路enter,不要设置密码

本机 .ssh 隐藏目录中会多几个文件,

authorized-keys config id_rsa id_rsa.pub known_hosts

其中id_rsa.pub是你的公钥,id_rsa是私钥。

1
2
3
4
5
6
vi config
#点i进入insert模式,按照各人信息填入,school是你想要的快捷名,以后无需输入ip和用户名了,ip是远程服务器ip
Host school
HostName ip address
User username
Port portnumber

3.然后开始免密操作,很简单,mac需要homebrew

1
brew install ssh-copy-id

然后,主要此处school是你的快捷名字。

1
ssh-copy-id school

这是最简单的操作,或者手动把你的公钥复制到远程服务器~/.ssh/authorized_keys中。

教你玩会scp

ssh就这么简单,既然如此简单,我们顺便提一下scp。

scp就是安全传输文件的命令,

scp没有那么多小技巧,唯一就是要注意路径怎么写,要用绝对路径,最好不要相对路径。

还有就是目录需要加-r

. 表示当前目录

~表示家目录

/表示根目录

了解以上3个即可帮你灵活使用scp了,记住,unix类的操作系统都是树形管理文件的,而不是windows分区形(其实我也不了解)

下面我们开始了解scp命令

1、从本地复制到远程

命令格式:

1
2
3
4
5
6
7
scp local_file remote_username@remote_ip:remote_folder 
或者
scp local_file remote_username@remote_ip:remote_file
或者
scp local_file remote_ip:remote_folder
或者
scp local_file remote_ip:remote_file
  • 第1,2个指定了用户名,命令执行后需要再输入密码,第1个仅指定了远程的目录,文件名字不变,第2个指定了文件名;
  • 第3,4个没有指定用户名,命令执行后需要输入用户名和密码,第3个仅指定了远程的目录,文件名字不变,第4个指定了文件名;

应用实例:

1
2
3
4
scp /home/space/music/1.mp3 root@www.runoob.com:/home/root/others/music 
scp /home/space/music/1.mp3 root@www.runoob.com:/home/root/others/music/001.mp3
scp /home/space/music/1.mp3 www.runoob.com:/home/root/others/music
scp /home/space/music/1.mp3 www.runoob.com:/home/root/others/music/001.mp3

复制目录命令格式:

1
2
3
scp -r local_folder remote_username@remote_ip:remote_folder 
或者
scp -r local_folder remote_ip:remote_folder
  • 第1个指定了用户名,命令执行后需要再输入密码;
  • 第2个没有指定用户名,命令执行后需要输入用户名和密码;

应用实例:

1
2
scp -r /home/space/music/ root@www.runoob.com:/home/root/others/ 
scp -r /home/space/music/ www.runoob.com:/home/root/others/

上面命令将本地 music 目录复制到远程 others 目录下。

2、从远程复制到本地

从远程复制到本地,只要将从本地复制到远程的命令的后2个参数调换顺序即可,如下实例

应用实例:

1
2
scp root@www.runoob.com:/home/root/others/music /home/space/music/1.mp3 
scp -r www.runoob.com:/home/root/others/ /home/space/music/

说明

1.如果远程服务器防火墙有为scp命令设置了指定的端口,我们需要使用 -P 参数来设置命令的端口号,命令格式如下:

1
2
#scp 命令使用端口号 4588
scp -P 4588 remote@www.runoob.com:/usr/local/sin.sh /home/administrator

2.使用scp命令要确保使用的用户具有可读取远程服务器相应文件的权限,否则scp命令是无法起作用的。

原文:[https://www.zhihu.com/search?type=content&q=%20%E7%88%AC%E8%99%AB%E6%80%8E%E4%B9%88%E5%AD%A6](https://www.zhihu.com/search?type=content&q= 爬虫怎么学)

博客:https://cuiqingcai.com/947.html

python爬虫入门

1.爬虫基础了解

1.什么是爬虫

爬虫,即网络爬虫,大家可以理解为在网络上爬行的一直蜘蛛,互联网就比作一张大网,而爬虫便是在这张网上爬来爬去的蜘蛛咯,如果它遇到资源,那么它就会抓取下来。想抓取什么?这个由你来控制它咯。

比如它在抓取一个网页,在这个网中他发现了一条道路,其实就是指向网页的超链接,那么它就可以爬到另一张网上来获取数据。这样,整个连在一起的大网对这之蜘蛛来说触手可及,分分钟爬下来不是事儿。

2.浏览网页的过程

在用户浏览网页的过程中,我们可能会看到许多好看的图片,比如 http://image.baidu.com/ ,我们会看到几张的图片以及百度搜索框,这个过程其实就是用户输入网址之后,经过DNS服务器,找到服务器主机,向服务器发出一个请求,服务器经过解析之后,发送给用户的浏览器 HTML、JS、CSS 等文件,浏览器解析出来,用户便可以看到形形色色的图片了。

因此,用户看到的网页实质是由 HTML 代码构成的,爬虫爬来的便是这些内容,通过分析和过滤这些 HTML 代码,实现对图片、文字等资源的获取。

3.URL的含义

URL,即统一资源定位符,也就是我们说的网址,统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。

URL的格式由三部分组成:
①第一部分是协议(或称为服务方式)。
②第二部分是存有该资源的主机IP地址(有时也包括端口号)。
③第三部分是主机资源的具体地址,如目录和文件名等。

爬虫爬取数据时必须要有一个目标的URL才可以获取数据,因此,它是爬虫获取数据的基本依据,准确理解它的含义对爬虫学习有很大帮助。

2. Urllib库基本使用

1
2
3
4
5
6
import urllib
import urllib.request as urllib2


response = urllib2.urlopen("http://www.baidu.com")
print(response.read())
1
2
3
4
5
6
7
 urlopen(url, data, timeout)

第一个参数url即为URL,第二个参数data是访问URL时要传送的数据,第三个timeout是设置超时时间。

第二三个参数是可以不传送的,data默认为空None,timeout默认为 socket._GLOBAL_DEFAULT_TIMEOUT

第一个参数URL是必须要传送的,在这个例子里面我们传送了百度的URL,执行urlopen方法之后,返回一个response对象,返回信息便保存在这里面。
1
2
3
4
5
 print response.read()
response对象有一个read方法,可以返回获取到的网页内容。
如果不加read直接打印会是什么?
<addinfourl at 139728495260376 whose fp = <socket._fileobject **object** at 0x7f1513fb3ad0>>
直接打印出了该对象的描述,所以记得一定要加read方法

构造Request

1
2
3
4
5
import urllib.request as urllib2

request = urllib2.Request("http://www.baidu.com")#推荐这样书写,因为在构建请求时还需要加入好多内容,通过构建一个request,服务器响应请求得到应答,这样显得逻辑上清晰明确。
response = urllib2.urlopen(request)
print(response.read())

POST和GET数据传送

上面的程序演示了最基本的网页抓取,不过,现在大多数网站都是动态网页,需要你动态地传递参数给它,它做出对应的响应。所以,在访问时,我们需要传递数据给它。

GET方式是直接以链接形式访问,链接中包含了所有的参数,当然如果包含了密码的话是一种不安全的选择,不过你可以直观地看到自己提交了什么内容。POST则不会在网址上显示所有的参数

1
2


爬虫入门

pip3 install requests selenium beautifulsoup4 pyquery pymysql pymongo redis flask django jupyter

安装各种库,安装MongoDB,redis,anaconda,pycharm,Python3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
#带参数get请求
>>> response=requests.get('https://httpbin.org/get?name=jackson&age=100')
>>> print(response.text)

>>> data={'name':'ap','age':99}
>>> response=requests.get('https://httpbin.org/get',params=data)
>>> print(response.text)

#解析json
import json
>>> response=requests.get('https://httpbin.org/get',params=data)
>>> print(response.json)
>>> print(json.loads(response.text))#以上两个打印一样
>>> print(type(response.json()))
<class 'dict'>

#获取二进制数据,可以保持图片视频
>>> response=requests.get("https://github.com/favicon.ico")
>>> print(type(response.text),type(response.content))
<class 'str'> <class 'bytes'>
>>> print(response.text)#一堆乱码
>>> print(response.content)#一堆16进制数字
#保存二进制图片视频
>>> response=requests.get("https://github.com/favicon.ico")
>>> with open('favicon.ico','wb') as f:#命名为favicoc
... f.write(response.content)
... f.close()
#添加headers
>>> headers={'User-Agent':'。。。一堆码读出来的'}
>>> response=requests.get("https://www.zhihu.com/explore",headers=headers)
>>> print(response.text)

#基本post请求
>>> data={'name':'ap','age':99}
>>> response=requests.post('https://httpbin.org/post',data=data)
>>> print(response.text)
#headers post,报表表单
>>> data={'name':'ap','age':99}
headers={'User-Agent':'。。。一堆码读出来的'}
>>> response=requests.post("https://httpbin.org/post",data=data,headers=headers)
>>> print(response.json())

#response属性

#找headers https://mkyong.com/computer-tips/how-to-view-http-headers-in-google-chrome/

找到最下面的User-Agent: Mozilla 。。。

看到python非常全资料/python3爬虫实战/课时09