创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否已经在已访问的列表中,若已存在就直接跳过。
grant all privileges on . to ‘数据库中用户名’@’ip 地址’ identified by ‘数据库密码’;
Python 主要的内置数据类型有:str,int,float,tuple,list,dict,set。
会打印出字符型的所有的内置方法。
new_str=old_str[::-1]
a,b = b,a
select
count(hotel)i
from
hotel_table
where
distance>20
group by
city
^[A-Za-z]|_.*\d$
通过状态码告诉客户端服务器的执行状态,以判断下一步该执行什么操作。
常见的状态机器码有:
1)浏览器向 DNS 服务器发送 itheima.com 域名解析请求;
2)DNS 服务器返回解析后的 ip 给客户端浏览器,浏览器想该 ip 发送页面请求;
3)DNS 服务器接收到请求后,查询该页面,并将页面发送给客户端浏览器;
4)客户端浏览器接收到页面后,解析页面中的引用,并再次向服务器发送引用资源请求;
5)服务器接收到资源请求后,查找并返回资源给客户端;
6)客户端浏览器接收到资源后,渲染,输出页面展现给用户。
思路:就是有几个嵌套链表就用几个 for 循环进行迭代,然后对最后一个结果进行打印。
内连接查询:查询的结果为两个表匹配到的数据。
右接查询:查询的结果为两个表匹配到的数据,右表特有的数据,对于左表中不存在的数据使用 null 填充。
左连接查询:查询的结果为两个表匹配到的数据,左表特有的数据,对于右表中不存在的数据使用 null 填充。
区别
1、左连接:左连接的结果集为left join左侧数据表中的数据,再加上left join左侧与右侧数据表之间匹配的数据。
2、右连接:右连接的结果集为rightjoin右侧数据表中的数据,再加上rightjoin左侧与右侧数据表之间匹配的数据。
3. 使用 Crontab
参考博客:https://blog.csdn.net/gaoshanliushui131/article/details/72721704
1.优化算法时间复杂度。
2.减少冗余数据。
3.合理使用 copy 与 deepcopy。
4.使用 dict 或 set 查找元素。
5.合理使用生成器(generator)和 yield。
6.优化循环。
7.优化包含多个判断表达式的顺序。
8.使用 join 合并迭代器中的字符串。
9.选择合适的格式化字符方式。
10 不借助中间变量交换两个变量的值。
11.使用 if is。
12.使用级联比较 x < y < z。
13.while 1 比 while True 更快。 14.使用**而不是 pow。
15.使用 cProfile, cStringIO 和 cPickle 等用 c 实现相同功能(分别对应 profile, StringIO, pickle) 的包。
16.使用最佳的反序列化方式。
17.使用 C 扩展(Extension)。
18.并行编程。
19.终级大杀器:PyPy。
20.使用性能分析工具。
在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作。
String(字符串),Hash(哈希),List(列表),Set(集合)及 zset(sortedset:有序集合
Redis 用到的地方很多,如我们最熟悉的分布式爬虫,Set 去重等,具体可以扫下面二维码查看。
索引在很多数据库中是提高性能的标志优化手段,所以在大数据量的情况下索引可以提高数据的查 询速度,如果没有索引 MongoDB 会扫描全部数据,才能获取满足条件的内容,在关系数据库中可以 使用强制索引方式查询数据库,确保更准确快速的查询到满足条件的数据。
语法:
1、ensureIndex() 基本语法 1 创建升序索引 -1 创建降序索引
2、mongodb 默认所以字段 _id ,创建文档,会自动创建,此索引不能删除由 mongodb 自己维护
相关参数:
1、unique 创建唯一索引,默认 false ,true 必须唯一索引,否则报错
实例:
1、创建升序索引 db.user.ensureIndex({age:1}); db.user.find({age:{$gte:20}});
我们常规处理并发的解决方案:
1.动态页面静态化。
2.制作数据库散列表,即分库分表。
3.增加缓存。
4.增加镜像。
5.部署集群。
6.负载均衡。
7.异步读取,异步编程。
8.创建线程池和自定义连接池,将数据持久化。
9.把一件事,拆成若干件小事,启用线程,为每个线程分配一定的事做,多个线程同时进行把该事 件搞定再合并。
根据自己熟悉的算法说下思路,如:快排、二分法。
这几个符号都是可以表示字符串的,如果是表示一行,则用单引号或者双引号表示,它们的区别是 如果内容里有"符号,并且你用双引号表示的话则需要转义字符,而单引号则不需要。
三单引号和三双引号也是表示字符串,并且可以表示多行,遵循的是所见即所得的原则。
另外,三双引号和三单引号可以作为多行注释来用,单行注释用#号。
Global 声明。
yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始
生成器。
当有多个返回值时,用 return 全部一起返回了,需要单个逐一返回时可以用 yield。
使用yield可以构造一个生成器,可以在循环中避免前面的内容被后面的内容覆盖。例如:
import pandas as pd
dic_list = [{'a':1,'b':2,'c':3},{'a':4,'b':5,'c':6}]
def get_abc(dic_list):
for item in dic_list:
js = {}
js['A'] = item['a']
js['B'] = item['b']
js['C'] = item['c']
yield(js)
pd.DataFrame(get_abc(dic_list))
output:
A B C
0 1 2 3
1 4 5 6
for i in range(1,11)
生成器:(i for i in range(1,10))
import os
import glob
from PIL import Image
def thumbnail_pic(path): a=glob.glob(r'./*.jpg') for x in a:
name=os.path.join(path,x) im=Image.open(name) im.thumbnail((80,80)) print(im.format,im.size,im.mode) im.save(name,'JPEG')
print('Done!')
if __name__=='__main__': path='.'
thumbnail_pic(path)
1、Headers:
从用户的 headers 进行反爬是最常见的反爬虫策略。Headers(上一讲中已经提及) 是一种区分
浏览器行为和机器行为中最简单的方法,还有一些网站会对 Referer (上级链接)进行检测(机器行 为不太可能通过链接跳转实现)从而实现爬虫。
2、IP 限制
一些网站会根据你的 IP 地址访问的频率,次数进行反爬。也就是说如果你用单一的 IP 地址访问频率过高,那么服务器会在短时间内禁止这个 IP 访问。
解决措施:构造自己的 IP 代理池,然后每次访问时随机选择代理(但一些 IP 地址不是非常稳定,需要经常检查更新)。
3、UA限制
UA 是用户访问网站时候的浏览器标识,其反爬机制与 ip 限制类似。
解决措施:构造自己的 UA 池,每次 python 做 requests 访问时随机挂上 UA 标识,更好地模拟浏览器行为。当然如果反爬对时间还有限制的话,可以在 requests 设置 timeout(最好是随机休眠,这 样会更安全稳定,time.sleep())。
4.验证码反爬虫或者模拟登陆 验证码:这个办法也是相当古老并且相当的有效果,如果一个爬虫要解释一个验证码中的内容,这在以前通过简单的图像识别是可以完成的,但是就现在来讲,验证码的干扰线,噪点都很多,甚至还出 现了人类都难以认识的验证码
相应的解决措施:验证码识别的基本方法:截图,二值化、中值滤波去噪、分割、紧缩重排(让高矮统一)、字库特征匹配识别。(python 的 PIL 库或者其他)
模拟登陆(例如知乎等):用好 python requests 中的 session(下面几行代码实现了最简单的 163 邮 箱的登陆,其实原理是类似的~~)。
5.Ajax 动态加载
网页的不希望被爬虫拿到的数据使用 Ajax 动态加载,这样就为爬虫造成了绝大的麻烦,如果一个爬
虫不具备 js 引擎,或者具备 js 引擎,但是没有处理 js 返回的方案,或者是具备了 js 引擎,但是没办法 让站点显示启用脚本设置。基于这些情况,ajax 动态加载反制爬虫还是相当有效的。
Ajax 动态加载的工作原理是:从网页的 url 加载网页的源代码之后,会在浏览器里执行 JavaScript 程序。这些程序会加载出更多的内容,并把这些内容传输到网页中。这就是为什么有些网页直接爬它的 URL 时却没有数据的原因。
处理方法:若使用审查元素分析”请求“对应的链接(方法:右键→审查元素→Network→清空,点 击”加载更多“,出现对应的 GET 链接寻找 Type 为 text/html 的,点击,查看 get 参数或者复制 Request URL),循环过程。如果“请求”之前有页面,依据上一步的网址进行分析推导第 1 页。以此类推,抓取 抓Ajax地址的数据。对返回的json使用requests中的json进行解析,使用eva(l )转成字典处理(上 一讲中的 fiddler 可以格式化输出 json 数据。
6.cookie 限制
一次打开网页会生成一个随机 cookie,如果再次打开网页这个 cookie 不存在,那么再次设置,第
三次打开仍然不存在,这就非常有可能是爬虫在工作了。
解决措施:在 headers 挂上相应的 cookie 或者根据其方法进行构造(例如从中选取几个字母进行
构造)。如果过于复杂,可以考虑使用 selenium 模块(可以完全模拟浏览器行为)。
HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准 (TCP),用于从 WWW 服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。
HTTPS:是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版,即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。
HTTPS 协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另 一种就是确认网站的真实性。
1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
2、cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当 使用 session。
3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减 轻服务器性能方面,应当使用 cookie。
4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
5、可以考虑将登陆信息等重要信息存放为 session,其他信息如果需要保留,可以放在 cookie 中。
1、 存储结构
MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)。
InnoDB:所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件), InnoDB 表的大小只受限于操作系统文件的大小,一般为 2GB。
2、 存储空间 MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。 InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。
3、 事务支持
MyISAM:强调的是性能,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是不提供事务支持。
InnoDB:提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
4、 CURD 操作
MyISAM:如果执行大量的 SELECT,MyISAM 是更好的选择。(因为没有支持行级锁),在增删的 时候需要锁定整个表格,效率会低一些。相关的是 innodb 支持行级锁,删除插入的时候只需要锁定改 行就行,效率较高
InnoDB:如果你的数据执行大量的 INSERT 或 UPDATE,出于性能方面的考虑,应该使用 InnoDB 表。DELETE 从性能上 InnoDB 更优,但DELETE FROM table 时,InnoDB 不会重新建立表,而是一 行一行的删除,在 innodb 上如果要清空保存有大量数据的表,最好使用 truncate table 这个命令。
5、 外键
MyISAM:不支持
InnoDB:支持
区别:
1、list、tuple 是有序列表;dict、set 是无序列表;
2、list 元素可变、tuple 元素不可变;
3、dict 和 set 的 key 值不可变,唯一性;
4、set 只有 key 没有 value;
5、set 的用途:去重、并集、交集等; 6、list、tuple:+、*、索引、切片、检查成员等;
7、dict 查询效率高,但是消耗内存多;list、tuple 查询效率低、但是消耗内存少
应用场景:
list,:简单的数据集合,可以使用索引;
tuple:把一些数据当做一个整体去使用,不能修改;
dict:使用键值和值进行关联的数据;
set:数据只出现一次,只关心数据是否出现, 不关心其位置;
装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因为就产生了装饰器,使得其满足:
不能修改被装饰的函数的源代码
不能修改被装饰的函数的调用方式
满足1、2的情况下给程序增添功能
那么根据需求,同时满足了这三点原则,这才是我们的目的。因为,下面我们从解决这三点原则入手来理解装饰器。
等等,我要在需求之前先说装饰器的原则组成:
< 函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器 >
这个式子是贯穿装饰器的灵魂所在!
每个 HTTP 请求和响应都会带有相应的头部信息。默认情况下,在发送 XHR 请求的同时,还会发送 下列头部信息:
Accept:浏览器能够处理的内容类型
Accept-Charset:浏览器能够显示的字符集
Accept-Encoding:浏览器能够处理的压缩编码
Accept-Language:浏览器当前设置的语言
Connection:浏览器与服务器之间连接的类型
Cookie:当前页面设置的任何
Cookie Host:发出请求的页面所在的域
Referer:发出请求的页面的
URL User-Agent:浏览器的用户代理字符串
HTTP 响应头部信息:
Date:表示消息发送的时间,时间的描述格式由 rfc822 定义
server:服务器名字。
Connection:浏览器与服务器之间连接的类型
content-type:表示后面的文档属于什么 MIME 类型
Cache-Control:控制 HTTP 缓存
Python 中的对象包含三要素:id、type、value。
其中 id 用来唯一标识一个对象,type 标识对象的类型,value 是对象的值。is 判断的是 a 对象是否 就是 b 对象,是通过 id 来判断的。==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判断的。
read() 每次读取整个文件,它通常将读取到底文件内容放到一个字符串变量中,也就是 说 .read() 生成文件内容是一个字符串类型;
readline()每只读取文件的一行,通常也是读取到的一行内容放到一个字符串变量中,返回 str 类型;
readlines()每次按行读取整个文件内容,将读取到的内容放到一个列表中,返回 list 类型;
match()函数只检测 RE 是不是在 string 的开始位置匹配,search()会扫描整个 string 查找匹配;也 就是说 match()只有在 0 位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返 回 none;
例如:
print(re.match(‘super’, ‘superstition’).span()) 会返回(0, 5) 而 print(re.match(‘super’, ‘insuperable’)) 则返回 None search()会扫描整个字符串并返回第一个成功的匹配:
例如:
print(re.search(‘super’, ‘superstition’).span())返回(0, 5)
print(re.search(‘super’, ‘insuperable’).span())返回(2, 7) 其中 span 函数定义如下,返回位置信息:
span([group]):
返回(start(group), end(group))。
*args 表示任何多个无名参数,它是一个 tuple。
**kwargs 表示关键字参数,它是一个 dict。
sys.path 是喜闻乐见的 PATH 环境变量,os.path 是一个 module,提供 split、join、basename 等“处理目录、文件名”的工具。
并行(parallel)是指同一时刻,两个或两个以上时间同时发生。 并发(parallel)是指同一时间间隔(同一段时间),两个或两个以上时间同时发生。
1)线程 2)进程 3)协程 4)threading。
并发
并行
7层从上到下分别是 7应用层6表示层5 会话层 4传输层3 网络层 2数据链路层1物理层;其 中高层(即 7、6、5、4 层)定义了应用程序的功能,下面 3 层(即 3、2、1 层)主要面向通过网络的 端到端的数据流。
HTTP 属于应用层。
HTTP 是 hypertext transfer protocol(超文本传输协议)的简写,它是 TCP/IP 协议的一个应用层 协议,用于定义 WEB 浏览器与 WEB 服务器之间交换数据的过程。客户端连上 web 服务器后,若想获 得 web 服务器中的某个 web 资源,需遵守一定的通讯格式,HTTP 协议用于定义客户端与 web 服务器通迅的格式。
HTTP 请求有 8 种:
OPTIONS / HEAD / GET / POST / PUT / DELETE / TRACE / CONNECT 。
代理服务器英文全称是 Proxy Server,其功能就是代理网络用户去取得网络信息。形象的说:它是网络信息的中转站。
代理服务器可以实现各种时髦且有用的功能。它们可以改善安全性,提高性能,节省费用。
代理可以假扮 Web 服务器。这些被称为替换物(surrogate)或反向代理(reverse proxy)的代理接收 发送给 Web 服务器的真实请求,但与 Web 服务器不同的是,它们可以发起与其他服务器的通信,以 便按需定位所请求的内容。
可以用这些反向代理来提高访问慢速 Web 服务器上公共内容的性能。在这种配置中,通常将这些 反向代理称为服务器加速器(server accelerator)。还可以将替换物与内容路由功能配合使用,以创建按 需复制内容的分布式网络。
1)https 协议需要到 ca 申请证书,一般免费证书很少,需要交费。
2)http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
3)http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
4)http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、
身份认证的网络协议,比 http 协议安全。
进程
我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。
进程一般由程序、数据集合和进程控制块三部分组成。
程序用于描述进程要完成的功能,是控制进程执行的指令集;
数据集合是程序在执行时所需要的数据和工作区;
程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进程一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序、数据和进程控制块三部分组成。
线程
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
进程与线程的区别
前面讲了进程与线程,但可能你还觉得迷糊,感觉他们很类似。的确,进程与线程有着千丝万缕的关系,下面就让我们一起来理一理:
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多。
协程
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。
因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。
死锁产生的原因:
1.系统资源的竞争
当系统中供多个进程共享的资源如打印机、公用队列的等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
2.进程运行推进顺序不当引起死锁
● 进程推进顺序合法
当进程P1和P2并发执行时,如果按照下述顺序推进:P1:Request(R1); P1:Request(R2); P1: Relese(R1);P1: Relese(R2); P2:Request(R2); P2:Request(R1); P2: Relese(R2);P2: Relese(R1);这两个进程便可顺利完成,这种不会引起进程死锁的推进顺序是合法的。
● 进程推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁。例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁。
产生死锁的四个必要条件:
● 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
● 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
● 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
● 循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
一般我们所说的内存泄漏指的是堆内存的泄漏。
堆内存是程序从堆中为其分配的,大小任意的,使用完后要显示释放内存。
当应用程序用关键字new等创建对象时,就从堆中为它分配一块内存,使用完后程序调用free或者delete释放该内存,
否则就说该内存就不能被使用,我们就说该内存被泄漏了。
引用计数:是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术, 当一个对象的引 用被创建或者复制时,对象的引用计数加 1;当一个对象的引用被销毁时,对象的引用计数减 1;当对 象的引用计数减少为 0 时,就意味着对象已经没有被任何人使用了,可以将其所占用的内存释放了。虽 然引用计数必须在每次分配和释放内存的时候加入管理引用计数的动作,然而与其他主流的垃圾收集技 术相比,引用计数有一个最大的有点,即“实时性”,任何内存,一旦没有指向它的引用,就会立即被 回收。而其他的垃圾收集计数必须在某种特殊条件下(比如内存分配失败)才能进行无效内存的回收。
**引用计数机制执行效率问题:**引用计数机制所带来的维护引用计数的额外操作与 Python 运行中所 进行的内存分配和释放,引用赋值的次数是成正比的。而这点相比其他主流的垃圾回收机制,比如“标 记-清除”,“停止-复制”,是一个弱点,因为这些技术所带来的额外操作基本上只是与待回收的内存数量有关。
如果说执行效率还仅仅是引用计数机制的一个软肋的话,那么很不幸,引用计数机制还存在着一个致命的弱点,正是由于这个弱点,使得侠义的垃圾收集从来没有将引用计数包含在内,能引发出这个致
命的弱点就是循环引用(也称交叉引用)。
循环引用可以使一组对象的引用计数不为 0,然而这些对象实际上并没有被任何外部对象所引用, 它们之间只是相互引用。这意味着不会再有人使用这组对象,应该回收这组对象所占用的内存空间,然 后由于相互引用的存在,每一个对象的引用计数都不为 0,因此这些对象所占用的内存永远不会被释放。 比如:这一点是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。
要解决这个问题,Python 引入了其他的垃圾收集机制来弥补引用计数的缺陷:“标记-清除”,“分代回收”两种收集技术。
**标记-清除:**标记-清除”是为了解决循环引用的问题。可以包含其他对象引用的容器对象(比如:list,set,dict,class,instance)都可能产生循环引用。我们必须承认一个事实,如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那 么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计 数为 0。我们必须先将循环引用摘掉,那么这两个对象的有效计数就现身了。假设两个对象为 A、B, 我们从 A 出发,因为它有一个对 B 的引用,则将 B 的引用计数减 1;然后顺着引用达到 B,因为 B 有一 个对 A 的引用,同样将 A 的引用减 1,这样,就完成了循环引用对象间环摘除。
但是这样就有一个问题,假设对象 A 有一个对象引用 C,而 C 没有引用 A,如果将 C 计数引用减 1, 而最后 A 并没有被回收,显然,我们错误的将 C 的引用计数减 1,这将导致在未来的某个时刻出现一个 对 C 的悬空引用。这就要求我们必须在 A 没有被删除的情况下复原 C 的引用计数,如果采用这样的方 案,那么维护引用计数的复杂度将成倍增加。
原理:“标记-清除”采用了更好的做法,我们并不改动真实的引用计数,而是将集合中对象的引用 计数复制一份副本,改动该对象引用的副本。对于副本做任何的改动,都不会影响到对象生命走起的维护。
这个计数副本的唯一作用是寻找 root object 集合(该集合中的对象是不能被回收的)。当成功寻 找到 root object 集合之后,首先将现在的内存链表一分为二,一条链表中维护 root object 集合,成 为 root 链表,而另外一条链表中维护剩下的对象,成为 unreachable 链表。之所以要剖成两个链表, 是基于这样的一种考虑:现在的 unreachable 可能存在被 root 链表中的对象,直接或间接引用的对象, 这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从 unreachable 链表中移到 root 链表中;当完成标记后,unreachable 链表中剩下的所有对象就是名副其实的垃圾对象了,接下 来的垃圾回收只需限制在 unreachable 链表中即可。
分代回收 背景:分代的垃圾收集技术是在上个世纪 80 年代初发展起来的一种垃圾收集机制,一系 列的研究表明:无论使用何种语言开发,无论开发的是何种类型,何种规模的程序,都存在这样一点相 同之处。即:一定比例的内存块的生存周期都比较短,通常是几百万条机器指令的时间,而剩下的内存 块,起生存周期比较长,甚至会从程序开始一直持续到程序结束。
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统 中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾 回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额 外操作。为了提高垃圾收集的效率,采用“空间换时间的策略”。
原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”, 垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾, 就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量, 如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
一、写 pythonic 代码
二、理解 Python 和 C 语言的不同之处
三、在代码中适当添加注释
Python 中有三种形式的代码注释:块注释、行注释以及文档注释。 使用块注释或者行注释的时候仅仅注释那些复杂的操作、算法,或者难以理解,不能一目了然的代码给外部可访问的函数和方法(无论简单与否)添加文档注释。注释要清楚的描述方法的功能,并对参数、返回值以及可能发生的异常进行说明,使得外部调用它的人员仅仅看文档注释就能正确使用。较为复杂的内部方法也需要进行注释。
四、通过适当添加空行使代码布局更为优雅、合理。
五、编写函数的 4 个原则
1)函数设计要尽量短小 2)函数声明要做到合理、简单、易于使用 3)函数参数设计应该考虑向下兼容 4)一个函数只做一件事情,尽量保证函数语句粒度的一致性
六、将常量集中到一个文件
在 Python 中如何使用常量呢,一般来说有一下两种方式: 1)通过命名风格来提醒使用者该变量代表的意义为常量。如 TOTAL,MAX_OVERFLOW,然而这
种方式并没有实现真正的常量,其对应的值仍然可以改变,这只是一种约定俗成的风格。 2)通过自定义的类实现常量功能,这要求符合“命名全部为大写”和“值一旦绑定便不可再修改”
这两个条件。
type()
isinstance()
Python 中的 random 函数,可以生成随机浮点数、整数、字符串,甚至帮助你随机选择列表序列 中的一个元素,打乱一组数据等。
1)抽象类:规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所 以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗、墙面的样式由你自己来定,所以抽象类 与作为基类的普通类的区别在于约束性更强。
2)接口类:与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本 区别在于用途:与不同个体间沟通的规则(方法),你要进宿舍需要有钥匙,这个钥匙就是你与宿 舍的接口,你的同室也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人 交流的接口。
3)区别和关联:
委托:假装这件事是我在做,但是事实上我委托了其他人来帮我处理这件事。(Python 中的委托与此相似。)
1)面向对象 2)免费 3)开源 4)可移植 5)功能强大 6)可混合 7)简单易用