From d8ccdec8567dd5715f3f35a125af0462d85a7592 Mon Sep 17 00:00:00 2001
From: Frank
+## Stargazers over time
+
+
+
## License
From 99808403fe1144858d5e450c2972ea56fde05583 Mon Sep 17 00:00:00 2001
From: Frank
-## Stargazers over time
-
-
## License
From e0b35bc1bbf6ba590199ed9a2e37bea2d7f35778 Mon Sep 17 00:00:00 2001
From: Frank
+## Stargazers over time
+
+
+
## License
From 12eb6179e15fdedcae5f07ec3186cb5a0abe830b Mon Sep 17 00:00:00 2001
From: qiurenbo

@@ -1928,4 +1928,4 @@ static int indexFor(int h, int length) {
# 更新日志
- 2018/8/3 v2.5 基础版
-- 2018/9/1 v3.0 初稿版
\ No newline at end of file
+- 2018/9/1 v3.0 初稿版
From 4e2bb3ef245aac7a6157b358cd9dfa9be8ccde2f Mon Sep 17 00:00:00 2001
From: Dodii <44020773+dodisefti23@users.noreply.github.com>
Date: Tue, 29 Oct 2019 22:40:48 -0700
Subject: [PATCH 13/98] Create queue1
---
queue1 | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
create mode 100644 queue1
diff --git a/queue1 b/queue1
new file mode 100644
index 00000000..bd400c63
--- /dev/null
+++ b/queue1
@@ -0,0 +1,94 @@
+package queue;
+
+import java.lang.reflect.Array;
+import java.util.Scanner;
+public class Queue {
+ public Integer pointer;
+ private final Integer sizeArray;
+ private final T[] element;
+
+ public Queue(Class namaClass, Integer size) {
+ pointer = -1;
+ sizeArray = size;
+ element = (T[]) Array.newInstance(namaClass, size);
+ }
+
+ public void enqueue(T data)
+ {
+ if(pointer < (sizeArray - 1))
+ {
+ pointer++;
+ element[pointer] = data;
+ }
+ else
+ {
+ System.out.println("Queue Sudah Penuh");
+ }
+ }
+
+ public void dequeue()
+ {
+ if(pointer >= 0) {
+ for (Integer i = 0; i <= pointer; i++) {
+ element[i] = element[i + 1];
+ }
+ pointer--;
+ }
+ else
+ {
+ System.out.println("Queue Sudah Kosong");
+ }
+ }
+ public void viewQueue()
+ {
+ for(Integer i = 0; i <= pointer; i++)
+ {
+ System.out.print(element[i]);
+ System.out.print(" ");
+ }
+ }
+
+ public void clearQueue()
+ {
+ pointer = -1;
+ }
+
+ public static void main(String[] args)
+ {
+ Integer pil;
+ Queue queue = new Queue<>(String.class, 10);
+ Scanner scanner = new Scanner(System.in);
+
+ do
+ {
+ queue.viewQueue();
+ System.out.println();
+ System.out.println("1. Enqueue");
+ System.out.println("2. Dequeue");
+ System.out.println("3. Clear");
+ System.out.println("4. Exit");
+ pil = scanner.nextInt();
+ if(pil > 0 && pil < 4)
+ {
+ switch (pil) {
+ case 1:
+ String data;
+ System.out.print("Masukkan Data: ");
+ data = scanner.next();
+ queue.enqueue(data);
+ break;
+ case 2:
+ queue.dequeue();
+ break;
+ case 3:
+ queue.clearQueue();
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ break;
+ }while(true);
+ }
+}
From 423b80ba9170d092a2c49dd482c6df09c9387fad Mon Sep 17 00:00:00 2001
From: Frank
- 和 400+ 技术达人在线交流: + 和 500+ 技术达人在线交流: QQ技术交流群
@@ -55,6 +55,13 @@
+🎯 本仓库即将重构并出版,欢迎留言讨论
+
+[《全栈开发技术地图》出版进行中 · Issue #49](https://github.com/frank-lam/fullstack-tutorial/issues/49)
+
+
+
+
## 前言
- [谈谈技术学习的一些方法论](https://www.frankfeekr.cn/2019/05/09/谈谈技术学习的一些方法论/)
From b3b349e7b388f9986dad89735faa7e85a2231b4e Mon Sep 17 00:00:00 2001
From: Frank Lin
欢迎志同道合的小伙伴加入开源小组:⊱ 开源小组,英雄招募令
@@ -55,13 +67,6 @@
-🎯 本仓库即将重构并出版,欢迎留言讨论
-
-[《全栈开发技术地图》出版进行中 · Issue #49](https://github.com/frank-lam/fullstack-tutorial/issues/49)
-
-
-
-
## 前言
- [谈谈技术学习的一些方法论](https://www.frankfeekr.cn/2019/05/09/谈谈技术学习的一些方法论/)
From ebbc05abdd68283946ad7ea5cedfb6c5b42cadaa Mon Sep 17 00:00:00 2001
From: Frank Lin
- 本仓库即将重构并出版,欢迎留言交流
+ 本仓库即将重构并出版,欢迎留言交流
《全栈开发技术地图》出版进行中 · Issue #49
From b0593c3b7f7ed27febf0a01ff20b0047b03d9135 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=A6=99=E9=A5=BD=E9=A5=BDzizizi?= <863461783@qq.com>
Date: Tue, 19 Nov 2019 17:36:30 +0800
Subject: [PATCH 18/98] Update Linux.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
more 命令处修复两处文案错误(enter,more);英文处前后添加空格
---
notes/Linux.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/notes/Linux.md b/notes/Linux.md
index 745a95f5..5d9fadd8 100644
--- a/notes/Linux.md
+++ b/notes/Linux.md
@@ -742,12 +742,12 @@ cp [-adfilprsu] source destination
和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。
-- 按Space键:显示文本的下一屏内容。
-- 按Enier键:只显示文本的下一行内容。
-- 按斜线符`/`:接着输入一个模式,可以在文本中寻找下一个相匹配的模式。
-- 按H键:显示帮助屏,该屏上有相关的帮助信息。
-- 按B键:显示上一屏内容。
-- 按Q键:退出rnore命令。
+- 按 Space 键:显示文本的下一屏内容。
+- 按 Enter 键:只显示文本的下一行内容。
+- 按斜线符 `/` :接着输入一个模式,可以在文本中寻找下一个相匹配的模式。
+- 按 H 键:显示帮助屏,该屏上有相关的帮助信息。
+- 按 B 键:显示上一屏内容。
+- 按 Q 键:退出 more 命令。
### 4. less
@@ -2197,4 +2197,4 @@ https://www.cnblogs.com/CEO-H/p/7794306.html
- 2018/8/23 v2.0 基础版1
-- 2018/8/26 - 27 v2.5 基础版2
\ No newline at end of file
+- 2018/8/26 - 27 v2.5 基础版2
From 8241476514ca6eba6b4fe4270531094d441205c0 Mon Sep 17 00:00:00 2001
From: Mark
+
+例子:
+```python
+class ClassDemo(object):
+ def run(self):
+ raise NotImplementedError
+
+ def wrong(self):
+ # Will raise a TypeError
+ NotImplemented = "don't do this"
+ return NotImplemented
+
+
+class ChildClass(ClassDemo):
+ def run(self):
+ print("Hello world")
+
+ def wrong(self):
+ print("wrong")
+
+
+ChildClass().run() # Hello world
+
+wrong = ClassDemo().wrong()
+print(wrong) # don't do this
+
+```
+
+
+这里区分下 NotImplemented && NotImplementedError
+
+```
+ type(NotImplemented)
+
+运行demo.py方法在当前目录会产生一个`__pycache__`目录
+
+进入这个目录我们看到里面有个文件mymodule.cpython-36.pyc
+
+
+这里就有两个问题了
+
+`__pycache__`目录是什么?
+
+pyc文件又是什么?
+
+
+#### `__pycache__`
+
+`__pycache__`是包含编译并准备执行Python 3字节码的目录
+
+当第一次运行 python 脚本时,解释器会将 *.py 脚本进行编译并保存到 `__pycache__` 目录
+
+**这里有个问题?**
+
+我就写个一个文件,里面就打印一句话,会不会产生pycache目录呢?
+
+```
+print("hello world")
+```
+
+实验表明:
+> 并不会产生这个pycache目录,这个目录只有当**`import xxx`**才会生成
+
+
+**`这个目录能不能删掉呢?删掉有什么影响呢?`**
+
+下次执行脚本时,若解释器发现你的 *.py 脚本没有变更,便会跳过编译一步,直接运行保存在 `__pycache__ `目录下的 *.pyc 文件
+
+
+**执行python脚本会导致字节代码在内存中生成并保持到程序关闭。**
+
+**如果导入模块,为了更快的可重用性,Python将创建一个缓存.pyc(PYC是'Python''Compiled')文件,其中导入的模块的字节代码被缓存。**
+
+想法是通过避免在重新导入时重新编译(编译一次,运行多次策略)来加速python模块的加载。
+
+文件名与模块名称相同。
+初始点后面的部分表示创建缓存的Python实现(可能是CPython),后跟其版本号。
+
+如前面说的mymodule.cpython-36.pyc文件
+
+#### pyc文件
+
+python2 代码 运行是直接在本地产生pyc文件
+python3 代码运行是把pyc文件放在pycache目录
+
+
+.pyc文件是由.py文件经过编译后生成的字节码文件,其加载速度相对于之前的.py文件有所提高,而且还可以实现源码隐藏,以及一定程度上的反编译
+
+
+pyc文件,是python编译后的字节码(bytecode)文件。
+只要你运行了py文件,python编译器就会自动生成一个对应的pyc字节码文件。
+这个pyc字节码文件,经过python解释器,会生成机器码运行
+
+下次调用直接调用pyc,而不调用py文件。直到你这个py文件有改变。
+
+python解释器会检查pyc文件中的生成时间,对比py文件的修改时间,如果py更新,那么就生成新的pyc。
+
+
+#### pyo文件
+
+pyo文件也是优化(注意这两个字,便于后续的理解)编译后的程序(相比于.pyc文件更小),也可以提高加载速度。
+
+但对于嵌入式系统,它可将所需模块编译成.pyo文件以减少容量。 但总的来说,作用上是几乎与原来的.py脚本没有区别的,
+
+
+在所有的Python选项中:
+
+>-O,表示优化生成.pyo字节码
+
+>-OO,表示进一步移除-O选项生成的字节码文件中的文档字符串
+
+>-m,表示导入并运行指定的模块
+
+
+执行下面的命令查看是否生成pyo文件
+
+```python
+#py_compile是Python的自带模块
+python -O -m py_compile demo.py
+python -OO -m py_compile demo.py
+```
+
+到pycache目录看到生成了cpython-36.opt-1.pyc文件
+
+
+
+我们发现下面的结果:
+
+1.`咦,说好的生成pyo文件的呢?怎么又生成了pyc文件?`
+
+2.两个pyc文件
+-O 选择生成的是xxxx-1.pyc
+-OO 选项生成的是xxxx-2.pyc
+
+`这两个pyc又有啥区别呢?`
+
+
+>查资料,发现从python3.5+开始就去除了pyo文件后缀名,
+[消除pyo文件原文链接](https://www.python.org/dev/peps/pep-0488/)
+[PYC目录链接](https://www.python.org/dev/peps/pep-3147/)
+
+为啥官方要去除pyo呢?
+
+我读完这两篇文章,简单总结下:
+
+由于pyc 文件在 Python 主要版本之间并不兼容
+所以引入了一种更灵活的替代机制 -pyc
+
+格式包含实现名称和版本号
+
+*.pyc 文件可以表示优化和未优化的字节码。
+
+优化级别信息可以包含在 *.pyc 文件的名字中,
+
+优化级别:
+```
+0: .pyc
+1 (-O): .pyo
+2 (-OO): .pyo
+```
+
+
+
+## python程序执行原理
+
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md"
new file mode 100644
index 00000000..b700a7aa
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md"
@@ -0,0 +1,206 @@
+# 第23章 python时间模块
+
+
+
+
+- [第23章 python时间模块](#%e7%ac%ac23%e7%ab%a0-python%e6%97%b6%e9%97%b4%e6%a8%a1%e5%9d%97)
+ - [time 模块](#time-%e6%a8%a1%e5%9d%97)
+ - [datetime模块](#datetime%e6%a8%a1%e5%9d%97)
+ - [字符串转换成datetime类型](#%e5%ad%97%e7%ac%a6%e4%b8%b2%e8%bd%ac%e6%8d%a2%e6%88%90datetime%e7%b1%bb%e5%9e%8b)
+ - [datetime转换成字符串类型](#datetime%e8%bd%ac%e6%8d%a2%e6%88%90%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%b1%bb%e5%9e%8b)
+ - [时间戳转string](#%e6%97%b6%e9%97%b4%e6%88%b3%e8%bd%acstring)
+
+
+
+
+## time 模块
+
+
+time模块提供各种操作时间的函数
+
+一般有两种表示时间的方式:
+
+第一种: 是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的
+
+第二种: 以数组的形式表示即(struct_time),共有九个元素,分别表示,同一个时间戳的struct_time会因为时区不同而不同
+
+
+例子:
+```python
+import time
+
+print(time.time())
+# 1578030257.4879787
+
+print(time.localtime())
+# time.struct_time(tm_year=2020, tm_mon=1, tm_mday=3, tm_hour=13, tm_min=44, tm_sec=17, tm_wday=4, tm_yday=3, tm_isdst=0)
+
+```
+
+
+
+## datetime模块
+
+Python提供了多个内置模块用于操作日期时间,像calendar,time,datetime。time模块。
+相比于time模块,datetime模块的接口则更直观、更容易调用。
+
+datetime模块定义了下面这几个类:
+
+datetime.date:表示日期的类。常用的属性有year, month, day, today;
+
+datetime.time:表示时间的类。常用的属性有hour, minute, second, microsecond;
+
+datetime.datetime:表示日期时间。
+
+datetime.timedelta:表示时间间隔,即两个时间点之间的长度。
+
+datetime.tzinfo:与时区有关的相关信息。
+
+datetime中,表示日期时间的是一个datetime对象
+
+datetime中提供了strftime方法,可以将一个datetime型日期转换成字符串
+
+
+举一个例子:
+```python
+import datetime
+
+
+def datetostr(date):
+ return str(date)[0:10]
+
+def getDaysByNum(num):
+ today = datetime.date.today()
+ oneday = datetime.timedelta(days=1)
+ li = []
+ for i in range(0, num):
+ today = today - oneday
+ li.append(datetostr(today))
+ return li
+
+
+print(getDaysByNum(3))
+```
+
+
+
+## 字符串转换成datetime类型
+
+```python
+import datetime
+s = "2016-01-12"
+print(datetime.datetime.strptime(s, '%Y-%m-%d')
+```
+
+
+## datetime转换成字符串类型
+```python
+import datetime
+s = "2016-01-12"
+print(datetime.datetime.strptime(s, '%Y-%m-%d').strftime('%Y-%m-%d'))
+
+```
+
+## 时间戳转string
+```python
+import time
+
+time1 = 1353254400
+
+time_str = time.strftime('%Y-%m-%d', time.localtime(time1))
+print(time_str)
+```
+
+##几个例子
+
+获取小时:
+```python
+import datetime as dt
+hours = [dt.time(i).strftime('%H:%M') for i in range(24)]
+
+print(hours)
+```
+
+
+时间相减:
+```python
+import time
+import datetime
+
+
+def String2Time(s):
+ return time.strptime(s, "%Y-%m-%d %H:%M:%S")
+
+
+def dateMinDate(d1, d2):
+ d1 = String2Time(d1)
+ d2 = String2Time(d2)
+ # 这里的*号表示将time.struct_time类型转换成datetime.datetime
+ delta = datetime.datetime(*d1[:6]) - datetime.datetime(*d2[:6])
+ print(delta)
+
+
+if __name__ == "__main__":
+ dateMinDate("2012-06-26 15:20:00", "2012-06-26 15:10:00")
+
+```
+
+
+时间间隔:
+```python
+import datetime
+date1 = '2011-05-03'
+date2 = '2011-05-10'
+datelist = []
+
+start = datetime.datetime.strptime(date1, '%Y-%m-%d')
+end = datetime.datetime.strptime(date2, '%Y-%m-%d')
+step = datetime.timedelta(days=1)
+
+while start <= end:
+ days = start.strftime("%Y-%m-%d") # 此处要加入这一句
+ datelist.append(days)
+ start += step
+
+print(datelist)
+```
+
+
+计算多久之前:
+```python
+import datetime
+import sys
+if sys.version_info[0] >= 3:
+ unicode = str
+
+
+def timebefore(d):
+ chunks = (
+ (60 * 60 * 24 * 365, u'年'),
+ (60 * 60 * 24 * 30, u'月'),
+ (60 * 60 * 24 * 7, u'周'),
+ (60 * 60 * 24, u'天'),
+ (60 * 60, u'小时'),
+ (60, u'分钟'),
+ )
+ # 如果不是datetime类型转换后与datetime比较
+ if not isinstance(d, datetime.datetime):
+ d = datetime.datetime(d.year, d.month, d.day)
+ now = datetime.datetime.now()
+ delta = now - d
+ # 忽略毫秒
+ before = delta.days * 24 * 60 * 60 + delta.seconds # python2.7直接调用 delta.total_seconds()
+ # 刚刚过去的1分钟
+ if before <= 60:
+ return u'刚刚'
+ for seconds, unit in chunks:
+ count = before // seconds
+ if count != 0:
+ break
+ return unicode(count) + unit + u"前"
+
+
+d = datetime.datetime.fromtimestamp(1571500800)
+print(timebefore(d))
+
+```
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md"
new file mode 100644
index 00000000..caaf5ea6
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md"
@@ -0,0 +1,190 @@
+# 第15章 上下文管理器和else块
+
+
+
+- [第15章 上下文管理器和else块](#%e7%ac%ac15%e7%ab%a0-%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8%e5%92%8celse%e5%9d%97)
+ - [try..except..finally](#tryexceptfinally)
+ - [with语句](#with%e8%af%ad%e5%8f%a5)
+ - [上下文管理器](#%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8)
+ - [@contextmanager 装饰器](#contextmanager-%e8%a3%85%e9%a5%b0%e5%99%a8)
+
+
+
+## try..except..finally
+
+看一个例子:
+```python
+def get_result():
+ a = 3
+ b = 0
+ try:
+ b = 3 / 0
+ except Exception as e:
+ print("ERROR=", e)
+ finally:
+ print("a=", a)
+
+
+if __name__ == "__main__":
+ get_result()
+
+```
+
+返回结果是:
+```python
+ERROR= division by zero
+a= 3
+```
+
+
+总结:
+>try..except..else没有捕获到异常,执行else语句
+try..except..finally不管是否捕获到异常,都执行finally语句
+
+
+
+## with语句
+
+
+python文本文件读写的3种方法
+
+```python
+# 第一种方法:
+file1 = open("test.txt")
+file2 = open("output.txt", "w")
+while True:
+ line = file1.readline()
+ # 这里可以进行逻辑处理
+ file2.write(line)
+ if not line:
+ break
+
+# 记住文件处理完,关闭是个好习惯
+file1.close()
+file2.close()
+
+# 读文件有3种方法:
+# - read()将文本文件所有行读到一个字符串中。
+# - readline()是一行一行的读
+# - readlines()是将文本文件中所有行读到一个list中,文本文件每一行是list的一个元素。
+
+# 优点:readline()可以在读行过程中跳过特定行。
+
+# 第二种方法:文件迭代器,用for循环的方法
+file2 = open("output.txt", "w")
+for line in open("test.txt"):
+ # 这里可以进行逻辑处理
+ file2.write(line)
+
+# 第三种方法: 推荐使用这个方法文件上下文管理器
+with open('somefile.txt', 'r') as f:
+ data = f.read()
+
+with open('somefile.txt', 'r') as f:
+ for line in f:
+ print(line)
+
+with open('somefile.txt', 'w') as f:
+ f.write("hello")
+```
+
+
+
+**这里重点说说第三种方法**
+
+打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open
+写法,我们需要try,except,finally,做异常判断,并且文件最终不管遇到什么情况,都要执行finally f.close()关闭文件,with方法帮我们实现了finally中f.close
+
+
+## 上下文管理器
+
+上下文管理器协议包含`__enter__ 和 __exit__` 两个方法。
+
+with 语句开始运行时,会在上下文管理器对象上调用 `__enter__ `方法。
+with 语句运行结束后,会在上下文管理器对象上调用` __exit__ `方法,以此扮演 finally 子句的角色。
+
+最常见的例子是确保关闭文件对象
+```python
+class T(object):
+ def __enter__(self):
+ print('T.__enter__')
+ return '我是__enter__的返回值'
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ print('T.__exit__')
+
+
+with T() as t:
+ print(t)
+```
+
+返回结果:
+```
+T.__enter__
+我是__enter__的返回值
+T.__exit__
+```
+
+
+with之于上下文管理器,就像for之于迭代器一样。with就是为了方便上下文管理器的使用。
+
+
+## @contextmanager 装饰器
+
+
+下文管理器就不得不提一下@contextmanager 装饰器,它能减少创建上下文管理器的样板代码量,
+
+因为不用编写一个完整的类,定义` __enter__和 __exit__ `方法,而只需实现有一个 yield 语句的生成器,生成想让` __enter__ `方法返回的值。
+
+
+@contextmanager 装饰器优雅且实用,把三个不同的 Python 特性结合到了一起:**函数装饰器、生成器和 with 语句。**
+
+在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把函数的定义体分成两部分:
+
+ 1. yield 语句前面的所有代码在 with 块开始时(即解释器调用` __enter__` 方法时)执行
+ 2. yield 语句后面的代码在with 块结束时(即调用 `__exit__` 方法时)执行。
+
+
+例子:
+```python
+import sys
+import contextlib
+
+
+@contextlib.contextmanager
+def WoHa(n):
+ original_write = sys.stdout.write
+
+ def reverse_write(text):
+ original_write(text[::-1])
+
+ sys.stdout.write = reverse_write
+ yield n
+ sys.stdout.write = original_write
+ return True
+
+
+obj1 = WoHa('你手机拿反了')
+with obj1 as content:
+ print('哈哈镜花缘')
+ print(content)
+
+print('#### with 执行完毕后,在输出content: ####')
+print(content)
+
+```
+
+
+返回结果:
+```
+缘花镜哈哈
+了反拿机手你
+#### with 执行完毕后,在输出content: ####
+你手机拿反了
+```
+
+
+这里我们需要注意的是:
+
+代码执行到yield时,会产出一个值,这个值会绑定到 with 语句中 as 子句的变量上。
+执行 with 块中的代码时,这个函数会在yield这里暂停。
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md"
new file mode 100644
index 00000000..c8a186b8
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md"
@@ -0,0 +1,617 @@
+# 第18章 使用asyncio包处理并发
+
+
+
+- [第18章 使用asyncio包处理并发](#第18章-使用asyncio包处理并发)
+ - [主线程与子线程](#主线程与子线程)
+ - [守护线程](#守护线程)
+ - [线程同步](#线程同步)
+ - [asyncio介绍](#asyncio介绍)
+ - [关键组件说明](#关键组件说明)
+ - [回调](#回调)
+ - [asyncio与gevent关系](#asyncio与gevent关系)
+ - [asyncio与Flask](#asyncio与flask)
+ - [aiohttp](#aiohttp)
+ - [与单进程、多进程对比](#与单进程多进程对比)
+
+
+
+
+## 主线程与子线程
+
+
+当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元。
+
+当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是`setDaemon(False)`),主线程执行完自己的任务以后,就退出了。
+
+此时子线程会继续执行自己的任务,直到自己的任务结束。
+
+
+例子:
+```python
+import time
+import threading
+
+
+def run():
+ time.sleep(2)
+ print('当前线程名称是:%s' % threading.currentThread().name)
+ time.sleep(2)
+
+
+if __name__ == "__main__":
+
+ start_time = time.time()
+ print('这是主线程:%s' % threading.current_thread().name)
+
+ thread_list = []
+
+ for i in range(5):
+ t = threading.Thread(target=run)
+ thread_list.append(t)
+
+ for t in thread_list:
+ t.start()
+
+ print('主线程结束:%s' % threading.current_thread().name)
+
+print('一共用时:%f' % float(time.time() - start_time))
+```
+
+运行结果:
+```
+这是主线程:MainThread
+主线程结束:MainThread
+一共用时:0.002953
+当前线程名称是:Thread-2
+当前线程名称是:Thread-1
+当前线程名称是:Thread-5
+当前线程名称是:Thread-3
+当前线程名称是:Thread-4
+```
+
+
+##守护线程
+当我们使用`setDaemon(True)`方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行
+
+可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。
+
+例子:
+```python
+import time
+import threading
+
+
+def run():
+ time.sleep(2)
+ print('当前线程名称是:%s' % threading.currentThread().name)
+ time.sleep(2)
+
+
+if __name__ == "__main__":
+
+ start_time = time.time()
+ print('这是主线程:%s' % threading.current_thread().name)
+
+ thread_list = []
+
+ for i in range(5):
+ t = threading.Thread(target=run)
+ thread_list.append(t)
+
+ for t in thread_list:
+ t.setDaemon(True)
+ t.start()
+
+ print('主线程结束:%s' % threading.current_thread().name)
+
+print('一共用时:%f' % float(time.time() - start_time))
+```
+
+运行结果:
+```
+这是主线程:MainThread
+主线程结束:MainThread
+一共用时:0.002954
+```
+
+
+## 线程同步
+
+join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止
+
+例子:
+```python
+import time
+import threading
+
+
+def run():
+ time.sleep(2)
+ print('当前线程名称是:%s' % threading.currentThread().name)
+ time.sleep(2)
+
+
+if __name__ == "__main__":
+
+ start_time = time.time()
+ print('这是主线程:%s' % threading.current_thread().name)
+
+ thread_list = []
+
+ for i in range(5):
+ t = threading.Thread(target=run)
+ thread_list.append(t)
+
+ for t in thread_list:
+ t.start()
+ t.join()
+
+ print('主线程结束:%s' % threading.current_thread().name)
+
+print('一共用时:%f' % float(time.time() - start_time))
+```
+
+运行结果:
+```
+这是主线程:MainThread
+当前线程名称是:Thread-1
+当前线程名称是:Thread-2
+当前线程名称是:Thread-3
+当前线程名称是:Thread-4
+当前线程名称是:Thread-5
+主线程结束:MainThread
+一共用时:20.015406
+```
+
+
+
+
+##asyncio介绍
+
+asyncio的编程模型就是一个`消息循环`, 异步非阻塞的协程
+
+
+### 关键组件说明
+
+asyncio使用了与以往python用法完全不同的构造:`事件循环、协程和futures`
+
+
+关于asyncio的一些关键字的说明:
+
+>* event_loop 事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数
+
+>* coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
+
+>* task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态
+
+>* future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别
+
+>* async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
+
+
+例子:
+
+
+
+```python
+import asyncio
+import datetime
+
+
+# 写法1
+# @asyncio.coroutine
+# def hello():
+
+# print('hello world')
+# r = yield from asyncio.sleep(1)
+
+# print('hello again')
+
+# 写法2
+async def hello():
+ print('hello world')
+ await asyncio.sleep(1)
+ print('hello again')
+
+
+if __name__ == "__main__":
+
+ loop = asyncio.get_event_loop()
+ task = loop.create_task(hello())
+
+ print(datetime.datetime.now())
+
+ # print(task)
+ loop.run_until_complete(task)
+
+ # print(task)
+ print(datetime.datetime.now())
+ loop.close()
+```
+
+
+
+协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。
+
+所谓task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。
+
+在通过loop.create_task(hello())的时候,任务其实是处于pending状态。
+
+在hello中通过asyncio.sleep(1)耗时一秒最后任务执行完后状态变为done.
+
+asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。
+
+当传入一个协程,其内部会自动封装成task,task是Future的子类
+
+
+
+再来看一个例子:
+```python
+import asyncio
+import random
+
+
+async def MyCoroutine(id):
+ process_time = random.randint(1, 5)
+ # 使用asyncio.sleep模拟一些耗时的操作
+ await asyncio.sleep(process_time)
+ print("协程: {}, 执行完毕。用时: {} 秒".format(id, process_time))
+
+
+async def main():
+ # ensure_future方法 接收协程或者future作为参数,作用是排定他们的执行时间
+ tasks = [asyncio.ensure_future(MyCoroutine(i)) for i in range(10)]
+
+ # 返回结果
+ await asyncio.gather(*tasks)
+
+
+# 事件循环
+loop = asyncio.get_event_loop()
+try:
+ loop.run_until_complete(main())
+finally:
+ loop.close()
+```
+
+运行结果:
+```
+协程: 7, 执行完毕。用时: 1 秒
+协程: 9, 执行完毕。用时: 1 秒
+协程: 5, 执行完毕。用时: 2 秒
+协程: 0, 执行完毕。用时: 4 秒
+协程: 6, 执行完毕。用时: 4 秒
+协程: 2, 执行完毕。用时: 4 秒
+协程: 3, 执行完毕。用时: 4 秒
+协程: 8, 执行完毕。用时: 4 秒
+协程: 4, 执行完毕。用时: 4 秒
+协程: 1, 执行完毕。用时: 4 秒
+```
+
+
+从输出结果可以看出两点:
+1.协程并没有按照顺序返回结果;
+2.批量运行的任务所用的时间和所有任务中用时最长的相同。
+
+
+
+
+### 回调
+
+```python
+import asyncio
+import requests
+
+async def request():
+ url = 'https://www.baidu.com'
+ status = requests.get(url)
+ return status
+
+def callback(task):
+ print('Status:', task.result())
+
+coroutine = request()
+task = asyncio.ensure_future(coroutine)
+task.add_done_callback(callback)
+print('Task:', task)
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(task)
+print('Task:', task)
+```
+
+也可以不用回调:
+```python
+import asyncio
+import requests
+
+
+async def request():
+ url = 'https://www.baidu.com'
+ status = requests.get(url)
+ return status
+
+
+coroutine = request()
+task = asyncio.ensure_future(coroutine)
+print('Task:', task)
+
+loop = asyncio.get_event_loop()
+loop.run_until_complete(task)
+print('Task:', task)
+print('Task Result:', task.result())
+
+```
+
+## asyncio与gevent关系
+
+
+gevent是第三方库,通过greenlet实现协程
+
+其基本思路是:
+当一个greenlet遇到IO操作时,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
+
+
+asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持,不需要第三方的支持,
+
+asyncio的编程模型就是一个消息循环。
+
+我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
+
+很多异步io操作这两个库都可以用,只是他们在不同场景下的效率和易用性可能有区别,当然这个得进行深入的测试和研究,单就现在普通的场景来说 区别并不大
+
+
+
+## asyncio与Flask
+
+首先使用flask编写一个web服务器
+```python
+from flask import Flask
+import time
+
+app = Flask(__name__)
+
+@app.route('/')
+def index():
+ time.sleep(3)
+ return 'Hello!'
+
+if __name__ == '__main__':
+ app.run(threaded=True)
+```
+
+这里run() 方法加了一个参数 threaded,这表明 Flask 启动了多线程模式,不然默认是只有一个线程的。
+
+如果不开启多线程模式,同一时刻遇到多个请求的时候,只能顺次处理,这样即使我们使用协程异步请求了这个服务,也只能一个一个排队等待,瓶颈就会出现在服务端。
+
+所以,多线程模式是有必要打开的。
+
+
+程序运行后会开启一个web, 端口默认5000
+
+使用asyncio模块进行测试
+```python
+import asyncio
+import requests
+import time
+
+start = time.time()
+
+async def request():
+ url = 'http://127.0.0.1:5000'
+ print('Waiting for', url)
+ response = requests.get(url)
+ print('Get response from', url, 'Result:', response.text)
+
+tasks = [asyncio.ensure_future(request()) for _ in range(5)]
+loop = asyncio.get_event_loop()
+loop.run_until_complete(asyncio.wait(tasks))
+
+end = time.time()
+print('Cost time:', end - start)
+```
+
+
+运行结果:
+```
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Cost time: 15.0814049243927
+```
+
+可以发现和正常的请求并没有什么两样,依然还是顺次执行的,耗时 15 秒,平均一个请求耗时 3 秒,说好的异步处理呢?
+
+其实,要实现异步处理,我们得先要有挂起的操作,当一个任务需要等待 IO 结果的时候,可以挂起当前任务,转而去执行其他任务,这样我们才能充分利用好资源,上面方法都是一本正经的串行走下来,连个挂起都没有,怎么可能实现异步?
+
+要实现异步,接下来我们再了解一下 await 的用法,使用 await 可以将耗时等待的操作挂起,让出控制权。
+
+当协程执行的时候遇到 await,时间循环就会将本协程挂起,转而去执行别的协程,直到其他的协程挂起或执行完毕。
+
+改进后代码:
+```python
+import asyncio
+import requests
+import time
+
+start = time.time()
+
+
+# 这里增加异步get
+# 否则会报错:TypeError: object Response can't be used in 'await' expression
+async def get(url):
+ return requests.get(url)
+
+
+async def request():
+ url = 'http://127.0.0.1:5000'
+ print('Waiting for', url)
+ response = await get(url) # 修改这里增加await
+ print('Get response from', url, 'Result:', response.text)
+
+
+tasks = [asyncio.ensure_future(request()) for _ in range(5)]
+loop = asyncio.get_event_loop()
+loop.run_until_complete(asyncio.wait(tasks))
+
+end = time.time()
+print('Cost time:', end - start)
+```
+
+运行结果:
+```
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Waiting for http://127.0.0.1:5000
+Get response from http://127.0.0.1:5000 Result: Hello!
+Cost time: 15.083374977111816
+```
+
+还是不行,它还不是异步执行,也就是说我们仅仅将涉及 IO 操作的代码封装到 async 修饰的方法里面是不可行的!
+
+我们必须要使用支持**异步操作的请求方式**才可以实现真正的异步,所以这里就需要 aiohttp 派上用场了.
+
+
+### aiohttp
+
+首先执行下面命令进行安装这个模块:
+
+```python
+pip install aiohttp
+```
+
+
+例子:
+```python
+import asyncio
+import aiohttp
+import time
+
+start = time.time()
+
+async def get(url):
+ session = aiohttp.ClientSession()
+ response = await session.get(url)
+ result = await response.text()
+ session.close()
+ return result
+
+async def request():
+ url = 'http://127.0.0.1:5000'
+ print('Waiting for', url)
+ result = await get(url)
+ print('Get response from', url, 'Result:', result)
+
+tasks = [asyncio.ensure_future(request()) for _ in range(5)]
+loop = asyncio.get_event_loop()
+loop.run_until_complete(asyncio.wait(tasks))
+
+end = time.time()
+print('Cost time:', end - start)
+```
+
+运行结果:
+```
+xx
+Cost time: 3.056924819946289
+```
+
+
+我们发现这次请求的耗时由 15 秒变成了 3 秒,耗时直接变成了原来的 1/5
+
+这就是异步操作的便捷之处,当遇到阻塞式操作时,任务被挂起,程序接着去执行其他的任务,而不是傻傻地等着,这样可以充分利用 CPU 时间,而不必把时间浪费在等待 IO 上
+
+
+这里将 task 数量设置成 100 ,发现结果也是3秒
+```
+Cost time: 3.4409260749816895
+```
+
+### 与单进程、多进程对比
+
+
+单进程代码:
+```python
+import requests
+import time
+
+start = time.time()
+
+
+def request():
+ url = 'http://127.0.0.1:5000'
+ print('Waiting for', url)
+ result = requests.get(url).text
+ print('Get response from', url, 'Result:', result)
+
+
+for _ in range(100):
+ request()
+
+end = time.time()
+print('Cost time:', end - start)
+```
+
+运行结果:
+```
+Cost time: 301.17162680625916
+```
+
+
+
+
+多进程版本:
+
+
+```python
+import requests
+import time
+import multiprocessing
+
+start = time.time()
+
+
+def request(_):
+ url = 'http://127.0.0.1:5000'
+ print('Waiting for', url)
+ result = requests.get(url).text
+ print('Get response from', url, 'Result:', result)
+
+
+if __name__ == "__main__": # 这一行在windows下执行一定要添加,否则会报错
+
+ cpu_count = multiprocessing.cpu_count()
+ print('Cpu count:', cpu_count)
+ pool = multiprocessing.Pool(cpu_count)
+
+ pool.map(request, range(100))
+
+ end = time.time()
+ print('Cost time:', end - start)
+```
+
+运行结果:
+```
+Cost time: 48.85933017730713
+```
+
+
+这里想查看cpu个数也可以使用:
+```python
+ import psutil
+ psutil.cpu_count()
+```
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md"
new file mode 100644
index 00000000..b62a9718
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md"
@@ -0,0 +1,170 @@
+# 第17章 使用future处理并发
+
+
+
+- [第17章 使用future处理并发](#%e7%ac%ac17%e7%ab%a0-%e4%bd%bf%e7%94%a8future%e5%a4%84%e7%90%86%e5%b9%b6%e5%8f%91)
+ - [futures模块](#futures%e6%a8%a1%e5%9d%97)
+ - [多线程模式ThreadPoolExecutor](#%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%bc%8fthreadpoolexecutor)
+ - [多进程模式ProcessPoolExecutor](#%e5%a4%9a%e8%bf%9b%e7%a8%8b%e6%a8%a1%e5%bc%8fprocesspoolexecutor)
+ - [深入原理](#%e6%b7%b1%e5%85%a5%e5%8e%9f%e7%90%86)
+
+
+## futures模块
+
+Python3引入的concurrent.futures模块。concurrent.futures 是python3新增加的一个库,用于并发处理,提供了多线程和多进程的并发功能 类似于其他语言里的线程池(也有一个进程池),他属于上层的封装,对于用户来说,不用在考虑那么多东西了
+
+
+concurrent提供了两种并发模型,一个是多线程ThreadPoolExecutor,一个是多进程ProcessPoolExecutor。对于IO密集型任务宜使用多线程模型。对于计算密集型任务应该使用多进程模型。
+
+
+### 多线程模式ThreadPoolExecutor
+多线程模式适合IO密集型运算,这里我要使用sleep来模拟一下慢速的IO任务
+这里使用Google fire开源库来简化命令行参数处理
+
+
+```python
+import time
+import fire
+import threading
+from concurrent.futures import ThreadPoolExecutor, wait
+
+
+# 分割子任务
+def each_task(index):
+ time.sleep(1) # 睡1s,模拟IO
+ print("thread %d square %d" % (threading.current_thread().ident, index))
+ return index * index # 返回结果
+
+
+def run(thread_num, task_num):
+ # 实例化线程池,thread_num个线程
+ executor = ThreadPoolExecutor(thread_num)
+ start = time.time()
+
+ fs = [] # future列表
+ for i in range(task_num):
+ fs.append(executor.submit(each_task, i)) # 提交任务
+
+ wait(fs) # 等待计算结束
+ end = time.time()
+ duration = end - start
+
+ s = sum([f.result() for f in fs]) # 求和
+ print("total result=%d cost: %s" % (s, duration))
+ executor.shutdown() # 销毁线程池
+
+
+if __name__ == '__main__':
+ fire.Fire(run)
+```
+
+执行返回结果:
+```
+python 1.py 2 10 # 也就是2个线程跑10个任务
+thread 5216 square 0
+thread 5856 square 1
+thread 5216 square 2
+thread 5856 square 3
+thread 5216 square 4
+thread 5856 square 5
+thread 5216 square 6
+thread 5856 square 7
+thread 5856 square 9
+thread 5216 square 8
+total result=285 cost: 5.06045389175415
+```
+
+为什么输出乱了,这是因为print操作不是原子的,它是两个连续的write操作合成的,第一个write输出内容,第二个write输出换行符,write操作本身是原子的,但是在多线程环境下,这两个write操作会交错执行,所以输出就不整齐了。如果将代码稍作修改,将print改成单个write操作,输出就整齐了 ---`发现改了还是不行`
+
+
+
+调大参数看看效果:
+```
+python 1.py 10 10
+thread 4792 square 9
+thread 10380 square 6
+thread 14420 square 7
+thread 5720 square 3
+thread 13808 square 4
+thread 17344 square 1
+thread 9172 square 5
+thread 10684 square 8
+thread 11696 square 0
+thread 6300 square 2
+total result=285 cost: 1.05
+```
+
+可以看到1s中就完成了所有的任务。这就是多线程的魅力,可以将多个IO操作并行化,减少整体处理时间。
+
+
+
+### 多进程模式ProcessPoolExecutor
+
+相比多线程适合处理IO密集型任务,多进程适合计算密集型。
+
+```python
+import os
+import sys
+import math
+import time
+import fire
+from concurrent.futures import ProcessPoolExecutor, wait
+
+
+# 分割子任务
+def each_task(n):
+ # 按公式计算圆周率
+ s = 0.0
+ for i in range(n):
+ s += 1.0 / (i + 1) / (i + 1)
+ pi = math.sqrt(6 * s)
+ # os.getpid可以获得子进程号
+ sys.stdout.write("process %s n=%d pi=%s\n" % (os.getpid(), n, pi))
+ return pi
+
+
+def run(process_num, *ns): # 输入多个n值,分成多个子任务来计算结果
+ # 实例化进程池,process_num个进程
+ executor = ProcessPoolExecutor(process_num)
+ start = time.time()
+ fs = [] # future列表
+ for n in ns:
+ fs.append(executor.submit(each_task, int(n))) # 提交任务
+ wait(fs) # 等待计算结束
+ end = time.time()
+ duration = end - start
+ print("total cost: %.2f" % duration)
+ executor.shutdown() # 销毁进程池
+
+
+if __name__ == '__main__':
+ fire.Fire(run)
+```
+
+执行返回结果:
+```
+python 1.py 1 5000000 5001000 5002000 5003000
+process 10024 n=5000000 pi=3.141592462603821
+process 10024 n=5001000 pi=3.141592462641988
+process 10024 n=5002000 pi=3.1415924626801544
+process 10024 n=5003000 pi=3.141592462718321
+total cost: 4.62
+```
+
+增大一个进程看看效果:
+```
+python 1.py 2 5000000 5001000 5002000 5003000
+process 5860 n=5000000 pi=3.141592462603821
+process 14948 n=5001000 pi=3.141592462641988
+process 14948 n=5003000 pi=3.141592462718321
+process 5860 n=5002000 pi=3.1415924626801544
+total cost: 2.78
+```
+
+从耗时上看缩短了接近1半,说明多进程确实起到了计算并行化的效果
+
+
+### 深入原理
+
+
+[Python最广为使用的并发库futures使用入门与内部原理](https://mp.weixin.qq.com/s/NBBDou4rIMo9KibVb4fjeg)
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md"
new file mode 100644
index 00000000..1424db87
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md"
@@ -0,0 +1,223 @@
+# 第6章 使用一等函数实现设计模式
+
+
+
+- [第6章 使用一等函数实现设计模式](#第6章-使用一等函数实现设计模式)
+ - [单例模式](#单例模式)
+ - [为什么](#为什么)
+ - [是什么](#是什么)
+ - [怎么用](#怎么用)
+
+
+设计模式
+每个设计模式都是围绕如下三个问题:
+
+>1.为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的问题?
+
+>2.是什么?通过Python语言来去实现这个设计模式,用于解决为什么中提到的问题。
+
+>3.怎么用?理解了为什么我们也就基本了解了什么情况下使用这个模式,不过在这里还是会细化使用场景,阐述模式的局限和优缺点
+
+
+## 单例模式
+这一篇我们先来看看单例模式。单例模式是设计模式中逻辑最简单,最容易理解的一个模式,简单到只需要一句话就可以理解,即**`保证只有一个对象实例的模式`**。
+
+问题的关键在于实现起来并没有想象的那么简单。
+
+不过我们还是先来讨论下为什么需要这个模式吧。
+
+### 为什么
+
+我们首先来看看单例模式的使用场景,然后再来分析为什么需要单例模式。
+* **Python的logger就是一个单例模式,用以日志记录**
+* **Windows的资源管理器是一个单例模式**
+* **线程池,数据库连接池等资源池一般也用单例模式**
+* **网站计数器**
+
+
+
+从这些使用场景我们可以总结下什么情况下需要单例模式:
+1.当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,**`它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;`**
+
+2.当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。
+
+
+当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。
+
+
+接下来看看如何使用Python来实现一个单例模式。
+
+### 是什么
+
+最开始的想法很简单,实现如下:
+
+```python
+class Singleton(object):
+ __instance = None
+
+ def __init__(self):
+ pass
+
+ def __new__(cls, *args, **kwd):
+ if Singleton.__instance is None:
+ Singleton.__instance = object.__new__(cls, *args, **kwd)
+ return Singleton.__instance
+
+
+s1 = Singleton()
+s2 = Singleton()
+print(s1)
+print(s2)
+
+if s1 == s2:
+ print("True")
+else:
+ print("False")
+
+```
+
+运行结果是:
+```
+<__main__.Singleton object at 0x000002176FE884A8>
+<__main__.Singleton object at 0x000002176FE884A8>
+True
+
+```
+
+### 怎么用
+
+在 Python 中,我们可以用多种方法来实现单例模式:
+
+> 使用模块
+使用 `__new__`
+使用装饰器(decorator)
+使用元类(metaclass)
+
+
+1.使用模块
+其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。
+
+因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
+如果我们真的想要一个单例类,可以考虑这样做:
+
+```python
+# mysingleton.py
+class My_Singleton(object):
+ def foo(self):
+ pass
+
+
+my_singleton = My_Singleton()
+
+# 将上面的代码保存在文件 mysingleton.py 中,然后这样使用:
+
+from mysingleton import my_singleton
+my_singleton.foo()
+
+```
+
+
+2.使用`__new__`方法
+为了使类只能出现一个实例,我们可以使用 __new__ 来控制实例的创建过程,代码如下:
+
+```python
+class Singleton(object):
+ _instance = None
+
+ def __new__(cls, *args, **kw):
+ if not cls._instance:
+ cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)
+ return cls._instance
+
+
+class MyClass(Singleton):
+ a = 1
+
+
+one = MyClass()
+two = MyClass()
+
+print(one == two)
+print(one is two)
+print(id(one), id(two))
+```
+
+运行结果:
+```
+True
+True
+2483856443152 2483856443152
+```
+
+3.使用装饰器
+我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下:
+
+```python
+from functools import wraps
+
+
+def singleton(cls):
+ instances = {}
+
+ @wraps(cls)
+ def getinstance(*args, **kw):
+ if cls not in instances:
+ instances[cls] = cls(*args, **kw)
+ return instances[cls]
+
+ return getinstance
+
+
+@singleton
+class MyClass(object):
+ a = 1
+
+
+one = MyClass()
+two = MyClass()
+
+print(one == two)
+
+```
+
+在上面,我们定义了一个装饰器 singleton,它返回了一个内部函数 getinstance,该函数会判断某个类是否在字典 instances 中,
+
+如果不存在,则会将 cls 作为 key,cls(*args, **kw) 作为 value 存到 instances 中,否则,直接返回 instances[cls]。
+
+
+4.使用元类
+
+元类(metaclass)可以控制类的创建过程,它主要做三件事:
+
+拦截类的创建
+修改类的定义
+返回修改后的类
+使用元类实现单例模式的代码如下:
+
+```python
+class Singleton(type):
+ _instances = {}
+
+ def __call__(cls, *args, **kwargs):
+ if cls not in cls._instances:
+ cls._instances[cls] = super(Singleton,
+ cls).__call__(*args, **kwargs)
+ return cls._instances[cls]
+
+
+# # Python2
+# class MyClass(object):
+# __metaclass__ = Singleton
+
+
+#Python3
+class MyClass(metaclass=Singleton):
+ pass
+
+
+one = MyClass()
+two = MyClass()
+
+print(one == two)
+
+```
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md"
new file mode 100644
index 00000000..0d966f7a
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md"
@@ -0,0 +1,313 @@
+# 第5章 函数
+
+
+
+- [第5章 函数](#第5章-函数)
+ - [函数](#函数)
+ - [高阶函数](#高阶函数)
+ - [map函数](#map函数)
+ - [filter函数](#filter函数)
+ - [reduce函数](#reduce函数)
+ - [lambda 函数](#lambda-函数)
+ - [sorted函数](#sorted函数)
+ - [`__call__`方法](#__call__方法)
+ - [函数注解](#函数注解)
+ - [冻结参数的函数](#冻结参数的函数)
+
+
+
+
+## 函数
+
+在python中一切都可以视作为对象,包括函数
+
+```python
+def function_try():
+ '''it is funciton try doc'''
+
+ print('function_try doc')
+
+
+if __name__ == "__main__":
+
+ # __doc__属性用于生成对象的帮助文本
+ print(function_try.__doc__)
+ # __name__则是输出了具体的函数名
+ print(function_try.__name__)
+```
+
+我们还可以将函数名赋值给变量,通过变量的形式来访问。
+
+```python
+def function_try():
+ '''it is funciton try doc'''
+
+ print('function_try doc')
+
+
+if __name__ == "__main__":
+
+ fun = function_try
+
+ # __doc__属性用于生成对象的帮助文本
+ print(fun.__doc__)
+ # __name__则是输出了具体的函数名
+ print(fun.__name__)
+```
+
+函数也可以通过参数被传递给另外一个函数。
+
+```python
+def function_try():
+ '''it is funciton try doc'''
+
+ print('function_try doc')
+
+
+def function_try1(func):
+ print(func.__doc__)
+ print(func.__name__)
+ func()
+
+
+if __name__ == "__main__":
+
+ fun = function_try
+ function_try1(fun)
+
+```
+
+## 高阶函数
+
+>**接受函数为参数,或者把函数作为结果返回的函数是高阶函数**
+
+### map函数
+```python
+def cube(x):
+ return x * x
+
+
+data = map(cube, range(1, 11))
+print(list(data)) # 此处要加list
+
+
+def add_num(n):
+ return n + 1
+
+
+print(list(map(add_num, range(1, 6))))
+```
+
+再看几个例子:
+```python
+result = ['1', '2', '3']
+
+print(list(map(int, result))) # [1, 2, 3]
+
+# 也可以用列表推导式
+print([int(i) for i in result])
+
+```
+
+```python
+t = ((1, 'a'), (2, 'b'))
+print(type(t))
+print(dict(t))
+print(dict((y, x) for x, y in t))
+
+# map方法
+# print(dict(map(None, t))) # 这个python3已废弃
+print(dict(map(reversed, t)))
+
+```
+
+
+### filter函数
+Filter的作用在于根据第一个参数为true还是false来进行过滤。
+下面例子中将大于3的数值过滤出来。
+```python
+s = [1, 2, 3, 4, 5]
+
+print(list(filter(lambda x: x > 3, s)))
+# [4, 5]
+```
+
+### reduce函数
+就是将参数累加的应用于函数中。就好比我们要对1到100进行求值。
+代码如下。效果等同于sum(range(100))
+```python
+# python3 已经将reduce移到functools模块中
+from functools import reduce
+from operator import add
+
+print(reduce(add, range(100)))
+
+```
+
+### lambda 函数
+这是Python支持一种有趣的语法,`它允许你快速定义单行的最小函数`
+例子:
+```python
+g = lambda x: x * 2
+print(g(3))
+
+# 相等于下面的表达式
+print((lambda x: x * 2)(3))
+
+D = {'jack': 23, 'rose': 21, 'flank': 22}
+
+value_sort = sorted(D.items(), key=lambda d: d[1]) # 值(value)排序
+print(value_sort)
+# [('rose', 21), ('flank', 22), ('jack', 23)]
+
+key_sort = sorted(D.items(), key=lambda d: d[0]) # 按键(key)排序按
+print(key_sort)
+# [('flank', 22), ('jack', 23), ('rose', 21)]
+
+key_reverse = sorted(D.items(), key=lambda d: d[0], reverse=True) # 按键(key)降序
+print(key_reverse)
+# [('rose', 21), ('jack', 23), ('flank', 22)]
+
+# 值(value)降序
+value_reverse = sorted(D.items(), key=lambda d: d[1], reverse=True)
+print(value_reverse)
+# [('jack', 23), ('flank', 22), ('rose', 21)]
+
+```
+python lambda是在python中使用lambda来创建匿名函数,而用def创建的方法是有名称的,除了从表面上的方法名不一样外,python lambda还有哪些和def不一样呢?
+
+>1 **python lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量**。
+
+>2 **python lambda它只是一个表达式,而def则是一个语句**。
+
+下面是python lambda的格式,看起来好精简阿。
+```python
+lambda x: print x
+```
+
+lambda表达式在“:”后只能有一个表达式。
+也就是说,在 def中,用return可以返回的也可以放在lambda后面,不能用return返回的也不能定义在python lambda后面。
+
+因此,像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。
+
+下面举几个python lambda的例子吧
+1. 单个参数的:
+```python
+g = lambda x:x*2
+print(g(3))
+# 结果是6
+```
+2.多个参数的:
+```python
+m = lambda x,y,z: (x-y)*z
+print(m(3,1,2))
+
+#结果是4
+```
+
+
+### sorted函数
+
+**`sorted是另外返回一个排序后的列表,原列表fruits并没有发生改变`**
+
+```python
+fruits = ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana']
+
+ret = sorted(fruits, key=len)
+
+print('before sorted %s' % fruits)
+# before sorted ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana']
+
+print('after sorted %s' % ret)
+# after sorted ['fig', 'apple', 'cherry', 'banana', 'rasberry', 'strawberry']
+
+```
+
+与它很相像的`sort函数`,看下面的例子:
+```python
+a = [5, 4, 3, 2, 1]
+
+sorted(a) # 将a从大到小排序,不影响a本身结构
+print("a=", a) # a= [5, 4, 3, 2, 1]
+
+a.sort() # 将a从小到大排序,影响a本身结构
+print(a) # [1, 2, 3, 4, 5]
+
+b = a.sort()
+print("b=", b) # b= None
+
+c = ['aa', 'bb', 'BB', 'CC', 'zz']
+print(sorted(c))
+# 按列表中元素每个字母的ascii码从小到大排序
+# 如果要从大到小,请用sorted(b,reverse=True)
+# ['BB', 'CC', 'aa', 'bb', 'zz']
+```
+
+## `__call__`方法
+
+前面介绍到可以把函数当做对象。那么我们可以像调用函数那样调用类么。
+答案是肯定的。
+
+只需要我们重写类中的`__call__`就可以了
+
+```python
+class function_try(object):
+ def __init__(self, value):
+ self.data = value
+
+ def __call__(self, *args, **kwargs):
+ print('function try was called')
+
+ for a in args:
+ print(a)
+
+
+if __name__ == "__main__":
+
+ f = function_try(3)
+ f('hello', 'world')
+```
+
+
+除了__doc__, __name__这些函数还有很多属性。可以用**dir(func)**的方法进行查看
+
+## 函数注解
+
+Python 3提供了一种句法,用于为函数声明中的参数和返回值附加元数据
+Python不做检查、不做强制、不做验证,什么操作都不做
+
+```python
+# def test(text, max_len=5):
+# 下面是python3 新写法
+def test(text: str, max_len: 'int>0' = 5) -> str:
+ if len(text) > max_len:
+ return "OK"
+
+
+text = "hello world"
+
+# print(len(text))
+a = test(text)
+print(a, type(a))
+
+print(test.__annotations__)
+#{'text':
+总结:
+
+>三种方法都可以通过对象进行调用,但类方法和静态方法无法访问对象属性,类方法通过对象调用获取的仍是类属性(并非对象属性);
+
+>普通方法无法通过类名调用,类方法和静态方法可以,但静态方法不能进行访问,仅仅只是通过传值得方式(与函数调用相同)
+
+
+
+
+#### `@property`
+
+访问类或实例的属性时是直接通过obj.XXX的形式访问,但property是一个特性的属性
+
+访问添加了装饰器@property的方法时无须额外添加()来调用,而是以**属性的形式来访问**。
+
+所以,利用这个特性可以虚拟实现类属性的只读设置。
+
+```python
+class Student(object):
+ # 上面的birth是可读写属性,而age就是一个只读属性
+ # 因为age可以根据birth和当前时间计算出来。
+ @property # 相当于property.getter(birth) 或者property(birth)
+ def birth(self):
+ return self._birth
+
+ @birth.setter # 相当于birth = property.setter(birth)
+ def birth(self, value):
+ self._birth = value
+
+ @property
+ def age(self):
+ return 2019 - self._birth
+
+
+student = Student()
+
+student.birth = 1987
+print(student.birth)
+print(student.age)
+
+```
+
+这里有个问题,**`为什么property属性只能类的实例调用?直接使用类调用可以不可以?`**
+
+
+```python
+class Test(object):
+ @property
+ def p(self):
+ return "Hello World"
+
+
+# 类调用
+print(Test.p) #
+注意两点:
+
+>nonlocal不能代替global。 如果在上述代码的函数a中,就只能使用global。
+ 因为,外层就是Global作用域了。
+
+> **在多重嵌套中,nonlocal只会上溯一层; 而如果上一层没有,则会继续上溯**。
+
+举个例子来说明:
+
+```python
+def a():
+ i = 1
+
+ def b():
+ # i = 2
+ def c():
+ nonlocal i
+ i = 3
+
+ c()
+ print('b:', i)
+
+ b()
+ print('a:', i)
+
+
+a()
+```
+
+返回结果:
+```
+b: 3
+a: 3
+```
+
+
+把上面的注释打开,对比这个:
+```python
+def a():
+ i = 1
+
+ def b():
+ i = 2
+
+ def c():
+ nonlocal i
+ i = 3
+
+ c()
+ print('b:', i)
+
+ b()
+ print('a:', i)
+
+
+a()
+```
+返回结果:
+```
+b: 3
+a: 1
+
+```
+
+
+
+### Global作用域
+
+看一个例子:
+```python
+i = 0
+
+
+def a():
+ i = 1
+ print('local:', i)
+
+
+a()
+print('global:', i)
+```
+
+返回结果:
+```
+local: 1
+global: 0
+```
+
+
+**如果在一个局部(函数)内声明一个global变量,**
+
+**那么这个变量在局部的所有操作将对全局的变量有效**
+
+```python
+i = 0
+
+
+def a():
+ global i
+ i = 1
+ print('local:', i)
+
+
+a()
+print('global:', i)
+```
+
+返回结果:
+```
+local: 1
+global: 1
+
+```
+
+
+```python
+i = 0
+
+
+def a():
+ i = 1
+ print('local:', i)
+ # 可以查看局部作用域
+ print(locals())
+ # 查看全局作用域
+ print(globals())
+
+
+a()
+print('global:', i)
+print("*" * 10)
+print(locals())
+print(globals())
+```
+
+
+
+**记住两点:**
+
+**`global永远打印全局的名字`**
+
+**`locals 输出什么 根据locals所在位置`**
+
+
+### build-in作用域
+
+Python内置模块的名字空间
+
+```python
+def list_length(a):
+ return len(a)
+
+
+a = [1, 2, 3]
+print(list_length(a)) # 3
+
+# python3 写法
+import builtins
+print(builtins.len) #
+
+### 不可变数据类型:数值型、字符串型string和元组tuple
+
+不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址)
+
+```python
+a = 3
+b = 3
+
+print("ID a=", id(a)) # ID a= 1570991200
+print("ID b=", id(b)) # ID b= 1570991200
+
+print(a == b) # 判断值是否相等 True
+
+print(a is b) # 判断地址是否一样 True
+
+```
+
+
+再看一个例子:
+
+```python
+# 列表是可变的
+a = [1, 2, 3, 4]
+a[0] = 100
+a.insert(3, 300)
+
+print(a)
+
+# 字符串是不可变的
+s = "Hello world"
+# s[0] = "z"
+# print(s) # TypeError: 'str' object does not support item assignment
+
+# 字符串replace方法只是返回一个新字符串,并不改变原来的值
+print(s.replace('world', 'Mars'))
+print(s)
+
+# 如果想改变字符串的值,可以用重新赋值的方法
+s = s.replace('world', 'Mars')
+print(s)
+
+# 或者使用bytearray代替字符串
+c = bytearray('abcde', 'utf-8')
+c[1:3] = b'12'
+print(c) # bytearray(b'a12de')
+
+```
+
+
+
+* 总结:
+
+可变数据类型:
+>**list, dictionary, set**, numpy array, user defined objects
+
+
+不可变数据类型
+> **integer, float,** long, complex, **string, tuple**, frozenset
+
+
+
+### 字符串不可变的原因
+
+1.`列表可以通过以下的方法改变,而字符串不支持这样的变化`
+
+```python
+a = [1, 2, 3, 4]
+
+# 此时a 和 b 指向同一块区域,改变 b 的值,a 也会同时改变
+b = a
+
+print("ID a=", id(a))
+print("ID b=", id(b))
+
+b[0] = 100
+print(a) # [100, 2, 3, 4]
+print(b) # [100, 2, 3, 4]
+
+```
+
+2.`字符串与整数浮点数一样被认为是基本类型,而基本类型在Python中是不可变的。
+
+
+
+
+## 闭包
+
+### 什么是闭包
+**一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。**
+
+这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是**自由变量**。
+
+也就是说闭包是嵌套函数,内部函数调用外部函数的变量
+
+例子:
+```python
+def outer():
+ a = 1
+
+ def inner():
+ print(a)
+ print(inner.__closure__)
+
+ return inner
+
+
+inn = outer()
+inn()
+```
+
+
+返回结果:
+```
+1
+(
+这里的返回值 两个cell的意思是:
+
+**`内部函数引用了外部函数的变量, int和func类型`**
+
+
+这里inner函数就是一个闭包,并且拥有该闭包持有的自由变量a
+
+总结创建一个闭包必须满足以下几点:
+
+1.必须有一个内嵌函数
+
+2.内嵌函数必须引用外部函数中的变量
+
+3.外部函数的返回值必须是内嵌函数
+
+
+### 为什么要使用闭包
+
+两个特点:
+* **封装**
+* **代码复用**
+
+
+```python
+def set_passline(passline):
+ def cmp(val):
+ if val >= passline:
+ print("pass")
+ else:
+ print("failed")
+
+ return cmp
+
+
+f_100 = set_passline(60)
+f_100(89)
+f_100(59)
+```
+
+### 怎么使用闭包
+
+
+* 在python中很重要也很常见的一个使用场景就是装饰器
+`装饰器是特殊的闭包形式`
+
+```python
+def decorator_func(func):
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+@decorator_func
+def func(name):
+ print('my name is', name)
+
+
+# 等价于
+# decorator_func(func)
+
+func("tony")
+```
+
+
+
+* 惰性求值
+惰性求值属性可以在对象被使用的时候才进行计算,这样可以减少一些资源消耗,提高程序效率
+
+```python
+def out_func(a):
+ def inner_func():
+ return a
+
+ return inner_func
+
+
+func = out_func(1)
+print(func()) # 1
+```
+
+
+
+* 需要对某个函数的参数提前赋值的情况
+
+当然在Python中已经有了很好的解决访问 functools.parial,但是用闭包也能实现
+
+```python
+def partial(**outer_kwargs):
+ def wrapper(func):
+ def inner(*args, **kwargs):
+ for k, v in outer_kwargs.items():
+ kwargs[k] = v
+ return func(*args, **kwargs)
+
+ return inner
+
+ return wrapper
+
+
+@partial(age=15)
+def say(name=None, age=None):
+ print(name, age)
+
+
+say(name="tony")
+
+# 当然用functools比这个简单多了
+# import functools
+# functools.partial(say, age=15)(name='tony')
+```
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md"
new file mode 100644
index 00000000..4560002a
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md"
@@ -0,0 +1,13 @@
+# 第19章 动态属性和特性
+
+
+这里主要讲:
+
+`_getattr__方法`
+
+`__new__方法`
+
+`@property`
+
+
+可以查看以前章节内容
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md"
new file mode 100644
index 00000000..991094f1
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md"
@@ -0,0 +1,609 @@
+# 第16章 协程-补充内容
+
+
+
+- [第16章 协程-补充内容](#第16章-协程-补充内容)
+ - [多进程VS多线程](#多进程vs多线程)
+ - [多进程](#多进程)
+ - [multiprocessing 模块](#multiprocessing-模块)
+ - [多进程通讯共享数据](#多进程通讯共享数据)
+ - [队列](#队列)
+ - [管道](#管道)
+ - [Managers](#managers)
+ - [进程池](#进程池)
+ - [多线程 GIL](#多线程-gil)
+ - [GIL是什么](#gil是什么)
+ - [GIL例子](#gil例子)
+ - [GIL释放](#gil释放)
+ - [为什么这样设计](#为什么这样设计)
+ - [能不能去掉GIL](#能不能去掉gil)
+ - [threading模块](#threading模块)
+ - [multiprocessing.dummy 模块](#multiprocessingdummy-模块)
+ - [两者关系](#两者关系)
+ - [多进程分析详解](#多进程分析详解)
+
+
+## 多进程VS多线程
+
+
+### 多进程
+
+**`多进程就是利用 CPU 的多核优势`**,在同一时间并行地执行多个任务,可以大大提高执行效率。
+
+
+#### multiprocessing 模块
+
+例子:
+```python
+from multiprocessing import Process
+
+
+def fun1(name):
+ print('测试%s多进程' % name)
+
+
+if __name__ == '__main__':
+
+ process_list = []
+ for i in range(5): # 开启5个子进程执行fun1函数
+ p = Process(target=fun1, args=('Python', )) # 实例化进程对象
+ p.start()
+ process_list.append(p)
+
+ for i in process_list:
+ p.join()
+
+ print('结束测试')
+```
+
+返回结果:
+```
+测试Python多进程
+测试Python多进程
+测试Python多进程
+测试Python多进程
+测试Python多进程
+结束测试
+```
+
+
+
+类继承方式:
+```python
+from multiprocessing import Process
+
+
+class MyProcess(Process): # 继承Process类
+ def __init__(self, name):
+ super(MyProcess, self).__init__()
+ self.name = name
+
+ def run(self):
+ print('测试%s多进程' % self.name)
+
+
+if __name__ == '__main__':
+ process_list = []
+ for i in range(5): # 开启5个子进程执行fun1函数
+ p = MyProcess('Python') # 实例化进程对象
+ p.start()
+ process_list.append(p)
+
+ for i in process_list:
+ p.join()
+
+ print('结束测试')
+```
+
+#### 多进程通讯共享数据
+
+多进程之间可以通过管道,队列,Managers来实现通讯和共享数据
+
+
+##### 队列
+
+```python
+from multiprocessing import Process, Queue
+
+
+def fun1(q, i):
+ print('子进程%s 开始put数据' % i)
+ q.put('我是%s 通过Queue通信' % i)
+
+
+if __name__ == '__main__':
+ q = Queue()
+
+ process_list = []
+ for i in range(3):
+ # 注意args里面要把q对象传给我们要执行的方法,这样子进程才能和主进程用Queue来通信
+ p = Process(target=fun1, args=(
+ q,
+ i,
+ ))
+ p.start()
+ process_list.append(p)
+
+ for i in process_list:
+ p.join()
+
+ print('主进程获取Queue数据')
+ print(q.get())
+ print(q.get())
+ print(q.get())
+ print('结束测试')
+```
+
+运行结果:
+```
+子进程0 开始put数据
+子进程1 开始put数据
+子进程2 开始put数据
+主进程获取Queue数据
+我是0 通过Queue通信
+我是1 通过Queue通信
+我是2 通过Queue通信
+结束测试
+```
+
+
+
+
+##### 管道
+```python
+from multiprocessing import Process, Pipe
+
+
+def fun1(conn):
+ print('子进程发送消息:')
+ conn.send('你好主进程')
+ print('子进程接受消息:')
+ print(conn.recv())
+ conn.close()
+
+
+if __name__ == '__main__':
+ conn1, conn2 = Pipe() # 关键点,pipe实例化生成一个双向管
+ p = Process(target=fun1, args=(conn2, )) # conn2传给子进程
+ p.start()
+ print('主进程接受消息:')
+ print(conn1.recv())
+ print('主进程发送消息:')
+ conn1.send("你好子进程")
+ p.join()
+ print('结束测试')
+```
+
+返回结果:
+```
+主进程接受消息:
+子进程发送消息:
+子进程接受消息:
+你好主进程
+主进程发送消息:
+你好子进程
+结束测试
+```
+
+
+##### Managers
+Queue和Pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。那么要用到Managers
+
+```python
+from multiprocessing import Process, Manager
+
+
+def fun1(dic, lis, index):
+
+ dic[index] = 'a'
+ dic['2'] = 'b'
+ lis.append(index) # [0,1,2,3,4,0,1,2,3,4,5,6,7,8,9]
+ #print(l)
+
+
+if __name__ == '__main__':
+ with Manager() as manager:
+ dic = manager.dict() # 注意字典的声明方式,不能直接通过{}来定义
+ l = manager.list(range(5)) # [0,1,2,3,4]
+
+ process_list = []
+ for i in range(10):
+ p = Process(target=fun1, args=(dic, l, i))
+ p.start()
+ process_list.append(p)
+
+ for res in process_list:
+ res.join()
+ print(dic)
+ print(l)
+```
+
+返回结果:
+```
+{0: 'a', '2': 'b', 2: 'a', 1: 'a', 3: 'a', 5: 'a', 4: 'a', 9: 'a', 6: 'a', 8: 'a', 7: 'a'}
+[0, 1, 2, 3, 4, 0, 2, 1, 3, 5, 4, 9, 6, 8, 7]
+```
+
+可以看到主进程定义了一个字典和一个列表,在子进程中,可以添加和修改字典的内容,在列表中插入新的数据,实现进程间的数据共享,即可以共同修改同一份数据
+
+
+#### 进程池
+
+进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。就是固定有几个进程可以使用。
+
+进程池中有两个方法:
+apply:同步,一般不使用
+apply_async:异步
+
+异步:
+```python
+from multiprocessing import Process, Pool
+import os, time, random
+
+
+def fun1(name):
+ print('Run task %s (%s)...' % (name, os.getpid()))
+ start = time.time()
+ time.sleep(random.random() * 3)
+ end = time.time()
+ print('Task %s runs %0.2f seconds.' % (name, (end - start)))
+
+
+if __name__ == '__main__':
+ pool = Pool(5) # 创建一个5个进程的进程池
+
+ for i in range(10):
+ pool.apply_async(func=fun1, args=(i, ))
+
+ pool.close()
+ pool.join()
+ print('结束测试')
+```
+
+运行结果是:
+```
+
+Run task 0 (13892)...
+Run task 1 (15796)...
+Run task 2 (6648)...
+Run task 3 (9260)...
+Run task 4 (1872)...
+Task 3 runs 0.79 seconds.
+Run task 5 (9260)...
+Task 5 runs 0.10 seconds.
+Run task 6 (9260)...
+Task 2 runs 1.43 seconds.
+Run task 7 (6648)...
+Task 4 runs 2.44 seconds.
+Task 1 runs 2.44 seconds.
+Run task 8 (1872)...
+Run task 9 (15796)...
+Task 0 runs 2.58 seconds.
+Task 9 runs 0.14 seconds.
+Task 8 runs 0.88 seconds.
+Task 6 runs 2.55 seconds.
+Task 7 runs 2.07 seconds.
+结束测试
+```
+
+对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
+
+
+
+另外一个例子:
+```python
+from multiprocessing import Manager, Pool
+import os, time
+
+
+def reader(q):
+ print("reader 启动(%s),父进程为(%s)" % (os.getpid(), os.getpid()))
+ for i in range(q.qsize()):
+ print("reader 从Queue获取到消息:%s" % q.get(True))
+
+
+def writer(q):
+ print("writer 启动(%s),父进程为(%s)" % (os.getpid(), os.getpid()))
+ for i in "itcast":
+ q.put(i)
+
+
+if __name__ == "__main__":
+ print("(%s)start" % os.getpid())
+
+ q = Manager().Queue() # 使用Manager中的Queue
+ po = Pool()
+
+ po.apply_async(writer, (q, ))
+ time.sleep(1)
+ po.apply_async(reader, (q, ))
+
+ po.close()
+ po.join()
+
+ print("(%s)End" % os.getpid())
+```
+
+运行结果是:
+```
+(9516)start
+writer 启动(10852),父进程为(10852)
+reader 启动(13392),父进程为(13392)
+reader 从Queue获取到消息:i
+reader 从Queue获取到消息:t
+reader 从Queue获取到消息:c
+reader 从Queue获取到消息:a
+reader 从Queue获取到消息:s
+reader 从Queue获取到消息:t
+(9516)End
+```
+
+同步:
+```python
+from multiprocessing import Process, Pool
+import os, time, random
+
+
+def fun1(name):
+ print('Run task %s (%s)...' % (name, os.getpid()))
+ start = time.time()
+ time.sleep(random.random() * 3)
+ end = time.time()
+ print('Task %s runs %0.2f seconds.' % (name, (end - start)))
+
+
+if __name__ == '__main__':
+ pool = Pool(5) # 创建一个5个进程的进程池
+
+ for i in range(10):
+ pool.apply(func=fun1, args=(i, ))
+
+ pool.close()
+ pool.join()
+ print('结束测试')
+```
+
+运行结果:
+```
+Run task 0 (12996)...
+Task 0 runs 0.96 seconds.
+Run task 1 (5704)...
+Task 1 runs 2.68 seconds.
+Run task 2 (6808)...
+Task 2 runs 1.31 seconds.
+Run task 3 (3020)...
+Task 3 runs 0.85 seconds.
+Run task 4 (16980)...
+Task 4 runs 2.35 seconds.
+Run task 5 (12996)...
+Task 5 runs 1.25 seconds.
+Run task 6 (5704)...
+Task 6 runs 2.43 seconds.
+Run task 7 (6808)...
+Task 7 runs 1.79 seconds.
+Run task 8 (3020)...
+Task 8 runs 0.72 seconds.
+Run task 9 (16980)...
+Task 9 runs 0.77 seconds.
+结束测试
+```
+
+
+### 多线程 GIL
+
+
+#### GIL是什么
+
+GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
+
+多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大
+
+#### GIL例子
+
+
+```python
+import time
+
+
+def decrement(n):
+ while n > 0:
+ n -= 1
+
+
+start = time.time()
+decrement(100000000)
+cost = time.time() - start
+print(cost) # 4.968633651733398
+
+```
+
+使用threading多线程模块:
+```python
+import time
+import threading
+
+
+def decrement(n):
+ while n > 0:
+ n -= 1
+
+
+start = time.time()
+
+t1 = threading.Thread(target=decrement, args=[50000000])
+t2 = threading.Thread(target=decrement, args=[50000000])
+
+t1.start() # 启动线程,执行任务
+t2.start() # 同上
+
+t1.join() # 主线程阻塞,直到t1执行完成,主线程继续往后执行
+t2.join() # 同上
+
+cost = time.time() - start
+print(cost) # 4.755946159362793
+```
+
+
+按理来说,两个线程同时并行地运行在两个 CPU 之上,时间应该减半才对,现在不减反增。
+
+是什么原因导致多线程不快反慢的呢?
+
+**原因就在于 GIL ,在` Cpython 解释器`(Python语言的主流解释器)中,有一把全局解释锁(Global Interpreter Lock)**,
+
+在解释器解释执行 Python 代码时,先要得到这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想获得 CPU 执行代码指令,
+
+就必须先获得这把锁,如果锁被其它线程占用了,那么该线程就只能等待,直到占有该锁的线程释放锁才有执行代码指令的可能。
+
+因此,这也就是为什么两个线程一起执行反而更加慢的原因,因为同一时刻,只有一个线程在运行,其它线程只能等待,即使是多核CPU,
+
+也没办法让多个线程「并行」地同时执行代码,只能是交替执行,因为多线程涉及到上线文切换、锁机制处理(获取锁,释放锁等),
+
+所以,多线程执行不快反慢。
+
+
+#### GIL释放
+什么时候 GIL 被释放呢?
+
+当一个线程遇到 I/O 任务时,将释放GIL。
+
+计算密集型(CPU-bound)线程执行 100 次解释器的计步(ticks)时(计步可粗略看作 Python 虚拟机的指令),也会释放 GIL。
+
+可以通过 sys.setcheckinterval()设置计步长度,sys.getcheckinterval() 查看计步长度。相比单线程,这些多是多线程带来的额外开销
+
+#### 为什么这样设计
+CPython 解释器为什么要这样设计?
+
+多线程是为了适应现代计算机硬件高速发展充分利用多核处理器的产物,通过多线程使得 CPU 资源可以被高效利用起来,
+
+但是多线程有个问题,怎么解决共享数据的同步、一致性问题,因为,对于多个线程访问共享数据时,可能有两个线程同时修改一个数据情况,
+
+如果没有合适的机制保证数据的一致性,那么程序最终导致异常,
+
+所以,Python之父就搞了个全局的线程锁,不管你数据有没有同步问题,反正一刀切,上个全局锁,保证数据安全。
+
+这也就是多线程鸡肋的原因,因为它没有细粒度的控制数据的安全,而是用一种简单粗暴的方式来解决。
+
+
+#### 能不能去掉GIL
+那么把 GIL 去掉可行吗?
+
+去掉GIL的 Python 在单线程条件下执行效率将近慢了2倍。
+
+
+
+#### threading模块
+
+```python
+import threading
+from time import sleep
+
+
+class Mythead(threading.Thread):
+ def run(self):
+ for i in range(3):
+ sleep(1) # 挂起1s
+ # self.name是该线程的名字,默认会分配一个形如“Thread-N”的名字,其中 N 是一个十进制数
+ print(u'线程:%s,索引%s' % (self.name, i))
+
+
+if __name__ == '__main__':
+ for i in range(3):
+ t = Mythead()
+ t.start()
+```
+
+运行结果:
+```
+线程:Thread-2,索引0
+线程:Thread-3,索引0
+线程:Thread-4,索引0
+线程:Thread-2,索引1
+线程:Thread-3,索引1
+线程:Thread-4,索引1
+线程:Thread-2,索引2
+线程:Thread-3,索引2
+线程:Thread-4,索引2
+```
+
+从上面的运行结果来看,多线程程序的执行顺序是确定的。
+
+
+```python
+import threading
+import logging
+from time import sleep
+
+#配置logging,以“模式名/线程名字(10个字符)/信息”方式输出。
+logging.basicConfig(level=logging.DEBUG,
+ format='[%(levelname)s](%(threadName)-10s) %(message)s')
+
+
+def foo():
+ logging.debug('Starting...')
+ sleep(2)
+ logging.debug('Exiting...')
+
+
+threading.Thread(name='mythread', target=foo).start()
+threading.Thread(target=foo).start()
+
+
+```
+返回结果是:
+```
+[DEBUG](mythread ) Starting...
+[DEBUG](Thread-2 ) Starting...
+[DEBUG](mythread ) Exiting...
+[DEBUG](Thread-2 ) Exiting...
+```
+
+
+比较有趣的是, multiprocessing中提供了一个dummy module, 以multiprocessing API的方式提供了对threading模块的封装,
+
+这就意味着使用如下代码时:
+
+#### multiprocessing.dummy 模块
+
+
+multiprocessing.dummy 模块与 multiprocessing 模块的区别:
+dummy 模块是多线程,而 multiprocessing 是多进程, api 都是通用的。
+
+from multiprocessing.dummy import Pool, Process
+Pool和Process的底层其实都是使用threading的实现(即ThreadPool和Thread),
+
+```python
+# from multiprocessing import Pool
+from multiprocessing.dummy import Pool as ThreadPool
+import time
+from urllib.request import urlopen
+
+urls = [
+ 'http://www.baidu.com',
+ 'http://home.baidu.com/',
+ 'http://tieba.baidu.com/',
+]
+start = time.time()
+results = map(urlopen, urls)
+print('Normal:', time.time() - start)
+
+start2 = time.time() # 开8个 worker,没有参数时默认是 cpu 的核心数
+pool = ThreadPool(processes=8)
+results2 = pool.map(urlopen, urls)
+pool.close()
+pool.join()
+
+print('Thread Pool:', time.time() - start2)
+```
+
+
+### 两者关系
+
+**多进程适合在CPU密集操作**(cpu操作指令比较多,如位多的的浮点运算)。
+**多线程适合在IO密性型操作**(读写数据操作比多的的,比如爬虫)
+
+
+### 多进程分析详解
+
+
+[深入Python多进程编程基础](https://mp.weixin.qq.com/s/7cM0CmtklFQs0tlnw8mFGg)
+[深入Python多进程通信原理与实战](https://mp.weixin.qq.com/s/qKU6z1PvBENTy8QxdZxCzg)
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md"
new file mode 100644
index 00000000..730b9fed
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md"
@@ -0,0 +1,528 @@
+# 第16章 协程
+
+
+
+
+- [第16章 协程](#第16章-协程)
+ - [进程 VS 线程](#进程-vs-线程)
+ - [进程](#进程)
+ - [堆栈](#堆栈)
+ - [数据结构的堆栈](#数据结构的堆栈)
+ - [内存分配中的堆和栈](#内存分配中的堆和栈)
+ - [线程](#线程)
+ - [线程安全](#线程安全)
+ - [互斥锁](#互斥锁)
+ - [阻塞VS非阻塞](#阻塞vs非阻塞)
+ - [阻塞](#阻塞)
+ - [非阻塞](#非阻塞)
+ - [同步VS异步](#同步vs异步)
+ - [同步](#同步)
+ - [异步](#异步)
+ - [区别](#区别)
+ - [并发 VS并行](#并发-vs并行)
+ - [协程](#协程)
+ - [greenlet](#greenlet)
+ - [gevent](#gevent)
+ - [协程VS子程序](#协程vs子程序)
+
+
+
+## 进程 VS 线程
+
+
+进程和线程说明
+
+* 是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
+
+* 线程是进程的一个执行实例,是程序执行的最小单位,它是比进程更小的能独立运行的基本单位
+
+* 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行
+* 一个程序至少有一个进程,一个进程至少有一个线程
+
+可以联想
+打开迅雷.exe 就是打开进程
+多个任务 就是多个线程**
+
+### 进程
+进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
+
+
+进程:
+1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立
+2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制
+
+
+**进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。**
+
+`怎么得出来这结论的?`
+
+#### 堆栈
+
+什么是堆栈?在计算机中堆栈的概念分为:**数据结构的堆栈和内存分配中堆栈**。
+
+
+
+#### 数据结构的堆栈
+
+堆:堆可以被看成是一棵树,如:**堆排序**。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。
+
+栈:一种先进后出的数据结构。
+
+
+
+####内存分配中的堆和栈
+内存分配中的堆和栈
+栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
+
+堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
+
+堆栈缓存方式
+栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
+堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
+
+### 线程
+线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
+
+
+线程:
+1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源
+2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃
+
+
+线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
+
+
+### 线程安全
+
+因为线程间共享进程中的全局变量,所以当其他线程改变了共享的变量时,可能会对本线程产生影响。
+
+所谓线程安全的约束是指一个函数被多个并发线程反复调用时,要一直产生正确的结果。要保证线程安全,主要是通过加锁的方式保证共享变量的正确访问。
+
+
+换句话说, **线程安全 是在多线程的环境下, 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取**.
+
+既然,多线程环境下必须存在资源的竞争,那么如何才能保证同一时刻只有一个线程对共享资源进行存取?
+
+`加锁可以保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取`.
+
+通常加锁也有2种不同的粒度的锁:
+
+1. fine-grained(所谓的**细粒度**), 那么程序员需要自行地加,解锁来保证线程安全
+
+2. coarse-grained(所谓的**粗粒度**), 那么语言层面本身维护着一个全局的锁机制,用来保证线程安全
+
+
+前一种方式比较典型的是 java, Jython 等, 后一种方式比较典型的是 CPython (即Python).
+至于Python中的全局锁机制,也即 `GIL (Global Interpreter Lock)`
+
+
+
+### 互斥锁
+每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
+
+同一进程中的多线程之间是共享系统资源的,
+
+多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。
+
+
+
+## 阻塞VS非阻塞
+
+### 阻塞
+阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。
+
+
+常见的阻塞形式有:网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。
+
+阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。
+如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。
+
+
+### 非阻塞
+
+程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的。
+
+非阻塞并不是在任何程序级别、任何情况下都可以存在的。
+
+仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。
+
+非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。
+
+
+
+
+## 同步VS异步
+
+### 同步
+
+多个任务之间有先后顺序执行,一个执行完下个才能执行
+
+
+### 异步
+
+多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调
+
+
+
+
+## 区别
+
+同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。
+
+
+
+
+## 并发 VS并行
+
+1、多线程程序在单核上运行,就是并发
+多线程,看起来一起执行,GIL在同一时刻限制了多个线程只能有一个线程被CPU执行
+
+2、多线程程序在多核上运行,就是并行
+多进程,多个进程在同一时刻可以占用多个CPU
+
+
+举个例子:
+1、并发: 因为是在一个cpu上,比如有十个线程,每个线程进行10毫秒(进行轮询操作)
+从人的角度看,好像这10个线程都在运行,但是从微观上看,在某个时间点看,
+其实只有一个线程在执行,这就是并发
+
+2.并行:因为在多个CPU上(比如10个CPU),比如有10个线程,每个线程执行10毫秒(各自在不同CPU上执行),
+从人的角度看,好像这10个线程都在运行,但是从微观上看,在某个时间点看,
+也同时有10个线程在执行,这就是并行
+
+
+
+
+
+## 协程
+协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。
+
+协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
+
+
+协程拥有自己的寄存器上下文和栈。
+
+协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以**上下文的切换非常快。**
+
+简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
+
+
+能够在一个线程中实现并发的概念
+能够规避在一些任务中的IO操作
+在任务的执行过程中,检测到IO就切换到其他任务
+协程在一个线程上 提高CPU的利用率
+协程相比与多线程优势 切换的效率更快
+
+应用场景:
+爬虫的例子,请求过程中的IO等待
+
+```python
+from gevent import monkey
+monkey.patch_all()
+import gevent
+from urllib.request import urlopen
+
+
+def get_url(url):
+ response = urlopen(url)
+ content = response.read().decode('utf-8')
+ return len(content)
+
+
+g1 = gevent.spawn(get_url, 'http://www.baidu.com')
+g2 = gevent.spawn(get_url, 'http://www.taobao.com')
+g3 = gevent.spawn(get_url, 'http://www.hao123.com')
+gevent.joinall([g1, g2, g3])
+print(g1.value)
+print(g2.value)
+print(g3.value)
+```
+
+运行结果:
+```
+156535
+133524
+298483
+```
+可以看到结果是同时显示出来的
+
+
+
+
+**Python里最常见的yield就是协程的思想**
+
+
+举个例子:
+```python
+import threading
+
+
+class Thread(threading.Thread):
+ def __init__(self, name):
+ threading.Thread.__init__(self)
+ self.name = name
+
+ def run(self):
+ for i in range(10):
+ print(self.name)
+
+
+threadA = Thread("A")
+threadB = Thread("B")
+
+threadA.start()
+threadB.start()
+
+```
+
+
+返回结果是:
+```
+A
+A
+A
+B
+B
+B
+B
+B
+B
+B
+B
+B
+BA
+A
+A
+A
+A
+
+A
+A
+```
+那么总共发生了 20 次切换:主线程 -> A -> B -> A -> B …
+
+使用协程方式:
+
+```python
+import greenlet
+
+
+def run(name, nextGreenlets):
+ for i in range(10):
+ print(name)
+ if nextGreenlets:
+ nextGreenlets.pop(0).switch(chr(ord(name) + 1), nextGreenlets)
+
+
+greenletA = greenlet.greenlet(run)
+greenletB = greenlet.greenlet(run)
+
+greenletA.switch('A', [greenletB])
+
+```
+
+
+返回结果:
+```
+A
+B
+B
+B
+B
+B
+B
+B
+B
+B
+B
+
+```
+此时发生了 2 次切换:主协程 -> A -> B
+
+
+yield例子:
+```python
+def consumer():
+ print(123)
+ while True:
+ x = yield
+ print("处理了数据:", x)
+
+
+def producer():
+ c = consumer()
+ next(c)
+ for i in range(10):
+ print("生产了数据:", i)
+ c.send(i)
+
+
+producer()
+```
+
+### greenlet
+greenlet 是一个轻量级的协程实现,使用的方法简单而清晰。创建 greenlet 实例执行方法,在方法内部可通过 **`greenlet.switch()`** 切换至其他 greenlet 实例进行执行
+
+例子:
+```python
+from greenlet import greenlet
+
+
+def eat():
+ print("eating start")
+ g2.switch()
+ print("eating end")
+ g2.switch()
+
+
+def play():
+ print("playing start")
+ g1.switch()
+ print("playing end")
+
+
+g1 = greenlet(eat)
+g2 = greenlet(play)
+g1.switch()
+
+```
+
+运行结果:
+```
+eating start
+playing start
+eating end
+playing end
+
+```
+
+
+### gevent
+
+gevent 基于 greenlet 库进行了封装,基于 libev 和 libuv 提供了高效的同步API。
+
+对 greenlet 在业务开发中的不便之处,提供了很好的解决方案:
+
+对于 greenlet 实例的管理,不使用树形关系进行组织,隐藏不必要的复杂性
+
+因为协程还是运行在一个OS进程中,所以协程不能跑阻塞任务,否则就要将整个OS进程阻塞住了。
+
+采用 monkey patching 与第三方库协作,将阻塞任务变成非阻塞,也不需要手工通过greenlet.switch() 切换;
+
+
+例子:
+```python
+import gevent
+
+
+def foo():
+ print('Running in foo')
+ gevent.sleep(0)
+ print('Explicit context switch to foo again')
+
+
+def bar():
+ print('Explicit context to bar')
+ gevent.sleep(0)
+ print('Implicit context switch back to bar')
+
+
+gevent.joinall([
+ gevent.spawn(foo),
+ gevent.spawn(bar),
+])
+
+```
+
+运行结果:
+```
+Running in foo
+Explicit context to bar
+Explicit context switch to foo again
+Implicit context switch back to bar
+```
+
+通过这个例子可以看到 两个上下文通过调用 gevent.sleep()来互相切换。
+
+
+再看一个例子:
+```python
+import gevent
+import random
+
+
+def task(pid):
+ """
+ Some non-deterministic task
+ """
+ gevent.sleep(random.randint(0, 2) * 0.001)
+ print('Task', pid, 'done')
+
+
+def synchronous():
+ for i in range(1, 10):
+ task(i)
+
+
+def asynchronous():
+ threads = [gevent.spawn(task, i) for i in range(10)]
+ gevent.joinall(threads)
+
+
+print('Synchronous:')
+synchronous()
+
+print('Asynchronous:')
+asynchronous()
+
+```
+
+运行结果是:
+```
+Synchronous:
+Task 1 done
+Task 2 done
+Task 3 done
+Task 4 done
+Task 5 done
+Task 6 done
+Task 7 done
+Task 8 done
+Task 9 done
+Asynchronous:
+Task 0 done
+Task 4 done
+Task 5 done
+Task 1 done
+Task 3 done
+Task 7 done
+Task 2 done
+Task 6 done
+Task 8 done
+Task 9 done
+```
+
+
+在同步的情况下,任务是按顺序执行的,在执行各个任务的时候会阻塞主线程。
+
+而gevent.spawn 的重要功能就是封装了greenlet里面的函数。
+初始化的greenlet放在了threads这个list里面,被传递给了 gevent.joinall 这个函数,它会阻塞当前的程序来执行所有的greenlet。
+
+在异步执行的情况下,所有任务的执行顺序是完全随机的。
+每一个greenlet的都不会阻塞其他greenlet的执行。
+
+
+
+
+
+
+
+## 协程VS子程序
+
+`协程是为了实现单线程的i/o密集型的任务并发(通过event loop)
+如果子程序的话,永远是串行的`
+
+什么是子程序?
+
+协程比子程序的优势在哪里?
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md"
new file mode 100644
index 00000000..3c957db9
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md"
@@ -0,0 +1,472 @@
+# 第14章 可迭代的对象、迭代器和生成器
+
+
+- [第14章 可迭代的对象、迭代器和生成器](#第14章-可迭代的对象迭代器和生成器)
+ - [可迭代对象](#可迭代对象)
+ - [迭代器](#迭代器)
+ - [迭代器是什么](#迭代器是什么)
+ - [迭代器内存消耗检测](#迭代器内存消耗检测)
+ - [迭代器使用](#迭代器使用)
+ - [生成器](#生成器)
+ - [yield生成器](#yield生成器)
+ - [生成器执行过程](#生成器执行过程)
+ - [生成器创建](#生成器创建)
+ - [生成器判断](#生成器判断)
+ - [yield与协程](#yield与协程)
+
+
+
+## 可迭代对象
+
+举个例子:
+
+```python
+import re
+
+re_word = re.compile(r'\w+')
+
+
+class Sentence(object):
+ def __init__(self, text):
+ self.text = text
+ self.word = re_word.findall(text)
+
+ def __getitem__(self, item):
+ return self.word[item]
+
+ def __len__(self):
+ return len(self.word)
+
+ def __str__(self):
+ return 'Sentence(%s)' % self.word
+
+
+if __name__ == "__main__":
+
+ s = Sentence("Today is Tuesday")
+ print(s)
+
+ for word in s:
+ print(word)
+
+```
+
+返回结果:
+```
+Sentence(['Today', 'is', 'Tuesday'])
+Today
+is
+Tuesday
+```
+
+我们知道一个对象可以迭代是因为实现了`__iter__`方法,
+但是在Sentence中并没有实现`__iter__`方法。那为什么可以迭代呢。
+
+原因在于在python中实现了iter和getitem的都是可迭代的。首先会检查是否实现了iter方法,如果实现了则调用,如果没有但是实现了`__getitem__`方法。
+
+Python就会创建一个迭代器。
+
+尝试按照顺序获取元素。如果尝试失败则会抛出typeerror异常,提示object is not iterable.
+
+因此:
+
+> 如果对象实现了能返回迭代器的`__iter__`方法,那么对象就是可迭代的。
+>
+>如果实现了`__getitem__`方法,而且其参数是从零开始的索引。
+
+这种对象也可以迭代。
+
+
+我们用`__iter__`方法来改造之前的Sentence。
+在`__iter__`中返回一个可迭代对象iter(self.word)。
+当执行for word in s的时候就会调用`__iter__`方法
+
+```python
+import re
+
+re_word = re.compile(r'\w+')
+
+
+class Sentence(object):
+ def __init__(self, text):
+ self.text = text
+ self.word = re_word.findall(text)
+
+ def __iter__(self):
+ return iter(self.word)
+
+ def __len__(self):
+ return len(self.word)
+
+ def __str__(self):
+ return 'Sentence(%s)' % self.word
+
+
+if __name__ == "__main__":
+
+ s = Sentence("Today is Tuesday")
+ print(s)
+
+ for word in s:
+ print(word)
+
+```
+
+
+
+再来看下next, next的作用是返回下一个元素,如果没有元素了,抛出stopIteration异常。
+
+我们来看下next的使用方法。如果要遍历一个字符串,最简单的方法如下:
+
+```python
+s = 'abc'
+
+for char in s:
+ print(char)
+
+```
+
+如果不用for方法,代码需要修改如下:
+```python
+s = 'abc'
+
+it = iter(s)
+
+while True:
+ try:
+ print(next(it))
+ except StopIteration:
+ del it
+ break
+```
+
+首先将s变成一个iter对象,然后不断调用next获取下一个字符,如果没有字符了,则会抛出StopIteration异常释放对it的引用
+
+```python
+s = 'abc'
+
+it = iter(s)
+
+print(next(it))
+print(next(it))
+print(next(it))
+print(next(it)) # StopIteration
+
+
+```
+
+因为只有3个字符,但是调用了4次it.next()导致已经找不到字符因此抛出异常。
+
+
+总结:
+> 可迭代对象:实现了`__iter__`方法,就是可迭代的,可以返回自身作为迭代器。
+> 也可以返回其他一个可迭代对象
+
+
+## 迭代器
+
+### 迭代器是什么
+
+迭代器:在Python2中实现了next方法,在python3中实现了`__next__`方法。
+
+首先要让x通过iter变成一个可迭代对象,然后使用迭代器来调用元素
+
+使用迭代器好处就是:**每次只从对象中读取一条数据,不会造成内存的过大开销。**
+
+
+
+
+
+
+### 迭代器内存消耗检测
+
+可以看看它的内存占用:
+
+使用python2.7.15+:
+
+```python
+import sys
+i = iter(range(1000000))
+print sys.getsizeof(i)
+
+
+r = range(1000000)
+print sys.getsizeof(r)
+
+
+y = xrange(1000000) # 注意 xrange跟range的区别: xrange是range的迭代器的表达方式
+print sys.getsizeof(y)
+```
+
+结果返回:
+```
+56
+8000064
+32
+```
+
+
+使用python 3.6+
+```python
+import sys
+i = iter(range(1000000))
+print (sys.getsizeof(i))
+
+
+r = range(1000000)
+print (sys.getsizeof(r))
+
+```
+
+返回结果:
+```
+32
+48
+```
+
+
+这里有个问题:为什么在python2跟Python3的运行结果相差这么大呢?
+**这是因为python3内部机制已经将range转换成了一个迭代器了。**
+
+这里可以看的出来适合大的数据,比如好几个G的数据, 使用了迭代器 内存使用大幅度减少,这是迭代器最大的优点。
+
+
+总结下:
+我们简单说迭代器就是访问集合元素,迭代器就是有一个next()方法的对象,而不是通过索引来计数的。
+
+### 迭代器使用
+
+那么我们怎么能访问迭代器里面的元素呢?
+
+迭代器有两个方法 ,分别是**iter()和next()**方法
+
+这两个方法是迭代器最基本的方法
+一个用来获得迭代器对象,一个用来获取容器中的下一个元素。
+
+**itertools是python提供的非常高效创建与使用迭代器的模块**
+
+```python
+from itertools import chain
+test = chain.from_iterable('ABCDEFG')
+# print(test)
+# print(dir(test)) # 查看类具体方法
+
+print(test.__next__()) # 'A'
+print(test.__next__()) # 'B'
+print(test.__next__()) # 'C'
+
+test2 = chain('AB', 'CDE', 'F')
+print(list(test2)) # ['A', 'B', 'C', 'D', 'E', 'F']
+```
+
+
+我们知道迭代器是不支持索引的,原因就是索引需实现明元素确占用的内存地址,而迭代器是用到元素的时候才会创建。如下:
+
+```python
+i = iter(range(3)) # 创建迭代器
+# i.index(2) # 获取元素为2的索引
+# AttributeError: 'range_iterator' object has no attribute 'index'
+
+# 列表
+l = range(3)
+print(l.index(2)) # 获取索引2
+
+```
+
+这个时候可以使用内建函数enumerate(),这个函数很重要。
+
+```python
+for i, j in enumerate(iter(['A', 'B', 'C'])):
+ # for i, j in enumerate(['A', 'B', 'C']):
+ print(i, j)
+
+```
+
+运行结果返回:
+```
+0 A
+1 B
+2 C
+```
+
+
+
+可以看下这个函数的源码是怎么写的
+
+```python
+class enumerate(Iterator[Tuple[int, _T]], Generic[_T]):
+ def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ...
+ def __iter__(self) -> Iterator[Tuple[int, _T]]: ...
+ if sys.version_info >= (3,):
+ def __next__(self) -> Tuple[int, _T]: ...
+ else:
+ def next(self) -> Tuple[int, _T]: ..
+```
+
+## 生成器
+生成器是迭代器,但你只能遍历它一次(iterate over them once)
+
+因为生成器并没有将所有值放入内存中,而是实时地生成这些值
+
+```python
+mygenerator = (x * x for x in range(3)) # 生成器
+# mygenerator = [x * x for x in range(3)] # 列表
+for i in mygenerator:
+ print("i=", i)
+
+for i in mygenerator:
+ print("New=", i)
+```
+
+运行结果:
+```
+i= 0
+i= 1
+i= 4
+```
+
+注意,你不能执行for i in mygenerator第二次,因为每个生成器只能被使用一次
+
+
+### yield生成器
+
+在Python中,使用生成器可以很方便的支持迭代器协议。
+
+生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,
+而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。
+
+也就是说,yield是一个语法糖,内部实现支持了迭代器协议
+
+同时yield内部是一个状态机,维护着挂起和继续的状态。
+
+
+下面看看生成器的使用:
+```python
+def Zrange(n):
+ i = 0
+ while i < n:
+ yield i
+ i += 1
+
+
+zrange = Zrange(3)
+print(zrange) #
+
+## setdefault用法
+
+使用setdefault处理找不到的键
+
+简单例子:
+```python
+d = {'a': 1, 'b': 2}
+d['c'] = 3
+
+# 若原来没有,设置,否则原值不变
+d.setdefault('c', 4)
+
+print(d) # {'a': 1, 'b': 2, 'c': 3}
+
+```
+
+字典也有get方法,如下所示:
+```python
+d = {'a': 1, 'b': 2}
+# d['c'] = 3
+
+
+# 使用get 方法设置的默认值不会改变原字典
+a = d.get('c', 4)
+print(a) # 4
+
+print(d) # {'a': 1, 'b': 2}
+
+```
+
+`可以看到两者的区别:`
+> get 方法设置的默认值不会改变原字典, 而setdefault设置的默认值会改变原字典的值。
+
+
+再看一个例子:
+
+```python
+some_dict = {'abc': 'a', 'cdf': 'b', 'gh': 'a', 'fh': 'g', 'hfz': 'g'}
+new_dict = {}
+
+for k, v in some_dict.items():
+ new_dict.setdefault(v, []).append(k)
+
+print(new_dict) # {'a': ['abc', 'gh'], 'b': ['cdf'], 'g': ['fh', 'hfz']}
+
+```
+
+
+
+
+## defaultdict用法
+
+所有的映射类型在处理找不到的键的时候,都会牵扯到`__missing__`方法。这也是这个方法称作“missing”的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么个东西存在的.
+
+举个例子来说:
+```python
+class Dict(dict):
+ def __missing__(self, key):
+ self[key] = []
+ return self[key]
+
+
+dct = Dict()
+print(dct["foo"]) # []
+
+dct["foo"].append(1)
+dct["foo"].append(2)
+print(dct["foo"]) # [1,2]
+
+```
+defaultdict是属于collections 模块下的一个工厂函数,用于构建字典对象,
+接收一个函数(可调用)对象为作为参数。
+参数返回的类型是什么,key对应value就是什么类型
+
+```python
+
+from collections import defaultdict
+
+# 参数为 list,它就会构建一个默认value为list的字典,
+# 例如result['a']的值默认就是list对象。
+dct = defaultdict(list)
+
+print(dct) # defaultdict(
+
+default性能效率检测
+
+```python
+import timeit
+from collections import defaultdict
+
+
+def way1():
+ d = {}
+ chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000
+ for c in chars:
+ if c in d:
+ d[c] += 1
+ else:
+ d[c] = 1
+ return d
+
+
+def way2():
+ d = defaultdict(int)
+ chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000
+ for c in chars:
+ d[c] += 1
+
+ return d
+
+
+if __name__ == "__main__":
+ t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10)
+ print(t1) # 6e-07(表示6x10^(-7),也就是6x0.0000001,如果写成6e07表示6x10^7)
+
+ t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10)
+ print(t2) # 4.000000000000097e-07
+
+```
+
+
+> 对于字典的使用,我们要学会用**defaultdict**来代替,
+> 一来是因为有缺省值非常安全,如果访问不存在的key,不会报错;
+> 二来是Pyhon性能会大幅提高。
+仅仅换了字典数据结构,性能就大幅的提高了很多。
+
+
+
+## 字典的变种
+
+
+python字典 dict 默认是无序的,要有序请用collection中的orderdict
+
+python 2 代码:
+```python
+import collections
+
+d = {}
+d['name'] = 'zhf'
+d['age'] = 33
+d['city'] = 'chengdu'
+
+
+
+for k, v in d.items():
+ print k, v
+
+a = collections.OrderedDict()
+a['name'] = 'zhf'
+a['age'] = 33
+a['city'] = 'chengdu'
+
+
+print "*" * 10
+for k, v in a.items():
+ print k,v
+
+```
+返回结果:
+```
+city chengdu
+age 33
+name zhf
+**********
+name zhf
+age 33
+city chengdu
+
+```
+
+python3.6.7 代码:
+```python
+import collections
+
+d = {}
+d['name'] = 'zhf'
+d['age'] = 33
+d['city'] = 'chengdu'
+
+for k, v in d.items():
+ print(k, v)
+
+a = collections.OrderedDict()
+a['name'] = 'zhf'
+a['age'] = 33
+a['city'] = 'chengdu'
+
+print("*" * 10)
+for k, v in a.items():
+ print(k, v)
+
+```
+
+运行结果:
+```
+name zhf
+age 33
+city chengdu
+**********
+name zhf
+age 33
+city chengdu
+
+```
+
+> `可以看到3.6版本的Python已经使得dict变得紧凑以及关键字变得有序`
+
+
+
+
+## collections.Counter 用法
+
+简单例子:
+```python
+import collections
+
+string = "aaabbbc"
+
+ct = collections.Counter(string)
+print(ct) # Counter({'a': 3, 'b': 3, 'c': 1})
+print(dict(ct)) # {'a': 3, 'b': 3, 'c': 1}
+
+ct.update('abcdef')
+print(dict(ct)) # {'a': 4, 'b': 4, 'c': 2, 'd': 1, 'e': 1, 'f': 1}
+
+```
+
+
+## 集合
+
+集合的本质是许多唯一对象的聚集。因此集合可以去掉重复的元素
+
+
+```python
+d = ['abc', 'def', 'abc', 'def']
+
+s = set(d)
+
+print(s) # {'def', 'abc'}
+
+```
+
+假设有2个集合a和b,需要统计集合a的哪些元素在集合b中也出现。
+如果不使用集合,代码只能写成下面的形式:
+```python
+a = ['abc', 'def', 'aaa']
+b = ['abc', 'bbb', 'ccc', 'def']
+
+for n in a:
+ if n in b:
+ print(n)
+```
+但是如果使用集合,则不用这么麻烦。
+在集合中。a|b返回的是合集,a&b返回的是交集。
+a-b返回的是差集。**-差集是指属于A但不属于B的结合**
+
+```python
+a = ['abc', 'def', 'aaa']
+b = ['abc', 'bbb', 'ccc', 'def']
+
+print(set(a) & set(b))
+print(set(a) | set(b))
+print(set(a) - set(b)) # 相等于 print(set(a).difference(b))
+```
+
+结果返回:
+```
+{'def', 'abc'}
+{'aaa', 'def', 'ccc', 'abc', 'bbb'}
+{'aaa'}
+```
+
+
+## 关于字典,列表,集合运行效率的对比
+
+列表交集代码:
+
+```python
+from time import time
+
+t = time()
+lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]
+listb = [2, 4, 6, 9, 23]
+intersection = []
+
+for i in range(1000000):
+ for a in lista:
+ for b in listb:
+ if a == b:
+ intersection.append(a)
+
+print("total run time:%s" % (time() - t))
+# total run time:5.015027046203613
+```
+
+集合交集代码:
+```python
+from time import time
+t = time()
+
+lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]
+listb = [2, 4, 6, 9, 23]
+intersection = []
+
+for i in range(1000000):
+ list(set(lista) & set(listb))
+
+print("total run time:%s" % (time() - t))
+# total run time:1.0969467163085938
+```
+
+
+字典代码:
+```python
+from time import time
+
+t = time()
+list = [
+ 'a', 'b', 'is', 'python', 'jason', 'hello', 'hill', 'with', 'phone',
+ 'test', 'dfdf', 'apple', 'pddf', 'ind', 'basic', 'none', 'baecr', 'var',
+ 'bana', 'dd', 'wrd'
+]
+# list = dict.fromkeys(list, True) # total run time:0.9107456207275391
+print(list)
+filter = []
+for i in range(1000000):
+ for find in ['is', 'hat', 'new', 'list', 'old', '.']:
+ if find not in list:
+ filter.append(find)
+print("total run time:%s" % (time() - t))
+# total run time:2.0197031497955322
+```
+
+
+Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。
+
+因此在需要多数据成员进行频繁的查找或者访问的时候,`使用 dict 而不是 list 是一个较好的选择`
+
+> 一个良好的算法能够对性能起到关键作用,因此性能改进的首要点是对算法的改进。
+> 在算法的时间复杂度排序上依次是:
+**O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)**
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md"
new file mode 100644
index 00000000..afdb5cfc
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md"
@@ -0,0 +1,104 @@
+# 第8章-对象引用、可变性和垃圾回收
+
+
+
+
+- [第8章-对象引用、可变性和垃圾回收](#%e7%ac%ac8%e7%ab%a0-%e5%af%b9%e8%b1%a1%e5%bc%95%e7%94%a8%e5%8f%af%e5%8f%98%e6%80%a7%e5%92%8c%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6)
+ - [浅拷贝](#%e6%b5%85%e6%8b%b7%e8%b4%9d)
+ - [深拷贝](#%e6%b7%b1%e6%8b%b7%e8%b4%9d)
+ - [两者区别](#%e4%b8%a4%e8%80%85%e5%8c%ba%e5%88%ab)
+
+
+
+## 浅拷贝
+
+**`copy 浅复制,不会拷贝其子对象,修改子对象,将受影响`**
+
+```python
+import copy
+a = [1, 2, 3]
+b = copy.copy(a)
+
+print("id a=", id(a)) # id a= 1669348705224
+print("id b=", id(b)) # id b= 1669348161608
+
+print("id a 0=", id(a[0])) # id a 0= 1586588704
+print("id b 0=", id(b[0])) # id b 0= 1586588704
+
+a[0] = "hello"
+print("new a=", a) # new a= ['hello', 2, 3]
+print("new b=", b) # new b= [1, 2, 3]
+
+b[0] = "world"
+print("new 2 a=", a) # new a= ['hello', 2, 3]
+print("new 2 b=", b) # new 2 b= ['world', 2, 3]
+
+
+```
+
+
+
+## 深拷贝
+**`deepcopy 深复制,将拷贝其子对象,修改子对象,将不受影响`**
+
+```python
+import copy
+a = [1, 2, 3]
+b = copy.deepcopy(a)
+
+print("id a=", id(a)) # id a= 2760920581064
+print("id b=", id(b)) # id b= 2760920037448
+
+print("id a 0=", id(a[0])) # id a 0= 1586588704
+print("id b 0=", id(b[0])) # id b 0= 1586588704
+
+a[0] = "hello"
+print("new a=", a) # new a= ['hello', 2, 3]
+print("new b=", b) # new b= [1, 2, 3]
+
+b[0] = "world"
+print("new 2 a=", a) # new a= ['hello', 2, 3]
+print("new 2 b=", b) # new 2 b= ['world', 2, 3]
+```
+
+## 两者区别
+
+发现**对于不可变对象**,比如整数、字符串、元组、还有由这些不可变对象组成的集合对象,**浅拷贝和深拷贝没有区别,都是拷贝一个新对象**
+
+
+**`两者的区别在于拷贝组合对象`**,比如列表中还有列表,字典中还有字典或者列表的情况时,浅拷贝只拷贝了外面的壳子,里面的元素并没有拷贝,而深拷贝则是把壳子和里面的元素都拷贝了一份新的。
+
+看一个例子:
+
+```python
+import copy
+
+a = [0, [1, 2], 3]
+b = copy.copy(a)
+c = copy.deepcopy(a)
+
+c[1][0] = "hello"
+print("a=", a) # a= [0, [1, 2], 3]
+print("c=", c) # c= [0, ['hello', 2], 3]
+
+b[1][0] = "world"
+
+print("new a=", a) # new a= [0, ['world', 2], 3]
+print("new b=", b) # new b= [0, ['world', 2], 3]
+```
+
+
+
+**小提示:**
+
+> 可以利用[python在线调试](http://www.pythontutor.com/visualize.html#mode=display)进行相关调试分析
+
+
+
+
+
+
+
+
+
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md"
new file mode 100644
index 00000000..8f364fe8
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md"
@@ -0,0 +1,2 @@
+# 第20章 属性描述符
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md"
new file mode 100644
index 00000000..7e471795
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md"
@@ -0,0 +1,63 @@
+# 常见问题答疑
+
+
+- [常见问题答疑](#%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98%e7%ad%94%e7%96%91)
+ - [1.Python 类self方法](#1python-%e7%b1%bbself%e6%96%b9%e6%b3%95)
+
+
+
+## 1.Python 类self方法
+
+Python的类的方法和普通的函数有一个很明显的区别,在类的方法必须有个额外的第一个参数(self),但在调用这个方法的时候不必为这个参数赋值
+
+Python的类的方法的这个特别的参数指代的是对象本身,而按照Python的惯例,它用self来表示。(当然我们也可以用其他任何名称来代替,只是规范和标准在那建议我们一致使用self)
+
+**为何Python给self赋值而你不必给self赋值?**
+
+例子说明:
+
+创建了一个类MyClass,实例化MyClass得到了MyObject这个对象,然后调用这个对象的方法MyObject.method(arg1,arg2),
+
+这个过程中,Python会自动转为Myclass.mehod(MyObject,arg1,arg2)
+这就是Python的self的原理了。
+
+即使你的类的方法不需要任何参数,但还是得给这个方法定义一个self参数,虽然我们在实例化调用的时候不用理会这个参数不用给它赋值。
+
+
+实例:
+
+```python
+class Demo(object):
+ def hello(self):
+ print('Hello')
+
+
+p = Demo()
+p.hello() # hello
+Demo.hello(p) # hello
+
+```
+
+如果把self去掉的话,这样就报错了
+```
+TypeError: hello() takes no arguments (1 given)
+```
+
+
+**self在Python里不是关键字**
+
+**self代表当前对象的地址**
+
+```python
+class Demo(object):
+ def hello(self):
+ print("self=", self)
+ print('Hello')
+
+
+p = Demo()
+p.hello()
+# self= <__main__.Demo object at 0x000001CD84029EF0>
+# hello
+```
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md"
new file mode 100644
index 00000000..2ed73f16
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md"
@@ -0,0 +1,767 @@
+# 第2章 序列构成的数组
+
+
+
+- [第2章 序列构成的数组](#第2章-序列构成的数组)
+ - [列表推导](#列表推导)
+ - [数组与列表效率对比](#数组与列表效率对比)
+ - [使用memoryview模块](#使用memoryview模块)
+ - [bisect模块用法](#bisect模块用法)
+ - [pickle模块用法](#pickle模块用法)
+ - [双向队列](#双向队列)
+
+
+
+第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构
+
+## 列表推导
+列表的推导,将一个字符串编程一个列表,有下面的2种方法。
+其中第二种方法更简洁。可读性也比第一种要好。
+
+
+```python
+str = 'abc'
+string = []
+
+# 第一种方法
+for s in str:
+ print(string.append(s))
+
+# 第二种方法
+ret = [s for s in str]
+print(ret)
+```
+
+> 用这种for…in的方法来推导列表,有个好处就是不会有变量泄露也就是越界的问题
+
+列表的推导还有一种方式,称为生成器表达式。
+表达式都差不多,不过是方括号编程了圆括号而已
+
+`生成器的好处是什么呢?`
+
+> 列表推导是首先生成一个组合的列表,这会占用到内存。
+而生成式则是在每一个for循环运行时才生成一个组合,这样就不会预先占用内存
+生成器表达式不会一次将整个列表加载到内存之中,而是生成一个生成器对象(Generator objector),所以一次只加载一个列表元素
+
+举个例子来说明:
+
+有20000个数组的列表。分别用列表推导法和生成式表达法来进行遍历。
+并用`memory_profiler` 来监控代码占用的内存
+
+列表推导法例子:
+```python
+# 如果系统没有这个模块就执行pip install memory_profiler进行安装
+from memory_profiler import profile
+
+
+@profile
+def fun_try():
+ test = []
+
+ for i in range(20000):
+ test.append(i)
+
+ for num in [t for t in test]:
+ print(num)
+
+
+fun_try()
+```
+
+这个代码运行结果是:
+
+```
+ Line # Mem usage Increment Line Contents
+================================================
+ 5 39.9 MiB 39.9 MiB @profile
+ 6 def fun_try():
+ 7 39.9 MiB 0.0 MiB test = []
+ 8
+ 9 40.8 MiB 0.0 MiB for i in range(20000):
+ 10 40.8 MiB 0.1 MiB test.append(i)
+ 11
+ 12 41.2 MiB 0.2 MiB for num in [t for t in test]:
+ 13 41.2 MiB 0.0 MiB print(num)
+
+```
+
+
+生成式例子:
+
+```python
+# 如果系统没有这个模块就执行pip install memory_profiler进行安装
+from memory_profiler import profile
+
+
+@profile
+def fun_try():
+ test = []
+
+ for i in range(20000):
+ test.append(i)
+
+ for num in (t for t in test):
+ print(num)
+
+
+fun_try()
+
+```
+
+这个代码运行结果是:
+```
+Line # Mem usage Increment Line Contents
+================================================
+ 5 40.1 MiB 40.1 MiB @profile
+ 6 def fun_try():
+ 7 40.1 MiB 0.0 MiB test = []
+ 8
+ 9 41.1 MiB 0.0 MiB for i in range(20000):
+ 10 41.1 MiB 0.1 MiB test.append(i)
+ 11
+ 12 41.1 MiB 0.0 MiB for num in (t for t in test):
+ 13 41.1 MiB 0.0 MiB print(num)
+
+```
+
+
+结论:
+
+ `通过这两个结果可以看到列表推导法增加了0.2MB的内存
+ 除非特殊的原因,应该经常在代码中使用生成器表达式。
+ 但除非是面对非常大的列表,否则是不会看出明显区别的。
+`
+
+
+下面介绍下元组。说到元组,第一个反应就应该是不可变列表。
+但作者同时还介绍了元组的很多其他特性。
+首先来看下元组的拆包。
+
+```python
+#元组拆包
+t = (20, 8)
+a, b = t
+print(a, b)
+```
+
+如果在进行拆包的同时,并不是对所有的元组数据都感兴趣。
+**_占位符**就能帮助处理这种情况
+
+```python
+import os
+ _, filename = os.path.split("/home/jian/prj/demo.txt")
+print(_, filename) #/home/jian/prj demo.txt
+```
+上面元组拆包的时候是对可迭代对象进行遍历,然后一一赋值到变量。
+但是如果想通过给每个元组元素的命名来访问,则需用到命名元组namdtuple
+
+```python
+# collections.namedtuple构建一个带有字段名的元组和一个有名字的类
+from collections import namedtuple, OrderedDict
+
+City = namedtuple('City', 'name country population')
+tokyo = City('Tokyo', 'JP', '123')
+tokyo_data = ('Tokyo', 'JP', '123')
+
+print(tokyo) # City(name='Tokyo', country='JP', population='123')
+print(tokyo.name, tokyo.country, tokyo.population) # Tokyo JP 123
+print(City._fields) # ('name', 'country', 'population')
+
+tokyo = City._make(tokyo_data)
+print(tokyo) # City(name='Tokyo', country='JP', population='123')
+
+OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])
+print(tokyo._asdict())
+# OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])
+
+```
+
+
+**符号用来处理剩下的元素**
+
+```python
+a, b, *rest = range(5)
+print(a, b, rest) # 0 1 [2, 3, 4]
+
+a, *b, rest = range(5)
+print(a, b, rest) # 0 [1,2,3] 4
+
+```
+
+
+* 序列的操作:
+增量赋值:
+增量运算符+,*等其实是调用`__iadd__/__add__/__mul__`方法。
+**对于可变对象来说,+调用的是`__iadd__`,对于不可变对象来说对象调用的是`__add__`。**
+
+**两者有什么区别呢。**
+
+先看下面的例子
+```python
+str = [1, 2, 3]
+str1 = (1, 2, 3)
+
+print("str:%d" % id(str))
+print("str1:%d" % id(str1))
+
+str += str
+str1 += str1
+
+print("str:%d" % id(str))
+print("str1:%d" % id(str1))
+```
+
+得到的结果如下:
+
+```
+str:2256630301640
+str1:2256630239736
+str:2256630301640
+str1:2256630606152
+
+```
+
+str是列表,str1是元组,列表是可变对象,元组是不可变对象。
+
+在进行加法运算后,str的id没有改变,因此还是之前的对象,但是str1的id却发生了改变,不是之前的对象了。
+
+这种的差别在于`__iadd__`的方法类似调用a.extend(b)的方式,是在原有的对象上进行扩展操作。
+
+但是`__add__`的方式类似于a=a+b。
+首先a+b得到一个新的的对象,然后复制给a,因此变量和之前的对象没有任何联系。
+而是被关联到一个新的对象。同样的乘法`__imul__/__mul__`也是类似的道理
+
+列表组成的列表:
+```python
+board = [['_'] * 3 for i in range(3)]
+print(board)
+
+board[1][2] = 'x'
+print(board)
+
+```
+运行结果:
+```
+[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
+[['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]
+
+```
+
+输出三个列表的ID
+```python
+board = [['_'] * 3 for i in range(3)]
+print(board)
+
+board[1][2] = 'x'
+print(board)
+
+# 输出3个列表的ID
+print(id(board[0]))
+print(id(board[1]))
+print(id(board[2]))
+
+```
+
+结果是分别属于不同的ID:
+```
+3221578302664
+3221578302536
+3221578302600
+```
+
+我们再来看下另外一种用法。下面的代码对一个包含3个列表的列表进行*3的操作。
+```python
+board = [['_'] * 3] * 3
+print(board)
+
+board[1][2] = 'x'
+print(board)
+print(id(board[0]))
+print(id(board[1]))
+print(id(board[2]))
+```
+
+运行结果是:
+发现id都一样。说明了全部指向的是同一个对象。
+```
+[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
+[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']]
+1195278432328
+1195278432328
+1195278432328
+```
+
+如果对不可变对象中的可变对象进行赋值会产生什么后果,比如下面的这段代码
+```python
+t = (1, 2, [30, 40])
+t[2] += [50, 60]
+
+print(t)
+```
+
+运行结果直接报错:
+```
+TypeError: 'tuple' object does not support item assignment
+```
+
+我们可以把代码放在[python在线调式网站](http://pythontutor.com/visualize.html#mode=edit)进行调试
+
+把代码稍微修改下然后再看看情况
+```python
+t = (1, 2, [30, 40])
+t[2].append([50, 60])
+
+print(t)
+```
+
+`为什么这两种实现会带来不同的结果呢?`
+
+原因在于t是一个元组属于不可变对象。但用t[2]+=[50,60]的时候是对一个元组进行赋值。
+所以报错误。
+
+但是同时t[2]又属于一个列表可变对象。因此数据也更新成功了
+
+但是如果用t[2].append([50,60])的操作则是对一个列表进行操作,而并没有对一个元组进行赋值。因此能够更新成功且不会报错误。
+
+这是一个很有趣的例子。
+
+对于理解可变对象和不可变对象的操作很有帮助。
+
+
+`查看背后的字节码情况:`
+```python
+import dis
+a = (1, 2, 3)
+
+byte_info = dis.dis('a[2]+=1')
+print(byte_info)
+```
+
+打印出反编译的结果(反编译后的代码与汇编语言接近)是:
+```
+ 1 0 LOAD_NAME 0 (a)
+ 2 LOAD_CONST 0 (2)
+ 4 DUP_TOP_TWO
+ 6 BINARY_SUBSCR
+ 8 LOAD_CONST 1 (1)
+ 10 INPLACE_ADD
+ 12 ROT_THREE
+ 14 STORE_SUBSCR
+ 16 LOAD_CONST 2 (None)
+ 18 RETURN_VALUE
+
+```
+
+>插播一个有趣的例子
+
+python之交换变量值
+
+```python
+import dis
+
+def swap1():
+ x = 5
+ y = 6
+ x, y = y,x
+
+def swap2():
+ x = 5
+ y = 6
+ tmp = x
+ x = y
+ y = tmp
+
+if __name__ == "__main__":
+ print ("***SWAP1***")
+ print (dis.dis(swap1))
+ print ("***SWAP2***")
+ print (dis.dis(swap2))
+```
+
+查看结果:
+```
+*** SWAP1***
+ 5 0 LOAD_CONST 1 (5)
+ 3 STORE_FAST 0 (x)
+ 6 6 LOAD_CONST 2 (6)
+ 9 STORE_FAST 1 (y)
+ 7 12 LOAD_FAST 1 (y)
+ 15 LOAD_FAST 0 (x)
+ 18 ROT_TWO
+ 19 STORE_FAST 0 (x)
+ 22 STORE_FAST 1 (y)
+ 25 LOAD_CONST 0 (None)
+ 28 RETURN_VALUE
+
+***SWAP2***
+10 0 LOAD_CONST 1 (5)
+ 3 STORE_FAST 0 (x)
+11 6 LOAD_CONST 2 (6)
+ 9 STORE_FAST 1 (y)
+12 12 LOAD_FAST 0 (x)
+ 15 STORE_FAST 2 (tmp)
+13 18 LOAD_FAST 1 (y)
+ 21 STORE_FAST 0 (x)
+14 24 LOAD_FAST 2 (tmp)
+ 27 STORE_FAST 1 (y)
+ 30 LOAD_CONST 0 (None)
+ 33 RETURN_VALUE
+```
+
+得到结论
+>通过字节码可以看到,swap1和swap2最大的区别在于,swap1中通过ROT_TWO交换栈顶的两个元素实现x和y值的互换,swap2中引入了tmp变量,多了一次LOAD_FAST, STORE_FAST的操作。
+>
+>执行一个ROT_TWO指令比执行一个LOAD_FAST+STORE_FAST的指令快,
+>
+>**这也是为什么swap1比swap2性能更好的原因**。
+
+
+
+* 数组:
+
+在列表和元组中,存放的是具体的对象,如整数对象,字符对象。
+如下面的整数b。占据28个字节。因为存放的是整数对象,而非整数本身。
+
+```python
+import sys
+b = 1
+
+print(sys.getsizeof(b)) # 28
+```
+
+对于存放大量数据来说。我们选择用数组的形式要好很多。
+**因为数组存储的不是对象,而是数字的机器翻译。也就是字节表述。**
+
+
+
+
+在array中需要规定各个字符的类型,如上表中的Type code。
+定义好类型后则数组内的元素必须全是这个类型,否则会报错。
+
+就像下面的代码。类型规定的是b也就是单字节的整数。
+但是在插入的时候却是c字符,则报错:`TypeError: an integer is required`
+
+```python
+import array
+
+num = array.array('b')
+num.append('c')
+
+print(num)
+```
+
+
+
+**在上表中,每个类型都有字节大小的限制。如果超出了字节大小的限制也是会报错的**
+
+还是b的这个类型,是有符号的单字节整数,那么范围是-128到127.
+如果我们插入128.则报错:`signed char is greater than maximum` 提示超过了最大
+
+```python
+import array
+
+num = array.array('b')
+num.append(128)
+
+print(num)
+```
+
+对于数组这种结构体来说,由于占用的内存小,因此在读取和写入文件的时候的速度更快,相比于列表来说的话。
+
+## 数组与列表效率对比
+下面来对比下:
+首先是用列表生成并写入txt文档的用法
+
+```python
+import time
+import struct
+
+# struct 例子
+# a = 20
+# 'i'表示一个int
+# # struct.pack把python值转化成字节流
+# data = struct.pack('i', a)
+# print(len(data))
+# print(repr(data))
+# print(data)
+
+# 拆包
+# a1 = struct.unpack('i', data)
+# print("a1=", a1)
+
+
+def arry_try_list():
+
+ floats = [float for float in range(10**7)]
+
+ fp = open('list.bin', 'wb')
+ start = time.clock()
+
+ for f in floats:
+
+ strdata = struct.pack('i', f)
+ fp.write(strdata)
+
+ fp.close()
+
+ end = time.clock()
+
+ print(end - start)
+
+
+arry_try_list()
+
+```
+
+执行结果:5.8789385
+
+
+再看数组的形式:
+```python
+from array import array
+from random import random
+import time
+
+
+def array_try():
+
+ floats = array('d', (random() for i in range(10**7)))
+ start = time.clock()
+
+ fp = open('floats.bin', 'wb')
+ floats.tofile(fp)
+
+ fp.close()
+
+ end = time.clock()
+ print(end - start)
+
+
+array_try()
+```
+执行结果: 0.045192899999999994
+
+可以看到速度明显提升了很多
+
+再来对比读文件的速度
+```python
+from array import array
+import time
+
+
+def array_try():
+
+ floats = array('d')
+ start = time.clock()
+
+ fp = open('floats.bin', 'rb')
+ # 比直接从文本文件里面读快,后者使用内置的float方法把每一行文字变成浮点数
+ floats.fromfile(fp, 10**7)
+ fp.close()
+
+ end = time.clock()
+ print(end - start)
+
+
+array_try()
+```
+
+执行结果是:0.1172238
+
+***
+## 使用memoryview模块
+精准地修改了一个数组的某个字节
+```python
+#memoryview内存视图
+#在不复制内容的情况下操作同一个数组的不同切片
+from array import array
+
+#5个短整型有符号整数的数组,类型码是h
+numbers = array('h', [-2, -1, 0, 1, 2])
+
+memv = memoryview(numbers)
+print(len(memv))
+
+#转成B类型,无符号字符
+memv_oct = memv.cast('B')
+tolist = memv_oct.tolist()
+print(tolist)
+
+memv_oct[5] = 4
+print(numbers)
+```
+
+***
+## bisect模块用法
+
+```python
+
+import bisect
+
+# 以空格作为分隔打印S中所有元素再换行.
+def print_all(S):
+ for x in S:
+ print(x, end = " ")
+ print("")
+
+# 有序向量SV.
+SV = [1, 3, 6, 6, 8, 9]
+
+#查找元素.
+key = int(input())
+print(bisect.bisect_left(SV, key))
+print(bisect.bisect_right(SV, key))
+
+# 插入新元素.
+key = 0
+bisect.insort_right(SV, key)
+
+# 删除重复元素的最后一个. 思考: 如何删除第一个?
+key = 6
+i = bisect.bisect_right(SV, key)
+i -= 1
+# 注意此时i < len(SV)必然成立. 如果确实有key这个元素则删除.
+if (i > 0 and SV[i] == key): del(SV[i])
+print_all(SV)
+
+# 删除重复key所在区间: [bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)).
+del SV[bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)]
+print_all(SV)
+
+# 无序向量USV.
+USV = [9, 6, 1, 3, 8, 6]
+
+# 插入新元素
+key = 0
+USV.append(key)
+
+# 删除重复元素的最后一个.
+key = 6
+# 逆向遍历, 如果存在则删除.
+i = len(USV) - 1
+while i > 0:
+ if USV[i] == key:
+ USV[i] = USV[-1]
+ USV.pop()
+ break
+ i -= 1
+print_all(USV)
+
+# 删除重复元素的第一个.
+try:
+ i = USV.index(key)
+except:
+ pass
+else:
+ USV[i] = USV[-1]
+ USV.pop()
+print_all(USV)
+```
+***
+##pickle模块用法
+pickle模块是将python值转成成byte
+```python
+try:
+ import cPickle as pickle
+except:
+ import pickle
+
+data = [{'a': 'A', 'b': 2, 'c': 3.0}]
+
+# 编码
+data_string = pickle.dumps(data)
+
+print("DATA:", data)
+print("PICKLE:", data_string)
+print(type(data_string))
+
+# 解码
+data_from_string = pickle.loads(data_string)
+print(data_from_string)
+
+```
+
+***
+## 双向队列
+双向队列deque
+
+```python
+import timeit
+from collections import deque
+
+
+def way1():
+ mylist = list(range(100000))
+ for i in range(100000):
+ mylist.pop()
+
+
+def way2():
+ mydeque = deque(range(100000))
+ for i in range(100000):
+ mydeque.pop()
+
+
+if __name__ == "__main__":
+ t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10)
+ print(t1)
+
+ t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10)
+ print(t2)
+
+```
+
+结果:
+```
+6e-07
+1.0000000000001327e-06
+```
+
+
+
+> **deque是双向队列,如果你的业务逻辑里面需要大量的从队列的头或者尾部删除,添加,用deque的性能会大幅提高!**
+
+> 如果只是小队列,并且对元素需要随机访问操作,那么list会快一些。
+
+
+```python
+# 双向队列用法
+
+from collections import deque
+
+# 创建一个队列
+q = deque([1])
+print(q)
+
+# 往队列中添加一个元素
+q.append(2)
+print(q)
+
+# 往队列最左边添加一个元素
+q.appendleft(3)
+print(q)
+
+# 同时入队多个元素
+q.extend([4, 5, 6])
+print(q)
+
+# 在最左边同时入队多个元素
+q.extendleft([7, 8, 9])
+print(q)
+
+# 剔除队列中最后一个
+q.pop()
+print(q)
+
+# 删除队列最左边的一个元素
+q.popleft()
+print(q)
+
+# 清空队列
+# q.clear()
+
+# 获取队列长度
+# length = q.maxlen
+# print(length)
+
+```
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md"
new file mode 100644
index 00000000..84ef729d
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md"
@@ -0,0 +1,159 @@
+# 第10章-序列的修改、散列和切片
+
+
+
+- [第10章-序列的修改、散列和切片](#%e7%ac%ac10%e7%ab%a0-%e5%ba%8f%e5%88%97%e7%9a%84%e4%bf%ae%e6%94%b9%e6%95%a3%e5%88%97%e5%92%8c%e5%88%87%e7%89%87)
+ - [反射](#%e5%8f%8d%e5%b0%84)
+ - [作用](#%e4%bd%9c%e7%94%a8)
+ - [Python反射方法](#python%e5%8f%8d%e5%b0%84%e6%96%b9%e6%b3%95)
+
+
+
+
+## 反射
+
+**反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力**,在python中一切皆对象(类,实例,模块等等都是对象),那么我们就可以通过反射的形式操作对象相关的属性。
+
+用字符串数据类型的变量名或者函数名来调用对应的属性
+A.b getattr(A, 'b')
+
+
+### 作用
+
+1.实现可插拔机制
+
+**可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用**,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
+
+
+举个例子:
+
+demo.py
+```python
+class FanShe(object):
+ x = 1
+
+ def test1(self):
+ print('this is test1')
+
+ def test(self):
+ print("this is test")
+
+```
+
+
+其他文件调用:
+```python
+import demo as tf
+
+c = tf.FanShe
+
+# 判断c对象中的test是否存在,存在即调用执行,不存在就执行其他逻辑代码
+if hasattr(c, 'test'):
+ func = getattr(c, 'test')
+ print(func("hello"))
+
+else:
+ print('不存在此方法')
+ print('处理其他的逻辑')
+```
+
+
+
+
+2.动态导入模块
+
+demo/`__init__.py`
+demo/work.py
+
+work.py
+
+```python
+def test():
+ print('this is test')
+```
+
+在外层想要调用这个work.py里面的test方法可以使用下面的方法:
+
+```python
+M = __import__('demo.work')
+print(M)
+M.work.test()
+
+# 可以写成这样
+import importlib
+
+M = importlib.import_module('demo.work')
+print(M)
+M.test()
+
+```
+
+### Python反射方法
+
+Python中的反射主要有下面几个方法:
+
+1.hasattr(object,name)
+判断对象中有没有一个name字符串对应的方法或属性
+
+2.getattr(object, name, default=None)
+获取对象name字符串属性的值,如果不存在返回default的值
+
+3.setattr(object, key, value)
+设置对象的key属性为value值,等同于object.key = value
+
+4.delattr(object, name)
+删除对象的name字符串属性
+
+
+```python
+class Cat(object):
+ class_level = '贵族'
+
+ def __init__(self, name, type, speed, age):
+ self.name = name
+ self.type = type
+ self.speed = speed
+ self.age = age
+
+ def run(self):
+ print('%s岁的%s%s正在以%s的速度奔跑' %
+ (self.age, self.type, self.name, self.speed))
+
+
+xiaohua = Cat('小花', '波斯猫', '10m/s', 10)
+xiaohua.run() # 10岁的波斯猫小花正在以10m/s的速度奔跑
+
+# hasattr(object, name) 判断对象中有没有一个name字符串对应的方法或属性
+print(hasattr(xiaohua, 'name')) # 判断xiaohua有没有name的属性,返回True,说明小花有name的属性,
+# 注意'name'一定要是字符串类型
+
+print(hasattr(xiaohua, 'size')) # 返回False说明小花没有size的属性
+
+# 2.getattr(object, name, default=None) 获取对象name字符串属性的值,
+# 如果不存在返回default的值
+print(getattr(xiaohua, 'speed', '20m/s')) # 10m/s
+
+# 获取xiaohua的speed的属性,speed的属性存在
+print(xiaohua.__dict__)
+# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10}
+
+print(getattr(xiaohua, 'weight', '5kg'))
+# 5kg, 获取xiaohua的weight属性,此属性不存在,返回default的值
+
+# print(getattr(xiaohua, 'weight')) # 不设置default时,属性不存在
+# 会报错 AttributeError: 'Cat' object has no attribute 'weigh'
+
+# 3.setattr(object, key, value)# 设置对象的key属性为value值
+# 等同于object.key = value
+
+setattr(xiaohua, 'weight', '5kg')
+# 给xiaohua增加weight的属性
+print(xiaohua.__dict__)
+# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10, 'weight': '5kg'}
+
+# 4.delattr(object, name) 删除对象的name字符串属性
+
+delattr(xiaohua, 'weight')
+print(xiaohua.__dict__)
+# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10}
+```
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md"
new file mode 100644
index 00000000..c01cda06
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md"
@@ -0,0 +1,383 @@
+# 第1章 序幕
+
+
+- [第1章 序幕](#%e7%ac%ac1%e7%ab%a0-%e5%ba%8f%e5%b9%95)
+ - [tuple和nametuple的区别](#tuple%e5%92%8cnametuple%e7%9a%84%e5%8c%ba%e5%88%ab)
+ - [__repr__和__str__的区别](#repr%e5%92%8cstr%e7%9a%84%e5%8c%ba%e5%88%ab)
+
+
+
+这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法。比如`__len__`,`__getitem__`. 并用一个纸牌的程序来讲解了这些方法
+
+## tuple和nametuple的区别
+>**Nametuple是类似于元组的数据类型。**
+
+>**除了能够用索引来访问数据,还支持用方便的属性名来访问数据。**
+
+传统的元组访问如下。
+```python
+tup1 = ('abc', 'def', 'ghi')
+print(tup1[1])
+```
+
+对每个元素的访问都必须通过索引来找到。这种找法很不直观
+
+使用nametuple来构造:
+```python
+import collections
+tup2 = collections.namedtuple('tuple2', ['name', 'age', 'height'])
+t1 = tup2('zhf', '33', '175')
+print(t1)
+print(t1.age)
+print(t1.height)
+print(t1.name)
+```
+
+得到结果如下,namedtupel中tuple2是类型名,name,age,height是属性名字从上面的访问可以看到,直接用t1.age的方法访问更加直观。
+
+当然也可以用索引比如t1[0]的方法来访问
+
+程序执行返回结果如下:
+```python
+tuple2(name='zhf', age='33', height='175')
+33
+175
+zhf
+```
+
+
+namedtupe1也支持迭代访问:
+```python
+for t in t1:
+ print(t)
+```
+
+
+
+**`和元组一样,namedtupel中的元素也是不可变更的`**
+
+如果执行t1.age+=1。将会提示无法设置元素
+
+```python
+ t1.age += 1
+AttributeError: can't set attribute
+```
+
+下面来看下书中的纸牌例子,代码如下
+```python
+from collections import namedtuple
+Card = namedtuple('Card', ['rank', 'suit'])
+class FrenchDeck(object):
+ ranks = [str(n) for n in range(2, 11)] + list('JQKA')
+ suits = 'spades diamonds clubs hearts'.split()
+ def __init__(self):
+ self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
+ def __len__(self):
+ return len(self._cards)
+ def __getitem__(self, position):
+ return self._cards[position]
+if __name__ == '__main__':
+ deck = FrenchDeck()
+ # 如果类中有一个__len__方法, len函数首先就会调用类里面的方法
+ print(len(deck))
+ print(deck[1])
+```
+
+首先定义了的纸牌元组Card, rank代表纸牌数字,suit代表纸牌花色。
+然后在FrenchDeck首先定义了ranks和suit的具体值。
+在`__init__`中对self._cards进行初始化。
+`__len__`反馈self._cards的长度。`__getitem__`反馈具体的纸牌值。
+
+结果如下:
+纸牌的长度为52,其中deck[1]为Card(rank=’3’,suit=’spades’)
+
+可以看到len(deck)其实调用的是`__len__`方法。
+deck[1]调用的是`__getitem__`由于有了`__getitem__`方法,
+
+还可以进行迭代访问,如下:
+```python
+for d in deck:
+ print (d)
+```
+
+既然是可迭代的,那么我们可以模拟随机发牌的机制。
+
+```python
+from random import choice
+ print (choice(deck))
+```
+>随机抽取,使用random.choice标准库
+`要看看choice底层实现原理,搞明白机制`
+
+
+接下来看另外一个例子,关于向量运算的。
+比如有向量1 vector1(1,2),向量2 vector2(3,4)。
+那么vector1+vector2的结果应该是(4,6)。
+
+Vector1和vector2都是向量,如何实现运算呢。`方法是__add__,__mul__`
+
+代码如下:
+
+```python
+from math import hypot
+class vector(object):
+ def __init__(self, x=0, y=0):
+ self.x = x
+ self.y = y
+ def __repr__(self):
+ return 'Vector(%r,%r)' % (self.x, self.y)
+ def __abs__(self):
+ return hypot(self.x, self.y)
+ def __bool__(self):
+ return bool(abs(self))
+ def __add__(self, other):
+ x = self.x + other.x
+ y = self.y + other.y
+ return vector(x, y)
+ def __mul__(self, scalar):
+ return vector(self.x * scalar, self.y * scalar)
+if __name__ == '__main__':
+ v1 = vector(1, 2)
+ v2 = vector(2, 3)
+ print(v1 + v2)
+ print(abs(v1))
+ print(v1 * 3)
+
+```
+返回结果:
+```
+Vector(3,5)
+2.23606797749979
+Vector(3,6)
+```
+
+
+
+在这里`__add__,_ _mul_ __,__abs__`分别实现了向量加法,乘法,以及求模的运算。
+
+值得一提的是`__repr__`的方法。
+
+这个方法是在需要打印对象的时候调用。
+
+例如print vector(1,2)的时候得到vector(1,2).
+
+否则就是表示对象的字符串:
+
+## `__repr__和__str__`的区别
+
+主要区别如下:
+
+* **`区别1`**
+> 如果_`_str__ 存在会首先调用__str__ `
+> 如果`__str__ 不存在会调用__repr__`
+
+测试代码如下:
+``` python
+
+class DemoClass(object):
+ def __repr__(self):
+ return 'DemoClass repr'
+
+ def __str__(self):
+ return 'DemoClass str'
+
+
+demo = DemoClass()
+print(demo) #返回是:DemoClass str
+```
+
+```python
+class DemoClass(object):
+ def __repr__(self):
+ return 'DemoClass repr'
+
+
+demo = DemoClass()
+print(demo) # DemoClass repr
+
+```
+
+
+* **`区别2`**
+```python
+class DemoClass(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ # %r 字符串 (采用repr()的显示)
+ return "repr My name is %r" % (self.name)
+
+ def __str__(self):
+ return "str My name is %s" % (self.name)
+
+
+demo = DemoClass("tony")
+print(demo) # 返回"str My name is tony"
+
+# 使用repr(obj)的时候,会自动调用__repr__函数
+print(repr(demo)) # 返回"repr My name is tony"
+
+demo1 = DemoClass("'tony1")
+print(demo1) # str My name is 'tony1
+print(repr(demo1)) # repr My name is "'tony1"
+
+```
+
+ 可以看到有一些区别的
+> `__repr__`所返回的字符串应该准确、无歧义,并且尽可能表达出如何用代码创建出这个被打印的对象。
+
+> `__repr__和__str__`的区别在于,后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。
+
+
+再比如下面一个例子:
+
+``` python
+class DemoClass(object):
+ def __init__(self, name):
+ self.name = name
+
+ def say(self):
+ print(self.name)
+
+ def __repr__(self):
+ return "DemoClass('jacky')"
+
+ def __str__(self):
+ return "str My name is %s" % (self.name)
+
+
+demo = DemoClass("tony")
+demo.say() # tony
+
+demo_repr = type(repr(demo))
+print(demo_repr) #
+
+* **`区别3`**
+> **这里额外比较repr和str函数的区别**
+
+在 Python 中要将某一类型的变量或者常量转换为字符串对象通常有两种方法,即str() 或者 repr()
+```python
+>>> a = 10
+>>> type(str(a))
+
+不过这个例子可能还是无法很好表达到底 str() 与 repr() 各有什么意义
+我们再来看一个例子。
+
+```python
+>>> from datetime import datetime
+>>> now = datetime.now()
+>>> print(str(now))
+2017-04-22 15:41:33.012917
+>>> print(repr(now))
+datetime.datetime(2017, 4, 22, 15, 41, 33, 12917)
+```
+通过 str() 的输出结果我们能很好地知道 now 实例的内容,
+但是却丢失了 now 实例的数据类型信息。
+
+而通过 repr() 的输出结果我们不仅能获得 now 实例的内容,
+还能知道 now 是 datetime.datetime 对象的实例。
+
+
+再比如:
+```python
+
+string = "Hello\tWill\n"
+print(str(string))
+print(repr(string))
+```
+
+结果:
+
+```python
+Hello Will
+'Hello\tWill\n'
+```
+
+
+再比如:
+```python
+
+a = "哈哈"
+
+print(str(a))
+print(repr(a))
+
+# 返回结果
+"""
+哈哈
+'哈哈'
+"""
+```
+
+
+
+
+因此 str() 与 repr() 的不同在于:
+* **str() 的输出追求可读性,输出格式要便于理解,适合用于输出内容到用户终端。**
+* **repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用。**
+
+
+
+另外如果想要自定义类的实例能够被 str() 和 repr() 所调用,
+那么就需要在自定义类中重载` __str__ 和 __repr__` 方法。
+
+
+
+[^1]: `__repr__和__str__`的区别
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md"
new file mode 100644
index 00000000..b9e9ec3d
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md"
@@ -0,0 +1,1231 @@
+# 第7章-扩展内容
+
+
+
+- [第7章-扩展内容](#第7章-扩展内容)
+ - [python面向对象](#python面向对象)
+ - [基础篇](#基础篇)
+ - [三种编程方式](#三种编程方式)
+ - [创建类和对象](#创建类和对象)
+ - [面向对象三大特性](#面向对象三大特性)
+ - [封装](#封装)
+ - [继承](#继承)
+ - [新式类与经典类](#新式类与经典类)
+ - [多态](#多态)
+ - [进阶篇](#进阶篇)
+ - [类的成员](#类的成员)
+ - [字段](#字段)
+ - [方法](#方法)
+ - [属性](#属性)
+ - [属性的两种定义方式](#属性的两种定义方式)
+ - [公有成员和私有成员](#公有成员和私有成员)
+ - [特殊的类的成员](#特殊的类的成员)
+ - [`__module__ 和 __class__`](#__module__-和-__class__)
+ - [`__init__`](#__init__)
+ - [`__del__`](#__del__)
+ - [`__call__`](#__call__)
+ - [`__dict__`](#__dict__)
+ - [` __getitem__&__setitem__&__delitem__`](#-__getitem____setitem____delitem__)
+ - [` __getslice__&__setslice__&__delslice__`](#-__getslice____setslice____delslice__)
+ - [` __iter__`](#-__iter__)
+ - [` __new__`](#-__new__)
+
+
+## python面向对象
+
+### 基础篇
+
+#### 三种编程方式
+
+>面向过程:根据业务逻辑从上到下写垒代码
+函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
+面向对象:对函数进行分类和封装,让开发“更快更好更强…”
+
+
+面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。
+
+```python
+while True:
+ if cpu利用率 > 90%:
+ # 发送邮件提醒
+ 连接邮箱服务器
+ 发送邮件
+ 关闭连接
+
+ if 硬盘使用空间 > 90%:
+ # 发送邮件提醒
+ 连接邮箱服务器
+ 发送邮件
+ 关闭连接
+
+ if 内存占用 > 80%:
+ # 发送邮件提醒
+ 连接邮箱服务器
+ 发送邮件
+ 关闭连接
+```
+
+随着时间的推移,开始使用了函数式编程,增强代码的重用性和可读性,就变成了这样:
+```python
+def 发送邮件(内容)
+ #发送邮件提醒
+ 连接邮箱服务器
+ 发送邮件
+ 关闭连接
+
+while True:
+
+ if cpu利用率 > 90%:
+ 发送邮件('CPU报警')
+
+ if 硬盘使用空间 > 90%:
+ 发送邮件('硬盘报警')
+
+ if 内存占用 > 80%:
+ 发送邮件('内存报警')
+```
+
+今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)。
+
+#### 创建类和对象
+面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,
+所以面向对象编程其实就是对 “类” 和 “对象” 的使用。
+
+* 类就是一个模板,模板里可以包含多个函数,函数里实现一些功能
+* 对象则是根据模板创建的实例,通过实例对象可以执行类中的函数
+
+
+```python
+# 创建Test类,python3 初始类都继承object类
+class Test(object):
+ # 类中定义的函数也叫方法
+ def demo(self): # 类中的函数第一个参数必须是self
+ print("self=", self)
+ print("Method=", dir(self))
+ print("Hello")
+
+
+# 根据类Test创建对象obj
+obj = Test() # 类名加括号
+obj.demo() # 执行demo方法
+
+# 获取一个实例的类名
+print(obj.__class__.__name__)
+
+# 判断一个对象是否拥有某个属性
+if hasattr(obj, 'demo'):
+ print("it has demo method")
+else:
+ print("have no method")
+```
+
+返回结果:
+```
+self= <__main__.Test object at 0x00000224F3539A58>
+Method= ['__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__', 'demo']
+Hello
+```
+
+
+结论:
+
+>方法的第一个参数是调用这个方法的实例对象本身.
+>从上面结果可以看出来self对于代码不是特殊的,只是另一个对象.
+
+
+
+
+#### 面向对象三大特性
+
+##### 封装
+
+封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
+所以,在使用面向对象的封装特性时,需要:
+
+* 将内容封装到某处
+* 从某处调用被封装的内容
+
+```python
+# 1. 将内容封装到某处
+class Test(object):
+ # __init__方法成为构造方法,根据类创建对象时自动执行
+ def __init__(self, name):
+ self.name = name
+
+ def detail(self):
+ print(self.name)
+
+
+# 自动执行__init__方法
+# 将tony封装到obj self的name属性中
+# 当执行obj,self等于obj
+obj = Test('tony')
+
+# 2.从某处调用封装的内容
+# 通过对象直接调用, 格式:对象.属性名
+print(obj.name)
+
+# 通过self调用
+# python默认会将obj传给self参数,即obj.detail(obj)
+# 此时方法内部的self=obj,所以 self.name='tony'
+print(obj.detail())
+
+```
+
+
+综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
+
+
+##### 继承
+继承,面向对象中的继承和现实生活中的继承相同,即:**子可以继承父的内容**。
+
+例如:
+猫可以:喵喵叫、吃、喝、拉、撒
+狗可以:汪汪叫、吃、喝、拉、撒
+
+如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能
+
+```python
+class 猫:
+ def 喵喵叫(self):
+ print '喵喵叫'
+ def 吃(self):
+ # do something
+ def 喝(self):
+ # do something
+ def 拉(self):
+ # do something
+ def 撒(self):
+ # do something
+
+
+class 狗:
+ def 汪汪叫(self):
+ print '喵喵叫'
+ def 吃(self):
+ # do something
+ def 喝(self):
+ # do something
+ def 拉(self):
+ # do something
+ def 撒(self):
+ # do something
+```
+
+上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
+
+动物:吃、喝、拉、撒
+猫:喵喵叫(猫继承动物的功能)
+狗:汪汪叫(狗继承动物的功能)
+
+```python
+class 动物:
+
+ def 吃(self):
+ # do something
+
+ def 喝(self):
+ # do something
+
+ def 拉(self):
+ # do something
+
+ def 撒(self):
+ # do something
+
+# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
+class 猫(动物):
+
+ def 喵喵叫(self):
+ print '喵喵叫'
+
+# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
+class 狗(动物):
+
+ def 汪汪叫(self):
+ print '喵喵叫'
+```
+
+所以,对于面向对象的继承来说,**其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。**
+
+```python
+class Animal(object):
+ def eat(self):
+ print("%s 吃 " % self.name)
+
+ def drink(self):
+ print("%s 喝 " % self.name)
+
+ def shit(self):
+ print("%s 拉 " % self.name)
+
+ def pee(self):
+ print("%s 撒 " % self.name)
+
+
+class Cat(Animal):
+ def __init__(self, name):
+ self.name = name
+ self.breed = '猫'
+
+ def cry(self):
+ print('喵喵叫')
+
+
+class Dog(Animal):
+ def __init__(self, name):
+ self.name = name
+ self.breed = '狗'
+
+ def cry(self):
+ print('汪汪叫')
+
+
+c1 = Cat('小白家的小黑猫')
+c1.eat()
+
+c2 = Cat('小黑的小白猫')
+c2.drink()
+
+d1 = Dog('胖子家的小瘦狗')
+d1.eat()
+```
+
+那么问题又来了,多继承呢?
+* 是否可以继承多个类
+* 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
+
+
+1、Python的类可以继承多个类,Java和C#中则只能继承一个类
+2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
+
+###### 新式类与经典类
+注意:
+
+当类是经典类时,多继承情况下,会按照**深度优先**方式查找 -`找到第一个`
+
+当类是新式类时,多继承情况下,会按照**广度优先**方式查找 -`找到最后一个`
+
+
+经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法
+
+从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
+
+经典类 -python2.7测试
+```python
+class D:
+ def bar(self):
+ print 'D.bar'
+
+
+class C(D):
+ def bar(self):
+ print 'C.bar'
+
+
+class B(D):
+ def bar(self):
+ print 'hhhhh'
+ print 'B.bar'
+
+
+class A(B, C):
+ pass
+ # def bar(self):
+ # print 'A.bar'
+
+
+a = A()
+a.bar()
+
+print A.__mro__ # 报错 ,__mro__只对新式类有效
+
+```
+
+执行bar方法时
+首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
+
+所以,查找顺序:**A --> B --> D --> C**
+
+在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
+
+
+新式类-python3.6+
+```python
+ class D(object):
+ def bar(self):
+ print('D.bar')
+
+
+class C(D):
+ def bar(self):
+ print('C.bar')
+
+
+class B(D):
+ def bar(self):
+ print('B.bar')
+
+
+class A(B, C):
+ pass
+ # print('A.bar')
+
+
+a = A()
+a.bar()
+
+# 方法解析顺序Method Resolution Order - MRO
+print(A.__mro__)
+# (
+
+###### 属性的两种定义方式
+
+属性的定义有两种方式:
+
+* 装饰器 即:在方法上应用装饰器
+* 静态字段 即:在类中定义值为property对象的静态字段
+
+装饰器方式:在类的普通方法上应用@property装饰器
+
+静态字段方式,创建值为property对象的静态字段
+当使用静态字段的方式创建属性时,经典类和新式类无区别
+
+```python
+class Foo(object):
+ def get_bar(self):
+ return 'hello'
+
+ BAR = property(get_bar)
+
+
+obj = Foo()
+reuslt = obj.BAR # 自动调用get_bar方法,并获取方法的返回值
+print(reuslt)
+
+```
+
+
+property的构造方法中有个四个参数
+>* 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
+>* 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
+>* 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
+>* 第四个参数是字符串,调用 对象.属性.`__doc__` ,此参数是该属性的描述信息
+
+
+```python
+class Foo(object):
+ def get_bar(self):
+ return 'hello'
+
+ # 必须两个参数
+ def set_bar(self, value):
+ return 'set value' + value
+
+ def del_bar(self):
+ return 'hello'
+
+ BAR = property(get_bar, set_bar, del_bar, 'description...')
+
+
+obj = Foo()
+
+print(obj.BAR) # 自动调用第一个参数中定义的方法:get_bar
+obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
+print(obj.BAR)
+
+print(obj.BAR.__doc__) # 自动获取第四个参数中设置的值:description...
+del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法
+
+```
+
+实例属性和类属性关系
+
+记住:
+>**实例属性不能左右类属性**
+
+>**但是类属性可以左右实例属性**
+
+```python
+class A(object):
+ x = 7
+
+
+foo = A()
+foo.x += 7
+print(A.x) # 7
+print(foo.x) # 14
+
+print("####")
+A.x = A.x + 1
+print(A.x) # 8
+print(foo.x) # 14
+
+
+```
+
+`也有特例`:
+```python
+class B(object):
+ y = [1, 2, 3]
+
+
+bar = B()
+bar.y.append(4)
+
+print(bar.y) # [1, 2, 3, 4]
+print(B.y) # [1, 2, 3, 4]
+
+```
+
+
+
+##### 公有成员和私有成员
+
+
+>* `公有成员,在任何地方都能访问`
+>* `私有成员,只有在类的内部才能方法`
+
+
+私有成员和公有成员的定义不同:**私有成员命名时,前两个字符是下划线**。
+(特殊成员除外,例如:`__init__、__call__、__dict__`等)
+
+
+```python
+class C(object):
+ def __init__(self):
+ self.name = '公有字段'
+ self.__foo = "私有字段"
+```
+
+
+
+1. 静态字段
+
+>* `公有静态字段:类可以访问;类内部可以访问;派生类中可以访问`
+
+>* `私有静态字段:仅类内部可以访问`
+
+```python
+class C(object):
+
+ name = "公有静态字段"
+
+ def func(self):
+ print(C.name)
+
+
+class D(C):
+ def show(self):
+ print(C.name)
+
+
+C.name # 类访问
+
+obj = C()
+obj.func() # 类内部可以访问
+
+obj_son = D()
+obj_son.show() # 派生类中可以访问
+
+```
+
+
+```python
+class C(object):
+
+ __name = "公有静态字段"
+
+ def func(self):
+ print(C.__name)
+
+
+class D(C):
+ def show(self):
+ print(C.__name)
+
+
+C.__name # 类访问 ==> 错误
+
+obj = C()
+obj.func() # 类内部可以访问 ==> 正确
+
+obj_son = D()
+obj_son.show() # 派生类中可以访问 ==> 错误
+
+```
+
+
+2.普通字段
+
+>* **`公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问`**
+>* **`私有普通字段:仅类内部可以访问`**
+
+
+```python
+class C(object):
+ def __init__(self):
+ self.foo = "公有字段"
+
+ def func(self):
+ print(self.foo) # 类内部访问
+
+
+class D(C):
+ def show(self):
+ print(self.foo) # 派生类中访问
+
+
+obj = C()
+
+obj.foo # 通过对象访问
+obj.func() # 类内部访问
+
+obj_son = D()
+obj_son.show() # 派生类中访问
+
+```
+
+```python
+class C:
+ def __init__(self):
+ self.__foo = "私有字段"
+
+ def func(self):
+ print(self.foo) # 类内部访问
+
+ def __doSomething(self):
+ print("do something")
+
+
+class D(C):
+ def show(self):
+ print(self.foo) # 派生类中访问
+
+
+obj = C()
+
+obj.__foo # 通过对象访问 ==> 错误
+obj.func() # 类内部访问 ==> 正确
+
+obj_son = D()
+obj_son.show() # 派生类中访问 ==> 错误
+
+# 强制访问私有字段-不建议这样使用
+print(obj._C__foo)
+
+# 访问私有方法
+print(obj._C__doSomething())
+
+```
+
+
+
+总结:
+
+> 在python里,标识符有字母、数字、下划线组成。
+
+>在python中,所有标识符可以包括英文、数字以及下划线(_),但不能以数字开头。
+>python中的标识符是**区分大小写**的。
+
+>以下划线开头的标识符是有特殊意义的。以**单下划线开头(`_foo`)**的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用"from xxx import *"而导入;
+
+>以双下划线开头的(`__foo`)代表类的私有成员
+
+>以双下划线开头和结尾的(`__foo__`)代表python里特殊方法专用的标识,如`__init__()`代表类的构造函数。
+
+
+#### 特殊的类的成员
+
+
+##### `__module__ 和 __class__`
+
+
+>`__module__` 表示当前操作的对象在那个模块
+>`__class__` 表示当前操作的对象的类是什么
+
+
+demo.py 单独访问:
+可以看到`__module__`表示的main模块,`__class__`表示main模块中的Test类
+
+```python
+class Test(object):
+ def __init__(self):
+ self.name = "tony"
+
+
+obj = Test()
+print(obj.__module__) # __main__
+print(obj.__class__) #
+被其他文件引用:
+可以看到`__module__`表示的demo模块,`__class__`表示demo模块中的Test类
+
+```python
+from demo import Test
+
+obj = Test()
+print(obj.__module__) # demo
+
+print(obj.__class__) #
这里要`注意`:
+```python
+class Test(object):
+ def __init__(self, name):
+ self.name = name
+ # return "111" # 问题:能不能给它加个返回值?
+ # 默认的返回值
+ # <__main__.Test object at 0x000001E27BA18518>
+
+
+test = Test("tony") # 自动执行类中的__init__方法
+print(test)
+# TypeError: __init__() should return None, not 'str'
+
+```
+
+
+
+##### `__del__`
+
+析构方法,当对象在内存中被释放时,自动触发执行。
+
+>此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行
+
+>所以,**析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。**
+
+```python
+class Test(object):
+ def __del__(self):
+ print("I am deleted...")
+
+
+test = Test()
+del test # I am deleted...
+
+```
+
+
+
+##### `__call__`
+
+
+对象后面加括号,触发执行。
+
+>构造方法的执行是由创建对象触发的,即:对象 = 类名()
+而对于` __call__ `方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
+
+```python
+class Foo(object):
+ def __init__(self):
+ print('init')
+
+ def __call__(self):
+ print('call')
+
+
+obj = Foo()
+print(obj) # 执行__init__方法
+# init
+# <__main__.Foo object at 0x000001EC6ACC8470>
+
+print(obj()) # 执行__call__方法
+
+```
+
+
+
+##### `__dict__`
+
+类或对象中的所有成员
+
+```python
+class Province:
+
+ country = 'China'
+
+ def __init__(self, name, count):
+ self.name = name
+ self.count = count
+
+ def func(self, *args, **kwargs):
+ print('func')
+
+
+# 获取类的成员,即:静态字段、方法、
+print(Province.__dict__)
+
+obj1 = Province('HeBei', 10000)
+print(obj1.__dict__)
+# 获取 对象obj1 的成员
+# 输出:{'count': 10000, 'name': 'HeBei'}
+
+obj2 = Province('HeNan', 3888)
+print(obj2.__dict__)
+# 获取 对象obj2 的成员
+# 输出:{'count': 3888, 'name': 'HeNan'}
+```
+
+
+
+##### ` __getitem__&__setitem__&__delitem__`
+
+用于索引操作,如字典。以上分别表示获取、设置、删除数据
+
+
+```python
+class Foo(object):
+ def __getitem__(self, key):
+ print('__getitem__', key)
+
+ def __setitem__(self, key, value):
+ print('__setitem__', key, value)
+
+ def __delitem__(self, key):
+ print('__delitem__', key)
+
+
+obj = Foo()
+
+result = obj['k1'] # 自动触发执行 __getitem__
+obj['k1'] = 'tony' # 自动触发执行 __setitem__
+del obj['k1'] # 自动触发执行 __delitem__
+
+print(obj['k1']) # 自动触发执行 __getitem__
+
+```
+
+
+
+##### ` __getslice__&__setslice__&__delslice__`
+
+该三个方法用于分片操作,如:列表
+
+ **`仅限于python2.6+`**
+~~python3已废弃这些方法~~
+
+python3 已经变成`__getitem__`&`__setitem__`&`__delitem__`
+
+
+
+##### ` __iter__`
+
+用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了` __iter__`
+
+```python
+class Foo(object):
+ pass
+
+
+# obj = Foo()
+
+# for i in obj:
+# print(i) # TypeError: 'Foo' object is not iterable
+
+
+class FooX(object):
+ def __iter__(self):
+ pass
+
+
+objx = FooX()
+
+# for i in objx:
+# print(i) # TypeError: iter() returned non-iterator of type 'NoneType'
+
+
+class FooY(object):
+ def __init__(self, sq):
+ self.sq = sq
+
+ def __iter__(self):
+ return iter(self.sq)
+
+
+objy = FooY([11, 22, 33, 44])
+
+for i in objy:
+ print(i)
+
+# 11
+# 22
+# 33
+# 44
+
+# 也可也写成
+bj = iter([11, 22, 33, 44])
+
+for i in bj:
+ print(i)
+
+```
+
+
+
+##### ` __new__`
+
+`__new__()`: 构造方法
+
+
+**1.如果有`__new__`和`__init__`都存在, 优先找`__new__`**
+
+
+这两个方法是用来创建object的子类对象,静态方法`__new__()`用来**创建类的实例**,
+
+然后再调用`__init__()`来**初始化实例**。
+
+```python
+class A(object):
+ def __init__(self, *args, **kwargs):
+ print("init %s" % self.__class__)
+
+ def __new__(cls, *args, **kwargs):
+ print("new %s" % cls)
+ return object.__new__(cls, *args, **kwargs)
+
+
+a = A()
+a
+
+# new
+
+**`2.其他区别`**
+
+`__new__`方法默认返回实例对象供`__init__`方法、实例方法使用。
+
+```python
+class Foo(object):
+ price = 50
+
+ def __new__(cls, *agrs, **kwds):
+ inst = object.__new__(cls, *agrs, **kwds)
+ return inst
+
+ def how_much_of_book(self, n):
+ print(self)
+ return self.price * n
+
+
+foo = Foo()
+print(foo.how_much_of_book(8))
+# Foo类中重载了__new__方法,它的返回值为Foo类的实例对象
+# <__main__.Foo object at 0x0000017BF8678630>
+# 400
+```
+
+
+`__init__ `方法为初始化方法,为类的实例提供一些属性或完成一些动作
+
+```python
+class Foo(object):
+ def __new__(cls, *agrs, **kwds):
+ inst = object.__new__(cls, *agrs, **kwds)
+ return inst
+
+ def __init__(self, price=50):
+ self.price = price
+
+ def how_much_of_book(self, n):
+ print(self)
+ return self.price * n
+
+
+foo = Foo()
+print(foo.how_much_of_book(8))
+```
+
+
+
+**3.几点注意事项**
+
+`__new__ `方法创建实例对象供`__init__ `方法使用,`__init__`方法定制实例对象。
+
+`__new__` 方法必须返回值,`__init__`方法不需要返回值。(如果返回非None值就报错)
+
+
+**4. 一般用不上`__new__`方法**
+
+`__new__`方法可以用在下面二种情况。
+
+1> 继承不可变数据类型时需要用到`__new__`方法(like int, str, or tuple) 。
+
+```python
+class Inch(float):
+ def __new__(cls, arg=0.0):
+ return float.__new__(cls, arg * 0.0254)
+
+
+print(Inch(12))
+```
+
+
+2>用在元类,定制创建类对象
+
+元类:
+
+当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
+
+但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
+
+**连接起来就是:先定义metaclass,就可以创建类,最后创建实例。**
+
+```python
+# metaclass是类的模板,所以必须从`type`类型派生:
+class MetaClass(type):
+ """
+ 参数必须是这四个
+ cls:当前准备创建的类
+ name:类的名字
+ bases:类的父类集合
+ attrs:类的属性和方法,是一个字典
+ """
+ def __new__(cls, name, bases, attrs):
+ print("Allocating memory for class", name)
+ print("new cls=", cls)
+ print("new name=", name)
+ print("new bases=", bases)
+ print("new attrs=", attrs)
+ return super(MetaClass, cls).__new__(cls, name, bases, attrs)
+
+ def __init__(cls, name, bases, attrs):
+ print("Initializing class", name)
+ print("int cls=", cls)
+ print("int name=", name)
+ print("int bases=", bases)
+ print("int attrs=", attrs)
+ super(MetaClass, cls).__init__(name, bases, attrs)
+
+
+class Myclass(metaclass=MetaClass):
+ def foo(self, param):
+ print(param)
+
+
+p = Myclass()
+p.foo("hello")
+
+```
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md"
new file mode 100644
index 00000000..76d74786
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md"
@@ -0,0 +1,240 @@
+# 第11章-抽象类
+
+
+
+
+- [第11章-抽象类](#%e7%ac%ac11%e7%ab%a0-%e6%8a%bd%e8%b1%a1%e7%b1%bb)
+ - [ABC类](#abc%e7%b1%bb)
+
+
+## ABC类
+
+python中并没有提供抽象类与抽象方法,
+但是提供了内置模块**abc(abstract base class)**来模拟实现抽象类。
+
+可以通过abc将基类声明为抽象类的方式,然后注册具体类作为这个基类的实现。
+
+
+假设我们在写一个关于动物的代码。涉及到的动物有鸟,狗,牛。
+
+首先鸟,狗,牛都是属于动物的。既然是动物那么肯定需要吃饭,发出声音。
+
+但是具体到鸟,狗,牛来说吃饭和声音肯定是不同的。
+
+需要具体去实现鸟,狗,牛吃饭和声音的代码。
+
+概括一下抽象基类的作用:定义一些共同事物的规则和行为
+
+
+例子:
+
+```python
+class Animal(object):
+ def eat(self):
+ raise NotImplementedError
+
+ def voice(self):
+ raise NotImplementedError
+
+
+class Dog(Animal):
+ def voice(self):
+ print('wow....')
+
+
+class Bird(Animal):
+ def voice(self):
+ print('jiji....')
+
+
+class Cow(Animal):
+ def voice(self):
+ print('Oh.....')
+
+
+if __name__ == "__main__":
+
+ d = Dog()
+ d.voice()
+ d.eat() # raise NotImplementedError
+
+```
+
+这样实现有个缺点,就是只有子类调用eat方法的时候才会报错。
+
+子类是可以正常实例化的。
+
+但是你能够想象鸟,狗,牛不会吃饭么? 如果不会吃饭那肯定不算是动物了。
+
+所以正常的实现应该是如果没有实现eat方法,实例化就应该是失败的。
+
+那么这里就要用到抽象基类的一般使用方法.
+
+
+```python
+import abc
+
+
+class Animal(object):
+
+ __metaclass__ = abc.ABCMeta
+
+ @abc.abstractmethod
+ def eat(self):
+ return
+
+ @abc.abstractmethod
+ def voice(self):
+ return
+
+
+class Dog(Animal):
+ def voice(self):
+ print('wow....')
+
+ def eat(self):
+ print("Dog eat...")
+
+
+class Bird(Animal):
+ def voice(self):
+ print('jiji....')
+
+ def eat(self):
+ print("Bird eat...")
+
+
+class Cow(Animal):
+ def voice(self):
+ print('Oh.....')
+
+ def eat(self):
+ print("Cow eat...")
+
+
+if __name__ == "__main__":
+
+ d = Dog()
+ b = Bird()
+ c = Cow()
+
+ d.voice()
+ d.eat()
+
+ b.voice()
+ b.eat()
+
+ c.voice()
+ c.eat()
+
+```
+
+返回结果:
+```
+wow....
+Dog eat...
+jiji....
+Bird eat...
+Oh.....
+Cow eat...
+```
+
+
+使用注册的方式:
+
+```python
+from abc import ABCMeta, abstractmethod
+
+
+class Animal(metaclass=ABCMeta):
+ @abstractmethod
+ def eat(self):
+ return
+
+ @abstractmethod
+ def voice(self):
+ return
+
+
+class Dog(object):
+ def voice(self):
+ print('wow....')
+
+ def eat(self):
+ print("Dog eat...")
+
+
+# 使用注册方式,必须两个方法都要写,否则会报错
+Animal.register(Dog)
+
+if __name__ == "__main__":
+
+ d = Dog()
+
+ d.voice()
+ d.eat()
+
+```
+
+
+
+**`继承和注册这两种方法有什么区别呢?`**
+
+
+animals.py
+```python
+from abc import ABCMeta, abstractmethod
+
+
+class Animal(metaclass=ABCMeta):
+ @abstractmethod
+ def eat(self):
+ return
+
+ @abstractmethod
+ def voice(self):
+ return
+
+```
+
+sub_class.py
+```python
+from animals import Animal
+
+
+class Dog(Animal):
+ def voice(self):
+ print('wow....')
+
+ def eat(self):
+ print("Dog eat...")
+
+
+print('subclass=', issubclass(Dog, Animal))
+print('Instance=', isinstance(Dog(), Animal))
+
+```
+
+abc_register.py
+```python
+from animals import Animal
+
+
+class Dog(object):
+ def voice(self):
+ print('wow....')
+
+ def eat(self):
+ print("Dog eat...")
+
+
+Animal.register(Dog)
+
+print('subclass=', issubclass(Dog, Animal))
+print('Instance=', isinstance(Dog(), Animal))
+```
+
+
+
+
+
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md"
new file mode 100644
index 00000000..23169388
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md"
@@ -0,0 +1,317 @@
+# 第4章 文本和字节序列
+
+
+
+- [第4章 文本和字节序列](#%e7%ac%ac4%e7%ab%a0-%e6%96%87%e6%9c%ac%e5%92%8c%e5%ad%97%e8%8a%82%e5%ba%8f%e5%88%97)
+ - [编码和解码](#%e7%bc%96%e7%a0%81%e5%92%8c%e8%a7%a3%e7%a0%81)
+ - [chardet模块](#chardet%e6%a8%a1%e5%9d%97)
+ - [json与字典区别](#json%e4%b8%8e%e5%ad%97%e5%85%b8%e5%8c%ba%e5%88%ab)
+ - [json模块和simplejson模块](#json%e6%a8%a1%e5%9d%97%e5%92%8csimplejson%e6%a8%a1%e5%9d%97)
+
+
+
+## 编码和解码
+
+> markdom可以插入emoji表情包
+其中作为前缀,x1f54为对应表情的unicode编码
+🥺
+[emoji](https://unicode.org/Public/emoji/12.1/emoji-data.txt)
+
+
+把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码
+
+> decode的作用是将其他编码的字符串转换成unicode编码
+> 如str1.decode('gb2312'),表示将gb2312编码的字符串转换成unicode编码。
+>
+> encode的作用是将unicode编码转换成其他编码的字符串
+> 如str2.encode('gb2312'),表示将unicode编码的字符串转换成gb2312编码。
+
+
+**`Python 3默认使用UTF-8编码源码`,Python 2(从2.5开始)则默认使用ASCII。**
+如果加载的.py模块中包含UTF-8之外的数据,而且没有声明编码,会报错
+
+```sequence
+title: python 2 编解码过程
+编码前其他编码字符串->unicode: 编码 decode
+unicode->>编码后其他编码字符串:解码 encode
+```
+
+>**在新版本的python3中,取消了unicode类型,代替它的是使用unicode字符的字符串类型(str),字符串类型(str)成为基础类型**
+
+如下所示,而编码后的变为了字节类型(bytes)
+
+```sequence
+title: python 3 编解码过程
+
+编码前其他编码字符串->str(unicode): 编码 decode
+str(unicode)->>编码后其他编码字符串:解码 encode
+```
+
+
+```python
+# 指定字符串类型对象u
+u = "中文"
+print(u, type(u))
+
+# 以gb2312编码对u进行编码,获得bytes类型对象str
+str = u.encode('gb2312')
+print(str, type(str))
+
+# 以gb2312编码对字符串str进行解码,获得字符串类型对象u1
+u1 = str.decode('gb2312')
+print(u1, type(u1))
+```
+
+返回结果:
+```
+中文
+
+## chardet模块
+
+> **chardet模块检测读取出来的str是什么编码格式的**
+
+```python
+from urllib.request import urlopen
+import chardet
+
+rawdata = urlopen('http://baidu.com/').read()
+chr = chardet.detect(rawdata)
+print(chr) # {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
+```
+
+json.dumps在默认情况下,对于非ascii字符生成的是相对应的字符编码,而非原始字符.
+
+>首先明确,chardet这个是**`判断编码`**而不是判断数据类型的,
+
+>其次,加encode只是将其变成了字节
+
+**chardet只能够接受字节而不能接收字符类型的**
+
+```python
+import json
+import chardet
+
+dict1 = {"haha": "哈哈"}
+
+# json.dumps 默认ascii编码
+print(json.dumps(dict1)) # {"haha": "\u54c8\u54c8"}
+
+# 禁止ascii编码后默认utf-8
+print(json.dumps(dict1, ensure_ascii=False)) # {"haha": "哈哈"}
+
+# ascii
+ss = chardet.detect(json.dumps(dict1).encode())
+print(ss)
+
+# utf-8
+ss = chardet.detect(json.dumps(dict1, ensure_ascii=False).encode())
+print(ss)
+
+```
+
+
+
+## json与字典区别
+
+在python中,字典的输出内容跟json格式内容一样,但是字典的格式是字典,json的格式是字符串,所以在传输的时候(特别是网页)要转换使用。
+
+`重要函数`
+>编码:**把一个Python对象编码转换成Json字符串 json.dumps()**
+
+>解码:**把Json格式字符串解码转换成Python对象 json.loads()**
+
+举个例子:
+```python
+import json
+dic = {'str': 'this is a string', 'list': [1, 2, 'a', 'b']}
+print(type(dic)) #
+
+`几个主要函数`
+>dump,dumps,load,loads
+
+>**带s跟不带s的区别是 带s的是对 字符串的处理,而不带 s的是对文件对像的处理。**
+
+
+```python
+import json
+from io import StringIO
+
+# 创建文件流对象
+io = StringIO()
+
+# 把 json编码数据导向到此文件对象
+json.dump(['streaming API'], io)
+
+# 取得文件流对象的内容
+print(io.getvalue()) # ["streaming API"]
+```
+
+
+
+**参数ensure_ascii**
+>默认为True,所有的非ascii字符在输出时都会被转义为\uxxxx的序列,
+返回的对象是一个只由ascii字符组成的str类型,为False时不会进行转义输出,反回的对象是个unicode。(**这个参数对包含中文的json输出时非常有用**)
+
+
+
+举个例子:
+
+```python
+import json
+
+data = {u'我': u'是', u'美': u'女'}
+
+print(json.dumps(data)) # {"\u6211": "\u662f", "\u7f8e": "\u5973"}
+
+print(json.dumps(data, ensure_ascii=False)) # {"我": "是", "美": "女"}
+
+```
+
+处理中文json时,要想不每次都给一堆重复的参数,可以用partial
+
+```python
+import json
+from functools import partial
+
+json_dumps = partial(json.dumps, ensure_ascii=False, sort_keys=True)
+
+data = {u'我': u'是', u'美': u'女'}
+
+print(json_dumps(data)) # {"我": "是", "美": "女"}
+
+```
+
+
+看一个例子:
+```python
+import json
+
+# 使用双引号、单引号都可以的,建议使用双引号
+h = '{"foo":"bar", "foo2":"bar2"}'
+d = "{'muffin' : 'lolz', 'foo' : 'kitty'}"
+
+json_obj1 = json.dumps(h)
+json_obj2 = json.dumps(d)
+
+dic1 = json.loads(json_obj1)
+dic2 = json.loads(json_obj2)
+
+print("json_obj1=", json_obj1)
+print("json_obj2=", json_obj2)
+print("dic1=", dic1)
+print("dic1 type=", type(dic1))
+print("dic2=", dic2)
+print("dic2 type=", type(dic2))
+```
+
+
+总结:
+>**Python 的字典是一种数据结构,JSON 是一种数据格式。**
+>**Python的字典key可以是任意可hash对象,json只能是字符串**
+
+
+
+
+## json模块和simplejson模块
+
+
+
+环境:
+**python 3.6+**
+**json: 2.0.9**
+**simplejson: 3.16.0**
+
+
+
+* 区别1
+
+```python
+import json
+import simplejson
+
+json_str = b'hello'
+json_str = json_str.decode() # 使用json模块必须要先加入这一行否则会报错
+print(json.dumps(json_str))
+print(simplejson.dumps(json_str))
+```
+
+比较性能方面的差异:
+
+[压测数据包链接](http://abral.altervista.org/jsonpickle-bench.zip)
+
+压测脚本:
+```python
+import timeit
+import simplejson
+import pickle
+import json
+
+times = 100
+print("Times are for %i iterations" % times)
+
+f = open('words.pickle', "rb")
+words = pickle.load(f)
+f.close()
+
+
+def bench(cmd, imprt):
+ t = timeit.Timer(cmd, imprt)
+ s = t.timeit(number=times)
+ print("%s total=%02f avg=%02f" % (cmd, s, (s / times)))
+ return s
+
+
+def simplejson_loads():
+ simplejson.loads(simplejson.dumps(words))
+
+
+def simplejson_dumps():
+ simplejson.dumps(words)
+
+
+b1 = bench('simplejson_loads()', 'from __main__ import simplejson_loads')
+b2 = bench('simplejson_dumps()', 'from __main__ import simplejson_dumps')
+
+
+def json_dumps():
+ json.dumps(words)
+
+
+def json_loads():
+ json.loads(json.dumps(words))
+
+
+b3 = bench('json_loads()', 'from __main__ import json_loads')
+b4 = bench('json_dumps()', 'from __main__ import json_dumps')
+
+```
+
+
+返回结果是:
+```
+Times are for 100 iterations
+simplejson_loads() total=1.760785 avg=0.017608
+simplejson_dumps() total=0.921571 avg=0.009216
+json_loads() total=1.351725 avg=0.013517
+json_dumps() total=0.716219 avg=0.007162
+```
+
+> `从上面结果可以看到json 在loads操作和dumps操作都比simplejson好`
+~~网上说的simplejson比json快,感觉说的不对~~
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md"
new file mode 100644
index 00000000..ef55c8ca
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md"
@@ -0,0 +1,59 @@
+# 第13章 正确重载运算符
+
+
+- [第13章 正确重载运算符](#%e7%ac%ac13%e7%ab%a0-%e6%ad%a3%e7%a1%ae%e9%87%8d%e8%bd%bd%e8%bf%90%e7%ae%97%e7%ac%a6)
+ - [几个不常用的运算符](#%e5%87%a0%e4%b8%aa%e4%b8%8d%e5%b8%b8%e7%94%a8%e7%9a%84%e8%bf%90%e7%ae%97%e7%ac%a6)
+
+
+
+## 几个不常用的运算符
+
+这里我们看几个之前没讲过的运算符`__neg__,__pos__,__invert__`
+
+`__neg__`是在-v的时候调用
+
+`__pos__`是在+v的时候调用
+
+`__invert__`是在~v的时候调用
+
+
+看下面的例子:
+
+```python
+class Vector(object):
+ def __init__(self, x):
+ self.x = x
+
+ def __neg__(self):
+ return "Vector(%d)" % (-self.x)
+
+ def __str__(self):
+ return "Vector(%s)" % (str(self.data))
+
+ def __iter__(self):
+ return iter(self.data)
+
+ def __pos__(self):
+ return "Vector(%d)" % (self.x + 1)
+
+ def __invert__(self):
+ return "Vector(%d)" % (~self.x)
+
+
+if __name__ == "__main__":
+
+ v = Vector(1)
+
+ print(-v) # Vector(-1)
+ print(+v) # Vector(2)
+ print(~v) # Vector(-2)
+
+```
+
+返回结果:
+```
+Vector(-1)
+Vector(2)
+Vector(-2)
+
+```
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md"
new file mode 100644
index 00000000..18cb8588
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md"
@@ -0,0 +1,244 @@
+# 第9章-符合Python风格的对象
+
+
+
+- [第9章-符合Python风格的对象](#%e7%ac%ac9%e7%ab%a0-%e7%ac%a6%e5%90%88python%e9%a3%8e%e6%a0%bc%e7%9a%84%e5%af%b9%e8%b1%a1)
+ - [format表示法](#format%e8%a1%a8%e7%a4%ba%e6%b3%95)
+ - [__slots__方法](#slots%e6%96%b9%e6%b3%95)
+
+
+
+## format表示法
+
+```python
+
+>>> print "Hello %(name)s !" % {'name': 'James'}
+Hello James !
+
+>>> print "I am years %(age)i years old" % {'age': 18}
+I am years 18 years old
+
+#format的写法:
+
+>>> print "Hello {name} !".format(name="James")
+Hello James !
+```
+
+
+## `__slots__`方法
+
+
+Python内置属性很多,其中`__slots__`属性比较少会被用到,基本不会被当作是必选项。但如果您对内存使用有颇高的要求,`__slots__`会给你很大帮助。
+
+`__slots__`的目的又是什么呢?
+
+答案是:优化内存使用。限制实例属性的自由添加只是副作用而已。
+
+那么`__slots__`属性究竟如何神奇?
+这要先从`__dict_`_属性说起。
+
+
+` __dict__`属性的用途在于记录实例属性:
+
+`__dict__`属性用来记录实例中的属性信息,如果对实例中的属性信息进行修改或者添加新的属性,`__dict__`都会对其进行记录。
+
+```python
+class Person(object):
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+
+person = Person("tony", 20) # 对Person类实例化
+print(person.__dict__) # 记录实例所有的属性 {'name': 'tony', 'age': 20}
+
+person.age = 'jacky'
+person.gender = 'male'
+
+print(person.__dict__) # {'name': 'tony', 'age': 'jacky', 'gender': 'male'}
+
+```
+
+
+
+那么`__slots__`跟`__dict__`又有什么关系呢?
+
+简单点理解:
+
+ `__slots_`_存在的价值在于删除`__dict__`属性,从而来**优化类实例对内存的需求**。
+
+而这带来的副作用就是:由于缺少了`__dict__`,类实例木有办法再自由添加属性了!
+
+
+```python
+class Person(object):
+ __slots__ = ("name", "age")
+
+ def __init__(self, name, age):
+ self.name = name
+ self.age = age
+
+
+person = Person("tony", 20) # 对Person类实例化
+
+print("__dict__" in dir(person)) # False, 没有了__dict__属性
+
+person.age = 'jacky'
+person.gender = 'male' # AttributeError: 'Person' object has no attribute 'gender'
+# 这就是__slots__的副作用,不能自由添加实例属性了
+
+```
+
+
+总结:
+
+默认情况下,自定义的对象都使用dict来存储属性(通过obj.`__dict__`查看),而python中的dict大小一般比实际存储的元素个数要大(以此降低hash冲突概率),因此会浪费一定的空间。
+
+在新式类中使用`__slots__`,就是告诉Python虚拟机,这种类型的对象只会用到这些属性,
+因此虚拟机预留足够的空间就行了
+
+如果声明了`__slots__`,那么对象就不会再有`__dict__`属性。
+
+
+到底能省多少,取决于类自身有多少属性、属性的类型,以及同时存在多少个类的实例
+
+
+可以查看这个[链接](http://tech.oyster.com/save-ram-with-python-slots/)
+
+
+
+我们也可以自己测试下结果:
+
+```python
+# 使用 profile 进行性能分析
+import profile
+
+
+class A(object):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+
+def profile_result():
+ f = [A(1, 2) for i in range(100000)]
+
+
+if __name__ == "__main__":
+ profile.run("profile_result()")
+```
+
+
+返回结果:
+```
+ 100006 function calls in 0.297 seconds
+
+ Ordered by: standard name
+
+ ncalls tottime percall cumtime percall filename:lineno(function)
+ 1 0.000 0.000 0.297 0.297 :0(exec)
+ 1 0.000 0.000 0.000 0.000 :0(setprofile)
+ 1 0.016 0.016 0.297 0.297
+改用`__slots__`的方式:
+
+```python
+# 使用 profile 进行性能分析
+from memory_profiler import profile
+
+
+class A(object):
+ __slots__ = ('x', 'y')
+
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+
+@profile
+def profile_result():
+ f = [A(1, 2) for i in range(100000)]
+
+
+if __name__ == "__main__":
+ profile_result()
+
+```
+
+
+返回结果:
+
+```
+Filename: d:\学习\python\demo\demo.py
+Line # Mem usage Increment Line Contents
+================================================
+ 13 50.6 MiB 50.6 MiB @profile
+ 14 def profile_result():
+ 15 57.9 MiB 0.7 MiB f = [A(1, 2) for i in range(100000)]
+
+```
+
+
+**可以看到内存使用由原先的68.8MB->57.9MB**
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md"
new file mode 100644
index 00000000..83baddc4
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md"
@@ -0,0 +1,15 @@
+# 第21章 类元编程
+
+
+
+- [第21章 类元编程](#%e7%ac%ac21%e7%ab%a0-%e7%b1%bb%e5%85%83%e7%bc%96%e7%a8%8b)
+ - [类工厂函数 collections.namedtuple](#%e7%b1%bb%e5%b7%a5%e5%8e%82%e5%87%bd%e6%95%b0-collectionsnamedtuple)
+ - [ABC元类](#abc%e5%85%83%e7%b1%bb)
+
+
+## 类工厂函数 collections.namedtuple
+
+
+## ABC元类
+
+这些内容可以查看第11章
\ No newline at end of file
diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md"
new file mode 100644
index 00000000..a2170f82
--- /dev/null
+++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md"
@@ -0,0 +1,262 @@
+# 第12章-类继承
+
+
+
+- [第12章-类继承](#%e7%ac%ac12%e7%ab%a0-%e7%b1%bb%e7%bb%a7%e6%89%bf)
+ - [super函数](#super%e5%87%bd%e6%95%b0)
+ - [问题](#%e9%97%ae%e9%a2%98)
+
+
+
+
+## super函数
+
+
+Py 2.x 和 Py 3.x 中有一个很大的区别就是类,无论是类的定义还是类的继承。
+**Py 3.x 中类的继承可以直接使用 super() 关键字代替原来的 super(Class, self)。**
+
+那么 super() 到底是依据什么来继承的呢?
+
+super()函数根据传进去的两个参数具体作用如下:
+
+>通过第一参数传进去的类名确定当前在MRO中的哪个位置。MRO(Method Resolution Order)
+>通过第二个参数传进去的self,确定当前的MRO列表。
+
+```python
+class A(object):
+ def name(self):
+ print('name is xiaoming')
+ # super(A, self).name()
+
+
+class B(object):
+ def name(self):
+ print('name is cat')
+
+
+class C(A, B):
+ def name(self):
+ print('name is wang')
+ super(C, self).name()
+
+
+if __name__ == '__main__':
+
+ c = C()
+ print(c.__class__.__mro__)
+ c.name()
+```
+
+返回结果是:
+```
+(
+再看一个例子:
+
+```python
+class Base(object):
+ def func_1(self):
+ print('this is base')
+
+
+class A(Base):
+ def func_1(self):
+ super().func_1()
+ print('this is A')
+
+
+class B(Base):
+ def func_1(self):
+ super().func_1()
+ print('this is B')
+
+
+class C(A, B):
+ def func_1(self):
+ super().func_1()
+ print('this is c')
+
+
+print(C.mro())
+C().func_1()
+
+print(help(C))
+```
+
+
+返回结果:
+```
+Help on class C in module __main__:
+
+class C(A, B)
+| Method resolution order:
+| C
+| A
+| B
+```
+
+“从左到到右”的意思,C继承关系最近的是A, 然后是B,最后是上层BASE。
+调用方法时的入栈顺序是 C A B Base,
+出栈顺序是Base B A C,
+
+```python
+class Base(object):
+ def __init__(self):
+ print("Base created")
+
+
+class ChildA(Base):
+ def __init__(self):
+ Base.__init__(self)
+
+
+class ChildB(Base):
+ def __init__(self):
+ # super(ChildB, self).__init__()
+ # 或者更简单的写法
+ # super().__init__()
+
+ # 如果不用super写法就要这样写
+ print("self=", self) # <__main__.ChildB object at 0x000001EA8CD085F8>
+ mro = type(self).mro()
+ print(mro)
+ # [
+可以看到结果是一样的,那么 `Base.__init__(self)` 和 `super().__init__()`
+有什么区别呢?
+
+请看这个例子:
+
+```python
+class Base(object):
+ def __init__(self):
+ print("Base created")
+
+
+class UserDependency(Base):
+ def __init__(self):
+ print("UserDependency init...")
+ super().__init__()
+
+
+class ChildA(Base):
+ def __init__(self):
+ print("ChildA init...")
+ Base.__init__(self)
+
+
+class ChildB(Base):
+ def __init__(self):
+ print("ChildB init..")
+ # super(ChildB, self).__init__()
+ # 或者更简单的写法
+ super().__init__()
+
+
+class UserA(ChildA, UserDependency):
+ def __init__(self):
+ print("UserA init...")
+ super(UserA, self).__init__()
+
+
+class UserB(ChildB, UserDependency):
+ def __init__(self):
+ print("UserB init...")
+ super(UserB, self).__init__()
+
+
+if __name__ == "__main__":
+ print(UserA())
+ print("#" * 10)
+ print(UserB())
+```
+
+返回结果:
+```
+UserA init...
+ChildA init...
+Base created
+<__main__.UserA object at 0x0000019295A38B00>
+##########
+UserB init...
+ChildB init..
+UserDependency init...
+Base created
+<__main__.UserB object at 0x0000019295A38B00>
+
+```
+
+
+
+**`这里我们看到了区别,在多重继承中,没有使用super方法的类,没有调用UserDenpendency方法`**
\ No newline at end of file
From aa4b093fb86cb2f0f797cfded1754d4d8988baef Mon Sep 17 00:00:00 2001
From: Frank Lin BpjivT<(mEX20`Ry!!ok!7qbr5=21QNp2hw01#ridQX-zi*aOflp5I
zj%?SRia$0@-0N6a9
+{(1pRCd5(80HoTPPL25_5IH{mIo`9_<1T_
zg!!XahR?jGU%pFWIaR