diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index d7a963a62..000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: "node_js" -node_js: - - "8" -before_install: - - find . -type f -name "*.md" ! -path "./README.md" -a ! -path "./SUMMARY.md"|xargs -i sed -ri '/^/d;0,/^## /s/(^## .*)/\n<\/extoc>\n\n\1/' '{}' -install: - - "npm install gitbook" - - "npm install gitbook-cli@2.3.0" -branches: - only: - - master -env: - global: - - GH_REF: github.com/lyonyang/blogs.git -script: - - bash summary_create.sh - - travis_wait 100 bash deploy.sh diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/01-\350\257\255\350\250\200\345\237\272\347\241\200.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/01-\350\257\255\350\250\200\345\237\272\347\241\200.md" deleted file mode 100644 index 6ba615f07..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/01-\350\257\255\350\250\200\345\237\272\347\241\200.md" +++ /dev/null @@ -1,428 +0,0 @@ -# Attack on Python - 语言基础 🐍 - - - - - - - - - - - - -## Hello World - -学一门语言基本都是从Hello World开始的 , 如下一个最简单的Hello World程序 - -```python -Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32 -Type "help", "copyright", "credits" or "license" for more information. ->>> print("Hello World") -Hello World ->>> -``` - -此为Python 3.5.2版本 , 上述代码为在Windows环境命令行中执行 , 即以管理员身份运行 "命令提示符" - -```python -# 已添加环境变量 -C:\Windows\system32>python -``` - -Python 2.7.x 版本的Hello World程序 - -```python -Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)] on win32 -Type "help", "copyright", "credits" or "license" for more information. ->>> print "Hello World" -Hello World ->>> -``` - -当然使用`Python shell` 仅仅适合处理一些非常简单的小程序 , 对于比较复杂 , 代码量稍微大一点的就不适合了 - - - -## 变量 - -变量用于存储在计算机程序中被引用和操作的信息 - -变量可能被明确为是能表示可变状态、具有存储空间的抽象(如在Java和Visual Basic中) , 变量的唯一目的是在内存中标记和存储数据 , 这些数据可以在整个程序中使用 - -声明变量 - -```python -# 声明一个变量name,并绑定值"Lyon" -name = "Lyon" -# 同时为多个变量赋值 -a = b = c = 1 -``` - -Python变量定义的规则 : - -1. 变量名只能是 字母、数字或者下划线的任意组合 - -2. 变量名的第一个字符不能是数字 - -3. 以下关键字不能声明为变量名 , 属于Python中的保留字and - - -| and | exec | not | -| -------- | ------- | ------ | -| assert | finally | or | -| break | for | pass | -| class | from | print | -| continue | global | raise | -| def | if | return | -| del | import | try | -| elif | in | while | -| else | is | with | -| except | lambda | yield | - - -## 行和缩进 - -Python 与其他语言最大的区别就是 , Python 的代码块不使用大括号 `{}` 来控制类 , 函数以及其他逻辑判断 , Python 最具特色的就是用缩进来写模块 - -缩进的空白数量是可变的 , 但是所有代码块语句必须包含相同的缩进空白数量 , 这个必须严格执行 - -```python -if True: - print "True" -else: - print "False" -''' -执行会出现错误提醒: -IndentationError: unexpected indent -''' -``` - -`IndentationError: unexpected indent` 错误是Python编译器在告诉你 , 你的文件里格式有问题 , 可能是tab和空格没对齐的问题 - -还有`IndentationError: unindent does not match any outer indentation level` 错误表明 , 你使用的缩进方式不一致 , 有的是 tab 键缩进 , 有的是空格缩进 , 改为一致即可。 - -因此 , 在 Python 的代码块中必须使用相同数目的行首缩进空格数 - -建议你在每个缩进层次使用 **单个制表符** 或 **两个空格** 或 **四个空格** , 切记不能混用 - -## 多行语句 - -Python语句中一般以新作为为语句的结束符 - -但是我们可以使用斜杠 ` \ ` 将一行的语句分为多行显示 , 如下 : - -```python -total = item_one + \ - item_two + \ - item_three -``` - -语句中包含 [], {} 或 () 括号就不需要使用多行连接符 , 如下实例 : - -```python -days = ['Monday', 'Tuesday', 'Wednesday', - 'Thursday', 'Friday'] -``` - -**同一行使用多条语句** - -Python可以在同一行中使用多条语句 , 语句之间使用分号 `;` 分割 , 如下 : - -```python -#!/usr/bin/python -import sys; x = 'runoob'; sys.stdout.write(x + '\n') -``` - -## 字符串 - -Python 可以使用引号( **'** )、双引号( **"** )、三引号( **'''** 或 **"""** ) 来表示字符串 , 引号的开始与结束必须的相同类型的 - -其中三引号可以由多行组成 , 编写多行文本的快捷语法 , 常用于文档字符串 , 在文件的特定地点 , 被当做注释 - -```python -word = 'word' -sentence = "This is a sentence" -paragraph = """This is a paragraph - Contains multiple statements""" -``` - -## 注释 - -Python中单行注释采用 ` # ` 开头 - -```python -# 第一个注释 -print("Hello,Python") # 第二个注释 -``` - -Python中多行注释采用三个单引号 ` ''' ` 或三个双引号 `""" ` - -```python -''' -这是多行注释,使用单引号。 -这是多行注释,使用单引号。 -这是多行注释,使用单引号。 -''' -""" -这是多行注释,使用双引号。 -这是多行注释,使用双引号。 -这是多行注释,使用双引号。 -""" -``` - -## 字符编码 - -Python解释器在加载 `.py` 文件中的代码时 , 会对内容进行编码 (默认ASCII) - -然而ASCII是无法处理中文的 , 所以如果我们的代码中出现了中文 , 那么需要在代码的顶端加上一句声明 - -```python -#!/usr/bin/env python -# -*- coding:utf-8 -*- -''' -第一行,为脚本语言指定解释器 -第二行,告诉Python解释器,用utf-8编码来进行编码 -''' -``` - -## 用户输入 - -当我们需要用户自己输入信息时 , 就可以使用`input` 语句 , 如下 : - -```python -# 让用户进行输入,并用变量name接收用户输入的值 -name = input("Please input your name:") -``` - -上述代码 , 会一直等待用户输入 , 直到用户按回车键后才会退出 - -## 输出 - -当我们需要让控制台输出一些我们想要的信息时 , 可以使用`print` 语句 , 在Hello World里我们已经见到了 - -```python -#!/usr/bin/python -# -*- coding: UTF-8 -*- -# Author:Lyon -x = "a" -y = "b" -# 换行输出 -print(x) -print(y) -print('---------') -# 不换行输出 -print(x,) -print(y,) -# 不换行输出 -print(x, y) -''' -执行结果: -a -b ---------- -a b a b -''' -``` - -## 数据类型 - -我们知道在变量创建时 , 会在内存中开辟一个空间 , 用来存放变量的值 , 而这些变量的值可以是各种各样的类型 , 如 : 数字 , 字符串 , 列表 , 元组 , 字典 , 集合等等 - -**数字类型** - -1. int (整型) - - 整数的大小范围由计算机字长确定 - -2. long (长整型) - - 跟C语言不同 , Python的长整数没有指定位宽 , 即 : Python没有限制长整数数值的大小 , 但实际上由于机器内存有限 , 我们使用的长整数数值不可能无限大 - - 注意 , 自从Python 2.2 起 , 如果整数发生溢出 , Python会自动将整数数据转换为长整数 , 所以如今在长整数数据后面不加字母 L 也不会导致严重后果了 - -3. float (浮点型) - - 浮点数用来处理实数 , 即带有小数的数字 , 类似于C语言中的double类型 , 占8个字节(64位) , 其中52位表示底 , 11位表示指数 , 剩下的一位表示符号 - -4. complex (复数) - - 复数由实数部分和虚数部分组成,一般形式为x+yh,其中的x是复数的实数部分,y是复数的虚数部分,这里的x和y都是实数 - -注 : Python中存在整数小数字池 : -5~257 , 在此范围的整数数字共享 - -**布尔值** - -即真或假 , 1或0 - -更多数据类型 , 后续文章中详细整理 - -## 数据运算 - -算术运算符 - -| 运算符 | 描述 | 实例 | -| ---- | ------------------------- | ---------------------------------------- | -| + | 加 - 两个对象相加 | a + b 输出结果 30 | -| - | 减 - 得到负数或是一个数减去另一个数 | a - b 输出结果 -10 | -| * | 乘 - 两个数相乘或是返回一个被重复若干次的字符串 | a * b 输出结果 200 | -| / | 除 - x除以y | b / a 输出结果 2 | -| % | 取模 - 返回除法的余数 | b % a 输出结果 0 | -| ** | 幂 - 返回x的y次幂 | a**b 为10的20次方 , 输出结果 100000000000000000000 | -| // | 取整除 - 返回商的整数部分 | 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 | - -比较运算符 - -| 运算符 | 描述 | 实例 | -| ---- | ---------------------------------------- | -------------------------- | -| == | 等于 - 比较对象是否相等 | (a == b) 返回 False | -| != | 不等于 - 比较两个对象是否不相等 | (a != b) 返回 True | -| <> | 不等于 - 比较两个对象是否不相等 | (a <> b) 返回 True这个运算符类似 != | -| > | 大于 - 返回x是否大于y | (a > b) 返回 False | -| < | 小于 - 返回x是否小于y , 所有比较运算符返回1表示真 , 返回0表示假
这分别与特殊的变量True和False等价 , 注意 , 这些变量名的大写 | (a < b) 返回 True | -| >= | 大于等于 - 返回x是否大于等于y。 | (a >= b) 返回 False | -| <= | 小于等于 - 返回x是否小于等于y。 | (a <= b) 返回 True | - -赋值运算符 - -| 运算符 | 描述 | 实例 | -| ----- | -------- | ---------------------------- | -| = | 简单的赋值运算符 | c = a + b 将 a + b 的运算结果赋值为 c | -| += | 加法赋值运算符 | c += a 等效于 c = c + a | -| -= | 减法赋值运算符 | c -= a 等效于 c = c - a | -| *= | 乘法赋值运算符 | c *= a 等效于 c = c * a | -| /= | 除法赋值运算符 | c /= a 等效于 c = c / a | -| %= | 取模赋值运算符 | c %= a 等效于 c = c % a | -| \*\*= | 幂赋值运算符 | c \*\*= a 等效于 c = c \*\* a | -| //= | 取整除赋值运算符 | c //= a 等效于 c = c // a | - -位运算符 - -| 运算符 | 描述 | 实例 | -| ---- | ---------------------------------------- | ---------------------------------------- | -| & | 按位与运算符 : 参与运算的两个值 , 如果两个相应位都为1 , 则该位的结果为1 , 否则为0 | (a & b) 输出结果 12 , 二进制解释 : 0000 1100 | -| \ | 按位或运算符 : 只要对应的二个二进位有一个为1时 , 结果位就为1 | (a 丨 b) 输出结果 61 , 二进制解释 : 0011 1101 | -| ^ | 按位异或运算符 : 当两对应的二进位相异时 , 结果为1 | (a ^ b) 输出结果 49 , 二进制解释 : 0011 0001 | -| ~ | 按位取反运算符 : 对数据的每个二进制位取反 , 即把1变为0 , 把0变为1 , ~x 类似于 -x-1 | (~a ) 输出结果 -61 , 二进制解释 : 1100 0011 , 在一个有符号二进制数的补码形式 | -| << | 左移动运算符 : 运算数的各二进位全部左移若干位 , 由 << 右边的数字指定了移动的位数 , 高位丢弃 , 低位补0 | a << 2 输出结果 240 , 二进制解释 : 1111 0000 | -| >> | 右移动运算符 : 把">>"左边的运算数的各二进位全部右移若干位 , >> 右边的数字指定了移动的位数 | a >> 2 输出结果 15 , 二进制解释 : 0000 1111 | - -逻辑运算符 - -| 运算符 | 逻辑表达式 | 描述 | 实例 | -| ---- | ------- | ---------------------------------------- | --------------------- | -| and | x and y | 布尔"与" - 如果 x 为 False , x and y 返回 False , 否则它返回 y 的计算值 | (a and b) 返回 20 | -| or | x or y | 布尔"或" - 如果 x 是非 0 , 它返回 x 的值 , 否则它返回 y 的计算值 | (a or b) 返回 10 | -| not | not x | 布尔"非" - 如果 x 为 True , 返回 False , 如果 x 为 False , 它返回 True | not(a and b) 返回 False | - -成员运算符 - -| 运算符 | 描述 | 实例 | -| ------ | ---------------------------------- | --------------------------------- | -| in | 如果在指定的序列中找到值返回 True , 否则返回 False | x 在 y 序列中 , 如果 x 在 y 序列中返回 True | -| not in | 如果在指定的序列中没有找到值返回 True , 否则返回 False | x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True | - -身份运算符 - -| 运算符 | 描述 | 实例 | -| ------ | ------------------------- | ---------------------------------------- | -| is | is 是判断两个标识符是不是引用自一个对象 | **x is y ** , 类似 **id(x) == id(y)** , 如果引用的是同一个对象则返回 True , 否则返回 False | -| is not | is not 是判断两个标识符是不是引用自不同对象 | **x is not y** , 类似 **id(a) != id(b) , 如果引用的不是同一个对象则返回结果 True , 否则返回 False | - -运算符优先级表 , 从上到下优先级依次增高 - -| Operator | Description | -| ---------------------------------------- | ---------------------------------------- | -| [`lambda`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#lambda) | Lambda expression | -| [`if`](https://docs.python.org/3/reference/compound_stmts.html#if) – [`else`](https://docs.python.org/3/reference/compound_stmts.html#else) | Conditional expression | -| [`or`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#or) | Boolean OR | -| [`and`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#and) | Boolean AND | -| [`not`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#not) `x` | Boolean NOT | -| [`in`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#in), [`not in`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#not-in), [`is`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#is), [`is not`](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#is-not), `<`, `<=`, `>`, `>=`, `!=`, `==` | Comparisons, including membership tests and identity tests | -| `丨` | Bitwise OR | -| `^` | Bitwise XOR | -| `&` | Bitwise AND | -| `<<`, `>>` | Shifts | -| `+`, `-` | Addition and subtraction | -| `*`, `@`, `/`, `//`, `%` | Multiplication, matrix multiplication, division, floor division, remainder [[5\]](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#id21) | -| `+x`, `-x`, `~x` | Positive, negative, bitwise NOT | -| `**` | Exponentiation [[6\]](https://docs.python.org/3/reference/expressions.html?highlight=operator%20precedence#id22) | -| `await` `x` | Await expression | -| `x[index]`, `x[index:index]`, `x(arguments...)`, `x.attribute` | Subscription, slicing, call, attribute reference | -| `(expressions...)`, `[expressions...]`, `{key: value...}`, `{expressions...}` | Binding or tuple display, list display, dictionary display, set display | - -## if ... else - -场景一 : 用户登录验证 - -```python -# 导入getpass模块 -import getpass -# 等待用户输入 -name = input("请输入用户名:") -# 等待用户输入密码,密码不可见 -password = getpass.getpass("请输入密码:") -# 如果用户密码正确,执行如下 -if name =="Lyon" and password =="yang": - print("欢迎你!") -# 否则执行如下 -else: - print("用户名或密码错误") -``` - -场景二 : 猜年龄游戏 - -```python -# 定义一个年龄 -age =21 -# 用户输入 -user_input = int(input("input your guess num:")) -if user_input == age: - print("Congratulations, you got it !") -elif user_input < age: - print("Oops,think bigger!") -else: - print("think smaller!") -``` - -## for循环 - -循环10次 - -```python -for i in range(10): - print("loop:", i ) -''' -执行结果: -loop: 0 -loop: 1 -loop: 2 -loop: 3 -loop: 4 -loop: 5 -loop: 6 -loop: 7 -loop: 8 -loop: 9 -''' -``` - -小于5就跳入下一次循环 - -```python -for i in range(10): - if i<5: - continue - print("loop:", i) -``` - -## while循环 - -写一个死循环 - -```python -count = 0 -while True: - print("你是风儿我是沙,缠缠绵绵走天涯", count) - count += 1 -``` diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/02-\346\225\260\345\255\227.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/02-\346\225\260\345\255\227.md" deleted file mode 100644 index e9106a586..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/02-\346\225\260\345\255\227.md" +++ /dev/null @@ -1,128 +0,0 @@ -# Attack on Python - 数字 🐍 - - - - - - - - - - - - -## 整型 - -在 `Python 2.7` 版本中 , `Python` 把 `int` 和 `long` 是分开的 - -`int` 类型的最大值是 `2147483647` , 超过了这个值就是 `long` 类型了(长整数不过是大一些的数) ; 而在3.x中 , `int` 和 `long` 整合到一起了 , 以 `int` 来表示 - -```python ->>> num = 123 ->>> type(num) - -``` - -## 浮点型 - -float有两种表现形式 , 一种是十进制数形式 , 它由数字和小数点组成 , 并且这里的小数点是不可或缺的 ; 另一种是指数形式 , 用e(大写也可以)来表示之后可以有正负号 , 来表示指数的符号 , e就是10的幂 , 指数必须是整数 - -```python ->>> a = 10E2 ->>> a -1000.0 ->>> b = 10e2 ->>> b -1000.0 ->>> c = 1.1 ->>> type(c) - -``` - -小 `Tips` : 在我们工作中很多时候会需要一个无穷大 , 或者无穷小的预设值 , 就可以使用 `float` 来实现 , 无穷小和无穷大分别是 , `float('-inf')` 和 `float('inf')` - -## 空值 - -表示该值是一个空对象 , 空值是python里一个特殊的值 , 用None表示 - -None不能理解为0 , 因为0是有意义的 , 而None是一个特殊的空值 ; None有自己的数据类型NoneType , 它与其他的数据类型比较永远返回False , 你可以将None复制给任何变量 , 但是你不能创建其他NoneType对象 - -```python ->>> type(None) - ->>> None == 0 -False ->>> None == True -False ->>> None == False -False -``` - -## 布尔值 - -bool就是用来表征真假的一种方式 - -True为真 , False为假 ; Python中的值是自带bool值的 , 非0即真 , 为0即假 - -```python ->>> False + False -0 ->>> True + True -2 ->>> True + False -1 -``` - -## 复数 - -复数有实数和虚数部分组成 , 一般形式为 `x + yj` , 其中的 x 是复数的实数部分 , y是复数的虚数部分 , 这里x和y都是实数 - -注意 , 虚数部分不区分大小写 - -```python ->>> -.6545 + 0J -(-0.6545+0j) ->>> 4.53e1 - 7j -(45.3-7j) ->>> 45.j -45j ->>> 3.14j -3.14j -``` - -## 类型转换 - -```python -int(x [,base]) 将x转换为一个整数 -float(x ) 将x转换到一个浮点数 -complex(x) 将x转换为复数 -str(x) 将对象x转换为字符串 ,通常无法用eval()求值 -repr(x) 将对象x转换为表达式字符串 ,可以用eval()求值 -eval(str) 用来计算在字符串中的有效Python表达式,并返回一个对象 -tuple(s) 将序列s转换为一个元组 -list(s) 将序列s转换为一个列表 -chr(x) 将一个整数转换为一个字符 -unichr(x) 将一个整数转换为Unicode字符 -ord(x) 将一个字符转换为它的整数值 -hex(x) 将一个整数转换为一个十六进制字符串 -oct(x) 将一个整数转换为一个八进制字符串 -``` - -## 数学函数 - -```python -abs(x) 返回数字的绝对值,如abs(-10) 返回 10 -ceil(x) 返回数字的上入整数,如math.ceil(4.1) 返回 5 -cmp(x, y) 如果 x < y 返回 -1, 如果 x == y 返回 0, 如果 x > y 返回 1 -exp(x) 返回e的x次幂(ex),如math.exp(1) 返回2.718281828459045 -fabs(x) 返回数字的绝对值,如math.fabs(-10) 返回10.0 -floor(x) 返回数字的下舍整数,如math.floor(4.9)返回 4 -log(x) 如math.log(math.e)返回1.0,math.log(100,10)返回2.0 -log10(x) 返回以10为基数的x的对数,如math.log10(100)返回 2.0 -max(x1, x2,...) 返回给定参数的最大值,参数可以为序列 -min(x1, x2,...) 返回给定参数的最小值,参数可以为序列 -modf(x) 返回x的整数部分与小数部分,两部分的数值符号与x相同,整数部分以浮点型表示 -pow(x, y) x**y 运算后的值。 -round(x [,n]) 返回浮点数x的四舍五入值,如给出n值,则代表舍入到小数点后的位数 -sqrt(x) 返回数字x的平方根,数字可以为负数,返回类型为实数,如math.sqrt(4)返回 2+0j -``` \ No newline at end of file diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/03-\345\255\227\347\254\246\344\270\262.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/03-\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index 8167a2f2d..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/03-\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,471 +0,0 @@ -# Attack on Python - 字符串 🐍 - - - - - - - - - - - - -## 介绍 - -字符串是 `Python` 中最基本的数据类型之一 , 它是一个定长对象 , 这意味着它的一旦创建 , 再也无法改变长度 - -所以关于字符串的操作 , 都会返回一个新的字符串 , 而无法在原来的字符串上直接操作 - -字符串的使用需要用引号括起来 , 例如 : `name = "Lyon"` ; 这里name就是一个变量名 , 而引号里面的`Lyon` 则就是该变量绑定的值 , 该值的类型为 " str" 类型 , 我们可以利用`type()` 函数进行查看 : - -```python ->>> name = "Lyon" ->>> type(name) - ->>> -``` - -这就是字符串类型 , 当然如上使用的是双引号 , 这里其实还可以使用单引号`'Lyon'`以及三引号`'''Lyon'''`(或者是`"""Lyon"""` , 单引号双引号都可以) , 不过对于三引号 , 我们通常是表示多行字符串 , 这样我们就不需要利用 " \n " (换行符)来进行每一行的换行了 - -对于嵌套引号的时候要注意 , 需要用不同的引号来避免歧义 , 比如 : `'I am "Lyon"'` , 也可以 `"I am 'Lyon'"` - -对于所有的基本数据类型 , 我们都应该熟悉其特性以及操作 - -字符串操作主要有 **拷贝、拼接、查找、比较、统计、切片、测试、大小写等** - -## 拷贝 - -```python ->>> a = "Lyon" ->>> b = a ->>> print(a,b) -Lyon Lyon -``` - -## 拼接 - -```python ->>> a = "Hello" ->>> b = "Lyon" ->>> print(a + b) -HelloLyon -``` - -小 `Tips` : 由于字符串是定长对象 , 这就导致我们如果做 `+` 运算 , 两两相加都会生成一个新的字符串 , 于是如果你这样操作 `a + a + a + a + a` 除了最后的结果 , 在内存中还会创建 3 个在运算过程中需要的字符串 , 所以如果拼接操作过多 , 我们正确的方式应该是使用 `''.join(list())` , 也就是通过 `join` 方法 - -```python ->>> a = "Lyon" ->>> b = "Hello" ->>> print(a.join(b)) -HLyoneLyonlLyonlLyono #HLyon eLyon lLyon lLyon o -``` - -## 查找 - -```python ->>> name = "Lyon" -# 返回L字符所在的下标,下标是从0开始的整数 ->>> name.index('L') -0 -# 如果不存在就会报错 ->>> name.index('N') -Traceback (most recent call last): - File "", line 1, in -ValueError: substring not found -# 也可以用in,not in来进行判断 ->>>'L' in name ->>> -``` - -## 比较 - -本来 `Python 2` 中有个 `str.cmp()` 方法来比较两个对象 , 并根据结果返回一个整数 , 整数的正负就是数值的大小了 , 但是在 `Python 3` 中就没有这个方法了 , 官方文档如下 : - -```The cmp() function should be treated as gone, and the __cmp__() special method is no longer supported. Use __lt__() for sorting, __eq__() with __hash__(), and other rich comparisons as needed. (If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b).) -The cmp() function should be treated as gone, and the __cmp__() special method is no longer supported. Use __lt__() for sorting, __eq__() with __hash__(), and other rich comparisons as needed. (If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b).) -``` - -大致的意思就是cmp()函数已经走了 , 如果你真的需要cmp函数 , 你可以用表达式`(a>b)-(a>> a = "100" ->>> b = "50" ->>> cmp(a,b) # a>b 负数 --1 ->>> cmp(b,a) # b>> name = "Lyon" - # name中"L"的个数 ->>> name.count("L") -1 -``` - -## 切片 - -```python ->>> name = "i like Lyon" -# 切取第7个到第9个字符,注意空格也是一个字符 ->>> name[7:10] -'Lyo' ->>> name = "i like Lyon" -# 第7到第10各,顾头不顾尾 ->>> name[7:11] -'Lyon' -``` - -## 检测 - -```python ->>> name = "Lyon" -# 检测"L"是否在name中,返回bool值 ->>> "L" in name -True ->>> num = "3412313" -# 检测num里面是否全都是整数 ->>> num.isdigit() -True ->>> name = "Lyon" -# 检测name是否可以被当作标标志符,即是否符合变量命名规则 ->>> name.isidentifier() -True  -# 检测name里面有没有"L",有就返回下标 ->>> name.find('L') -0 -# 检测name里面有没有"N",没有就返回-1 ->>> name.find('N') --1 -``` - -检测相关 - -```python -str.startswith(prefix[,start[,end]]) # 是否以prefix开头 -str.endswith(suffix[,start[,end]]) # 以suffix结尾 -str.isalnum() # 是否全是字母和数字,并至少有一个字符 -str.isalpha() # 是否全是字母,并至少有一个字符 -str.isdigit() # 是否全是数字,并至少有一个字符 -str.isspace() # 是否全是空白字符,并至少有一个字符 -str.islower() # 是否全是小写 -str.isupper() # 是否便是大写 -str.istitle() # 是否是首字母大写的 -``` - -注 : 返回值全为 `bool` 值 - -## 大小写 - -```python ->>> name = "I am Lyon" -# 大小写互换 ->>> name.swapcase() -'i AM lYON' -# 首字母大写,其它都小写 ->>> name.capitalize() -'I am lyon' -# 转换为大写 ->>> name.upper() -'I AM LYON' -# 转换为小写 ->>> name.lower() -'i am lyon' -``` - -## 更多 - -```python - | capitalize(...) - | S.capitalize() -> str - | - | Return a capitalized version of S, i.e. make the first character - | have upper case and the rest lower case. - | - | casefold(...) - | S.casefold() -> str - | - | Return a version of S suitable for caseless comparisons. - | - | center(...) - | S.center(width[, fillchar]) -> str - | - | Return S centered in a string of length width. Padding is - | done using the specified fill character (default is a space) - | - | count(...) - | S.count(sub[, start[, end]]) -> int - | - | Return the number of non-overlapping occurrences of substring sub in - | string S[start:end]. Optional arguments start and end are - | interpreted as in slice notation. - | - | encode(...) - | S.encode(encoding='utf-8', errors='strict') -> bytes - | - | Encode S using the codec registered for encoding. Default encoding - | is 'utf-8'. errors may be given to set a different error - | handling scheme. Default is 'strict' meaning that encoding errors raise - | a UnicodeEncodeError. Other possible values are 'ignore', 'replace' and - | 'xmlcharrefreplace' as well as any other name registered with - | codecs.register_error that can handle UnicodeEncodeErrors. - | - | endswith(...) - | S.endswith(suffix[, start[, end]]) -> bool - | - | Return True if S ends with the specified suffix, False otherwise. - | With optional start, test S beginning at that position. - | With optional end, stop comparing S at that position. - | suffix can also be a tuple of strings to try. - | - | expandtabs(...) - | S.expandtabs(tabsize=8) -> str - | - | Return a copy of S where all tab characters are expanded using spaces. - | If tabsize is not given, a tab size of 8 characters is assumed. - | - | find(...) - | S.find(sub[, start[, end]]) -> int - | - | Return the lowest index in S where substring sub is found, - | such that sub is contained within S[start:end]. Optional - | arguments start and end are interpreted as in slice notation. - | - | Return -1 on failure. - | - | format(...) - | S.format(*args, **kwargs) -> str - | - | Return a formatted version of S, using substitutions from args and kwargs. - | The substitutions are identified by braces ('{' and '}'). - | - | format_map(...) - | S.format_map(mapping) -> str - | - | Return a formatted version of S, using substitutions from mapping. - | The substitutions are identified by braces ('{' and '}'). - | - | index(...) - | S.index(sub[, start[, end]]) -> int - | - | Like S.find() but raise ValueError when the substring is not found. - | - | isalnum(...) - | S.isalnum() -> bool - | - | Return True if all characters in S are alphanumeric - | and there is at least one character in S, False otherwise. - | - | isalpha(...) - | S.isalpha() -> bool - | - | Return True if all characters in S are alphabetic - | and there is at least one character in S, False otherwise. - | - | isdecimal(...) - | S.isdecimal() -> bool - | - | Return True if there are only decimal characters in S, - | False otherwise. - | - | isdigit(...) - | S.isdigit() -> bool - | - | Return True if all characters in S are digits - | and there is at least one character in S, False otherwise. - | - | isidentifier(...) - | S.isidentifier() -> bool - | - | Return True if S is a valid identifier according - | to the language definition. - | - | Use keyword.iskeyword() to test for reserved identifiers - | such as "def" and "class". - | - | islower(...) - | S.islower() -> bool - | - | Return True if all cased characters in S are lowercase and there is - | at least one cased character in S, False otherwise. - | - | isnumeric(...) - | S.isnumeric() -> bool - | - | Return True if there are only numeric characters in S, - | False otherwise. - | - | isprintable(...) - | S.isprintable() -> bool - | - | Return True if all characters in S are considered - | printable in repr() or S is empty, False otherwise. - | - | isspace(...) - | S.isspace() -> bool - | - | Return True if all characters in S are whitespace - | and there is at least one character in S, False otherwise. - | - | istitle(...) - | S.istitle() -> bool - | - | Return True if S is a titlecased string and there is at least one - | character in S, i.e. upper- and titlecase characters may only - | follow uncased characters and lowercase characters only cased ones. - | Return False otherwise. - | - | isupper(...) - | S.isupper() -> bool - | - | Return True if all cased characters in S are uppercase and there is - | at least one cased character in S, False otherwise. - | - | join(...) - | S.join(iterable) -> str - | - | Return a string which is the concatenation of the strings in the - | iterable. The separator between elements is S. - | - | ljust(...) - | S.ljust(width[, fillchar]) -> str - | - | Return S left-justified in a Unicode string of length width. Padding is - | done using the specified fill character (default is a space). - | - | lower(...) - | S.lower() -> str - | - | Return a copy of the string S converted to lowercase. - | - | lstrip(...) - | S.lstrip([chars]) -> str - | - | Return a copy of the string S with leading whitespace removed. - | If chars is given and not None, remove characters in chars instead. - | - | partition(...) - | S.partition(sep) -> (head, sep, tail) - | - | Search for the separator sep in S, and return the part before it, - | the separator itself, and the part after it. If the separator is not - | found, return S and two empty strings. - | - | replace(...) - | S.replace(old, new[, count]) -> str - | - | Return a copy of S with all occurrences of substring - | old replaced by new. If the optional argument count is - | given, only the first count occurrences are replaced. - | - | rfind(...) - | S.rfind(sub[, start[, end]]) -> int - | - | Return the highest index in S where substring sub is found, - | such that sub is contained within S[start:end]. Optional - | arguments start and end are interpreted as in slice notation. - | - | Return -1 on failure. - | - | rindex(...) - | S.rindex(sub[, start[, end]]) -> int - | - | Like S.rfind() but raise ValueError when the substring is not found. - | - | rjust(...) - | S.rjust(width[, fillchar]) -> str - | - | Return S right-justified in a string of length width. Padding is - | done using the specified fill character (default is a space). - | - | rpartition(...) - | S.rpartition(sep) -> (head, sep, tail) - | - | Search for the separator sep in S, starting at the end of S, and return - | the part before it, the separator itself, and the part after it. If the - | separator is not found, return two empty strings and S. - | - | rsplit(...) - | S.rsplit(sep=None, maxsplit=-1) -> list of strings - | - | Return a list of the words in S, using sep as the - | delimiter string, starting at the end of the string and - | working to the front. If maxsplit is given, at most maxsplit - | splits are done. If sep is not specified, any whitespace string - | is a separator. - | - | rstrip(...) - | S.rstrip([chars]) -> str - | - | Return a copy of the string S with trailing whitespace removed. - | If chars is given and not None, remove characters in chars instead. - | - | split(...) - | S.split(sep=None, maxsplit=-1) -> list of strings - | - | Return a list of the words in S, using sep as the - | delimiter string. If maxsplit is given, at most maxsplit - | splits are done. If sep is not specified or is None, any - | whitespace string is a separator and empty strings are - | removed from the result. - | - | splitlines(...) - | S.splitlines([keepends]) -> list of strings - | - | Return a list of the lines in S, breaking at line boundaries. - | Line breaks are not included in the resulting list unless keepends - | is given and true. - | - | startswith(...) - | S.startswith(prefix[, start[, end]]) -> bool - | - | Return True if S starts with the specified prefix, False otherwise. - | With optional start, test S beginning at that position. - | With optional end, stop comparing S at that position. - | prefix can also be a tuple of strings to try. - | - | strip(...) - | S.strip([chars]) -> str - | - | Return a copy of the string S with leading and trailing - | whitespace removed. - | If chars is given and not None, remove characters in chars instead. - | - | swapcase(...) - | S.swapcase() -> str - | - | Return a copy of S with uppercase characters converted to lowercase - | and vice versa. - | - | title(...) - | S.title() -> str - | - | Return a titlecased version of S, i.e. words start with title case - | characters, all remaining cased characters have lower case. - | - | translate(...) - | S.translate(table) -> str - | - | Return a copy of the string S in which each character has been mapped - | through the given translation table. The table must implement - | lookup/indexing via __getitem__, for instance a dictionary or list, - | mapping Unicode ordinals to Unicode ordinals, strings, or None. If - | this operation raises LookupError, the character is left untouched. - | Characters mapped to None are deleted. - | - | upper(...) - | S.upper() -> str - | - | Return a copy of S converted to uppercase. - | - | zfill(...) - | S.zfill(width) -> str - | - | Pad a numeric string S with zeros on the left, to fill a field - | of the specified width. The string S is never truncated. - | - | ---------------------------------------------------------------------- -``` diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/04-\345\205\203\347\273\204.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/04-\345\205\203\347\273\204.md" deleted file mode 100644 index 770313825..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/04-\345\205\203\347\273\204.md" +++ /dev/null @@ -1,155 +0,0 @@ -# Attack on Python - 元组 🐍 - - - - - - - - - - - - -## 介绍 - -元组和字符串一样 , 也是定长对象 - -元组的创建很简单 , 只需要在括号中添加元素 , 并使用逗号隔开即可 - -## 创建 - -```python -# 创建一个带有元素的元组 -mytuple = ("Lyon", "Alex", "Leon", 1, 2, 3) -# 也可以不加括号,但是一定要加引号 -mytuple = "Lyon", "Alex", "Leon", 1, 2, 3 -# 创建一个空元组 -mytuple = () -# 当元组中只有一个元素,加逗号来消除歧义哟,这是一个好习惯,因为()既可以表示tuple又可以表示数学公式中的小括号 -only_one = ("Lyon",) -``` - -## 访问 - -```python -# 创建一个元组 -names = ("Lyon", "Alex", "Leon", 1, 2, 3) -# 访问元组中的第一个元素并打印结果,下标索也是从0开始 -print(names[0]) -# 访问元组中第一和第二个元素并打印结果 -print(names[0:2]) -''' -打印结果: -Lyon -('Lyon', 'Alex') -''' -``` - -## 修改 - -```python -# 创建一个元组 -tuple_name = ("Lyon", "Alex", "Leon", 1, 2, 3) -# 创建另一个元组 -tuple_num = (1, 2, 3, 4, 5) -# 生成一个新的元组 -tuple_total = tuple_name + tuple_num -# 打印tuple_total -print(tuple_total) -# 复制元组内元素一次 -tuple_total = tuple_name * 2 -# 打印tuple_total看结果 -print(tuple_total) -# 在列表中可以通过索引取值后进行修改,但是元组里面是非法的哦 -tuple_name[0] = "lyon" # 这里直接就报错 -''' -打印结果: -('Lyon', 'Alex', 'Leon', 1, 2, 3, 1, 2, 3, 4, 5) -('Lyon', 'Alex', 'Leon', 1, 2, 3, 'Lyon', 'Alex', 'Leon', 1, 2, 3) -''' -``` - -注意 : 元组是不可变的 , 所以对于所有的修改操作 , 都是在根据原元组生成了一个新的元组 - -## 删除 - -```python -#创建一个元组 -names = ("Lyon", "Alex", "Leon", 1, 2, 3) -# 删除元组names -del names -# TypeError: 'tuple' object doesn't support item deletion -del names[0] -``` - -## 切片 - -```python -names = ("Lyon", "Kenneth", "Leon", "Charlie") -# 打印子集,第二个至第三个 -print(names[1:2]) -# 打印子集,倒数第三个(即第二个)至第三个 -print(names[-3:3]) -# 打印子集,第一个至第三个,隔一个取一个 -print(names[0:2:1]) -''' -打印结果: -('Kenneth', 'Leon') -('Kenneth', 'Leon') -('Leon',) -''' -``` - -## 检测 - -```python -# 创建一个元组 -tuple_name = ("Lyon","Alex","Leon",1,2,3) -# "Lyon"是否在tuple_name中,打印结果 -print("Lyon" in tuple_name) -# 打印结果:True -``` - -## 更多 - -实例 - -```python -# 创建一个元组 -tuple_name = ("Lyon","Alex","Leon",1,2,3) -# 计算元组长度 -tuple_len = len(tuple_name) -# 打印结果 -print(tuple_len) -# 创建一个元素全为数字的元组 -tuple_num = (1,2,3,4,5) -# 返回元组中的最大值 -print(max(tuple_num)) -# 返回元组中的最小值 -print(min(tuple_num)) -# 创建一个列表 -list_name = ["Lyon","Alex","Leon"] -# 将列表转换为元组 -tuple_names = tuple(list_name) -# 打印tuple_names -print(tuple_names) -''' -打印结果: -6 -5 -1 -('Lyon', 'Alex', 'Leon') -''' -``` - -方法 - -```python - | count(...) - | T.count(value) -> integer -- return number of occurrences of value - | - | index(...) - | T.index(value, [start, [stop]]) -> integer -- return first index of value. - | Raises ValueError if the value is not present. -``` \ No newline at end of file diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/05-\345\210\227\350\241\250.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/05-\345\210\227\350\241\250.md" deleted file mode 100644 index 99c253b94..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/05-\345\210\227\350\241\250.md" +++ /dev/null @@ -1,301 +0,0 @@ -# Attack on Python - 列表 🐍 - - - - - - - - - - - - -## 介绍 - -列表是我们以后最常用的数据类型之一 , 通过列表可以对数据实现最方便的存储、修改等操作 - -列表是变长对象 , 且列表是有序的 - -列表相当于其他语言中的数组 - -## 创建 - -```python -# 创建一个列表 -names = ["Alex","Lyon","Leon"] -# 创建一个空列表 -names = [] -# 也可通过list方法 -names = list() -``` - -## 访问 - -```python -# 创建一个列表 -names = ["Alex","Lyon","Leon"] -# 与字符串的索引一样,列表索引从0开始,访问列表中的第一个元素 -fristname = names[0] -# 打印结果 -print(fristname) -# 访问列表中第三个元素 -threename = names[2] -# 打印结果 -print(threename) -# 访问列表中最后一个元素 -endname = names[-1] -# 打印结果 -print(endname) -# 访问倒数第二个元素 -penultimate = names[-2] -# 打印结果 -print(penultimate) -''' -执行结果: -Alex -Leon -Leon -Lyon -''' -``` - -**获取下标** - -```python -# 创建一个列表 -names = ['Alex', 'Lyon', 'Leon', 'CTO','Lyon'] -# 获取下标并打印 -print(names.index('Lyon')) -# 注:只返回找到的第一个下标 -''' -执行结果: -1 -''' -``` - -**统计** - -```python -# 创建一个列表 -names = ['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing',"IT",21,"man"] -# 统计 "Lyon" 的个数,并打印 -print(names.count("Lyon")) -''' -执行结果: -1 -''' -``` - -## 切片 - -```python -# 创建一个列表 -names = ["Alex","Lyon","Leon","CTO","WuHan"] -# 取下标为1至下标3之间的值,包括1,不包括4 -cutnames1 = names[1:3] -# 打印cutnames1 -print(cutnames1) -# 取下标为1至-1的值,不包括-1(-1就是最后一个) -cutnames2 = names[1:-1] -# 打印cutnames2 -print(cutnames2) -# 从第一个到第三个 -cutnames3 = names[0:3] -# 从头开始取,0可以省略,跟上面的效果一样 -cutnames4 = names[:3] -# 打印cutnames3,cutnames4 -print(cutnames3,cutnames4) -# 想取最后一个,只能这样写,切片是不包含后一个参数的 -cutnames5 = names[3:] -# 后面的2是代表,每隔一个元素,就取一个 -cutnames6 = names[0::2] -# 或者这样 -cutnames7 = names[::2] -# 打印cutnames6,cutnames7 -print(cutnames6,cutnames7) -''' -执行结果: -['Lyon', 'Leon'] -['Lyon', 'Leon', 'CTO'] -['Alex', 'Lyon', 'Leon'] ['Alex', 'Lyon', 'Leon'] -['Alex', 'Leon', 'WuHan'] ['Alex', 'Leon', 'WuHan'] -''' -``` - -## 追加 - -```python -# 创建一个列表 -names = ["Alex","Lyon","Leon","CTO","WuHan"] -# 追加一个元素 -names.append("New") -# 打印names -print(names) -# 注:append 方法只能追加到列表的最后一位 -''' -执行结果: -['Alex', 'Lyon', 'Leon', 'CTO', 'WuHan', 'New'] -''' -``` - -## 插入 - -```python -# 创建一个列表 -names = ["Alex","Lyon","Leon","CTO","WuHan","New"] -# 插入到下标1前面 -names.insert(1,"Insert") -# 打印names -print(names) -# 如果下标不存在就会插入到最后一个 -names.insert(7,"NoIndex") -# 打印names -print(names) -''' -执行结果: -['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'WuHan', 'New'] -['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'WuHan', 'New', 'NoIndex'] -''' -``` - -## 修改 - -```python -# 创建一个列表 -names = ['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'WuHan', 'New', 'NoIndex'] -# 把 'WuHan' 改成 'BeiJing' -names[5] = 'BeiJing' -# 打印names -print(names) -# 注:就是通过下标直接改变list本身 -''' -执行结果: -['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'New', 'NoIndex'] -''' -``` - -## 删除 - -```python -# 创建一个列表 -names = ['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'New', 'NoIndex'] -# 删除下标为7的元素 -del names[7] -#打印names -print(names) -# 删除 'Insert',remove删除指定元素 -names.remove("Insert") -# 打印names -print(names) -# 删除最后一个元素 -names.pop() -# 打印names -print(names) -''' -执行结果: -['Alex', 'Insert', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'New'] -['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'New'] -['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing'] -''' -``` - -## 扩展 - -```python -# 创建一个列表 -names = ['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing'] -# 创建另一个列表 -name = ["IT",21,"man"] -# 将name扩展到names -names.extend(name) -# 打印names -print(names) -# 这里还有一个"万恶的'+' "也是可以的 -print(names + name) -''' -执行结果: -['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'IT', 21, 'man'] -['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'IT', 21, 'man'] -''' -``` - -## 拷贝 - -```python -# 创建一个列表 -names = ['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing',"IT",21,"man"] -# 拷贝names,这只是浅copy -names_copy = names.copy() -# 打印names_copy -print(names_copy) -''' -执行结果: -['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing', 'IT', 21, 'man'] -''' -``` - -注意 : 在 `Python 2.7` 中列表的内置方法是没有 `copy` 这个方法的 , 这是在 `Python 3` 后加的 , 并且 `Python 3`也只有有 `copy (浅copy)` 这一个方法 , 用深 `copy` 需要我们导入 `copy` 模块 , 即 `import copy` - -## 排序&翻转 - -```python -# 创建一个列表 -names = ['Alex', 'Lyon', 'Leon', 'CTO', 'BeiJing',"IT",21,"man"] -# 在python3中不同的数据类型不能一起排序,换成str -names[-2] = "21" -# 排序,顺序为数字>大写>小写 -names.sort() -# 打印names -print(names) -# 翻转 -names.reverse() -# 打印names -print(names) -''' -执行结果: -['21', 'Alex', 'BeiJing', 'CTO', 'IT', 'Leon', 'Lyon', 'man'] -['man', 'Lyon', 'Leon', 'IT', 'CTO', 'BeiJing', 'Alex', '21'] -''' -``` - -所有方法如下 : - -```python - | append(...) - | L.append(object) -> None -- append object to end - | - | clear(...) - | L.clear() -> None -- remove all items from L - | - | copy(...) - | L.copy() -> list -- a shallow copy of L - | - | count(...) - | L.count(value) -> integer -- return number of occurrences of value - | - | extend(...) - | L.extend(iterable) -> None -- extend list by appending elements from the iterable - | - | index(...) - | L.index(value, [start, [stop]]) -> integer -- return first index of value. - | Raises ValueError if the value is not present. - | - | insert(...) - | L.insert(index, object) -- insert object before index - | - | pop(...) - | L.pop([index]) -> item -- remove and return item at index (default last). - | Raises IndexError if list is empty or index is out of range. - | - | remove(...) - | L.remove(value) -> None -- remove first occurrence of value. - | Raises ValueError if the value is not present. - | - | reverse(...) - | L.reverse() -- reverse *IN PLACE* - | - | sort(...) - | L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE* -``` - diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/06-\345\255\227\345\205\270.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/06-\345\255\227\345\205\270.md" deleted file mode 100644 index a273a0a78..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/06-\345\255\227\345\205\270.md" +++ /dev/null @@ -1,227 +0,0 @@ -# Attack on Python - 字典 🐍 - - - - - - - - - - - - -## 介绍 - -字典是一种 `key - value` 的数据类型 , 用 冒号 `" : "` 来关联键值对 , 每个对象之间用逗号 `" , "` 分割 , 整个字典包括在花括号 `"{ }"` 中 - -字典中的键(key)是唯一的 , 但值(value)则不必 - -字典是变长对象 , 在 `Python 3.5` 之前字典是无序的 , 但是在 `3.6` 之后官方就已经改成有序的了 , 所以在使用时需要注意一下 - -注意 : `key` 是不可变的 , 所以可变对象无法作为字典的 `key` , 如 : `list` , 对于不可变的数据类型则可以 , 如 : `str`、`int`、`tuple` - -## 创建 - -```python -# 创建一个空字典 -empty_info = {} -# 创建一个字典 -info = {"name":"Lyon","age":21} -# 也可调用dict()方法 -info = dict() -``` - -## 增加 - -```python -# 创建一个字典 -info = {"name":"Lyon","age":21} -# 增加新的键/值对 -info["school"] = "university" -# 打印info -print(info) -# 注:字典是无序的,所以打印结果也是随机打印 -''' -执行结果: -{'school': 'university', 'age': 21, 'name': 'Lyon'} -''' -``` - -## 修改 - -```python -# 创建一个字典 -info = {"name":"Lyon","age":21,"school":"university"} -# 修改age -info["age"] = 18 -# 打印info -print(info) -''' -执行结果: -{'age': 18, 'school': 'university', 'name': 'Lyon'} -''' -``` - -## 删除 - -```python -# 创建一个字典 -info = {"name":"Lyon","age":21,"school":"university"} -# 标准删除姿势 -info.pop("school") -# 打印info -print(info) -# 换个姿势 -del info["age"] -# 打印info -print(info) -# 随机删除 -info.popitem() -# 打印info -print(info) -''' -执行结果: -{'name': 'Lyon', 'age': 21} -{'name': 'Lyon'} -{} -''' -``` - -## 查找 - -```python -# 创建一个字典 -info = {"name":"Lyon","age":21,"school":"university"} -# 标准查找,判断name是否在字典info中 -print("name" in info) #打印:True -# 获取值 -print(info.get("name")) #打印:Lyon -# 换换姿势 -print(info["name"]) #打印:Lyon -# 这种方式要注意如果key不存在就会报错,而get仅仅返回None -print(info["home"]) -# 报错:KeyError: 'home' -''' -执行结果: -True -Lyon -Lyon -KeyError:'home' -''' -``` - -## 遍历 - -```python -# 创建一个字典 -info = {"name":"Lyon","age":21,"school":"university"} -# 方法1,推荐 -for key in info: - print(key,info[key]) -# 方法2 -for k,v in info.items(): - print(k,v) -''' -执行结果: -school university -name Lyon -age 21 -school university -name Lyon -age 21 -''' -``` - -## 嵌套 - -```python -# 创建一个多级嵌套字典 -datas ={ - '湖北省':{ - "武汉市":{ - "武昌区":["Hello"], - "洪山区":["Sorry"], - "江夏区":["Welcome"], - }, - }, - '湖南省':{ - "长沙市":{ - "岳麓区":{}, - "天心区":{}, - "芙蓉区":{}, - }, - }, - '广东省':{ - "佛山市":{ - "三水区":{}, - "顺德区":{}, - "男海区":{}, - }, - }, -} -# 修改最里层的value -datas["湖北省"]["武汉市"]["武昌区"].append("Lyon") -# 打印结果 -print(datas["湖北省"]["武汉市"]) -''' -执行结果: -{'洪山区': ['Sorry'], '武昌区': ['Hello', 'Lyon'], '江夏区': ['Welcome']} -''' -``` - -## 更多 - -```python -len(dict) # 计算字典元素个数 -dict.clear() # 清空词典所有条目 -dict.fromkeys(seq, val)) # 创建一个新字典,以列表 seq 中元素做字典的键,val 为字典所有键对应的初始值 -dict.has_key(key) # 如果键在字典dict里返回true,否则返回false -dict.items() # 以列表返回可遍历的(键, 值) 元组数组 -dict.keys() # 以列表返回一个字典所有的键 -dict.values() # 以列表返回字典中的所有值 -dict.setdefault(key, default=None) # 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default -dict.update(dict2) # 把字典dict2的键/值对更新到dict里 -``` -方法合集 - -```python - | clear(...) - | D.clear() -> None. Remove all items from D. - | - | copy(...) - | D.copy() -> a shallow copy of D - | - | fromkeys(iterable, value=None, /) from builtins.type - | Returns a new dict with keys from iterable and values equal to value. - | - | get(...) - | D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None. - | - | items(...) - | D.items() -> a set-like object providing a view on D's items - | - | keys(...) - | D.keys() -> a set-like object providing a view on D's keys - | - | pop(...) - | D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - | If key is not found, d is returned if given, otherwise KeyError is raised - | - | popitem(...) - | D.popitem() -> (k, v), remove and return some (key, value) pair as a - | 2-tuple; but raise KeyError if D is empty. - | - | setdefault(...) - | D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D - | - | update(...) - | D.update([E, ]**F) -> None. Update D from dict/iterable E and F. - | If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] - | If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v - | In either case, this is followed by: for k in F: D[k] = F[k] - | - | values(...) - | D.values() -> an object providing a view on D's values -``` - diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/07-\351\233\206\345\220\210.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/07-\351\233\206\345\220\210.md" deleted file mode 100644 index 9689cc9d2..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/07-\351\233\206\345\220\210.md" +++ /dev/null @@ -1,181 +0,0 @@ -# Attack on Python - 集合 🐍 - - - - - - - - - - - - -## 介绍 - -集合是变长对象 , 集合是无序且不重复的数据组合 , 因此我们可以用来做 : - -- 去重 , 把一个列表变成集合 , 就自动去重了 -- 集合运算 , 求两个集合的并集 , 交集 , 差集 , 对称差集 - -在 `Python 2.7` 中集合表示如下 : - -```python -set([1,2,3]) -``` - -在 `Python 3.x` 中则是如下 : - -```python -{1,2,3} -``` - -我们可以通过 `set()` 方法 , 将 `list` 和 `tuple` 转换为集合 : `set(list())` , `set(tuple())` - -## 创建 - -与字符串等数据类型一样 , 集合支持如下方式创建 - -```python -# 创建空集合只能用这种方式,参数为一个可迭代对象 -s = set() -# 注意集合是单个元素,字典是键值对 -s = {1,2,3} -``` - -## 添加 - -为集合添加元素 - -```python -# 定义集合 -s = {'lyon','kenneth'} -# 添加一项 -s.add('geek') -``` - -注意 : 集合不支持 "+" - -## 更新 - -```python -# 定义集合 -s = {'lyon','kenneth'} -# 添加多项,参数为可迭代对象 -s.update(['1','2','3']) -``` - -## 删除 - -```python -# 定义集合 -s = {'lyon','kenneth'} -# 删除一项 -s.remove('kenneth') -# 清空集合 -s.clear() -``` - -## 关系运算 - -```python -a = {1,2,3,4,5} -b = {1,2,3} -# 测试是否b中的每一个元素都在a中,即 b<=a ,返回bool值 -b.issubset(a) -# 测试是否a中的每一个元素都在b中,即 b>=a ,返回bool值 -b.issuperset(a) -``` - -## 集合操作 - -```python ->>>a = {1,2,3} ->>>b = {4,5,6} -# 求并集 ->>>a.union(b) -# 同上,求并集 ->>>a | b -# 求交集 ->>>a.intersection(b) -# 同上,求交集 ->>>a & b -# 求差集 ->>>a.difference(b) -# 同上,求差集 ->>>a - b -# 求对称差集 ->>>a.symmetric_difference(b) -# 同上,求对称差集 ->>>a ^ b -``` - -集合对象所有方法 - -```python - | add(...) - | Add an element to a set. - | - | This has no effect if the element is already present. - | - | clear(...) - | Remove all elements from this set. - | - | copy(...) - | Return a shallow copy of a set. - | - | difference(...) - | Return the difference of two or more sets as a new set. - | - | (i.e. all elements that are in this set but not the others.) - | - | difference_update(...) - | Remove all elements of another set from this set. - | - | discard(...) - | Remove an element from a set if it is a member. - | - | If the element is not a member, do nothing. - | - | intersection(...) - | Return the intersection of two sets as a new set. - | - | (i.e. all elements that are in both sets.) - | - | intersection_update(...) - | Update a set with the intersection of itself and another. - | - | isdisjoint(...) - | Return True if two sets have a null intersection. - | - | issubset(...) - | Report whether another set contains this set. - | - | issuperset(...) - | Report whether this set contains another set. - | - | pop(...) - | Remove and return an arbitrary set element. - | Raises KeyError if the set is empty. - | - | remove(...) - | Remove an element from a set; it must be a member. - | - | If the element is not a member, raise a KeyError. - | - | symmetric_difference(...) - | Return the symmetric difference of two sets as a new set. - | - | (i.e. all elements that are in exactly one of the sets.) - | - | symmetric_difference_update(...) - | Update a set with the symmetric difference of itself and another. - | - | union(...) - | Return the union of sets as a new set. - | - | (i.e. all elements that are in either set.) - | - | update(...) - | Update a set with the union of itself and others. -``` \ No newline at end of file diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/08-\345\255\227\347\254\246\347\274\226\347\240\201.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/08-\345\255\227\347\254\246\347\274\226\347\240\201.md" deleted file mode 100644 index 1c65214ad..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/08-\345\255\227\347\254\246\347\274\226\347\240\201.md" +++ /dev/null @@ -1,127 +0,0 @@ -# Attack on Python - 字符编码 🐍 - - - - - - - - - - - - -## 介绍 - -**字符编码** - -字符编码 (Character encoding) 也称字集码 , 它是一套**法则** , 使用该法则能够对自然语言的字符的一个集合 (如字母表或音节表) , 与其他东西的一个集合 (如号码或电脉冲) 进行配对 , 即在符号集合与数字系统之间建立对应关系 - -再简单一点说其实就是一张具有对应关系的表格 , 如下 - -``` -+----+-----------+ -| id | character | -+----+-----------+ -| 65 | A | -| 66 | B | -| 67 | C | -+----+-----------+ -``` - -如上表所示 , 这就是一套法则 , 使我们用数字成功的表示了字符 - -> *为什么要一套这样的法则 ?* - -众所周知 , 计算机只认识机器码 , 也就是一堆0101之类的二进制数字 , 计算机并不认识我们的 "A" , "B" ,"C" , 我们为了使其友好的显示 , 就需要一套这样的法则 , 来完成这些转换 , 于是两个名词诞生了 - -**编码** - -通俗的说 , 就是按照何种规则将字符存储在计算机中 . 比如 "A" 用65表示 , 也就是把字符"A"以二进制的方式存储在计算机中 - -**解码** - - 反之 , 将存储在计算机中的二进制数解析显示出来 , 这就是解码 - -在Python中 - -```python -'''既然是对于字符,那么自然对应着Python中的字符串了''' -'''Python中提供了两个函数来完成编码和解码''' -# 编码函数encode() -encode() -character → byte -# 解码函数decode() -byte → character -``` - -**PS : 必须采用相对应的法则 , 否则就会出错 , 也就是我们常说的乱码** - -最后还有一个名词 , 字符集 - -**字符集** - -是一个系统支持的所有抽象字符的集合 , 字符是各种文字和符号的总称 , 包括各国家文字、标点符号、图形符号、数字等 - -字符编码就是在字符集与数字系统之间建立的对应关系 - -## ASCII - -ASCII (American Standard Code for Information Interchange , 美国信息交换标准码) , 是基于拉丁字母的一套电脑编码系统 , 主要用于显示现代英语 - -ASCII字符集 : 主要包括控制字符 (回车键 , 退格 , 换行键等) , 可显示字符 (英文大小写字符 , 阿拉伯数字和西文符号) - -ASCII编码 : 将ASCII字符集转换为计算机可以接收的数字系统的数的规则 , 使用7位(Bit)表示一个字符 , `1 Byte = 8 Bit` , 一共可以表示128(2的7次方)个字符 - -具体ASCII字符集映射到数字编码规则可以自行查询 - -## ANSI - -ANSI编码为在ASCII编码(7位)的基础上 , 将其最后一位也使用上 , 即使用8位 - -ANSI使使计算机支持更多语言 , 通常对于没超过128的即用ASCII编码 , 超过的即用扩展的ASCII编码ANSI - -当然不同的国家和地区指定了不同的标准 , 由此产生了GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准 - -在简体中文Windows操作系统中 , ANSI 编码代表 GBK 编码 ; 在繁体中文Windows操作系统中 , ANSI编码代表Big5 ; 在日文Windows操作系统中 , ANSI 编码代表 Shift_JIS 编码 - -## GBXXX - -**GB2312编码** - -计算机发明之初及后面很长一段时间 , 只应用于美国及西方一些发达国家 , 于是到中国时 , 一个字节8位 , 256个字符是远远不能满足的 , 要想想中国有多少汉字 , 于是聪明的中国人这样规定 : - -**一个小于127的字符的意义与原来相同 , 但是两个大于127的字符连在一起时 , 就表示一个汉字** , 前面的一个字节称为高字节 , 后面的为低字节 , 这样就组合出了大约7000多个简体汉字了 , 这就是`GB2312` ,全称 `信息交换用汉字编码字符集 ▪ 基本集` - -**GB18030** - -由于7000多个汉字还是不够用 , 于是继续改进 , 每个汉字可以由1个 , 2个或4个字节组成 , 于是庞大的编码空间形成了 , 最多可以定义161万个字符 , 这就是`GB18030` , 全称 `信息技术中文编码字符集` - -## Unicode - -各种各样的字符编码都出来了 , 大家各用各的 , 那么问题就来了 , 一旦出现在网络上 , 由于不兼容 , 互相访问就出现了乱码现象 , 为了解决这个问题 , Unicode编码系统应运而生 - -Unicode编码系统为表达任意语言的任意字符而设计 , 它使用2字节的数字来表达每个字母 , 符号 , 或者表意文字 , 每个数字代表唯一的至少在某种语言中使用的符号 (并不是所有的数字都用上了 , 但是总数已经超过了65535 所以2个字节的数字是不够用的) - -总而言之 , Unicode是业界的一种标准 , 也叫做统一码 , 万国码 , 单一码 , 标准万国码 - -所以Unicode编码也成为了一个编码转换的基础 , 因为大家都支持他 , 从一种编码到另一中编码 , 只需要Unicode在中间搭桥就能简单的实现了 - -**UTF - 8** - -对于Unicode来讲 , 任何字符都使用2个字节来存储 , 这明显是很浪费内存的 , 因为我们编写代码时 , 用到中文毕竟极少 , 所以为了节省内存 , 就有了`UTF-8` , UTF - 8规定 , 英文只使用1个字节 , 中文使用3个字节 - -虽然说UTF - 8具有良好的国际兼容性 , 但中文需要比GBK/BIG5版本多占用50%的数据库存储空间 , 因此并不推荐使用 - -## Python编码处理 - -在Python3中 , 源代码读取进行语法校验时 , 会将源代码中的字符串从声明的编码转换成Unicode类型 , 等到语法校验通过后 , 再将这些字符换回初始的编码 , 这也就是说 , Python3中 , 字符串默认编码就是Unicode - -查看默认编码 - -```python ->>> import sys ->>> sys.getdefaultencoding() -``` - -**PS : Windows下命令行的字符编码默认是GBK ; 并且Python2中 , 字符串是有两种类型的 , 这里不多说明** \ No newline at end of file diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/09-\346\226\207\344\273\266\346\223\215\344\275\234.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/09-\346\226\207\344\273\266\346\223\215\344\275\234.md" deleted file mode 100644 index 9acca1082..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/09-\346\226\207\344\273\266\346\223\215\344\275\234.md" +++ /dev/null @@ -1,272 +0,0 @@ -# Attack on Python - 文件操作 🐍 - - - - - - - - - - - - -## 介绍 - -在磁盘上读写文件的功能都是由操作系统提供的 , 现代操作系统不允许普通的程序直接操作磁盘 , 所以 , 读写文件就是请求操作系统打开一个文件对象 (通常称为文件描述符) ; 然后 , 通过操作系统提供的接口从这个文件对象中读取数据 (读文件) , 或者把数据写入这个文件对象 (写文件) - -在Python中我们进行文件操作需要首先利用`open()` 函数获取一个文件流来操作文件 - -这个流就是我们所使用的文件描述符 , 是一个I/O通道 - -## open() - -```python -open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): - """ - file:文件名 - mode:模式 - buffering:设置缓冲策略 - encoding:指定使用编码 - errors:指定处理编码和解码错误的方式 - newline:控制通用换行模式的工作方式(只适用文本模式) - closefd:如果为False并且给出了文件描述符而不是文件名,则文件关闭时,文件描述符将保持打开;如果给定文件名,则closefd必须为True,否则将抛出异常 - opener:自定义开启器 - """ -``` - -对于上述参数中 , 我们主要需要了解的就是`file` , `mode` , `encoding` 这三个 - -对于mode , 有以下模式 : - -| Character | Meaning | -| --------- | ---------------------------------------- | -| `'r'` | open for reading (default) | -| `'w'` | open for writing, truncating the file first | -| `'x'` | open for exclusive creation, failing if the file already exists | -| `'a'` | open for writing, appending to the end of the file if it exists | -| `'b'` | binary mode | -| `'t'` | text mode (default) | -| `'+'` | open a disk file for updating (reading and writing) | -| `'U'` | [universal newlines](https://docs.python.org/3.5/glossary.html#term-universal-newlines) mode (deprecated) | - -常使用的就是`'r'` , `'w'` , `'a'` , `'+'` , `'b'` , 当然还可以组合使用 , 下面进行详细介绍 : - -- r , 只读模式 , 文件必须已经存在 -- r+ , 可读可写模式 , 文件必须已经存在 -- w , 只写模式 , 会重新创建 , 意味着文件如果已存在会被空文件覆盖 -- w+ , 可写可读模式 , 同样会创建文件 -- a , 追写模式 , 文件不存在参考'w' -- a+ , 追写并可读模式 , 文件不存在参考'w' -- b , 以二进制的模式进行处理 (Linux可忽略 , Windows处理二进制文件时需标注) , 可以用该模式来读取图片 , 视频等等 - - rb , 同r - - wb , 同w - - ab , 同a - -简单实例 - -file.txt - -```txt -A man is not old until his regrets take place of his dreams.   - -Nothing can help us endure dark times better than our faith. - -No one but ourselves can degrade us. -``` - -实例 - -```python -f = open('file.txt','r') -contents = f.read -print(contents) -''' -执行结果: -A man is not old until his regrets take place of his dreams.   - -Nothing can help us endure dark times better than our faith. - -No one but ourselves can degrade us. -''' -``` - -## file-like object - -以下内容可以学习完模块篇之后再继续学习 - -`io` 模块提供了Python处理各种类型I/O的主要工具 , 有三种主要类型 , 即`文本I/O` , `二进 制I/O`和`原始I/O` , 这些是通用类别 , 并且可以为它们中的每一个使用各种后备存储 - -三种主要类型详细见 : [`TextIOBase`](https://docs.python.org/3.5/library/io.html?highlight=io#io.TextIOBase) , [`BufferedIOBase`](https://docs.python.org/3.5/library/io.html?highlight=io#io.BufferedIOBase) , [`RawIOBase`](https://docs.python.org/3.5/library/io.html?highlight=io#io.RawIOBase) - -属于这些类别中的任何一个的具体对象称为`file-like object` - -创建这些类别的具体对象最简单的方法就是使用内置的`open()` 函数 , 其也被定义在io模块中 , 下面仅介绍一些这些类别对象常用的方法 : - -```python -detach() -''' -Separate the underlying binary buffer from the TextIOBase and return it. - -After the underlying buffer has been detached, the TextIOBase is in an unusable state. - -Some TextIOBase implementations, like StringIO, - may not have the concept of an underlying buffer and calling this method will raise UnsupportedOperation. - -New in version 3.1. -''' - -read(size) -''' -Read and return at most size characters from the stream as a single str. -If size is negative or None, reads until EOF. -''' - -readline(size=-1) -''' -Read until newline or EOF and return a single str. -If the stream is already at EOF, an empty string is returned. - -If size is specified, at most size characters will be read. -''' - -readlines(hint=-1) -''' -Read and return a list of lines from the stream. hint can be specified to control the number of lines read: no more lines will be read if the total size (in bytes/characters) of all lines so far exceeds hint. - -Note that it’s already possible to iterate on file objects using for line in file: ... without calling file.readlines(). -''' - -readable() -''' -Return True if the stream can be read from. -If False, read() will raise OSError. -''' - -write(s) -''' -Write the string s to the stream and return the number of characters written. -''' - -writable() -''' -Return True if the stream supports writing. -If False, write() and truncate() will raise OSError. -''' - -writelines(lines) -''' -Write a list of lines to the stream. -Line separators are not added, -so it is usual for each of the lines provided to have a line separator at the end. -''' - -seek(offset[, whence]) -''' -Change the stream position to the given offset. -Behaviour depends on the whence parameter. -The default value for whence is SEEK_SET. - -SEEK_SET or 0: seek from the start of the stream (the default); -offset must either be a number returned by TextIOBase.tell(), or zero. -Any other offset value produces undefined behaviour. -SEEK_CUR or 1: “seek” to the current position; -offset must be zero, which is a no-operation (all other values are unsupported). -SEEK_END or 2: seek to the end of the stream; -offset must be zero (all other values are unsupported). -Return the new absolute position as an opaque number. - -New in version 3.1: The SEEK_* constants. -''' - -tell() -''' -Return the current stream position as an opaque number. -The number does not usually represent a number of bytes in the underlying binary storage. -''' - -close() -''' -Flush and close this stream. -This method has no effect if the file is already closed. -Once the file is closed, -any operation on the file (e.g. reading or writing) will raise a ValueError. - -As a convenience, it is allowed to call this method more than once; -only the first call, however, will have an effect. -''' - -fileno() -''' -Return the underlying file descriptor (an integer) of the stream if it exists. An OSError is raised if the IO object does not use a file descriptor. -''' - -flush() -''' -Flush the write buffers of the stream if applicable. -This does nothing for read-only and non-blocking streams. -''' - -isatty() -''' -Return True if the stream is interactive (i.e., connected to a terminal/tty device). -''' - -seek(offset[, whence]) -''' -Change the stream position to the given byte offset. -offset is interpreted relative to the position indicated by whence. -The default value for whence is SEEK_SET. Values for whence are: - -SEEK_SET or 0 – start of the stream (the default); -offset should be zero or positive -SEEK_CUR or 1 – current stream position; -offset may be negative -SEEK_END or 2 – end of the stream; -offset is usually negative -Return the new absolute position. - -New in version 3.1: The SEEK_* constants. - -New in version 3.3: Some operating systems could support additional values, -like os.SEEK_HOLE or os.SEEK_DATA. -The valid values for a file could depend on it being open in text or binary mode. -''' - -seekable() -''' -Return True if the stream supports random access. -If False, seek(), tell() and truncate() will raise OSError. -''' - -truncate(size=None) -''' -Resize the stream to the given size in bytes (or the current position if size is not specified). -The current stream position isn’t changed. -This resizing can extend or reduce the current file size. -In case of extension, the contents of the new file area depend on the platform (on most systems, additional bytes are zero-filled). -The new file size is returned. - -Changed in version 3.5: Windows will now zero-fill files when extending. -''' -``` - -注意 : 当使用完文件后一定要记得使用`close()` 方法将其关闭 ; 其次在进行文件操作时要注意文件描述符所在的位置 - -## with - -为了避免打开文件后忘记手动关闭 , 可以通过管理上下文 , 即使用`with`语句 , 如下 : - -```python -with open('filepath','mode') as f: - pass -``` - -在Python 2.7以上的版本 , 支持同时对多个文件同时进行上下文管理 , 如下 : - -```python -with open('filepath1','mode') as f1,open('filepath2','mode') as f2: - pass -``` - -更多文档资料 : https://docs.python.org/3.5/library/io.html?highlight=io#module-io \ No newline at end of file diff --git "a/01-Python/01-\345\237\272\347\241\200\347\257\207/README.md" "b/01-Python/01-\345\237\272\347\241\200\347\257\207/README.md" deleted file mode 100644 index ef69d25f1..000000000 --- "a/01-Python/01-\345\237\272\347\241\200\347\257\207/README.md" +++ /dev/null @@ -1,46 +0,0 @@ -# Attack on Python - 基础篇 🐍 - - - - - - - - - - -## 前言 - -基础篇中的内容 , 应对的是 `Python` 的基础语法 , 以及基础数据类型的文章 - -在开始之前 , 你可以熟悉一下 `Python` 的语言参考 : [The Python Language Reference](https://docs.python.org/3/reference/index.html) - -最好的教程就是官方文档 , 所以阅读官方文档是一个好的学习习惯 - -## 介绍 - -Python基础主要包括基础语句 , 基础数据类型 , 字符编码 , 文件操作等 - -## 基础语句 - -- Hello World -- 变量 -- 行和缩进 -- 多行语句 -- 注释 -- input -- print -- 数据运算 -- 条件语句 -- for -- while - -## 数据类型 - -- 数字 , Number -- 字符串 , String -- 元组 , Tuple -- 列表 , List -- 字典 , Dictionary -- 集合 , Set - diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/01-\345\207\275\346\225\260\345\237\272\347\241\200.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/01-\345\207\275\346\225\260\345\237\272\347\241\200.md" deleted file mode 100644 index b3236032e..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/01-\345\207\275\346\225\260\345\237\272\347\241\200.md" +++ /dev/null @@ -1,321 +0,0 @@ -# Attack on Python - 函数基础 🐍 - - - - - - - - - - - - -## 介绍 - -函数是组织好的 , 可重复使用的 , 用来实现单一 , 或相关联功能的代码段 - -函数能提高应用的模块性 , 和代码的重复利用率 , 比如我们一直使用的`print()` , `input()` 等等 , 都是函数 - -如下我们写了一个用户认证程序 ; 而现在我们又需要写一个用户管理系统 , 管理系统中有很多的功能 , 比如添加用户 , 删除用户 , 查询用户 , 修改用户 ; 但是这些功能必须先通过用户认证程序才能使用 , 明显我们不可能在每一个功能前加上一段用户认证代码 , 因为这将大大增加我们的重复代码 - -那么为了解决这个问题我们就可以将用户认证功能封装到一个函数之中 , 而后续我们如果需要使用这个函数仅需调用即可 , 着就是函数的魅力所在 , 当然更多的还是通过下面进一步了解函数 - -## 定义函数 - -```python -# 自定义函数,function_name为函数名 -def function_name(): - """注释""" - - ''' - 功能代码块 - ''' - # 返回值,一般都具有返回值,当然也不可以不设定 - return result -``` - -简单实例 - -```python -def hello(): - print("Hello Lyon!") - return None -``` - -**注意 :** 上述仅为定义函数 , 函数并不会执行 , 只有当函数被调用时 , 函数内部代码才会执行 - -## 函数调用 - -函数调用通过*函数名*后加`()` 进行调用 , 如下 : - -```python -# 定义函数 -def hello(): - print("Hello Lyon!") - return None -# 调用函数 -hello() -``` - -既然函数调用是通过函数名后加括号 , 在这个固定语法之中前者函数名有是什么? 如下 : - -```python -# 定义函数 -def hello(): - print("Hello Lyon!") - return None -# 打印函数名 -print(hello) -''' -执行结果: - -''' -``` - -我们可以发现 , 函数名打印出来的是一个内存地址 , 由此不难理解 : - -**函数名相当于一个变量 , 而变量所绑定的对象就是函数对象本身 ; ** - -## 参数说明 - -**形参 :** 变量只有在被调用时才分配内存单元 , 在调用结束时 , 即刻释放所分配的内存单元 ; 因此 , 形参只在函数内部有效 , 函数调用结束返回主调用函数后则不能再使用该形参变量 - -**实参 : **可以是常量、变量、表达式、函数等 , 无论实参是何种类型的量 , 在进行函数调用时 , 它们都必须有确定的值 , 以便把这些值传送给形参 ; 因此应预先用赋值 , 输入等办法使参数获得确定值 - -```python -# 定义函数func -def func(argument1,argument2): # argument1与argument2都为形参,形式参数 - print(argument1,argument2) - -# 调用函数func -func("Hello", "Lyon") # Hello和Lyon都是实参,实际参数 -''' -执行结果: -Hello Lyon -''' -``` - -**位置参数 :** 即参数必须以正确的顺序传入函数 , 传入的数量必须和声明的一样 , 不一样就报错 - -```python -# 用户登录验证 -def login(username,password): - if username == "Lyon" and password == "123456": - print("Login successfully!") - else: - print("Login failed!") -# 进行调用 -login("Lyon","123456") -# 进行调用 -login("Lyon","78910JkQ") -''' -执行结果: -Login successfully! -Login failed! -''' -``` - -### 默认参数 - -调用时不指定就以默认值传入 , 指定则按指定值传入 - -```python -# 同时定义位置参数和默认参数 -def add_userinfo(name,age,province="北京"): - return name,province -# 位置参数必填,默认参数可选 -add_userinfo("Lyon",18) -''' -执行结果: -('Lyon', '北京') -''' -``` - -注:通过默认参数,我们就算不传参数也不会报错 , 即`province` 默认为`"北京"` - -### 关键字参数 - -正常情况下 , 给函数传参数的时候要按照顺序传 , 如果不想按照顺序就可以使用关键参数 - -```python -def add_userinfo(name,age,province="北京"): - return name,province -add_userinfo("Lyon",province="湖北",age=18) -# 注意关键参数不用按照顺序传入,但是关键参数必须写在位置参数后面 -``` - -### 非固定参数 - -当我们想要传入多个参数 , 但是我们又不确定的时候就可以使用非固定参数 ; 非固定参数有两个 , 一个 `*args (元组形式)` 以及 `**kwargs (字典形式) ` - -```python -# 设定两个非固定参数 -def main(*args,**kwargs): - # 打印args,以及args的类型 - print(args,type(args)) - # 打印kwargs,以及kwargs的类型 - print(kwargs,type(kwargs)) -# 调用 -main((1,2,3,4),{1:2,3:4}) -``` - -对于非固定参数 , 其主要在于`*` 号 , `*` 号的作用是进行打包与解包 : - -- 一个`*` 号 , 则表示打包成元组或者将元组进行解包 , 过程如下 : - - ```python - def main(n,*args): - return args - # 传递参数,第一个参数被认为是位置参数n,余后参数*号将会对其进行打包成元组,但参数形式必须符合元组规范 - result = main(1,2,3,4,5) - print(result) - ''' - 执行结果: - (2, 3, 4, 5) - ''' - ''' - 额外说明: - 传递参数时,*号将参数封装成一个元组,即元组args - ''' - ``` - -- 两个`**` 号 , 则表示打包成字典或者将字典进行解包 , 过程如下 : - - ```python - def main(**kwargs): - return kwargs - # 传递参数,**号将会对其进行打包成字典,但参数形式必须符合字典规范,即必须key-value - result = main(n2=2,n3=3,n4=4) - print(result) - ''' - 执行结果: - {'n4': 4, 'n2': 2, 'n3': 3} - ''' - ''' - 额外说明: - 传递参数时,**号将参数封装成一个字典,即字典kwargs - ''' - ``` - -- 两者的解包如下 : - - ```python - # 进行打包 - def main(*args,**kwargs): # 参数状态:(1,2,3,4,5){'n1':1,'n2':2,'n3'=3} - # 进行解包 - return (*args),{**kwargs} # 参数状态:1,2,3,4,5,n1=1,n2=2,n3=3 - result = main(1,2,3,4,5,n1=1,n2=2,n3=3) - print(result) - ''' - 执行结果: - (1, 2, 3, 4, 5, {'n2': 2, 'n3': 3, 'n1': 1}) - ''' - # 解包补充 - '''只要是可迭代对象我们都可以对其进行解包,如下''' - mytuple = (1,2,3,4,5,6,7) - # _为占位符,*c打包成列表 - a,_,b,*c,d = mytuple - print(a) - print(b) - print(c) - print(d) - ''' - 执行结果: - 1 - 3 - [4, 5, 6] - 7 - ''' - ``` - -### 参数顺序与传递 - -**参数顺序** - -在函数头部 (定义参数) : 一般参数 → 默认参数 → 非固定参数`*args` → 非固定参数`**kwargs` - -在函数调用中 (传递参数) : 位置参数 → 关键字参数 → 默认参数 → 非固定参数`*args` → 非固定参数`**kwargs` - -**参数传递** - -在我们使用过程中 , 如果没有非固定参数 , 那么我们的关键参数或者默认参数可以用关键字进行传递 ; 如果有非固定参数 , 必须按照位置参数的方式进行传递 - -默认参数和非固定参数`*args`位置可以进行调换 , 调换后默认参数传递需要加上关键字 - -## 全局与局部变量 - -局部变量:只在函数内部起作用的变量 - -全局变量:在整个程序中都起作用 - -```python -# 全局变量name -name = "Lyon" -def func(name): - print(name) - # 局部变量name - name = "Kenneth" - print(name) -# 调用函数 -func(name) -print(name) -''' -执行结果: -Lyon -Kenneth -Lyon -''' -``` - -总结 : 全局变量**作用域**是整个程序 , 局部变量**作用域**是定义该变量的子程序 ; 当全局变量与局部变量同名时 : 在定义局部变量的子程序内 , 局部变量起作用 ; 在其他地方全局变量起作用 - -**global语句 : **可以将局部变量变成全局变量 , 在函数内部变量前加上 global 即可如 : `global name` - -## return语句 - -`return` 语句用于返回函数的执行结果 , 比如操作类函数一般都不需要返回值 , 当然可由我们的需要自己进行设定 - -不使用`return` 即返回None , 没有返回值 - -我们函数在执行过程中如果遇到return语句 , 就会结束并返回结果 - -```python -def sum( arg1, arg2 ): - # 返回2个参数的和 - total = arg1 + arg2 - print("两数之和:",total) - return total - # 上一步函数就已经结束,不会往下执行 - print("已经返回!") -# 调用sum函数 -total = sum( 10, 20 ) -''' -执行结果: -两数之和: 30 -''' -``` -如果我们返回函数名 - -```python -def func(): - print("I am Lyon") - # 返回func,函数名 → 内存地址 - return func -# result1接收返回值func函数名 -result1 = func() -# 返回一个函数对象 -print(result1) -# 可以继续调用 -result2 = result1() -print(result2) -result2() -''' -执行结果: -I am Lyon - -I am Lyon - -I am Lyon -''' -``` diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/02-\345\214\277\345\220\215\345\207\275\346\225\260.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/02-\345\214\277\345\220\215\345\207\275\346\225\260.md" deleted file mode 100644 index f9c3e3136..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/02-\345\214\277\345\220\215\345\207\275\346\225\260.md" +++ /dev/null @@ -1,103 +0,0 @@ -# Attack on Python - 匿名函数 🐍 - - - - - - - - - - - - -## 介绍 - -匿名函数顾名思义就是一个没有名字的函数 , 我们可以通过 `lambda` 关键字来定义 - -`lambda` 是一个表达式 , 而并非语句 , 所以可以出现在def语句所不能出现的位置 , 并且不需要指定函数名; `lambda` 表达式还可以提高代码的可读性 , 简化代码 - -`lambda` 表达式主要用于写一些简单的方法 , 对于复杂的还是用函数写的好 - -示例: - -```python -# 普通函数 -def func(x): - return x * x -print(func(5)) ------------------------ -# 匿名函数,自带return功能 -func = lambda x : x * x -print(func(5)) ---------------------------------------------------- -func = lambda arguments : expression using argument -``` - -使用匿名函数可以减少命名空间使用内存 , 因为没有函数名 - -可直接后面传递参数 - -```python ->>> (lambda x,y : x if x > y else y)(1,2) -2 -``` - -非固定参数 - -```python ->>> (lambda *args : args)(1,2,3,4) -(1, 2, 3, 4) -``` - -***PS : 匿名函数主要是与其他函数搭配使用*** - -## 运用 - -***结合使用*** - -map , 计算平方 - -```python -# map后返回的对象为map对象,所以利用list方法进行强转 ->>> list(map(lambda x : x * x, [1,2,3,4])) -[1,4,9,16] -``` - -filter , 筛选偶数 - -```python ->>> list(filter(lambda x : x % 2 == 0,[1,2,3,4])) -[2,4] -``` - -reduce , 求和 - -```python -# python3中已经没有reduce方法了,调用需要导入 ->>> from functools import reduce -# reduce(function, sequence, initial=None) ->>> reduce(lambda x , y : x + y, [1,2,3,4,5],100) -115 -``` - -***嵌套使用*** - -版本一 - -```python -def func(x): - return lambda x : x + y -f = func(2) -print(f(2)) -# output: 4 -``` - -版本二 - -```python -func = lambda x : (lambda y: x + y) -y = func(1) -y(2) -# output: 3 -``` \ No newline at end of file diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/03-\345\207\275\346\225\260\350\277\233\351\230\266.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/03-\345\207\275\346\225\260\350\277\233\351\230\266.md" deleted file mode 100644 index 1d7b9d75a..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/03-\345\207\275\346\225\260\350\277\233\351\230\266.md" +++ /dev/null @@ -1,350 +0,0 @@ -# Attack on Python - 函数进阶 🐍 - - - - -## 介绍 - -接下来我们会介绍一些函数更高级的用法 - -## 嵌套函数 - -嵌套函数即函数里面再套一个函数 , 如下 : - -```python -# 全局变量name -name = "Lyon_1" -def func(): - # 第一层局部变量name - name = "Lyon_2" - print("第1层打印",name) - - #嵌套 - def func2(): - # 第二层局部变量name - name = "Lyon_3" - print("第2层打印", name) - - # 嵌套 - def func3(): - # 第三层局部变量 - name = "Lyon_4" - print("第3层打印", name) - # 调用内层函数 - func3() - # 调用内层函数 - func2() -func() -print("最外层打印", name) -''' -执行结果: -第1层打印 Lyon_2 -第2层打印 Lyon_3 -第3层打印 Lyon_4 -最外层打印 Lyon_1 -''' -``` - -嵌套函数不能越级调用 , 也就是说我们不能在`func2` 的外部去调用`func3` , 当然反过来我们的代码就进入无限递归了 - -当然我们有时需要的就是在嵌套函数中 , 使用上一层的变量 , 那么我们可以使用`nonlocal` 语句 - -`nonlocal` 的作用就是改变变量的作用域 , 但是不会扩展到全局变量 , 即只能在函数内部改变 ; nonlocal声明之后 , 会从上层开始找并返回第一个变量 , 没找到则会报错 - -```python -def func(arg): - n = arg - def func1(): - n = 2 - def func2(): - nonlocal n # n = 2 - n += 1 - func2() - print(n) # n = 3 - func1() - print(n) -func(10) -''' -执行结果: -3 -10 -''' -``` - -## 高阶函数 - -高阶函数就是将一个函数以参数的形式传入另一个函数 - -```python -# 定义一个主函数,并设置一个参数func -def main_func(func): - # 返回func的值 - return func - -# 定义一个函数作为参数传入主函数 -def func(): - # 返回"Lyon"给func() - return "Lyon" - -# res接收main_func的返回值,将func()的返回值作为参数传入main_func函数 -res = main_func(func()) -print(res) -''' -执行结果: -Lyon -''' -``` - -## 闭包 - -闭包是一个结构体 , 闭包必须是内部定义的函数 (嵌套函数) , 该函数包含对外部作用域而不是全局作用域名字 (命名空间) 的引用 - -```python -def foo(): - # 局部变量name - name = 'Lyon' - # 内部定义的函数 - def bar(): - # 引用了外部定义的变量name,即内部函数使用外部函数变量,这一行为就叫闭包 - print("I am",name) - return "In the bar" - # 调用bar并打印结果 - print(bar()) - return "In the foo" -# 调用foo并打印结果 -print(foo()) -''' -执行结果: -I am Lyon -In the bar -In the foo -''' -``` - -我们可以通过查看函数对象的 `__closure__` 属性来显示的查看是否有闭包 - -```python -def foo(): - # 局部变量name - name = 'Lyon' - def bar(): - print("I am",name) - return "In the bar" - print(bar.__closure__) -foo() -''' -执行结果: -(,) -''' -``` - -1. 闭包的这种引用方式 , 我们完全可以把闭包当做一个局部的 `"全局命名空间"` , 也就是说它只是在闭包的作用域中是可见的 , 对外并不可见 , 且闭包只有调用时才会创建 , 所以每个闭包都是完全独立的 , 拥有自己的环境 - -2. 而且在闭包中被引用的变量的生命周期将会得到提升 , 只要有一个闭包引用了这个变量 , 它就会一直存在 - -我们来用两个例子加深一下印象 - -我们可以利用上面第一条所说的来实现一个累加器 - -```python -# 利用闭包实现一个累加器 -def add(): - count = [0] - def inner(): - count[-1] += 1 - return count[-1] - return inner - -adder1 = add() # 实例化第一个累加器 -adder2 = add() # 实例化第二个累加器 -print(adder1()) -print(adder1()) -print(adder1()) -print(adder2()) -''' -执行结果: -1 -2 -3 -1 -''' -``` - -可以看到两个累加器互不干扰 , 这就像对象的实例化 , 所以你应该知道了 , 闭包可以用来实现对象系统 - -我们再看看生命周期提升的好处 - -```python -# 方式一, 利用闭包 -def func(): - name = "Lyon" - def inner(): - hello_name = 'Hello' + name - [inner() for _ in range(10)] - -# 方式二, 不利用闭包 -def func(): - def inner(): - name = "Lyon" - hello_name = 'Hello' + name - [inner() for _ in range(10)] - -func() -""" -首先我们不讨论这段代码是否有实际意义, 只讨论它们的执行方式 -我们对比一下方式1和方式2, 它们两者的区别在于 name = "Lyon" 一个在inner外部, 一个在内部 -当func执行时 -方式1: 创建name变量, 然后执行10次inner函数 -方式2: 执行10次inner函数, 每次执行inner函数中, 创建name变量 -""" -``` - -通过代码 , 很明显 , 方式1只需要创建1次 `name` , 而方式2会创建10次 , 原因就在于当一个函数执行完毕 , Python 的垃圾回收机制会将无用的对象进行销毁 - -虽然从这里可以看出 , 闭包的使用可以提升某些时候的性能 , 但是同时 , 由于生命周期的提升 , 它将永远都不会被销毁 , 这不见得是一件好事 , 所以使用闭包还是需要注意不要滥用 - -我们再留一个思考 , 思考一下下面这道面试题的结果会是什么呢 - -```python -s = [lambda x: x + i for i in range(10)] -print(s[0](10)) -print(s[1](10)) -print(s[2](10)) -print(s[3](10)) -``` - -## 装饰器 - -装饰器即给原来的函数进行装饰的工具 - -装饰器由函数去生成 , 用于装饰某个函数或者方法 (类中的说法) , 它可以让这个函数在执行之前或者执行之后做某些操作 - -装饰器其实就是上一节闭包中的应用 , 而 `Python` 为了方便我们使用就创造出一个语法糖来方便我们使用 - -语法糖 : 指那些没有给计算机语言添加新功能 , 而只是对人类来说更"甜蜜"的语法 , 语法糖主要是为程序员提供更实用的编码方式 , 提高代码的可读性 , 并有益于更好的编码风格 - -语法糖如下 : - -```python -# 装饰器函数 -def decorator(func): - def inner(): - # 我们可以在func执行前, 干一些别的事情 - # 引用外部传入的func, 一般是一个函数对象 - func() - # 当然也可以在func执行后, 干一些别的事情 - return inner - -# 语法糖版本,@ 函数名 -@decorator -def func(): - pass - -# 闭包调用版本 -func = decorator(func) -``` - -该语法糖只是将我们闭包中最后自己处理的部分进行处理了 , 如下 : - -```python -@decorator - ↓ 等价 -func = decorator(func) -``` - -实例 - -```python -def decorator(func): - def inner(): - print("I am decorator") - func() - return inner -@decorator # → func = decorator(func) -def func(): - print("I am func") - return func -func() -''' -执行结果: -I am decorator -I am func -''' -``` - -多个装饰器装饰同一个函数 - -```python -def decorator1(func): - def inner(): - return func() - return inner - -def decorator2(func): - def inner(): - return func() - return inner - -@decorator1 -@decorator2 -def func(): - print("I am func") -func() -``` - -被装饰函数带有参数 - -```python -def decorator(func): - def inner(*args,**kwargs): - return func(*args,**kwargs) - return inner - -@decorator -def func(name): - print("my name is %s" % name) -func("Lyon") -``` - -带参数的装饰器 - -```python -F = False -def outer(flag): - def decorator(func): - def inner(*args,**kwargs): - if flag: - print('before') - ret = func(*args,**kwargs) - print('after') - else: - ret = func(*args, **kwargs) - return ret - return inner - return decorator - -@outer(F) # outer(F) = decorator(func) -def func(): - print('I am func') -``` - -我们利用装饰器虽然功能达到了 , 但是注意原函数的元信息却没有赋值到装饰器函数内部 , 比如函数的注释信息 , 如果我们需要将元信息也赋值到装饰器函数内部 , 可以使用 `functools` 模块中的`wraps()`方法 , 如下 : - -```python -import functools -def outer(func): - @functools.wraps(func) - def inner(*args, **kwargs): - print(inner.__doc__) - return func() - return inner -@outer -def func(): - """ - I am func - """ - return None -func() -``` - -我们也可以自己手动修改 , 比如 `inner.__qualname__ = func.__qualname__` , `inner.__doc__ = func.__doc__` diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/04-\345\206\205\347\275\256\345\207\275\346\225\260.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/04-\345\206\205\347\275\256\345\207\275\346\225\260.md" deleted file mode 100644 index 2de2b47f5..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/04-\345\206\205\347\275\256\345\207\275\346\225\260.md" +++ /dev/null @@ -1,615 +0,0 @@ -# Attack on Python - 内置函数 🐍 - - - - - - - - - - - - -## str类型代码的执行(3个) - -> `exec`(object[, globals[, locals]]) 👈 - -将字符串当做表达式去执行,没有返回值 - -```python -# 流程语句用exec ->>> exec("print('123')") -123 ->>> exec('1+2+3+4') -10 ->>> res = exec('1+2+3+4') -None -``` - -> `eval`(expression, globals=None, locals=None) 👈 - -将字符串当做表达式去执行,并返回执行结果 - -```python -# 简单求值表达式用eval ->>> res = eval('1+2+3+4') ->>> res -10 -``` - -> `compile`(source, filename, mode, flags=0, dont_inherit=False, optimize=-1) 👈 - -把字符传编译成python可执行的代码,但是不会执行 - -*filename* : 默认`sys.stout`,即默认打印在控制台,打印到指定文件 - -*mode* : 指定compile后的对象的执行模式,注意有个`single`模式,当source带有变量赋值时,eval模式是解释不了的,所以需要用single模式或者exec模式 - -```python -# 交互语句用single ->>> code3 = 'name = input("please input your name:")' ->>> compile3 = compile(code3,'','single') -# 执行前name变量不存在 ->>> name -# 报错说'name'变量没有定义 -Traceback (most recent call last): - File "", line 1, in - name -NameError: name 'name' is not defined ->>> exec(compile3) -# 执行时显示交互命令,提示输入 -please input your name:'pythoner' -# 执行后name变量有值 ->>> name -"'pythoner'" -``` - -## 数据类型相关(38) - -### 数字相关 - -#### 数据类型 - -> `bool`([*x*]) 👈 - -查看一个元素的布尔值 - -> `int`(*x=0*) / `int`(*x*, *base=10*) 👈 - -获取一个数的十进制或者进行进制转换 - -```python ->>> int('1') -1 -# 二进制转十进制 ->>> int('0b11',base=2) -3 -``` - -> `float`([*x*]) 👈 - -将整数和字符串转换成浮点数 - -> `complex`([*real*[, *imag*]]) 👈 - -创建一个值为real + imag * j的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数 - -```python ->>> complex(1, 2) -(1+2j) -# 数字 ->>> complex(1) -(1+0j) -# 当做字符串处理 ->>> complex("1") -(1+0j) -# 注意:这个地方在“+”号两边不能有空格,也就是不能写成"1 + 2j",应该是"1+2j",否则会报错 ->>> complex("1+2j") -(1+2j) -``` - -#### 进制转换 - -> `bin`(*x*) 👈 - -将整数x转换为二进制字符串,如果x不为Python中int类型,x必须包含方法`__index__()`并且返回值为整数 - -```python -# 返回一个整数的二进制 ->>> bin(999) -'0b1111100111' -# 非整型的情况,必须包含__index__()方法且返回值为integer的类型 ->>> class myType: -... def __index__(self): -... return 35 -... ->>> myvar = myType() ->>> bin(myvar) -'0b100011' -``` - -> `oct`(*x*) 👈 - -转换为八进制 - -```python ->>> oct(8) -'0o10' -``` - -> `hex`(*x*) 👈 - -转换为十六进制 - -```python ->>> oct(13) -'0o15' -``` - -#### 数学运算 - ->`abs`(*x*) 👈 - -返回一个数的绝对值 - -```python ->>> num = -1 ->>> abs(num) -1 -``` - -> `divmod`(*a*, *b*) 👈 - -返回两个数的除,余 - -```python ->>> divmod(5,2) -# 第一个数为整除,第二个为取余 -(2, 1) -``` - ->`min`(*iterable*, **[, key, default]*) 👈 -> ->`min`(*arg1*, *arg2*, **args*[, *key*]) 👈 - -返回最小值,如果多个参数最小值一样,则返回第一个 - -```python ->>> min([1,2,3,4]) -1 -# 返回第一个 ->>> min([1,2,3],[4,5],[1,2]) -[1,2,3] -``` - ->`max`(*iterable*, **[, key, default]*) 👈 -> ->`max`(*arg1* , *arg2*, **args*[, *key*]) 👈 - -返回最大值,如果多个参数最大值,则返回第一个 - -```python ->>> max([1,2,3,4]) -4 ->>> max([2,3],[1,2,3]) -[2, 3] -``` - -> `sum`(*iterable*[, *start*]) 👈 - -求和,参数为可迭代对象 - -```python ->>> sum((1,2,3,4)) -10 -``` - -> `round`(*number[, ndigits]*) 👈 - -小数精确 - -```python -# 保留两位小数,四舍五入 ->>> round(1.235,2) -1.24 -``` - -> `pow`(*x*, *y*[, *z*]) 👈 - -幂运算 - -```python - ->>> pow(2,2) -4 -# 参数z相当余 x**y % z ->>> pow(2,2,2) -0 -``` - -### 数据类型相关 - -#### 序列 - -列表和元组 - ->`list`([*iterable*]) 👈 - -将可迭代对象转换成list对象,实际上我们创建一个空list时,python解释器自动为我们调用了该方法 - -> `tuple`([*iterable*]) 👈 - -将可迭代对象转换成tuple对象,与list类似 - -相关内置函数 - ->`reversed`(*seq*) 👈 - -顺序翻转,与list中reverse的区别在于,该翻转为新生成了一个对象,而不是在原对象上操作 - ->`slice`(*stop*) 👈 -> ->`slice`(*start*, *stop*[, *step*]) 👈 - -返回切片操作的三个参数 - -```python -# 相当于[0:2:],注意最后一个参数不能为0而是None ->>> op = slice(0,2,None) ->>> l = [1,2,3,4] ->>> l[op] -[1,2,3] -``` - -字符串 - ->`str`(*object=''*) 👈 -> ->`str`(*object=b''*, *encoding='utf-8'*, *errors='strict'*) 👈 - -返回一个字符串对象,创建字符串时python解释器为我们调用了该方法进行创建 - -> `repr`(*object*) 👈 - -返回一个可打印的字符串对象 - -```python ->>> repr(123) -``` - ->`format`(*value*[, *format_spec*]) 👈 - -格式化字符串 - ->`bytes`([*source*[, *encoding*[, *errors*]]]) 👈 - -将字符串转成bytes类型 - -```python ->>> bytes('lyon',encoding='utf-8') -b'lyon' -``` - -> `bytearray`([*source*[, *encoding*[, *errors*]]]) 👈 - -返回一个byte数组,Bytearray类型是一个可变的序列,并且序列中的元素的取值范围为[0,255] - -*source* : - -1. 如果source为整数,则返回一个长度为source的初始化数组; -2. 如果source为字符串,则按照指定的encoding将字符串转换为字节序列; -3. 如果source为可迭代类型,则元素必须为[0,255]中的整数; -4. 如果source为与buffer接口一致的对象,则此对象也可以被用于初始化bytearray - ->`memoryview`(*obj*) 👈 - -函数返回给定参数的内存查看对象(Momory view) - -所谓内存查看对象,是指对支持缓冲区协议的数据进行包装,在不需要复制对象基础上允许Python代码访问 - ->`ord`(*c*) 👈 - -把一个字符转换成ASCII表中对应的数字 - -```python ->>> ord('a') -97 -``` - -> `chr`(*i*) 👈 - -返回一个数字在ASCII编码中对应的字符 - -```python ->>> chr(66) -'B' -``` - -> `ascii`(*object*) 👈 - -在对象的类中寻找` __repr__`方法,获取返回值 - -```python ->>> class Foo: -... def __repr_(self): -... return "hello" -... ->>> obj = Foo() ->>> r = ascii(obj) ->>> print(r) -# 返回的是一个可迭代的对象 -<__main__.Foo object at 0x000001FDEE13D320> -``` - -#### 数据集合 - -字典 - - ->`dict`(**\*kwarg*) -> ->`dict`(*mapping*, **\*kwarg*) -> ->`dict`(*iterable*, **\*kwarg*) - -转换成字典类型,创建一个字典时python解释器会自动帮我们调用该方法 - -集合 - -> `set`([*iterable*]) 👈 - -转换成集合类型,创建集合时,事实上就是通过该方法进行创建的 - -> `frozenset`([*iterable*]) 👈 - -定义冻结集合,即不可变集合,存在hash值 - -好处是它可以作为字典的key,也可以作为其它集合的元素。缺点是一旦创建便不能更改,没有add,remove方法 - -#### 相关内置函数 - ->`len`(*s*) 👈 - -返回一个对象的长度 - ->`enumerate`(*iterable*, *start=0*) 👈 - -为元素生成序号,可以定义序号的初始值,默认从0开始 - -```python ->>> l = ['a','b','c'] ->>> for i,k in enumerate(l,0): -... print(i,k) -... -0 a -1 b -2 c -``` - -> `all`(*iterable*) 👈 - -判断一个可迭代对象中的元素是否都为空,返回bool值 - ->`any`(*iterable*) 👈 - -判断一个可迭代对象中是否有真元素,返回bool值 - ->`zip`(**iterables*) 👈 - -将两个长度相同的序列整合成键值对,返回一个zip对象可以用dict方法转换查看 - -```python ->>> l1 = ['k1','k2','k3'] ->>> l2 = ['v1','v2','v3'] ->>> ret = zip(l1,l2) ->>> dict(ret) -{'k1':'v1','k2':'v2','k3':'v3'} -``` - -> `filter`(*function*, *iterable*) 👈 - -筛选过滤,把可迭代对象中的元素一一传入function中进行过滤 - -```python -# 筛选出偶数 ->>> def func(x): -... return x % 2 == 0 ->>> f = filter(func,[1,2,3,4,5]) - ->>> ret = list(f) -[2,4] -``` - ->`map`(*function*, *iterable*, *...*) 👈 - -将可迭代对象中的元素一一传入function中执行并返回结果 - -```python ->>> def func(s): -... return s + ' hello' ->>> m = map(func,['alex','egon','lyon']) ->>> m - ->>> ret = list(m) ->>> ret -['alex hello', 'egon hello', 'lyon hello'] -``` - -> `sorted`(*iterable*, ***, *key=None*, *reverse=False*) 👈 - -为一个对象进行排序,在list中有个sort方法;区别:sort会改变原list,而sorted则不会改变原list - -```python ->>> l = [3,4,5,1,2,9,8,7,6] ->>> sorted(l) -[1,2,3,4,5,6,7,8,9] ->>> l - -``` - -## 迭代器/生成器相关(3个) - -> `range`(*stop*) 👈 -> -> `range`(*start*, *stop*[, *step*]) 👈 - -返回一个序列,为一个可迭代对象,并可用下标取值 - -```python ->>> from collections import Iterable ->>> r = range(10) ->>> r[0] -0 ->>> isinstance(r,Iterable) -True ->>> list(r) -[0,1,2,3,4,5,6,7,8,9] -``` - -> `next`(*iterator*[, *default*]) 👈 - -拿取迭代器中的元素,一次只拿一个 - -```python ->>> Iter = iter([1,2,3,4]) ->>> next(Iter) -1 ->>> next(Iter) -2 ->>> next(Iter) -3 ->>> next(Iter) -4 -# 没有元素就会进行报错 ->>> next(Iter) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - ->`iter`(*object*[, *sentinel*]) 👈 - -创建一个迭代器 - -```python ->>> obj = iter([1,2,3,4]) ->>> obj - -``` - -## 作用域相关(2个) - ->`locals`() 👈 - -打印函数局部命名空间 - ->`globals`() 👈 - -打印函数的全局命名空间 - -## 面向对象相关(8个) - -### 定义类方法 - ->`classmethod`(*function*) 👈 - -返回一个函数的类方法 - ->`staticmethod`(*function*) 👈 - -返回一个函数的属性方法 - ->`property`(*fget=None*, *fset=None*, *fdel=None*, *doc=None*) 👈 - -返回一个静态属性 - -### 判断类之间的关系 - ->`isinstance`(*object*, *classinfo*) 👈 - -判断对象的类型,返回bool值,主要用于判断类之间的关心,因为type无法判断类之间的关心 - ->`issubclass`(*class*, *classinfo*) 👈 - -判断一个类是否为另一个类的子类,返回bool值 - -### 所有类的基类 - -> *class*`object` 👈 - -返回一个基类,不接收任何参数 - -### 继承 - -> `super`([*type*[, *object-or-type*]]) 👈 - -用于继承父类 - -### 封装 - -> `vars`([*object*]) 👈 - -返回一个对象中包含的属性 - -## 反射相关(4个) - ->`hasattr`(*object*, *name*) > `vars`([*object*]) 👈 - -参数是一个对象和一个字符串。如果字符串是对象的一个属性的名称,则结果为True,否则为False - ->`getattr`(*object*, *name*[, *default*]) > `vars`([*object*]) 👈 - -返回对象的命名属性的值,name必须是字符串,如果字符串是对象属性之一的名称,则返回该属性的值 - ->`setattr`(*object*, *name*, *value*) > `vars`([*object*]) 👈 - -为某个对象设置一个属性 - ->`delattr`(*object*, *name*) > `vars`([*object*]) 👈 - -删除对象中的属性值 - -## 其他(10个) - ->`input`([*prompt*]) > `vars`([*object*]) 👈 - -交互式输入 - ->`print`(**objects*, *sep=' '*, *end='\n'*, *file=sys.stdout*, *flush=False*) > `vars`([*object*]) 👈 - -交互式输出 - ->`open`(*file*, *mode='r'*, *buffering=-1*, *encoding=None*, *errors=None*, *newline=None*, *closefd=True*, *opener=None*) > `vars`([*object*]) 👈 - -打开文件 - ->`help`([*object*]) > `vars`([*object*]) 👈 - -查找官方说明 - ->`hash`(*object*) > `vars`([*object*]) 👈 - -返回一个hash地址 - ->`callable`(*object*) > `vars`([*object*]) 👈 - -判断一个对象是否可以被调用执行 - ->`dir`([*object*]) 👈 - -返回一个对象中的所有方法 - ->`id`(*object*) 👈 - -返回一个对象的内存地址 - ->`type`(*object*) -> ->`type`(*name*, *bases*, *dict*) 👈 - -查看一个对象的数据类型 - ->`__import__`(*name*, *globals=None*, *locals=None*, *fromlist=()*, *level=0*) 👈 - -该函数是由import进行调用的,我们一般不用 \ No newline at end of file diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/05-\350\277\255\344\273\243\345\231\250.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/05-\350\277\255\344\273\243\345\231\250.md" deleted file mode 100644 index 3c9debb14..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/05-\350\277\255\344\273\243\345\231\250.md" +++ /dev/null @@ -1,101 +0,0 @@ -# Attack on Python - 迭代器 🐍 - - - - - - -## 介绍 - -迭代器一般用于对容器对象进行遍历访问 , 例如我们对 `Python` 中的 `str` , `list` , `tuple` , `dict` , `set` 等对象的遍历都可以通过迭代器进行遍历访问 - -在介绍迭代器之前 , 我们需要介绍一下可迭代对象 - -## 可迭代对象 - -迭代是重复反馈过程的活动 , 其目的通常是为了逼近所需目标或结果 - -可迭代对象 , 即可以按照迭代的方式进行存取数据的对象 , 在 `Python` 中我们可以理解为可以用 `for` 循环遍历的对象就是可迭代对象 - -可迭代对象的标志是 , 它具有` __iter__() `方法 - -如何判断一个对象为可迭代对象 - -```python -# 导入模块 ->>> from collections import Iterable ->>> l = ['lyon','oldboy'] -# 判断是否为Iterable , 即可迭代对象 ->>> isinstance(l,Iterable) -# 返回bool值 -True -``` - -## 迭代器 - -__for循环做的那些事__ : for循环是我们用来遍历一个数据集合的方法 , 其实就是根据一定的要求 (这个要求叫做'协议' ) 进行一次次的迭代的效果 . 当我们用 `for` 循环去遍历时 , 它做的第一件事就是判断对象是否是可迭代对象 , 如果是 , 那么它就会通过 `__iter__` 方法返回一个迭代器 , 最后利用` __next__() `方法将迭代器中的内容一个接一个的取出来 - -也就是说在 `Python` 中 , 迭代器已经内置在语言中了 , 我们可以称这种为隐式迭代器 - -所以迭代器其实就是遍历访问容器对象的一种工具 , 设计人员不需要关心容器对象的内存分配的实现细节 - -特点: - -1. 不依赖索引取值 , 访问者不需要关心迭代器内部的结构 , 仅需通过 `__next__()` 方法去访问 -2. 不能随机访问集合中的某个值 , 只能从头到尾依次访问 , 不可返回访问 -3. 惰性计算 , 只有在需要访问时才会生成值 , 节省内存 - -在 `Python` 中有一个`iter()`方法 , 作用就是将可迭代对象变成一个迭代器 , 实质上 `iter()` 是去调用了` __iter__() `方法 , 看代码: - -```python ->>> l = ['lyon'] ->>> l.__iter__() -# iterator即迭代器 - -``` - -可迭代对象与迭代器的区别: - -```python -# 用dir方法查看对象中的所有方法 ->>> dir_list = dir([1,2]) ->>> dir_iter = dir([1,2].__iter__()) -# 筛选出不同点 ->>> set(dir_iter) - set(dir_list) -{'__length_hint__', '__setstate__', '__next__'} -``` - -我们可以看出迭代器比可迭代对象多出了三个方法 , 所以我们可以根据这一点来判断一个对象到底是可迭代对象还是一个迭代器 - -```python -# 创建一个迭代器 ->>> i = iter([1,2,3,4]) -# 查看迭代器中元素的长度 ->>> i.__length_hint__() -4 -# 根据索引指定迭代开始位置 ->>> i.__setstate__(3) -# 进行取值 ->>> i.__next__() -4 -``` - -判断方法 - -```python -# 导入Iterable类 ->>> from collections import Iterable -# 导入Iterator类 ->>> from collections import Iterator -# 是否为可迭代对象 ->>> isinstance(obj,Iterable) -# 是否为迭代器 ->>> isinstance(obj,Iterator) -# 注意:迭代器也是可迭代对象 -``` - -在迭代时 , 我们需要注意迭代器中是否有值的问题 , 即当我们一直调用` __next__ ` 方法取值时 , 如果值都取完了 , 而此时我们再执行 ` __next__ ` 方法 , 解释器就会抛出 `StopIteration` , 因为已经没有值可以取了 - -迭代器的实现 , 一种常见的方式是使用受限的协程 , 就是生成器 , 另外生成器也可以叫做 "半协程" - -关于协程的文章 , 你可以在 `并发篇` 中找到它 , 下一章我们会介绍 `Python` 的生成器 \ No newline at end of file diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/06-\347\224\237\346\210\220\345\231\250.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/06-\347\224\237\346\210\220\345\231\250.md" deleted file mode 100644 index b19498e16..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/06-\347\224\237\346\210\220\345\231\250.md" +++ /dev/null @@ -1,129 +0,0 @@ -# Attack on Python - 生成器 🐍 - - - - - - -## 介绍 - -生成器 , 又称为 "半协程" , 它与迭代器 , 协程都有着亲密的关系 - -## 生成器 - -生成器非常类似于返回数组的函数 , 都是具有参数、可被调用、产生一系列的值 , 但是生成器不是构造出数组包含所有的值并一次性返回 , 而是每次产生一个值 , 因此生成器看起来像函数 , 但行为像迭代器 - -因此我们可以利用生成器进行惰性求值 , 不提前存储 , 每次都是通过计算 - -在 `Python` 中 , 生成器是用来实现迭代器的 , 所以生成器实际上是迭代器的构造器 - -虽然迭代器是由生成器构造 , 但是生成器同样是可迭代对象 , 自然生成器也可以算是迭代器 , 至少在 `Python` 中我们可以这样来判断 - -```python -from collections import Iterator -print(isinstance((i for i in range(10)), Iterator)) -``` - -## 生成器函数 - -一个函数调用时返回一个迭代器 , 那么这个函数就叫做生成器函数 - -利用生成器做一个range( 2.x中的xrange ) 的功能 - -```python -# 定义生成器 ->>> def range(n): -... start = 0 -... while start < n: -... yield start -... start += 1 ->>> obj = range(5) ->>> obj.__next__() ->>> obj.__next__() ->>> obj.__next__() ->>> obj.__next__() ->>> obj.__next__() - -# 也可以使用()定义生成器 -range = (i for i in range(5)) -``` - -`yield` 的作用 : `yield` 的作用是中断函数的执行并记录中断的位置 , 等下次重新调用这个函数时 , 就会接着上次继续执行 - -PS : 调用生成器函数时 , 仅仅会返回一个生成器 , 并不会执行函数的内容 , 生成器只能由 `next()` 进行调用执行 , 实质上`next()` 方法就是调用的` __next__() ` 方法 - -**yield from** - -```python -def func1(): - for i in 'AB': - yield i - for j in range(3): - yield j -print(list(func())) - -def func2(): - yield from 'AB' - yield from range(3) - -print(list(func2())) -``` - -除了通过 `yield` 和 `yield from` 语句 , 我们还可以通过生成器表达式来定义 , 也就是 `(i for i in range(10))` 这种方式 , 而其他的推导式则是使用 `()` 之外的定义 - -```python -# 列表推导式 -l = [i for i in range(10)] -# 字典推导式 -d = {i:i for i in range(10)} -# 集合推导式 -s = {i for i in range(10)} -``` - -## 应用 - -监听文件 - -```python -import time -def tail(filename): - # 打开文件 - f = open(filename,encoding='utf-8') - # 从文件末尾算起 - f.seek(0, 2) - while True: - # 读取文件中新的文本行 - line = f.readline() - if not line: - time.sleep(0.1) - continue - yield line -tail_g = tail('tmp') -# 生成器也是可迭代对象 -for line in tail_g: - print(line) -``` - -计算动态平均值 - -```python -def averager(): - total = 0 - count = 0 - average = None - while True: - term = yield average - total += term - count += 1 - average = total/count -# 生成生成器 -g_avg = averager() -# 激活生成器,不激活无法send -next(g_avg) -# send相当于先传参,后调用next() -print(g_avg.send(10)) -print(g_avg.send(30)) -print(g_avg.send(50)) -``` - -当然在我们工作中更多的是利用生成器来实现惰性计算 diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/07-\351\200\222\345\275\222.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/07-\351\200\222\345\275\222.md" deleted file mode 100644 index 11ad34f37..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/07-\351\200\222\345\275\222.md" +++ /dev/null @@ -1,144 +0,0 @@ -# Attack on Python - 递归 🐍 - - - - - - - - - - - - -## 递归算法 -递归算法是一种直接或者间接地调用自身算法的过程(递归函数就是一个体现)。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简介而且易于理解。 - -特点:👈 - -1. 递归就是再过程或函数里调用自身 -2. 再使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。递归算法解题通常显得很简洁,但递归算法解题的运行效率低 -3. 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序 - -要求:👈 - - -1. 每次调用在问题规模上都有所减少(通常是减半) -2. 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入) -3. 再问题的规模极小时必须要直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归条用将会称为死循环而不能正常结束 - -## 递归函数 - -面向函数编程中,利用递归思想来解决一些简单的问题是非常简单便洁的 - -递归函数就是函数内部通过调用自己本身来实现功能的函数。既然是调用自身,那么每次调用,需要解决的问题就应该有所减少,不然这个函数就没有尽头的执行下去。 - -打印10-0 - -```python -def counter(num): - # 打印num - print(num) - # 如果num小于等于0 - if num <= 0: - # 返回num - return num - # 必须相对上一次有所减少 - num -= 1 - # 反复调用,直到return将函数停止运行 - counter(num) -# 调用函数 -counter(10) -``` - -## 递归应用 - -用递归实现斐波那契数列 - -```python -l = [] -def fibonacci(n1,n2): - # 大于1000后结束递归 - if n1 > 2000: - # 终止函数,并返回 "不搞了" - return "不搞了!" - # 追加进列表 - l.append(n1) - # 前两个数之和 - n3 = n1 + n2 - # 进行递归 - fibonacci(n2, n3) -# 从0开始 -fibonacci(0, 1) -print(l) -``` - -用递归实现三级菜单 - -```python -menu = { - '北京': { - '海淀': { - '五道口': { - 'soho': {}, - '网易': {}, - 'google': {} - }, - '中关村': { - '爱奇艺': {}, - '汽车之家': {}, - 'youku': {}, - }, - '上地': { - '百度': {}, - }, - }, - '昌平': { - '沙河': { - '老男孩': {}, - '北航': {}, - }, - '天通苑': {}, - '回龙观': {}, - }, - '朝阳': {}, - '东城': {}, - }, - '上海': { - '闵行': { - "人民广场": { - '炸鸡店': {} - } - }, - '闸北': { - '火车战': { - '携程': {} - } - }, - '浦东': {}, - }, - '山东': {}, -} -def threeLM(menu): - while True: - # 打印本级菜单内容 - for key in menu: - # 打印字典的key - print(key) - # 用户输入内容 - chooice = input("请输入菜单>>") - if chooice == 'back': - return - elif chooice == 'quit': - return 'q' - if chooice in menu.keys(): - # 将新字典作为参数进行递归调用 - ret = threeLM(menu[chooice]) - if ret == 'q':return 'q' -threeLM(menu) -``` - - - - - diff --git "a/01-Python/02-\345\207\275\346\225\260\347\257\207/README.md" "b/01-Python/02-\345\207\275\346\225\260\347\257\207/README.md" deleted file mode 100644 index 18c8ca07f..000000000 --- "a/01-Python/02-\345\207\275\346\225\260\347\257\207/README.md" +++ /dev/null @@ -1,26 +0,0 @@ -# Attack on Python - 函数篇 🐍 - - - - - - - - - - -## 介绍 - -该目录下为Python函数篇 , 主要为面向函数编程 , 内容概述如下 - -### 函数 - -- 函数基础语法 -- 嵌套函数 -- 高阶函数 -- 闭包 -- 装饰器 -- 递归 -- 匿名函数 -- 内置函数 -- 迭代器、生成器 diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/01-\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/01-\351\235\242\345\220\221\345\257\271\350\261\241.md" deleted file mode 100644 index 297d58967..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/01-\351\235\242\345\220\221\345\257\271\350\261\241.md" +++ /dev/null @@ -1,235 +0,0 @@ -# Attack on Python - 面向对象 🐍 - - - - - - - - - - -## 介绍 - -编程范式 - -编程是程序员用 特定的语法 + 数据结构 + 算法组成的代码来告诉计算机如何执行任务的过程 , 而实现一个任务的方式有很多种不同的方式 , 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式 - -- 面向过程编程 Procedural Programming - -面向过程编程就是程序从上到下一步步执行 , 基本设计思路就是程序一开始是要着手解决一个大的问题 , 然后把一个大问题分解成很多个小问题或子过程 , 这写子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决 - -在Python中 , 我们通过把大段代码拆成函数 , 通过一层一层的函数调用 , 就可以把复杂任务分解成简单的任务 , 这种分解可以称之为面向过程的程序设计 . 函数就是面向过程的程序设计的基本单元 - -- 函数式编程 Functional Programming - -函数式编程就是一种抽象程度很高的编程范式 , 纯粹的函数式编程语言编写的函数没有变量 , 函数式编程的一个特点就是 , 允许把函数本身作为参数传入另一个函数 , 还允许返回一个函数 , Python对函数式编程提供部分支持 . 由于Python允许使用变量 , 因此 , Python不是纯函数式编程语言 - -- 面向对象编程 Object Oriented Programming - -面向对象编程是利用"类"和"对象"来创建各种模型来实现对真实世界的描述 , 使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单 , 并且可以大大提高程序开发效率 , 另外 , 基于面向对象的程序可以使它人更加容易理解你的代码逻辑 , 从而使团队开发变得更从容 - -## 类与实例 - -类的语法 - -```python -class 类名: - pass -``` - -一个栗子🌰 - -```python -# 创建一个人的'类',首字母要大写 -class Person(object): - # 构造函数,初始化属性 - def __init__(self,name): - self.name = name - # 人可以吃饭 - def eat(self): - print("I am eatting") -# 创造了一个叫做'Lyon'的人 -p = Person('Lyon') -# 执行吃饭功能 -p.eat() -# 执行结果: I am eatting -``` - -* 类 (class) - -类就是 `对现实生活中一类具有共同特征事物的抽象` - -类起到一个模板的作用 , 当我们创建一个类时 , 就相当于创建了一个初始的'模型' , 我们可以通过这个'模型' 来创建出一个个具有相同特征或功能的事物 , 来帮助我们更好的处理问题 - -在上述栗子中类名Person 后有一个` (object) ` , 这是新式类的写法 , 而在python3.x 以上的版本中 , 默认为新式类 , 所以也可直接 ` class Person: ` - -我们创建类时 , 都默认继承了object类 , object详解见后期文章 - -* 实例 (instance) - -我们知道类是一个抽象 , 既然是抽象那就是不可操作的 , 所以我们如果进行操作 , 就需要将这一抽象的概念变成具体的事物 , 这个过程我们称为实例化 - -实例化: ` 由抽象的类转换成实际存在的对象的过程 ` - -实例: ` 由类进行实例化所得到的对象 ` , 上述栗子中的 ` p ` 就是一个实例 - -## 属性与方法 - -属性是实体的描述性质或特征 , 比如人有名字 , 年龄 , 性别等 . 当然还有人所能做的事情也是一种属性 , 比如吃饭 , 睡觉 , 喝水等 . 对于这两种属性 , 一种是表示特征的 , 叫做静态属性 , 另一种则是表示功能的 , 叫做动态属性 - -在Python中 , 我们将**静态属性** 就称为` 属性 ` , 将**动态属性** 就称为` 方法 ` , 并且以变量来表示属性 , 以函数表示方法 , - -**PS:类中的函数已经不叫函数了 , 而叫做方法** - - -调用方式: `类名.属性名` - -```python -class Person: - # 类变量 - role = 'student' - # 构造函数 - def __init__(self,name): - # 实例变量 - self.name = name -``` - - -调用方式: 类名 . 方法名( ) - -```python -class Person: - # 普通方法 - def eat(self): - pass -``` - -***特殊的类属性*** - -| 属性名 | 说明 | -| -------------- | ----------------- | -| \_\_dict\_\_ | 查看类或对象成员 , 返回一个字典 | -| \_\_name\_\_ | 查看类的名字 | -| \_\_doc\_\_ | 查看类的描述信息 , 即注释部分 | -| \_\_base\_\_ | 查看第一个父类 | -| \_\_bases\_\_ | 查看所有父类 , 返回一个元组 | -| \_\_module\_\_ | 查看类当前所在模块 | -| \_\_class\_\_ | 查看对象通过什么类实例化而来 | - -PS:对于属性和方法 , 在网上分类各种各样的都有 , 比如字段 , 还有菜鸟教程中的一些 , 其实本质上都是一个东西 - -## 构造函数 - -在上述例子中 , 可以看到有一个\_\_init\_\_ 方法 , 这个方法叫做构造方法 , 用于初始化属性 , 所以如果我们要设置属性 , 那么构造方法是必须要的 - -> ***self*** - -我们直接通过实例来说明 - -```python -class Foo: - def __init__(self,name): - self.name = name - def func(self): - print(id(self)) -a = Foo('Lyon') -# 打印实例a的内存地址 -print(id(a)) -# 调用类中的func方法,即打印self的内存地址 -a.func() -''' -执行结果: -1703689404544 -1703689404544 -结果分析: -我们发现a的内存地址和self的内存地址是一样的,也就是说self其实就是实例本身 -那么在我们进行实例化的时候,self.name = name 就是给实例添加一个name属性,该属性的值就是我们在实例化时传入的'Lyon' -所以如果我们需要给对象添加属性的话,可以直接通过 对象.属性名 = 属性值 的方式进行添加 -''' -``` - -将上栗子中的构造函数再换个姿势看看 - -```python -a = Foo('Lyon') -# 等价于如下,用类名调用类中的方法 -Foo.__init__(a,'Lyon') -``` - -## 命名空间 - -在函数中 , Python解释器在执行时 , 会将函数名称依次加载到命名空间 , 类当然也一样 - -我们创建一个类时 , Python解释器一执行就会创建一个类的命名空间 , 用来存储类中定义的所有名称( 属性和方法 ) , 而我们进行实例化时 , Python解释器又会为我们创建一个实例命名空间 , 用来存放实例中的名称 - -当我们利用 ` 对象. 名称 ` 来访问对象属性 ( 静态与动态 ) 时 , Python解释器会先到该对象的命名空间中去找**该名称** , 找不到就再到类 ( 该对象实例化之前的类 ) 的命名空间中去找 , 最后如果都没找到 , 那么就抛出异常了 - -命名空间的本质是一个字典 , 我们可以访问对象的 `__dict__` 属性得到命名空间 - -访问属性实例 - -```python -class A(object): - """ - 这是一个类 - """ - pass -a = A() -# 访问实例a的__doc__属性 -print(a.__doc__) -''' -执行结果: - - 这是一个类 - -''' -``` - -## 嵌套组合 - -对象交互 - -```python -class Person: - def __init__(self, name): - self.name = name - def attack(self,per): - print("{} attacked {}".format(self.name, per.name)) -lyon = Person("Lyon") -kenneth = Person("kenneth") -lyon.attack(kenneth) -# 执行结果: Lyon attacked kenneth -``` - -类的组合 - -传参时组合 - -```python -class BirthDate: - def __init__(self, year, month, day): - self.year = year - self.month = month - self.day = day -class Person: - def __init__(self, name, birthdate): - self.name = name - self.birthdate = birthdate -p = Person('Lyon', BirthDate(2000, 1, 1)) -``` - -定义时组合 - -```python -class BirthDate: - def __init__(self, year, month, day): - self.year = year - self.month = month - self.day = day -class Person: - def __init__(self, name, year, month, day): - self.name = name - self.birthdate = BirthDate(year, month, day) -p = Person('Lyon', 2000, 1, 1) -``` diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/02-\347\273\247\346\211\277.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/02-\347\273\247\346\211\277.md" deleted file mode 100644 index 496ba2f11..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/02-\347\273\247\346\211\277.md" +++ /dev/null @@ -1,598 +0,0 @@ -# Attack on Python - 继承 🐍 - - - - - - - - - - -## 抽象与继承 - -**抽象** - -> 抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征 - -比如 🍎 , 🍌 , 🍇 , 等 , 它们共同的特性就是水果 , 我们得出水果这个概念的过程就是一个抽象的过程 , 抽象能使复杂度降低 , 好让人们能够以纵观的角度来了解许多特定的事态 - -有抽象就会有具体 , 我们会用抽象的对象来表示一类事物 , 而用具体的对象表示某个事物 , 比如苹果 , 香蕉 , 葡萄都是具体的对象 , 水果则是抽象的对象 - -**继承** - -> 继承是基于抽象的结果 - -抽象可以让我们来以纵观的角度了解一类事物事物 , 并且这类事物都拥有该抽象中所有的特征 , 相当于继承了该抽象中的特征 , 这样我们就可以只将这类事物不同的特征放到具体中 , 而不需要再次关心共同特征 , 所以***先有抽象后才能有继承*** - -介绍抽象的概念时利用了水果来进行说明 , 为了更好的理解 , 继承就用动物为例子 - -```python -'-----------抽象出动物类-----------' -# 从狗和猫中抽取共同的特征,它们都能吃,喝,睡,玩 -class Animal(object): - # 吃 - def eat(self): - pass - # 喝 - def drink(self): - pass - # 睡 - def sleep(self): - pass - # 玩 - def play(self): - pass -'------------具体动物类------------' -# 所有的类默认是继承了object类的,让'猫'类继承动物类 -class Cat(Animal): - # 抓老鼠 - def catch_mouse(self): - pass -# 让'狗'类继承动物类 -class Dog(Animal): - # 跳墙 - def jump_wall(self): - pass -``` - -我们把🌰栗子中的Animal类叫做父类 , 基类或超类 , Cat和Dog类叫做子类或派生类 - -> 简单的继承方式就是在类名后面加入要继承的类 - -使用继承可以减少我们代码重用 , 简化代码 - -## 新式类与经典类 - -在说新式类与经典类之前 , 先说一说单继承和多继承 - -**单继承与多继承** - -> 单继承就是只以一个类作为父类进行继承 - -```python -# 定义基类 -class Parent: - pass -# 继承基类 -class Subclass(Parent): - pass -``` - -> 多继承就是同时以多个类做为基类进行继承 - -```python -# 定义第一个基类 -class Parent1: - pass -# 定义第二个基类 -class Parent2: - pass -# 定义第三个基类 -class Parent3: - pass -# 继承三个基类 -class Subclass1(Parent1,Parent2,Parent3): - pass -``` - -在多继承中我们需要考虑一个继承优先的问题 , 就像上面的例子 , 如果我们所定义的三个父类中 , 都拥有一个同样的方法那么Python解释器会怎么去继承父类的方法? 三个同名的方法明显只能选择其中一个进行继承 , 这就关系到经典类和新式类了 - -**经典类和新式类** - -经典的东西都是比较旧的 , so , 在Python 2.x 中默认都是经典类 , 只有显示继承了object才是新式类 ; 而Python 3.x 中默认都是新式类 , 不必显示的继承object - -> 经典类与新式类在声明时的区别在于 , 新式类需要加上object关键字 - -```python -# python 2.x 环境下 -# 经典类 -class A(): - pass -# 新式类 -class A(object): - pass - -# python 3.x 环境下 -class A: - pass -``` - -> 经典类与新式类多继承顺序的区别在于 , 经典类会按照` 深度优先 ` (纵向)的方式查找 , 新式类会按照` 广度优先 ` (横向)的方式查找 - -**实例环境Python2** - -经典类 - -```python -# 经典类 -class A(): - def __init__(self): - pass - def display(self): - print "This is from A" -class B(A): - def __init__(self): - pass -class C(A): - def __init__(self): - pass - def display(self): - print "This is from C" -class D(B,C): - def __init__(self): - pass -obj = D() -obj.display() -''' -执行结果: This is from A -说明:经典类深度优先,我们通过实例调用display方法时,Python解释器会先找B类,如果B类中没有就会去B类的父类(即A类)中查找,如果在所有的父类中都没有找到需要的方法,才会开始继续找下一个继承的类(即C类) -''' -``` - -新式类 - -```python -# 新式类 -class A(object): - def __init__(self): - pass - def display(self): - print "This is from A" -class B(A): - def __init__(self): - pass -class C(A): - def __init__(self): - pass - def display(self): - print "This is from C" -class D(B,C): - def __init__(self): - pass -obj = D() -obj.display() -''' -执行结果: This is from C -说明:新式类广度优先,Python解释器首先到B类进行查找,B类中没有就直接去C类中找,并不会去B类的父类(A类)中去查找,如果C类中没有才会再去B类的父类(A类)中查找,最后如果没找到就会报错 -''' -``` - -## 派生 - -利用继承机制 , 新的类可以从已有的类中派生 - -子类继承了父类 , 父类派生了子类 , 继承是站在子类的角度 , 派生是站在父类的角度 , 我们在子类中可以添加新的属性或方法 . 但是要注意父类属性名与子类属性名相同 , 以及父类与子类中方法名的情况 , 说的有点绕了 , 通过实例进一步描述 - -属性名 , 方法名不发生冲突 - -```python -# 创建一个基类 -class Person: - # 基类属性 - country = 'China' - # 构造方法 - def __init__(self, name, age): - self.name = name - self.age = age - # 工作方法 - def work(self): - print("I am working ...") -# 派生一个子类,继承基类中的属性和方法 -class Man(Person): - # 子类属性 - male = 'man' - # 新增睡觉方法 - def sleep(self): - print("I am sleepiing ...") -# 实例化子类 -man = Man('Lyon', 18) -# 调用从基类继承过来的工作方法 -man.work() -# 访问从基类继承过来的国家属性 -print(man.country) -# 调用子类中的睡觉方法 -man.sleep() -# 访问子类中的male属性 -print(man.male) -''' -执行结果: -I am working ... -China -I am sleepiing ... -man -''' -``` - -属性或方法冲突 , 会按照加载顺序进行覆盖 , 定义过程就已完成 - -```python -# Python解释器开始执行,将Person类的名字以及类中包含的属性名方法名加载到Person类的命名空间 -class Person: - country = 'China' - # 注意构造方法也是方法,Python解释器加载时仅仅会将__init__这个名字加载到命名空间,并不会执行内部代码 - def __init__(self, name, age): - self.name = name - self.age = age - # 加载方法名 - def work(self): - print("I am working ...") -# Python解释器将Man类的名字加载到Man的命名空间,随后由于Person类在这步之前已经完成加载,此时就会通过Person类名从Person的命名空间中取出属性和方法名加载到Man类的命名空间 -class Man(Person): - # 由于上一已完成Person类中的同名__init__的加载,此时会将其覆盖 - def __init__(self, male, country): - self.male = male - self.country = country - # 同__init__,将同名work覆盖 - def work(self): - print("I don't like working ...") - # 加载到Man类的命名空间 - def sleep(self): - print("I am sleepiing ...") -# 实例化Man类 -man = Man('male', 'America') -# 此work为覆盖后的work即子类自己的work -man.work() -# country为父类的类属性,在实例化时被实例属性覆盖 -print(man.country) -# 调用子类中的sleep方法 -man.sleep() -# 打印实例属性male -print(man.male) -''' -执行结果: -I don't like working ... -America -I am sleepiing ... -male -''' -``` - -当然我们在使用时仅需注意一下几点: - -1. 重名时 , 会以子类的方法或属性为主 , 因为父类的会被覆盖 -2. 构造方法里是实例属性 , 子类如果也有构造方法 , 以子类的构造方法为主 - -通俗的讲 : ` 我有就用我的 , 没有就拿你的 ` - -但是上述派生中有两个问题: - -1. 当子类父类都有构造方法时 , 如果子类需要父类构造方法中的实例属性怎么办 ? -2. 当子类父类都有同名方法时 , 如果子类需要用父类中的方法怎么办? - -这两个问题放到下节 `super` 中解决 - -## super - -先解决上节中的两个问题 , 既然父类中的方法被覆盖掉了 , 那么我们不妨再加载一次父类中的方法 , 将子类中的再次覆盖 - -解决问题1 : 子类父类构造方法中实例属性集合 - -```python -class Person: - def __init__(self, name, age): - self.name = name - self.age = age -class Man(Person): - # 实例属性集合也还是要传参的,只是传入后各拿各的 - def __init__(self, name, age, male): - self.male = male - # 通过类名.方法调用Person类中的__init__方法,即将__init__中的代码拿过来用了一遍 - Person.__init__(self, name, age) -# 实例化Man类 -man = Man('Lyon', 18, 'male') -# 访问man中的name实例属性 -print(man.name) -# 访问man中的age实例属性 -print(man.age) -# 访问man中的male -print(man.male) -''' -执行结果: -Lyon -18 -male -''' -``` - -解决问题2 : 使用父类中的重名方法 - -对于第二个问题明显不能利用问题1同样的方式了 , 因为调用就意味着执行 , 虽然我们可以以问题1中的方式执行父类的方法 , 但是子类的方法也还是会照常执行 , so , 我们得换个方式 - -```python -class Person: - def work(self): - print("I am working ...") -class Man(Person): - def work(self): - print("I don't like working ...") -man = Man() -# 将实例man作为self传入Person类中的work方法 -# Person().work() -Person.work(man) -''' -执行结果: I am working ... -''' -``` - -两个问题解决了 , 但是我们发现通过这两种方式来解决会对后期修改造成非常大的麻烦 , 只要类名一变 , 那么我们就得一个个修改 , 开发中来个100个就够你改半小时了 ... 所以就有了super - -> super - -super只能用在新式类中 , 在经典类中则只能按照上面的方式进行处理了 - -截取官方文档中的一部分 - -```python -# 相当于super(type, obj),first argument一般是self实例本身 -super() -> same as super(__class__, ) -# 返回非绑定父类对象 -super(type) -> unbound super object -# 返回父类的实例 -super(type, obj) -> bound super object; requires isinstance(obj, type) -# 返回父类的实例 -super(type, type2) -> bound super object; requires issubclass(type2, type) -# type参数为子类 -``` - -Python中一切皆对象 , 所以其实super是一个类 , 在我们使用super时事实上调用了super类的初始化函数 , 产生了一个super对象 - -首先用super的方式解决上面的问题吧 - -问题1 - -```python -class Person: - def __init__(self, name, age): - self.name = name - self.age = age -class Man(Person): - def __init__(self, name, age, male): - self.male = male - super().__init__(name, age) -``` - -问题2 - -```python -class Person: - def work(self): - print("I am working ...") -class Man(Person): - def work(self): - print("I don't like working ...") -man = Man() -# super的第一个参数是要找父类的那个类 -super(Man,man).work() -``` - -但是在我们使用多继承时 , 这两者的区别就能显现出来了 - -使用\_\_init\_\_ - -```python -class A(object): - def __init__(self): - print("This is from A") - -class B(A): - def __init__(self): - print("This is from B") - A.__init__(self) - print("This is from B") - -class C(A): - def __init__(self): - print("This is from C") - A.__init__(self) - print("This is from C") - -class D(B,C): - def __init__(self): - print("This is from D") - B.__init__(self) - C.__init__(self) - print("This is from D") -d = D() -''' -执行结果: -This is from D -This is from B -This is from A -This is from B -This is from C -This is from A -This is from C -This is from D -''' -``` - -使用super - -```python -class A(object): - def __init__(self): - print("This is from A") -class B(A): - def __init__(self): - print("This is from B") - super().__init__() - print("This is from B") -class C(A): - def __init__(self): - print("This is from C") - super().__init__() - print("This is from C") -class D(B,C): - def __init__(self): - print("This is from D") - super().__init__() - print("This is from D") -d = D() -''' -执行结果: -This is from D -This is from B -This is from C -This is from A -This is from C -This is from B -This is from D -''' -``` - -用\_\_init\_\_ 和 super我们得到的结果是不一样的 , 因为super是一个类名 , super( ) 事实上调用了super类的初始化函数 , 产生了一个super对象 , 所以使用super可以避免父类被重复调用 - -PS : super的查找方式遵循MRO表中的顺序 , MRO表后续文章中在研究 - -## 抽象类与接口 - -Python本身不提供抽象类和接口机制 - -抽象类 - -> 在Java中抽象类的定义是这样的 : 由abstract 修饰的类叫抽象类 , 该类不能被实例化 , 并且仅支持单继承 - -在Python中如果要实现抽象类 , 需要借助abc模块 . ABC是Abstract Base Class的缩写 - -在abc模块中有一个用来生成抽象类的元类 ` ABCMeta ` - -生成抽象类 - -```python -# 导入抽象元类和抽象方法 -from abc import ABCMeta,abstractmethod -class Abstract_class(metaclass=ABCMeta): - # 使用抽象方法进行约束 - @abstractmethod - # 父类可以简单实现,子类必须实现 - def func(self): - print('hello func') -``` - -抽象类提供了继承的概念 , 它的出发点就是为了继承 , 否则它没有存在的任何意义 , 所以说定义的抽象类一定是用来继承的 - -接口 - -> 在Java中接口是一个抽象类型 , 是抽象方法的集合 , 接口通常以interface来声明 . 一个类通过继承接口的方式 , 从而来继承接口的抽象方法 , 达到约束的目的 - -在Python中默认是没有的 , 所以我们如果要使用接口 , 有两种方法 , 第一种就是我们在抽象类的基础上进行定义 , 第二种则是借助第三方模块 ` zope.interface ` - -这里我们只说第一中方法 - -```python -# 导入抽象元类和抽象方法 -from abc import ABCMeta,abstractmethod -class Abstract_class(metaclass=ABCMeta): - # 使用抽象方法进行约束 - @abstractmethod - # 父类不能实现,子类必须实现 - def func(self): - pass -``` - -与抽象类中的例子比较 , 因为在Python中抽象类与接口类这两者区分并不清晰 , 我们在对于方法是否实现上 , 修改之后基本就实现了一个接口 - -> 什么时候使用抽象类与接口 - -- 当几个子类的父类,有相同的功能需要被实现的时候,就使用` 抽象类 ` -- 当几个子类,有相同的功能,但是实现各不相同的时候,就使用` 接口` (接口归一) - -接口归一实例 - -```python -from abc import ABCMeta, abstractmethod -# 定义接口 -class Payment(metaclass = ABCMeta): - @abstractmethod - def pay(self, money): pass -# 继承接口 -class Applepay(Payment): - def pay(self, money): - print('The payment method is Applepay , {}'.format(money)) -# 继承接口 -class Zhifubao(Payment): - def pay(self, money): - print('The payment method is Zhiwubaopay , {}'.format(money)) -# 继承接口 -class Wexin(Payment): - # 没有接口中的pay方法,实例化时就报错 - def fuqian(self, money): - print('The payment method is Wexinpay , {}'.format(money)) -# 接口归一 -def payment(obj,money): - obj.pay(money) -# 实例化就报错,没有pay方法 -# wexin = Wexin() -zhifubao = Zhifubao() -apple = Applepay() -payment(zhifubao,100) -payment(apple,1000) -``` - -***总结*** - -1. 抽象类与接口都不能被实例化 (抽象方法约束) , 所以必须被继承才能使用 -2. 抽象类中的方法能够被实现 , 接口中的方法不能被实现 -3. 抽象类中可以有构造方法 , 接口中不可有 -4. 抽象类最好不要用多继承 , 而接口类可以 - - -## isinstance 和 issubclass - -isinstance(obj, cls) 检查obj是否是类cls的对象 - -```python -class Foo: - pass -obj = Foo() -print(isinstance(obj, Foo)) -print(isinstance(obj, object)) -print(isinstance(obj, type)) -''' -执行结果: -True #obj是类Foo的对象 -True #obj是object的对象,Foo类继承了object类 -False #object类是有type类的实例 -''' -``` - -issubclass(sub, super) 检查sub类是否是super类的派生类 - -```python -class A: - pass -class B(A): - pass -print(issubclass(B, B)) -print(issubclass(B, A)) -print(issubclass(B, object)) -print(issubclass(B, type)) -''' -执行结果: -True #B类是自己的派生类 -True #B类是A类的派生类 -True #B类是object类的派生类,因为A类继承了object类 -False #B类不是type类的派生类,type类实例化产生了object类 -''' -``` diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/03-\345\244\232\346\200\201.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/03-\345\244\232\346\200\201.md" deleted file mode 100644 index 50bb7557f..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/03-\345\244\232\346\200\201.md" +++ /dev/null @@ -1,152 +0,0 @@ -# Attack on Python - 多态 🐍 - - - - - - - - - - -## 介绍 - -上一篇中已经得知 , 继承可以扩展已存在的代码模块(类) , 其目的是为了解决***代码重用*** 问题 - -多态则是为了实现另一个目的 : 接口重用 - -## 多态 - -多态 (Polymorphism) 按字面的意思就是"多种状态" , 比如动物有多种形态 , 人 , 猫 , 狗 ; 文件也有多种格式 exe , txt , md(MarkDown格式) , 这就是多态 - - 在面向对象语言中 , 接口的多种不同的实现方式即为多态 - -> 多态性是允许你将父对象设置成为一个或多个他的子对象相等的技术 , 赋值之后 , 父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作 - -静态多态性 - -必须在编译时就能确定其处理方式 - -```python -n1 = 12 -n2 = 34 -# int类型相加 -print(n1 + n2) -s1 = 'hello ' -s2 = 'word' -# str类型相加 -print(s1 + s2) -''' -执行结果: -46 -hello word -''' -``` - -如上述例子我们利用运算符 "+", 完成了两种情况下的运算 , 并且Python解释器在执行前就已确定处理方式 , 即编译过程中就已经知道了调用哪个函数 - -动态多态性 - -编译时无法立即确定其处理方式 , 只有在执行时才确定处理方式 , 注意一定要同名 - -```python -from abc import ABCMeta,abstractclassmethod -# 接口继承 -class Animal(metaclass=ABCMeta): - @abstractclassmethod - # 约束派生类必须有talk方法 - def talk(self): - pass -class Cat(Animal): - def talk(self): - print("喵喵喵") -class Dog(Animal): - def talk(self): - print("汪汪汪") -c = Cat() -d = Dog() -# 因为接口的缘故,我们无需考虑实例化后的对象具体是什么类型,因为动物都有talk方法,所以我们可以直接使用 -c.talk() -d.talk() -# 我们进行接口统一 -def talk(obj): - obj.talk() -talk(c) -talk(d) -''' -执行结果: -喵喵喵 -汪汪汪 -喵喵喵 -汪汪汪 -''' -``` - -上栗中, Python解释器在解释时是无法确定处理方式的 , 因为存在几个同名的方法 , 编译时并不能确定是哪一个 , 只有在执行时 , 才能确定使用哪个类中的talk() 方法 , 这就是动态多态性 - -小结: - -1. 静态多态性与动态多态性的区别在于 , 编译时是否能确定其处理方式 -2. 通过多态可以实现用一个函数名调用不同内容的函数 - -## 多态性的好处 - -多态性是面向对象的三大特性之一 , 有很多人说Python不支持多态 , 事实上Python处处是多态 , 比如内置函数len() 就是多态的一种体现 - -多态的作用: - -1. 增加了程序的灵活性 - - 以不变应万变 , 不论对象有多少中形态 , 使用者都是同一种形式去调用 , 如 talk(obj) - -2. 增加了程序的可扩展性 - - 通过继承Animal类派生了一个新的类 , 使用者无需更改自己的代码 , 依旧利用 talk(obj) 进行调用 - -对于多态 , 可能会觉得比较模糊 , 这是因为 , 我们在写程序时不知不觉就用上了 , 哈哈所以还是说处处是多态 - -## 鸭子类型 - -Python崇尚鸭子类型 - -以下是维基百科中对鸭子类型得论述 : - -在程序设计中 , 鸭子类型 (英语 : duck typing) 是动态类型的一种风格。在这种风格中 , 一个对象有效的语义 , 不是由继承自特定的类或实现特定的接口 , 而是由当前方法和属性的集合决定 . 这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试 , " 鸭子测试 "可以这样表述: - -" 如果走起来像鸭子 , 游泳起来像鸭子 , 叫起来也像鸭子 , 那么它就是鸭子 " - -**在鸭子类型中 , 关注的不是对象的类型本身 , 而是它是如何使用的** . 例如 , 在不使用鸭子类型的语言中 , 我们可以编写一个函数 , 它接受一个类型为鸭的对象 , 并调用它的走和叫方法 . 在使用鸭子类型的语言中 , 这样的一个函数可以接受一个任意类型的对象 , 并调用它的走和叫方法 . 如果这些需要被调用的方法不存在 , 那么将引发一个运行时错误 . 任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述 , 这种决定类型的方式因此得名。 - -鸭子类型通常得益于不测试方法和函数中参数的类型 , 而是依赖文档 , 清晰的代码和测试来确保正确使用 . 从静态类型语言转向动态类型语言的用户通常试图添加一些静态的 ( 在运行之前的 ) 类型检查 , 从而影响了鸭子类型的益处和可伸缩性 , 并约束了语言的动态特性 - -例1 : 利用标准库中定义的各种 ' 与文件类似的对象 ' , 尽管这些对象的工作方式像文件 , 但他们没有继承内置对象的方法 - -```python -# 文本文件 -class TxtFile: - def read(self): - pass - def write(self): - pass -# 磁盘文件 -class DiskFile: - def read(self): - pass - def write(self): - pass -``` - -二者都像鸭子, 二者看起来都像文件,因而就可以当文件一样去用 - -例2 : 序列类型有多种形态 : 字符串 , 列表 , 元组 , 但他们没有直接的继承关系 - -```python -# 三者都是序列类型 -name = 'Lyon' -namel = ['Lyon'] -namet = ('Lyon',) -# 字符串,列表,元组并没有直接关系,都可以调用len(),并且我们无需考虑类型 -print(len(name)) -print(len(namel)) -print(len(namet)) -``` \ No newline at end of file diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/04-\345\260\201\350\243\205.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/04-\345\260\201\350\243\205.md" deleted file mode 100644 index 2f42cc5c7..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/04-\345\260\201\350\243\205.md" +++ /dev/null @@ -1,174 +0,0 @@ -# Attack on Python - 封装 🐍 - - - - - - - - - - -## 介绍 - -封装就是把客观事物封装成抽象的类 , 并且类可以把自己的数据和方法只让可信的类或者对象操作 , 对不可信的进行信息隐藏 - -## 私有问题 - -当我们类中的一些属性或者方法想要对不可信的类或者对象隐藏时 , 我们就可以将这些属性或者方法 , 定义成私有属性或者私有方法 - -在Python中用双下划线开头的方式将属性隐藏起来 , 即带双下划线就为私有属性或者私有方法 - -- 私有属性 - -```python -class A: - def __init__(self,name): - # 定义私有属性 - self.__name = name -# 实例化 -a = A("Lyon") -# 访问a中的__name属性 -print(a.__name) -# 执行结果 : AttributeError: 'A' object has no attribute '__name' -''' -结果报错,意思是对象A中没有__name这个属性 -也就是说,外部已经不能直接利用 .__name 来访问这个属性了 -因为此时它是一个私有属性 -''' -``` - -将属性定义成私有属性其实是一种变形操作 , 即类中所有以双下划线开头的名称都会自动变形成:\_类名+名称 如下: - -```python -class A: - def __init__(self, name): - # 定义私有属性 - self.__name = name -# 实例化 -a = A("Lyon") -# 访问a中的__name属性 -print(a._A__name) -# 执行结果: Lyon -''' -__name自动变形为 _A__name -所以使用a._A__name是可以访问到的 -''' -``` - -由上可知变形的特点如下: - -1. 类中定义的\_\_name只能在内部使用 , 并且内部使用是引用的变形的结果,即( self.\_A\_\_name) -2. 这种变形其实是针对外部的变形 , 在外部是无法通过__name访问的 - -***PS : 这种变形机制其实并没有真正意义上限制我们从外部直接访问属性 , 知道了类名和属性名就可以拼出名字 : \_类名\_\_属性 , 然后就可以访问了 , 如 a.\_A\_\_name . 并且变形的过程只在类的定义时发生一次*** - -- 私有方法 - -```python -class A: - def __func(self): - print("In the A") -a = A() -a.__func() -# 执行结果: AttributeError: 'A' object has no attribute '__func' -``` - -```python -a._A__func() -# 执行结果: In the A -``` - -## 当私有遇到继承 - -当我们在继承中使用私有属性或者方法时 , 因为变形机制 , 我们已经不能将私有属性或者方法 , 来与普通属性或者方法那样看待了 - -- 私有属性继承 - -```python -class A: - def __init__(self, ame): - self.__name = ame -class B(A): - def __init__(self, name, ame): - self.__name = name - # 继承父类中的属性 - super().__init__(ame) -a = B('a', 'b') -print(a._A__name) -print(a._B__name) -''' -执行结果: -b -a -''' -``` - -例子说明 : 在上节中已经知道变形操作这回事了 , 当遇到继承时需要注意的就是 , 我们表面上看到的是两个类中都只有一个\_\_name属性 , 但是由于变形 , 使其在定义完成后就分别变成了\_A\_\_name 和 \_B\_\_name , 所以继承时已经是两个不同的属性了 , 所以两个属性都存在 , 只是我们表面上还是看不到 - -- 私有方法继承 - -与私有属性继承一样 , 需要注意私有方法名变形的问题 - -我们可以利用这一特点 , 来实现继承时达到子类不会覆盖父类方法的效果 - -```python -class A: - def __func(self): - print('from A') - def test1(self): - self.__func() -class B(A): - def __func(self): - print('from B') - def test2(self): - self.__func() -b=B() -b.test1() -b.test2() -''' -执行结果: -from A -from B -''' -``` - -## 封装与扩展性 - -封装在于明确区分内外 , 使得类实现者可以修改封装内的东西而不影响外部调用者的代码 ; 而外部使用者只知道一个接口(函数) , 只要接口(函数)名 , 参数不变 , 使用者的代码永远无需改变 . 这就提供了一个良好的合作基础 , 相当于只要接口这个基础约定不变 , 则代码改变也不足为虑 - -原始类 - -```python -class Room: - def __init__(self, name, owner, width, length, high): - self.name = name - self.owner = owner - self.__width = width - self.__length = length - self.__high = high - # 对外提供的求面积接口,隐藏内部实现详解 - def tell_area(self): - return self.__width * self.__length -r1 = Room('卧室','Lyon','0.3','2','2') -r1.tell_area() -``` - -修改类 - -```python -class Room: - def __init__(self, name, owner, width, length, high): - self.name = name - self.owner = owner - self.__width = width - self.__length = length - self.__high = high - # 对外提供的求体积接口,隐藏内部实现详解 - def tell_area(self): - return self.__width * self.__length * self.__high -r1 = Room('卧室','Lyon','0.3','2','2') -r1.tell_area() -``` - -我们发现我们将类的功能作出了修改 , 但是对于使用类功能的人来说 , 接口并没有发生变化 , 他们依然可以用原来的接口使用新功能 diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/05-\346\226\271\346\263\225\350\275\254\346\215\242.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/05-\346\226\271\346\263\225\350\275\254\346\215\242.md" deleted file mode 100644 index cfd7c1f5a..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/05-\346\226\271\346\263\225\350\275\254\346\215\242.md" +++ /dev/null @@ -1,188 +0,0 @@ -# Attack on Python - 方法转换 🐍 - - - - - - - - - - -## 属性方法 - -属性方法就是通过使用装饰器 `@property ` , 将一个方法变成一个静态属性 , 于是我们就可以通过访问属性 , 来或得一个方法的返回值 - -```python -from urllib.request import urlopen -class Web_page: - def __init__(self, url): - self.url = url - self.__content = None - # 将content方法变成属性 - @property - def content(self): - # 返回私有属性 - return self.__content if self.__content else urlopen(self.url).read() -con = Web_page('http://www.baidu.com') -res = con.content -print(res) -``` - -在property中为我们实现了三种方法 , get , set , delete - -```python -class Foo: - # 获取属性 - @property - def AAA(self): - print("执行了get方法") - # 设定属性值 - @AAA.setter - def AAA(self, value): - print("执行了set方法") - # 删除属性 - @AAA.deleter - def AAA(self): - print("执行了delete方法") -# 实例化 -f = Foo() -# 获取属性 -f.AAA -# 设置属性值,必须设置参数,即使不使用 -f.AAA = 'aaa' -# 删除属性值 -del f.AAA -''' -执行结果: -执行了get方法 -执行了set方法 -执行了delete方法 -''' -``` - -换一种写法看看 - -```python -class Foo: - def get_AAA(self): - print('执行了get方法') - def set_AAA(self,value): - print('执行了set方法') - def delete_AAA(self): - print('执行了delete方法') - # 实例化property类 - AAA = property(get_AAA, set_AAA, delete_AAA) -# 实例化 -f = Foo() -# 获取属性直接调用,执行了get_AAA -f.AAA -# 设置属性值,传入参数执行了set_AAA -f.AAA = 'aaa' -# 删除属性值,执行了delete_AAA -del f.AAA -''' -执行结果: -执行了get方法 -执行了set方法 -执行了delete方法 -''' -``` - -实际应用 - -```python -class Goods: - def __init__(self): - # 原价 - self.original_price = 100 - # 折扣 - self.discount = 0.8 - @property - def price(self): - # 实际价格 = 原价 * 折扣 - new_price = self.original_price * self.discount - return new_price - @price.setter - def price(self, value): - self.original_price = value - @price.deleter - def price(self): - del self.original_price -goods = Goods() -goods.price -goods.price = 200 -print(goods.price) -del goods.price -``` - -## 类方法 - -类方法是通过@classmethod装饰器 , 将普通方法变成类方法 , 类方法只能与类属性交互 , 不能访问实例变量 , 并且默认有一个cls参数传进来表示本类 - -```python -class Person: - country = 'China' - def __init__(self,name,age): - self.name = name - self.age = age - @classmethod - def search(cls): - # 在类方法中不能使用实例变量,会抛出AttributeError - print("I come from {}".format(cls.country)) - # print("{} come from {}".format(self.name,cls.country)) 报错 -p = Person('lyon','18') -p.search() -# 执行结果: I come from China -``` - -_PS_:类方法中的默认参数可以改成self , 并不会改变结果 , 同样只能访问类变量 , 不能访问实例变量 - -## 静态方法 - -静态方法是通过@staticmethod装饰器将类中的方法变成一个静态方法 - -静态方法就像静态属性一样 , 在类中可以通过 self. 的方式进行调用 , 但是静态是不能够访问实例变量或类变量的 , 也就是说静态方法中的self已经跟本类没有关系了 , 它与本类唯一的关联就是需要通过类名来进行调用 - -```python -class Person: - country = 'China' - def __init__(self,name,age): - self.name = name - self.age = age - # 已经跟本类没有太大的关系了,所以类中的属性无法调用 - @staticmethod - def search(): - print("我是静态方法") -p = Person('lyon','18') -p.search() -# 执行结果: 我是静态方法 -``` - -加上self , self只为一个普通参数而已 - -```python -class Person: - country = 'China' - def __init__(self,name,age): - self.name = name - self.age = age - @staticmethod - def search(self): - print("{} come from {}".format(self.name,self.country)) -p = Person('lyon','18') -# 将实例传入search方法中 -p.search(p) -# 执行结果: lyon come from China -``` - - - - - - - - - - - diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/06-\351\255\224\346\234\257\346\226\271\346\263\225.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/06-\351\255\224\346\234\257\346\226\271\346\263\225.md" deleted file mode 100644 index 3699d1d5d..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/06-\351\255\224\346\234\257\346\226\271\346\263\225.md" +++ /dev/null @@ -1,332 +0,0 @@ -# Attack on Python - 魔术方法 🐍 - - - - - - - - - - -## 介绍 - -在 Python 中 , 我们自定义类都是基于 Object 对象实现的 , 而在 Object 对象中有一些特殊的操作符 (`__method__`) 控制着整个对象的行为 , 所以 , 如果我们想对对象的行为进行控制 , 我们就需要自己来实现这些方法 - -下面 , 看看这些方法吧 - -## 基本行为 - -| 操作符 | 控制行为 | 调用说明 | -| ------------ | ----------------------- | ------------------------------------------------------------ | -| `__new__` | 对象创建 | `__init__` 只是用处初始化 , `__new__` 调用的结果会交给 `__init__` 进一步处理 | -| `__init__` | 对象初始化 | 构造函数 , 进行属性设置 | -| `__del__` | 对象删除 | 析构函数 , 进行对象的销毁 | -| `__repr__` | 对象显示 , 针对对象 | 终端显示 , 返回值必须为字符串 , 实例见表下方 | -| `__str__` | 对象显示 , 针对 `print` | `print` 显示结果 , 返回值必须为字符串 , 如果未实现该方法 , `print` 将使用 `__repr__` | -| `__bytes__` | 字节对象转换 | 返回值必须为一个bytes对象 , bytes(obj) | -| `__format__` | 格式化字符串 | 返回值必须为字符串对象 , format(obj) | -| `__lt__` | `<` 运算 | `x < y ` , 返回布尔值 , 下同 | -| `__le__` | `<=` 运算 | `x <= y ` | -| `__eq__` | `=` 运算 | `x == y ` | -| `__ne__` | `!=` 运算 | `x != y ` | -| `__gt__` | `>` 运算 | `x > y ` | -| `__ge__` | `>=` 运算 | `x >= y` | -| `__hash__` | 可哈希 | 返回一个哈希对象 , hash(obj) , 注意 : 定义该方法同时应该定义 `__eq__` | -| `__bool__` | 真假测试 | 返回布尔值 | -| `__call__` | 对象调用 | 在对象被调用时执行 | -| `__len__` | `len()` | 使用 `len(obj)` 时被调用 , 为防止值测试抛出 ` OverflowError ` , 必须定义 `__bool__()` | - -`__repr__` 与 `__str__` 对比实例 : - -```python -# 类定义 -class Foo: - - def __init__(self, name): - self.name = name - - def __repr__(self): - return '' % self.name - - def __str__(self): - return '' % self.name - -# 终端结果 ->>> obj = Foo('Lyon') ->>> obj - ->>> print(obj) - -``` - -## 访问行为 - -| 操作符 | 控制行为 | 调用说明 | -| ------------------ | ---------------- | ------------------------------------------------------------ | -| `__getattr__` | `.` 属性访问运算 | 获取 `x.name` , `__getattribute__` 查询失败后被调用 , 下方实例进一步说明 | -| `__getattribute__` | `.` 属性访问运算 | 获取 `x.name` , 查询属性时被调用 | -| `__setattr__` | `.` 属性赋值运算 | `self.attr = value → self.__setattr__("attr", value)` , 见下方实例进一步说明 | -| `__delattr__` | `.` 属性删除运算 | `del obj.name` 时被调用 | -| `__dir__` | `dir` 运算 | `dir()` 调用时被调用 , 必须返回一个序列 , `dir()` 会将序列转换成 list 并排序 | - -`__getattr__` 说明实例 : - -```python -# __getattr__ -# 注意在定义__getattr__或者__getattribute__时,不要出现 self. 因为这样会导致递归调用 -# 正确的方式是,使用object的__getattr__,__getattribute__,或者直接定义返回值 -class Foo: - - def __init__(self, name): - self.name = name - - def __getattr__(self, item): - return 'Attribute <%s> fetch failure' % item - - def __getattribute__(self, item): - # return object.__getattribute__(self, item) - if item == "name": - return 'Lyon' - else: - raise AttributeError(item) - -x = Foo('Lyon') -print(x.name) -print(x.age) -""" -执行结果: -Lyon -Attribute fetch failure -""" -``` - - `__setattr__` 说明实例 : - -```python -# __setattr__ -# 与__getattr__一样,在定义__setattr__时,不要出现 self. 因为这样会导致递归调用 -# 正确的方式是,使用object的__setattr__,或者使用self.__dict__[key] -class Foo: - - def __init__(self, name): - self.name = name - - def __setattr__(self, key, value): - # object.__setattr__(self, key, value) - if key == "name": - self.__dict__[key] = value - else: - raise AttributeError(key + ' not allowed') - - -x = Foo('Lyon') -x.name = "Kenneth" -x.age = 18 -print(x.__dict__) -""" -执行结果: -{'name': 'Kenneth'} -Traceback (most recent call last): - File "test.py", line 19, in - x.age = 18 - File "test.py", line 11, in __setattr__ - raise AttributeError(key + ' not allowed') -AttributeError: age not allowed -""" -``` - -## 描述器行为 - -| 操作符 | 控制行为 | 调用说明 | -| -------------- | ---------------- | ------------------------------------------------------------ | -| `__get__` | `.` 对象访问运算 | 访问对象时被调用 , 对象访问意指 `.` 后面接的不是一个属性而是一个对象 , 见下方实例说明 | -| `__set__` | `.` 对象赋值运算 | 对象赋值时被调用 | -| `__delete__` | `.` 对象删除运算 | 对象删除时被调用 | -| `__set_name__` | 所有者创建 | 在创建所有者时被调用 , Python 3.6 新增 | - -`__get__` , `__set__` , `__delete__` 实例 - -```python -# 关于对象访问一说,是建立在两个的使用基础上的 -# 单纯来讲,就是所有者类中的一个属性,是另一个类的实例 -class Dependency: - """ 附属类 """ - - def __get__(self, instance, owner): - print('%s.%s is called...' % ('Dependency', '__get__')) - - def __set__(self, instance, value): - print('%s.%s is called...' % ('Dependency', '__set__')) - - def __delete__(self, instance): - print('%s.%s is called...' % ('Dependency', '__delete__')) - -class Owner: - """ 所有者类 """ - dependency = Dependency() - -o = Owner() -o.dependency -o.dependency = 'Lyon' -del o.dependency - -""" -执行结果: -Dependency.__get__ is called... -Dependency.__set__ is called... -Dependency.__delete__ is called... -""" -``` - -`__set_name__` 是在上例 `Owner` 实例创建时被调用 , Python 3.6 新增 - -## 容器行为 - -| 操作符 | 控制行为 | 调用说明 | -| -------------- | ---------------- | --------------------------------------------- | -| `__getitem__` | 序列方式访问 | `self[key]` 时被调用 | -| `__missing__` | 序列方式访问失败 | `self[key]` 时 key 不在字典中被调用 | -| `__setitem__` | 序列方式赋值 | `self[key] = value` 时被调用 | -| `__delitem__` | 序列方式删除 | `del self[key]` 时被调用 | -| `__iter__` | 迭代环境 | 通过 `iter(obj)` 调用 , 如使用for循环进行遍历 | -| `__reversed__` | `reversed()` | `reversed(obj)` 时被调用 | -| `__contains__` | 成员关系 `in` | `item in self` 时调用 | - -## 运算行为 - -```python -# 基本运算行为 -object.__add__(self, other) # + -object.__sub__(self, other) # - -object.__mul__(self, other) # * -object.__matmul__(self, other) # @ -object.__truediv__(self, other) # / -object.__floordiv__(self, other) # // -object.__mod__(self, other) # % -object.__divmod__(self, other) # divmod() -object.__pow__(self, other[, modulo]) # pow() ** -object.__lshift__(self, other) # << -object.__rshift__(self, other) # >> -object.__and__(self, other) # & -object.__xor__(self, other) # ^ -object.__or__(self, other) # | - -# 二进制运算行为 -object.__radd__(self, other) -object.__rsub__(self, other) -object.__rmul__(self, other) -object.__rmatmul__(self, other) -object.__rtruediv__(self, other) -object.__rfloordiv__(self, other) -object.__rmod__(self, other) -object.__rdivmod__(self, other) -object.__rpow__(self, other) -object.__rlshift__(self, other) -object.__rrshift__(self, other) -object.__rand__(self, other) -object.__rxor__(self, other) -object.__ror__(self, other) - -# 加=运算行为 -object.__iadd__(self, other) # += -object.__isub__(self, other) # -= -object.__imul__(self, other) # *= -object.__imatmul__(self, other) -object.__itruediv__(self, other) -object.__ifloordiv__(self, other) -object.__imod__(self, other) -object.__ipow__(self, other[, modulo]) -object.__ilshift__(self, other) -object.__irshift__(self, other) -object.__iand__(self, other) -object.__ixor__(self, other) -object.__ior__(self, other) - -# 一元算数运算 -object.__neg__(self) -object.__pos__(self) -object.__abs__(self) -object.__invert__(self) - -# complex(),int(),float() -object.__complex__(self) -object.__int__(self) -object.__float__(self) - -# 整数值hex(X),bin(X),oct(X),o[X],O[X:] -object.__index__(self) - -# round(),trunc(),floor(),ceil() -object.__round__(self[, ndigits]) -object.__trunc__(self) -object.__floor__(self) -object.__ceil__(self) -``` - -## 上下文管理行为 - -| 操作符 | 控制行为 | 调用说明 | -| ------------ | ------------------------- | ------------------------------ | -| `__enter__` | 进入上下文环境 | 使用with进入上下文环境时被调用 | -| `__exit__` | 退出上下文环境 | 退出上下文环境时被调用 | -| `__aenter__` | 进入上下文环境 , 异步方法 | 使用with进入上下文环境时被调用 | -| `__aexit__` | 退出上下文环境 , 异步方法 | 退出上下文环境时被调用 | - -实例 - -```python -class Foo: - def __init__(self, name): - self.name = name - - def __enter__(self): - # 返回值赋值给as指定变量 - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - print('exc_type',exc_type) # 异常类型 - print('exc_val',exc_val) # 异常值 - print('exc_tb',exc_tb) # 追溯信息 - return True # 返回值为True,那么异常会被清空,就好像啥都没发生一样, - # with后的语句正常执行 - # 为False异常会抛出 - -with Foo('Lyon') as f: - raise AttributeError('ignore exception') -print('over') -``` - -## \_\_slots\_\_ - -`__slots__` 的作用是阻止在实例化类时为实例分配dict , 默认情况下每个类都会有一个dict,通过`__dict__` 访问 , 这个dict维护了这个实例的所有属性 - -作用 : - -- 减少内存使用 -- 限制对实例添加新的属性 - -缺点 : - -- 不可被继承 -- 不可动弹添加新属性 - -实例 - -```python -class Foo: - __slots__ = ['name', 'age'] - - def __init__(self, name, age): - self.name = name - self.age = age - -f = Foo('Lyon', 18) -print(f.name) -print(f.age) - -# 报错 -f.sex = 'Man' -``` - -更多 [Data model](https://docs.python.org/3/reference/datamodel.html#object.__index__) \ No newline at end of file diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/07-\345\217\215\345\260\204.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/07-\345\217\215\345\260\204.md" deleted file mode 100644 index 5a21b9f0f..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/07-\345\217\215\345\260\204.md" +++ /dev/null @@ -1,169 +0,0 @@ -# Attack on Python - 反射 🐍 - - - - - - - - - - -## 介绍 - -反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力 - -Python面向对象中的反射是通过字符串的形式来操作对象相关的属性 , 在Python中一切皆对象 , 并且只要是对象就可以使用反射 - -## hasattr - -判断对象中是否具有给定名称的属性 - -```python -def hasattr(*args, **kwargs): # real signature unknown - """ - Return whether the object has an attribute with the given name. - This is done by calling getattr(obj, name) and catching AttributeError. - """ - pass -``` - -实例1 - -```python -# 定义一个字符串 -name = 'lyon' -# 查看是否具有给定名称的属性 -bool = hasattr(name,'__len__') -# 打印bool -print(bool) -# 执行结果:True -''' -说明:很多初学者可能一直不理解为什么说Python里一切皆对象,因为没有意识到,在Python中str、list、int ...等这些数据类型,其实就是用class写出来的一个模型,那么既然是类就会有属性这一说,就可以利用反射来操作对象了 -''' -``` - -实例2 - -```python -import sys -def s1(): - pass -def s2(): - pass -this_modules = sys.modules[__name__] -print(type(this_modules),hasattr(this_modules,'s1')) -# 执行结果: True -``` - -## getattr - -从一个对象中获取属性名称 - -```python -def getattr(object, name, default=None): # known special case of getattr - """ - Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. - When a default argument is given, it is returned when the attribute doesn't - exist; without it, an exception is raised in that case. - """ - pass -``` - -实例 - -```python -class A: - def __init__(self,name,age): - self.name = name - self.age = age - def hello(self): - print('hello {}'.format(self.name)) -# 创建一个实例a -a = A('Lyon',18) -# 获取静态属性age -age = getattr(a,'age') -# 打印age -print(age) -# 获取动态属性hello,即方法 -hello = getattr(a,'hello') -# 执行hello -hello() -# 如果不存在就需要设置default参数,否则就报错 -birthday = getattr(a,'birthday','today') -# 打印birthday,即为default参数 -print(birthday) -''' -执行结果: -18 -hello Lyon -today -''' -``` - -## setattr - -定义属性 - -```python -def setattr(x, y, v): # real signature unknown; restored from __doc__ - """ - Sets the named attribute on the given object to the specified value. - setattr(x, 'y', v) is equivalent to ``x.y = v'' - """ - pass -``` - -实例 - -```python -class B: - def __init__(self): - pass -b = B() -# 新增属性,如果存在即为修改 -setattr(b, 'age', 18) -# 打印age属性 -print(b.age) -# 新增add方法 -setattr(b, 'add', lambda age: age + 1) -# 修改age属性 -b.age = b.add(b.age) -# 打印age属性 -print(b.age) -''' -执行结果: -18 -19 -''' -``` - -## delattr - -删除对象中的属性 - -```python -def delattr(x, y): # real signature unknown; restored from __doc__ - """ - Deletes the named attribute from the given object. - delattr(x, 'y') is equivalent to ``del x.y'' - """ - pass -``` - -实例 - -```python -class C: - def __init__(self,name,age): - self.name = name - self.age = age - def add(self): - self.age = self.age + 1 -c = C('Lyon',18) -# 删除c中的 -delattr(c,'name') -# print(c.name) 报错 -delattr(c,'add') -# c.add() 报错 -``` \ No newline at end of file diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/08-\345\274\202\345\270\270\345\244\204\347\220\206.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/08-\345\274\202\345\270\270\345\244\204\347\220\206.md" deleted file mode 100644 index 64c861722..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/08-\345\274\202\345\270\270\345\244\204\347\220\206.md" +++ /dev/null @@ -1,259 +0,0 @@ -# Attack on Python - 异常处理 🐍 - - - - - - - - - - -## 介绍 - -在我们写程序时难免会出现错误 , 一种为语法错误 , 即为python解释器的语法检测都通不过的错误 , 这种错误只能我们在程序执行前就处理好 . 另一种为逻辑错误 , 这是我们在程序设计时所出现的错误 , 也就是我们通常所说的bug - -在编程过程中为了增加友好性 , 在程序出现bug时一般不会将错误信息显示给用户 , 而是显示一个提示错误的页面 - -基本语法 - -```python -try: - pass -except Exception as e: - pass -# except: 默认就为Exception -``` - -实例 - -```python -try:0 - # 用户输入 - num = input("Please input the number:") - # 遇到无法int的对象就用except进行捕获 - int(num) -# 利用ValueError来捕获错误,并将捕获的错误返回给e -except ValueError as e: - # 打印捕获信息 - print(e) -''' -执行结果: -Please input the number:Lyon -invalid literal for int() with base 10: 'Lyon' -''' -``` - -PS : 在try代码块中只要出现异常 , 那么代码块中异常后面的代码就不会执行了 - -## 异常种类 - -Python中的异常种类非常多 , 上述中说了个ValueError只能处理值错误 , 当我们需要处理其他的错误时 , 就需要对症下药了 , 并且异常其实也是class , 并且所有的异常都继承了BaseException类 - -常用异常 - -| 异常名称 | 说明 | -| ----------------- | ---------------------------------------- | -| ValueError | 传入无效的参数 | -| AttributeError | 与对象的属性相关 | -| IOError | 输入/输出操作失败 , 基本上是无法打开文件 | -| ImportError | 无法引入模块或包 , 基本上是路径问题或名称错误 | -| IndentationError | 缩进错误 | -| IndexError | 下标索引超出范围 , 即索引不存在 | -| KeyError | 字典中不存在该key | -| KeyboardInterrupt | 用户中断执行 , 即被Ctrl + C | -| NameError | 变量还未声明/初始化 | -| SyntaxError | 语法错误 | -| TypeError | 传入对象类型与要求的不符合 | -| UnboundLocalError | 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它 | -| ValueError | 传入无效的参数 | - -继承关系与其他异常 - -```python -# 所有异常都继承自BaseException类 -BaseException - +-- SystemExit - +-- KeyboardInterrupt - +-- GeneratorExit - +-- Exception - +-- StopIteration - +-- StopAsyncIteration - +-- ArithmeticError - | +-- FloatingPointError - | +-- OverflowError - | +-- ZeroDivisionError - +-- AssertionError - +-- AttributeError - +-- BufferError - +-- EOFError - +-- ImportError - +-- ModuleNotFoundError - +-- LookupError - | +-- IndexError - | +-- KeyError - +-- MemoryError - +-- NameError - | +-- UnboundLocalError - +-- OSError - | +-- BlockingIOError - | +-- ChildProcessError - | +-- ConnectionError - | | +-- BrokenPipeError - | | +-- ConnectionAbortedError - | | +-- ConnectionRefusedError - | | +-- ConnectionResetError - | +-- FileExistsError - | +-- FileNotFoundError - | +-- InterruptedError - | +-- IsADirectoryError - | +-- NotADirectoryError - | +-- PermissionError - | +-- ProcessLookupError - | +-- TimeoutError - +-- ReferenceError - +-- RuntimeError - | +-- NotImplementedError - | +-- RecursionError - +-- SyntaxError - | +-- IndentationError - | +-- TabError - +-- SystemError - +-- TypeError - +-- ValueError - | +-- UnicodeError - | +-- UnicodeDecodeError - | +-- UnicodeEncodeError - | +-- UnicodeTranslateError - +-- Warning - +-- DeprecationWarning - +-- PendingDeprecationWarning - +-- RuntimeWarning - +-- SyntaxWarning - +-- UserWarning - +-- FutureWarning - +-- ImportWarning - +-- UnicodeWarning - +-- BytesWarning - +-- ResourceWarning -``` - -为什么要说继承关系 , 因为在使用except是 , 它不但捕获该异常 , 还会把该异常类的子类也全部捕获 - - 所以我们把 ` Exception` 也叫做万能异常 , 因为除了SystemExit , KeyboardInterrupt 和 GeneratorExit 三个异常之外 , 其余所有异常基本都为Exception的子类 - -## 异常其他结构 - -多分支 - -```python -name = 'Lyon' -try: - int(name) -except IndexError as e: - print(e) -except KeyError as e: - print(e) -# ValueError捕获成功 -except ValueError as e: - print(e) -# 执行结果:invalid literal for int() with base 10: 'Lyon' -``` - -else - -```python -num = '1' -try: - int(num) -except ValueError as e: - print(e) -# 与for..else 和 while...else类似,没被打断就执行 -else: - print('没有异常就执行我') -# 执行结果: 没有异常就执行我 -``` - -finally - -```python -num = 'Lyon' -try: - int(num) -except ValueError as e: - print(e) -else: - print('没有异常就执行我') -finally: - print('不管怎么样都执行我') -''' -执行结果: -invalid literal for int() with base 10: 'Lyon' -不管怎么样都执行我 -''' -``` - -## 主动触发异常 - -raise - -```python -try: - raise TypeError('类型错误') -except Exception as e: - print(e) -# 执行结果: 类型错误 -``` - -## 自定义异常 - -通过继承BaseException来实现 - -```python -class LyonException(BaseException): - def __init__(self,msg): - self.msg = msg - def __str__(self): - return self.msg -try: - # 主动触发异常 - raise LyonException('你就是错了,别问为什么') -# 捕获LyonException -except LyonException as e: - print(e) -# 执行结果: 你就是错了,别问为什么 -``` - -## 断言 - -断定条件成立 , 不成立就出现AssertionError异常 - -```python -try: - # 断定1等于1 - assert 1 == 1 - print('第一个断言成功就执行') - assert 2 == 1 - print("第二个断言失败不执行") -# 捕获AssertionError异常 -except Exception: - print("抓到你了") -''' -执行结果: -第一个断言成功就执行 -抓到你了 -''' -``` - - - - - - - - - - - - - diff --git "a/01-Python/03-\345\257\271\350\261\241\347\257\207/README.md" "b/01-Python/03-\345\257\271\350\261\241\347\257\207/README.md" deleted file mode 100644 index c62b01d87..000000000 --- "a/01-Python/03-\345\257\271\350\261\241\347\257\207/README.md" +++ /dev/null @@ -1,15 +0,0 @@ -# Attack on Python - 对象篇 🐍 - - - - - - - - - - -## 介绍 - -一切皆对象 - diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/01-\346\250\241\345\235\227.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/01-\346\250\241\345\235\227.md" deleted file mode 100644 index 86c4566f8..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/01-\346\250\241\345\235\227.md" +++ /dev/null @@ -1,211 +0,0 @@ -# Attack on Python - 模块 🐍 - - - - - - - - - - -## import - -我们知道一个模块就是一个py文件 , 当我们执行py文件时 , python解释器会先加载内置命名空间 , 其次是加载全局命名空间( 学习函数就已知道 ) , 还有个局部命名空间就不说了 - -当python解释器遇到我们的import语句时 , import会将模块进行初始化 , 即会将模块中的内容执行一遍 , 既然执行 , 那么被import的模块的全局命名空间就创建成功了 , 并且会将这个创建成功的命名空间加载到使用import语句的本地的全局命名空间 . 于是我们就可以在本地使用被导入模块了 - -自定义模块my_module.py , 文件名my_module.py , 模块名my_module - -```python -在模块my_module.py下 ----------------文件内容---------------- -| print('from the my_module.py') | -| def read(): | -| print('in the module.py read') | --------------------------------------- -在当前文件test.py下 ----------------文件内容---------------- -| import my_module | -| my_module.read() | --------------------------------------- -# 执行test.py文件,打印结果 -''' -# 执行了my_module.py的print语句 -from the my_module.py -# 成功调用my_module.py中的read函数 -in the module.py read -''' -``` - -import语句是可以在程序中的任意位置使用的 , 且针对同一个模块import多次时 , 为了防止你重复导入 , python进行了如下优化 : 第一次导入后就将模块名加载到内存了 , 后续的import语句仅是对已经加载大内存中的模块对象增加一次引用 , 不会重新执行模块内的语句 - -import多次同以模块 - -```python -在模块my_module.py下 ----------------文件内容---------------- -| print('from the my_module.py') | -| def read(): | -| print('in the module.py read') | --------------------------------------- -在当前test.py文件下 ----------------文件内容---------------- -| import my_module | -| import my_module | -| import my_module | -| my_module.read() | --------------------------------------- -# 执行test.py文件,打印结果 -''' -# 仅执行了一次my_module.py中的print语句 -from the my_module.py -# 成功调用my_module.py中的read函数 -in the module.py read -''' -``` - -我们可以从sys.modules中找到当前已经加载的模块 , sys.modules是一个字典 , 内部包含模块名与模块对象的映射 ,该字典决定了导入模块时是否需要重新导入 - -每个模块的命名空间都是相互独立的 , 这样我们在编写自己的模块时 , 就不用担心我们定义在自己模块中全局变量在被导入时 , 与使用者的同名全局变量冲突 - -***ps:模块中的内容使用 :` 模块名 .函数或者变量或者类 `来进行调用*** - -总结 - -首次导入模块时python会做三件事 - -1. 为源文件(如my_module模块) 创建新的命名空间 , 在my_module中定义的函数和方法若是使用到了globals() 时访问的就是这个命名空间 -2. 在新创建的命名空间执行模块中包含的代码 , 如上例中执行了模块中的print语句 , 并加载了函数 -3. 创建名字my_module 来引用该命名空间 , 使用my_module.名字的方式访问my_module.py文件中定义的名字 , 且名字与test.py文件中的名字来自两个完全不同的地方 - -## import ... as ... - -为模块取名 - -根据用户需求选择额不同的sql(数据库)功能 - -```python -# 在mysql.py中 -def sqlparse(): - print('from mysql sqlparse') -``` - -```python -# 在oracle.py中 -def sqlparse(): - print('from oracle sqlparse') -``` - -```python -# 在test.py中 -db_type = input('Please choice the database >>').strip() -if db_type == 'mysql': - import mysql as db -elif db_type == 'oracle': - import oracle as db -``` - -一行导入多个模块 - -```python -import sys,os,re -``` - -## from ... import ... - -相当于import , 同样会执行一遍my_module文件 , 同样也会创建命名空间 , 但是from .. . import ... 是将my_module中的名字直接导入到当前的命名空间 , 也就意味着可以直接调用 , 而不用像import那样 , 利用 my_module *.* 名字 来进行调用 - -两种方式对比 - -```python -# import方式 -import my_module -# 模块名 + '.' + 函数名进行调用 -my_module.read() -# from...import...方式 -from my_module import read -# 直接用函数名调用 -read() -``` - -PS : 利用from...import...方式进行导入 , 一般用来指定导入模块中的某一部分 , 或者方便使用 , 还有一个特殊的导入 from ... import * (作用是导入模块中的所有内容 , 但是有弊端) - -as - -```python -from my_module import read as r -``` - -多行 - -```python -from my_module import (read1, - read2, - read3) -``` - -## from ... import * - -from my_module import \* 会将my_module 中的所有的不是以下划线 ' _ ' 开头的名字都导入到当前位置 , 在大部分情况下我们python程序不应该使用这种导入方式 , 因为你无法知道 \* 导入了什么名字 , 很有可能会覆盖掉你已经定义过的名字 , 而且可读性极其的差 - -在my_module.py中新增一行 - -```python -# 这样在另外一个文件中用from my_module import * 就能导入列表中规定的两个名字 -__all__ = ['money' , 'read1'] -``` - -## if \_\_name\_\_ == '\_\_main\_\_' - -所有的模块都有一个内置属性 \_\_name\_\_ , 可以用来查看模块名 - -在当前文件执行时会返回' \__main__ ', 如果不在当前文件执行那么就会返回所执行的模块名 - -```python -# my_module.py中 -print(__name__) -# 执行my_module.py -执行结果: __main__ -``` - -```python -# test.py中 -import my_modlue -# 执行 test.py -执行结果: my_module -``` - -所以利用\__name__ 属性 , 我们就可以实现 , 模块可以自己执行 , 也可以导入到别的模块中执行 , 并且他不会执行 **两次 ** - -```python -# my_module.py中 -def main(): - print('we are in %s' % __name__) -# 如果在当前文件下就会执行 -if __name__ == '__main__': - main() -``` - -```python -# test.py中 , 执行test.py -# 解释from语句时 , 并不会执行my_module中的main() -from my_module import main -# 执行main() -main() -执行结果:we are in my_module -# 结果显示只执行了一次main() -``` - - - - - - - - - - - - - diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/02-\345\214\205.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/02-\345\214\205.md" deleted file mode 100644 index 7e14a2ca0..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/02-\345\214\205.md" +++ /dev/null @@ -1,168 +0,0 @@ -# Attack on Python - 包 🐍 - - - - - - - - - - -## 介绍 - -为了帮助组织模块并提供命名层次结构 , Python有一个概念 : 包 - -包就相当于一个文件系统的目录 , 模块相当于目录中的文件 , 也就是说所有的包都时模块 , 但不是所有的模块都是包 - -包只是一种特殊的模块 , 具体来说 , 包含`__path__` 属性的任何模块都被视为包 - -所有模块都有一个名称 , 子包名与他们的父包名由点隔开 , 类似于Python的标准属性访问语法 - -Python定义了两种类型的包 , 即 [regular packages](https://docs.python.org/3/glossary.html#term-regular-package) 和 [namespace packages](https://docs.python.org/3/glossary.html#term-namespace-package) , 我们通常使用的就是regular packages , 对于namespace packages可通过上述链接进行学习 - -## 常规包 - -常规包时传统的包 , 因为它们存在于Python 3.2 及更早的版本中 ; 常规包通常实现为包含`__init__.py` 文件的目录 - -当我们导入常规包时 , 这个`__init__.py`文件会被隐式执行 (这意味着我们应该在`__init__.py` 文件中完成我们的导入 , 即初始化包) , 它定义的对象被绑定到包命名空间中 ; Python会在导入时为模块添加一些其他属性 , 如下 : - -```python -parent/ - __init__.py - one/ - __init__.py - two/ - __init__.py - three/ - __init__.py -''' -导入parent.one将隐式执行parent/__init__.py和parent/one/__init__.py -随后导入parent.two或parent.three将执行parent/two/__init__.py和parent/three/__init__.py -''' -``` - -在我们使用`import`导入文件时 , 产生命名空间的名字来源于文件 , `import packages`产生的命名空间的名字同样来源于文件 , 即包下的`__init__.py` , 导入包本质就是在导入该文件 - -注意 : 在Python 3中 , 即使包下没有`__init__.py`文件 , `import packages`仍然不会报错 , 而在Python 2中 , 包下一定要有该文件 , 否则`import packages`就会抛出异常 - -## 导入包 - -glance包 - -```python -glance/ -├── __init__.py -├── api -│ ├── __init__.py __all__ = ['policy','versions'] -│ ├── policy.py -│ └── versions.py -├── cmd __all__ = ['manage'] -│ ├── __init__.py -│ └── manage.py -└── db __all__ = ['models'] - ├── __init__.py - └── models.py -``` - -### import - -```python -import glance.db.models -glance.db.models.register_models('mysql') -``` - -### from ... import ... - -```python -# import后接的必须是明确的模块或者方法或者类或者变量,否则会抛出异常 -from glance.db import models -models.register_models('mysql') -from glance.db.models import register_models -register_models('mysql') -``` - -### 绝对导入与相对导入 - -我们的glance包时写给别人用的 , 但是在glance包内部也会有彼此之间互相导入的需求 , 那么就有了绝对导入和相对导入两种方式 : - -绝对导入 : 以`glance`作为起始 - -相对导入 : 用`.`或者`..` 的方式最为起始 , 只能在一个包中使用 , 即包内目录 - -我们在`glance/api/version.py`中导入`glance/cmd/manage.py` - -glance/api/version.py 下 - -```python -# 绝对导入 -from glance.cmd import manage -manage.main() -# 相对导入,一个点表示当前目录,两个点表示上一层 -from ..cmd import manage -manage.main() -``` - -绝对导入 - -```python -glance/ -├── __init__.py from glance import api - from glance import cmd - from glance import db -├── api -│ ├── __init__.py from glance.api import policy - from glance.api import versions -│ ├── policy.py -│ └── versions.py -├── cmd from glance.cmd import manage -│ ├── __init__.py -│ └── manage.py -└── db from glance.db import models - ├── __init__.py - └── models.py -``` - -相对导入 - -```python -glance/ -├── __init__.py from . import api #.表示当前目录 - from . import cmd - from . import db -├── api -│ ├── __init__.py from . import policy - from . import versions -│ ├── policy.py -│ └── versions.py -├── cmd from . import manage -│ ├── __init__.py -│ └── manage.py from ..api import policy - #..表示上一级目录,想再manage中使用policy中的方法就需要回到上一级glance目录往下找api包,从api导入policy -└── db from . import models - ├── __init__.py - └── models.py -``` -### 单独导入 - -单独导入包时不会导入包中所有包含的所有子模块 , 如 : - -```python -import glance -glance.cmd.manage.main() -''' -执行结果: -AttributeError: module 'glance' has no attribute 'cmd' -''' -``` - -上述导入会隐式执行`__init__.py` , 所以我们可以让这个文件来初始化 , 如下 : - -```python -# glance/__init__.py -from . import cmd -# glance/cmd/__init__.py -from . import manage -``` - -关于导入系统 : https://docs.python.org/3/reference/import.html \ No newline at end of file diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/03-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/03-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" deleted file mode 100644 index 98d623881..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/03-\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ /dev/null @@ -1,243 +0,0 @@ -# Attack on Python - 正则表达式 🐍 - - - - - - - - - - -## 介绍 - -正则表达式并不是python的一部分,而是在各个编程语言都有的一种用于处理字符串的强大工具。 - -使用正则处理字符串在效率上可能不如str自带的方法,但是它的功能十分强大。python中的正则封装在re模块中。 - -## 匹配方法 - -首先将匹配方法进行说明,即re模块的内置方法 - -> `re.match`(*pattern*, *string*, *flags=0*) : 👈 - -从字符串的开头开始匹配,匹配成功返回一个`_sre.SRE_Match`类型,可用`.group()` 取出结果,失败返回None - -*pattern* : 匹配格式 - -*string* : 要匹配的字符串 - -*flags* : 编译标志位,用于修改正则表达式的匹配方式 - -```python -# 导入re模块,后续方法实例省略这一步 ->>> import re ->>> res = re.match('lyon','lyon') -# 查看类型 ->>> type(res) - -# 用.group()取出结果 ->>> res.group() -'lyon' -``` - -> `re.search`(*pattern*, *string*, *flags=0*) : 👈 - -扫描整个字符串,匹配成功则返回匹配到的第一个对象(`_sre.SRE_Match`类型),失败返回None - -*pattern* : 匹配格式 - -*string* : 要匹配的字符串 - -*flags* : 编译标志位,用于修改正则表达式的匹配方式 - -```python -# 匹配数字 ->>> re.search('\d+','abc123abc').group() -'123' -``` - -> `re.findall`(*pattern*, *string*, *flags=0*) : 👈 - -匹配字符串所有的内容,把匹配到的字符串以列表的形式返回 - -*pattern* : 匹配格式 - -*string* : 要匹配的字符串 - -*flags* : 编译标志位,用于修改正则表达式的匹配方式 - -```python -# 匹配数字 ->>> re.findall('\d','abc123abc456') -['1','2','3','4','5','6'] -``` - -> `re.split`(*pattern*, *string*, *maxsplit=0*, *flags=0*) : 👈 - -指定格式进行切分,返回一个列表 - -*pattern* : 切分格式 - -*string* : 要切分的字符串 - -*maxsplit* : 切分次数 - -*flags* : 编译标志位,用于修改正则表达式的匹配方式 - -```python -# 以数字进行切分 ->>> re.split('\d+','abc123abc123+-*/45') -['abc', 'abc', '+-*/', ''] -``` - -> `re.sub`(*pattern*, *repl*, *string*, *count=0*, *flags=0*) : 👈 - -替换匹配到的字符串并返回替换后的结果 - -*pattern* : 匹配格式 - -*repl* : 替换格式 - -*string* : 要匹配替换的字符串 - -*flags* : 编译标志位,用于修改正则表达式的匹配方式 - -```python ->>> re.sub("abc","def","abc123abc") -'def123def' -# 只替换查找到的字符串一次 ->>> re.sub("abc","def","abc123abc",count=1) -'def123abc' -``` - -*flags说明(轻轻了解) :* - -| 标志 | 说明 | -| --------------------- | ---------------------------------- | -| re.I (re.IGNORECASE) | 忽略大小写(括号内为全拼写法,效果一样) | -| re.M (MULTILINE) | 多行模式,改变 '^' 和 '$' 的行为 (改变?见下节匹配模式) | -| re.S (DOTALL) | 任意匹配模式,改变 ' . ' 的行为(同上) | -| re.L (LOCALE) | 做本地化识别(locale-aware)匹配,法语等 | -| re.X (VERBOSE) | 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解 | -| re.U | 根据Unicode字符集解析字符,这个标志影响\w,\W,\b,\B | - -```python -# 忽略大小写 ->>> re.findall('a','aA123aAAA',flags=re.I) -['a', 'A', 'a', 'A', 'A', 'A'] -``` - -*注意转义的问题:当我们的匹配格式中有我们需要匹配的特殊字符,如 ' \ '、' * '、' + '等,为了让解释器知道我们这是需要匹配的,我们可以在格式前加 'r' 进行转义,或者在每个需要匹配的之前加个 ' \ '来完成转义。* - -*`.group()`小知识:* - -在我们使用`.group()`方法时,要注意如果我们的正则表达式没有匹配到结果,即返回None时,用`.group()`时就会报错,因为`"NoneType"`是没有该方法的,只有`_sre.SRE_Match`类型才能使用该方法。 - -## 匹配模式 - -### 字符匹配 - -| 字符 | 描述 | -| :---- | ---------------------------------------- | -| . | 默认匹配除\n之外的任意一个字符,若指定flag DOTALL,则匹配任意字符,包括换行 | -| \d \D | 匹配数字0-9/非数字 | -| \s | 匹配空白字符、\t、\n、\r , re.search("\s+","ab\tc1\n3").group() 结果 '\t' | -| \S | 非空白字符 | -| \w | 匹配[A-Za-z0-9] | -| \W | 匹配非[A-Za-z0-9] | -| \b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 | -| \B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 | - -### 次数匹配 - -| 字符 | 描述 | -| -------- | ---------------------------------------- | -| * | 匹配\*号前的字符0次或多次,re.findall("ab*","cabb3abcbbac") 结果为['abb', 'ab', 'a'] | -| + | 匹配前一个字符1次或多次,re.findall("ab+","ab+cd+abb+bba") 结果['ab', 'abb'] | -| ? | 匹配前一个字符0次或者1次 | -| {m} | 匹配前一个字符m次 | -| {n,m} | 匹配前一个字符n到m次,re.findall("ab{1,3}","abb abc abbcbbb") 结果'abb', 'ab', 'abb'] | -| *?/+?/?? | 转为非贪婪模式(尽可能少的匹配) | -| [...] | 字符集,匹配字符集中任意字符,字符集可给出范围或者逐个列出 | - -### 边界匹配 - -| 字符 | 描述 | -| ---- | ---------------------------------------- | -| ^ | 匹配字符串开头,若指定flags MULTILINE,这种也可以匹配上,(r'^a','\nabc\neee',flags=re.MULTILINE) | -| $ | 匹配字符结尾,或e.search("foo$","bfoo\nsdfsf",flags=re.MULTILINE).group()也可以 | -| \A | 只从字符开头匹配,re.search("\Aabc","alexabc") 是匹配不到的 | -| \Z | 匹配字符结尾,同$ | - -### 分组匹配 - -| 字符 | 描述 | -| :-------- | :--------------------------------------- | -| 丨 | 匹配丨左或丨右的字符,re.search("abc丨ABC","ABCBabcCD").group() 结果'ABC' | -| (...) | 分组匹配,re.search("(abc){2}a(123丨456)c", "abcabca456c").group() 结果 abcabca456c | -| (?P\<..>) | 命名分组匹配 re.search("(?P\[0-9]{4})(?P\[0-9]{2})(?P\[0-9]{4})","371481199306143242").groupdict("city") 结果{'province': '3714', 'city': '81', 'birthday': '1993'} | - -## 补充 - -补充方法 - -> `re.subn`(pattern, repl, string, count=0, flags=0) : - -返回替换后的字符串和替换次数 - -> `re.escape`(pattern) : - -自动进行转义,除了ASCII字母、数字和'_'之外 - -> `re.compile`(pattern, flags=0) : - -生成一个_sre.SRE_Pattern对象,以便多次调用 - -> `re.finditer`(pattern, string, flags=0) : - -返回一个匹配结果的迭代器,可迭代取值 - -> `re.fullmatch`(pattern, string, flags=0) : - -完整匹配,不完整则返回None - -> `re.template`(pattern, flags=0) : - -没人知道是干嘛的,跟compile差不多 - -> `re.purge()` : - -清除正则表达式缓存 - -''' -当你在程序中使用 re 模块,无论是先使用 compile 还是直接使用比如 findall 来使用正则表达式操作文本,re 模块都会将正则表达式先编译一下, 并且会将编译过后的正则表达式放到缓存中,这样下次使用同样的正则表达式的时候就不需要再次编译, 因为编译其实是很费时的,这样可以提升效率,而默认缓存的正则表达式的个数是 100, 当你需要频繁使用少量正则表达式的时候,缓存可以提升效率,而使用的正则表达式过多时,缓存带来的优势就不明显了 -''' - -## 实例 - -连续匹配 - -```python -# 导入模块 ->>> import re -# 获取字符串 ->>> source ='192.168.0.1 25/Oct/2012:14:46:34 "GET /api HTTP/1.1" 200 44 "http://abc.com/search" "Mozilla/5.0"' -# 设置匹配格式 ->>> res = re.match('^(?P[^ ]*) (?P[^ ]*) "(?P[^"]*)" (?P[^ ]*) (?P[^ ]*) "(?P[^"]*)" "(?P[^"]*)"',source) -# 返回一个字典,groupdict中的key为组名,value为值 ->>> source_dic = res.groupdict() -# for循环打印 ->>> for k in source_dic: - #打印key和vaule -... print(k+": "+source_dic[k]) -... -# 打印结果 -date: 25/Oct/2012:14:46:34 -remote_ip: 192.168.0.1 -referrer: http://abc.com/search -status: 200 -user_agent: Mozilla/5.0 -size: 44 -request: GET /api HTTP/1.1 -``` diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/04-\345\272\217\345\210\227\345\214\226.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/04-\345\272\217\345\210\227\345\214\226.md" deleted file mode 100644 index 529addab1..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/04-\345\272\217\345\210\227\345\214\226.md" +++ /dev/null @@ -1,209 +0,0 @@ -# Attack on Python - 序列化 🐍 - - - - - - - - - - -## 介绍 - -先说个例子 , 当我们将一个字典或者列表再或者变量存入磁盘中 , 而存入磁盘后原本数据类型就得不到保持了 . 这个时候我们就得用序列化和反序列化了 - -序列化是将对象进行存储时保持当时对象的状态 , 实现其生命周期的延长 - -反序列化则是将存储的对象读取出来并转成原本的数据类型 - -序列化的目的 - -1. 以某种存储形式使自定义对象持久化 -2. 将对象从一个地方传递到另一个地方 -3. 使程序更具维护性 - -***此时应该想到 eval :***那么问题来了 , 序列化所达到的功能我用eval()也能达到啊 , eval()直接就可以把字符串转换成python解释器能解释的代码 , 即可以直接将字符串中的字典 , 列表都转成原来的数据类型 . 但是要注意的是 , eval本来就是将字符串内容转换成python可以执行的代码 , 并执行它 , 这样看来eval就不安全了 , 因为如果在我能读取的内容中含有一些其他的 ' 危险代码 ' 如 ' 删除文件 ' , 于是造成了毁灭性的打击 , 所以eval是存在风险的 - -Python为我们提供了三个序列化工具 , 分别是 json , pickle , shelve - -## json - -用于字符串和python数据类型之间进行转换 , 因为json表示出来就是一个字符串 - -json模块提供了四个方法 - -| 方法 | 描述 | -| ----- | ----------------------------- | -| dump | 接收一个文件句柄 , 将原数据类型转换成字符串写入文件 | -| load | 接收一个文件句柄 , 将文件中的字符串转换成原数据类型返回 | -| dumps | 接收一个数据类型 , 将其转换成字符串 | -| loads | 接收一个字符串 , 将其转换成原数据类型 | - -dump 和 load 实例 - -```python -# 导入json模块 -import json -# 创建一个文件句柄 -f = open('json_file','w') -# 创建一个字典 -dic = {'k1':'v1','k2':'v2'} -# 将字典转换成字符串写入文件 -json.dump(dic,f) -# 关闭文件 -f.close() -# 创建一个文件句柄 -f = open('json_file') -# 将文件中的字符串读出并转换成原数据类型 -dic2 = json.load(f) -# 关闭文件句柄 -f.close() -# 打印类型和结果 -print(type(dic2),dic2) -# {'k1': 'v1', 'k2': 'v2'} -``` - -dumps 和 loads 实例 - -```python -# 导入json模块 -import json -# 创建一个新列表 -lst = ['1','2','3','4'] -# 将列表转换成字符串,用j_d来接收返回值 -j_d = json.dumps(lst) -# 将字符串转换成原数据类型,用j_s来接收返回值 -j_s = json.loads(j_d) -# 打印j_d的值以及类型 -print(j_d,type(j_d)) -# ["1", "2", "3", "4"] -# 打印j_s的值以及类型 -print(j_s,type(j_s)) -# ['1', '2', '3', '4'] -``` - -loads的特殊情况 - -```python -# 导入json模块 -import json -# 创建一个字符串,内部为一个字典 -dic_s = "{'k1':'v1','k2':'v2','k3':3}" -# 将字符串转换成字典 -json.loads(dic_s) -# 解释器出现报错 -# json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1) -''' -报错原因,用json的loads功能时,字符串类型的字典中的字符串必须由 "" 表示 -即上面的dic_s应该改为 '{"k1":"v1","k2":"v2","k3":3}' - -结论:用json的loads功能时,字符串类型的字典中的字符串必须由 "" 表示 -''' -``` - -PS : json可用于不同语言之间的数据交换 - -## pickle - -用于python特有的类型和python的数据类型间进行转换 - -pickle模块也提供了四个方法 , 与json一样 dumps , dump , loads , load - -由于pickle是对于python特有的类型 , 所以 load 和 loads方法不仅支持字典 , 列表 , 它还能把python中任意的数据类型进行序列化 - -```python --------dumps和loads-------- -# 导入pickle模块 -import pickle -# 创建一个字典 -dic = {'k1':'v1','k2':'v2'} -# 将字典转换成二进制内容 -p_d = pickle.dumps(dic) -# 将二进制内容转换成字典 -p_l = pickle.loads(p_d) -# 打印p_d -print(p_d) -# b'\x80\x03}q\x00(X\x02\x00\x00\x00k2q\x01X\x02\x00\x00\x00v2q\x02X\x02\x00\x00\x00k1q\x03X\x02\x00\x00\x00v1q\x04u.' -# 打印p_d的类型 -print(type(p_d)) -# -# 打印p_l -print(p_l) -# {'k2': 'v2', 'k1': 'v1'} -# 打印p_l的类型 -print(type(p_l)) -# ----------dump 和 load--------- -# 创建一个文件句柄 -f = open('pickle_file','wb') -# 写入内容 -pickle.dump('lyon',f) -# 关闭文件 -f.close() -# 创建一个文件句柄 -f = open('pickle_file','rb') -# 读出内容 -p_f = pickle.load(f) -# 关闭文件 -f.close() -# 打印 -print(p_f) -# lyon -``` - -**但是pickle仅仅只能对python中的数据进行序列化 , 反序列化时其他语言就无法读懂了这是什么了** , 所以我们一般用推荐使用json - -## shelve - -shelve也是python提供给我们的序列化工具 , 比pickle用起来简单一些 - -shelve只提供给我们一个open方法 , 是用key来访问的 , 使用起来和字典类似 - -```python -# 导入shelve模块 -import shelve -# shelve提供open方法 -f = shelve.open('shelve_file') -# 直接对文件句柄进行操作,就可以写入文件中 -f['key'] = {'int':10, 'float':9.5, 'string':'Sample data'} -# 关闭文件 -f.close() -# 打开文件 -f1 = shelve.open('shelve_file') -# 直接用key取值,key不存在就报错 -existing = f1['key'] -# 关闭文件 -f1.close() -# 打印结果 -print(existing) -# {'float': 9.5, 'int': 10, 'string': 'Sample data'} -``` - -shelve不支持多个应用同时往一个数据库进行操作 , 所以当我们知道我们的应用如果只进行操作 , 我们可以设置shelve.open() 方法的参数来进行 - - shelve.open(filename, flag='c', protocol=None, writeback=False) - -```python -import shelve -# flag参数为设置操作模式,r 设置只读模式 -f = shelve.open('shelve_file', flag='r') -existing = f['key'] -f.close() -print(existing) -``` - -` writeback `参数 , 可以减少我们出错的概率 , 并且让对象的持久化对用户更加的透明了 ; 但这种方式并不是所有的情况下都需要 , 首先 , 使用writeback以后 , shelf在open()的时候会增加额外的内存消耗 , 并且当数据库在close()的时候会将缓存中的每一个对象都写入到数据库 , 这也会带来额外的等待时间 , 因为shelve没有办法知道缓存中哪些对象修改了 , 哪些对象没有修改 , 因此所有的对象都会被写入 - -```python -import shelve -f1 = shelve.open('shelve_file') -print(f1['key']) -f1['key']['new_value'] = 'this was not here before' -f1.close() -# 设置writeback -f2 = shelve.open('shelve_file', writeback=True) -print(f2['key']) -f2['key']['new_value'] = 'this was not here before' -f2.close() -``` \ No newline at end of file diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/05-os\346\250\241\345\235\227.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/05-os\346\250\241\345\235\227.md" deleted file mode 100644 index dea581b49..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/05-os\346\250\241\345\235\227.md" +++ /dev/null @@ -1,220 +0,0 @@ -# Attack on Python - OS模块 🐍 - - - - - - - - - - -## 介绍 - -`os`模块为我们提供了与操作系统相关的诸多接口 - -在Python中 , 使用字符串类型来表示文件名 , 命令行参数和环境变量 - -`os`模块功能总体分为以下几个部分 : - -- 当前进程和用户操作 -- 文件描述符操作 -- 文件和目录操作 -- 进程管理 -- 调度程序接口 (仅在一些Unix平台上) -- 系统信息处理 - -总体概况 - -```python -DESCRIPTION - This exports: - - all functions from posix, nt or ce, e.g. unlink, stat, etc. - - os.path is either posixpath or ntpath - - os.name is either 'posix', 'nt' or 'ce'. - - os.curdir is a string representing the current directory ('.' or ':') - - os.pardir is a string representing the parent directory ('..' or '::') - - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\') - - os.extsep is the extension separator (always '.') - - os.altsep is the alternate pathname separator (None or '/') - - os.pathsep is the component separator used in $PATH etc - - os.linesep is the line separator in text files ('\r' or '\n' or '\r\n') - - os.defpath is the default search path for executables - - os.devnull is the file path of the null device ('/dev/null', etc.) -``` - -注意 : 在`os`模块中有很多方法只有在Unix系统上才能使用 - -由于`os`模块提供的方法太多 , 所以本文仅介绍一些在windows下常用的方法 - -## OS - -```python -os.getcwd() -""" -Return a string representing the current working directory. -""" - -os.chdir(path) -""" -Change the current working directory to path. -""" - -os.curdir -""" -The constant string used by the operating system to refer to the current directory. -This is '.' for Windows and POSIX. Also available via os.path. -""" - -os.pardir -""" -The constant string used by the operating system to refer to the parent directory. -This is '..' for Windows and POSIX. Also available via os.path. -""" - -os.makedirs(name, mode=0o777, exist_ok=False) -""" -Recursive directory creation function. Like mkdir(), -but makes all intermediate-level directories needed to contain the leaf directory. -""" - -os.removedirs(name) -""" -Remove directories recursively. -Works like rmdir() except that, -if the leaf directory is successfully removed, -removedirs() tries to successively remove every parent directory mentioned in path until an error is raised -""" - -os.rmdir(path, *, dir_fd=None) -""" -Remove (delete) the directory path. -Only works when the directory is empty, otherwise, OSError is raised. -In order to remove whole directory trees, shutil.rmtree() can be used. -""" - -os.listdir(path='.') -""" -Return a list containing the names of the entries in the directory given by path. -The list is in arbitrary order, and does not include the special entries '.' and '..' even if they are present in the directory. -""" - -os.remove(path, *, dir_fd=None) -""" -Remove (delete) the file path. -If path is a directory, OSError is raised. Use rmdir() to remove directories. -""" - -os.rename(src, dst, *, src_dir_fd=None, dst_dir_fd=None) -""" -Rename the file or directory src to dst. -""" - -os.stat(path, *, dir_fd=None, follow_symlinks=True) -""" -Get the status of a file or a file descriptor. -""" - -os.sep -""" -The character used by the operating system to separate pathname components. -This is '/' for POSIX and '\\' for Windows. -""" - -os.linesep -""" -The string used to separate (or, rather, terminate) lines on the current platform. -This may be a single character, such as '\n' for POSIX, or multiple characters, for example, '\r\n' for Windows. -""" - -os.pathsep -""" -The character conventionally used by the operating system to separate search path components (as in PATH), such as ':' for POSIX or ';' for Windows. -Also available via os.path. -""" - -os.name -""" -The name of the operating system dependent module imported. -The following names have currently been registered: 'posix', 'nt', 'java'. -""" - -os.system(command) -""" -Execute the command (a string) in a subshell. -""" - -os.popen(cmd, mode='r', buffering=-1) -""" -Open a pipe to or from command cmd. -The return value is an open file object connected to the pipe, -which can be read or written depending on whether mode is 'r' (default) or 'w'. -""" - -os.environ -""" -A mapping object representing the string environment. -""" -``` - -更多`os`模块相关 : [os](https://docs.python.org/3/library/os.html?highlight=os#module-os) — Miscellaneous operating system interfaces - -## OS.Path - -```python -os.path.abspath(path) -""" -Return a normalized absolutized version of the pathname path. -On most platforms, this is equivalent to calling the function normpath() as follows: normpath(join(os.getcwd(), path)). -""" - -os.path.exists(path) -""" -Return True if path refers to an existing path or an open file descriptor. -Returns False for broken symbolic links. -""" - -os.path.isabs(path) -""" -Return True if path is an absolute pathname. -""" - -os.path.isfile(path) -""" -Return True if path is an existing regular file. -""" - -os.path.isdir(path) -""" -Return True if path is an existing directory. -""" - -os.path.join(path, *paths) -""" -Join one or more path components intelligently. -""" - -os.path.getatime(path) -""" -Return the time of last access of path. -""" - -os.path.getmtime(path) -""" -Return the time of last modification of path. -""" - -os.path.getsize(path) -""" -Return the size, in bytes, of path. -Raise OSError if the file does not exist or is inaccessible. -""" -``` - -更多`os.path`相关 : [os.path](https://docs.python.org/3/library/os.path.html#module-os.path) — Common pathname manipulations - -补充 : - -- 如果需要读取命令行上所有文件中的所有行 , 可以查看[`fileinput`](https://docs.python.org/3/library/fileinput.html#module-fileinput) 模块 -- 如果需要创建临时文件和目录 , 可以查看[`tempfile`](https://docs.python.org/3/library/tempfile.html#module-tempfile) 模块 -- 关于文件和文件集合的高级操作 , 可以查看[`shutil`](https://docs.python.org/3/library/shutil.html#module-shutil) 模块 \ No newline at end of file diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/06-random\346\250\241\345\235\227.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/06-random\346\250\241\345\235\227.md" deleted file mode 100644 index a85f35573..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/06-random\346\250\241\345\235\227.md" +++ /dev/null @@ -1,206 +0,0 @@ -# Python - 标准库之random - - - - - - - - - - - - -## 介绍 🍀 - -`random`模块为我们提供了各种分布的伪随机数生成器 - -`random`模块功能分为以下几个部分 : - -- Bookkeeping functions -- Functions for integers -- Functions for sequences -- Real-valued distributions - -## Bookkeeping functions 🍀 - -```python -random.seed(a=None, version=2): - """ - Initialize the random number generator. - """ -random.getstate(): - """ - Return an object capturing the current internal state of the generator. - This object can be passed to setstate() to restore the state. - """ -random.setstate(state): - """ - State should hava been obtained from a previous call to getstate(), - and setstate() restores the internal state of the generator to what it was at the time getstate() was called. - """ -random.getrandbits(k): - """ - Returns a Python integer with k random bits. - This method is supplied with the Mersenne Twister generator and some other generators may also provide it as an optional part of the API. - When available, getrandbits() enables randrange() to handle arbitrarily large ranges. - """ -``` - -## Functions for integers 🍀 - -```python -random.randrange(stop) -random.randrange(start, stop[, step]): - """ - Return a randomly selected element from range(start, stop, step). - This is equivalent to choice(range(start, stop, step)), - but doesn't actually build a range object. - """ -random.randint(a, b): - """ - Return a random integer N such that a <= N <= b. - Alias for randrange(a, b+1). - """ -``` - -## Functions for sequences 🍀 - -```python -random.choice(seq): - """ - Return a random element from the non-empty sequence seq. - If seq is empty, raises IndexError. - """ -random.choices(population, weights=None, *, cum_weights=None, k=1): - """ - Return a k sized list of elements chosen from the population with replacement. - If the population is empty, raises IndexError. - """ -random.shuffle(x[, random]): - """ - Shuffle the sequence x in place. - """ -random.sample(population, k): - """ - Return a k length list of unique elements chosen from the population sequence or set. - Used for random sampling without replacement. - """ -``` - -## Real-valued distributions 🍀 - -```python -random.random(): - """ - Return the next random floating point number in the range [0.0, 1.0). - """ -random.uniform(a, b): - """ - Return a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a. - """ -random.triangular(low, high, mode): - """ - Return a random floating point number N such that low <= N <= high and with the specified mode between those bounds. - """ -random.betavariate(alpha, beta): - """ - Beta distribution. - Conditions on the parameters are alpha > 0 and beta > 0. - Returned values range between 0 and 1. - """ -random.expovariate(lambd): - """ - Exponential distribution. lambd is 1.0 divided by the desired mean. - """ -random.gammavariate(alpha, beta): - """ - Gamma distribution. - (Not the gamma function!) Conditions on the parameters are alpha > 0 and beta > 0. - """ -random.gauss(mu, sigma): - """ - Gaussian distribution. - mu is the mean, and sigma is the standard deviation. - This is slightly faster than the normalvariate() function defined below. - """ -random.lognormvariate(mu, sigma): - """ - Log normal distribution. - """ -random.normalvariate(mu, sigma): - """ - Normal distribution. mu is the mean, and sigma is the standard deviation. - """ -random.vonmisesvariate(mu, kappa): - """ - mu is the mean angle, expressed in radians between 0 and 2*pi, - and kappa is the concentration parameter, - which must be greater than or equal to zero. - If kappa is equal to zero, - this distribution reduces to a uniform random angle over the range 0 to 2*pi. - """ -random.paretovariate(alpha): - """ - Pareto distribution. alpha is the shape parameter. - """ -random.weibullvariate(alpha, beta): - """ - Weibull distribution. - alpha is the scale parameter and beta is the shape parameter. - """ -``` - -## Examples and Recipes 🍀 - -Basic examples : - -```python ->>> import random ->>> random.random() # Random float: 0.0 <= x < 1.0 -0.37444887175646646 ->>> random.uniform(2.5, 10.0) # Random float: 2.5 <= x < 10.0 -3.1800146073117523 ->>> random.expovariate(1 / 5) # Interval between arrivals averaging 5 seconds -5.148957571865031 ->>> random.randrange(10) # Integer from 0 to 9 inclusive -7 ->>> random.randrange(0, 101, 2) # Even integer from 0 to 100 inclusive -26 ->>> random.choice(['win', 'lose', 'draw']) # Single random element from a sequence -'draw' ->>> deck = 'ace two three four'.split() ->>> random.shuffle(deck) # Shuffle a list ->>> deck -['four', 'two', 'ace', 'three'] ->>> random.sample([10, 20, 30, 40, 50], k=4) # Four samples without replacement -[40, 10, 50, 30] -``` - -Simulations : - -```python ->>> # Six roulette wheel spins (weighted sampling with replacement) ->>> random.choices(['red', 'black', 'green'], [18, 18, 2], k=6) -['red', 'green', 'black', 'black', 'red', 'black'] - ->>> # Deal 20 cards without replacement from a deck of 52 playing cards ->>> # and determine the proportion of cards with a ten-value ->>> # (a ten, jack, queen, or king). ->>> import collections ->>> deck = collections.Counter(tens=16, low_cards=36) ->>> seen = random.sample(list(deck.elements()), k=20) ->>> seen.count('tens') / 20 -0.15 ->>> # Estimate the probability of getting 5 or more heads from 7 spins ->>> # of a biased coin that settles on heads 60% of the time. ->>> trial = lambda: random.choices('HT', cum_weights=(0.60, 1.00), k=7).count('H') >= 5 ->>> sum(trial() for i in range(10000)) / 10000 -0.4169 ->>> # Probability of the median of 5 samples being in middle two quartiles ->>> trial = lambda : 2500 <= sorted(random.choices(range(10000), k=5))[2] < 7500 ->>> sum(trial() for i in range(10000)) / 10000 -0.7958 -``` - -更多random相关 : [random](https://docs.python.org/3/library/random.html?highlight=random#module-random) — Generate pseudo-random numbers \ No newline at end of file diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/07-sys\346\250\241\345\235\227.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/07-sys\346\250\241\345\235\227.md" deleted file mode 100644 index 26893f664..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/07-sys\346\250\241\345\235\227.md" +++ /dev/null @@ -1,224 +0,0 @@ -# Attack on Python - sys模块 🐍 - - - - - - - - - - -## 介绍 - -`sys`模块为我们提供了对解释器使用或维护的一些变量的访问 , 以及解释器交互的函数 - -`sys`模块总体分为四个部分 : - -- Dynamic objects , 动态对象 -- Static objects , 静态对象 -- Functions , 函数 -- Data , 配置 - -## Dynamic objects - -```python -argv -- command line arguments; argv[0] is the script pathname if known - -path -- module search path; path[0] is the script directory, else '' - -modules -- dictionary of loaded modules - -displayhook -- called to show results in an interactive session - -excepthook -- called to handle any uncaught exception other than SystemExit - To customize printing in an interactive session or to install a custom top-level exception handler, - assign other functions to replace these. - -stdin -- standard input file object; used by input() - -stdout -- standard output file object; used by print() - -stderr -- standard error object; used for error messages - By assigning other file objects (or objects that behave like files) - to these, it is possible to redirect all of the interpreter's I/O. - -last_type -- type of last uncaught exception - -last_value -- value of last uncaught exception - -last_traceback -- traceback of last uncaught exception -These three are only available in an interactive session after a -traceback has been printed. -``` - -## Static objects - -```python -builtin_module_names -- tuple of module names built into this interpreter - -copyright -- copyright notice pertaining to this interpreter - -exec_prefix -- prefix used to find the machine-specific Python library - -executable -- absolute path of the executable binary of the Python interpreter - -float_info -- a struct sequence with information about the float implementation. - -float_repr_style -- string indicating the style of repr() output for floats - -hash_info -- a struct sequence with information about the hash algorithm. - -hexversion -- version information encoded as a single integer - -implementation -- Python implementation information. - -int_info -- a struct sequence with information about the int implementation. - -maxsize -- the largest supported length of containers. - -maxunicode -- the value of the largest Unicode code point - -platform -- platform identifier - -prefix -- prefix used to find the Python library - -thread_info -- a struct sequence with information about the thread implementation. - -version -- the version of this interpreter as a string - -version_info -- version information as a named tuple - -dllhandle -- [Windows only] integer handle of the Python DLL - -winver -- [Windows only] version number of the Python DLL - -__stdin__ -- the original stdin; don't touch! - -__stdout__ -- the original stdout; don't touch! - -__stderr__ -- the original stderr; don't touch! - -__displayhook__ -- the original displayhook; don't touch! - -__excepthook__ -- the original excepthook; don't touch! -``` - -## Functions - -```python -displayhook() -- print an object to the screen, and save it in builtins._ - -excepthook() -- print an exception and its traceback to sys.stderr - -exc_info() -- return thread-safe information about the current exception - -exit() -- exit the interpreter by raising SystemExit - -getdlopenflags() -- returns flags to be used for dlopen() calls - -getprofile() -- get the global profiling function - -getrefcount() -- return the reference count for an object (plus one :-) - -getrecursionlimit() -- return the max recursion depth for the interpreter - -getsizeof() -- return the size of an object in bytes - -gettrace() -- get the global debug tracing function - -setcheckinterval() -- control how often the interpreter checks for events - -setdlopenflags() -- set the flags to be used for dlopen() calls - -setprofile() -- set the global profiling function - -setrecursionlimit() -- set the max recursion depth for the interpreter - -settrace() -- set the global debug tracing function -``` - -## Data - -```python -__stderr__ = <_io.TextIOWrapper name='' mode='w' encoding='cp9... - -__stdin__ = <_io.TextIOWrapper name='' mode='r' encoding='cp936... - -__stdout__ = <_io.TextIOWrapper name='' mode='w' encoding='cp9... - -api_version = 1013 - -argv = [''] - -base_exec_prefix = r'C:\Users\Lyon\AppData\Local\Programs\Python\Pytho... - -base_prefix = r'C:\Users\Lyon\AppData\Local\Programs\Python\Python35' - -builtin_module_names = ('_ast', '_bisect', '_codecs', '_codecs_cn', '_... - -byteorder = 'little' - -copyright = 'Copyright (c) 2001-2016 Python Software Foundati...ematis... - -dllhandle = 1373306880 - -dont_write_bytecode = False - -exec_prefix = r'C:\Users\Lyon\AppData\Local\Programs\Python\Python35' - -executable = r'C:\Users\Lyon\AppData\Local\Programs\Python\Python35\py... - -flags = sys.flags(debug=0, inspect=0, interactive=0, opt...ing=0, quie... - -float_info = sys.float_info(max=1.7976931348623157e+308, max_...epsilo.. - . -float_repr_style = 'short' - -hash_info = sys.hash_info(width=64, modulus=2305843009213693...iphash2... - -hexversion = 50660080 - -implementation = namespace(cache_tag='cpython-35', hexversion=506...in... - -int_info = sys.int_info(bits_per_digit=30, sizeof_digit=4) - -maxsize = 9223372036854775807 - -maxunicode = 1114111 - -meta_path = [, , '_ast': , - -stdin = <_io.TextIOWrapper name='' mode='r' encoding='cp936'> - -stdout = <_io.TextIOWrapper name='' mode='w' encoding='cp936'> - -thread_info = sys.thread_info(name='nt', lock=None, version=None) - -version = '3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1... - -version_info = sys.version_info(major=3, minor=5, micro=2, releaseleve.. - . -warnoptions = [] - -winver = '3.5' -``` -更多见 : [sys](https://docs.python.org/3/library/sys.html?highlight=sys#module-sys) — System-specific parameters and functions \ No newline at end of file diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/08-wsgiref\346\250\241\345\235\227.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/08-wsgiref\346\250\241\345\235\227.md" deleted file mode 100644 index a86d10750..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/08-wsgiref\346\250\241\345\235\227.md" +++ /dev/null @@ -1,290 +0,0 @@ -# Attack on Python - wsgiref模块 🐍 - - - - - - - - - - -## 介绍 - -`wsgiref` 模块是 `WSGI` 规范的一个参考实现 , 它可以用于将WSGI支持添加到Web服务器或框架中 , 它提供了用于操作WSGI环境变量和响应头的实用工具 、 用于实现WSGI服务器的基类 、 用于服务WSGI应用程序的样本HTTP服务器 、以及检查WSGI服务器和应用程序的验证工具 , 以满足WSGI规范(PEP3333) - -包内容 - -```python -handlers - server/gateway base classes -headers - WSGI response header tools -simple_server - a simple WSGI HTTP server -util - WSGI environment utilities -validate - WSGI conformance checker -``` - -## handlers - -这个模块提供了用于实现WSGI服务器和网关的基本处理程序类 . 这些基类处理与WSGI应用程序通信的大部分工作 , 只要它们提供了一个`CGI-like`环境 , 以及输入、输出和错误流 - -CLASSES - -```python -builtins.object -BaseHandler - """管理WSGI应用程序的调用""" - SimpleHandler - """初始化数据流,环境等的处理程序""" - BaseCGIHandler - """CGI-like系统,使用输入/输出/错误流和环境映射""" - CGIHandler - """CGI-based调用,通过sys.stdin/stdout/stderr和os.environ""" - IISCGIHandler - """CGI-based调用与IIS路径错误的解决方法""" -# 由上到下是一个基类到子类的过程 -``` - -以上类中主要的实现在`BaseHandler`中 , 其它几个都是在基类基础上做了简单的实现 - -FUNCTIONS - -```python -read_environ() - """读取环境,修改HTTP变量""" -``` - -本文中所有思维导图全部来自[这里 , 点我吧](https://github.com/minixalpha/SourceLearning/tree/master/wsgiref-0.1.2) - -![handlers](http://oux34p43l.bkt.clouddn.com/handlerss.bmp) - -对于各个类中的具体实现 , 可以去阅读源代码https://pypi.python.org/pypi/wsgiref - -## headers - -这个模块提供了一个类(Headers) , 可以使用mapping-like的接口来方便地操作WSGI响应头 , 也就是一个类似于dict的数据结构 , 并且其实现了dict操作中的`get` , `keys` , `values` 函数 - -CLASSES - -```python -builtins.object - Headers -class Headers(builtins.object) - """管理一个HTTP响应头的集合""" -``` - -headers思维导图 - -![headers](http://oux34p43l.bkt.clouddn.com/headers.bmp)! - -## simple_server - -这个模块实现了一个WSGI应用程序的简单HTTP服务器 (基于HTTP.server) , 每个服务器实例都在给定的主机和端口上提供一个WSGI应用 - -CLASSES - -```python -http.server.BaseHTTPRequestHandler(socketserver.StreamRequestHandler) - WSGIRequestHandler -# WSGIRequestHandler继承体系 -# +--------------------+ -# | BaseRequestHandler | -# +--------------------+ -# ↓ -# +-----------------------+ -# | StreamRequestHandler | -# +-----------------------+ -# ↓ -# +------------------------+ -# | BaseHTTPRequestHandler | -# +------------------------+ -# ↓ -# +--------------------+ -# | WSGIRequestHandler | -# +--------------------+ -http.server.HTTPServer(socketserver.TCPServer) - WSGIServer -# WSGIServer继承体系 -# +------------+ -# | BaseServer | -# +------------+ -# ↓ -# +------------+ -# | TCPServer | -# +------------+ -# ↓ -# +------------+ -# | HTTPServer | -# +------------+ -# ↓ -# +------------+ -# | WSGIServer | -# +------------+ -class WSGIRequestHandler(http.server.BaseHTTPRequestHandler) - """HTTP请求处理程序基类""" -class WSGIServer(http.server.HTTPServer) - """实现Python WSGI协议的BaseHTTPServer""" -``` - -FUNCTIONS - -```python -demo_app(environ, start_response) - """应用程序部分""" -make_server(host, port, app, server_class=, handler_class=) - """创建一个新的WSGI服务器,监听主机和端口""" -``` - -simple_server思维导图 - -![simple_server](http://oux34p43l.bkt.clouddn.com/simple_server.bmp) - -simple_server模块主要有两部分内容 - -1. 应用程序 - - 函数demo_app是应用程序部分 - -2. 服务器程序 - - 服务器程序主要分成Server和Handler两部分 , 另外make_server函数用来生成一个服务器实例 - -图上可知simple_server中还有一个`ServerHandler`模块 , 它继承于handlers模块中的`SimpleHandler` , 继承体系如下 - -```python -# +-------------+ -# | BaseHandler | -# +-------------+ -# ↓ -# +----------------+ -# | SimpleHandler | -# +----------------+ -# ↓ -# +---------------+ -# | ServerHandler | -# +---------------+ -``` - -该模块主要完成的功能如下 : - -- 启动服务器 -- 模块用户请求 -- 处理用户请求 - -执行`simple_server.py`时内容如下 - -```python -httpd = make_server('', 8000, demo_app) -sa = httpd.socket.getsockname() -print "Serving HTTP on", sa[0], "port", sa[1], "..." - -# M: webbrowser provides a high-level interface to allow displaying Web-based documents -# to users. Under most circumstances -import webbrowser -webbrowser.open('http://localhost:8000/xyz?abc') - -httpd.handle_request() # serve one request, then exit -``` - -### demo_app - -```python -demo_app(environ, start_response) -''' -参数说明: -environ:为一个字典 -start_response:为一个可调用函数 -return:返回一个可迭代对象 -另外demo_app中会调用start_response函数 -''' -def demo_app(environ,start_response): - from StringIO import StringIO - stdout = StringIO() - print >> stdout, "Hello world!" - print >> stdout - h = environ.items() - h.sort() - for k,v in h: - print >> stdout, k,'=',`v` - start_response("200 OK", [('Content-Type','text/plain')]) - return [stdout.getvalue()] -``` - -### make_server - -```python -def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler) -''' -参数说明: -host:主机名 -port:端口号 -server_class:生成server实例时所使用的基类,默认为WSGIServer -handler_class:用于处理请求的handler类,默认为WSGIRequestHandler -''' -def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler): - '''引用no_1y的注释,文尾有详细链接''' - # no_1y: -> HTTPServer.__init__ - # -> TCPServer.__init__ - # -> TCPServer.server_bind - # -> TCPServer.socket.bind - # -> TCPServer.server_activate - # -> TCPServer.socket.listen - server = server_class((host, port), handler_class) - # no_1y: conresponding to WSGIRequestHandler.handle() - # -> handler.run(self.server.get_app()) - server.set_app(app) - return server -""" -server_class为WSGIServer,生成时会沿着继承方向到达最底层的TCPServer,并完成对socket的绑定和监听 -set_app设置了app,它会在handler_class的handle函数中被取出来,交给handler的run函数执行 -""" -``` - -## util - -这个模块提供了用于处理WSGI环境的各种实用函数 , WSGI环境是一个包含在PEP 3333中描述的HTTP请求变量的字典 - -CLASSES - -```python -builtins.object - FileWrapper -class FileWrapper(builtins.object): - """ - 将文件类对象转换为迭代器的包装器 - """ -``` - -FUNCTIONS - -```python -application_uri(environ) - """返回应用程序的基本URI""" -guess_scheme(environ) - """返回一个猜测wsgi.url_scheme是否是http或https""" -request_uri(environ, include_query=True) - """返回完整的请求URI,包括任意的查询字符串""" -setup_testing_defaults(environ) - """用于设置虚拟环境的服务器和应用程序,目的是使WSGI的单元测试更加容易""" -shift_path_info(environ) - """将一个名称从PATH_INFO转移到SCRIPT_NAME,并返回它,如果在pathinfo中没有其他路径段,则返回None""" -``` - -util思维导图 - -![util](http://oux34p43l.bkt.clouddn.com/util.bmp) - -## validate - -在创建新的WSGI应用程序对象 , 框架 , 服务器或中间件时 , 使用wsgiref.validate验证新代码的一致性是很有用的 - -这个模块提供了一个函数 , 它创建了WSGI应用程序对象 , 它可以验证WSGI服务器或网关和WSGI应用程序对象之间的通信 , 从而检查双方是否符合协议的一致性 - -简单的说就是检查你对WSGI的实现是否满足标准 - -思维导图如下 - -![validate](http://oux34p43l.bkt.clouddn.com/validate.png) - -本文主要参考http://blog.csdn.net/on_1y/article/details/18818081 - -思维导图来自https://github.com/minixalpha/SourceLearning/tree/master/wsgiref-0.1.2 diff --git "a/01-Python/04-\346\250\241\345\235\227\347\257\207/README.md" "b/01-Python/04-\346\250\241\345\235\227\347\257\207/README.md" deleted file mode 100644 index ae1ec2a07..000000000 --- "a/01-Python/04-\346\250\241\345\235\227\347\257\207/README.md" +++ /dev/null @@ -1,2 +0,0 @@ -# Attack on Python - 模块篇 🐍 - diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/01-\347\275\221\347\273\234\347\274\226\347\250\213.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/01-\347\275\221\347\273\234\347\274\226\347\250\213.md" deleted file mode 100644 index 465aabc46..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/01-\347\275\221\347\273\234\347\274\226\347\250\213.md" +++ /dev/null @@ -1,57 +0,0 @@ -# Attack on Python - 网络编程 🐍 - - - - - - - - - - -## 前言 - -在互联网没有诞生之前 , 我们都是在自己的计算机上自娱自乐 , 那时候的程序也都是单机版的程序 , 随后互联网诞生了 , 用网络把各个计算机连接到了一起 , 让处在网络中的计算机可以互相通信 , 网络编程就是如何在程序中实现两台计算机之间的通信 - -最基本的例子莫过于我们传输文件了 , 没有网络的情况下我们只能利用U盘或者硬盘 , 先从我的计算机上将要传输的文件写入到我们的U盘或者硬盘 , 然后再用已有文件的U盘或者硬盘写入其他计算机 , 这样的局限性有多大可想而知 ; 利用网络我们可以直接十万八千里进行文件传输 , 比如用我的QQ传文件给你的QQ , 当然这个例子可能不怎么好 , 因为你传文件一般可能不会用QQ来传 - -## 网络协议 - -网络的存在是为了能使计算机之间进行通信 , 既然是通信那么就得有一门大家都会的语言吧 . 就像我跟你说话 , 我只会中文而你只会英文 , 那么我们两个拿什么交流 ? 花钱请个翻译官 ? 不存在的 ...... 那么在网络上的各台计算机之间也需要一种大家都会的语言 , 这就是网络协议 - -> 网络协议是网络上所有设备之间通信规则的集合 , 它规定了通信时信息必须采用的格式和这些格式的意义 - -为了使不同计算机厂家生产的计算机能够相互通信 , 以便在更大的范围内建立计算机网络 , 国际标准化组织( ISO ) 在1987年提出了 "开放系统互联参考模型" , 即著名的OSI/RM模型(Open System Interconection/Reference Model) . 它将计算机网络体系结构的通信协议分为七层 , 如下图 - -![OSI](http://oux34p43l.bkt.clouddn.com/OSI.png) - -在上图中右边协议部分我们可以了解各层中所包含的协议 , 互联网协议包含了上百种协议 , 但是最重要的两个协议是TCP和IP协议 , 所以我们把互联网的协议简称TCP/IP协议 - -## IP协议 - -IP ( Internet Protocol ) 就是为计算机网络相互连接进行通信而设计的协议 , 翻译过来即"因特网协议" , 简称"网协" - -它定义的地址称为IP地址 , 广泛采用v4版本即IPv4 , 它规定网络地址由32位2进制表示 , 范围为 `0.0.0.0 ~ 255.255.255.255 ` , 一个IP地址通常协程四段十进制数 , 例如 : ` 127.0.0.1 ` . 还有IPv6地址 , 规定网络地址由128位2进制表示 , 它是目前使用的IPv4的升级版 , 以字符串表示如 : `2001:0db8:85a3:0042:1000:8a2e:0370:7334 ` - -通信的时候 , 双方必须知道对方的标识 , 好比发邮件必须知道对方的邮件地址 . 互联网上每个计算机的唯一标识就是IP地址 , 如果一台计算机同时接入到两个或更多的网络 , 比如路由器 , 它就会有两个或多个IP地址 , 所以 , IP地址对应的实际上是计算机的网络接口 , 通常是网卡 - -IP协议负责把数据从一台计算机通过网络发送到另一台计算机 . 数据被分割成一小块一小块 , 然后通过IP包发送出去 , 由于互联网链路复杂 , 两台计算机之间经常有多条线路 , 因此 , 路由器就负责决定如何把一个IP包转发出去 ; IP包的特点是按块发送 , 途径多个路由 , 但不保证能到达 , 也不保证顺序到达 - -一个IP包除了包含要传输的数据外 , 还包含源IP地址和目标IP地址 , 源端口和目标端口 - -## TCP协议 - -TCP协议则是建立在 ` IP协议 ` 之上的 , TCP协议负责在两台计算机之间建立可靠连接 , 保证数据包按顺序到达 ; TCP协议会通过握手建立连接 , 然后 , 对每个IP包编号 , 确保对方按顺序收到 , 如果包丢掉了 , 就自动重发 - -许多常用的更高级的协议都是建立在TCP协议基础上的 , 比如用于浏览器的HTTP协议、发送邮件的SMTP协议等 - -互联网本质上就是一系列的网络协议 , 互联网协议的功能是定义计算机如何接入internet , 以及接入internet的计算机通信标准 - -## 网络编程 - -互联网已经建立成功了 , 也就是说一大堆协议都准备好了 , 你只是规定好了计算机怎么接入互联网 , 但是却没告诉计算机接入之后怎么收发消息 , 也就是说并没有完全实现通信 , 仅仅是"通"了而已 - -网络编程就是以实现计算机之间通信为目的的编程 , 而实现计算机之间的通信实质上是实现计算机上两个进程的通信 , 比如我在两台计算机上都装有QQ , 我用一台计算机上的QQ给另一台计算机上的QQ发消息 , 明显实现该通信并不是两台计算机直接通信的 , 而是通过QQ这个正在运行的软件即一个进程来实现该通信的 - -所以我们可以这样说**网络编程就是以实现进程间通信为目的的编程** - diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/02-Socket.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/02-Socket.md" deleted file mode 100644 index 3c19b2a01..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/02-Socket.md" +++ /dev/null @@ -1,231 +0,0 @@ -# Attack on Python - socket 🐍 - - - - - - - - - - -## C/S架构 - -在网络通信中 , 一般是一方求一方应 , 求的一方就是客户端即 ` Client ` , 应的一方就是服务端即` Server ` , 这就是C/S架构 , 在互联网中处处是C/S架构 , 比如我们访问百度 , 百度就是一个服务端 , 而我们的浏览器就是一个客户端 - -## Socket - -Socket是应用层与TCP/IP协议族通信的中间软件抽象层 , 它是一组接口 , 是从顶上三层 (osi七层协议的应用层) 进入传输层的接口 ; 顶上三层通常构成所谓的用户进程 , 底下四层却通常作为操作系统内核的一个部分提供 - -Socket又叫做套接字 , Python中socket为我们封装好了TCP/UDP协议 , 所以我们无需深入理解 , 只要遵循socket的规定去编程就可以了 - -**创建socket对象** - -创建socket对象就是一个建立TCP的过程 , 即三次握手 , 断开当然就是四次挥手了 - -![TCP communication](http://oux34p43l.bkt.clouddn.com/TCP%20communication.png) - -代码实现 - -```python -# 导入socket模块 -import socket -# 调用socket模块中的socket类实例化出对象 -sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) -'''或者可以使用 from module import * ,可以大幅度减少代码,仅仅提一下,毕竟有弊端''' -# 导入socket模块中的所有内容 -from socket import * -# 实例化socket类 -sock = socket(AF_INET,SOCK_STREAM,0) -``` - -**socket类参数说明** - -其构造函数源码 - -```python -def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None): - # 下面内容就不摘了 - pass -``` - -> *family* : 地址簇 - -| 参数 | 说明 | -| -------- | --------------- | -| AF_INET | IPv4 , 即默认为IPv4 | -| AF_INET6 | IPv6 | -| AF_UNIX | 针对Unix系统进程间通信 | - -> *type* : 类型 - -| 参数 | 说明 | -| -------------- | ---------------------------------------- | -| SOCK_STREAM | 面向流 , 即TCP | -| SOCK_DGRAM | 面向数据报 , 即UDP | -| SOCK_RAW | 原始套接字 , 可处理ICMP,IGMP等网络报文 ; 可以处理特殊的IPv4报文 ; 利用原始套接字 , 可以通过IP_HDRINCL套接字选项由用户构造IP头 | -| SOCK_RDM | 一种可靠的UDP形式 . SOCK_RAM用来提供对原始协议的低级访问 , 在需要执行某些特殊操作时使用 , 如发送ICMP报文 , SOCK_RAW通常仅限于高级用户或管理员运行的程序使用 | -| SOCK_SEQPACKET | 可靠的连续数据包服务 | - -> *proto* : 协议 - -| 参数 | 说明 | -| ---- | ---------------------------------------- | -| 0 | 与特定的地址家族相关的协议 , 如果是0 , 则系统就会根据地址格式和套接类别 , 自动选择一个合适的协议 | - -还有一个*fileno*参数是无需理会的 - -## 基于TCP - -TCP协议是有链接的 , 面向流的 , 数据传输可靠 , 必须先启动服务端 - -**TCP服务端** - -1. 创建套接字对象 *创建socket对象* -2. 绑定IP和端口 *绑定 bind()* -3. 开始监听链接 *监听 listen()* -4. 阻塞 , 等待客户端成功连接 *阻塞 accept()* -5. 接收请求数据 *接收 recv()* -6. 处理并发送请求数据 *发送 send()* -7. 通信完毕 , 关闭链接 , 关闭套接字 *关闭 close()* - -**TCP客户端** - -1. 创建套接字对象 *创建socket对象* -2. 连接服务端 , 按照IP和端口连接 *连接 connet()* -3. 发送请求数据 *发送 send()* -4. 接收请求数据 *接收 recv()* -5. 通信完毕 , 关闭套接字 *关闭 close()* - -简单实例 - -tcp_server.py - -```python -# 导入socket模块 -import socket -# 创建socket对象,默认参数就不填了 -sock = socket.socket() -# 绑定IP和端口,参数是一个元组(ip,port) -sock.bind(('127.0.0.1', 8080)) -# 开始监听,最大监听数为5 -sock.listen(5) -# 阻塞,等待连接,返回一个链接通道和一个地址 -conn,addr = sock.accept() -# 接收请求数据,接收大小为1024字节 -content = conn.recv(1024) -# 打印结果(bytes转成str显示) -print(content.decode()) -# 发送请求结果,必须以bytes类型 -conn.send(b'Hello Lyon') -# 关闭链接 -conn.close() -# 关闭套接字 -sock.close() -``` - -tcp_client.py - -```python -# 导入socket模块 -import socket -# 创建socket对象 -sock = socket.socket() -# 建立链接 -sock.connect(('127.0.0.1', 8080)) -# 发送请求数据,必须以bytes类型 -sock.send(b"I'm Lyon") -# 接收请求结果 -content = sock.recv(1024) -# 打印结果 -print(content.decode()) -# 关闭套接字 -sock.close() -``` - -## 基于UDP - -UDP协议是无链接的 , 面向数据报的 , 数据传输全靠吼 , 不可靠 , 先启动哪一端都不会报错 - -**UDP服务端** - -1. 创建套接字对象 *创建socket对象* -2. 绑定IP和端口 *绑定 bind()* -3. 接收请求数据 *接收 recvfrom()* -4. 通信完毕 , 关闭套接字 *关闭 close()* - -**UDP客户端** - -1. 创建套接字对象 *创建socket对象* -2. 发送请求数据 *发送 sendto()* -3. 通信完毕 , 关闭套接字 *关闭 close()* - -简单实例 - -udp_server.py - -```python -# 导入socket模块 -import socket -# 创建socket对象 -sock = socket.socket(type=socket.SOCK_DGRAM) -# 绑定ip和端口 -sock.bind(('127.0.0.1', 8090)) -# 接收请求,返回数据和地址 -data,addr = sock.recvfrom(1024) -# 打印请求 -print(data.decode()) -# 关闭套接字 -sock.close() -``` - -udp_client.py - -```python -# 导入socket模块 -import socket -# 创建socket对象 -sock = socket.socket(type=socket.SOCK_DGRAM) -# 发送请求到指定地址 -sock.sendto(b"I'm Lyon", ('127.0.0.1', 8090)) -# 关闭套接字 -sock.close() -``` - -## Socket对象方法 - -| 方法 | 描述 | -| ------------------------------------ | ---------------------------------------- | -| s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。 | -| s.listen() | 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。 | -| s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 | -| s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。 | -| s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 | -| s.recv() | 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。 | -| s.send() | 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。 | -| s.sendall() | 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。 | -| s.recvfrom() | 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。 | -| s.sendto() | 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。 | -| s.close() | 关闭套接字 | -| s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。 | -| s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) | -| s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 | -| s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 | -| s.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) | -| s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 | -| s.fileno() | 返回套接字的文件描述符。 | -| s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。 | -| s.makefile() | 创建一个与该套接字相关连的文件 | - -解决` OSError: [Errno 48] Address already in use ` 问题 - -添加一条socket配置 , 重用ip和端口 - -```python -import socket -sock = socket.socket() -# 添加在bind前 -sock.setsockopt(socket.SOL_SOCKET,SO_REUSEADDR,1) -sock.bind(address) -``` - diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/03-Socket\345\256\236\347\216\260QQ\350\201\212\345\244\251.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/03-Socket\345\256\236\347\216\260QQ\350\201\212\345\244\251.md" deleted file mode 100644 index 22ec46fa8..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/03-Socket\345\256\236\347\216\260QQ\350\201\212\345\244\251.md" +++ /dev/null @@ -1,127 +0,0 @@ -# Attack on Python - socket实现QQ聊天 🐍 - - - - - - - - - - -## 介绍 - -在上一篇中写了最基本版的socket服务端和客户端 , 即仅能通信一次后就自动关闭了 , 显然实际应用中可不是这样的 , 那就来写一个像QQ一样的聊天程序吧 - -## TCP实现 - -因为TCP是有链接的 , 这就导致只能有一个服务端 , 但是可以有多个客户端 - -tcpqq_server.py - -```python -import socket -sock = socket.socket() -sock.bind(('127.0.0.1', 8080)) -sock.listen(5) -# 实现链接循环 -while True: - print("Watiting for the link...") - conn, addr = sock.accept() - print("Your friend {} is online...".format(addr)) - # 实现通信循环 - while True: - messages = conn.recv(1024) - print("Messages from [{}]:{}".format(addr, messages.decode('utf-8'))) - if messages == b'q': - break - else: - while True: - data = input("Please input the messages to be sent:").strip().encode('utf-8') - # 注意发送的内容不能为空,否则接收方就会一直等下去 - if not data: - print("Can't be empty...") - continue - conn.send(data) - break - print("Your friend {} is offline...".format(addr)) - conn.close() -sock.close() -``` - -tcpqq_client.py - -```python -import socket -sock = socket.socket() -sock.connect(('127.0.0.1', 8080)) -# 实现通信循环 -while True: - messages = input("Please input your messages to be sent:").strip().encode('utf-8') - # 注意发送的内容不能为空,否则接收方就会一直等下去 - if not messages: - print("Can't be empty...") - continue - elif messages == b'q': - break - else: - sock.send(messages) - data = sock.recv(1024) - print("Messages from [{}]:{}".format(('127.0.0.1', 8080), data.decode('utf-8'))) -sock.close() -``` - -当然实际应用中是不会用TCP来完成的 , 而是用UDP , 这里只是模拟 , 并且以上还有有问题没有解决的 , 比如如果发送的消息大于1024字节 , 那么就不能完整接收信息了 , 后续再进行处理 - -TCP版本的服务端可以允许同时连入5个客户端 , 值得注意的是并不是同时连入 , 按照顺序排队 , 只有前面的人说完了会连入后序的客户端 - -## UDP实现 - -以为UDP是无链接的 , 所以它可以实现想跟谁说话就跟谁说话 - -udpqq_server.py - -```python -import socket -sock = socket.socket(type=socket.SOCK_DGRAM) -sock.bind(('127.0.0.1', 8080)) -# 实现通信循环 -while True: - data, addr = sock.recvfrom(1024) - print("Receive a message from {}:{}".format(addr, data.decode('utf-8'))) - if data == b'q': - break - while True: - messages = input("Please input the messages to be sent:").strip().encode('utf-8') - if not messages: - print("Can't be empty...") - continue - sock.sendto(messages, addr) - break -sock.close() -``` - -udpqq_client.py - -```python -import socket -sock = socket.socket(type=socket.SOCK_DGRAM) -# 实现通信循环 -while True: - messages = input("Please input your messages to be sent:").strip().encode('utf-8') - if not messages: - print("Can't be empty...") - continue - elif messages == b'q': - break - else: - sock.sendto(messages, ('127.0.0.1',8080)) - data, addr = sock.recvfrom(1024) - print("Receive a message from {}:{}".format(addr, data.decode('utf-8'))) -sock.close() -``` - -利用UDP实现才更接近现实 , 我们只需要知道他的ip和端口 , 我们就可以跟他讲话 , 在他即可以是服务端 , 也可以是客户端 , 不过必须注意接收和发送流程的问题 - -以上两种实现方式 , 都只是最基础的版本 , 在UDP中我们可以将所有人的ip和端口放到一个字典里或者其他存储里 , 利用ip和端口就可以实现跟所有人进行聊天了 - diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/04-Socket\345\256\236\347\216\260\350\277\234\347\250\213\346\211\247\350\241\214\345\221\275\344\273\244.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/04-Socket\345\256\236\347\216\260\350\277\234\347\250\213\346\211\247\350\241\214\345\221\275\344\273\244.md" deleted file mode 100644 index 9fb3271ee..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/04-Socket\345\256\236\347\216\260\350\277\234\347\250\213\346\211\247\350\241\214\345\221\275\344\273\244.md" +++ /dev/null @@ -1,133 +0,0 @@ -# Attack on Python - socket实现远程执行命令 🐍 - - - - - - - - - - -## os模块实现 - -osssh_server.py - -```python -# 导入socket模块 -import socket -# 导入os模块 -import os -# 创建套接字对象 -sock = socket.socket() -# 重置ip和端口 -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -# 绑定ip和端口 -sock.bind(('127.0.0.1', 8080)) -# 监听 -sock.listen(5) -# 链接循环 -while True: - print("Waitting for connection...") - # 阻塞 - conn, addr = sock.accept() - print("{}successful connection...".format(addr)) - while True: - cmd = conn.recv(1024) - # 接收为空说明客户端断开了连接 - if not cmd: - print("Client is disconnected...") - break - print("The command is {}".format(cmd.decode())) - # 利用os模块进行系统调用,py3中popen参数为str,所以先decode - data = os.popen(cmd.decode()).read() - # 发送命令执行结果 - conn.send(data.encode('utf-8')) - # 关闭链接 - conn.close() -# 关闭套接字 -sock.close() -``` - -osssh_client.py - -```python -# 导入socket模块 -import socket -# 创建套接字对象 -sock = socket.socket() -# 连接服务端 -sock.connect(('127.0.0.1', 8080)) -while True: - cmd = input("Please input the command:").strip() - if not cmd: - print("Can't empty...") - continue - elif cmd == 'exit': - break - # 发送命令 - sock.send(cmd.encode('utf-8')) - # 接收命令执行结果 - data = sock.recv(1024) - print(data.decode('utf-8')) -# 关闭套接字 -sock.close() -``` - -## subprocess模块实现 - -subprocess_server.py - -```python -import socket -import subprocess -sock = socket.socket() -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -sock.bind(('127.0.0.1', 8080)) -sock.listen(5) -while True: - print("Waitting for connection...") - conn, addr = sock.accept() - print("{}successful connection...".format(addr)) - while True: - cmd = conn.recv(1024) - if not cmd: - print("Client is disconnected...") - break - print("The command is {}".format(cmd.decode())) - # 利用subprocess模块进行系统调用 - data = subprocess.Popen(cmd.decode(),shell=True, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout = data.stdout.read() - stderr = data.stderr.read() - # 打包执行结果 - res = stdout + stderr - # 发送执行结果 - conn.send(res) - conn.close() -sock.close() -``` - -subprocess_client.py - -```python -import socket -sock = socket.socket() -sock.connect(('127.0.0.1', 8080)) -while True: - cmd = input("Please input the command:").strip() - if not cmd: - print("Can't empty...") - continue - elif cmd == 'exit': - break - sock.send(cmd.encode('utf-8')) - data = sock.recv(1024) - # Windows终端默认编码是gbk,所以得用gbk进行解码 - print(data.decode('gbk')) -sock.close() -``` - -以上两种方法实现了简单的ssh , 即远程执行命令 , 但是这两个都一个问题 , 当我们执行多次命令后 , 结果就不是我们想要得到了 , 它会发生粘包 , 即有可能上条命令的结果粘到这条命令的结果了 , 如何解决粘包问题 ? 下一篇整理 \ No newline at end of file diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/05-\347\262\230\345\214\205.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/05-\347\262\230\345\214\205.md" deleted file mode 100644 index 234770ec7..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/05-\347\262\230\345\214\205.md" +++ /dev/null @@ -1,230 +0,0 @@ -# Attack on Python - 粘包 🐍 - - - - - - - - - - -## 粘包 - - -由上一篇 `Socket实现远程执行命令` 中所出现的问题引出了粘包这个问题 , 粘包到底是什么? - -首先 , ` 粘包现象只出现在TCP中 ` , 为什么说只有在TCP中才会发生粘包现象 , 先来详细解释一下TCP与UDP吧 - -> **TCP** - -TCP (transprot control protocol, 传输控制协议) 是面向连接的 , 面向流的 , 提供高可靠性服务 . 收发两端都有要一一对应的socket(一对一模式) , 因此发送端为了将多个发往接收端的包 , 更有效的发到对方 , 使用了优化方法(Nagle算法) , ` 将多次间隔较小且数据量小的数据 , 合并成一个大的数据块 , 然后进行封包 . ` 必须提供科学的拆包机制 , 才能进行合理的分辨 , 所以说面向流的通信是无消息保护边界的 - -> **UDP** - -UDP(user datagram protocol, 用户数据报协议) 是无连接的 , 面向消息的 , 提供高效率服务 . 不使用块的合并优化算法 , 由于UDP支持的是一对多的模式 , 所以接收端的skbuff (套接字缓冲区) 采用了链式结构来记录每一个到达的UDP包 , 在每个UDP包中就有了消息头 (消息来源地址 , 端口等信息) , 这样 , 对于接收端来说 , 就容易进行区分处理了 . 即面向的通信是有消息保护边界的 - -> **区别** - -TCP是基于数据流的 , 于是收发的消息不能为空 , 这就需要在客户端和服务端都添加空消息的处理机制 , 防止程序卡住 , 而UDP是基于数据报的 , 就算收发空内容 , 也不是空消息 , UDP协议会自动帮你封装上消息头 - -**粘包现象发生的原因** - -粘包分为两种 - -1. 发送方引起的粘包 - - 这种情况下引起的粘包是TCP协议本身造成的 , TCP为了提高传输效率 , 发送方往往要收集到足够多的数据后才发送一个TCP段 `(超过时间间隔也会发送,时间间隔是很短的)` , 如果连续几次需要发送的数据都很少 , 通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去 , 所以几次的数据到接收方时就粘成一包了 - - 如下 : - - ```python - # 发送方第一次发送 - send(b"I'm ") - # 立马第二次,不超过时间间隔 - send(b"Lyon") - ------------- - # 接收 - data = recv(1024) - # 收到的是两次粘在一起的数据 - print(data.decode()) - # 打印结果: I'm Lyon - ``` - -2. 接收方引起的粘包 - - 这种情况引起的粘包则是因为接收方不及时接收缓冲区的数据包造成的 , 比如发送方一次发送了10字节的数据 , 而接收方只接收了2字节 , 那么剩余的8字节的数据将都在缓冲区等待接收 , 而此时发送方又发送了2字节的数据 , 过了一会接收方接收了20字节(大于剩余10字节) , 接收完毕 , 缓冲区剩余的数据就和第二次发送的数据粘成了一个包 , 产生粘包 - - 如下 : - - ```python - # 发送4字节内容 - send(b"I'm ") - # 接收1字节,缓冲区还有3字节 - data1 = recv(1) - print("data1:",data1) - # 发送4字节内容,粘到缓冲区中剩余的3字节后面 - send(b"Lyon") - # 接收7字节,接收完毕 - data2 = recv(7) - print("data2:",data2) - ''' - 打印结果: - data1:I - data2:'m Lyon - ''' - ``` - -**SO : 所以所谓粘包问题主要还是因为接收方不知道消息之间的界限 , 不知道一次性提取多少字节的数据所造成的** - -## 解决方法 - - -既然粘包是因为接收方不知道消息界限 , 那么我们就自己创建界限 - -### low方法 - -我们只需要对上一篇中` subprocess_server.py `以及` subprocess_client.py` 做一点点修改就行了 - -subprocess_server_development.py - -```python -import socket -import subprocess -sock = socket.socket() -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -sock.bind(('127.0.0.1', 8080)) -sock.listen(5) -while True: - print("Waitting for connection...") - conn, addr = sock.accept() - print("{}successful connection...".format(addr)) - while True: - # 接收指令 - cmd = conn.recv(1024) - if not cmd: - print("Client is disconnected...") - break - print("The command is {}".format(cmd.decode())) - # 获取执行结果 - data = subprocess.Popen(cmd.decode(),shell=True, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE) - # 获取错误句柄 - err = data.stderr.read() - if err: - res = err - else: - res = data.stdout.read() - # 发送数据长度 - conn.send(str(len(res)).encode('utf-8')) - # 防止与两次发送数据粘在一起 - ready = conn.recv(1024) - if ready == b'OK': - # sendall连续调用send完成发送 - conn.sendall(res) - conn.close() -sock.close() -``` - -subprocess_client_development.py - -```python -import socket -sock = socket.socket() -sock.connect(('127.0.0.1', 8080)) -while True: - cmd = input("Please input the command:").strip() - if not cmd: - print("Can't empty...") - continue - elif cmd == 'exit': - break - # 发送指令 - sock.send(cmd.encode('utf-8')) - # 获取数据长度 - length = sock.recv(1024).decode('utf-8') - # 发送标志 - sock.send(b'OK') - recvsize = 0 - data = b'' - # 循环接收 - while recvsize < int(length): - recvdata = sock.recv(1024) - recvsize += len(recvdata) - data += recvdata - print(data.decode('gbk')) -sock.close() -``` - -利用这种方式 , 我们需要提前先将数据大小发送过去 , 这无疑会放大网络延迟带来的性能损耗 - -### 制作报头 - -既然需要将大小发送过去 , 那我们是不是可以为字节流加上自定义固定长度报头 , 报头中包换数据大小等信息 , 然后一次直接发送过去 , 对方只要在接收的时候先从取出报头 , 再取数据 - -所以我们只需要固定好报头的长度可以了 , 我们可以利用struct模块来制作报头 , 只需对上方法稍作修改 - -subprocess_struct_server.py - -```python -import socket,struct -import subprocess -sock = socket.socket() -sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -sock.bind(('127.0.0.1', 8080)) -sock.listen(5) -while True: - print("Waitting for connection...") - conn, addr = sock.accept() - print("{}successful connection...".format(addr)) - while True: - cmd = conn.recv(1024) - if not cmd: - print("Client is disconnected...") - break - print("The command is {}".format(cmd.decode())) - data = subprocess.Popen(cmd.decode(),shell=True, - stdout=subprocess.PIPE, - stdin=subprocess.PIPE, - stderr=subprocess.PIPE) - err = data.stderr.read() - if err: - res = err - else: - res = data.stdout.read() - # 制作4位固定报头并发送 - conn.send(struct.pack('i', len(res))) - # 直接循环发送 - conn.sendall(res) - conn.close() -sock.close() -``` - -subprocess_struct_client.py - -```python -import socket,struct -sock = socket.socket() -sock.connect(('127.0.0.1', 8080)) -while True: - cmd = input("Please input the command:").strip() - if not cmd: - print("Can't empty...") - continue - elif cmd == 'exit': - break - sock.send(cmd.encode('utf-8')) - res = sock.recv(4) - # 解开报头取出数据长度 - length = struct.unpack('i', res)[0] - recvsize = 0 - data = b'' - # 循环接收 - while recvsize < length: - data += sock.recv(1024) - recvsize += len(data) - print(data.decode('gbk')) -sock.close() -``` \ No newline at end of file diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/06-Socketserver\345\256\236\347\216\260\345\244\232\345\271\266\345\217\221.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/06-Socketserver\345\256\236\347\216\260\345\244\232\345\271\266\345\217\221.md" deleted file mode 100644 index 1a3ae79e9..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/06-Socketserver\345\256\236\347\216\260\345\244\232\345\271\266\345\217\221.md" +++ /dev/null @@ -1,97 +0,0 @@ -# Attack on Python - Socketserver实现多并发 🐍 - - - - - - - - - - -## 介绍 - -在上面的整理篇章中 , 简单的网络编程基本已经会了 , 一个TCP , 一个UDP , 然后就是粘包问题 - -但是在上述中有一个问题 , 在现实生活中 , 一个服务端肯定常常需要同时服务好几个客户端 , 而上述篇章中并没有实现一对多同时进行的情况 , TCP中只能等前一个链接断开后续的才能连上 , 没连上就一直等 ; UDP则是接一次发一次 , 并不能同时接两次发两次 . 为了处理这个问题 , 即实现并发 (后续文章详细讲解) , Python中有一个socketserver模块可以满足我们的要求 - -## socketserver - -Python提供了两个级别访问的网络服务: - -1. 低级别的网络服务支持基本的socket , 它提供了标准的BSD Socket API , 可以访问底层操作系统Socket接口的全部方法 -2. 高级别的网络服务模块socketserver , 它提供了服务器中心类 , 可以简化网络服务器的开发 - -socket就不用说了 , now socketserver - -我们知道基于TCP的套接字 , 关键就是两个循环 , 一个链接循环(多人) , 一个通信循环(多消息) - -在socketserver模块中分为两大类 : server类 (解决链接问题) 和request类 (解决通信问题) - -如果想进一步了解 , 可以看看官方文档 , < [socketserver官方文档 ](https://docs.python.org/3/library/socketserver.html?highlight=socketserver#module-socketserver)> - -## 实现多并发 - -multi_socketserver_server.py - -```python -import socketserver -class MyServer(socketserver.BaseRequestHandler): - def handle(self): - # 创建一个链接,继承于socketserver中的BaseRequestHandler类 - conn = self.request - # 发送登录提示 - conn.sendall(b"Welcome to login...") - print("Client connect...") - while True: - print("Waitting for recving message...") - # 接收消息 - message = conn.recv(1024) - print(message.decode('utf-8')) - # 收到exit就退出 - if message == "exit": - break - # 回复消息 - data = input("Reply message:") - # 发送消息 - conn.sendall(data.encode('utf-8')) -if __name__ == "__main__": -  # 实例化 - server = socketserver.ThreadingTCPServer(('127.0.0.1', 999, ), MyServer) - # 调用serve_forever方法 - server.serve_forever() -''' -def serve_forever(self, poll_interval=0.5): - """ - Handle one request at a time until shutdown. - Polls for shutdown every poll_interval seconds. Ignores - self.timeout. If you need to do periodic tasks, do them in - another thread. - """ -''' -``` - -multi_socketserver_client.py - -```python -# 就是一个简单的TCP客户端 -import socket -sock = socket.socket() -# 连接服务端 -sock.connect(('127.0.0.1', 999, )) -login = sock.recv(1024) -print(login.decode('utf-8')) -while True: - message = input("Please input the message:").strip() - if message == "exit": - sock.sendall(b'exit') - break - else: - sock.sendall(message.encode('utf-8')) - print("Waitting for recving message...") - data = sock.recv(1024) - print(data.decode('utf-8')) -sock.close() -``` - -到这里 , 我们成功实现了多并发 , 多并发是什么? 这就关系到操作系统中的进程和线程了 , 网络编程既然是实现两个进程间的通信 , 那么就逃不过进程 , 线程等了 \ No newline at end of file diff --git "a/01-Python/05-\347\275\221\347\273\234\347\257\207/README.md" "b/01-Python/05-\347\275\221\347\273\234\347\257\207/README.md" deleted file mode 100644 index beaed0bd0..000000000 --- "a/01-Python/05-\347\275\221\347\273\234\347\257\207/README.md" +++ /dev/null @@ -1,2 +0,0 @@ -# Attack on Python - 网络篇 🐍 - diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/01-\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/01-\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" deleted file mode 100644 index 209f61b01..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/01-\350\277\233\347\250\213\344\270\216\347\272\277\347\250\213.md" +++ /dev/null @@ -1,186 +0,0 @@ -# Attack on Python - 进程与线程 🐍 - - - - - - - - - - -## 进程 - -进程是对正在运行程序的一个抽象 , 即一个进程就是一个正在执行程序的实例 - -从概念上说 , 每个进程拥有它自己的虚拟CPU . 当然 , 实际上真正的CPU在各进程之间来回切换 . 这种快速切换就是多道程序设计 . 但是某一瞬间 , CPU只能运行一个进程 , 但在1秒钟期间 , 它可能运行多个进程 , 就是CPU在进行快速切换 , 有时人们所说的 `伪并行` 就是指这种情形 - -### 创建进程 - -操作系统中 , 有4种事件导致进程的创建 - -1. 系统初始化 , 启动操作系统时 , 通常会创建若干个进程 , 分为前台进程和后台进程 -2. 执行了正在运行的进程所调用的进程创建系统调用 -3. 用户请求创建一个新的进程 -4. 一个批处理作业的初始化 - -从技术上看 , 在所有这些情况中 , 新进程都是由一个已存在的进程执行了一个用于创建进程的系统调用而创建的 . 这个进程可以是一个运行的用户进程 , 一个由键盘或鼠标启动的系统进程或者一个批处理管理进程 . 这个进程所做的工作是 , 执行一个用来创建新进程的系统调用 . 在Linux/Unix中提供了一个` fork()` 系统调用就是用来创建进程的 (子进程) , 当然在Windows中也有相对应的系统调用 - -在Python中的os模块封装了常见的系统调用 , 其中就包括fork , 可以在Python程序中轻松创建子进程 - -```python -'''因为Windows中没有fork调用,所以下程序只能在Unix/Linux下执行''' -import os -# os.getpid()获取父进程的ID -print("Process %s start..." % os.getpid()) -# fock()调用一次会返回两次 -pid = os.fork() -# 子进程返回0 -if pid == 0: - print("I am child process %s and my parent is %s" % (os.getpid(), os.getppid())) -# 父进程返回子进程的ID -else: - print("I %s just created a child process %s" % (os.getpid(), pid)) -``` - -### 终止进程 - -进程不可能永恒的存在 , 迟早都会终止 , 通常由下列条件引起 : - -1. 正常退出(自愿的) , 任务完成退出 -2. 出错退出(自愿的) , 进程中的错误 -3. 严重错误(非自愿) , 由进程引起的错误 -4. 被其他进程杀死(非自愿) , 某进程执行一个系统调用通知操作系统杀死某个其他进程 - -在有些系统中 , 当一个进程终止时 , 不论是自愿的还是其他原因 , 由该进程所创建的所有进程也一律立即被杀死 . 不过Unix和Windows都不是这种工作方式 - -### 进程状态 - -每个进程都有自己的程序计数器和内部状态 , 但进程之间经常需要相互作用 , 一个进程的输出结构可能作为另一个进程的输入 , 所以进程就会出现如下三种状态 : - -1. 运行态(该时刻进程实际占用CPU) -2. 就绪态(可运行 , 但因为其他进程正在运行而暂时停止) -3. 阻塞态(除非某中外部事件发生 , 否则进程不能运行) - -进程的三种状态之间有四种可能的转换关系 - -| 一个进程状态 | 另一个进程状态 | 过程 | -| ------ | ------- | ----------- | -| 运行态 | 阻塞态 | 进程为等待输入而 | -| 运行态 | 就绪态 | 调度程序选择另一个进程 | -| 就绪态 | 运行态 | 调度程序选择这个进程 | -| 阻塞态 | 就绪态 | 出现有效输入 | - -进程中还有一点就是进程实现的问题 , 这就是依靠进程表了 , 具体就不说明了 - -进程的作用主要是提供了多道编程(多进程) , 并且提高了计算机的利用率 , 但是有两点是进程没有解决的 : - -1. 进程在同一时间只能做一件事 , 显然这不够我们的需求 -2. 进程在执行过程中一旦阻塞 , 整个进程就挂起了 , 这也是对计算机资源的一种浪费 - -人们想到的解决办法就是 , 在一个进程里面再有一类进程 , 称为迷你进程 , 也就是下面要说的线程 - -## 线程 - -在传统操作系统中 , 每个进程有一个地址空间和一个控制线程 , 事实上 , 这几乎就是进程的定义 - -所以我们可以知道 , 线程是操作系统能够进程运算调度的最小单位 , 它被包含在进程之中 , 是进程中的实际运作单位 . 不过 , 经常存在在同一个地址空间中准并行运行多个控制线程的情况 , 这些线程就像分离的进程 - - 一个线程指的是进程中一个单一顺序的控制流 , 一个进程中可以**并发**多个线程 - -### 线程的使用 - -人们需要使用线程有两个理由 : - -1. 在多进程模型中 , 没有并行实体共享同一个地址空间和所有可用数据的能力 -2. 线程比进程更轻量级 , 在许多系统中 , 创建一个线程较创建一个进程要快10~100倍 - -### 线程与进程的区别 - -1. 线程是执行的指令集 , 进程是资源的集合 -2. 线程的启动速度要比进程的启动速度要快 -3. 两个线程的执行速度是一样的 -4. 进程与线程的运行速度是没有可比性的 -5. 线程共享创建它的进程的内存空间 , 进程的内存是独立的 -6. 两个线程共享的数据都是同一份数据 , 两个子进程的数据不是共享的 , 而且数据是独立的 -7. 同一个进程的线程之间可以直接交流 , 同一个主进程的多个子进程之间是不可以进行交流 , 如果两个进程之间需要通信 , 就必须要通过一个中间代理来实现 -8. 一个新的线程很容易被创建 , 一个新的进程创建需要对父进程进行一次克隆 -9. 一个线程可以控制和操作同一个进程里的其他线程 , 线程与线程之间没有隶属关系 , 但是进程只能操作子进程 -10. 改变主线程 , 有可能会影响到其他线程的行为 , 但是对于父进程的修改是不会影响子进程 - -## 并发与并行 - -### 并发 - -在早期操作系统只有一个处理器 , 所以想达到同时运行多个程序 , 显然是不可能的 , 唯一的办法就是骗自己 , 告诉自己这几个是"同时"在运行 , 怎么骗 ? 如下 - -🌰一 - -``` -现在你女朋友要你同时做三件事 - 1.洗衣服 - 2.洗碗 - 3.拖地 -明显你要同时完成是不可能的,那现在我赋予你超能力,你获得了光速加成,你可以在一瞬间到达洗衣房(厕所吧),厨房,客厅.然后你女朋友就发现了惊悚的一幕 - 1.你女朋友看向客厅,你正在客厅拖地 - 2.接着转头看向厨房,你正在洗碗 - 3.而后转头看向洗衣房,你正在洗衣服 -你女朋友就会告诉你:亲爱的,你是不是有分身呀,怎么可以同时做三件事情?我不管你得再分一个分身出来陪我玩,最后你成功的骗了你女朋友 -``` - -这就是操作系统中 , 单个CPU + 多道技术实现的并发 - -CPU就是你本人 , 多道技术就是我赋予你的用速度"同时"干多件事的能力 - -🌰二 - -``` -现在你女朋友已经知道你有超能力了,原来你一下只能干一件事情,她不高兴了,说道:我不管你得同时陪我还得做事情 -于是你又想出了一个办法 - 1.陪女朋友0.25秒 - 2.洗衣服0.25秒 - 3.洗碗0.25秒 - 4.拖地0.25秒 -以你女朋友的眼力绝对不可能看出你不在,就这样把1秒钟的时间平摊下来,然后一直循环下去,完美,再一次骗到了你女朋友 -``` - -这就是分时系统的并发 , 按时间进行分配 - -并发 , 就是伪并行的 - -### 并行 - -真正的同时运行 , 只有具备多个CPU才能实现 - -并发事实上就是串行 , 还是一个人在做多个任务 , 而并行则是多个人在做多个任务 . 明显一个人 , 即只有一个执行者同时不可能做两件事的 , 但是并行 , 多个执行者就能够同时做多件事 - -所以并发与并行 , 就是一瞬间是否能存在多个进程 - -## 同步与异步 - -### 同步 - -所谓同步 , 就是在发出一个功能调用时 , 在没有得到结果之前,该调用就不会返回 . 按照这个定义,其实绝大多数函数都是同步调用 . 但是一般而言 , 我们在说同步、异步的时候 , 特指那些需要其他部件协作或者需要一定时间完成的任务 - -### 异步 - -异步的概念和同步相对 , 当一个异步功能调用发出后 , 调用者不能立刻得到结果 . 当该异步功能完成后 , 通过状态、通知或回调来通知调用者 , 如果异步功能用状态来通知 , 那么调用者就需要每隔一定时间检查一次 , 效率就很低(有些初学多线程编程的人 , 总喜欢用一个循环去检查某个变量的值 , 这其实是一 种很严重的错误) . 如果是使用通知的方式 , 效率则很高 , 因为异步功能几乎不需要做额外的操作 . 至于回调函数 , 其实和通知没太多区别 - -## 阻塞与非阻塞 - -### 阻塞 - -阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已 - -### 非阻塞 - -非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程 - -## 小结 - -1. 对于进程和线程 , 直接阅读[《现代操作系统》](https://lyonyang.gitbooks.io/blog/ReadNotes/%E3%80%8A%E7%8E%B0%E4%BB%A3%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E3%80%8B.html) 一书再好不过了 -2. 并发与并行要注意执行顺序的问题 -3. 同步与异步针对的是函数/任务的调用方式 , 是否等待结果 -4. 阻塞与非阻塞针对的是进程或线程 , 阻塞进程则挂起 , 非阻塞即不挂起 - -这一篇基本属于纯理论 , 罗哩罗嗦了半天 diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/02-\345\244\232\347\272\277\347\250\213.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/02-\345\244\232\347\272\277\347\250\213.md" deleted file mode 100644 index 920882e97..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/02-\345\244\232\347\272\277\347\250\213.md" +++ /dev/null @@ -1,774 +0,0 @@ -# Attack on Python - 多线程 🐍 - - - - - - - - - - -## 介绍 - -在上一篇中说了一大堆理论 , 那么现在就开始实践了 - -先说线程再说进程 , 为什么 ? 因为在Python中有一个` Python GIL `全局解释器锁 , 这是个什么东西? 最后来说 - -总之线程和进程都是与操作系统有关的知识 , 所以操作系统基础 , 对于这两节内容的理解会有很大的帮助 - -## Threading - -Python通过两个标准库` _thread` (built-in) 和`threading`提供对线程的支持 , threading对_thread进行了封装 - -```python -_thread.py -''' -This module provides primitive operations to write multi-threaded programs. -The 'threading' module provides a more convenient interface. -''' -``` - -So , 明显我们一般直接使用threading - -threading模块中提供了Thread , Lock , RLock , Semaphore , Event , Condition , Timer等组件 - -## Thread - -参数说明 - -| 参数 | 说明 | -| ------ | ----------------------------------- | -| group | 未使用 , 值始终 | -| target | 表示调用对象 , 即子线程要执行的任务 | -| name | 子线程的名称 | -| args | 传入target函数中的位置参数 , 是一个元组 , 参数后必须加逗号 | -| kwargs | 表示调用对象的字典 | - -方法说明 - -| 方法 | 说明 | -| -------------------------------- | ---------------------------------------- | -| Thread.run (self) | 进程启动时运行的方法 , 由该方法调用target参数所指定的函数 , 在子类中可以进行重构 , 与线程中一样 | -| Thread.start (self) | 启动进程 , start方法就是去帮你调用run方法 | -| Thread.terminate (self) | 强制终止线程 , 不会进行任何清理操作 , 使用时需小心其子进程与锁的问题 | -| Thread.join (self, timeout=None) | 阻塞调用 , 主线程进行等待 , timeout为超时时间 | -| Thread.is_alive (self) | 这个方法在run()方法开始之前返回True , 在run()方法结束之后 , 返回所有活动线程的列表 | -| Thread.isDaemon(self) | 判断是否为守护线程 , 返回bool值 | -| Thread.setDaemon(self,daemonic) | 将子线程设置为守护线程 , daemonic = daemon | -| Thread.getName(self,name) | 获取线程名称 | -| Thread.setName(self,name) | 设置线程名称 | - -实例属性说明 - -| 属性 | 说明 | -| -------------- | ---------------------- | -| Thread.daemon | 默认值为False , True则为守护线程 | -| Thread.name | 线程的名称 | -| Thread.isAlive | 即为is_alive的返回值 | -| Thread.ident | 线程标识符 , 没启动则为None | - -**创建线程** - -Python中使用线程有两种方式 : 函数或者用类来包装线程对象 - -函数调用 - -```python -import threading -import time -# 定义线程要运行的函数 -def func(name): - print("I am %s" % name) - # 为了便于观察,让它睡上2秒 - time.sleep(2) -# 防止被导入执行两次 -if __name__ == '__main__': - # 创建一个线程实例,args参数是一个元组,必须加逗号 - t1 = threading.Thread(target=func, args=("Lyon",)) - # 再创建一个线程实例 - t2 = threading.Thread(target=func, args=("Kenneth",)) - # 启动线程 - t1.start() - # 启动另一个线程 - t2.start() - # 打印线程名 - print(t1.getName()) - # 打印线程名 - print(t2.getName()) -''' -执行结果: -I am Lyon -I am Kenneth -Thread-1 -Thread-2 -''' -``` - -类继承调用 - -```python -import threading -import time -# 继承threading中的Thread类 -class MyThread(threading.Thread): - # 线程中所需要的参数 - def __init__(self, name): - # threading.Thread.__init__(self) - super().__init__() - self.name = name - # 重构run方法,注意这个是表示线程活动的方法,必须有 - def run(self): - print("I am %s" % self.name) - time.sleep(2) -# 防止被导入执行两次 -if __name__ == '__main__': - # 创建一个线程实例 - t1 = MyThread('Lyon') - # 创建另一个线程实例 - t2 = MyThread('Kenneth') - # 启动线程,调用了类中的run方法 - t1.start() - # 启动另一个线程 - t2.start() - # 获取线程名 - print(t1.getName()) - # 获取线程名 - print(t2.getName()) -''' -执行结果: -I am Lyon -I am Kenneth -Lyon -Kenneth -''' -``` - -``` -Thread实例对象的方法 - # isAlive(): 返回线程是否活动的。 - # getName(): 返回线程名。 - # setName(): 设置线程名。 -threading模块提供的一些方法: - # threading.currentThread(): 返回当前的线程变量。 - # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 - # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 -``` - - - -## Join & setDaemon - -在说这两个方法之前 , 需要知道主线程与子线程的概念 - -主线程 : 当一个程序启动时 , 就有一个进程被操作系统创建 , 与此同时一个线程也立刻运行 , 该线程通常叫做程序的主线程 - -子线程 : 因为程序是开始时就执行的 , 如果你需要再创建线程 , 那么创建的线程就是这个主线程的子线程 - -` 主线程的重要性体现在两方面 : 1. 是产生其他子线程的线程 ; 2. 通常它必须最后完成执行比如执行各种关闭作` - -` 在Python中线程的一些机制与C/C++不同 , 在C/C++中 , 主线程结束后 , 其子线程会默认被主线程kill掉 . 而在Python中 , 主线程结束后 , 会默认等待子线程结束后 , 主线程才退出` - -> **Join** - -在上面的线程的创建时 , 获取线程名并不是在最后执行的 , 而是遇到sleep阻塞自动切换执行的 , 而sleep(2)则是在最后执行的 , 如果还不明白在看下面一个例子 - -遇到阻塞自动切换 - -```python -import threading -import time -# 定义线程要执行的函数 -def run(name): - # 打印内容 - print("I am %s" % name) - # 睡两秒 - time.sleep(2) - # 睡完继续起来干活 - print("When I'm done, I'm going to keep talking...") -if __name__ == '__main__': - # 创建一个线程实例 - lyon = threading.Thread(target=run, args=('Lyon',)) - # 创建另一个线程实例 - kenneth = threading.Thread(target=run, args=('Kenneth',)) - # 启动线程 - lyon.start() - # 启动另一个线程 - kenneth.start() - # 我是主线程,我应该最后执行的 - print("I was the main thread, and I ended up executing") -''' -执行结果: -I am Lyon -I am Kenneth -I was the main thread, and I ended up executing -When I'm done, I'm going to keep talking... -When I'm done, I'm going to keep talking... -结果分析: -第一行打印了 I am Lyon,这没问题第一个线程启动了 -第二行打印了 I am Kenneth,这就有问题了,这明明是第二个线程中的事情,我擦我的第一个线程都没执行完 -第三行打印了 I was the main thread, and I ended up executing,你牛逼把我主线程的事都打印了 -睡了两秒,看来是遇到阻塞自动切换了 -最后打印了两个线程中的 When I'm done, I'm going to keep talking... -''' -``` - -在很多情况下 , 我们需要的是让各个线程执行完毕后 , 才接着往下执行 , 也就是不跳过阻塞 , 就让它等下去 , 这个时候就需要用join了 - -join : 阻塞调用程序 , 知道join () 方法的线程调用终止 , 才会继续往下执行 - -上面加上join后 - -```python -import threading -import time -def run(name): - print("I am %s" % name) - time.sleep(2) - print("When I'm done, I'm going to keep talking...") -if __name__ == '__main__': - lyon = threading.Thread(target=run, args=('Lyon',)) - kenneth = threading.Thread(target=run, args=('Kenneth',)) - lyon.start() - lyon.join() - kenneth.start() - kenneth.join() - print("I was the main thread, and I ended up executing") -''' -执行结果: -I am Lyon -# sleep 2 seconds -When I'm done, I'm going to keep talking... -I am Kenneth -# sleep 2 seconds -When I'm done, I'm going to keep talking... -I was the main thread, and I ended up executing -''' -``` - -程序按照我们的意愿按顺序执行了 - -> **setDaemon** - -无论进程还是线程 , 都遵循 : 守护进程 (线程) 会等待主进程 (线程) 运行完毕后被销毁 - -对于主进程来说 , 运行完毕指的是主进程代码运行完毕 - -对于主线程来说 , 运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕 - -setDaemon() 与 join() 基本上是相对的 , join会等子线程执行完毕 ; 而setDaemon则不会等 , 只要主线程执行完了 , 我才不管你子线程执没执行完毕 , 统统给我回收 , 这样才能保证进程能正常结束 - -setDaemon设置守护线程 - -```python -import threading -import time -def run(name): - print("I am %s" % name) - time.sleep(2) - print("When I'm done, I'm going to keep talking...") -if __name__ == '__main__': - lyon = threading.Thread(target=run, args=('Lyon',)) - kenneth = threading.Thread(target=run, args=('Kenneth',)) - # 设置守护线程,必须在启动前设置 - lyon.setDaemon(True) - # 启动线程 - lyon.start() - # 设置守护线程 - kenneth.setDaemon(True) - kenneth.start() - print("I was the main thread, and I ended up executing") -''' -执行结果: -I am Lyon -I am Kenneth -I was the main thread, and I ended up executing -结果说明: -主线程一旦执行完毕,那么守护线程就一并退出,不管被守护线程是否执行完毕 -所以lyon和kenneth两个子线程并没有执行完毕,如果在主线程中在加上sleep(5), -即超过子线程阻塞,那么这两个子线程就能执行完毕了 -''' -``` - -将主线程设置为守护线程 - -```python -import threading -import time -def run(num): - print("I like num %d" % num) - time.sleep(2) - print("When I'm done, I'm going to keep talking...") -def main(): - for i in range(1, 6): - # 创建线程实例 - t = threading.Thread(target=run, args=(i,)) - # 启动线程 - t.start() - # 阻塞调用 - t.join() -if __name__ == '__main__': - # 创建一个主线程 - m = threading.Thread(target=main, args=[]) - # 设置为守护线程 - m.setDaemon(True) - # 启动线程 - m.start() - # 等待其子线程执行完毕后,再8秒退出 - m.join(timeout=8) -''' -执行结果: -I like num 1 -When I'm done, I'm going to keep talking... -I like num 2 -When I'm done, I'm going to keep talking... -I like num 3 -When I'm done, I'm going to keep talking... -I like num 4 -结果说明: -子线程并没有执行完毕,主线程退出,守护线程一并退出 -''' -``` - -## Python GIL - -```python -''' -In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple -native threads from executing Python bytecodes at once. This lock is necessary mainly -because CPython’s memory management is not thread-safe. (However, since the GIL -exists, other features have grown to depend on the guarantees that it enforces.) -''' -``` - -基本意思是说 , 在CPython解释器中 , 同一个进程下开启的多线程 , 同一时刻只能有一个线程执行 , 无法利用多核优势 - -GIL并不是Python的一种特性 , 它是在实现Python解释器(CPhthon)时引入的一个概念 , 就比如同一段代码可以通过CPython , PyPy , Psyco等不同的Python执行环境来执行 , 像JPython中就没有GIL . 由于CPython是大部分环境下默认的Python执行环境 , 所以在很多人的概念里CPython就是Python , 但是要记住 , GIL并不是Python的特性 , Python完全可以不依赖GIL - -> **GIL** - -GIL本质就是一把互斥锁 , 即会将并发运行变成串行 , 以此来控制同一时间内共享数据只能被一个任务进行修改 , 从而保证数据的安全性 - -` 保护不同的数据时 , 应该加不同的锁` , GIL是解释器级别的锁 , 又叫做全局解释器锁 - -CPython加入GIL主要的原因是为了降低程序的开发复杂度 , 让你不需要关心内存回收的问题 , 你可以理解为Python解释器里有一个独立的线程 , 每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的 , 此时你自己的程序 里的线程和Python解释器自己的线程是并发运行的 , 假设你的线程删除了一个变量 , py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻 , 可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了 , 结果就有可能新赋值的数据被删除了 , 为了解决类似的问题 , Python解释器简单粗暴的加了锁 , 即当一个线程运行时 , 其它人都不能动 , 这样就解决了上述的问题 , 这可以说是Python早期版本的遗留问题 . 毕竟Python出来的时候 , 多核处理还没出来呢 , 所以并没有考虑多核问题 - -以上就可以说明 , Python多线程不适合CPU密集型应用 , 但适用于IO密集型应用 - -##Lock 🍀 - -多线程与多进程最大的不同在于 , 多进程中 , 同一个变量 , 各自有一份拷贝存在于每个进程中 , 互不影响 , 但是在多线程中 , 所有变量对于所有线程都是共享的 , 因此 , 线程之间共享数据最大的危险在于多个线程同时修改一个变量 , 那就乱套了 , 所以我们需要GIL一样 , 来锁住数据 - -上面说了 , 保护不同的数据 , 要加不同的锁 , GIL是为了保护解释器的数据 , 明显我们还需要保护用户数据的锁 - -所以为了保证用户数据的安全 , 我们需要另一个锁 , 互斥锁(Mutex) - -无锁版本 - -```python -# 线程的调度是由操作系统决定的,一旦线程交替执行,并且次数足够多,那么就可能出问题了 -# 直接用廖大大的例子,地址:www.liaoxuefeng.com -import threading -# 假定这是你的银行存款: -balance = 0 -def change_it(n): - # 先存后取,结果应该为0: - global balance - balance = balance + n - balance = balance - n -def run_thread(n): - for i in range(100000): - change_it(n) -for j in range(10000): - t1 = threading.Thread(target=run_thread, args=(5,)) - t2 = threading.Thread(target=run_thread, args=(8,)) - # 这里跟join的位置有关系,因为join也是可以实现锁的功能的,下面说 - t1.start() - t2.start() - t1.join() - t2.join() - print(balance,end="") -''' -执行结果: -0 -0 -5 -5 -5 -# 这里我就只给出5次的结果,因为5次就已经出现错误了 -# 正常情况下数据不混乱,结果应该一直为0 -''' -``` - -加锁版本 - -```python -import threading -# 假定这是你的银行存款: -balance = 0 -def change_it(n): - # 先存后取,结果应该为0: - global balance - balance = balance + n - balance = balance - n -# 创建一把锁 -lock = threading.Lock() -def run_thread(n): - for i in range(100000): - # 先要获取锁: - lock.acquire() - try: - # 放心地改吧: - change_it(n) - finally: - # 改完了一定要释放锁: - lock.release() -for j in range(10000): - t1 = threading.Thread(target=run_thread, args=(5,)) - t2 = threading.Thread(target=run_thread, args=(8,)) - t1.start() - t2.start() - t1.join() - t2.join() - print(balance) -''' -执行结果: -0 -# 这里的结果一直都是0,So我就只写出一个结果了 -''' -``` - -**join vs lock** - -上面第一个无锁版本的例子中 , 其实join()就可以实现我们想要的功能 , 只需要各个线程后面不加多余的东西直接接join()就行 , 因为我们知道join()的功能是进行阻塞 , 一加join() , 肯定其他就没有线程能动了 , 上面例子中故意将`t1.join()` 加在了`t2.start()`的后面 , 就是为了能让t2"有机可趁" , 既然`join()` 就可以实现 , 那我们还要锁干嘛? - -我们应该想想 , join实现的原理 , join会使线程进行阻塞 , 也就是说会让真个线程变成完全串行的 , 既然只有一个线程在进行操作 , 那么它肯定就不会乱 , 但是使用join影响了执行效率 , 所以我们想能不能只让线程中的一部分串行? 答案是能的 , 就是利用互斥锁 , 想让哪里串行就让哪里串行 - -PS : `Python3.x`好像会自动加锁 , 但是`Python2.x`是不会的 , 写的时候还是都加上把 , 保证安全性 - -## RLock - -RLock叫做递归锁 , 在说之前先说一个死锁问题 - -进程也有死锁和递归锁 , 所谓死锁 : 是指两个或两个以上的进程或线程在执行过程中 , 因争夺资源而造成的一种互相等待的现象 , 若无外力作用 , 他们都将无法推进下去 . 此时称系统处于死锁状态或系统产生了死锁 , 这些永远在互相等待的进程称为死锁进程 , 如下 - -```python -import threading -import time -# 创建两个锁 -mutexA = threading.Lock() -mutexB = threading.Lock() -class MyThread(threading.Thread): - # 重构run方法 - def run(self): - self.func1() - self.func2() - def func1(self): - # 获取锁A - mutexA.acquire() - print("\033[31m%s get mutexA...\033[0m" % self.name) - # 获取锁B - mutexB.acquire() - print("\033[33m%s get mutexB...\033[0m" % self.name) - # 释放锁B - mutexB.release() - # 释放锁A - mutexA.release() - def func2(self): - mutexB.acquire() - print("\033[35m%s get mutexB...\033[0m" % self.name) - # 睡1秒 - time.sleep(1) - mutexA.acquire() - print("\033[37m%s get mutexA...\033[0m" % self.name) - mutexA.release() - mutexB.release() -if __name__ == '__main__': - for i in range(10): - t = MyThread() - t.start() -''' -执行结果: -Thread-1 get mutexA... -Thread-1 get mutexB... -Thread-1 get mutexB... -Thread-2 get mutexA... -# 到这里整个程序就永远等着了 -结果说明: -首先执行了func1,没有阻塞,顺利执行完毕 -然后执行func2,获取了锁B后就开始睡1一秒,也就是阻塞开始 -于是系统自动切换,再次执行了func1,而B的锁在阻塞前没释放 -最后func1中的mutexB.acquire()就一直等前面一个线程把锁给释放了 -等到天荒地老,海枯石烂,也等不到了 -''' -``` - -为了解决这样的问题 , 于是就有了递归锁 , 在Python中为了支持在同一线程中多次请求同一资源 , Python提供了可重入锁RLock - -这个RLock内部维护着一个Lock和一个counter变量 , counter记录了acquire的次数 , 从而使得资源可以被多次require . 直到一个线程所有的acquire都被release , 其他的线程才能获得资源 - -RLock版本 - -```python -# 仅仅只需如下修改 -mutexA = threading.Lock() -mutexB = threading.Lock() -# 以上两行修改为 -mutexA = mutexB = threading.RLock() -# 注意如果仅仅修改后部分,即将Lock() -> RLock()是不行的,那样等于创建了两把递归锁 -``` - -## queue - -我们可以使用队列处理线程编程中多个线程之间交换的安全问题 - -在queue中有三种模式 , Queue (先进先出 , FIFO) , LifoQueue (后进先出 , LIFO) , 还有一个可以设置优先级的队列PriorityQueue - -Queue - -```python -import Queue -q = Queue.Queue() -q.put('First') -q.put('Second') -q.put('Third') -print(q.get()) -print(q.get()) -print(q.get()) -''' -执行结果: -First -Second -Third -''' -``` - -LifoQueue - -```python -import Queue -q = Queue.LifoQueue() -q.put('First') -q.put('Second') -q.put('Third') -print(q.get()) -print(q.get()) -print(q.get()) -''' -执行结果: -Third -Second -First -''' -``` - -PriorityQueue - -```python -import Queue -q = Queue.PriorityQueue() -# put进入一个元组,元组的第一个元素是优先级,越小优先级越高 -q.put((20, 'A')) -q.put((10, 'B')) -q.put((30, 'C')) - -print(q.get()) -print(q.get()) -print(q.get()) -''' -执行结果: -(10, 'B') -(20, 'A') -(30, 'C') -''' -``` - -更多请阅读Python标准库目录下的queue模块内容 - -## Producer-Consumer - -**生产者 - 消费者问题** - -又称有界缓冲区问题 , 在进程中 , 两个进程共享一个公共的固定大小的缓冲区 , 其中一个是生产者 , 将信息放入缓冲区 ; 另一个是消费者 , 从缓冲区取出信息 . 问题在于当缓冲区满时 , 而此时生产者还想向其中放入一个新的数据项的情况 ; 相反 , 当缓冲区为空时 , 消费者视图从缓冲区中取数据 , 该如何去解决? - -为了解决这个问题于是引入了生产者和消费者模式 , 基本思路也是如进程中睡眠和唤醒 - -**生产者消费模式** - -通过一个容器来解决生产者和消费者的强耦合问题 . 生产者与消费者彼此之间不直接通讯 , 而通过阻塞队列来进行通讯 , 所以生产者生产完数据之后不用等待消费者处理 , 直接扔给阻塞队列 , 消费者不找生产者要数据 , 而是直接从阻塞队列里取 , 阻塞队列就相当于一个缓冲区 , 平衡了生产者和消费者的处理能力 - -在并发编程中使用生产者和消费者模式能解决绝大多数并发问题 , 在线程世界里 , 生产者就是生产数据的线程 , 消费者就是消费数据的线程 . 以下有两个生产者消费者问题的例子 - -基础版本 - -```python -import threading -import queue -def producer(): - for i in range(10): - # 进行生产,放入队列 - q.put("%d bottle of milk" % i) - print("Start waiting for all the milk to be taken...") - q.join() - print("All the milk was taken out...") - -def consumer(name): - # 队列中有就取 - while q.qsize() > 0: - print("%s got %s" % (name, q.get())) - q.task_done() -# 创建一个队列对象 -q = queue.Queue() -p = threading.Thread(target=producer,) -p.start() -c1 = consumer("Lyon") -``` - -生产与消费同时进行 - -```python -import time -import random -import queue -import threading -q = queue.Queue() -def Producer(name): - count = 1 - while count < 20: - time.sleep(random.randrange(3)) - # 将数据放入队列 - q.put(count) - print('Producer %s has produced %s bun...' % (name, count)) - count += 1 -def Consumer(name): - count = 1 - while count < 20: - time.sleep(random.randrange(4)) - # 不为空就取,为空就提示 - if not q.empty(): - # 从队列中取出信息 - data = q.get() - print(data) - print('\033[32;1mConsumer %s has eat %s bun...\033[0m' % (name, data)) - else: - print("No bun anymore...") - count += 1 -p1 = threading.Thread(target=Producer, args=('Lyon',)) -c1 = threading.Thread(target=Consumer, args=('Kenneth',)) -p1.start() -c1.start() -``` - -## Semaphore - -信号量(Semaphore) , 引入一个整型变量来累计线程的唤醒次数 , threading模块中 , 有一个Semaphore类管理一个内置的计数器 , 每当调用acquire()时内置计数器 -1 ;调用release()时内置计数器 +1;计数器不能小于0 ; 当计数器等于0时 , acquire()将阻塞线程知道其他线程调用release() - -一次最多连接5个线程 - -```python -import threading -import time -def func(): - # 内置计数器 -1 - sm.acquire() - print('%s get semaphores' % threading.current_thread().getName()) - time.sleep(2) - # 内置计数器 +1 - sm.release() -if __name__ == '__main__': - # 一次最多只能有5个线程获取信号量 - sm = threading.Semaphore(5) - for i in range(10): - t = threading.Thread(target=func) - t.start() -``` - -利用信号量可以解决生产者与消费者问题 , 《现代操作系统中》一书中进行了简单的实现 - -## Event - -在多线程中 , 每个线程都是互相独立的 , 互不影响 , 如果我们需要通过某个线程的状态来控制程序的执行过程 , 是非常难的 . 为了解决这些问题 , 我们就可以使用threading中的Event对象来实现我们的目的 - -Event对象中包含一个可由线程设置的信号标志 , 它允许线程等待某些事件的发生 . 在初始情况下 , Event对象中的信号标志被设置为假 ; 如果有线程等待一个Event对象 , 而这个Event对象的标志为假 , 那么这个线程将会被一直阻塞直至该标志为真 . 一个线程如果将一个Event对象的信号标志设置为真 , 它将唤醒所有等待这个Event对象的线程 . 如果一个线程等待一个已经被设置为真的Event对象 , 那么它将忽略这个事件 , 继续执行 - -| 方法 | 描述 | -| ------------- | ---------------------------------------- | -| Event.isSet() | 返回Event的状态 , isSet == is_set | -| Event.wait() | 如果Event.isSet() == False将阻塞线程 | -| Event.set() | 设置Event的状态值为True , 所有阻塞池中的线程激活进入就绪状态 , 等待操作系统调度 | -| Event.clear() | 回复Event的状态值为False | - -解决重复连接问题 - -```python -import threading -import time -import random -def conn_mysql(): - count = 1 - while not event.is_set(): - # 大于3次主动触发TimeoutError - if count > 3: - raise TimeoutError('Connection timeout...') - print('%s %sth attempt to connect' % (threading.current_thread().getName(), count)) - # 阻塞0.5秒 - event.wait(0.5) - count += 1 - print('%s connect successfully' % threading.current_thread().getName()) -def check_mysql(): - print('%s is checking mysql' % threading.current_thread().getName()) - time.sleep(random.randint(2, 4)) - # 激活线程 - event.set() -if __name__ == '__main__': - event = threading.Event() - conn1 = threading.Thread(target=conn_mysql) - conn2 = threading.Thread(target=conn_mysql) - check = threading.Thread(target=check_mysql) - conn1.start() - conn2.start() - check.start() -``` - -## Condition - -使线程等待 , 只有满足条件时 , 才释放线程 - -```python -import threading -def condition_func(): - ret = False - inp = input('>>>') - # 只有当inp等于1时才会执行 - if inp == '1': - ret = True - return ret -def run(n): - con.acquire() - con.wait_for(condition_func) - print("run the thread: %s" %n) - con.release() -if __name__ == '__main__': - con = threading.Condition() - for i in range(10): - t = threading.Thread(target=run, args=(i,)) - t.start() -``` - -## Timer - -threading模块中还有一个Timer类 , 可以指定时间后执行某操作 - -```python -import threading -def hello1(): - print("I am Lyon") -def hello2(): - print("Hello, future") -# 1秒后执行 -t1 = threading.Timer(1, hello1) -# 两秒后执行 -t2 = threading.Timer(2,hello2) -t1.start() -t2.start() -``` \ No newline at end of file diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/03-\345\244\232\350\277\233\347\250\213.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/03-\345\244\232\350\277\233\347\250\213.md" deleted file mode 100644 index d1232959b..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/03-\345\244\232\350\277\233\347\250\213.md" +++ /dev/null @@ -1,649 +0,0 @@ -# Attack on Python - 多进程 🐍 - - - - - - - - - - -## 介绍 - -上一篇 `多线程` 中已经对Python中多线程部分进行了整理 , 进程中有很多也是相似的 - -概念在并发编程第一篇中就已经介绍了 , So直接开始操作 - -## multiprocessing - -从上一篇我们也已经知道了 , Python中的多线程无法利用多核优势 , 所以如果我们想要充分地使用多核CPU的资源 , 那么就只能靠多进程了 , 因为进程是系统调度的 , Python提供了` multiprocessing`模块了对多进程的支持 - -multiprocessing模块中提供了Process , Queue , Pipe , Lock , RLock , Event , Condition等组件 , 与threading模块有很多相似之处 - -## Process - -用于创建进程的类 , 与threading模块中的` _Thread`类类似 - -```python -''' -Process类的构造函数 -def __init__(self, group=None, target=None, name=None, args=(), kwargs={}): -''' -``` - -参数说明 - -| 参数 | 说明 | -| ------ | ---------------------------------------- | -| group | 未使用 , 值始终 | -| target | 与threading.Tread中的target参数一样 , 表示调用对象 , 即子进程要执行的任务 | -| name | 子进程的名称 | -| args | 传入target函数中的位置参数 , 是一个元组 , 与线程一样 , 参数后必须加逗号 | -| kwargs | 表示调用对象的字典 | - -方法说明 - -| 方法 | 说明 | -| --------------------------------- | ---------------------------------------- | -| Process.run (self) | 进程启动时运行的方法 , 由该方法调用target参数所指定的函数 , 在子类中可以进行重构 , 与线程中一样 | -| Process.start (self) | 启动进程 , start方法就是去帮你调用run方法 | -| Process.terminate (self) | 强制终止进程 , 不会进行任何清理操作 , 使用时需小心其子进程与锁的问题 | -| Process.join (self, timeout=None) | 与线程中一样 , 阻塞调用 , 主进程进行等待 , timeout为超时时间 | -| Process.is_alive (self) | 判断进程是否正在运行 , 返回bool值 | - -实例属性说明 - -| 属性 | 说明 | -| ---------------- | ---------------------------------------- | -| Process.daemon | 默认值为False , True则为守护进程 | -| Process.name | 进程的名称 | -| Process.pid | 进程的pid | -| Process.exitcode | 进程运行时为None , 如果为-N , 表示被信号N结束 | -| Process.authkey | 进程的身份验证键 , 默认是由os.urandom()随机生成的32字符的字符串 . 这个键的用途是为涉及网络连接的底层进程间通信提供安全性 , 这类连接只有在具有相同的身份验证键时才能成功 | - -**创建进程** - -与创建线程的方式一样 , 有两种 - -函数调用 - -```python -import multiprocessing -import time -def hello(name): - print("I am %s" % name) - time.sleep(1) - print("Hello future...") -if __name__ == '__main__': - # 创建一个进程实例 - p = multiprocessing.Process(target=hello, args=("Lyon",)) - # 启动进程,实质调用run() - p.start() - print("End of main process...") -''' -执行结果: -End of main process... -I am Lyon -Hello future... -''' -``` - -类继承调用 - -```python -import multiprocessing -import time -# 自定义进程类,继承multiprocessing中的Process类 -class MyProcess(multiprocessing.Process): - def __init__(self, name): - super().__init__() - self.name = name - # 重构父类中的run方法 - def run(self): - print("I am %s" % self.name) - time.sleep(1) - print("Hello future...") -if __name__ == '__main__': - # 创建一个进程实例 - p = MyProcess('Lyon') - # 启动进程 - p.start() - print("End of main process...") -''' -执行结果: -End of main process... -I am Lyon -Hello future... -''' -``` - -在上栗创建进程中有一个问题 , 就是如果我们在Windows下 , 使用`start()`方法 , 就必须加上`if __name__ == '__main__':` , 进程是通过`fork`系统调用 , 而Windows中并没有fork , 所以多处理模块启动了一个新的Python进程 , 并导入了调用模块 . 如果进程在导入的时候被调用 , 那么这就会引发无限的新进程 , 后果不言而喻 . 当然还是可以直接使用`run()`的 - -## Join & Daemon - -join - -进程中join与线程中的join是一样的 , 就进行阻塞调用 , 让主进程进行等待 , 整体串行 - -实例 - -```python -# 多线程中的例子,换汤不换药 -import multiprocessing -import time -def run(name): - print("I am %s" % name) - time.sleep(2) - print("When I'm done, I'm going to keep talking...") -if __name__ == '__main__': - lyon = multiprocessing.Process(target=run, args=('Lyon',)) - kenneth = multiprocessing.Process(target=run, args=('Kenneth',)) - lyon.start() - lyon.join() - kenneth.start() - kenneth.join() - print("I was the main thread, and I ended up executing") -''' -执行结果: -I am Lyon -When I'm done, I'm going to keep talking... -I am Kenneth -When I'm done, I'm going to keep talking... -I was the main thread, and I ended up executing -''' -``` - -Daemon - -守护进程会在主进程代码执行结束后就终止 - -```python -# 还是多线程中的例子 -import multiprocessing -import time -def run(num): - print("I like num %d" % num) - time.sleep(2) - print("When I'm done, I'm going to keep talking...") -def main(): - for i in range(1, 6): - t = multiprocessing.Process(target=run, args=(i,)) - t.daemon = True - t.start() - t.join() -if __name__ == '__main__': - m = multiprocessing.Process(target=main, args=[]) - m.start() - m.join(timeout=8) -''' -执行结果: -I like num 1 -When I'm done, I'm going to keep talking... -I like num 2 -When I'm done, I'm going to keep talking... -I like num 3 -When I'm done, I'm going to keep talking... -I like num 4 -When I'm done, I'm going to keep talking... -I like num 5 -When I'm done, I'm going to keep talking... -''' -``` - -PS : 与线程不同的是 , 守护进程内无法再开启子进程 , 否则就抛出异常 - -## Lock - -进程之间的数据是不共享的 , 因为每个进程之间是相互独立的 , 但是进程共享一套文件系统 , 所以访问同一个文件 , 是没有问题的 , 但是如果有多个进程对同一文件进行修改 , 就会造成错乱 , 所以我们为了保护文件数据的安全 , 就需要给其进行加锁 - -同样的 , join为整体串行 , lock为局部串行 - -廖大大实例 , Lock - -```python -import multiprocessing -# 假定这是你的银行存款: -balance = 0 -def change_it(n): - # 先存后取,结果应该为0: - global balance - balance = balance + n - balance = balance - n -# 创建一把锁 -lock = multiprocessing.Lock() -def run_thread(n): - for i in range(100000): - # 先要获取锁: - lock.acquire() - try: - # 放心地改吧: - change_it(n) - finally: - # 改完了一定要释放锁: - lock.release() -# 在多线程例子中并没有写这句,但是多进程中使用start()必须加 -if __name__ == '__main__': - for j in range(10000): - t1 = multiprocessing.Process(target=run_thread, args=(5,)) - t2 = multiprocessing.Process(target=run_thread, args=(8,)) - t1.start() - t2.start() - t1.join() - t2.join() - print(balance) -''' -执行结果: -0 -. -# 数据安全得到了保障,所以全为0 -... -``` - -RLock - -```python -import multiprocessing -import time -mutexA = mutexB = multiprocessing.RLock() -class MyThread(multiprocessing.Process): - def run(self): - self.func1() - self.func2() - def func1(self): - mutexA.acquire() - print("\033[31m%s get mutexA...\033[0m" % self.name) - mutexB.acquire() - print("\033[33m%s get mutexB...\033[0m" % self.name) - mutexB.release() - mutexA.release() - def func2(self): - mutexB.acquire() - print("\033[35m%s get mutexB...\033[0m" % self.name) - time.sleep(1) - mutexA.acquire() - print("\033[37m%s get mutexA...\033[0m" % self.name) - mutexA.release() - mutexB.release() -if __name__ == '__main__': - for i in range(10): - t = MyThread() - t.start() -``` - -## Producer-consumer - -生产者消费者模式 , 在多线程中已经有过说明了 , 目的是为了解决并发问题 - -实例 - -```python -# 可与多线程篇中进行对照 -import time -import random -import multiprocessing -q = multiprocessing.Queue() -def Producer(name, q): - count = 1 - while count < 5: - time.sleep(random.randrange(3)) - q.put(count) - print('Producer %s has produced %s bun...' % (name, count)) - count += 1 -def Consumer(name , q): - count = 1 - while count < 20: - time.sleep(random.randrange(4)) - if not q.empty(): - data = q.get() - print(data) - print('\033[32;1mConsumer %s has eat %s bun...\033[0m' % (name, data)) - else: - print("No bun anymore...") -if __name__ == '__main__': - # 进程间的数据是不共享的,注意我们需要把q,即队列对象传入函数中 - p1 = multiprocessing.Process(target=Producer, args=('Lyon', q,)) - c1 = multiprocessing.Process(target=Consumer, args=('Kenneth', q,)) - p1.start() - c1.start() - p1.join() - c1.join() - print("End of main process...") -``` - -## Queue - -multiprocessing模块支持进程间通信有两种主要形式 , 队列和管道 - -在多线程中有queue模块 , 供我们实现队列接口 , 在多进程中则是Queue类为我们提供队列接口 - -Queue为单向通道 , 先进先出(FIFO) - -```python - -class Queue(object): - def __init__(self, maxsize=-1): - self._maxsize = maxsize - # 返回队列中目前项目数量,使用时防止竞争,最好令其串行 - def qsize(self): - return 0 - # 队列是否为空,返回True,使用时防止竞争,最好令其串行 - def empty(self): - return False - # 队列是否已满,返回True,使用时防止竞争,最好令其串行 - def full(self): - return False - # 将数据放入队列 - def put(self, obj, block=True, timeout=None): - pass - # 同上put - def put_nowait(self, obj): - pass - # 从队列中取出项 - def get(self, block=True, timeout=None): - pass - # 同上get - def get_nowait(self): - pass - # 关闭队列,垃圾回收会调用此方法 - def close(self): - pass - # 连接队列的后台线程,用于等待所有队列项消耗 - def join_thread(self): - pass - # 不会在在进程退出时自动连接后台线程,可防止join_thread()方法阻塞 - def cancel_join_thread(self): - pass -``` - -实例 - -```python -import multiprocessing -q = multiprocessing.Queue(3) -q.put("First") -q.put("Second") -q.put("Third") -print(q.full()) -print(q.get()) -print(q.get()) -print(q.get()) -print(q.empty()) -''' -执行结果: -True -First -Second -Third -True - -''' -``` - -## Pipe - -介绍 - -```python -# Pipe在进程之间创建一条管道,并返回元组(connection(),connection()) -def Pipe(duplex=True): - return Connection(), Connection() -# 管道端的连接对象 -class Connection(object): - # 发送对象 - def send(self, obj): - pass - # 接收另一端发送的对象 - def recv(self): - pass - # 返回连接使用的整数文件描述符 - def fileno(self): - return 0 - # 关闭链接 - def close(self): - pass - # 如果链接上的数据可用,返回True - def poll(self, timeout=None): - pass - # 发送字节到数据缓冲区,buffer是支持缓冲区接口的任意对象,offset为偏移量,size为字节数 - def send_bytes(self, buffer, offset=-1, size=-1): - pass - # 接收一条完整字节消息 - def recv_bytes(self, maxlength=-1): - pass - # 接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口 - def recv_bytes_into(self, buffer, offset=-1): - pass -''' -Connection类与我们网络编程中所使用的socket(TCP)类似,socket(TCP)对象之间通信也是双向的 -... -``` - -基于管道实现进程间通信 - -```python -import multiprocessing -def producer(seq, p): - left,right = p - # 关闭不使用的一端 - right.close() - for i in seq: - # 发送进管道中 - left.send(i) - else: - # 关闭管道 - left.close() -def consumer(p, name): - left,right = p - # 关闭不使用的一端 - left.close() - while True: - # 如果消费者不使用的一端忘记关闭,消费者中的recv()就一直等下去 - try: - bun = right.recv() - print('%s got %s buns...' % (name, bun)) - # 触发EOFError - except EOFError: - right.close() - break -if __name__ == '__main__': - # 创建管道实例 - left, right = multiprocessing.Pipe() - c1 = multiprocessing.Process(target=consumer, args=((left, right), 'c1')) - c1.start() - seq = (i for i in range(10)) - producer(seq, (left, right)) - right.close() - left.close() - c1.join() - print('End of main process...') -``` - -## Manager - -进程之间是相互独立的 , 在multiprocessing模块中的Manager可以实现进程间数据共享 , 并且Manager还支持进程中的很多操作 , 比如Condition , Lock , Namespace , Queue , RLock , Semaphore等 - -由于基于消息传递(Queue , Pipe)的并发编程才是未来的主流 , 所以对于Manager应该尽量避免使用 - -Manager实例 - -```python -import multiprocessing -# 既然数据共享了,就需要像多线程那样,防止竞争 -def run(d,lock): - # 演示没加锁的实例 - # lock.acquire() - d['count'] -= 1 - # lock.release() -if __name__ == '__main__': - # lock = multiprocessing.Lock() - with multiprocessing.Manager() as m: - dic = m.dict({'count' : 100}) - process_list = [] - for i in range(100): - p = multiprocessing.Process(target=run, args=(dic, lock,)) - process_list.append(p) - p.start() - for p in process_list: - p.join() - print(dic) -''' -执行结果: -# 该结果看缘分了,没加锁数据共享,导致混乱,与线程中一样 -{'count': 1} -''' -``` - -更多详细内容< [multiprocessing.Manager](https://docs.python.org/3/library/multiprocessing.html?highlight=manager#module-multiprocessing.managers) > - -## Semaphore - -与线程中一样 - -```python -class Semaphore(object): - def __init__(self, value=1): - pass - - def acquire(self, blocking=True, timeout=None): - pass - - def release(self): - pass -``` - -实例 - -```python -import multiprocessing -import time -def func(sem, num): - sem.acquire() - print('%s get semaphores' % num) - time.sleep(2) - sem.release() -if __name__ == '__main__': - sem = multiprocessing.Semaphore(5) - for i in range(1,11): - t = multiprocessing.Process(target=func, args=(sem, i,)) - t.start() -``` - -## Event - -与线程中一样 - -```python -class Event(object): - def is_set(self): - return False - def set(self): - pass - def clear(self): - pass - def wait(self, timeout=None): - pass -``` - -实例 - -```python -import multiprocessing -import time -import random -def conn_mysql(conn, event): - count = 1 - while not event.is_set(): - if count > 3: - # 主动触发超时异常 - raise TimeoutError('Connection timeout...') - print('%s %sth attempt to connect' % (conn, count)) - event.wait(0.5) - count += 1 - print('%s connect successfully' % conn) -def check_mysql(conn, event): - print('%s is checking mysql' % conn) - time.sleep(random.randint(2, 4)) - event.set() -if __name__ == '__main__': - event = multiprocessing.Event() - for i in range(10): - conn = multiprocessing.Process(target=conn_mysql, args=('conn'+str(i), event)) - conn.start() -``` - -## Pool - -`multiprocessing`中的Process实现了我们对多进程的需求 , 但是当我们进行并发编程时 , 一旦需要开启的进程数量非常大时 , 使用Process已经不能满足我们的要求了 . 因为进程是需要占用系统资源的 , 操作系统不可能去无限的开启进程 ; 并且使用Process动态生成多个进程 , 我们还需要手动的去限制进程的数量 , 所以这个时候我们就应该用进程池(Pool)来实现了 - -`multiprocessing.Pool` - -参数说明 - -| 参数 | 说明 | -| ----------- | -------------------------------- | -| numprocess | 要创建的进程数 , 如果省略 将默认使用cpu_count() | -| initializer | 每个进程启动时要执行的可调用对象 | -| initargs | 传给initializer的参数组 | - -方法说明 - -| 方法 | 说明 | -| ---------------------------------------- | ---------------------------------------- | -| Pool.apply(self, func, args=(), kwds={}) | 在一个进程池中执行func(*args , **kwargs) , 并返回结果 | -| Pool.apply_async(self, func, args=(), kwds={}, callback=None, | 与apply()方法一样 , 该方法为异步版本应用的方法 , 返回结果是AsyncResult类的实例 , callback指定回调的函数 . callback禁止执行任何阻塞操作 , 否则将接收其他异步操作中的结果 | -| Pool.close(self) | 关闭进程池 , 如果所有操作持续挂起 , 它们将在工作进程终止前完成 | -| Pool.join(self) | 等待所有工作进程退出 | -| Pool.get(self, timeout=None) | 获取结果 , timeout可选 | -| Pool.ready(self) | 完成调用就返回True | -| Pool.successful(self) | 完成调用并且没有引发异常返回True , 在结果就绪之前调用此方法会引发异常 | -| Pool.wait(self, timeout=None) | 等待结果变为可用 | -| Pool.terminate(self) | 立即终止所有工作进程 , 垃圾回收会自动调用此方法 | - -同步调用apply - -```python -from multiprocessing import Pool -import os -import time -def run(n): - print("%s run..." % os.getpid()) - # 不令其阻塞,结果会同时打印 - time.sleep(2) - return n**2 -if __name__ == '__main__': - # 进程池没满就新创建进程执行请求,否则就等待 - # 注意,这里指定进程池数量为3,会一直是这三个进程在执行,只不过执行的请求可能改变 - pool = Pool(3) - res_list = [] - for i in range(10): - # 获取执行结果,同步运行,会阻塞等待拿到结果,等待过程中无论是否阻塞都会在原地等 - # 注意等待过程中由于阻塞,其cpu权限会被夺走 - res = pool.apply(run, args=(i,)) - res_list.append(res) - print(res_list) -``` - -异步调用apply_async - -```python -from multiprocessing import Pool -import os -import time -def run(n): - print("%s run..." % os.getpid()) - time.sleep(2) - return n**2 -if __name__ == '__main__': - # 进程池没满就新创建进程执行请求,否则就等待 - # 注意,这里指定进程池数量为3,会一直是这三个进程在执行,只不过执行的请求可能改变 - pool = Pool(3) - res_list = [] - for i in range(10): - res = pool.apply_async(run, args=(i,)) - res_list.append(res) - pool.close() - pool.join() - for res in res_list: - print(res.get()) -``` \ No newline at end of file diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/04-\345\244\232\350\277\233\347\250\213\345\256\236\344\276\213\345\217\212\345\233\236\350\260\203\345\207\275\346\225\260.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/04-\345\244\232\350\277\233\347\250\213\345\256\236\344\276\213\345\217\212\345\233\236\350\260\203\345\207\275\346\225\260.md" deleted file mode 100644 index 20d7ed6b9..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/04-\345\244\232\350\277\233\347\250\213\345\256\236\344\276\213\345\217\212\345\233\236\350\260\203\345\207\275\346\225\260.md" +++ /dev/null @@ -1,164 +0,0 @@ -# Attack on Python - 多进程实例及回调函数 🐍 - - - - - - - - - - -## 进程池实例 - -**使用进程池维护固定数目的进程** - -server.py - -```python -import socket -import os -import multiprocessing -server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -server.bind(('127.0.0.1', 8080)) -server.listen(5) -def talk(conn, client_addr): - print("Process pid : %s" % os.getpid()) - while True: - try: - msg = conn.recv(1024) - if not msg:break - conn.send(msg.upper()) - except Exception: - break -if __name__ == '__main__': - pool = multiprocessing.Pool() - while True: - conn, client_addr = server.accept() - # 同步则一时间只有一个客户端能访问,所以使用异步 - pool.apply_async(talk,args=(conn, client_addr,)) -``` - -client.py - -```python -import socket -client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -client.connect(('127.0.0.1', 8080)) -while True: - msg = input("Please input message:").strip() - if not msg: continue - client.send(msg.encode('utf-8')) - data = client.recv(1024) - print(data.decode('utf-8')) -``` - -## 回调函数 - -回调函数就是一个通过函数指针调用的函数 , 如果你把函数的指针(地址)作为参数传递给另一个函数 , 当这个指针被用来调用其所指向的函数时 , 我们就说这是回调函数 - -回调函数不是由该函数的实现方直接调用 , 而是在特定的事件或条件发生时由另外的一方调用的 , 用于对该事件或条件进程响应 - -进程池中使用回调函数 - -`apply_async`(*func*[, *args*[, *kwds*[, *callback*[, *error_callback*]]]]) - -```python -If callback is specified then it should be a callable which accepts a single argument. When the result becomes ready callback is applied to it, that is unless the call failed, in which case the error_callback is applied instead. -''' -意思是如果指定了回调,那么它应该是可调用的,调用失败则会应用error_callback -''' -``` - -实例 - -```python -import multiprocessing -import requests -import os -def get_page(url): - print('Process %s get %s...' % (os.getpid(), url)) - respone = requests.get(url) - if respone.status_code == 200: - return {'url': url, 'text': respone.text} -# 进行回调的函数,处理结果 -def pasrse_page(res): - print('Process %s parse %s...' % (os.getpid(), res['url'])) - parse_res = 'url : %s\nsize : %s\n' % (res['url'], len(res['text'])) - with open('db.txt', 'a') as f: - f.write(parse_res) -if __name__ == '__main__': - urls = [ - 'https://www.baidu.com', - 'https://www.python.org', - 'https://www.openstack.org', - 'https://help.github.com/', - 'http://www.sina.com.cn/' - ] - p = multiprocessing.Pool(3) - res_list = [] - for url in urls: - # 执行并返回结果,异步, - res = p.apply_async(get_page, args=(url,), callback=pasrse_page) - res_list.append(res) - p.close() - p.join() - # 拿到的是get_page的结果,其实完全没必要拿该结果,该结果已经传给回调函数处理了 - print([res.get() for res in res_list]) -``` - -处理结果db.txt - -```txt -url : https://www.openstack.org -size : 60191 -url : https://www.python.org -size : 49081 -url : https://www.baidu.com -size : 2443 -url : https://help.github.com/ -size : 118622 -url : http://www.sina.com.cn/ -size : 601426 -``` - -爬虫案例 - -```python -from multiprocessing import Pool -import requests -import re -def get_page(url, pattern): - response = requests.get(url) - if response.status_code == 200: - print(response.text) - return (response.text,pattern) -def parse_page(info): - page_content, pattern = info - res=re.findall(pattern, page_content) - for item in res: - dic={ - 'index' : item[0], - 'title' : item[1], - 'actor' : item[2].strip()[3:], - 'time' : item[3][5:], - 'score' : item[4]+item[5] - } - print(dic) -if __name__ == '__main__': - pattern1=re.compile(r'
.*?board-index.*?>(\d+)<.*?title="(.*?)".*?star.*?>(.*?)<.*?releasetime.*?>(.*?)<.*?integer.*?>(.*?)<.*?fraction.*?>(.*?)<',re.S) - url_dic={ - 'http://maoyan.com/board/7' : pattern1, - } - p=Pool() - res_l=[] - for url,pattern in url_dic.items(): - res = p.apply_async(get_page, args=(url, pattern), callback=parse_page) - res_l.append(res) - for i in res_l: - i.get() - ''' - 不是每次抓取都能成功 - ''' -``` \ No newline at end of file diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/05-\345\215\217\347\250\213.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/05-\345\215\217\347\250\213.md" deleted file mode 100644 index 8f8d9e1eb..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/05-\345\215\217\347\250\213.md" +++ /dev/null @@ -1,240 +0,0 @@ -# Attack on Python - 协程 🐍 - - - - - - - - -## 介绍 -[协程 (`Coroutine`)](https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B) , 就是一组可以协调工作 [(协作式)](https://zh.wikipedia.org/wiki/%E5%8D%8F%E4%BD%9C%E5%BC%8F%E5%A4%9A%E4%BB%BB%E5%8A%A1) 的子程序 `(函数)` - -协程的本质就是一组函数 , 一组协同工作的函数 - -### 线程和协程的区别 - -相对于线程而言 , 线程是抢占式的 , 它的调度方案是由操作系统控制的 , 而协程是协作式或者说非抢占式的 , 由程序自己主动让出处理器 , 所以协程会更加的灵活 ; 并且线程是昂贵的 , 线程上下文切换的成本要高于协程上下文切换 , 而且协程与 `CPU` 和操作系统通常没有关系 , 所以没有理论上限 , 也就是说 , 协程要比线程轻得多 , 这也是为什么协程又被叫做 "微线程" - -"微线程" 只是用来说明协程比线程要轻量级 , 但是协程和线程完全是两个概念 , 说白了协程只是一组函数 , 而线程是操作系统的内核对象 - -在历史上是先有的协程 , 后有的线程 , 协程出现的目的是为了实现并发 , 而线程则是为了并行 , 也就是线程可以利用多核优势 (当然在 `Python` 中由于 `GIL` 锁线程同样无法利用多核优势) , 而协程无法利用多核优势 , 并且非抢占式调度的公平性是一个很大的问题 , 所以相对而言协程都没参与多核 `CPU` 并行处理 , 而线程可以利用多核达到真正的并行计算 , 这两者的差距就不言而喻了 , 这也是为什么后来线程比协程要更加广泛 ; 而到了现代 , 协程更多的是用来做成一组执行队列 , 比如迭代器 , 事件循环等等 - -那为什么协程现在被网络上神化了呢 ? 接着往下看 - -要实现协程 , 需要实现中断 , 恢复 , 切换上下文这三项功能 , 在实现之前 , 我们先说说生成器 - -## 生成器 - -生成器是一次生成一个值的特殊类型函数 , 它可以进行惰性求值 , 因为生成器每次调用都只生成一个值 , 所以他不需要提前将数据加载到内存 - -在 `Python` 中我们可以通过 `yield` 来定义一个生成器 , `yield` 语句会把你需要的值返回给调用生成器的地方 , 后退出函数 下一次调用生成器函数的时候又从上次中断的地方开始执行 , 而生成器内的所有变量参数都会被保存下来供下一次使用 , 这就是生成器实现的原理 - -### 生成器和协程的关系 - -生成器和协程的区别就是它们都可以挂起自身的执行 , 或者说拥有中断能力 , 但是协程除了中断能力 , 还拥有控制能力 , 协程可以控制在它让位之后哪个协程立即续它来执行 , 而生成器不能 , 生成器只能把控制权转交给调用生成器的调用者 , 所以生成器 , 也叫做 "半协程" , 是协程的子集 - -## 实现协程 - -在 `Python` 的早期版本里 , 我们可以通过 `yield` 以及 `send` 方法来实现协程 - -```python -import time -from queue import Queue - -q = Queue() - -def produce(consumer): - count = 0 - while True: - while not q.qsize(): - count += 1 - q.put(count) - time.sleep(1) - print('[PRODUCER] Producing %s...' % count) - # 恢复 - consumer.send(count) - -def consumer(): - while True: - while q.qsize(): - count = q.get() - time.sleep(1) - print('[CONSUMER] Consuming %s...' % count) - # 中断 - yield - -consumer = consumer() -# 初始化生成器 -next(consumer) -produce(consumer) -``` - -一个协程作为生产者 , 一个协程作为消费者 , 这样我们就实现了一个简单的多任务生产者消费者模型 , 生产者消费者协同工作自动切换 , `yield` 来中断执行 , `send` 来恢复执行 , 而代码逻辑控制了上下文的切换 , 这个例子只是为了证明协程的实用性 - -你可能会发现 , 把上面的代码按照 `yield` 拆分成几个函数功能上是一样的 , 我们把拆分的函数叫做**子例程** , 实际上 , 子例程可以看做是特定状态的协程 , 任何的子例程都可以转写成不使用 `yield` 的协程 - -### 子例程和协程的区别 - -相对于子例程而言 , 协程更加灵活 , 协程更加适合用来实现彼此比较熟悉的程序组件 , 或者说耦合度高一点的组件 , 比如 : [协作式多任务](https://zh.wikipedia.org/wiki/%E5%8D%8F%E4%BD%9C%E5%BC%8F%E5%A4%9A%E4%BB%BB%E5%8A%A1)、[异常处理](https://zh.wikipedia.org/wiki/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86)、[事件循环](https://zh.wikipedia.org/wiki/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF)、[迭代器](https://zh.wikipedia.org/wiki/%E8%BF%AD%E4%BB%A3%E5%99%A8)、[无限列表](https://zh.wikipedia.org/wiki/%E6%83%B0%E6%80%A7%E6%B1%82%E5%80%BC)和[管道](https://zh.wikipedia.org/wiki/%E7%AE%A1%E9%81%93_(%E8%BD%AF%E4%BB%B6)) - -协程的切换概念是 "让步" , 而子例程的切换概念是 "出产" , 一个主动 , 一个被动 , 以下摘自 [Wiki](https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B) : - -- 子例程可以调用其他子例程 , 调用者等待被调用者结束后继续执行 , 故而子例程的生命期遵循后进先出 , 即最后一个被调用的子例程最先结束返回 , 协程的生命期完全由对它们的使用需要来决定 -- 子例程的起始处是惟一的入口点 , 每当子例程被调用时,执行都从被调用子例程的起始处开始 , 协程可以有多个入口点 , 协程的起始处是第一个入口点 , 每个 `yield` 返回出口点都是再次被调用执行时的入口点 -- 子例程只在结束时一次性的返回全部结果值 , 协程可以在 `yield` 时不调用其他协程 , 而是每次返回一部分的结果值 , 这种协程常称为[生成器](https://zh.wikipedia.org/wiki/%E7%94%9F%E6%88%90%E5%99%A8_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BC%96%E7%A8%8B))或[迭代器](https://zh.wikipedia.org/wiki/%E8%BF%AD%E4%BB%A3%E5%99%A8) - -所以到这里 , 协程的应用并没有线程那么广泛 , 可能也并没有想象中那么强大 , 而且协程是在单线程下的 , 只要一处阻塞那么整个协程全部都得阻塞 , 并且 `IO` 是系统调用 , 这个不是用户态能处理的 , 协程无法绕开 - -以上就是 "纯协程" 了 , 所以综上 , 协程的性能是比不过线程的 , 所以遇到 `IO` 正确的操作应该是使用多线程 , 不会一堵全堵 , 但是线程的调度算法是比较僵硬的 , 时间片的算法无法准确地识别线程是否正在等待 `IO` , 从而造成了很多空等的 `CPU` 资源 , 所以我们应该使用像 `epoll` 这种异步回调的方式 , 让我们来看看异步回调的代码是怎么写的 : - -有3个 `IO` 操作按顺序执行 , 先执行 `select_data` , 耗时 `1` 秒 , 随后执行 `update_data` , 耗时 `0.5` 秒 , 最后再执行 `delete_data` , 耗时 `0.3` 秒 - -```python -import time - -# 3个 IO 操作顺序执行, 顺序如下: select_data, update_data, delete_data -# 功能函数 -def select_data(callback): - def callback_for_select(): - time.sleep(1) - result = 'select_data_result\n' - return callback(result) - # 模拟IO回调 - return callback_for_select() - - -def update_data(select_result, callback): - def callback_for_update(): - time.sleep(0.5) - result = select_result + 'update_data_result\n' - return callback(result) - # 模拟IO回调 - return callback_for_update() - - -def delete_data(update_result, callback): - def callback_for_delete(): - time.sleep(0.3) - result = update_result + 'delete_data_result' - return callback(result) - # 模拟IO回调 - return callback_for_delete() - -# 我们的调用代码 -def select_callback(select_result): - def update_callback(update_result): - def delete_callback(delete_result): - result = delete_result - return result - return delete_data(update_result, delete_callback) - return update_data(select_result, update_callback) - -result = select_data(select_callback) -print(result) - -# 运行结果 -""" -select_data_result -update_data_result -delete_data_result -""" -``` - -不难发现 , 异步回调实际上就是一组子例程协同工作的过程 , 只不过它的切换由我们注册的回调函数来控制 , 上面这段代码中 , 通过闭包来保存上下文 , 为了能让这段代码跑起来 , 我们这里就通过调用回调函数来模拟 `IO` 事件的回调 - -上面这段代码 , 如果我们使用同步的方式 , 会是这样的 : - -```python -# 功能函数 -def select_data(): - return 'select_data_result\n' - -def update_data(select_data_result): - return select_data_result + 'update_data_result\n' - -def delete_data(update_data_result): - return update_data_result + 'delete_data_result' - -# 我们的调用函数 -select_result = select_data() -update_result = update_data(select_result) -delete_result = delete_data(update_result) -``` - -异步和同步在代码的可读性上差别还是相当的大的 , 异步回调的代码实现相当的复杂 , 而且很容易遇到 `callback hell` , 而在上面我们已经知道了 , 子例程可以看作是特定的协程 , 任何子例程都可以转写为不调用 `yield` 的协程 , 如下 : - -```python -def select_data(): - # 模拟IO回调 - yield 'select_data_result\n' - -def update_data(select_result): - # 模拟IO回调 - yield select_result + 'update_data_result\n' - -def delete_data(update_result): - # 模拟IO回调 - yield update_result + 'delete_data_result' - -def main(): - select_result = next(select_data()) - update_result = next(update_data(select_result)) - delete_result = next(delete_data(update_result)) - return delete_result - -main() -``` - -这里简单的解释一下 , 因为代码无法体现出 `IO` 的异步回调 , 所以在异步回调的版本中通过 `callback_for_xx() ` 进行模拟 , 而这个 `yield` 的版本中就是通过 `yield` 进行模拟 , 另外不管是操作系统的切换(线程切换) , 还是我们自己控制的切换(协程切换) , 都是切换出当前的执行线让 `CPU` 去做别的事情 - -到这里我们直接对比 , 明显协程的方式的实现代码要比异步回调方式的实现代码可读性要高得多 , 没有了回调噩梦 - -这也是我们为什么要使用协程的原因 , 可以更好的和 `异步IO` 结合 , 如果用一句话概括的话 : **让原来要使用异步回调方式写的非人类代码 ,可以用看似同步的方式写出来** - -还有一点要说明的是 , 现在网络上的 "协程" 其实不只是 "协程" , 你在上面可以看到我有写过 "纯协程" ; 网络上的协程实际上是协程和一些组件的结合体 , 因为协程本质就是一组协同工作的程序 , 举个典型例子 , `IO` 阻塞就不是协程能处理的 , 而是协程 + `epoll` 的结果 , 而且有的协程库还融合了多线程来实现 - -## 使用协程 - -到这里 , 协程的概念已经讲完了 , 那么协程要怎么去使用呢 ? - -在 `Python 3.4` 引入了 `asyncio` 对异步 IO 的支持 , 而在 `Python 3.5` 引入了 `async/await` 两个关键字提供了对无栈协程(见后文)的支持 - -协程的使用有个很大的问题 , 那就是我们要如何去控制调度 , 有一个好的想法就是我们可以弄一个任务队列 , 然后再跑一个死循环 , 切换就把当前任务追到队列的尾部 , 再从头部取一个任务 , 直到所有任务完成 , 当然它还要你应该具备遇到时钟阻塞 , `IO` 切换的功能 , 它就是事件循环 - -我们先看看已有的 `asyncio` 怎么去编写异步代码 : - -```python -import asyncio - -# 功能函数 -async def select_data(): - await asyncio.sleep(1) - return 'select_data_result\n' - -async def update_data(select_data_result): - await asyncio.sleep(0.5) - return select_data_result + 'update_data_result\n' - -async def delete_data(update_data_result): - await asyncio.sleep(0.3) - return update_data_result + 'delete_data_result' - -# 调用函数 -async def main(): - select_result = await select_data() - update_result = await update_data(select_result) - delete_result = await delete_data(update_result) - return delete_result - -print(asyncio.get_event_loop().run_until_complete(main())) -``` - -`async` 用来定义一个协程 , `await` 则是用来切换上下文 , 最后利用 `asyncio.get_event_loop` 获取事件循环来完成我们的任务 - -## 事件循环 - - - -## 有栈协程与无栈协程 - diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/06-IO\345\244\232\350\267\257\345\244\215\347\224\250.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/06-IO\345\244\232\350\267\257\345\244\215\347\224\250.md" deleted file mode 100644 index a87b25bb1..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/06-IO\345\244\232\350\267\257\345\244\215\347\224\250.md" +++ /dev/null @@ -1,134 +0,0 @@ -# Attack on Python - IO多路复用 🐍 - - - - - - - - - - -## 前言 - -在网络编程中 , 如果服务端需要面临同时接收上千甚至上万次的客户端请求 , 利用 "进程池" 或 "线程池" 或许可以缓解部分压力 , 但是并不是一个好的选择 , 因为超过数量还是得等 ; 又或者线程一旦进行堵塞 ; 以及任务之间的高度独立 , 并不需要互相通信或者等待时 , 我们就需要用到I/O多路复用(IO Multiplexing) 了 , 又叫做事件驱动IO (Event driven IO) - -## I/O多路复用 - -> I/O多路复用是指单个线程中 , 通过记录跟踪每个I/O流(sock)的状态 , 来同时管理多个I/O流 - -在I/O多路复用中只要一遇到IO就注册一个事件 , 然后主程序就可以继续干其他的事情了 , 直到IO处理完毕 , 继续恢复之前中断的任务 , 也就是说**一个线程可以同时处理多个请求** - -举🌰 - -在UI编程中 , 常常要对鼠标点击进行响应 , 还要同时对键盘敲击也进行响应 - -**多进程多线程方式 : ** - -创建一个进程 , 进程中由两个线程 , 一个循环检测鼠标点击 , 一个循环检测键盘敲击 , 一旦检测到有情况就再开一个线程去处理 , 然后一直开下去......基本上是由创建进程/线程 , 维护进程/线程来解决的 , 这样对于CPU的资源是很浪费的 - -**IO多路复用(事件驱动) : ** - -创建一个事件(消息)队列 , 鼠标点击就往队列中增加一个鼠标点击事件 , 键盘敲击就往队列中增加一个键盘敲击事件 , 创建一个线程(IO线程)负责不断从队列中取出事件 , 根据不同的事件 , 调用不同的函数 , 如onClick() , onKeyDown()等 , 即一个线程解决了所有事件的问题 , 这就是复用 - -**比较 :** 与多进程多线程技术相比 , I/O多路复用最大的优势是系统开销小 , 系统不必创建进程/线程 , 也不必维护这些进程/线程 , 从而大大减小了系统的开销 - -目前常见支持I/O多路复用的系统调用select , poll , epoll ,I/O多路复用就是通过一种机制 , 一个进程可以监视多个描述符 , 一旦某个描述符就绪(一般是读就绪或者写就绪) , 能够通知程序进行相应的读写操作 - -而I/O多路复用的具体实现就是 , select , poll , epoll - -## Select - -select 监视的文件描述符(FD)分3类 , 分别是writefds、readfds和exceptfds , 程序启动后select函数会阻塞 , 直到有描述符就绪(有数据 可读、可写、或者有except) , 或者超时(timeout指定等待时间 , 如果立即返回设为null即可) , 函数返回 , 当select函数返回后 , 可以通过遍历fdset , 来找到就绪的描述符 - -I/O多路复用概念被提出来后 , select是第一个实现的 , select虽然实现了I/O多路复用 , 但是暴露出了很多问题 : - -- select 会修改传入的参数数组 , 这对于一个需要调用很多次的函数 , 是非常不友好的 - -- select 如果任何一个sokc(I/O stream) 出现了数据 , select仅仅会返回 , 但是并不会告诉你是哪个sock上有数据 , 于是你只能自己一个一个的找 , 十几个sock还好 , 但是数量一旦多了 , 这无谓的开销可就大了 - -- select 只能监视1024个链接 - -- select对socket进行扫描时是线性扫描 , 即采用轮询的方法 , 效率较低 - -- select 不是线程安全的 , 如果你把一个sock(I/O stream) 加入到select , 然后突然另外一个线程发现这个sock不用 , 需要收回 , 那么对不起 , select不支持 , 并且如果你想关掉这个sock , 那么select的标准行为是不可预测的 - - ``` - If a file descriptor being monitored by select() is closed in another thread , the result is unspecified - ``` - - Python实现select模型代码 - - ```python - import select - import socket - sk1 = socket.socket() - sk1.bind(('127.0.0.1', 8002, )) - sk1.listen() - demo_li = [sk1] - outputs = [] - message_dict = {} - while True: - r_list, w_list, e_list = select.select(sk1, outputs, [], 1) - print(len(demo_li),r_list) - for sk1_or_conn in r_list: - if sk1_or_conn == sk1: - conn, address = sk1_or_conn.accept() - demo_li.append(conn) - message_dict[conn] = [] - else: - try: - data_bytes = sk1_or_conn.recv(1024) - # data_str = str(data_bytes, encoding="utf-8") - # print(data_str) - # sk1_or_conn.sendall(bytes(data_str+"good", encoding="utf-8")) - except Exception as e: - demo_li.remove(sk1_or_conn) - else: - data_str = str(data_bytes, encoding="utf-8") - message_dict[sk1_or_conn].append(data_str) - outputs.append(sk1_or_conn) - for conn in w_list: - recv_str = message_dict[conn][0] - del message_dict[conn][0] - conn.sendall(bytes(recv_str+"Good", encoding="utf-8")) - outputs.remove(conn) - ``` - -## Poll - -poll本质上和select没有区别 , 它将用户传入的数组拷贝到内核空间 , 然后查询每个fd对应的设备状态 , 如果设备就绪则在设备等待队列中加入一项并继续遍历 , 如果遍历完所有fd后没有发现就绪设备 , 则挂起当前进程 , 直到设备就绪或者主动超时 , 被唤醒后它又要再次遍历fd , 这个过程经历了多次无谓的遍历 - -它没有最大连接数的限制 , 原因是它是基于链表来存储的 , 但是同样有缺点 : - -- 大量的fd的数组被整体复制于用户态和内核地址空间之间 , 而不管这样的复制是不是有意义 -- poll还有一个特点是"水平触发" , 如果报告了fd后 , 没有被处理 , 那么下次poll时会再次报告该fd -- 同样不是线程安全的 - -## Epoll 🍀 - -poll是在2.6内核中提出的 , 是之前的select和poll的增强版本 , 相对于select和poll来说 , epoll更加灵活 , 没有描述符限制 ; epoll使用一个文件描述符管理多个描述符 , 将用户关系的文件描述符的事件存放到内核的一个事件表中 , 这样在用户空间和内核空间的copy只需一次 - -**基本原理 : ** - -epoll支持水平触发和边缘触发 , 最大的特点在于边缘触发 , 它只告诉进程哪些fd刚刚变为就绪态 , 并且只会通知一次 ; 还有一个特点是 , epoll使用"事件"的就绪通知方式 , 通过epoll_ctl注册fd , 一旦该fd就绪 , 内核就会采用类似callback的回调机制来激活该fd , epoll_wait便可以收到通知 - -**epoll的优点 : ** - -- 没有最大并发连接的限制 , 能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口) -- 效率提升 , 不是轮询的方式 , 不会随着FD数目的增加效率下降 , 只有活跃可用的FD才会调用callback函数 ; 即Epoll最大的优点就在于它只管你"活跃"的连接 , 而跟连接总数无关 , 因此在实际的网络环境中 , Epoll的效率就会远远高于select和poll -- 内存拷贝 , 利用mmap()文件映射内存加速与内核空间的消息传递 ; 即epoll使用mmap减少复制开销 -- 是线程安全的 - -epoll对文件描述符的操作有两种模式 : LT(level trigger)和ET(edge trigger) , LT模式是默认模式 , LT模式与ET模式的区别如下 : - -**LT模式 : **当epoll_wait检测到描述符事件发生并将此事件通知应用程序 , 应用程序可以不立即处理该事件 , 下次调用epoll_wait时 , 会再次响应应用程序并通知此事件 - -**ET模式 : **当epoll_wait检测到描述符事件发生并将此事件通知应用程序 , 应用程序必须立即处理该事件 , 如果不处理 , 下次调用epoll_wait时 , 不会再次响应应用程序并通知此事件 - -**LT模式** LT(level triggered)是缺省的工作方式 , 并且同时支持block和no-block socket , 在这种做法中 , 内核告诉你一个文件描述符是否就绪了 , 然后你可以对这个就绪的fd进行IO操作 , 如果你不作任何操作 , 内核还是会继续通知你的 - -**ET模式** ET(edge-triggered)是高速工作方式 , 只支持no-block socket , 在这种模式下 , 当描述符从未就绪变为就绪时 , 内核通过epoll告诉你 , 然后它会假设你知道文件描述符已经就绪 , 并且不会再为那个文件描述符发送更多的就绪通知 , 直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如 , 你在发送 , 接收或者接收请求 , 或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误) , 但是请注意 , 如果一直不对这个fd作IO操作(从而导致它再次变成未就绪) , 内核不会发送更多的通知(only once) , ET模式在很大程度上减少了epoll事件被重复触发的次数 , 因此效率要比LT模式高 , epoll工作在ET模式的时候 , 必须使用非阻塞套接口 , 以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死 - -在select/poll中 , 进程只有在调用一定的方法后 , 内核才对所有监视的文件描述符进行扫描 , 而epoll事先通过epoll_ctl()来注册一个文件描述符 , 一旦基于某个文件描述符就绪时 , 内核会采用类似callback的回调机制 , 迅速激活这个文件描述符 , 当进程调用epoll_wait()时便得到通知 (此处去掉了遍历文件描述符 , 而是通过监听回调的的机制 , 这正是epoll的魅力所在) - diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/07-\345\256\236\347\216\260\347\272\277\347\250\213\346\261\240.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/07-\345\256\236\347\216\260\347\272\277\347\250\213\346\261\240.md" deleted file mode 100644 index 895697b77..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/07-\345\256\236\347\216\260\347\272\277\347\250\213\346\261\240.md" +++ /dev/null @@ -1,180 +0,0 @@ -# Attack on Python - 实现线程池 🐍 - - - - - - - - - - -## 方式一 - -```python -import Queue -import threading - - -class ThreadPool(object): - - def __init__(self, max_num=20): - self.queue = Queue.Queue(max_num) - for i in xrange(max_num): - self.queue.put(threading.Thread) - - def get_thread(self): - return self.queue.get() - - def add_thread(self): - self.queue.put(threading.Thread) - -""" -使用: -pool = ThreadPool(10) - -def func(arg, p): - import time - time.sleep(2) - p.add_thread() - - -for i in range(30): - thread = pool.get_thread() - t = thread(target=func, args=(i, pool)) - t.start() -""" -``` - -## 方式二 - -```python -import queue -import threading -import contextlib -import time - -StopEvent = object() - -class ThreadPool(object): - - def __init__(self, max_num, max_task_num = None): - if max_task_num: - self.q = queue.Queue(max_task_num) - else: - self.q = queue.Queue() - self.max_num = max_num - self.cancel = False - self.terminal = False - self.generate_list = [] - self.free_list = [] - - def run(self, func, args, callback=None): - """ - 线程池执行一个任务 - :param func: 任务函数 - :param args: 任务函数所需参数 - :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) - :return: 如果线程池已经终止,则返回True否则None - """ - if self.cancel: - return - if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: - self.generate_thread() - w = (func, args, callback,) - self.q.put(w) - - def generate_thread(self): - """ - 创建一个线程 - """ - t = threading.Thread(target=self.call) - t.start() - - def call(self): - """ - 循环去获取任务函数并执行任务函数 - """ - current_thread = threading.currentThread() - self.generate_list.append(current_thread) - - event = self.q.get() - while event != StopEvent: - - func, arguments, callback = event - try: - result = func(*arguments) - success = True - except Exception as e: - success = False - result = None - - if callback is not None: - try: - callback(success, result) - except Exception as e: - pass - - with self.worker_state(self.free_list, current_thread): - if self.terminal: - event = StopEvent - else: - event = self.q.get() - else: - - self.generate_list.remove(current_thread) - - def close(self): - """ - 执行完所有的任务后,所有线程停止 - """ - self.cancel = True - full_size = len(self.generate_list) - while full_size: - self.q.put(StopEvent) - full_size -= 1 - - def terminate(self): - """ - 无论是否还有任务,终止线程 - """ - self.terminal = True - - while self.generate_list: - self.q.put(StopEvent) - - self.q.queue.clear() - - @contextlib.contextmanager - def worker_state(self, state_list, worker_thread): - """ - 用于记录线程中正在等待的线程数 - """ - state_list.append(worker_thread) - try: - yield - finally: - state_list.remove(worker_thread) - -""" -pool = ThreadPool(5) - -def callback(status, result): - # status, execute action status - # result, execute action return value - pass - -def action(i): - print(i) - -for i in range(30): - ret = pool.run(action, (i,), callback) - -time.sleep(5) -print(len(pool.generate_list), len(pool.free_list)) -print(len(pool.generate_list), len(pool.free_list)) -pool.close() -pool.terminate() -""" -``` - diff --git "a/01-Python/06-\345\271\266\345\217\221\347\257\207/README.md" "b/01-Python/06-\345\271\266\345\217\221\347\257\207/README.md" deleted file mode 100644 index a44db52d9..000000000 --- "a/01-Python/06-\345\271\266\345\217\221\347\257\207/README.md" +++ /dev/null @@ -1,2 +0,0 @@ -# Attack on Python - 并发篇 🐍 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/01-\345\257\271\350\261\241\346\234\272\345\210\266.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/01-\345\257\271\350\261\241\346\234\272\345\210\266.md" deleted file mode 100644 index daf2633e8..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/01-\345\257\271\350\261\241\346\234\272\345\210\266.md" +++ /dev/null @@ -1,299 +0,0 @@ -# Attack on Python - 对象机制 🐍 - - - - - - - - - - -## 介绍 - -在Python中一切皆对象 - -我们知道Python是用C语言设计出来的 , 而在Python中 , 对象就是C中的结构体在堆上申请的一块内存 - -对象是不能被静态初始化的 , 并且也不能在栈空间上生存 ; 唯一列外的就是类型对象 , Python中所有的内建类型对象 (如整数类型对象 , 字符串类型对象) 都是被静态初始化的 - -在Python中 , 一个对象一旦被创建 , 那么它在内存中的大小就固定不变了 , 这就意味着对于那些可变长度的数据对象 (如列表) , 只能在对象内维护一个指向一块可变大小的内存区域的指针 - -利用这种对象机制可以使由指针维护对象的工作变得非常的简单 - -## 对象机制的基石 - -Python中一切皆对象 , 而所有的对象都拥有一些相同的内容 , 其被定义在`PyObject`中 - -我们先对比源码 , 从源码目录`Python-2.7\Include\object.h`中 , 截取如下片段 : - -```C -106:typedef struct _object { -107: PyObject_HEAD /*这个宏如下*/ -108:} PyObject; - - -77:/* PyObject_HEAD defines the initial segment of every PyObject. */ -78:#define PyObject_HEAD \ -79: _PyObject_HEAD_EXTRA \ -/* Py_ssize_t 是一个所占字节数与 size_t 相同的有符号的整数类型*/ -80: Py_ssize_t ob_refcnt; \ -81: struct _typeobject *ob_type; - - -65:/* Define pointers to support a doubly-linked list of all live heap objects. */ -66:#define _PyObject_HEAD_EXTRA \ -67: struct _object *_ob_next; \ -68: struct _object *_ob_prev; -``` - -从源码目录`Python-3.5.4\Include\object.h`中 , 截取如下片段 : - -```C -106:typedef struct _object { -107: _PyObject_HEAD_EXTRA /* 与2.7相比没有发生任何实质性变化 */ -108: Py_ssize_t ob_refcnt; -109: struct _typeobject *ob_type; -110:} PyObject; - - -82:/* PyObject_HEAD defines the initial segment of every PyObject. */ -83:#define PyObject_HEAD PyObject ob_base; - - -70:/* Define pointers to support a doubly-linked list of all live heap objects. */ -71:#define _PyObject_HEAD_EXTRA \ -72: struct _object *_ob_next; \ -73: struct _object *_ob_prev; - -75:#define _PyObject_EXTRA_INIT 0, 0, - -78:#else -79:#define _PyObject_HEAD_EXTRA -``` - - 两个版本源码并没有什么真正意义上的改变 , 从中我们可以看出 , `PyObject`主要由`ob_refcnt` , `ob_type` , `_PyObject_HEAD_EXTRA` 几个部分组成 , 而对于`_PyObject_HEAD_EXTRA` , 我们发现它只有在DEBUG模式下才不为空 , 所以我们可以将其忽略 - - -### ob_refcnt - -`ob_refcnt` 是内存管理机制的核心 , 它实现了基于`引用计数`的垃圾回收机制 , 例如 : - -对于某一个对象A , 当有一个新的`PyObject *` (对象指针) 引用该对象时 , A的引用计数 (ob_refcnt) 就会增加 ; 而当这个`PyObject *` 被删除时 , A的引用计数就会减少 , 并且当A的引用计数减少到0时 , A就可以从堆上被删除 , 以释放出内存供别的对象使用 - -`ob_refcnt`是一个32位的整型变量 , 这实际蕴含着Python所做的一个假设 , 即对一个对象的引用不会超过一个整型变量的最大值 , 这个假设如果不是恶意代码的话 , 明显是成立的 - -### ob_type - -`ob_type`是对象类型的核心 , 源码中我们可以看到 , 它是一个指向`_typeobject`的结构体的指针 , 该结构体对应的是一种特殊的对象 , 它是用来指定一个对象类型的类型对象 , 也就是说`ob_type`所指向的位置存放着一个对象的类型信息 - -Python就是利用`ob_type`构造了对象类型的基石 - -`PyObject`中定义了所有Python对象中都必须有的内容 , 即`ob_refcnt`和`ob_type` , 当然一个对象中肯定不止于这些 , 不同的对象中还保存了各自的特殊信息 , 于是才实现了各种基础数据类型 - - -## 定长对象和变长对象 - -### 定长对象 - -我们把不包含可变长度数据的对象称为 "定长对象" , 并且定长对象在内存中所占的大小是一样的 , 比如我们的整数对象 , 内存中 1 和 100占用的内存大小都是`sizeof(PyIntObject)` - -你可能会将定长对象理解为 "不可变对象" , 但是实际上并不是这样 , 因为像Python的字符串 , 元组这两者都是 "不可变对象" , 但是他们却是 "变长对象" , 我们通过源码来看看Python中的整数对象 : - -目录`Python-2.7\Include\intobject.h`中 , 截取如下片段 : - -```C -23:typedef struct { -24: PyObject_HEAD /*PyObject对象宏 */ -25: long ob_ival; /*PyIntObject的特殊信息*/ -26:} PyIntObject; -``` - -如上 , 也就是说在Python 2.x中 , 整数对象都是定长对象 , 因为`PyIntObject`结构体中没有任何多余的内容 , 但是别忘了数字还有`Long`类型 , 而`Long`则是变长对象 - -源码如下 : - -`Python-2.7\Include\longintrepr.h`中 , 截取如下片段 : - -```C -90:struct _longobject { -91: PyObject_VAR_HEAD /*变长对象基石*/ -92: digit ob_digit[1]; -93:}; -``` - -注意 : 在Python 3.x中 , `Long`类型和`Int`类型合并到一起去了 , 我们在3.x中所看到的`Int`类型 , 实际上是`Long` 类型 , 关于数字类型将会在下一篇中整理 - -Python 3.x中这部分源码也在`logintrepr.h`中 , 分别在第89 - 92行 - -### 变长对象 - -上面已经说明了定长对象 , 变长对象则就是包含可变长度数据的对象 - -定长对象与变长对象的区别在于 : 定长对象占用的内存大小是一样的 , 而变长对象占用的大小不一样 , 实例如下 : - -```python ->>> a = 1 ->>> type(a) - ->>> a.__sizeof__() -24 ->>> b = 100 ->>> type(b) - ->>> b.__sizeof__() -24 -``` - -注意 : 字符串是变长对象 , Python2.7中源码如下 : - -```C -// Python2.7\Include\stringobject.h - -35:typedef struct { -36: PyObject_VAR_HEAD /*变长对象基石*/ -37: long ob_shash; -38: int ob_sstate; -39: char ob_sval[1]; - /* 省略注释 */ -49:} PyStringObject; -``` - -实例说明 - -```python -# env : Python 2.x ->>> a = "lyon" ->>> b = "lyonyang" ->>> a.__sizeof__() -37 ->>> b.__sizeof__() -41 -``` - - -### PyVarObject - -`PyVarObject`就是Python中变长对象的基石 , 上面的`PyStringObject`中我们已经见过了, 那么继续翻源码 : - -`Python-2.7\Include\object.h : ` - -```C -110:typedef struct { -111: PyObject_VAR_HEAD -112:} PyVarObject; - -/* PyObject_VAR_HEAD defines the initial segment of all variable-size - * container objects. These end with a declaration of an array with 1 - * element, but enough space is malloc'ed so that the array actually - * has room for ob_size elements. Note that ob_size is an element count, - * not necessarily a byte count. - */ -96:#define PyObject_VAR_HEAD \ -97: PyObject_HEAD \ -98: Py_ssize_t ob_size; /* Number of items in variable part */ -``` - -`Python-3.5.4\Include\object.h : ` - -```C -112:typedef struct { -113: PyObject ob_base; /* 等价于PyObject_HEAD */ -114: Py_ssize_t ob_size; /* Number of items in variable part */ -115:} PyVarObject; -``` - -版本2.7 与 3.5.4无变化 , 我们可以看出 , PyVarObject其实就是在PyObject上的一个扩展而已 , 而这个扩展就是在PyVarObject中多出了一个`ob_size`变量 , 这是一个整型变量 , 该变量记录的是变长对象中一共容纳了多少个元素 - -注意 : 变长对象通常都是容器 , 并且`ob_size`指明的是所容纳元素的个数 , 而不是字节的数量 , 比如一个列表中有5个元素 , 那么`ob_size`的值就是5 - -所以对于判断Python底层实现的对象是否是变长对象 , 只需查看其定义中是否具有`ob_size`属性 - - -## 类型对象 - -上面已经提到过了在`PyObject`中有一个`ob_type`指针 , 它指向对象的类型信息 , 这样在分配内存空间时 , 就可以根据`ob_type`所指向的信息来决定对象申请多大的空间 - -`ob_type`指向结构体`_typeobject` , 如下 : - -`Python-2.7\Include\object.h :` - -```C -324:typedef struct _typeobject { -325: PyObject_VAR_HEAD -326: const char *tp_name; /* For printing, in format "." */ -327: Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ -329: /* Methods to implement standard operations */ - ... -338: /* Method suites for standard classes */ - ... -344: /* More standard operations (here for binary compatibility) */ - ... -411:} PyTypeObject; -``` - -`Python-3.5.4\Include\object.h : ` - -```C -343:typedef struct _typeobject { -344: PyObject_VAR_HEAD -345: const char *tp_name; /* For printing, in format "." */ -346: Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ - -348: /* Methods to implement standard operations */ - ... -358: /* Method suites for standard classes */ - ... -364: /* More standard operations (here for binary compatibility) */ - -432:} PyTypeObject; -``` - -同样 , 在版本2.7 与 3.5.4之间不能存在差异 - -我们可以将该结构体主要分为4个部分 : - -1. 类型名 , 即`tp_name` , 主要是Python内部以及调试的时候使用 -2. 创建该类型对象时分配内存空间大小的信息 , 即 `tp_basicsize` , `tp_itemsize` -3. 与该类型对象相关联的操作信息 , 可以通过源码进行详查 -4. 类型的类型信息 - -由于在PyObject的定义中包含了PyTypeObject , 我们可以认为PyObject对象是继承了PyTypeObject对象 , 而PyTypeObject则是最原始的抽象 - -因为在实际的Python中确实如此 : object类 (即PyObject) 的基类就是type类 (即PyTypeObject) - -我们用Python简单描述 : - -```python ->>> isinstance(object, type) -True -``` - -并且由于Python对外提供了C API , 以及Python本身就是用C写成的 , 所以Python内部也大量使用了这些API - -Python中的API分为两种 : - -1. 范型API , 或者称为AOL (Abstract Object Layer) , 这类API都具有诸如Pyobject_***的形式 , 可以应用于任何Python对象上 -2. 类型相关API , 或者称为COL (Concrete Object Layer) , 这类API通常只能作用在某一种类型的对象上 , 对于Python内建对象 , 都提供了这样一组API , 诸如PyInt_Type - -所以对于Python中的内建类型对象 , 可以利用以上两种API进行创建 : - -1. 范型API : `PyObject *intobj = PyObject_New(PyObject, &PyInt_Type)` -2. 类型API : `PyObject *intobj = PyInt_FromLong(10)` - -注意 : 我们经常所见到的``中的 `int` 代表的就是Python内部的`PyInt_Type` - -**总结 :** - -通过这一篇文章我们已经理清了Python对象机制中的核心定义 - -以下从上往下依次扩展 - -PyTypeObject - - 类型对象基石 - -PyObject - - 对象基石 - -PyVarObject - - 变长对象基石 - - - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/02-\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/02-\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272.md" deleted file mode 100644 index 33f3b0d5c..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/02-\345\257\271\350\261\241\347\232\204\345\210\233\345\273\272.md" +++ /dev/null @@ -1,154 +0,0 @@ -# Attack on Python - 对象的创建 🐍 - - - - - - - - - - -## 介绍 - -上一篇关于Python中对象实现中我们知道 , 创建一个对象Python提供了两种API , 即范型API和类型API - -而在对象真正创建时 , Python为我们使用的是类型API - -因为如果使用范型API , 那么意味着Python要提前为我们准备好`PyObject_New` 这一系列的API , 对于创建内置类型的对象这并没有问题 , 但是如果对于创建用户自定义的类型这样就非常的不明智了 , 因为需要提前创建好诸多的`_New`对象 - -## 创建对象 - -我们定义一个类 , 通过这个自定义类来说明Python对象的创建流程 - -```python -# Python对象的基石,即PyObject -class object(): - pass - -# 自定义类 -class MyObject(object): - pass -``` - -**创建object对象** - -在分析自定义类型的对象创建之前 , 我们需要分析一下object对象是如何创建的 , 虽然我们在实际中是不会也不需要去创建object对象的 , 但是这有利于我们下一步的分析 : - -![创建object对象](https://github.com/lyonyang/blogs/blob/master/assets/%E5%88%9B%E5%BB%BAobject%E5%AF%B9%E8%B1%A1.png?raw=true) - -object对象的创建 : 如上图 , 创建object对象首先调用类型API (PyBaseObject_Type) , 并且会首先调用API中的`tp_new` , 因为这里是创建object , 所以`tp_new`中不会为NULL - -**创建自定义对象** - -无论是Python 2.x还是3.x , Python中所有的类都是以`object`类为基础的 , 也就是说所有的类都继承了`object`类 , 所以自定义类型对象的创建流程如下 : - -![创建myobject对象](https://github.com/lyonyang/blogs/blob/master/assets/%E5%88%9B%E5%BB%BAmyobject%E5%AF%B9%E8%B1%A1.png?raw=true) - -无论是自定义对象的创建还是object对象的创建 , 其创建对象的流程都是一样的 : - -1. 首先都会调用其类型API中的`tp_new` , 如果我们自定义类型中`tp_new`为NULL , 那么它将通过`tp_base`指定的基类继续去寻找`tp_new` , 直到找到`tp_new`为止 , 不要担心会找不到 , Python中所有的类都继承了object类 , 而object类中是一定有`tp_new`的 -2. 在找到`tp_new`之后会回到原点拿取`tp_basicsize` , 这里面记录了该对象应该占用内存大小的信息 , 拿取后申请内存完成创建 , 返回一个新对象 -3. 拿到新对象我们对新对象进行初始化 - -通过这三大步 , 一个对象的创建基本就完成了 - -站在Python的角度来看 , `tp_new`对应的就是特殊操作符中的`__new__`方法 , 此方法返回一个对象实例 , `tp_init ` 对应的就是特殊操作符中的`__init__`方法 , 当我们创建一个类时一般都会对`__init__`方法进行重载以达到我们的目标 - -当然`PyBaseObject_Type`并不是类型对象的终点 , 在其之上还存在着一个`PyType_Type` - -更多关于类型对象的信息详见上一篇 , 其中定义了对象的行为 - - -## 类型的类型 - -我们知道PyObject中有一个 `ob_type`指针 , 记录着PyObject的类型信息 , 但是这个结构体也是一个对象 , 就是上一篇中所说的类型对象PyTypeObject - -既然是对象 , 那么就肯定有类型 , 而这个类型就是PyType_Type - -`Python-2.7\Objects\typeobject.c` - -```C -2730:PyTypeObject PyType_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "type", /* tp_name */ - sizeof(PyHeapTypeObject), /* tp_basicsize */ - sizeof(PyMemberDef), /* tp_itemsize */ - (destructor)type_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc)type_repr, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)_Py_HashPointer, /* tp_hash */ - (ternaryfunc)type_call, /* tp_call */ - 0, /* tp_str */ - (getattrofunc)type_getattro, /* tp_getattro */ - (setattrofunc)type_setattro, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS, /* tp_flags */ - type_doc, /* tp_doc */ - (traverseproc)type_traverse, /* tp_traverse */ - (inquiry)type_clear, /* tp_clear */ - type_richcompare, /* tp_richcompare */ - offsetof(PyTypeObject, tp_weaklist), /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - type_methods, /* tp_methods */ - type_members, /* tp_members */ - type_getsets, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - offsetof(PyTypeObject, tp_dict), /* tp_dictoffset */ - type_init, /* tp_init */ - 0, /* tp_alloc */ - type_new, /* tp_new */ - PyObject_GC_Del, /* tp_free */ - (inquiry)type_is_gc, /* tp_is_gc */ -2772:}; -``` - -在Python 3.5.4中内容是一样就不列出了 , 行数3328-3369 - -所有的对象中的类型对象都是由PyType_Type对象进行创建的 , 包括PyObject , 如下 : - -```python ->>> object.__class__ - ->>> int.__class__ - ->>> class A(object): -... pass -... ->>> A.__class__ - ->>> type.__class__ - ->>> -``` - -通过这一实验 , 我们可以知道其实所有类的祖宗实际上是`type` , 也就是`PyType_Type` , 所以它在Python中被称为 metaclass(元类) - -我们发现就算是`type`类竟然也是由`type` (PyType_Type)产生的 , 就像在type类中成了一个 "圈一样" , 自己引用自己 , 事实上确实是这样 , 同样以上一小节的例子进行说明 , 如下图 : - -![object_type_relation](https://github.com/lyonyang/blogs/blob/master/assets/object_type_relation.png?raw=true) - -也就是说PyType_Type中的`ob_type`指针最终指向了自己本身 - -这些基本上就是Python对象的创建流程了 , 但是注意对于Python内部的类型 , 创建时可能存在一些差异 , 但是这些差异并不会影响我们分析的结果 - -**总结 :** - -这一篇主要整理了对象创建的流程 , 以及对类型对象的整理 - -1. `tp_new`对应到C++中 , 可以视为`new`操作符 , Python中则是`__new__`操作符 -2. `tp_init`则是Python中的`__init__` 也就是类的构造函数 , 功能就是对创建的新对象进行初始化 -3. Python中一切皆对象 , 类型也是对象 ; 对象必然具有类型 , PyType_Type是类型对象的创造者 -4. PyType_Type的类型就是其本身 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/03-\346\225\264\346\225\260\345\257\271\350\261\241.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/03-\346\225\264\346\225\260\345\257\271\350\261\241.md" deleted file mode 100644 index 0f80190cf..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/03-\346\225\264\346\225\260\345\257\271\350\261\241.md" +++ /dev/null @@ -1,319 +0,0 @@ -# Attack on Python - 整数对象 🐍 - - - - - - - - - - -## 介绍 - -在Python的应用程序中 , 整数的使用非常地广泛 - -这就意味着整数对象的创建和销毁肯定是非常的频繁的 , 并且我们知道Python中采用了引用计数机制 , 即一个整数类型的变量`ob_refcnt` , 这样Python中对于整数对象的创建和销毁会更加的疯狂 , 这样的执行效率明显我们是无法接受的 , 更何况Python已经背负了人们对其执行效率的不满 , 所以Python中大量采用了内存对象池的技术 - -整数对象必然也使用了内存对象池技术 , 也就是整数对象池 , 当然我们应该从整数对象的创建开始说起 , 以及Python 2.x中与Python 3.x两个版本之间的差异 - -## 整数类型 - -**Python 2.x中的整数类型** - -在Python 2.x中有两种整数类型 , 一种是`int` 也就是我们通常说的整型 , 另一种是`long`也就是长整型 , 根据两种对象的源码 , 我们可以知道 , `int` (PyIntObject) 属于定长对象 , 而`long` (PyLongObject) 属于变长对象 - -对于`int` , 当其进行运算时 , 如果值溢出 , 那么Python将会将值自动转为`long`类型 , 如下 : - -```python -# python 2.x ->>> n = 2147483647 ->>> type(n) - -# 加法溢出 ->>> n = n + 1 ->>> n -2147483648L ->>> type(n) - ->>> n = -2147483647 ->>> type(n) - -# 减法溢出 ->>> n = n - 2 ->>> n --2147483649L ->>> type(n) - -``` - -但是`long`就不会出现这种溢出情况了 , 因为`long`是一个变长对象 , 当空间不够存放这个数字值 , 加空间就是了 , 无非是从1Byte 到2 Byte的过程 , 以此类推 - -**Python 3.x中的整数类型** - -在Python 3.x中 , 只有`long`了 , 我们所见到的`int`实际上就是`long` , 根据源码的注释所说 , 大概意思就是对于未来而言 , `long`比`int`好 , 并且在Python 3.x的[官方文档](https://docs.python.org/3.5/c-api/long.html)中 , 第一句就说明了 : - -``` -All integers are implemented as “long” integer objects of arbitrary size. -``` - -还有一点值得注意的就是 , 在3.x的源码中 , 已经没有`intobject.h`这个文件了 , 而只有`longobject.h` , 我们可以在`Python-3.5.4\Objects\longobject.c`中看到`long`的类型信息 : - -```C -5179:PyTypeObject PyLong_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "int", /* tp_name */ - offsetof(PyLongObject, ob_digit), /* tp_basicsize */ - sizeof(digit), /* tp_itemsize */ - long_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_reserved */ - long_to_decimal_string, /* tp_repr */ - &long_as_number, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)long_hash, /* tp_hash */ - 0, /* tp_call */ - long_to_decimal_string, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */ - long_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - long_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - long_methods, /* tp_methods */ - 0, /* tp_members */ - long_getset, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - long_new, /* tp_new */ - PyObject_Del, /* tp_free */ -5220:}; -``` - -注意 : 在此文件中还有一个`long_as_number` 域 , 其中定义了一个对象作为数值对象时所有可选的操作 , 其中2.7中一共有39个函数指针 , 3.5.2中一共有34个函数指针 , 每一个函数指针都代表着一种可选的操作 , 包括加法 , 减法 , 乘法 , 模运算等等 ; 具体行数见`5142-5176` - -**创建方式** - -对于整数对象的创建 , 其途径都定义在`intobject.c`或者`longobject.c`中 , 方式都不止一种 , 例如创建`int`就有以下3种方式 : - -1. 从long值创建 , `PyInt_FromLong(long ival)` -2. 从Py_UNICODE对象生成 , `PyInt_FromUnicode(Py_UNICODE *s, int length, int base)` -3. 从字符串生成 , `PyInt_FromString(char *s, char **pend, int base)` - -而对于创建`long`方法就更多了 , 这些创建方法都定义在`Python\Objects\`目录下对应的`.c`文件中 - - -## 小整数对象池 - -在实际编程中 , 数值比较小的整数 , 比如 1, 2, 29等 , 可能在程序中会非常频繁地使用 ; 在Python中 , 所有的对象都存货在系统堆上 , 也就是说 , 如果没有特殊的机制 , 对于这些频繁使用的小整数对象 , Python将一次又一次使用malloc在堆上申请空间 , 并且不厌其烦地一次次free释放空间 , 这样的操作会严重影响Python的整体性能 - -所以Python中对于小整数对象使用了**对象池技术** , 也就是Python会直接将小整数对象缓存在内存中 , 并将其指针存放在`small_ints`中 , 这个小整数集合的范围无论是在Python 2.x 还是在Python 3.x , 其范围都设定在[-5, 257) , 源码如下 : - -`Python-2.7\Objects\intobject.c` - -```C -67:#ifndef NSMALLPOSINTS -68:#define NSMALLPOSINTS 257 -69:#endif -70:#ifndef NSMALLNEGINTS -71:#define NSMALLNEGINTS 5 -72:#endif -73:#if NSMALLNEGINTS + NSMALLPOSINTS > 0 -/* References to small integers are saved in this array so that they - can be shared. - The integers that are saved are those in the range - -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). -*/ -79:static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; -``` - -`Python-3.5.4\Objects\longobject.c` - -```C -12:#ifndef NSMALLPOSINTS -13:#define NSMALLPOSINTS 257 -14:#endif -15:#ifndef NSMALLNEGINTS -16:#define NSMALLNEGINTS 5 -17:#endif - -25:#if NSMALLNEGINTS + NSMALLPOSINTS > 0 -/* Small integers are preallocated in this array so that they - can be shared. - The integers that are preallocated are those in the range - -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). -*/ -31:static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; -``` - -**小整数池测试** - -```python -# Python 2.7 ->>> a = 1 ->>> id(a) -87319208L ->>> b = 1 ->>> id(b) -87319208L - -# Python 3.5.3 ->>> a = 1 ->>> id(a) -1852703184 ->>> b = 1 ->>> id(b) -1852703184 -``` - -超出小整数集合的整数对象 , 内存地址就不一样了 , 这一点可以自己尝试 - -对于小整数集合的范围我们是可以修改的 , 但是修改的方法非常原始 , 那就是修改Python的源码然后重新编译 - -注意 : **小整数对象池**中完全地缓存其对象 , 也就是说在执行我们的程序之前**小整数对象池**就已经激活 - - -## 通用整数对象池 - -小整数对象池解决了小整数频繁的使用问题 , 但是我们并不能保证大整数就不会被频繁的使用 , 所以对于这些整数 , Python运行环境将提供一块内存空间 , 供这些大整数轮流使用 , 结构体如下 : - -`Python-2.7\Objects\intobject.c` - -```C -33:#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */ -34:#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */ -35:#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject)) - -37:struct _intblock { -38: struct _intblock *next; -39: PyIntObject objects[N_INTOBJECTS]; -40:}; - -42:typedef struct _intblock PyIntBlock; - -44:static PyIntBlock *block_list = NULL; -45:static PyIntObject *free_list = NULL; -``` - -在上述结构体中 , `N_INTOBJECTS`表示所维护的对象的个数 , 在32位的系统上 , 一个`int`类型所需要的内存为12bytes , 所以可以计算出这个值应该是82 , 这一个值我们也可以通过修改源码进行修改 - -而`PyIntBlock`的单向列表通过`block_list`维护 , 每一个block中都维护了一个`PyIntObject`数组 , 这就是真正用于存储被缓存的`PyIntObject`对象的内存 , 而对于这个内存中的空闲内存则是由单向链表`free_list`进行管理 ; 最开始时这两个指针都指向一个空值 (NULL) - -在Python 3.5.4中 , 我没有找到如同2.7一样的源码 , 但是我们可以通过两个版本的实验发现 , 通用对象池机制是一样的 : - -```python -# Python 2.x ->>> id(257),id(258),id(259) -(81956248L, 81956224L, 81956200L) ->>> n = 258 ->>> id(n) -81956248L - -# Python 3.x ->>> id(257),id(258),id(259) -(1910529789904, 1910534766192, 1910534766096) ->>> n = 258 ->>> id(n) -1910529789904 -``` - -在进行实验时 , 走了很多弯路 , 有兴趣的话可以自己尝试 , 下面是上面实验的结果总结 : - -1. 申请完内存之后 , Python解释器就再也不会返回内存给操作系统了 , 就算对象被销毁 -2. 创建大整数对象时 , 会到堆里面找最近的那一块空内存 , 注意堆里面存储数据是由高到低进行存储的 -3. 也就是说 , 通用整数对象池机制所做的优化就是 , **解决了内存的频繁开辟问题** - -注意 : 如果第一块空间满了 , 那么就会往第二块进行存储 ; - - -## 添加和删除 - -通过使用`PyInt_FromLong` API为例 , 创建一个整数对象的过程如下 : - -`Python-2.7\Objects\intobject.c` - -```C - 87:PyInt_FromLong(long ival) - 88:{ - 89: register PyIntObject *v; - 90:#if NSMALLNEGINTS + NSMALLPOSINTS > 0 - /* 尝试使用小整数对象池 */ - - 91: if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { - 92: v = small_ints[ival + NSMALLNEGINTS]; - 93: Py_INCREF(v); - 94:#ifdef COUNT_ALLOCS - 95: if (ival >= 0) - 96: quick_int_allocs++; - 97: else - 98: quick_neg_int_allocs++; - 99:#endif -100: return (PyObject *) v; -101: } -102:#endif - /* 为通用整数对象池申请新的内存空间 */ - -103: if (free_list == NULL) { -104: if ((free_list = fill_free_list()) == NULL) -105: return NULL; -106: } -107: /* Inline PyObject_New */ -108: v = free_list; -109: free_list = (PyIntObject *)Py_TYPE(v); -110: PyObject_INIT(v, &PyInt_Type); -111: v->ob_ival = ival; -112: return (PyObject *) v; -113:} -``` - -`Python-3.5.4\Objects\longobject.c` 中`25行至296行` 可以查看到关于Python 3中的一些处理 - -```C -37:get_small_int(sdigit ival) - { - PyObject *v; - assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS); - v = (PyObject *)&small_ints[ival + NSMALLNEGINTS]; - Py_INCREF(v); - #ifdef COUNT_ALLOCS - if (ival >= 0) - quick_int_allocs++; - else - quick_neg_int_allocs++; - #endif - return v; -50:} -51:#define CHECK_SMALL_INT(ival) \ - do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \ - return get_small_int((sdigit)ival); \ -54: } while(0) - -231:PyLong_FromLong(long ival) - { - ...... -239: CHECK_SMALL_INT(ival); - ...... -296:} -``` - -也就是说整数对象的创建会通过两步来完成 : - -1. 如果小整数对象池机制被激活 (默认就已激活) , 则尝试使用小整数对象池 -2. 如果不能使用小整数对象池 , 则使用通用的整数对象池 - -对于整数对象的实现大概核心就是这些东西了 , 关于通用对象池的创建 , 可以通过源码或者 , 《Python源码剖析》一书进行探索 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/04-\345\255\227\347\254\246\344\270\262\345\257\271\350\261\241.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/04-\345\255\227\347\254\246\344\270\262\345\257\271\350\261\241.md" deleted file mode 100644 index 3c3d984e2..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/04-\345\255\227\347\254\246\344\270\262\345\257\271\350\261\241.md" +++ /dev/null @@ -1,619 +0,0 @@ -# Attack on Python - 字符串对象 🐍 - - - - - - - - - - -## 介绍 - -在前面有提到过 "定长对象" 和 "变长对象" , 这是一种对对象的二分法 - -当然不止这一种 , 还有一种就是 "可变对象(mutable)" 和 "不可变对象(immutable)" , 这种二分法是根据对象维护数据的可变性来进行区分的 , 在Python的官方文档中也是有说到的 - -可变对象维护的数据在对象被创建后还能再变化 , 比如一个`list`被创建后 , 可以向其中添加元素或删除元素 , 这些操作都会改变其维护的数据 ; 而不可变对象所维护的数据在对象创建之后就不能再改变了 , 比如Python中的`string`和`tuple` , 他们都不支持添加或删除的操作 - -**Python 2.x 与 Python 3.x** - -```python -# Python 2.7 ->>> name = 'lyon' ->>> type(name) - ->>> name.decode('utf-8') -u'lyon' ->>> uname = u'lyon' ->>> type(uname) - - -# Python 3.5.4 ->>> name = 'lyon' ->>> type(name) - ->>> name.decode('utf-8') -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'str' object has no attribute 'decode' ->>> uname = u'lyon' ->>> type(uname) - -``` - -在进行对比两种版本的差异前 , 我们需要知道在它们中有哪些字符串类型 : - -- Python 3.x中 , 有3种字符串类型 : - - `str` , 表示`Unicode`文本 (8位的和更宽的) - - `bytes` , 表示二进制数据 - - `bytearray` , 是bytes的一种可变的变体 -- Python 2.x中 , 有2中字符串类型 : - - `str` , 表示8位文本和二进制数据 - - `unicode` , 表示宽字符`Unicode`文本 - -虽然在2中没有`bytesarray` , 但是在Python 2.6 及之后的版本都可以使用`bytesarray` - -**总体差异 :** - -在Python 2.x 与 Python 3.x中 , 字符串的实现主要体现在 , Python 3.x中将Python 2.x中常规的`str`和`Unicode`字符串整合到了一个单独的类型`str`中 , 以支持常规的和`Unicode`文本 ; 这样的处理使得Python在编码处理方面更加的方便 - -接下来就来分析Python中的字符串对象了 - - -## PyStringObject - -在Python中 , `PyStringObject`是对字符串对象的实现 , `PyStringObject` 是一个拥有可变长度内存的对象 , 比如 : `"Lyon"` 和 `"KennethReitz"` 这两个字符串对象所需要的内存空间明显是不一样的 - -同时 , `PyStringObject` 对象又是一个不可变对象 , 即当创建了一个`PyStringObject`对象之后 , 该对象内部维护的字符串就不能再被改变了 , 这一点特性使得`PyStringObject`对象可以作为`dict`的键 , 但是同时也使得一些字符串的操作效率大大降低 , 比如多个字符串的连接操作 - -`PyStringObject`对象的定义如下 : - -`Python-2.7\Include\stringobject.h :` - -```C -35:typedef struct { -36: PyObject_VAR_HEAD /* 在前面的篇章已经介绍过了,变长对象宏 */ -37: long ob_shash; -38: int ob_sstate; -39: char ob_sval[1]; - -41: /* Invariants: -42: * ob_sval contains space for 'ob_size+1' elements. -43: * ob_sval[ob_size] == 0. -44: * ob_shash is the hash of the string or -1 if not computed yet. -45: * ob_sstate != 0 iff the string object is in stringobject.c's -46: * 'interned' dictionary; in this case the two references -47: * from 'interned' to this object are *not counted* in ob_refcnt. -48: */ -49:} PyStringObject; -``` - -定义说明 : - -1. `PyObject_VAR_HEAD`中有一个`ob_size`变量保存着对象中维护的可变长度内存的大小 - -2. `ob_shash`变量的作用是缓存该对象的hash值 , 这样可以避免每一次都重新计算该字符串对象的hash值 , 如果一个`PyStringObject`对象还没有被计算过hash值 , 那么`ob_shash`的初始值是`-1` - - 这个hash值在后期`dict`类型中发挥了巨大的作用 - -3. `ob_sstate`变量标记了该对象是否已经过`intern`机制的处理 , `intern`机制见下文 , 预存的字符串的hash值与`intern`机制将Python虚拟机的执行效率提升了20% - -4. `ob_sval`在定义中虽然是一个字符的字符数组 , 但是`ob_sval`实际上是作为一个字符指针指向一段内存的 , 这段内存保存着这个字符串对象所维护的实际字符串 , 而这段内存的实际长度(字节) , 正式通过`ob_size`来维护的 , 这就是变长对象的实现机制 , 比如一个字符串对象 "Lyon" , `ob_size`的值就是4 - -在Python 3.x中 , 遗留的字符串定义在`unicodeobject.h`中 , 不另行说明了 - - -## PyString_Type - -如下是`PyStringObject`的类型对象的定义 : - -`Python-2.7\Objects\stringobject.c :` - -```C - -3800:PyTypeObject PyString_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - "str", - PyStringObject_SIZE, - sizeof(char), - ...... - string_repr, /* tp_repr */ - &string_as_number, /* tp_as_number */ - &string_as_sequence, /* tp_as_sequence */ - &string_as_mapping, /* tp_as_mapping */ - (hashfunc)string_hash, /* tp_hash */ - 0, /* tp_call */ - ...... - &PyBaseString_Type, /* tp_base */ - ...... - string_new, /* tp_new */ - PyObject_Del, /* tp_free */ -3842:}; -``` - -对于类型对象就无需多说了 , 在前面的篇章也已经介绍过了 , 这里值得注意的是 , `tp_itemsize`和`ob_size`共同决定了应该额外申请的内存之总大小是多少 , `tp_itemsize`指明了由变长对象保存的元素的单位长度 , 这里就是单个字符在内存中的长度 - -`tp_as_number` , `tp_as_sequence` , `tp_as_mapping` 三个域都被设置了 , 表示`PyStringObject`对数值操作 , 序列操作和映射操作都支持 - - -## 创建PyStringObject对象 - -Python 2.7 提供了两个接口 : `PyString_FromString` 和 `PyString_FromStringAndSize` - -`Python-2.7\Objects\stringobject.c :` - -**PyString_FromString** - -```C -119:PyString_FromString(const char *str) - { - register size_t size; - register PyStringObject *op; - // 判断字符串长度 - assert(str != NULL); - size = strlen(str); - if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { - PyErr_SetString(PyExc_OverflowError, - "string is too long for a Python string"); - return NULL; - } - - // 处理null string - if (size == 0 && (op = nullstring) != NULL) { - #ifdef COUNT_ALLOCS - null_strings++; - #endif - Py_INCREF(op); - return (PyObject *)op; - } - - // 处理字符 - if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) { - #ifdef COUNT_ALLOCS - one_strings++; - #endif - Py_INCREF(op); - return (PyObject *)op; - } - - /* Inline PyObject_NewVar */ - op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); - if (op == NULL) - return PyErr_NoMemory(); - PyObject_INIT_VAR(op, &PyString_Type, size); - op->ob_shash = -1; - op->ob_sstate = SSTATE_NOT_INTERNED; - Py_MEMCPY(op->ob_sval, str, size+1); - /* share short strings */ - if (size == 0) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - nullstring = op; - Py_INCREF(op); - } else if (size == 1) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - characters[*str & UCHAR_MAX] = op; - Py_INCREF(op); - } - return (PyObject *) op; -169:} -``` - -传给`PyString_FromString`的参数必须是一个指向以`NUL('\0')` 结尾的字符串的指针 - -根据定义我们知道 , 在创建`PyStringObject`时 : - -- 首先会检查该字符串数组的长度 , 如果字符数组的长度大于`PY_SSIZE_T_MAX` , 那么Python将不会创建对应的`PyStringObject`对象 , `PY_SSIZE_T_MAX`是一个与平台相关的值 , 在`WIN32`系统下 , 该值为`2147483647` , 即2GB -- 接下来检查传入的字符串是不是一个空串 , 对于空串 , Python并不是每一次都会创建相应的`PyStringObject` ; Python运行时有一个`PyStringObject`对象指针`nullstring`专门负责处理空的字符数组 , 如果第一次在一个空字符串基础上创建`PyStringObject` , 由于`nullstring`指针被初始化为NULL , 所以iPython会为这个字符建立一个`PyStringObject`对象 , 将这个对象通过`intern`机制进行共享 , 然后将`nullstring`指向这个被共享的对象 , 以后再创建空字符串就直接返回`nullstring`的引用了 -- 如果不是创建空字符串对象 , 那么就申请内存 , 创建`PyStringObject`对象 ; 处理申请字符串本身所需要的内存外 , 还会申请额外的内存 , 存放了其他的属性 , 以字符数组`"Python"`为例 , 如下图 - -![PyStringObject内存布局](https://github.com/lyonyang/blogs/blob/master/assets/PyStringObject%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80.png?raw=true) - - -**PyString_FromStringAndSize** - -`Python-2.7\Objects\stringobject.c :` - -```C - 61:PyString_FromStringAndSize(const char *str, Py_ssize_t size) - { - register PyStringObject *op; - if (size < 0) { - PyErr_SetString(PyExc_SystemError, - "Negative size passed to PyString_FromStringAndSize"); - return NULL; - } - if (size == 0 && (op = nullstring) != NULL) { - #ifdef COUNT_ALLOCS - null_strings++; - #endif - Py_INCREF(op); - return (PyObject *)op; - } - if (size == 1 && str != NULL && - (op = characters[*str & UCHAR_MAX]) != NULL) - { - #ifdef COUNT_ALLOCS - one_strings++; - #endif - Py_INCREF(op); - return (PyObject *)op; - } - - if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) { - PyErr_SetString(PyExc_OverflowError, "string is too large"); - return NULL; - } - - /* Inline PyObject_NewVar */ - op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); - if (op == NULL) - return PyErr_NoMemory(); - PyObject_INIT_VAR(op, &PyString_Type, size); - op->ob_shash = -1; - op->ob_sstate = SSTATE_NOT_INTERNED; - if (str != NULL) - Py_MEMCPY(op->ob_sval, str, size); - op->ob_sval[size] = '\0'; - /* share short strings */ - if (size == 0) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - nullstring = op; - Py_INCREF(op); - } else if (size == 1 && str != NULL) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - characters[*str & UCHAR_MAX] = op; - Py_INCREF(op); - } - return (PyObject *) op; -116:} -``` - -`PyString_FromStringAndSize` 的操作和`PyString_FromString`几乎一样 , 只有一点 , `PyString_FromString`传入的参数必须是以`NUL('\0')` 结尾的字符数组的指针 , 而`PyString_FromStringAndSize`则没有这个要求 , 因为通过传的`size`参数就可以确定需要拷贝的字符的个数 - - -## intern机制 - -从上面两种创建方式的源码中发现 , 无论是`PyString_FromString`还是`PyString_FromStringAndSize` , 当字符数组的长度为0或1时 , 需要进行一个特别的操作 : `PyString_InternInPlace` , 这就是字符串的`intern`机制 , 也就是上面代码中`share short strings` 注释下的代码 - -```C - /* share short strings */ -if (size == 0) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - nullstring = op; - Py_INCREF(op); -} else if (size == 1 && str != NULL) { - PyObject *t = (PyObject *)op; - PyString_InternInPlace(&t); - op = (PyStringObject *)t; - characters[*str & UCHAR_MAX] = op; - Py_INCREF(op); -} -return (PyObject *) op; -``` - -字符串对象的`intern`机制的目的是 : 对于被共享之后的字符串 , 比如`"Ruby"` , 在整个Python的运行期间 , 系统中都只有唯一的一个与字符串`"Ruby"`对应的 `PyStringObject`对象 - -当判断两个字符串对象是否相同时 , 如果它们都被共享了 , 那么只需要检查它们对应的`PyObject * `是否相同就可以了 , 这个机制节省了空间 , 如下 : - -```python -# Python 2.7 ->>> str1 = 'lyon' ->>> str2 = 'lyon' ->>> id(str1) -79116928L ->>> id(str2) -79116928L - -# Python 3.5.4 ->>> str1 = 'lyon' ->>> str2 = 'lyon' ->>> id(str1) -2767446375480 ->>> id(str2) -2767446375480 -``` - -这个例子的创建过程 : - -1. 因为` 'lyon'` 对象不存在 , 所以调用接口创建`PyStringObject`对象 (创建时经过`intern`机制处理) -2. Python在查找系统中记录的已经被`intern`机制处理了的`PyStringObject` 对象 (上一步中同样会进行查找) , 发现`'lyon'`字符数组对应的`PyStringObject`已经存在 , 于是返回该对象的引用返回 - - -### PyString_InternInPlace - -我们已经知道了创建字符串对象时进行了特殊的操作`PyString_InternInPlace` , 其源码如下 : - -`Python-2.7\Objects\stringobject.c :` - -```C -4712:void - PyString_InternInPlace(PyObject **p) - { - register PyStringObject *s = (PyStringObject *)(*p); - PyObject *t; - - // 对PyStringObject进行类型和状态检查 - if (s == NULL || !PyString_Check(s)) - Py_FatalError("PyString_InternInPlace: strings only please!"); - /* If it's a string subclass, we don't really know what putting - it in the interned dict might do. */ - if (!PyString_CheckExact(s)) - return; - if (PyString_CHECK_INTERNED(s)) - return; - - // 创建记录经intern机制处理后的PyStringObject的dict - if (interned == NULL) { - interned = PyDict_New(); - if (interned == NULL) { - PyErr_Clear(); /* Don't leave an exception */ - return; - } - } - - // 检查PyStringObject对象s是否存在对应的intern后的PyStrinObject对象 - t = PyDict_GetItem(interned, (PyObject *)s); - if (t) { - - // 调整引用计数 - Py_INCREF(t); - Py_DECREF(*p); - *p = t; - return; - } - - // 在interned中记录检查PyStringObject对象s - if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) { - PyErr_Clear(); - return; - } - /* The two references in interned are not counted by refcnt. - The string deallocator will take care of this */ - // 调整引用计数 - Py_REFCNT(s) -= 2; - - // 调整s中的intern状态标志 - PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL; -4748:} -``` - -`PyString_InternInPlace` 首先会进行一系列检查 : - -- 检查传入的对象是否是一个`PyStringObject`对象 , `intern`机制只能应用在`PyStringObject`对象上 , 甚至对于它的派生类对象系统都不会应用`intern`机制 -- 检查传入的`PyStringObject`对象是否已经被`intern`机制处理过 - -在代码中 , 我们可以清楚的看到 , `intern`机制的核心在于`interned` , 它指向一个由`PyDict_new`创建的对象 , 也就是一个字典 , 也就是说`intern`机制的关键就是在系统中有一个存在映射关系的集合 , 它的名字叫做`interned` , 这个集合里面记录了被`intern`机制处理过的 - - -### 特殊的引用计数 - -`intern`机制进行处理时 , 会将`PyStringObject`对象的`PyObject`指针分别作为`key`和`value`添加到`interned`中, 也就是说在这里该对象的引用计数应该加了2 , 如果按照正常的引用计数机制 , 那么明显这个对象是永远都不会被删除的 , 比如`a = 1;del a` , 我们只能够让引用计数减1 , 却无法让其减2 , 所以这里肯定用了特殊的引用计数机制 - -特殊就在于 , `interned`中的指针不能作为对象的有效引用 , 这也是为什么在`PyString_InternInPlace`的代码清单中第`4746`行为什么会将引用计数减2的原因 - - 一个对象的引用计数在某个时刻减为0之后 , 系统将会销毁该对象 , 那么字符串中到底是怎么解决的呢 ? 看看`string_dealloc`代码清单 : - -`Python-2.7\Objects\stringobject.c :` - -```C -582:static void - string_dealloc(PyObject *op) - { - switch (PyString_CHECK_INTERNED(op)) { - case SSTATE_NOT_INTERNED: - break; - - case SSTATE_INTERNED_MORTAL: - /* revive dead object temporarily for DelItem */ - Py_REFCNT(op) = 3; - if (PyDict_DelItem(interned, op) != 0) - Py_FatalError( - "deletion of interned string failed"); - break; - - case SSTATE_INTERNED_IMMORTAL: - Py_FatalError("Immortal interned string died."); - - default: - Py_FatalError("Inconsistent interned string state."); - } - Py_TYPE(op)->tp_free(op); -602:} -``` - -在这份代码清单中 , `SSTATE_INTERNED_MORTAL` 和 `SSTATE_INTERNED_IMMORTAL` 表示着`PyStringObject`的两种状态 , 也就是说被`intern`机制处理后的`PyStringObject`对象分为两类 , 这两类的区别在于 , `SSTATE_INTERNED_IMMORTAL` 状态的`PyStringObject`对象是永远不会被销毁的 - -`PyString_IntenInPlace` 只能创建`SSTATE_INTERNED_MORTAL` 状态的`PyStringObject`对象 , 如果想创建`SSTATE_INTERNED_IMMORTAL`状态的对象 , 必须通过另外的接口 , 在调用了`PyString_InternInPlace`后 , 强制改变`PyStringObject`的`intern`状态 - -注意 : `intern`机制节省了内存空间 , 但是在我们创建`PyStringObject`时 , 无论在`interned`中是否存在 , 都是会创建一个`PyStringObject`对象的 , 只不过这是一个临时的对象 , 如果`interned`中有 , 那么就`PyString_InternInPlace` 会对这个对象的引用计数减1 , 于是它就会被销毁了 - - -## 字符缓冲池 - -与Python整数对象类似 , Python的设计者为`PyStringObject`中的一个字节的字符对应的`PyStringObject`对象也设计了一个对象池`characters` - -`Python-2.7\Objects\stringobject.c :` - -```C -13:static PyStringObject *characters[UCHAR_MAX + 1] -``` - -其中`UCHAR_MAX`是在系统头文件中定义的常量 , 这一个跟平台相关的常量 , 在Win32平台下 : - -```C -#define UCHAR_MAX 0xff /* maximum unsigned char value */ -``` - -在Python的整数对象体系中 , 小整数的缓冲池是在Python初始化时被创建的 , 而字符串对象体系中的字符串缓冲池则是以静态变量的形式存在着的 , 在Python初始化完成之后 , 缓冲池中的所有`PyStringObject`指针都为空 - -当我们创建一个字符串对象时 , 无论是通过调用`PyString_FromString` 还是`PyString_FromStringAndSize` , 如果字符串实际上是一个字符 , 则会对所创建字符串 (字符) 对象进行`intern`操作 , 再将`intern`的结果缓存到字符缓冲池`characters`中 - - -## 万恶的加号 - -字符串拼接绝对是再正常不过的事情了 , 一拼接 , 那么效率问题就来了 - -Python中提供了 `"+"` 来进行字符串拼接 , 可惜这实际上就是万恶之源 ; 我们除了使用`"+"` 外 , 还有一种方法就是使用list的`join`方法 , 这也是官方推荐我们使用的 - -**`"+"` 与 `join`** - -通过`"+"`操作符对字符串进行拼接时 , 会调用`string_concat`函数 : - -```C -1014:static PyObject * - string_concat(register PyStringObject *a, register PyObject *bb) - { - register Py_ssize_t size; - register PyStringObject *op; - ...... - #define b ((PyStringObject *)bb) - /* Optimize cases with empty left or right operand */ - ...... - // 计算字符串连接后的长度size - size = Py_SIZE(a) + Py_SIZE(b); - /* Check that string sizes are not negative, to prevent an - overflow in cases where we are passed incorrectly-created - strings with negative lengths (due to a bug in other code). - */ - ...... - // 创建新的PyStringObject对象,其维护的用于存储字符的内存长度为size - op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); - if (op == NULL) - return PyErr_NoMemory(); - PyObject_INIT_VAR(op, &PyString_Type, size); - op->ob_shash = -1; - op->ob_sstate = SSTATE_NOT_INTERNED; - - // 将a和b中的字符拷贝到新创建的PyStringObject中 - Py_MEMCPY(op->ob_sval, a->ob_sval, Py_SIZE(a)); - Py_MEMCPY(op->ob_sval + Py_SIZE(a), b->ob_sval, Py_SIZE(b)); - op->ob_sval[size] = '\0'; - return (PyObject *) op; - #undef b -1071:} -``` - -**小结 : 对于任意两个PyStringObject对象的连接 , 就会进行一次内存申请的动作** - -通过`join`函数对字符串进行拼接时 , 会调用`string_join`函数 : - -```C -1573:static PyObject * - string_join(PyStringObject *self, PyObject *orig) - { - char *sep = PyString_AS_STRING(self); - const Py_ssize_t seplen = PyString_GET_SIZE(self); - PyObject *res = NULL; - char *p; - Py_ssize_t seqlen = 0; - size_t sz = 0; - Py_ssize_t i; - PyObject *seq, *item; - // 拼接字符 - seq = PySequence_Fast(orig, ""); - if (seq == NULL) { - return NULL; - } - // 拼接字符长度 - seqlen = PySequence_Size(seq); - if (seqlen == 0) { - Py_DECREF(seq); - return PyString_FromString(""); - } - if (seqlen == 1) { - item = PySequence_Fast_GET_ITEM(seq, 0); - if (PyString_CheckExact(item) || PyUnicode_CheckExact(item)) { - Py_INCREF(item); - Py_DECREF(seq); - return item; - } - } - - /* There are at least two things to join, or else we have a subclass - * of the builtin types in the sequence. - * Do a pre-pass to figure out the total amount of space we'll - * need (sz), see whether any argument is absurd, and defer to - * the Unicode join if appropriate. - */ - // 遍历list中每一个字符串,获取所有字符串长度 - for (i = 0; i < seqlen; i++) { - const size_t old_sz = sz; - item = PySequence_Fast_GET_ITEM(seq, i); - if (!PyString_Check(item)){ - #ifdef Py_USING_UNICODE - if (PyUnicode_Check(item)) { - /* Defer to Unicode join. - * CAUTION: There's no gurantee that the - * original sequence can be iterated over - * again, so we must pass seq here. - */ - PyObject *result; - result = PyUnicode_Join((PyObject *)self, seq); - Py_DECREF(seq); - return result; - } - #endif - PyErr_Format(PyExc_TypeError, - "sequence item %zd: expected string," - " %.80s found", - i, Py_TYPE(item)->tp_name); - Py_DECREF(seq); - return NULL; - } - sz += PyString_GET_SIZE(item); - if (i != 0) - sz += seplen; - if (sz < old_sz || sz > PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "join() result is too long for a Python string"); - Py_DECREF(seq); - return NULL; - } - } - - /* Allocate result space. */ - // 创建长度为sz的PyStringObject对象 - res = PyString_FromStringAndSize((char*)NULL, sz); - if (res == NULL) { - Py_DECREF(seq); - return NULL; - } - - /* Catenate everything. */ - // 将list中的字符串拷贝到新创建的PyStringObject对象中 - p = PyString_AS_STRING(res); - for (i = 0; i < seqlen; ++i) { - size_t n; - item = PySequence_Fast_GET_ITEM(seq, i); - n = PyString_GET_SIZE(item); - Py_MEMCPY(p, PyString_AS_STRING(item), n); - p += n; - if (i < seqlen - 1) { - Py_MEMCPY(p, sep, seplen); - p += seplen; - } - } - - Py_DECREF(seq); - return res; -1668:} -``` - -**小结 : 首先统计出`list`中的对象个数 , 并统计这些对象的字符串总长度 , 申请一次内存空间 , 将所有的`PyStringObject`对象维护的字符串都拷贝到新开辟的内存空间中** - -通过小结可以很直接的得出答案 , 如果要拼接n个字符串对象 , 那么使用 "+" 需要申请空间`n-1`次 , 而使用`join`则仅需一次 - - - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/05-List\345\257\271\350\261\241.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/05-List\345\257\271\350\261\241.md" deleted file mode 100644 index 20d709261..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/05-List\345\257\271\350\261\241.md" +++ /dev/null @@ -1,510 +0,0 @@ -# Attack on Python - List对象 🐍 - - - - - - - - - - -## 介绍 - -元素的一个群是一个非常重要的抽象概念 , 我们可以将符合某一特性的一堆元素聚集为一个群 - -群的概念对于编程语言十分重要 , C语言就内建了数组的概念 , 每一种实现都为某种目的的元素聚集或元素访问提供极大的方便 - -`PyListObject`是Python提供的对列表的抽象 , 它可以支持对元素的插入 , 删除 , 添加等操作 , 所以它是一个可变对象 - -## PyListObject - -`Python-2.7\Include\listobject.h` - -```C -22:typedef struct { -23: PyObject_VAR_HEAD -24: /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ -25: PyObject **ob_item; -26: -27: /* ob_item contains space for 'allocated' elements. The number -28: * currently in use is ob_size. -29: * Invariants: -30: * 0 <= ob_size <= allocated -31: * len(list) == ob_size -32: * ob_item == NULL implies ob_size == allocated == 0 -33: * list.sort() temporarily sets allocated to -1 to detect mutations. -34: * -35: * Items must normally not be NULL, except during construction when -36: * the list is not yet visible outside the function that builds it. -37: */ -38: Py_ssize_t allocated; -39:} PyListObject; -``` - -分析 : - -- PyObject_VAR_HEAD , Python中的列表是一个变长对象 -- PyObject **ob_item , `ob_item`为指向元素列表的指针 , 实际上 , Python中的`list[0]` 就是`ob_item[0]` -- Py_ssize_t allocated , 与`PyListObject`对象的内存管理有关 - -实际上 , 在`PyObject_VAR_HEAD`中的`ob_size`和`allocated` 都和`PyListObject`对象的内存管理有关 : - -PyListObject采用的内存管理策略和C++中`vector`采取的内存管理策略是一样的 , 它并不是存了多少东西就申请对应大小的内存 , 因为这样的策略显然是低效的 , 而我们使用列表就是为了用户方便用户频繁地插入或删除元素 , 所以 , 在每一次需要申请内存的时候 , `PyListObject`总会申请一大块内存 , 这时申请的总内存的大小记录在`allocated`中 , 而其实际被使用了的内存的数量记录在了`ob_size`中 - -假如有一个能容纳10个元素的列表已经装入了5个元素 , 那么这个列表的`ob_size`就是5 , 而`allcoated`则是10 - -即 : `0 <= ob_size <= allocated` - -在`Python-3.5.4\Include\listobject.h`的22至40行 , 我们可以找到相同的代码 , 也就是说2.7与3.5.4的这一部分是没有区别的 - -## 创建与维护 - -在之前对于Python对象创建方式已有说明 , 为了创建一个列表 , Python只提供了唯一的一条途径 , 就是`PyList_New` - -`Python-2.7\Objects\listobject.c` - -```C -112:PyObject * - PyList_New(Py_ssize_t size) - { - PyListObject *op; - size_t nbytes; - #ifdef SHOW_ALLOC_COUNT - static int initialized = 0; - if (!initialized) { - Py_AtExit(show_alloc); - initialized = 1; - } - #endif - - if (size < 0) { - PyErr_BadInternalCall(); - return NULL; - } - /* Check for overflow without an actual overflow, - * which can cause compiler to optimise out */ - // 检查是否会发生溢出 - if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) - return PyErr_NoMemory(); - // 计算需要使用的内存总量 - nbytes = size * sizeof(PyObject *); - if (numfree) { - - // 缓冲池可用 - numfree--; - op = free_list[numfree]; - _Py_NewReference((PyObject *)op); - #ifdef SHOW_ALLOC_COUNT - count_reuse++; - #endif - } else { - - // 缓冲池不可用 - op = PyObject_GC_New(PyListObject, &PyList_Type); - if (op == NULL) - return NULL; - #ifdef SHOW_ALLOC_COUNT - count_alloc++; - #endif - } - - // 为对象中维护的元素列表申请空间 - if (size <= 0) - op->ob_item = NULL; - else { - op->ob_item = (PyObject **) PyMem_MALLOC(nbytes); - if (op->ob_item == NULL) { - Py_DECREF(op); - return PyErr_NoMemory(); - } - memset(op->ob_item, 0, nbytes); - } - Py_SIZE(op) = size; - op->allocated = size; - _PyObject_GC_TRACK(op); - return (PyObject *) op; -163:} -``` - -分析 : - -- 这个函数接受一个size参数 , 也就是我们可以在创建时指定`PyListObject`对象的初始元素个数 -- 在创建时 , 首先计算需要使用的内存总量 , 因为`PyList_New`指定的仅仅是元素的个数 , 而不是元素实际将占用的内存空间 , 在这里 , Python会检查指定的元素个数是否会大到使所需内存数量产生溢出的程度 , 并根据判断结果做出相应的操作 -- 检查缓冲池是否可用 -- 为维护对象申请内存空间 , 维护对象与PyListOjbect对象本身通过`ob_item`建立了连接 - - -当Python创建了新的`PyListObject`对象之后 , 会立即根据调用`PyList_New`时传递的size参数创建`PyListObject`对象所维护的元素列表 , 其中每一个元素都被初始化为`NULL` - -在完成了`PyListObject`对象及维护的列表的创建之后 , Python会调整该`PyListObject`对象 , 用于维护元素列表中元素数量的`ob_size`和`allocated`两个变量 - -对于缓冲池`free_list`中的对象个数 , 我们可以在源码中找到 , `free_list`最多会维护80个`PyListObject`对象 - -`Python-2.7\Objects\listobject.c` - -```C -94:#ifndef PyList_MAXFREELIST -95:#define PyList_MAXFREELIST 80 -96:#endif -97:static PyListObject *free_list[PyList_MAXFREELIST]; -98:static int numfree = 0; -``` - -`Python-3.5.4\Objects\listobject.c` - -```C -95:#ifndef PyList_MAXFREELIST -96:#define PyList_MAXFREELIST 80 -97:#endif -98:static PyListObject *free_list[PyList_MAXFREELIST]; -99:static int numfree = 0; -``` - - - -## 设置元素 - -在我们创建第一个`PyListObject`对象时 , 这时候缓冲池是不可用的 , 于是会调用`PyObject_GC_New`在系统堆上创建一个新的`PyListObject`对象 , 假如我们创建一个包含6个元素的`PyListObject` , 那么创建成功之后 , 这个对象的`ob_size`为6 , `allocated`为6 , 而`ob_item`则是指向这些元素的指针 - - 而当我们设置元素时 , 如现有一个列表`la = [1, 2, 3]` , 当我们执行`la[0] = 4`时 , 在Python内部 , 会调用`PyList_SetItem`来完成这个动作 ; 首先Python会进行类型检查 , 随后会进行索引的有效性检查 , 当这两者都通过后 , 将新设置的元素指针放到指定的位置 , 然后调整引用计数 , 将这个位置原来存放的对象的引用计数减1 , 源码如下 : - -`Python-2.7\Objects\listobject.c` - -```C -198:int - PyList_SetItem(register PyObject *op, register Py_ssize_t i, - register PyObject *newitem) - { - register PyObject *olditem; - register PyObject **p; - if (!PyList_Check(op)) { - Py_XDECREF(newitem); - PyErr_BadInternalCall(); - return -1; - } - if (i < 0 || i >= Py_SIZE(op)) { - Py_XDECREF(newitem); - PyErr_SetString(PyExc_IndexError, - "list assignment index out of range"); - return -1; - } - p = ((PyListObject *)op) -> ob_item + i; - olditem = *p; - *p = newitem; - Py_XDECREF(olditem); - return 0; -220:} -``` - -`Python-3.5.4\Objects\listobject.c` - -```C -215:int - PyList_SetItem(PyObject *op, Py_ssize_t i, - PyObject *newitem) - { - PyObject *olditem; - PyObject **p; - if (!PyList_Check(op)) { - Py_XDECREF(newitem); - PyErr_BadInternalCall(); - return -1; - } - if (i < 0 || i >= Py_SIZE(op)) { - Py_XDECREF(newitem); - PyErr_SetString(PyExc_IndexError, - "list assignment index out of range"); - return -1; - } - p = ((PyListObject *)op) -> ob_item + i; - olditem = *p; - *p = newitem; - Py_XDECREF(olditem); - return 0; -237:} -``` - -在两个版本中 , 没有变化 - - - -## 插入元素 - -设置元素和插入元素的动作是不同的 , 设置元素不会导致`ob_item`指向的内存发生变化 , 但是插入元素的动作则有可能使得`ob_item`指向的内存发生变化 - -Python内部通过调用`PyList_Insert`来完成元素的插入动作 , 而`PyList_Insert`实际上是调用了内部的`insl` , 如下 : - -`Python-2.7\Objects\listobject.c` - -```C -222:static int - ins1(PyListObject *self, Py_ssize_t where, PyObject *v) - { - Py_ssize_t i, n = Py_SIZE(self); - PyObject **items; - if (v == NULL) { - PyErr_BadInternalCall(); - return -1; - } - if (n == PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "cannot add more objects to list"); - return -1; - } - // 调整列表容量 - if (list_resize(self, n+1) == -1) - return -1; - // 确定插入点 - if (where < 0) { - where += n; - if (where < 0) - where = 0; - } - if (where > n) - where = n; - // 插入元素 - items = self->ob_item; - for (i = n; --i >= where; ) - items[i+1] = items[i]; - Py_INCREF(v); - items[where] = v; - return 0; - } - -255:int -256:PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) - { - // 类型检查 - if (!PyList_Check(op)) { - PyErr_BadInternalCall(); - return -1; - } - return ins1((PyListObject *)op, where, newitem); -263:} -``` - -`Python-3.5.4\Objects\listobject.c` - -```C -239:static int - ins1(PyListObject *self, Py_ssize_t where, PyObject *v) - { - Py_ssize_t i, n = Py_SIZE(self); - PyObject **items; - if (v == NULL) { - PyErr_BadInternalCall(); - return -1; - } - if (n == PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "cannot add more objects to list"); - return -1; - } - // 调整列表容量 - if (list_resize(self, n+1) == -1) - return -1; - // 确定插入点 - if (where < 0) { - where += n; - if (where < 0) - where = 0; - } - if (where > n) - where = n; - // 插入元素 - items = self->ob_item; - for (i = n; --i >= where; ) - items[i+1] = items[i]; - Py_INCREF(v); - items[where] = v; - return 0; - } - -272:int -273:PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) - { - // 类型检查 - if (!PyList_Check(op)) { - PyErr_BadInternalCall(); - return -1; - } - return ins1((PyListObject *)op, where, newitem); -280:} -``` - -在`insl`中 , 为了完成元素的插入工作 , 首先必须保证`PyListObject`对象有足够的内存来容纳我们期望插入的元素 , 这一步是通过`insl`中的`list_resize`函数来实现的 , 正是这个函数改变了`PyListObject`所维护的`PyObject *` 列表的大小 - -`Python-2.7\Objects\listobject.c` - -```c -24:static int - list_resize(PyListObject *self, Py_ssize_t newsize) - { - PyObject **items; - size_t new_allocated; - Py_ssize_t allocated = self->allocated; - - /* Bypass realloc() when a previous overallocation is large enough - to accommodate the newsize. If the newsize falls lower than half - the allocated size, then proceed with the realloc() to shrink the list. - */ - // 不需要重新申请内存 - if (allocated >= newsize && newsize >= (allocated >> 1)) { - assert(self->ob_item != NULL || newsize == 0); - Py_SIZE(self) = newsize; - return 0; - } - - /* This over-allocates proportional to the list size, making room - * for additional growth. The over-allocation is mild, but is - * enough to give linear-time amortized behavior over a long - * sequence of appends() in the presence of a poorly-performing - * system realloc(). - * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... - */ - // 计算重新申请的内存大小 - new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); - - /* check for integer overflow */ - if (new_allocated > PY_SIZE_MAX - newsize) { - PyErr_NoMemory(); - return -1; - } else { - new_allocated += newsize; - } - - if (newsize == 0) - new_allocated = 0; - // 扩展列表 - items = self->ob_item; - if (new_allocated <= ((~(size_t)0) / sizeof(PyObject *))) - // 最终调用C中的realloc - PyMem_RESIZE(items, PyObject *, new_allocated); - else - items = NULL; - if (items == NULL) { - PyErr_NoMemory(); - return -1; - } - self->ob_item = items; - Py_SIZE(self) = newsize; - self->allocated = new_allocated; - return 0; -73:} -``` - -同样的 , 在`Python-3.5.4\Objects\listobject.c` 中的第25至74行为该函数的定义 - -在调整`PyListObject`对象所维护的列表的内存时 , Python分两种情况处理 : - -- `newsize < allocated && newsize > allocated/2` , 也就是说当插入后使用的实际内存大小要小于总内存大小 , 以及要大于总内存大小的一半时 , 就简单调整`ob_size`值 -- 其他情况 , 调用`realloc` , 重新分配空间 - -我们可以发现 , 对于第二种情况 , 比如`newsize < allocated/2` 时 , Python也会调用`realloc`来收缩列表的内存空间 , 不得不说这是物尽其用的设计 - -## 删除元素 - -以`list`对象方法`remove`为例 , 当我们使用`remove`方法时 , `PyListObject`中的`listremove`操作就会被激活 - -`Python-2.7\Objects\listobject.c` - -```C -2336:static PyObject * - listremove(PyListObject *self, PyObject *v) - { - Py_ssize_t i; - - for (i = 0; i < Py_SIZE(self); i++) { - int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ); - if (cmp > 0) { - if (list_ass_slice(self, i, i+1, - (PyObject *)NULL) == 0) - Py_RETURN_NONE; - return NULL; - } - else if (cmp < 0) - return NULL; - } - PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); - return NULL; -2354:} -``` - -`Python-3.5.4\Objects\listobject.c` 第2197至2215见同上代码清单 - -首先Python会对整个列表进行遍历 , 在遍历`PyListObject`中所有元素的过程中 , 将待删除元素与`PyListObject`中的每个元素一一进行比较 , 比较操作是通过`PyObject_RichCompareBool`完成的 , 如果返回值大于0 , 则表示要删除的元素与列表中的元素匹配成功 , Python将立即调用`list_ass_slice`删除该元素 - -`Python-2.7\Objects\listobject.c` - -```C -607:/* a[ilow:ihigh] = v if v != NULL. // 不为空就替换 - * del a[ilow:ihigh] if v == NULL. // 为空就删除 - * - * Special speed gimmick: when v is NULL and ihigh - ilow <= 8, it's - * guaranteed the call cannot fail. - */ - static int - list_ass_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v) - { - ...... -709:} -``` - -`Python-3.5.4\Objects\listobject.c` 第572至579见同上代码清单 - -如上 , 对于`list_ass_slice`其实是有两种语义的 , 即`replace`和`remove` ; 于是 , 在Python列表中删除元素我们还可以这样做 : - -```python -# Python 2.x & 3.x ->>> la = [1,2,3,4,5] ->>> la[1:3] = [] ->>> la -[1, 4, 5] -``` - -对于`list`对象的`pop`方法 , 同样也是调用`list_ass_slice`来进行删除 , 源码位于`listobject.c`文件中 - - -## 对象缓冲池 - -在`PyList_New`中我们见过一个`free_list` , 这就是`PyListObject`对象缓冲池 ; 但是我们在`PyList_New`中并没有看到缓冲池中的`PyListObject`对象的添加过程 , 这是因为缓冲池对象并不像前面的字符串对象或者整数对象一样 , 是在创建时添加的 , Python列表的缓冲池是在其销毁的时候添加的 - -`Python-2.7\Objects\listobject.c` - -```C -296:static void - list_dealloc(PyListObject *op) - { - Py_ssize_t i; - PyObject_GC_UnTrack(op); - Py_TRASHCAN_SAFE_BEGIN(op) - // 销毁PyListObject对象维护的元素列表 - if (op->ob_item != NULL) { - /* Do it backwards, for Christian Tismer. - There's a simple test case where somehow this reduces - thrashing when a *very* large list is created and - immediately deleted. */ - i = Py_SIZE(op); - while (--i >= 0) { - Py_XDECREF(op->ob_item[i]); - } - PyMem_FREE(op->ob_item); - } - // 释放PyListObject自身 - if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op)) - free_list[numfree++] = op; - else - Py_TYPE(op)->tp_free((PyObject *)op); - Py_TRASHCAN_SAFE_END(op) -318:} -``` - -与`PyListObject`对象创建一样 , `PyListObject`对象的销毁也是分离的 , 首先销毁`PyListObject`对象所维护的元素列表 , 然后再释放`PyListObject`对象本身 ; 这样的工作无非是改变该对象的引用计数 , 然后再释放内存 , 但是我们发现 , 在释放`PyListObject`本身时 , Python会检查前面提到的这个缓冲池`free_list` - -首先Python会查看其中缓存的`PyListObject`对象的数量是否已经满了 , 如果没有 , 就将该待删除的`PyListObject`对象放到缓冲池中 , 以备后用 - -注意 , 我们也已经发现了 , 添加进缓冲池的是`PyListObject`对象本身 , 而不包括它之前维护的元素列表 , 也就是说我们在创建新的`PyListObject`时 , Python会首先唤醒这些已经 "死去" 的`PyListObject` , 然后赋予它们新的元素列表 , 使其能够重新做 "人" - -对于每次创建`PyListObject`对象时必须创建元素列表 , 这是Python为了避免过多的消耗系统内存 , 采取的时间换空间的做法 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/06-Dict\345\257\271\350\261\241.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/06-Dict\345\257\271\350\261\241.md" deleted file mode 100644 index 5da7d151d..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/06-Dict\345\257\271\350\261\241.md" +++ /dev/null @@ -1,627 +0,0 @@ -# Attack on Python - Dict对象 🐍 - - - - - - - - - - -## 介绍 - -为了刻画某种元素之间的对应关系 , 现代编程语言通常都在语言级或标准库中提供某种关联式的容器 ; 关联容器的设计总会极大地关注键的搜索效率 , 因为我们希望根据我们手中已有的某个元素来快速获得与之有某种联系的另一元素 - -在Python中同样提供关联式容器 , 即PyDictObject 对象 , 与`map`不同的是 , PyDictObject对搜索的效率要求极其苛刻 , 这也是因为PyDictObject对象在Python本身的实现中被大量采用 ; 比如Python会通过PyDictObject来建立执行Python字节码的运行环境 , 其中会存放变量名和变量值的元素对 , 通过查找变量名获得变量值 , 因此PyDictObject采用的是散列表 (hash table) , 因为理论上 , 在最优情况下 , 散列表能提供`O(1)`复杂度的搜索效率 - -## 散列表 - -散列表的基本思想 , 是通过一定的函数将需搜索的键值映射为一个整数 , 将这个整数视为索引值去访问某片连续的区域 - -对散列表这种数据结构的采用是以加速键的搜索过程为终极目标的 , 所以 , 将元素映射为整数的过程对于Python中`dict`的实现就显得尤为关键 ; 用于映射的函数称为散列函数 (hash function) , 映射后的值称为元素的散列值 (hash value) , 在散列表的实现中 , 所选择的散列函数的优劣直接决定所实现的散列表的搜索效率的高低 - -在使用散列表的过程中 , 不同的对象经过散列函数的作用 , 可能被映射为相同的散列值 , 这就是散列冲突 - -根据研究表明 , 当散列表的装载率大于2/3时 , 散列冲突发生的概率就会大大增加 - -解决散列冲突的方法有很多种 , 在Python中采用的是开放定址法 - -当产生散列冲突时 , Python会通过一个二次探测函数`f` , 计算下一个候选位置`addr` , 如果位置`addr`可用 , 则可将待插入元素放到位置`addr` ; 如果位置`addr`不可用 , 则Python会再次使用探测函数`f` , 获得下一个候选位置 , 以此依次寻找下去 - -最后 , 这些位置会形成一个`"冲突探测链"(或简称探测序列)` , 而当我们要删除某条探测链上的某个元素时 , 按照探测链会发生什么样的情况 ; 假如这条链的首元素位置为`a` , 尾元素的位置为`c` , 现在需要删除中间的某个位置`b`上的元素 , 如果直接将位置`b`上的元素删除 , 则会导致探测链的断裂 , 于是探测函数在探测时将再也不能到达位置`c`了 , 所以删除某条探测链上的元素时不能进行真正的删除 , 而是进行一种 "伪删除" 操作 , 必须要让该元素还存在于探测链上 - -在Python中 , 这种伪删除是在PyDictObject对象中实现的 - -## PyDictObject - -在Python2.7中 , 关联容器的一个(键 , 值)元素对称为一个`entry`或`slot` - -`Python-2.7\Include\dictobject.h` - -```C -50:typedef struct { - /* Cached hash code of me_key. Note that hash codes are C longs. - * We have to use Py_ssize_t instead because dict_popitem() abuses - * me_hash to hold a search finger. - */ - Py_ssize_t me_hash; - PyObject *me_key; - PyObject *me_value; -58:} PyDictEntry; -``` - -在PyDictEntry中 , `me_hash`域存储的是`me_key`的散列值 , 利用一个域来记录这个散列值可以避免每次查询的时候都要重新计算一遍散列值 - -在Python中 , 在一个PyDictObject对象生存变化的过程中 , 其中的entry会在不同的状态间转换 ; PyDictObject中entry可以在3种状态之间转换 : Unused , Active , Dummy - -- Unused : 当一个entry的`me_key`和`me_value`都为NULL时 , entry处于Unused态 ; 表明目前该entry中并没有存储(key , value)对 , 而且在此之前 , 也没有存储过它们 , 这时每一个entry在初始化时的状态 , 并且也只有在Unused态下 , entry的`me_key`域才会为NULL -- Active : 当entry中存储了一个(key , value)对时 , entry便转到了Active态 , 在Active态下 , `me_key`和`me_value`都不能为NULL -- Dummy : 当entry中存储的(key , value)对被删除后 , entry的状态不能直接从Active态转为Unused态 , 因为这样会导致冲突探测链的中断 , 所以entry中的`me_key`将指向dummy对象 , 从而entry进入Dummy态 , 这就是"伪删除"技术 ; 当Python沿着某条冲突链搜索时 , 如果发现一个entry处于Dummy态 , 说明目前该entry虽然是无效的 , 但是其后的entry可能是有效的 , 是应该被搜索的 , 这样就保证了冲突探测链的连续性 - - -在Python中 , 关联容器是通过PyDictObject对象来实现的 , 而一个PyDictObject - -对象实际上是一大堆entry的集合 : - -`Python-2.7\Include\dictobject.h` - -```C -70:typedef struct _dictobject PyDictObject; - struct _dictobject { - PyObject_HEAD - Py_ssize_t ma_fill; /* # Active + # Dummy */ - Py_ssize_t ma_used; /* # Active */ - - /* The table contains ma_mask + 1 slots, and that's a power of 2. - * We store the mask instead of the size because the mask is more - * frequently needed. - */ - Py_ssize_t ma_mask; - - /* ma_table points to ma_smalltable for small tables, else to - * additional malloc'ed memory. ma_table is never NULL! This rule - * saves repeated runtime null-tests in the workhorse getitem and - * setitem calls. - */ - PyDictEntry *ma_table; - PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, long hash); - PyDictEntry ma_smalltable[PyDict_MINSIZE]; -90:}; -``` - -定义说明 : - -- ma_fill , `ma_fill`域中维护着从PyDictObject对象创建开始直到现在 , 曾经及正处于Active态的entry个数 , 而`ma_used`则维护着当前正处于Active态的entry的数量 -- 在定义的最后 , 有一个名为`ma_smalltable`的PyDictEntry数组 , 这个数组意味着当创建一个PyDictObject对象时 , 至少有`PyDict_MINSIZE`个entry被同时创建 , 在`dictobject.h`中 , 这个值被设定为8 , 这个值被认为时通过大量的实验得出的最佳值 ; 它既不会态浪费内存空间 , 又能很好地满足Python内部大量使用PyDictObject的环境的需求 -- ma_table , ma_table域是关联对象的关键所在 , 它将指向一片作为PyDictEntry集合的内存的开始位置 , 当一个PyDictObject对象是一个比较小的dict时 (entry数量少于8) , ma_table域将指向`ma_smalltable` , 而当PyDictObject中的entry数量超过8个时 , 将会申请额外的内存空间 , 并将ma_table指向这块空间 , 这样 , 无论何时 , ma_table域都不会为NULL , 那么在程序运行时就不需要一次又一次的检查`ma_table`的有效性了 , 因为`ma_table`总是有效的 , 这两种`ma_table`见下图 -- ma_mask , PyDictObject中的`ma_mask`记录了一个PyDictObject对象中所拥有的entry数量 - -![ma_table](https://github.com/lyonyang/blogs/blob/master/assets/ma_table.png?raw=true) - - -## 创建与维护 - -Python内部通过PyDict_New来创建一个新的dict对象 - -`Python-2.7\Include\dictobject.c` - -```C -210:#define INIT_NONZERO_DICT_SLOTS(mp) do { \ - (mp)->ma_table = (mp)->ma_smalltable; \ - // PyDict_MINSIZE定义在dictobject.h中,默认值为8 - (mp)->ma_mask = PyDict_MINSIZE - 1; \ - } while(0) \ - - #define EMPTY_TO_MINSIZE(mp) do { \ - memset((mp)->ma_smalltable, 0, sizeof((mp)->ma_smalltable)); \ - (mp)->ma_used = (mp)->ma_fill = 0; \ - INIT_NONZERO_DICT_SLOTS(mp); \ -219:} while(0) - -220:/* Dictionary reuse scheme to save calls to malloc, free, and memset */ - #ifndef PyDict_MAXFREELIST - #define PyDict_MAXFREELIST 80 - #endif - static PyDictObject *free_list[PyDict_MAXFREELIST]; -226:static int numfree = 0; -...... -240:PyObject * - PyDict_New(void) - { - register PyDictObject *mp; - // 自动创建dummy对象 - if (dummy == NULL) { /* Auto-initialize dummy */ - dummy = PyString_FromString(""); - if (dummy == NULL) - return NULL; - #ifdef SHOW_CONVERSION_COUNTS - Py_AtExit(show_counts); - #endif - #ifdef SHOW_ALLOC_COUNT - Py_AtExit(show_alloc); - #endif - #ifdef SHOW_TRACK_COUNT - Py_AtExit(show_track); - #endif - } - if (numfree) { - // 使用缓冲池 - mp = free_list[--numfree]; - assert (mp != NULL); - assert (Py_TYPE(mp) == &PyDict_Type); - _Py_NewReference((PyObject *)mp); - if (mp->ma_fill) { - EMPTY_TO_MINSIZE(mp); - } else { - /* At least set ma_table and ma_mask; these are wrong - if an empty but presized dict is added to freelist */ - INIT_NONZERO_DICT_SLOTS(mp); - } - assert (mp->ma_used == 0); - assert (mp->ma_table == mp->ma_smalltable); - assert (mp->ma_mask == PyDict_MINSIZE - 1); - #ifdef SHOW_ALLOC_COUNT - count_reuse++; - #endif - } else { - // 创建PyDictObject对象 - mp = PyObject_GC_New(PyDictObject, &PyDict_Type); - if (mp == NULL) - return NULL; - EMPTY_TO_MINSIZE(mp); - #ifdef SHOW_ALLOC_COUNT - count_alloc++; - #endif - } - mp->ma_lookup = lookdict_string; - #ifdef SHOW_TRACK_COUNT - count_untracked++; - #endif - #ifdef SHOW_CONVERSION_COUNTS - ++created; - #endif - return (PyObject *)mp; -293:} -``` - -在定义的开始部分我们可以发现 , 自动创建`dummy`对象 , 这个`dummy`对象竟然时一个PyStringObject对象 , 实际上 , 它仅仅时用来作为一种指示标志 , 表明该entry曾被使用过 , 且探测序列下一个位置的entry有可能时有效的 , 从而防止探测序列中断 - -如果不使用缓冲池 , 创建时将调用`EMPTY_TO_MINSIZE` , 将`ma_smalltable`清零 , 同时设置`ma_size`和`ma_fill` , 初始时 , 这两个变量都为0 , 随后调用`INIT_NONZERO_DICT_SLOTS` , 其功能是将`ma_table`指向`ma_smalltable` , 并设置`ma_mask`为7 - -在创建过程的最后 , 将`lookdict_string`赋给了`ma_lookup` , 这个`ma_lookup`指定了PyDictObjec在entry集合中搜索某一特定entry时需要进行的动作 , 在`ma_lookup`中 , 包含了散列函数和发生冲突时二次探测函数的具体实现 , 它时PyDictObject的搜索策略 - -PyDictObject缓冲池见下文 - - -## 元素搜索 - -Python为PyDictObject对象提供了两种搜索策略 , lookdict和lookdict_string , 但是实际上 , 这两种策略使用的相同的算法 , lookdict_string只是对lookdict的一种针对PyStringObject对象的特殊形式 , 这是因为以PyStringObject对象作为PyDictObject对象中entry的键在Python中应用非常广泛 - -### lookdict - -`Python-2.7\Include\dictobject.c` - -```C -319:static PyDictEntry * - lookdict(PyDictObject *mp, PyObject *key, register long hash) - { - register size_t i; - register size_t perturb; - register PyDictEntry *freeslot; - register size_t mask = (size_t)mp->ma_mask; - PyDictEntry *ep0 = mp->ma_table; - register PyDictEntry *ep; - register int cmp; - PyObject *startkey; - // 散列,定位冲突探测链的第一个entry -331: i = (size_t)hash & mask; - ep = &ep0[i]; - // entry处于Unused态 - if (ep->me_key == NULL || ep->me_key == key) - return ep; - // entry处于Dummy态 - if (ep->me_key == dummy) -337: freeslot = ep; - else { - // 检查Active态entry - if (ep->me_hash == hash) { - startkey = ep->me_key; - Py_INCREF(startkey); - cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) - return NULL; - if (ep0 == mp->ma_table && ep->me_key == startkey) { - if (cmp > 0) - return ep; - } - else { - /* The compare did major nasty stuff to the - * dict: start over. - * XXX A clever adversary could prevent this - * XXX from terminating. - */ - return lookdict(mp, key, hash); - } - } - freeslot = NULL; - } -//------------------ 以上为第一检查-------------------- - - /* In the loop, me_key == dummy is by far (factor of 100s) the - least likely outcome, so test for that last. */ - - // 寻找探测链上的下一个entry - for (perturb = hash; ; perturb >>= PERTURB_SHIFT) { - i = (i << 2) + i + perturb + 1; - ep = &ep0[i & mask]; - // Unused态entry,搜索失败 - if (ep->me_key == NULL) - return freeslot == NULL ? ep : freeslot; - // 检查引用是否相同 - if (ep->me_key == key) - return ep; - // 检查值是否相同 - if (ep->me_hash == hash && ep->me_key != dummy) { - startkey = ep->me_key; - Py_INCREF(startkey); - cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); - Py_DECREF(startkey); - if (cmp < 0) - return NULL; - if (ep0 == mp->ma_table && ep->me_key == startkey) { - if (cmp > 0) - return ep; - } - else { - /* The compare did major nasty stuff to the - * dict: start over. - * XXX A clever adversary could prevent this - * XXX from terminating. - */ - return lookdict(mp, key, hash); - } - } - // 设置freeslot - else if (ep->me_key == dummy && freeslot == NULL) - freeslot = ep; - } - assert(0); /* NOT REACHED */ - return 0; -396:} -``` - -**第一次检查** - -PyDictObject中维护的entry的数量是有限的 , 而传入lookdict中的key的hash值却不一定在限定范围内 , 所以这就要求lookdict将hash值映射到某个entry上去 , lookdict采取的策略是 , 直接将hash值与entry的数量做一个`&`操作(见331行) , 该操作的结果就是entry的数量 , 也就是`ma_mask` - -之所以命名为mask而不是size , 是因为`ma_mask`会被用来进行大量的`&`操作 , 所以entry数量相关的变量被命名为`ma_mask` - - `freeslot`指向一个指示失败且立即可用的entry : - -在搜索过程中 , 如果探测链中的某个位置上 , entry处于Dummy态 , 那么如果在这个序列中搜索不成功 , 就会返回这个处于Dummy态的entry , 这个`freeslot`正是用来指向探测序列中第一个处于Dummy态的entry (`me_value`为NULL); 如果探测序列并没有Dummy态entry , 搜索失败时 , `freeslot`则指向一个处于Unused态的entry , 同样是一个能指示失败且立即可用的entry - -在元素搜索时 , 会先进行两个key的值检查 , 首先检查两个对象的hash值是否相同 , 如果不相同 , 就直接中断 ; 而如果相同 , 那么Python将通过PyObject_RichCompareBool进行比较 , 其原型如下 : - -```C -int PyObject_RichCompareBool(PyObject *v, PyObject *w, int op) -``` - -当`v op w`成立时 , 返回1 ; 不成立时 , 返回0 ; 如果在比较中发生了错误返回-1 - -在lookdict代码清单中 , 指定的Py_EQ , 表示进行相等比较操作 - -对于lookdict代码清单的前半部分 , 也就是第一次检查小结 : - -1. 根据hash值获取entry索引 , 这是冲突探测链上的第一个entry索引 -2. 两种情况下 , 搜索结束 : - 1. entry处于Unused态 , 表明冲突探测链搜索完成 , 搜索失败 - 2. `ep->me_key == key` , 表明entry的key与待搜索的key匹配 , 搜索成功 -3. 若当前entry处于Dummy态 , 设置`freeslot` -4. 检查Active态entry中的key与待查找的key是否值相同 - - -**后续操作** - -在第一个entry检查完毕后 , 后续的动作本质都是一样的 - -对于lookdict代码清单的前半部分小结 : - -1. 根据Python所采用的探测函数 , 获得探测链中的下一个待检查的entry -2. 检查到一个Unused态entry , 表明搜索失败 , 有如下两种结果 : - 1. 如果`freeslot`不为空 , 则返回`freeslot` 所指entry - 2. 如果`freeslot`为空 , 则返回该Unused态entry -3. 检查entry中的key与待查找的key是否引用相同 -4. 检查entry中的key与待查找的key是否值相同 -5. 在遍历过程中 , 如果发现Dummy态entry , 且`freeslot`未设置 , 则设置`freeslot` - -### lookdict_string - -`Python-2.7\Include\dictobject.c` - -```C -407:static PyDictEntry * - lookdict_string(PyDictObject *mp, PyObject *key, register long hash) - { - register size_t i; - register size_t perturb; - register PyDictEntry *freeslot; - register size_t mask = (size_t)mp->ma_mask; - PyDictEntry *ep0 = mp->ma_table; - register PyDictEntry *ep; - - /* Make sure this function doesn't have to handle non-string keys, - including subclasses of str; e.g., one reason to subclass - strings is to override __eq__, and for speed we don't cater to - that here. */ - // 选择搜索策略 - if (!PyString_CheckExact(key)) { - #ifdef SHOW_CONVERSION_COUNTS - ++converted; - #endif - mp->ma_lookup = lookdict; - return lookdict(mp, key, hash); - } - // 检查冲突链上第一个entry - i = hash & mask; - ep = &ep0[i]; - // entry处于Unused态,entry中的key与待搜索的key匹配 - if (ep->me_key == NULL || ep->me_key == key) - return ep; - // 第一个entry处于Dummy态,设置freeslot - if (ep->me_key == dummy) - freeslot = ep; - else { - // 检查Active态entry - if (ep->me_hash == hash && _PyString_Eq(ep->me_key, key)) - return ep; - freeslot = NULL; - } - - /* In the loop, me_key == dummy is by far (factor of 100s) the - least likely outcome, so test for that last. */ - // 遍历冲突链,检查每一个entry - for (perturb = hash; ; perturb >>= PERTURB_SHIFT) { - i = (i << 2) + i + perturb + 1; - ep = &ep0[i & mask]; - if (ep->me_key == NULL) - return freeslot == NULL ? ep : freeslot; - if (ep->me_key == key - || (ep->me_hash == hash - && ep->me_key != dummy - && _PyString_Eq(ep->me_key, key))) - return ep; - if (ep->me_key == dummy && freeslot == NULL) - freeslot = ep; - } - assert(0); /* NOT REACHED */ - return 0; -457:} -``` - -lookdict_string是一种有条件限制的搜索策略 , 即待搜索的key是一个PyStringObject对象 , 只有当假设成立时 , lookdict_string才会被使用 , 其中`_PyString_Eq`将保证能正确处理非`PyStringObject *`参数 - -其实lookdict_string仅仅是一个lookdict的优化版本 , 因为在Python中大量的使用了PyDictObject对象 , 以用来维护一个命名空间(名字空间)中变量名与变量值之间的对应关系 , 又或者是用来在为函数传递参数名与参数值的对应关系 , 而这些对象几乎都是用PyStringObject对象作为entry中的key , 所以lookdict_string的出现是很有必要的 , 它对Python整体的运行效率都有着重要的影响 - - -## 插入与删除 - -PyDictObject对象中元素的插入动作是建立在搜索的基础之上的 - -`Python-2.7\Include\dictobject.c` - -```C -512:static int -insertdict(register PyDictObject *mp, PyObject *key, long hash, PyObject *value) -{ - PyObject *old_value; - register PyDictEntry *ep; - typedef PyDictEntry *(*lookupfunc)(PyDictObject *, PyObject *, long); - - assert(mp->ma_lookup != NULL); - ep = mp->ma_lookup(mp, key, hash); - if (ep == NULL) { - Py_DECREF(key); - Py_DECREF(value); - return -1; - } - MAINTAIN_TRACKING(mp, key, value); - // 搜索成功 - if (ep->me_value != NULL) { - old_value = ep->me_value; - ep->me_value = value; - Py_DECREF(old_value); /* which **CAN** re-enter */ - Py_DECREF(key); - } - // 搜索失败 - else { - if (ep->me_key == NULL) - mp->ma_fill++; - else { - assert(ep->me_key == dummy); - Py_DECREF(dummy); - } - ep->me_key = key; - ep->me_hash = (Py_ssize_t)hash; - ep->me_value = value; - mp->ma_used++; - } - return 0; -546:} -``` - -insertdict中 , 根据搜索的结果采取不同的动作 : - -- 搜索成功 , 返回处于Active的entry , 并直接替换`me_value` -- 搜索失败 , 返回Unused或Dummy态的entry , 完整设置`me_key` , `me_hash` 和 `me_value` - -在Python中 , 对PyDictObject对象插入或设置元素两种情况 , 如下代码 : - -```python -d = {} -# entry不存在 -d[1] = 1 -# entry已存在 -d[1] = 2 -``` - -当这段代码执行时 , Python并不是直接调用insertdict , 因为insertdict需要一个hash值作为调用参数 , 所以在调用insertdict会先调用PyDict_SetItem - -`Python-2.7\Include\dictobject.c` - -```C -747:int - PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value) - { - register PyDictObject *mp; - register long hash; - register Py_ssize_t n_used; - if (!PyDict_Check(op)) { - PyErr_BadInternalCall(); - return -1; - } - assert(key); - assert(value); - mp = (PyDictObject *)op; - // 计算hash值 - if (PyString_CheckExact(key)) { - hash = ((PyStringObject *)key)->ob_shash; - if (hash == -1) - hash = PyObject_Hash(key); - } - else { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; - } - assert(mp->ma_fill <= mp->ma_mask); /* at least one empty slot */ - // 插入(key, value)元素对 - n_used = mp->ma_used; - Py_INCREF(value); - Py_INCREF(key); - // 必要时调整dict的内存空间 - if (insertdict(mp, key, hash, value) != 0) - return -1; - /* If we added a key, we can safely resize. Otherwise just return! - * If fill >= 2/3 size, adjust size. Normally, this doubles or - * quaduples the size, but it's also possible for the dict to shrink - * (if ma_fill is much larger than ma_used, meaning a lot of dict - * keys have been * deleted). - * - * Quadrupling the size improves average dictionary sparseness - * (reducing collisions) at the cost of some memory and iteration - * speed (which loops over every possible entry). It also halves - * the number of expensive resize operations in a growing dictionary. - * - * Very large dictionaries (over 50K items) use doubling instead. - * This may help applications with severe memory constraints. - */ - // 可转换为 (mp->mafill)/(mp->ma_mask+1) >= 2/3 - if (!(mp->ma_used > n_used && mp->ma_fill*3 >= (mp->ma_mask+1)*2)) - return 0; - return dictresize(mp, (mp->ma_used > 50000 ? 2 : 4) * mp->ma_used); -794:} -``` - -我们可以看到 , 在PyDict_SetItem中 , 会首先获取key的hash值 , 随后会调用insertdict来插入元素对 , 再接下来会检查是否需要改变PyDictObject内部`ma_table`所维护的内存区域的大小 - -至于如何调整 , 可以查看`dictobject.c`中的dictresize函数 , 接下来看如何删除一个元素 - -`Python-2.7\Include\dictobject.c` - -```C -796:int - PyDict_DelItem(PyObject *op, PyObject *key) - { - register PyDictObject *mp; - register long hash; - register PyDictEntry *ep; - PyObject *old_value, *old_key; - - if (!PyDict_Check(op)) { - PyErr_BadInternalCall(); - return -1; - } - assert(key); - // 同样先获取hash值 - if (!PyString_CheckExact(key) || - (hash = ((PyStringObject *) key)->ob_shash) == -1) { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; - } - // 搜索entry - mp = (PyDictObject *)op; - ep = (mp->ma_lookup)(mp, key, hash); - if (ep == NULL) - return -1; - if (ep->me_value == NULL) { - set_key_error(key); - return -1; - } - // 删除entry所维护的元素,将entry的状态转为dummy态 - old_key = ep->me_key; - Py_INCREF(dummy); - ep->me_key = dummy; - old_value = ep->me_value; - ep->me_value = NULL; - mp->ma_used--; - Py_DECREF(old_value); - Py_DECREF(old_key); - return 0; -832:} -``` - -与插入操作类似 , 先计算hash值 , 然后搜索相应的entry , 最后删除entry中维护的元素 , 并将entry从Active态变换为Dummy态 , 同时还将调整PyDictObject对象中维护table使用情况的变量 - -小结 : - -无论是插入还是删除元素 , 都会先计算hash值 , 随后进行搜索相应的entry , 随后插入或删除元素 , 转换entry的状态 ; 而PyDictObject对象元素的插入则主要是通过`freeslot`所指向的entry来进行的 - - -## 对象缓冲池 - -在PyDictObject的实现机制中 , 同样使用了缓冲池计数 , 并且其缓冲池机制与PyListObject中使用的缓冲池机制是一样的 - -`Python-2.7\Include\dictobject.c` - -```C -974:static void - dict_dealloc(register PyDictObject *mp) - { - register PyDictEntry *ep; - Py_ssize_t fill = mp->ma_fill; - PyObject_GC_UnTrack(mp); - Py_TRASHCAN_SAFE_BEGIN(mp) - // 调整dict中对象的引用计数 - for (ep = mp->ma_table; fill > 0; ep++) { - if (ep->me_key) { - --fill; - Py_DECREF(ep->me_key); - Py_XDECREF(ep->me_value); - } - } - // 释放从系统堆中申请的内存空间 - if (mp->ma_table != mp->ma_smalltable) - PyMem_DEL(mp->ma_table); - // 将被销毁的PyDictObject对象放入缓冲池 - if (numfree < PyDict_MAXFREELIST && Py_TYPE(mp) == &PyDict_Type) - free_list[numfree++] = mp; - else - Py_TYPE(mp)->tp_free((PyObject *)mp); - Py_TRASHCAN_SAFE_END(mp) -995:} -``` - -开始时 , 这个缓冲池中什么也没有 , 直到第一个PyDictObject被销毁时 , 这个缓冲池才开始接纳被缓冲的PyDictObject对象 , 与PyListObject对象一样 , 只保留了PyDictObject对象 - -但是需要注意的是 , 销毁时根据`ma_table`的两种情况处理方式也是不同的 : - -- 如果`ma_table`指向的是从系统堆申请的内存空间 (额外的内存) , 那么Python将释放这块内存空间归还给系统堆 -- 如果`ma_table`指向的是PyDictObject的`ma_smalltable` , 那么只需要调整`ma_smalltable`中的对象的引用计数就可以了 - -在创建新的PyDictObject对象时 , 如果在缓冲池中有可以使用的对象 , 则直接从缓冲池中取出使用 , 而不需要再重新创建 , 这一点在PyDict_New中就已经体现了 - -至此 , 对于Python 2.7中的dict对象就差不多了 , 对于Python 3.5.4版本的比较待后期继续 , 不过简单的对比之下就可以发现 , 在Python 3.5.4的版本中 , 新增了一个`dictnotes.txt`文件 , 而且由2.7的3个状态变成了4个状态 , 数据层次也发生了一些改变 , 比如PyDictObject从2.7中的一种形式 , 变成了两种形式 (联合表和分割表) , 新增了PyDictKeyObject对象等 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/07-Tuple\345\257\271\350\261\241.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/07-Tuple\345\257\271\350\261\241.md" deleted file mode 100644 index bf6c1a900..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/07-Tuple\345\257\271\350\261\241.md" +++ /dev/null @@ -1,250 +0,0 @@ -# Attack on Python - Tuple对象 🐍 - - - - - - - - - - -## 介绍 - -Python中的`tuple`与`str`一样 , 都属于不可变对象 , 即其所维护的数据在对象创建之后就不能再改变了 - -直接看PyTupleObject吧 - -## PyTupleObject - -`Python-2.7\Include\tupleobject.h:` - -```C -24:typedef struct { -25: PyObject_VAR_HEAD -26: PyObject *ob_item[1]; -27: -28: /* ob_item contains space for 'ob_size' elements. -29: * Items must normally not be NULL, except during construction when -30: * the tuple is not yet visible outside the function that builds it. -31: */ -32:} PyTupleObject; -``` - -通过上面的代码清单 , 我们可以看到 , PyTupleObject除了是一个不可变对象之外 , 它还是一个变长对象 ; 而`ob_item` 则为指向元素列表的指针 - -通过前面的整理 , 对于这些再熟悉不过了 - - -## 创建与维护 - -PyTupleObject对象的创建同其他对象一样 , 其是通过`PyTuple_New`来创建的 - -`Python-2.7\Objects\tupleobject.c` - -```C - 48:PyObject * - PyTuple_New(register Py_ssize_t size) - { - register PyTupleObject *op; - Py_ssize_t i; - // 大小为负数 - if (size < 0) { - PyErr_BadInternalCall(); - return NULL; - } - #if PyTuple_MAXSAVESIZE > 0 - // 如果是空元组,直接取free_list第一个返回 - if (size == 0 && free_list[0]) { - op = free_list[0]; - Py_INCREF(op); - #ifdef COUNT_ALLOCS - tuple_zero_allocs++; - #endif - return (PyObject *) op; - } - // 缓冲池可用 - if (size < PyTuple_MAXSAVESIZE && (op = free_list[size]) != NULL) { - free_list[size] = (PyTupleObject *) op->ob_item[0]; - numfree[size]--; - #ifdef COUNT_ALLOCS - fast_tuple_allocs++; - #endif - /* Inline PyObject_InitVar */ - #ifdef Py_TRACE_REFS - Py_SIZE(op) = size; - Py_TYPE(op) = &PyTuple_Type; - #endif - _Py_NewReference((PyObject *)op); - } - // 缓冲池不可用 - else - #endif - { - // 通过传入的size参数计算需要的内存总量 - Py_ssize_t nbytes = size * sizeof(PyObject *); - /* Check for overflow */ - if (nbytes / sizeof(PyObject *) != (size_t)size || - (nbytes > PY_SSIZE_T_MAX - sizeof(PyTupleObject) - sizeof(PyObject *))) - { - return PyErr_NoMemory(); - } - // 创建PyTupleObject对象 - op = PyObject_GC_NewVar(PyTupleObject, &PyTuple_Type, size); - if (op == NULL) - return NULL; - } - // 初始化每个元素 - for (i=0; i < size; i++) - op->ob_item[i] = NULL; - #if PyTuple_MAXSAVESIZE > 0 - // 第一次分配时将空数组放入缓冲池的第一个位置 - if (size == 0) { - free_list[0] = op; - ++numfree[0]; - Py_INCREF(op); /* extra INCREF so that this is never freed */ - } - #endif - #ifdef SHOW_TRACK_COUNT - count_tracked++; - #endif - _PyObject_GC_TRACK(op); - return (PyObject *) op; -108:} -``` - -分析 : - -- 我们不难发现 , PyTuple_New与PyList_New有很多相同之处 , 首先这个函数同样接受一个size参数 , 也就是我们在创建时指定PyTupleObject对象的初始元素个数 , 不同的地方在于两种对象在计算需要的内存总量的时机不同 -- 随后检查缓冲池是否可用 , 如果可用 , 那么不用多说 ; 如果缓冲池不可用 , 那么现在才计算所需内存总量 , 而在PyList_New中 , 无论缓冲池是否可用都会计算其所需内存总量 -- 缓冲池不可用之后 , 接下来就是创建PyTupleObject对象了 , 再然后初始化每个元素 -- 最后的一步 , 则是将空元组放入缓冲池的第一位置 , 在整个Python的执行过程中 , 这个操作只会执行一次 - -而对于缓冲池`free_list` , 如下 : - -`Python-2.7\Objects\tupleobject.c` - -```C - 7:#ifndef PyTuple_MAXSAVESIZE - 8:#define PyTuple_MAXSAVESIZE 20 /* Largest tuple to save on free list */ - 9:#endif -10:#ifndef PyTuple_MAXFREELIST -11:#define PyTuple_MAXFREELIST 2000 /* Maximum number of tuples of each size to save */ -12:#endif -13: -14:#if PyTuple_MAXSAVESIZE > 0 -15:/* Entries 1 up to PyTuple_MAXSAVESIZE are free lists, entry 0 is the empty -16: tuple () of which at most one instance will be allocated. -17:*/ -``` - -通过定义我们可以看到 , PyTupleObject对象缓冲池中维护的最大个数为2000 , 但是注意 , 不是所有的元组都会放入缓冲池 , 不用想也知道 , 这肯定是有一个界限的 , 也就是要小于`PyTuple_MAXSAVESIZE`的 , 从上面我们知道 , 这个值为20 , 也就是说只有`tuple`长度小于20的PyTupleObject才能被放入缓冲池 - -并且缓冲池的第一个位置是留给`()`的 (有且仅有一个) , 也就是空元组 ; 对于空元组它是在PyTupleObject对象创建时就已经被放入缓冲池了的 , 而其他的PyTupleObject对象什么时候会放入缓冲池中 , 与PyListObject对象也是一样的 , 就是在对象被销毁时 , 这一点同前面的篇章一样 , 放在最后来说 - - -## 设置元素 - -与PyListObject一样 , 在我们创建第一个PyTupleObject对象时 , 这时候缓冲池是不可用的 , 于是会调用PyObject_GC_New在系统堆上创建一个新的PyTupleObject对象 - -而当我们设置元素时 , 在Python内部会调用PyTupe_SetItem来完成这个动作 - -```C -135:int - PyTuple_SetItem(register PyObject *op, register Py_ssize_t i, PyObject *newitem) - { - register PyObject *olditem; - register PyObject **p; - // 类型与引用计数检查 - if (!PyTuple_Check(op) || op->ob_refcnt != 1) { - Py_XDECREF(newitem); - PyErr_BadInternalCall(); - return -1; - } - // 索引有效性检查 - if (i < 0 || i >= Py_SIZE(op)) { - Py_XDECREF(newitem); - PyErr_SetString(PyExc_IndexError, - "tuple assignment index out of range"); - return -1; - } - p = ((PyTupleObject *)op) -> ob_item + i; - olditem = *p; - *p = newitem; - Py_XDECREF(olditem); - return 0; -156:} -``` - -与PyListObject非常相似 , 首先进行类型检查 ,随后进行索引的有效性检查 , 当这两者都通过后 , 将新设置的元素指针放到指定的位置 , 然后调整引用计数 , 将这个位置原来存放的对象的引用计数减1 - -PyTupleObject对象是不可变对象 , 所以没有类似于PyListObject对象的插入等操作 - - -## 对象缓冲池 - -通过前面我们已经知道 , PyTupleObject对象的缓冲池机制在创建PyTupleObject对象时 , 仅仅会将空元组加入缓冲池中 , 而对于其他的PyTupleObject对象并没有出现在PyTuple_New中 - -其实PyTupleObject对象的缓冲池与PyListObject对象是一样 , 是在其销毁时添加的 - -`Python-2.7\Objects\tupleobject.c` - -```C -210:static void - tupledealloc(register PyTupleObject *op) - { - register Py_ssize_t i; - register Py_ssize_t len = Py_SIZE(op); - PyObject_GC_UnTrack(op); - Py_TRASHCAN_SAFE_BEGIN(op) - // 销毁PyTupeObject对象维护的元素列表 - if (len > 0) { - i = len; - while (--i >= 0) - Py_XDECREF(op->ob_item[i]); - #if PyTuple_MAXSAVESIZE > 0 - // 检查是否满足放入缓冲池的条件 - if (len < PyTuple_MAXSAVESIZE && - numfree[len] < PyTuple_MAXFREELIST && - Py_TYPE(op) == &PyTuple_Type) - { - op->ob_item[0] = (PyObject *) free_list[len]; - numfree[len]++; - free_list[len] = op; - goto done; /* return */ - } - #endif - } - Py_TYPE(op)->tp_free((PyObject *)op); - done: - Py_TRASHCAN_SAFE_END(op) -236:} -``` - -根据上面的代码清单 , 可以看出 , 在PyTupleObject对象进行销毁时 , 首先会销毁PyTupleObject对象维护的元素列表 , 然后判断该PyTupleObject的大小是否超过缓冲池可缓冲的最大大小 (`PyTuple_MAXSAVESIZE=20`) , 以及缓冲池是否已满 , 对象是否为PyTupleObject对象 - -随后 , 如果满足使用缓冲池的要求 , 那么就将这个PyTupleObject对象放入缓冲池中 , 这时这个PyTupleObject对象中的元素列表是已经被销毁了的 ; 如果不满足就直接销毁整个PyTupleObject对象 - -小结 : - -通过与PyListObject对象的实现相比较 , 其与PyTupleObject的差异基本取决于一个是可变对象 , 一个是不可变对象 , 我们可以看到在设置元素和缓冲池机制 , 在两种对象的源码上差别都非常的小 ; 而在对象创建时有所不同的是 , PyTupleObject对象会在创建时将空元组放入缓冲池中 (第一个位置) , 而PyListObject对象则不会 , 如下小实验 : - -```python -# Python 2.7 ->>> list1 = [] ->>> list2 = [] ->>> id(list1) -79581256L ->>> id(list2) -79684744L ->>> tuple1 = () ->>> tuple2 = () ->>> id(tuple1) -77598792L ->>> id(tuple2) -77598792L -# Python 3.5.3结果相同 -``` - -由于缓冲池实现的小差异 , 空元组是不会反复创建的 , 并且在缓冲池的第一位置 - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/08-\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/08-\345\236\203\345\234\276\345\233\236\346\224\266.md" deleted file mode 100644 index 3c7059f07..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/08-\345\236\203\345\234\276\345\233\236\346\224\266.md" +++ /dev/null @@ -1,113 +0,0 @@ -# Attack on Python - 垃圾回收 🐍 - - - - - - - - - - -## 介绍 - -引用计数在对Python内置数据类型的分析时 , 已经见过太多次了 , 就是通过对象中的`ob_refcnt`变量来实现的 - -在Python中引用计数是一种垃圾收集机制 , 并且是一种最直观 , 最简单的垃圾收集技术 - -虽然引用计数必须在每次分配和释放内存的时候加入管理引用计数的动作 , 然而与其他主流的垃圾收集技术相比 , 引用计数有一个最大的优点 , 即实时性 , 任何内存 , 一旦没有指向它的引用 , 就会立即被回收 ; 而其他的垃圾收集计数必须在某种特殊条件下 (比如内存分配失败) 才能进行无效内存的回收 - -引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放 , 引用赋值的次数是成正比的 , 这是Python的一个弱点 , 因此在Python内置数据类型中就大量使用了对象缓冲池机制 , 就是为了竭力弥补引用计数机制的软肋 - -除了执行效率这个软肋之外 , 引用计数还存在一个致命的弱点 , 那就是循环引用 - -## 循环引用 - -我们知道 , 当一个对象的引用被创建或复制时 , 对象的引用计数就会加1 ; 而当一个对象的引用被销毁时 , 对象的引用计数就会减1 ; 如果对象的引用计数减少为0 , 那么就以为着这个对象不会被任何人使用 , 那么就可以进行回收了 - -而引用计数的另一个现象就是循环引用了 , 就相当于有两个对象`a`和`b` , 其中`a`引用了`b` , `b`引用了`a` , 这样`a`和`b`的引用计数都为1 , 并且永远都不会为0 , 这就意味着 , 这两个对象永远都不会被回收了 , 这就是循环引用 , `a`与`b`形成了一个引用循环 , 示例如下 : - -```python -# 我们让list1中包含list2的引用,而list2中又包含list1的引用,形成引用循环 ->>> list1 = [] ->>> list2 = [] ->>> list1.append(list2) -# 此时还没有形成引用循环 ->>> list1 -[[]] -# 循环引用 ->>> list2.append(list1) ->>> l1 -[[[...]]] ->>> l2 -[[[...]]] -''' -[...]:这就是list循环引用的结果 -''' -``` - -除了上述两个对象互相引用之外 , 还可以引用自身 , 示例如下 : - -```python ->>> list3 = [] ->>> list3.append(list3) ->>> list3 -[[...]] -``` - -循环引用与手动进行内存管理所产生的内存泄漏毫无区别 , 不过循环引用对于`int`或者`str`类型明显是不存在的 - -所以为了解决循环引用的问题 , Python引入了主流垃圾收集技术中的标记——清除和分代收集两种技术来填补其内存管理机制中最致命的漏洞 - -## 标记清除 - -垃圾收集机制一般分为两个阶段 : 垃圾检测和垃圾回收 - -垃圾检测是从所有的已分配的内存中区别出可以回收的内存和不可回收的内存 , 而垃圾回收则是使系统重新掌握在垃圾检测阶段被标识出来的可回收内存块 - -对于标记——清除方法其简要工作过程如下 : - -- 寻找根对象的集合 , 所谓根对象就是一些全局引用和函数栈中的引用 , 这些引用的对象是不可被删除的, 而这个根对象集合也是垃圾检测动作的起点 -- 从根对象的集合 , 沿着根对象集合中的每一个引用 , 如果能到达某个对象A , 则A称为可达的 , 可达的对象也不可被删除 , 这个阶段就是垃圾检测阶段 -- 当垃圾检测阶段结束后 , 所有的对象分为了可达的和不可达的两部分 , 所有的可达的对象都必须予以保留 , 而所有的不可达对象所占用的内存将被回收 , 这就是垃圾回收阶段 - -## 分代回收 - -我们的开发程序 , 其一定比例的内存块的生存周期都比较短 , 通常是几百万条机器指令的时间 , 而只有剩下的极少部分内存块 , 生存周期比较长 , 而对于不同的语言 , 不同的应用程序 , 生存周期比较短的内存块的比例通常在80%到98%之间游走 - -从上面我们知道 , 标记——清除技术所带来的额外操作实际上与系统中总的内存块的数量是相关的 , 当需要回收的内存块越多时 , 垃圾检测带来的额外操作就越多 , 而垃圾回收带来的额外操作就越少 - -所以通常为了提高垃圾收集的效率 , 我们就可以采用一种以空间换时间的策略 , 分代回收计数 , 这也是当前支撑着Java的关键技术 - -分代回收 : 将系统中的所有内存块根据其存活时间划分为不同的集合 , 每一个集合就称为一个 "代" , 垃圾收集的频率随着 "代" 的存活时间的增大而减小 - -也就是说 , 活得越长的对象 , 就越可能不是垃圾 , 就应该越少去收集 . 而这个存活时间通常就是利用经过了几次垃圾收集动作来衡量 ; 如果一个对象经过的垃圾收集次数越多, 那么显然 , 其存活时间就越长 - -在Python中 , 一个 "代" 就是一个链表 , Python采用了三代的分代收集机制 - -`Python-2.7\Modules\gcmodule.c` - -```C -32:struct gc_generation { -33: PyGC_Head head; - /* 回收阀值 */ -34: int threshold; /* collection threshold */ - /* 实时个数 */ -35: int count; /* count of allocations or collections of younger -36: generations */ -37:}; - -39:#define NUM_GENERATIONS 3 -40:#define GEN_HEAD(n) (&generations[n].head) -41: -42:/* linked lists of container objects */ -43:static struct gc_generation generations[NUM_GENERATIONS] = { -44: /* PyGC_Head, threshold, count */ - /* 第0代,可收集700个container对象,一旦超出就立即触发垃圾回收机制 */ -45: {{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0}, -46: {{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0}, -47: {{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0}, -48:}; -49: -50:PyGC_Head *_PyGC_generation0 = GEN_HEAD(0); -``` diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/09-\345\205\203\347\261\273.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/09-\345\205\203\347\261\273.md" deleted file mode 100644 index d652fb9d1..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/09-\345\205\203\347\261\273.md" +++ /dev/null @@ -1,249 +0,0 @@ -# Attack on Python - 元类 🐍 - - - - - - - - - - -## 介绍 - -[元类](https://zh.wikipedia.org/wiki/%E5%85%83%E7%B1%BB) ( metaclass ) , 是一种实例是类的类 - -普通的类定义的是特定对象的行为 , 元类定义的则是特定的类及其对象的行为 , 不是所有面向对象编程语言都支持元类 - -## type - -元类在 Wiki 中的解释已经说的很明确了 , 它是一种实例是类的类 , 这也就意味着元类可以创造类 - -这么说你可能会不太清晰 , 我们从问题出发 , 在 Python 中是谁创建了类 , 也就是说 Python 中的元类是谁? - -如果你看过 Python 这一部分的源码 , 那么想必你对这个问题肯定了然于心 , 没错就是 `type` 类 - -```python ->>> object.__class__ - -``` - -至于 `type` 类为什么是元类 , 你可以从我的另一篇文章中获得答案 [《对象的创建》]() - -看下面的例子 : - -```python ->>> class Foo: -... pass -... ->>> f = Foo() -``` - -在这个例子中 , `Foo()` , 也就是调用 `Foo` 的 `__call__` 方法 , 它会做两件事情 : - -1. 调用 `__new__` , 创建对象 -2. 调用 `__init__` , 初始化对象 - -但是注意 , 这个 `__call__` 是 `object` 类的 , 因为 Python 3 中所有的类都默认继承了 `object` , 至于 Python 2 没什么好谈的 , 相信你查查就能知道 - -我们本就可以通过重载 `__new__` 来控制对象的创建 , 如下 : - -```python -def new(cls): - x = object.__new__(cls) - x.attr = 100 - return x - -Foo.__new__ = new - -f = Foo() -print(f.attr) - -g = Foo() -print(g.attr) -""" -执行结果如下: -100 -100 -""" -``` - -但是不同的是 , 你对 `type` 不能这么干 , Python 也不允许你这么干 , 如果唯一的元类都被动了 , 那就乱套了 - -```python -def new(cls): - x = type.__new__(cls) - x.attr = 100 - return x - -type.__new__ = new - -""" -Traceback (most recent call last): - File "", line 1, in -TypeError: can't set attributes of built-in/extension type 'type' -""" -``` - -所以你从这里也可以知道 , `type` 和 `object` 的区别就在于 : - -- `type` 的 `__new__` , 返回了一个类 -- `object` 的 `__new__` , 返回了一个对象实例 - -如果我们要定义一个元类 , 只需要如下 : - -```python -class Meta(type): - def __new__(cls, name, bases, dct): - x = super().__new__(cls, name, bases, dct) - x.attr = 100 - return x -``` - -当然你也看出来了 , 这只是继承 , 要让它真正成为元类 , 你还需要如下 : - -```python -class Foo(metaclass=Meta): - pass - -print(Foo.attr) -``` - -我们再看看这个 `Foo` 和普通的对象有什么不同 : - -```python -class Meta(type): - def __new__(cls, name, bases, dct): - x = super().__new__(cls, name, bases, dct) - x.attr = 100 - return x - -class Foo(metaclass=Meta): - pass - -class Bar(Foo): - pass - -print(type(Meta)) -print(type(object)) -print(type(Foo)) -print(type(Bar)) - -""" -执行结果如下: - - - - -""" -``` - -当指定了 `metaclass` 之后 , 类的创建将不再由 `type` 负责 , 而是由元类 `Meta` 负责 , 也就是说 `type` 类与这类的 `Meta` 类都是元类 , 大家是同一级 - -## 元类的作用 - -元类可以用来改变类的行为 , 这和类并没有什么差别 , 因为我们定义类也可以改变对象的行为 , 我们来看一个例子 - -```python -class Foo: - pass - -# 调用__call__ -f = Foo() - -# 如果我们想改变 () 也就是 __call__的行为要怎么做? -# 当然不可能是在Foo类中重载 __call__ 因为那是控制 Foo 实例化出来的对象的 -# 所以我们需要用元类来控制它 - -# 单例模式直接用metaclass来实现, 而且它是线程安全的 -class SingletonMeta(type): - _instances = {} - - def __call__(self, *args, **kwargs): - if self not in self._instances: - self._instances[self] = super(SingletonMeta, self).__call__(*args, **kwargs) - return self._instances[self] - - -class Singleton(metaclass=SingletonMeta): - pass - -a = Singleton() -b = Singleton() -c = Singleton() -d = Singleton() -e = Singleton() -``` - -如果你想要改变类的行为 , 除了 Python 默认提供的一个魔术方法 (`__new__`) , 你必须通过元类来改变 - -**因为 `__new__` 是唯一一个第一个参数不是 `self` 而是 `cls` 的魔术方法** - -所以上面这个例子 , 除了用元类 , 你也可以通过覆盖 `__new__` 来实现 - -元类其实就是一个类工厂 , 而类则是对象工厂 , 但是实际上我们不需要使用元类同样可以达到生产的目的 , 因为通常我们不会需要去改变类的行为 , 需要改变的是对象的行为 - -看下面几个例子 - -**继承** - -```python ->>> class Base: -... attr = 100 -... - ->>> class X(Base): -... pass -... - ->>> class Y(Base): -... pass -... - ->>> class Z(Base): -... pass -... - ->>> X.attr -100 ->>> Y.attr -100 ->>> Z.attr -100 -``` - -**类装饰器** - -```python ->>> def decorator(cls): -... class NewClass(cls): -... attr = 100 -... return NewClass -... ->>> @decorator -... class X: -... pass -... ->>> @decorator -... class Y: -... pass -... ->>> @decorator -... class Z: -... pass -... - ->>> X.attr -100 ->>> Y.attr -100 ->>> Z.attr -100 -``` - -总而言之 , 元类的作用就是用来创造类的 , 我们通常更多的是使用继承 (也就是利用抽象) 的方式来达到我们的目的 - -Python 之禅中这么说到 : 元类是深层次的魔术代码 , 99% 的用户都不需要关心它 , 如果你好奇你是否需要 , 那你就不需要 , 真正需元类的人 , 是很清楚他们需要的 , 并且 , 不需要一个理由来解释 - -简单的说 , 元类不适合在生产的代码中使用 , 它更适合用来设计 , 比如 Django , SQLAlchemy 中 , 你就能发现它的身影 , 总而言之 , **元类控制类 , 类控制对象** - diff --git "a/01-Python/07-\345\206\205\345\255\230\347\257\207/README.md" "b/01-Python/07-\345\206\205\345\255\230\347\257\207/README.md" deleted file mode 100644 index b512ce7e7..000000000 --- "a/01-Python/07-\345\206\205\345\255\230\347\257\207/README.md" +++ /dev/null @@ -1,80 +0,0 @@ -# Attack on Python - 内存篇 🐍 - - - - - - - - - - -## Python总体架构 - -Python总体分为三个部分 , 即文件组 , Python核心 (解释器) , 运行环境 , 如下 : - -``` - File Groups Python Core Runtime Environment - INTERPRETER -+---------------+ +----------------+ -| Core Modules | | Scanner | ↓ -+---------------+ +----------------+ +--------------------------+ -| Library | | Parser | ↓ | Object/Type Structures | -+---------------+ +----------------+ +--------------------------+ -| User-defined | | Compiler | ↓ | Memory Allocator | -| Modules | +----------------+ +--------------------------+ -+---------------+ | Code Evauator | ↓ | Current State of Python | - +----------------+ +--------------------------+ -``` - -## 源码组织 - -我们可以在Python官网中获取源码 , 即http://www.python.org - -本目录下深入整理主要参考Python 2.7 与Python 3.5.4源码 - -参考书籍 : Python源码剖析——深度探索动态语言核心技术 - -Python 源码目录结构如下 : - -``` -Python -├── Doc -├── Grammar -├── Include -├── Lib -├── Mac -├── Misc -├── Modules -├── Objects -├── Parser -├── PC -├── PCbuild -├── Programs -├── Python -└── Tools -``` - -主要说明 , 其中加粗部分为主要分析对象 : - - -**`Include`** : 该目录下包含了Python提供的所有头文件 , 如果用户需要自己用C或C++来编写自定义模块扩展Python , 那么就需要用到这里提供的头文件 - - -`Lib` : 该目录包含了Python自带的所有标准库 , Lib中的库都是用Python语言编写的 - - -`Modules` : 该目录中包含了所有用C语言编写的模块 , 比如random , cStringIO等 ; Modules中的模块时那些对速度要求非常严格的模块 , 而有一些对速度没有太严格要求的模块 , 比如os , 就是用Python编写的 , 并且放在Lib目录下 - - -`Parser` : 该目录中包含了Python解释器中的Scanner和Parser部分 , 即对Python源代码进行词法分析和语法分析的部分 ; 除了这些 , Parser目录下还包含了一些有用的工具 , 这些工具能够根据Python语言的语法自动生成Python语言的词法和语法分析器 , 与YACC非常类似 - - -**`Objects`** : 该目录中包含了所有Python的内建对象 , 包括整数 , list , dict等 , 同时 , 该目录还包括了Python在运行时需要的所有的内部使用对象的实现 - - -`Python` : 该目录下包含了Pyton解释器中的Compiler和执行引擎部分 , 是Python运行的核心所在 - - -`PCBuild` : 包含了VS使用的工程文件 - diff --git "a/01-Python/08-\347\225\252\345\244\226\347\257\207/04-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213PyMySQL.md" "b/01-Python/08-\347\225\252\345\244\226\347\257\207/04-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213PyMySQL.md" deleted file mode 100644 index 8c4ea4073..000000000 --- "a/01-Python/08-\347\225\252\345\244\226\347\257\207/04-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213PyMySQL.md" +++ /dev/null @@ -1,276 +0,0 @@ -# Python - 第三方库之PyMySQL - - - - - - - - - - - - -## 介绍 🍀 - -pymysql是用于Python 3.x 链接MySQL数据库的一个第三方库 , 其使用方法和MySQLdb几乎相同 , pymysql的目的就是为了称为MySQLdb的替代品 , 因为MySQLdb不支持Python 3.x以后的版本 - -安装 - -```cmd -$ pip install PyMySQL -``` - -包内容 - -```python -PACKAGE CONTENTS - _compat - _socketio - charset - connections - constants (package) - converters - cursors - err - optionfile - tests (package) - times - util -``` - -## 使用 🍀 - -包中我们主要需要了解`connectinos.py` 中的内容 - -在pymysql包中我们只需要使用`Connect()` 来创建一个Connection对象 - -```python -def Connect(*args, **kwargs): - """ - Connect to the database; see connections.Connection.__init__() for - more information. - """ - from .connections import Connection - return Connection(*args, **kwargs) # 返回一个Connection对象 -``` - -Connection.\_\_init \_\_() 参数如下 - -```python -Connect(*args, **kwargs) - Establish a connection to the MySQL database. Accepts several - arguments: - - host: Host where the database server is located - user: Username to log in as - password: Password to use. - database: Database to use, None to not use a particular one. - port: MySQL port to use, default is usually OK. (default: 3306) - bind_address: When the client has multiple network interfaces, specify - the interface from which to connect to the host. Argument can be - a hostname or an IP address. - unix_socket: Optionally, you can use a unix socket rather than TCP/IP. - charset: Charset you want to use. - sql_mode: Default SQL_MODE to use. - read_default_file: - Specifies my.cnf file to read these parameters from under the [client] section. - conv: - Conversion dictionary to use instead of the default one. - This is used to provide custom marshalling and unmarshaling of types. - See converters. - use_unicode: - Whether or not to default to unicode strings. - This option defaults to true for Py3k. - client_flag: Custom flags to send to MySQL. Find potential values in constants.CLIENT. - cursorclass: Custom cursor class to use. - init_command: Initial SQL statement to run when connection is established. - connect_timeout: Timeout before throwing an exception when connecting. - (default: 10, min: 1, max: 31536000) - ssl: - A dict of arguments similar to mysql_ssl_set()'s parameters. - For now the capath and cipher arguments are not supported. - read_default_group: Group to read from in the configuration file. - compress; Not supported - named_pipe: Not supported - autocommit: Autocommit mode. None means use server default. (default: False) - local_infile: Boolean to enable the use of LOAD DATA LOCAL command. (default: False) - max_allowed_packet: Max size of packet sent to server in bytes. (default: 16MB) - Only used to limit size of "LOAD LOCAL INFILE" data packet smaller than default (16KB). - defer_connect: Don't explicitly connect on contruction - wait for connect call. - (default: False) - auth_plugin_map: A dict of plugin names to a class that processes that plugin. - The class will take the Connection object as the argument to the constructor. - The class needs an authenticate method taking an authentication packet as - an argument. For the dialog plugin, a prompt(echo, prompt) method can be used - (if no authenticate method) for returning a string from the user. (experimental) - db: Alias for database. (for compatibility to MySQLdb) - passwd: Alias for password. (for compatibility to MySQLdb) -``` - -### 连接数据库 🍀 - -```python -import pymysql -# 连接MySQL数据库 -connection = pymysql.connect(host='localhost', - port=3306, - user='root', - password='myroot', - db='mydatabase', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) -``` - -pymysql包中的`cursors.py` 中的`class Cursor(object)` 可供我们建立与数据库进行交互的对象 , cursor(游标) , 下面就开始与数据库进行交互了 - -### 创建表 🍀 - -```mysql -import pymysql.cursors -# 连接MySQL数据库 -connection = pymysql.connect(host='localhost', - port=3306, - user='root', - password='myroot', - db='mydatabase', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) -try: - # 创建游标实例 - with connection.cursor() as cursor: - sql = """CREATE TABLE EMPLOYEE ( - FIRST_NAME CHAR(20) NOT NULL, - LAST_NAME CHAR(20), - AGE INT, - SEX CHAR(1), - INCOME FLOAT );""" - # 执行sql,并返回受影响行数 - cursor.execute(sql) - # executemany()可一次性执行多个sql语句,提高了多行插入的性能 - # 提交,不然无法保存新建或者修改的数据 - connection.commit() -finally: - connection.close() -``` - -**execute介绍** - -```python -def execute(self, query, args=None): - """Execute a query - :param str query: Query to execute. - :param args: parameters used with query. (optional) - :type args: tuple, list or dict - - :return: Number of affected rows - :rtype: int - - If args is a list or tuple, %s can be used as a placeholder in the query. - If args is a dict, %(name)s can be used as a placeholder in the query. - """ -# list example -cursor.execute("update hosts set host = '1.1.1.2' where nid > %s", (1,)) -# tuple example -cursor.execute("insert into hosts(host,color_id) values(%s,%s)", [("1.1.1.11",1),("1.1.1.11",2)]) -``` - -### 查询表 🍀 - -Python查询MySQL获取数据使用方法如下 : - -- fetchone(self) : 获取下一行查询结果 -- fetchmany(self, size=None) : 获取`size`行数的查询结果 -- fetchall(self) : 获取全部的返回结果 -- rowcount : 这是一个只读属性 , 并返回执行execute() 方法后影响的行数 - -在fetch数据时按照顺序进行 , 可以使用scroll(num, mode)来移动游标位置 , 如 : - -- cursor.scroll(1, mode='relative') , 相对当前位置移动 -- cursor.scroll(2, mode='absolute') , 相对绝对位置移动 - -```python -import pymysql.cursors -# 连接MySQL数据库 -connection = pymysql.connect(host='localhost', - port=3306, - user='root', - password='myroot', - db='mydatabase', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) -try: - # 创建游标实例 - with connection.cursor() as cursor: - sql = "SELECT * FROM user_info" - # 执行sql,并返回受影响行数 - cursor.execute(sql) - # 查询结果 - result = cursor.fetchall() - print(result) - # 提交 - connection.commit() -finally: - connection.close() -''' -执行结果: -[{'username': 'Lyon', 'id': 1, 'password': '456'}] -''' -``` - -注意 : fetch默认获取的数据是元组类型 , 可以在建立cursor(游标)对象时 , 设置cursor属性进行修改 , 如设置为字典类型 : `cursor(cursor=pymysql.cursors.DictCursor)` - -获取最新自增ID : `cursor.lastrowid` - -### 修改表 🍀 - -```python -import pymysql.cursors -# 连接MySQL数据库 -connection = pymysql.connect(host='localhost', - port=3306, - user='root', - password='myroot', - db='mydatabase', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) -try: - # 创建游标实例 - with connection.cursor() as cursor: - sql = "UPDATE user_info SET password = '456' WHERE username = 'Lyon'" - # 执行sql,并返回受影响行数 - effect_row = cursor.execute(sql) - print(effect_row) - # 提交 - connection.commit() -except: - # 发生错误时回滚 - connection.rollback() -# 关闭连接 -connection.close() -``` - -### 删除表 🍀 - -```python -import pymysql.cursors -# 连接MySQL数据库 -connection = pymysql.connect(host='localhost', - port=3306, - user='root', - password='myroot', - db='mydatabase', - charset='utf8mb4', - cursorclass=pymysql.cursors.DictCursor) -try: - # 创建游标实例 - with connection.cursor() as cursor: - sql = "DROP TABLE EMPLOYEE" - # 执行sql,并返回影响行数 - cursor.execute(sql) - # 提交 - connection.commit() -finally: - # 关闭连接 - connection.close() -``` \ No newline at end of file diff --git "a/01-Python/08-\347\225\252\345\244\226\347\257\207/05-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213MySQLdb.md" "b/01-Python/08-\347\225\252\345\244\226\347\257\207/05-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213MySQLdb.md" deleted file mode 100644 index 948fb7eb0..000000000 --- "a/01-Python/08-\347\225\252\345\244\226\347\257\207/05-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213MySQLdb.md" +++ /dev/null @@ -1,223 +0,0 @@ -# Python - 第三方库之MySQLdb - - - - - - - - - - - - -## 介绍 🍀 - -MySQLdb是用于Python链接MySQL数据库的接口 , 它实现了Python数据库API规范V2.0 , 基于MySQL C API 上建立的 - -Python DB-API使用流程 : - -1. 导入API模块 -2. 获取与数据的连接 -3. 执行SQL语句和存储过程 -4. 关闭数据库连接 - -MySQLdb只支持Python 3.x之前的版本 , 在Python 3.x中则是用PyMySQL来代替 - -安装 - -```python -https://sourceforge.net/projects/mysql-python/ -# 安装相关教程可以通过google,baidu等进行查找 -``` - -在上一篇已经介绍了PyMySQL , MySQLdb的用户与PyMySQL是一样的 , 所以这篇直接以实例进行整理 , 并补充对于事务的说明 - -## 连接数据库 🍀 - -```python -import MySQLdb -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -``` - -## 创建表 🍀 - -```python -import MySQLdb -# 连接数据库 -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -# 创建游标 -cursor = connection.cursor() -# 定义sql语句 -sql = """CREATE TABLE EMPLOYEE ( - FIRST_NAME CHAR(20) NOT NULL, - LAST_NAME CHAR(20), - AGE INT, - SEX CHAR(1), - INCOME FLOAT )""" -# 执行sql -cursor.execute(sql) -# 关闭连接 -connection.close() -``` - -## 查询表 🍀 - -查询方法如下 : - -- fetchone() : 获取下一条查询结果 , 结果集是一个对象 -- fetchall() : 获取全部查询结果 -- rowcount : 这是一个只读属性 , 并返回执行execute() 方法后的影响行数 - -```python -import MySQLdb -# 连接数据库 -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -# 创建游标 -cursor = connection.cursor() -# 定义sql语句 -sql = "SELECT * FROM EMPLOYEE \ - WHERE INCOME > '%d'" % (1000) -try: - # 执行SQL语句 - cursor.execute(sql) - # 获取所有记录列表 - results = cursor.fetchall() - for row in results: - fname = row[0] - lname = row[1] - age = row[2] - sex = row[3] - income = row[4] - # 打印结果 - print "fname=%s,lname=%s,age=%d,sex=%s,income=%d" % \ - (fname, lname, age, sex, income ) -except: - print "Error: unable to fecth data" -# 关闭连接 -connection.close() -``` - -## 修改表 🍀 - -插入数据 - -```python -import MySQLdb -# 连接数据库 -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -# 创建游标 -cursor = connection.cursor() -# 定义sql语句 -sql = """INSERT INTO EMPLOYEE(FIRST_NAME, - LAST_NAME, AGE, SEX, INCOME) - VALUES ('Mac', 'Mohan', 20, 'M', 2000)""" -try: - # 执行sql语句 - cursor.execute(sql) - # 提交到数据库执行 - connection.commit() -except: - # 出现异常回滚 - connection.rollback() -# 关闭连接 -connection.close() -``` - -更新数据 - -```python -import MySQLdb -# 连接数据库 -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -# 创建游标 -cursor = connection.cursor() -# 定义sql语句 -sql = "UPDATE EMPLOYEE SET AGE = AGE + 1 WHERE SEX = '%c'" % ('M') -try: - # 执行SQL语句 - cursor.execute(sql) - # 提交到数据库执行 - connection.commit() -except: - # 发生错误时回滚 - connection.rollback() -# 关闭连接 -connection.close() -``` - -## 删除表 🍀 - -```python -import MySQLdb -# 连接数据库 -connection = MySQLdb.Connect(host='localhost', - user='root', - passwd='myroot', - db='test', - port='3306', - charset='utf8') -# 创建游标 -cursor = connection.cursor() -# 定义sql语句 -sql = "DELETE FROM EMPLOYEE WHERE AGE > '%d'" % (20) -try: - # 执行SQL语句 - cursor.execute(sql) - # 提交修改 - connection.commit() -except: - # 发生错误时回滚 - connection.rollback() -# 关闭连接 -connection.close() -``` - -## 事务 🍀 - -事务机制是为了确保数据的一致性 - -事务应该具有4个属性 : - -1. 原子性 : 一个事务是一个不可分割的工作单位 , 事务中包括的诸操作要么都做 , 要么都不做 -2. 一致性 : 事务必须是数据库从一个一致性状态变到另一个一致性状态 , 一致性与原子性是密切相关的 -3. 隔离性 : 一个事务的执行不能被其他事务干扰 , 即一个事务内部的操作及使用的数据对并发的其他事务是隔离的 , 并发执行的各个事务之间不能互相干扰 -4. 持久性 : 也成为永久性 , 指一个事务一旦提交 , 它对数据库中数据的改变就应该是永久性的 , 接下来的其他操作或故障不应该对其有任何影响 - -Python DB-API 2.0的事务提供了两个方法 commit 和rollback , 在上述实例中已经见过了 - -```python -try: - # 执行SQL语句 - cursor.execute(sql) - # 向数据库提交 - connection.commit() -except: - # 发生错误时回滚 - connection.rollback() -``` \ No newline at end of file diff --git "a/01-Python/08-\347\225\252\345\244\226\347\257\207/06-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213SQlAlchemy.md" "b/01-Python/08-\347\225\252\345\244\226\347\257\207/06-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213SQlAlchemy.md" deleted file mode 100644 index 520840f92..000000000 --- "a/01-Python/08-\347\225\252\345\244\226\347\257\207/06-Python - \347\254\254\344\270\211\346\226\271\345\272\223\344\271\213SQlAlchemy.md" +++ /dev/null @@ -1,445 +0,0 @@ -# Python - 第三方库之SQlAlchemy - -[SQLAlchemy官方文档](https://docs.sqlalchemy.org/en/13/) - - - - - - - - - - - - -## 介绍 🍀 - -在介绍SQLAlchemy之前先介绍一下什么是ORM - -**ORM** - -ORM即`Object Relational Mapping` , 简称ORM , 中文意思就是对象关系映射 ; 是一种程序技术 , 用于实现面向对象编程语言里不同类型系统的数据之间的转换 - -换一个方式介绍 , 我们知道面向对象是从**软件工程基本原则**(如耦合 , 聚合 , 封装) 的基础上发展起来的 , 而关系型数据库是从**数学理论**发展而来的 , 两套理论完全是不匹配的 , 那么正是为了解决这个问题 , 对象关系映射技术诞生了 - -**SQLAlchemy** - -SQLAlchemy是Python中最有名的一款ORM框架 , 该框架建立在数据库API之上 , 使用关系对象映射进行数据库操作 - -SQLAlchemy对象关系映射代表了用户使用Python定义类来与数据库中的表相关联的一种方式 , 类的实例则对应数据表中的一行数据 , SQLAlchemy包括了一套将对象中的变化同步到数据库表中的系统 , 这套系统被称之为工作单元(unit of work) , 同时也提供了使用类查询来实现数据库查询以及查询表之间关系的功能 - -**安装** - -```cmd -$ pip3 install SQLAlchemy -``` - -**版本检查** - -```python ->>>import sqlalchemy ->>>sqlalchemy.__version__ -'1.1.14' -``` - -**各数据库Dialect** - -```mysql -MySQL-Python - mysql+mysqldb://:@[:]/ -pymysql - mysql+pymysql://:@/[?] -MySQL-Connector - mysql+mysqlconnector://:@[:]/ -cx_Oracle - oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...] --- 更多详见:http://docs.sqlalchemy.org/en/latest/dialects/index.html -``` - -**内部处理** - -SQLAlchemy操作数据库是利用Engine/ConnectionPooling/Dialect进行的 , Engine(引擎)使用ConnectionPooling连接数据库 , 然后再通过Dialect执行SQL语句 , SQLAlchemy Core如下 - -``` -SQLAlchemy Core -+-----------------+ +-------------------------+ +-----------------+ -| Schema/Types | | SQL Expression Language | | Engine | -+-----------------+ +-------------------------+ +-----------------+ - ↓ - +------------------+ +-------+ - |Connection Pooling| |Dialect| - +------------------+ +-------+ ---------------------------------------------------------------------- - DBAPI -``` - -## 连接数据库 🍀 - -```python -from sqlalchemy import create_engine -engine = create_engine("mysql+pymysql://root:myroot@localhost:3306/t1", echo=True) -``` - -`echo`参数是用来设置SQLAlchemy日志的 , 通过Python标准库logging模块实现 ; 设置为True表示所有操作记录可见 , 也可设置为False来减少日志的输出 - -`create_engine()` 的返回值是`Engine`的一个实例 , 此实例代表了操作数据库的核心接口 , 通过Dialect来处理数据库和数据库的API - -PS : 初次调用`create_engine()`时并不会真正的去连接数据库 , 只有在真正执行一条命令的时候才会去简历真正的DBAPI连接 ; 很多地方都会使用这种方式 , 以达到省资源的目的 - -## 声明映射 🍀 - -当使用ORM的时候 , 配置过程以描述数据库的表来开始 , 然后定义与之匹配的类 ; 而在SQLAlchemy中 , 这两个过程一般结合在一起 , 通过一个声明(Declarative)系统实现 , 该系统帮我们定义类以及实现与表的对应 - -声明系统实现类与表的对应是通过一系列基类实现的 , 即声明基类(Declarative Base Class) , 我们的应用程序经常只有一个此基类的实例 - -```python -from sqlalchemy.ext.declarative import declarative_base -Base = declarative_base() -``` - -根据声明的基类"Base" , 我们就可以通过它定义任何数量的映射类 - -## 使用原生SQL - -```python -from sqlalchemy import create_engine -from consts import DB_URI - -eng = create_engine(DB_URI) -with eng.connect() as con: - con.execute('drop table if exists users') - con.execute('create table users(Id INT PRIMARY KEY AUTO_INCREMENT, ' - 'Name VARCHAR(25))') - con.execute("insert into users(name) values('Lyon')") - con.execute("insert into users(name) values('Kenneth')") - rs = con.execute('select * from users') - for row in rs: - print(row) -``` - -## 使用表达式 - -SQLAlchemy 支持使用表达式的方式来操作数据库 - -```python -from sqlalchemy import (create_engine, Table, MetaData, Column, Integer, - String, tuple_) -from sqlalchemy.sql import select, asc, and_ -from consts import DB_URI - -eng = create_engine(DB_URI) - -meta = MetaData(eng) -users = Table( - 'Users', meta, - Column('Id', Integer, primary_key=True, autoincrement=True), - Column('Name', String(50), nullable=False), -) - -if users.exists(): - users.drop() -users.create() # 创建表 - - -def execute(s): - print('-' * 20) - rs = con.execute(s) - for row in rs: - print(row['Id'], row['Name']) - -with eng.connect() as con: - for username in ('xiaoming', 'wanglang', 'lilei'): - user = users.insert().values(Name=username) - con.execute(user) - - stm = select([users]).limit(1) - execute(stm) - - k = [(2,)] - stm = select([users]).where(tuple_(users.c.Id).in_(k)) - execute(stm) - - stm = select([users]).where(and_(users.c.Id > 2, - users.c.Id < 4)) - execute(stm) - - stm = select([users]).order_by(asc(users.c.Name)) - execute(stm) - - stm = select([users]).where(users.c.Name.like('%min%')) - execute(stm) -``` - - - -## ORM功能使用 🍀 - -流程如下 : - -1. 使用者通过ORM对象提交命令 -2. 将命令给SQLAlchemy Core转换成SQL -3. 匹配使用者事先配置好的engine -4. engine从连接池中取出一个链接 -5. 基于该链接通过Dialect调用DBAPI , 将SQL转交给数据库去执行 - -### 创建表 🍀 - -```python -# 创建单表 -from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import Column, Integer, String, Index, UniqueConstraint -# 根据Dialet创建引擎,echo=True表示输出所有操作日志 -engine = create_engine('mysql+pymysql://root:myroot@localhost:3306/test', echo=True) -# 声明基类 -Base = declarative_base() -# 定义映射类 -class Userinfo(Base): - # 表名 - __tablename__ = 'user_info' - # 设置主键自增列 - id = Column(Integer, primary_key=True, autoincrement=True) - name = Column(String(32)) - extra = Column(String(16)) - __table_args__ = ( - # 唯一索引,索引名为uix_id_name - UniqueConstraint('id', 'name', name='uix_id_name'), - # 联合索引 - Index('ix_id_name', 'name', 'extra'), - ) - # 定义格式 - def __repr__(self): - return "" % (self.id, self.name) -# 初始化函数 -def init_db(): - # 将所有继承Base类的类,创建表结构 - Base.metadata.create_all(engine) -def drop_db(): - # 将所有继承Base类的类,删除表 - Base.metadata.drop_all(engine) -init_db() -``` - -对应的SQL语句 - -```mysql -CREATE TABLE `UserInfo` ( - id INTEGER NOT NULL AUTO_INCREMENT, - name VARCHAR(32), - extra VARCHAR(16), - PRIMARY KEY (id), - CONSTRAINT uix_id_name UNIQUE (id, name) -) -``` - -创建其他表 - -```python -# 创建单表:业务线 -class Business(Base): - __tablename__='business' - id=Column(Integer,primary_key=True,autoincrement=True) - bname=Column(String(32),nullable=False,index=True) -# 一对多:多个服务可以属于一个业务线,多个业务线不能包含同一个服务 -class Service(Base): - __tablename__='service' - id=Column(Integer,primary_key=True,autoincrement=True) - sname=Column(String(32),nullable=False,index=True) - ip=Column(String(15),nullable=False) - port=Column(Integer,nullable=False) - business_id=Column(Integer,ForeignKey('business.id')) - __table_args__=( - UniqueConstraint(ip,port,name='uix_ip_port'), - Index('ix_id_sname',id,sname) - ) -# 一对一:一种角色只能管理一条业务线,一条业务线只能被一种角色管理 -class Role(Base): - __tablename__='role' - id=Column(Integer,primary_key=True,autoincrement=True) - rname=Column(String(32),nullable=False,index=True) - priv=Column(String(64),nullable=False) - business_id=Column(Integer,ForeignKey('business.id'),unique=True -# 多对多:多个用户可以是同一个role,多个role可以包含同一个用户 -class Users(Base): - __tablename__='users' - id=Column(Integer,primary_key=True,autoincrement=True) - uname=Column(String(32),nullable=False,index=True) -class Users2Role(Base): - __tablename__='users2role' - id=Column(Integer,primary_key=True,autoincrement=True) - uid=Column(Integer,ForeignKey('users.id')) - rid=Column(Integer,ForeignKey('role.id')) - __table_args__=( - UniqueConstraint(uid,rid,name='uix_uid_rid'), - ) -class Favor(Base): - __tablename__ = 'favor' - nid = Column(Integer, primary_key=True, autoincrement=True) - caption = Column(String(50), default='red', unique=True) -class Person(Base): - __tablename__ = 'person' - nid = Column(Integer, primary_key=True, autoincrement=True) - favor_id = Column(Integer, ForeignKey("favor.nid")) -''' -设置外键的另一种方式 ForeignKeyConstraint(['other_id'], ['othertable.other_id']) -''' -``` - -**扩展分析 : 根据流程可以发现 , 如果我们不依赖于SQLAlchemy的转换而自己写好sql语句 , 那么我们完全可以只用SQLAlchemy执行纯sql语句 , 即利用配置好的engine执行 , `engine.execute()`** - -### 删除表 🍀 - -```python -Base.metadata.drop_all(engine) # 把所有继承Base类的类,删除表 -``` - -### 操作表 🍀 - -ORM处理数据库的方式是通过Session来实现的 , 当我们需要与数据库进行对话时 , 就需要创建一个Session实例 : - -engine对象已经创建完成时 - -```python -from sqlalchemy.orm import sessionmaker -# 创建Session工厂,并连接engine -Session = sessionmaker(bind=engine) -# 创建Session实例 -session = Session() -``` - -engine未创建时 - -```python -from sqlalchemy.orm import sessionmaker -from sqlalchemy import create_engine -# 创建Session工厂 -Session = sessionmaker() -# 创建引擎 -engine = create_engine() -# 连接Session与engine -Session.configure(bind=engine) -# 创建Session实例 -session = Session() -``` - -#### 增加数据 🍀 - -单条数据 - -```python -Session = sessionmaker(bind=engine) -session = Session() -# 创建一条数据 -users = Userinfo(name='Hello', password='World') -# 把数据添加到表内 -session.add(users) -# 提交生效 -session.commit() -``` - -多条数据 - -```python -session.add_all([ - Userinfo(name='Lyon',extra='xxx'), - Userinfo(name='Kenneth Reitz',extra='xxx'), -]) -session.commit() -``` - -#### 删除数据 🍀 - -```python -session.query(Userinfo).filter(Userinfo.name == 'Kenneth Reitz').delete() -session.commit() -``` - -#### 修改数据 🍀 - -```python -session.query(Userinfo).filter(Users.id > 2).update({"name" : "099"}) -# synchronize_session同步会话 -session.query(Userinfo).filter(Users.id > 2).update({Users.name: Users.name + "099"}, synchronize_session=False) -# 设置评估标准 -session.query(Userinfo).filter(Users.id > 2).update({"num": Users.num + 1}, synchronize_session="evaluate") -session.commit() -''' -更多synchronize_session的参数可以查看官方文档 -''' -``` - -#### 查询数据 🍀 - -```python -# 查所有,取所有字段 -res = session.query(Userinfo).all() -print(res) -# 查所有,取指定字段,按照id排序 -res = session.query(Userinfo.name).order_by(Userinfo.id).all() -print(res) -# 查所有,取指定字段,第一条信息 -res = session.query(Userinfo.name).first() -print(res) -# 过滤查,逗号分隔,默认为and -res = session.query(Userinfo).filter(Userinfo.id > 1,Userinfo.id <1000) -print([(row.id, row.name) for row in res], type(res)) -''' -执行结果: -[] -[('Lyon',)] -('Lyon',) -[] -''' -``` - -#### 其他查询 🍀 - -```python -# 条件 -ret = session.query(MyClass).filter_by(name = 'some name') -ret = session.query(MyClass).filter(MyClass.id > 1, MyClass.name == 'Lyon').all() -ret = session.query(MyClass).filter(MyClass.id.between(1, 3), MyClass.name == 'eric').all() -ret = session.query(MyClass).filter(MyClass.id.in_([1,2,3])).all() -ret = session.query(MyClass).filter(~MyClass.id.in_([1,2,3])).all() -ret = session.query(MyClass).filter(MyClass.id.in_(session.query(MyClass.id).filter_by(name='Lyon'))).all() -from sqlalchemy import and_, or_ -ret = session.query(MyClass).filter(and_(MyClass.id > 3, MyClass.name == 'Lyon')).all() -ret = session.query(MyClass).filter(or_(MyClass.id < 2, MyClass.name == 'Lyon')).all() -ret = session.query(MyClass).filter( - or_( - MyClass.id < 2, - and_(MyClass.name == 'eric', MyClass.id > 3), - MyClass.extra != "" - )).all() -# 通配符 -ret = session.query(MyClass).filter(MyClass.name.like('e%')).all() -ret = session.query(MyClass).filter(~MyClass.name.like('e%')).all() -# 限制 -ret = session.query(MyClass)[1:2] -# 排序 -ret = session.query(MyClass).order_by(MyClass.name.desc()).all() -ret = session.query(MyClass).order_by(MyClass.name.desc(), MyClass.id.asc()).all() -# 分组 -from sqlalchemy.sql import func -ret = session.query(MyClass).group_by(MyClass.extra).all() -ret = session.query( - func.max(MyClass.id), - func.sum(MyClass.id), - func.min(MyClass.id)).group_by(MyClass.name).all() -ret = session.query( - func.max(MyClass.id), - func.sum(MyClass.id), - func.min(MyClass.id)).group_by(MyClass.name).having(func.min(MyClass.id) >2).all() -# 连表 -ret = session.query(Users, Favor).filter(Users.id == Favor.nid).all() -ret = session.query(Person).join(Favor).all() -ret = session.query(Person).join(Favor, isouter=True).all() -# 组合 -q1 = session.query(MyClass.name).filter(MyClass.id > 2) -q2 = session.query(Favor.caption).filter(Favor.nid < 2) -ret = q1.union(q2).all() -q1 = session.query(MyClass.name).filter(MyClass.id > 2) -q2 = session.query(Favor.caption).filter(Favor.nid < 2) -ret = q1.union_all(q2).all() -``` diff --git "a/01-Python/08-\347\225\252\345\244\226\347\257\207/README.md" "b/01-Python/08-\347\225\252\345\244\226\347\257\207/README.md" deleted file mode 100644 index c7ea00340..000000000 --- "a/01-Python/08-\347\225\252\345\244\226\347\257\207/README.md" +++ /dev/null @@ -1,2 +0,0 @@ -# Attack on Python - 番外篇 🐍 - diff --git a/01-Python/README.md b/01-Python/README.md deleted file mode 100644 index 29f4743fe..000000000 --- a/01-Python/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Attack on Python 🐍 - - - - - - - - - - -## 介绍 - -本目录下为 `进击的Python` 系列文章 - -目录如下 - -``` -Python -├── 基础篇 -├── 函数篇 -├── 对象篇 -├── 模块篇 -├── 网络篇 -├── 并发篇 -├── 内存篇 -└── 番外篇 -``` - -欢迎阅读 , 文章尚未整理完成 , 持续更新中 ... - diff --git a/02-Go/README.md b/02-Go/README.md deleted file mode 100644 index 164af10fe..000000000 --- a/02-Go/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Golang - -动一笔先 , 基础的语法等不会写 , 所以本目录下更新会比较慢 ... - - - - - - - - - - -## OOP - -封装 : `Golang` 中的封装就不用说了 , 通过大小写控制 , 和 `Python` 的 `__` 相比 , 差异还是比较大的 - -继承 : `Golang` 中的继承通过内嵌的方式实现 - -多态 : `Golang` 不像 `Python` , 天生多态 , 在 `Golang` 中 , 多态通过 `interface` 实现 , 详细后期慢慢慢慢更新... diff --git "a/04-\345\211\215\347\253\257/Vue/README.md" "b/04-\345\211\215\347\253\257/Vue/README.md" deleted file mode 100644 index f34a39f8d..000000000 --- "a/04-\345\211\215\347\253\257/Vue/README.md" +++ /dev/null @@ -1,2 +0,0 @@ -# Vue - diff --git "a/05-Web\346\241\206\346\236\266/03-ORM\347\256\200\344\273\213.md" "b/05-Web\346\241\206\346\236\266/03-ORM\347\256\200\344\273\213.md" deleted file mode 100644 index 737a4ef3a..000000000 --- "a/05-Web\346\241\206\346\236\266/03-ORM\347\256\200\344\273\213.md" +++ /dev/null @@ -1,115 +0,0 @@ -# ORM简介 - - - - - - - - - - -## 介绍 🍀 - -ORM在开发者和数据库之间建立了一个中间层 , 把数据库中的数据转换成了 Python 中的对象实体 , 这样既屏蔽了不同数据库之间的差异性 , 又使开发者可以非常方便地操作数据库中的数据 , 而且可以使用面向对象的高级特性 - -ORM 为我们做了如下操作 : - -- 将调用转换成 SQL 语句 -- 通过数据库引擎发送给数据库执行 -- 将数据库返回的结果记录用 ORM 映射技术转换成类对象 - -ORM 优点 : - -- 向开发者屏蔽了数据库的细节 , 使开发者无需与 SQL 语句打交道 , 提高了开发小懒虫 -- 便于数据库迁移 , 由于每种数据库的 SQL 语法有细微差别 , 所以基于 SQL 的数据访问层在更换数据库时通常需要花费大量的时间调试 SQL 语句 , 而 ORM 提供了独立于 SQL 的接口 , ORM 引擎会处理不同数据库之间的差异 , 所以迁移数据库时无须更改代码 -- 应用缓存优化等技术有时可以提高数据库操作的效率 - -## Python ORM 库介绍 🍀 - -Python 中提供 ORM 支持的组件有很多 , 每个组件的应用领域稍有区别 , 但是数据库操作的理论原理是相同的 , 下面对比较著名的 Python 数据库的 ORM 框架介绍 : - -- SQLAlchemy : 是 Python 中最成熟的 ORM 框架 , 资源和文档都很丰富 , 大多数 Python Web 框架对其都有很好的支持 , 能够胜任大多数应用场合 ; SQLAlchemy 被认为是 Python 事实上的 ORM 标准 -- Django ORM : 是 Python 世界中大名鼎鼎的 Django Web 框架独用的 ORM 技术 , Django是一个大而全的框架 , 这使得其灵活性大大降低 , 其他 Python Web 框架可以随意更换 ORM , 但在 Django 中不能这样做 , 因为 Django 内置的很多 model 是用 Django 内置 ORM 实现的 -- Peewee : 小巧灵活 , 是一个轻量级的 ORM , Peewee 是基于 SQLAlchemy 内核开发的 , 整个框架只由一个文件构成 , Peewee 提供了对多种数据库的访问方式 , 如 SQLite , MySQL , PostgreSQL , 适用于功能简单的小型网站 -- Storm : 是一个中型的 ORM 库 ,比 SQLAlchemy 和 Django 等轻量 , 比 Peewee 的功能更丰富 , Storm 要求开发者编写数据表的 DDL 代码 , 而不能直接从数据表类定义中自动生成表定义 -- SQLObject : 与 SQLAlchemy 相似 , 也是一套大而全的 ORM , SQLObject 的特点是其设计借鉴了 Ruby on Rails 的 ActiveRecord 模型 , 是的熟悉 Ruby 的开发者上手非常容易 - -## Peewee库使用 🍀 - -示例 : - -```python -# 引入Peewee包的所有内容 -from peewee import * - -# 建立一个SQLite数据库引擎对象,该引擎打开数据库文件sampleDB.db -db = SqliteDatabase("sampleDB.db") - -# 定义一个ORM的基类,在基类中指定本ORM所使用的数据库 -# 这样在之后所有的子类中就不用重复声明数据库了 -class BaseModel(Model): - class Meta: - database = db - -# 定义course表,继承自BaseModel -class Course(BaseModel): - id = PrimaryKeyField() - title = CharField(null=false) - period = IntegerField() - description = CharField() - - class Meta: - order_by = ('title',) - db_table = 'course' - -# 定义teacher表,继承自BaseModel -class Teacher(BaseModel): - id = PrimaryKeyField() - name = CharField(null=false) - gender = BooleanField() - description = CharField() - course_id = ForeignKeyField(Course, to_field="id", related_name="course") - - class Meta: - order_by = ('name',) - db_table = 'course' -``` - -使用 ORM 映射对数据内容进行增 , 删 , 改 , 查 : - -```python -# 建表,进需创建一次 -Course.create_table() -Teacher.craete_table() - -# 新增行 -Course.create(id=1, title='经济学', period=320, description='文理科学生均可选修') -Teacher.create(name='Lyon', gender=True, address='...', course_id=1) - -# 查询一行 -record = Course.get(Course.title='经济学') -print("课程:%s, 学时:%d" % (record.titel, record.period)) - -# 更新 -record.period = 200 -record.save() - -# 删除 -record.delete_instance() - -# 查询所有记录 -courses = Course.select() - -# 带条件查询,并将结果按period字段倒序排序 -courses = Course.select().where(Course.id<10).order_by(Course.period.desc()) - -# 统计所有课程的平均学时 -total = Course.select(fn.Avg(Course.period).alias('avg_period')) - -# 更新多个记录 -Course.update(period=300).where(Course.id>100).execute() - -# 多表连接操作,Peewee会自动根据ForeignKeyField的外键定义进行连接 -Record = Course.select().join(Teacher).where(Teacher.gender=True) -``` \ No newline at end of file diff --git "a/05-Web\346\241\206\346\236\266/Django-Rest-Framework/README.md" "b/05-Web\346\241\206\346\236\266/Django-Rest-Framework/README.md" deleted file mode 100644 index c72a6f4e0..000000000 --- "a/05-Web\346\241\206\346\236\266/Django-Rest-Framework/README.md" +++ /dev/null @@ -1,6 +0,0 @@ -# Django Rest Framework 教程 - -本教程是一个以官方文档为模板的中文教程 , 但在内容上会增加一些注释已经说明 , 帮助您简单的完成 django-rest-framework 的学习 - - -官网 : http://www.django-rest-framework.org/ \ No newline at end of file diff --git "a/05-Web\346\241\206\346\236\266/Django/01-Web\346\241\206\346\236\266\347\256\200\344\273\213.md" "b/05-Web\346\241\206\346\236\266/Django/01-Web\346\241\206\346\236\266\347\256\200\344\273\213.md" deleted file mode 100644 index 8705b15cf..000000000 --- "a/05-Web\346\241\206\346\236\266/Django/01-Web\346\241\206\346\236\266\347\256\200\344\273\213.md" +++ /dev/null @@ -1,214 +0,0 @@ -# Web框架简介 - - - - - - - - - - -## 介绍 🍀 - -框架 (Framework) , 特指为解决一个开发性问题而设计的具有一定约束性的支撑结构 , 使用框架可以帮助我们快速开发特定的系统 , 简单说就是使用别人搭好的舞台 , 你来做表演 - -对于Web应用 , 本质其实就是一个socket服务端 , 而我们使用的浏览器就是一个socket客户端 , 如下 : - -```python -# 服务端 -import socket -def handle_request(client): - """处理请求函数""" - buf = client.recv(1024) - print(buf) - client.send(b"HTTP/1.1 200 OK\r\n\r\n") - client.send(b"

Hello,Lyon

") -def main(): - """主函数""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(('localhost', 8000)) - sock.listen(5) - while True: - connection, address = sock.accept() - # 连接成功后直接处理请求 - handle_request(connection) - connection.close() -if __name__ == '__main__': - main() -''' -说明: -执行该脚本后,用浏览器(客户端)访问 127.0.0.1:8000 -连接成功后服务端直接应答以及发送 -

Hello,Lyon

-于是网页就直接显示出了Hello,Lyon的标题 -''' -``` - -上述通过socket实现了其本质 , 而对于真是开发中的Python Web程序来说 , 一般会分为两部分 : 服务器程序和应用程序 - -服务器程序负责对socket服务器进行封装 , 并在请求到来时 , 对请求的各种数据进行整理 , 如上述代码 - -应用程序负责具体的逻辑处理 - -为了方便应用程序的开发 , 就出现了众多的Web框架 , 常见的Python Web框架如下 : - -- Django : 全能型Web框架 -- Flask : 一个轻量级的Web框架 -- web.py : 一个小巧的Web框架 -- Bottle : 和Flask类似的Web框架 -- Tornado : Facebook的开源异步Web框架 - -不同的框架有不同的开发方式 , 但是无论如何 , 开发出的应用程序都要和服务器程序配合 , 才能为用户提供服务 - -也就是说框架和Web服务器之间进行通信 , 那么首先就需要两者互相支持 , 所以为了使Web服务器能够匹配多个不同的Web框架 , 就需要设立一个标准 , 在Python中这个标准就是WSGI - -## WSGI 🍀 - -WSGI : `WSGI (Web Server Gateway Interface , Web服务器网关接口)` - -它定义了使用Python编写Web应用与Web服务端之间的接口格式 , 实现了Web应用与Web服务器间的解藕 - -Python标准库提供的独立WSGI服务器称为**wsgiref** , 所以我们可以使用wsgiref模块开发一个自己的Web框架 - -关于wsgiref模块的更多内容请可以阅读< [Python之路 - wsgiref模块](https://lyonyang.github.io/blogs/01-Python/07-Standard-Library/Python%E4%B9%8B%E8%B7%AF%20-%20wsgiref%E6%A8%A1%E5%9D%97.html) > , 这也是完成下面内容的前提 - -## 自定义Web框架 🍀 - -### 框架 🍀 - -通过Python标准库提供的wsgiref模块开发 - -```python -# 从simple_server模块中导入make_server函数 -from wsgiref.simple_server import make_server -# 处理函数 -def index(): - return 'index' -# 处理函数 -def login(): - return 'login' - -# 路由管理函数 -def routers(): - urlpatterns = ( - ('/index/',index), - ('/login/',login), - ) - return urlpatterns - -# 该函数就是simple_server模块中的demo_app函数,即创建一个app -def RunServer(environ, start_response): - """ - 符合WSGI标准的一个HTTP处理函数 - environ:一个包含所有HTTP请求信息的dict对象 - start_response:一个发送HTTP响应的函数 - 注:该函数会在内部调用start_response函数 - """ - - # start_response接收两个参数,一个是HTTP响应码,如下"200 OK";另一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示 - start_response('200 OK', [('Content-Type', 'text/html')]) - # 获取路径信息,即url - url = environ['PATH_INFO'] - urlpatterns = routers() - func = None - # 查看路径是否存在 - for item in urlpatterns: - if item[0] == url: - func = item[1] - break - if func: - return func() - else: - return '404 not found' -if __name__ == '__main__': - # 创建一个WSGI服务器 - httpd = make_server('', 8000, RunServer) - print("Serving HTTP on port 8000...") - # 开始监听HTTP请求 - httpd.serve_forever() -``` - -### 模板引擎 🍀 - -在上一步中 , 对于处理函数`login`以及`index`仅仅做了最简单的处理 , 在现实的Web请求中一般会返回一个复杂的符合HTML规则的字符串 , 所以我们一般会将要返回给用户HTML写在指定文件中 , 然后再返回 , 进一步优化如下 : - -```python -# 通过文件处理发送HTML信息 -def index(): - # return 'index' - f = open('index.html') - data = f.read() - return data -def login(): - # return 'login' - f = open('login.html') - data = f.read() - return data -``` - -如上我们实现了静态的页面处理 , 要使其能够给用户返回动态内容 , 我们有两种方法 : - -- 自定义一套特殊的语法 , 进行替换 -- 使用开源工具jinja2 , 遵循其指定语法 - -*Jinja2介绍* : Jinja2是Python下一个被广泛应用的模板引擎 , 他的设计思想来源于Django的模板引擎 , 并扩展了其语法和一系列强大的功能 , 他基于unicode并能在python2.4之后的版本运行 , 包括python3 - -于是我们可以再进一步 - -```python -from jinja2 import Template -def index(): - # return 'index' - # template = Template('Hello {{ name }}!') - # result = template.render(name='John Doe') - f = open('index.html') - result = f.read() - template = Template(result) - data = template.render(name='John Doe', user_list=['Lyon', 'Kenneth']) - return data.encode('utf-8') -def login(): - # return 'login' - f = open('login.html') - data = f.read() - return data -``` - -以上就完成了一个最简单的Web框架 - -## MVC和MTV 🍀 - -> **MVC** - -MVC (Model View Controller , 模型-视图-控制器) 是一种Web架构的模式 , 它把业务逻辑 , 模型数据 , 用户界面分离开来 , 让开发者将数据与表现解藕 , 前端工程师可以只改页面效果部分而不用接触后端代码 , DBA(数据库管理员) 可以重新命名数据表并且只需要更改一个地方 , 无序从一大堆文件中进行查找和替换 - -MVC模式甚至还可以提高代码复用能力 , 现在MVC模式依然是主流 - -MVC三要素 : - -- Model表示应用程序核心(比如数据库记录列表) , 是应用程序中用于处理应用程序数据逻辑的部分 , 通常模型对象负责在数据库中存取数据 -- View显示数据(数据库记录) , 是应用程序中处理数据显示的部分 , 通常视图是依据模型数据创建的 -- Controller处理输入(写入数据库记录) , 是应用程序中处理用户交互的部分 , 通常控制器负责从视图读取数据 , 控制用户输入 , 并向模型发送数据 - -MVC模式同时提供了对HTML , CSS和JavaScript的完全控制 - -MVC的特点是通信单向的 : - -1. 浏览器发送请求 -2. Contorller和Model交互获取数据 -3. Contorller调用View -4. View渲染数据返回 - -> **MTV** - -在Python的世界中 , 基本都使用了MVC的变种MTV (Model Templates View , 模型-模板-视图) - -MTV三要素 : - -1. Model , 和MVC的Model一样 , 处理与数据相关的所有事务 : 如何存取 , 如何确认有效性 , 包含哪些行为以及数据之间的关系等 -2. Template , 处理与表现相关的决定 : 如何在页面或其他类型文档中进行显示出来 -3. View , 处理业务逻辑 , 视图就是一个特定URL的回调函数 , 回调函数中描述数据 : 从Model取出对应的数据 , 调用相关的模板 . 它就是Contorller要调用的那个用来做Model和View之间的沟通函数 , 从而完成控制 - -两者的区别在于 : MVC中的View的目的是「呈现哪一个数据」 , 而MTV的View的目的是「数据如何呈现」 - -下一篇就开始学习Django啦 diff --git "a/05-Web\346\241\206\346\236\266/Tornado/README.md" "b/05-Web\346\241\206\346\236\266/Tornado/README.md" deleted file mode 100644 index 5683ab41b..000000000 --- "a/05-Web\346\241\206\346\236\266/Tornado/README.md" +++ /dev/null @@ -1 +0,0 @@ -# Tornado初识 \ No newline at end of file diff --git a/06-Redis/README.md b/06-Redis/README.md deleted file mode 100644 index 91c5ad108..000000000 --- a/06-Redis/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Redis - -REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统 - -Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API - -它通常被称为数据结构服务器 , 因为值(value)可以是 字符串(String), 哈希(Map) , 列表(list) , 集合(sets) 和有序集合(sorted sets)等类型 diff --git "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/README.md" "b/07-\350\256\276\350\256\241\346\250\241\345\274\217/README.md" deleted file mode 100644 index f3dd88e31..000000000 --- "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/README.md" +++ /dev/null @@ -1,3 +0,0 @@ -# 设计模式 - - diff --git a/09-Linux/Docker/README.md b/09-Linux/Docker/README.md deleted file mode 100644 index 348e62d30..000000000 --- a/09-Linux/Docker/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Docker - - - - - - - - - - -## 介绍 - -Docker 是一个开源的应用容器引擎 , 基于 Go 语言 并遵从 Apache2.0 协议开源 - -Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中 , 然后发布到任何流行的 Linux 机器上 , 也可以实现虚拟化 - -## 功能和组件 - -Docker 的实现引入了以下核心概念 : - -- Docker 客户端 -- Docker daemon -- Docker 容器 -- Docker 镜像 -- Registry - -## Docker客户端 - -Docker 是一个典型的 C/S 架构的应用程序 , 但在发布上 , Docker 将客户端和服务器端统一在同一个二进制文件中 , 不过 , 这只是对于 Linux 系统而言的 , 在其他平台如 Mac 上 , Docker 只提供了客户端 - -Docker 客户端一般通过 Docker command 来发起清楚 , 另外 , 也可以通过 Docker 提供的一整套 RESTful API 来发起请求 , 这种方式更多地被应用在应用程序的代码中 - -## Docker daemon - -Docker daemon 也可以被理解为 Docker Server , 另外 , 人们也常常用 Docker Engine 来直接描述它 , 因为这实际上就是驱动整个 Docker 功能的核心引擎 - -简单地说 , Docker daemon 实现的功能就是接收客户端发来的请求 , 并实现请求所要求的功能 , 同时针对请求返回响应的结果 , 在功能的实现上 , 因为涉及了容器 , 镜像 , 存储等多方便的内容 , daemon 内部的机制会复杂很多 , 涉及了多个模块的实现和交互 - -## Docker容器 - -在 Docker 的功能和概念中 , 容器是一个核心内容 , 相对于传统虚拟化 , 它作为一项基础技术在性能上给 Docker 带来了极大优势 - -在概念上 , 容器很好地诠释了 Docker 集装箱的理念 , 集装箱可以存放任何货物 , 可以通过邮轮将货物运输到世界各地 , 运输集装箱的邮轮和装载卸载集装箱的码头都不用关心集装箱里的货物 , 这是一种标准的集装和运输方式 ; 类似的 , Docker 的容器就是 "软件界的集装箱" , 它可以安装任意的软件和库文件 , 做任意的运行环境配置 , 开发及运维人员在转义和部署应用的时候 , 不用关心容器里装了什么软件 , 也不用了解它们是如何配置的 , 而管理容器的 Docker 引擎同样不关心容器里的内容 , 它只要像码头工人一样让这个容器运行起来就可以了 , 就像所有其他容器那样 - -## Docker镜像 - -与容器相对应 , 如果说容器提供了一个完整的 , 隔离的运行环境 , 那么镜像则是这个运行环境的静态体现 , 是一个还没有运行起来的 "运行环境" - -相对传统虚拟化中的 ISO 镜像 , Docker 镜像要轻量化很多 , 它只是一个可定制的 rootfs , Docker 镜像的另一个创新是它是层级的并且是可服用的 - -Docker 镜像通常是通过 Dockerfile 来创建的 , Dockerfile 提供了镜像内容的定制 , 同时也体现了层级关系的建立 , 另外 Docker 镜像也可以通过使用 docker commit 这样的命令来手动将修改后的内容生成镜像 - -## Registry - -Registry 是一个存放镜像的仓库 , 它通常被部署在互联网服务或者云端 , 通常 , 集装箱是需要通过邮轮经行海洋运输到世界各地的 , 而互联网时代的传输则要方便很多 , 在镜像的传输过程中 , Registry 就是这个传输的重要中转站 ; 假如我们在公司将一个软件的运行环境作成镜像 , 并上传到 Registry 中 , 这时就可以很方便地在家里的笔记本上 , 或者在客户的生产环境上直接从 Registry 上下载并运行了 - -## 比较 - -跟传统VM比较 , Docker 具有如下优点 : - -1. 操作启动快 , 运行时的性能可以获取极大提升 , 管理操作(启动 , 停止 , 开始 , 重启等等) 都是以秒或毫秒为单位的 -2. 轻量级虚拟化 , 你会拥有足够的“操作系统” , 仅需添加或减小镜像即可 -3. 开源免费 , 开源的 , 免费的 , 低成本的 , 由现代Linux内核支持并驱动 -4. 前景及云支持 - -跟传统VM比较 , Docker 具有如下缺点 : - -1. 它是一项新型的技术 , 可能还不够稳定 -2. Go 语言还没有完全成熟 diff --git a/09-Linux/Git/README.md b/09-Linux/Git/README.md deleted file mode 100644 index b5b7a831c..000000000 --- a/09-Linux/Git/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Git - diff --git a/09-Linux/README.md b/09-Linux/README.md deleted file mode 100644 index 8b1378917..000000000 --- a/09-Linux/README.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Code/django-rest-framework code/.idea/dataSources.local.xml b/Code/django-rest-framework code/.idea/dataSources.local.xml deleted file mode 100644 index 9b6c6ecf6..000000000 --- a/Code/django-rest-framework code/.idea/dataSources.local.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - master_key - false - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/dataSources.xml b/Code/django-rest-framework code/.idea/dataSources.xml deleted file mode 100644 index ec59a0095..000000000 --- a/Code/django-rest-framework code/.idea/dataSources.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:D:\桌面\tutorial\db.sqlite3 - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/dataSources/9d4dd1c0-4ff9-4d35-886c-99b8f009c2c3.xml b/Code/django-rest-framework code/.idea/dataSources/9d4dd1c0-4ff9-4d35-886c-99b8f009c2c3.xml deleted file mode 100644 index 71433936e..000000000 --- a/Code/django-rest-framework code/.idea/dataSources/9d4dd1c0-4ff9-4d35-886c-99b8f009c2c3.xml +++ /dev/null @@ -1,445 +0,0 @@ - - - - - 1 - - -
-
-
-
-
-
-
-
-
-
-
-
-
- - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(80)(0,-1)|12 - - - 1 - name - - 1 - - - id - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - group_id -permission_id - - 1 - - - id - 1 - - - 1 - group_id - 1 - 1 - ~.auth_group.#1 - no_action - no_action - - - 1 - permission_id - 1 - 1 - ~.auth_permission.#1 - no_action - no_action - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(100)(0,-1)|12 - - - 1 - VARCHAR(255)(0,-1)|12 - - - content_type_id -codename - - 1 - - - id - 1 - - - 1 - content_type_id - 1 - 1 - ~.django_content_type.#1 - no_action - no_action - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(128)(0,-1)|12 - - - DATETIME(0,-1)|12 - - - 1 - BOOL(0,-1)|4 - - - 1 - VARCHAR(30)(0,-1)|12 - - - 1 - VARCHAR(30)(0,-1)|12 - - - 1 - VARCHAR(254)(0,-1)|12 - - - 1 - BOOL(0,-1)|4 - - - 1 - BOOL(0,-1)|4 - - - 1 - DATETIME(0,-1)|12 - - - 1 - VARCHAR(150)(0,-1)|12 - - - 1 - username - - 1 - - - id - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - user_id -group_id - - 1 - - - id - 1 - - - 1 - user_id - 1 - 1 - ~.auth_user.#1 - no_action - no_action - - - 1 - group_id - 1 - 1 - ~.auth_group.#1 - no_action - no_action - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - user_id -permission_id - - 1 - - - id - 1 - - - 1 - user_id - 1 - 1 - ~.auth_user.#1 - no_action - no_action - - - 1 - permission_id - 1 - 1 - ~.auth_permission.#1 - no_action - no_action - - - 1 - INTEGER(0,-1)|4 - - - 1 - DATETIME(0,-1)|12 - - - TEXT(0,-1)|12 - - - 1 - VARCHAR(200)(0,-1)|12 - - - 1 - SMALLINT UNSIGNED(0,-1)|4 - - - 1 - TEXT(0,-1)|12 - - - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - id - 1 - - - 1 - content_type_id - 1 - 1 - ~.django_content_type.#1 - no_action - no_action - - - 1 - user_id - 1 - 1 - ~.auth_user.#1 - no_action - no_action - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(100)(0,-1)|12 - - - 1 - VARCHAR(100)(0,-1)|12 - - - app_label -model - - 1 - - - id - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(255)(0,-1)|12 - - - 1 - VARCHAR(255)(0,-1)|12 - - - 1 - DATETIME(0,-1)|12 - - - id - 1 - - - 1 - VARCHAR(40)(0,-1)|12 - - - 1 - TEXT(0,-1)|12 - - - 1 - DATETIME(0,-1)|12 - - - expire_date - - 1 - - - session_key - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(64)(0,-1)|12 - - - 1 - VARCHAR(32)(0,-1)|12 - - - id - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - VARCHAR(64)(0,-1)|12 - - - 1 - VARCHAR(32)(0,-1)|12 - - - 1 - VARCHAR(16)(0,-1)|12 - - - id - 1 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - 1 - INTEGER(0,-1)|4 - - - user_id -group_id - - 1 - - - id - 1 - - - 1 - user_id - 1 - 1 - ~.quickstart_user.#1 - no_action - no_action - - - 1 - group_id - 1 - 1 - ~.quickstart_group.#1 - no_action - no_action - - - (0,-1)|12 - - - (0,-1)|12 - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/markdown-navigator/profiles_settings.xml b/Code/django-rest-framework code/.idea/markdown-navigator/profiles_settings.xml deleted file mode 100644 index 9c51dfede..000000000 --- a/Code/django-rest-framework code/.idea/markdown-navigator/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/misc.xml b/Code/django-rest-framework code/.idea/misc.xml deleted file mode 100644 index 046c6a220..000000000 --- a/Code/django-rest-framework code/.idea/misc.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/modules.xml b/Code/django-rest-framework code/.idea/modules.xml deleted file mode 100644 index 9898cb6b5..000000000 --- a/Code/django-rest-framework code/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/tutorial.iml b/Code/django-rest-framework code/.idea/tutorial.iml deleted file mode 100644 index 26f8b8f6d..000000000 --- a/Code/django-rest-framework code/.idea/tutorial.iml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Code/django-rest-framework code/.idea/workspace.xml b/Code/django-rest-framework code/.idea/workspace.xml deleted file mode 100644 index f7f17a0dd..000000000 --- a/Code/django-rest-framework code/.idea/workspace.xml +++ /dev/null @@ -1,967 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1527410830678 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index e15322b8c..1756f2db8 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -# Welcome to Lyon's blog! 🍀 +# Welcome to Lyon's blog! [![Build Status](https://travis-ci.org/lyonyang/blogs.svg?branch=master)](https://travis-ci.org/lyonyang/blogs) -[![Author](https://img.shields.io/badge/Author-Lyon-orange.svg)]() -[![Python Versions](https://img.shields.io/badge/python-2.x%2C%203.x-blue.svg)](https://www.python.org/) -[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/lyonyang/blogs/blob/master/LICENSE) -**本博客即将重构...精彩内容就快出来啦...* +[![Author](https://img.shields.io/badge/author-Lyon-%2342b983)]() +[![Python Versions](https://img.shields.io/badge/python-2.x%2C3.x-%2342b983)](https://www.python.org/) +[![Django Version](https://img.shields.io/badge/django-1.11-%2342b983)](https://docs.djangoproject.com/en/1.11/) +[![Flask Version](https://img.shields.io/badge/flask-1.0-%2342b983)](http://flask.pocoo.org/docs/1.0/) +[![Tronado Version](https://img.shields.io/badge/tornado-6.1-%2342b983)](https://www.tornadoweb.org/en/stable/) +[![License](https://img.shields.io/badge/license-apache%202.0-%2342b983)](https://github.com/lyonyang/blogs/blob/master/LICENSE) -**如需转载, 请注明出处** -如果我的博客对你有帮助 , 那就帮我点个星星吧 🤣 [Star](https://github.com/lyonyang/blogs) +## 介绍 🐙 -## 作者 🍀 +致力构建一个高质量的后端技术图谱 ![octocat](https://github.githubassets.com/images/icons/emoji/octocat.png) + +## 关于我 🤩 > 个人主页 : [Blogs](https://lyonyang.github.io/blogs/) @@ -20,38 +23,9 @@ > 微信 : [Happy547903993]() -## 感谢 🍀 +## 感谢 🚀 感谢以下老哥们提供的宝贵意见与指正, 一起进步 1. **Jesse** - -## 博客向导 🍀 - -本博客由 [`Travis CI`](https://travis-ci.org/lyonyang/blogs) 自动构建 - -[目录](SUMMARY.md) : - -```tree -. -├── Python   -├── Go -├── MySQL           -├── Front-End    -│ └── Vue -├── Web-Framework -│ ├── Django -│ ├── Django-Rest-Framework -│ └── Flask -├── Redis -├── DesignPattern -├── Algorithms -├── Linux -│ └── Git -└── Read -``` - -博客搭建指南 : [GitHub Pages&Gitbook&Travis CI持续构建博客](https://lyonyang.github.io/blogs/09-Linux/Git/GitHub%20Pages&Gitbook&Travis%20CI%E6%8C%81%E7%BB%AD%E6%9E%84%E5%BB%BA%E5%8D%9A%E5%AE%A2.html) - - diff --git a/Read/README.md b/Read/README.md deleted file mode 100644 index b24360745..000000000 --- a/Read/README.md +++ /dev/null @@ -1 +0,0 @@ -# 读书 diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index 26712ae49..000000000 --- a/SUMMARY.md +++ /dev/null @@ -1,202 +0,0 @@ -# Summary - -* [介绍](README.md) -* [目录](SUMMARY.md) -* [Python](01-Python/README.md) - * [基础篇](01-Python/01-基础篇/README.md) - * [语言基础](01-Python/01-基础篇/01-语言基础.md) - * [数字](01-Python/01-基础篇/02-数字.md) - * [字符串](01-Python/01-基础篇/03-字符串.md) - * [元组](01-Python/01-基础篇/04-元组.md) - * [列表](01-Python/01-基础篇/05-列表.md) - * [字典](01-Python/01-基础篇/06-字典.md) - * [集合](01-Python/01-基础篇/07-集合.md) - * [字符编码](01-Python/01-基础篇/08-字符编码.md) - * [文件操作](01-Python/01-基础篇/09-文件操作.md) - * [函数篇](01-Python/02-函数篇/README.md) - * [函数基础](01-Python/02-函数篇/01-函数基础.md) - * [匿名函数](01-Python/02-函数篇/02-匿名函数.md) - * [函数进阶](01-Python/02-函数篇/03-函数进阶.md) - * [内置函数](01-Python/02-函数篇/04-内置函数.md) - * [迭代器](01-Python/02-函数篇/05-迭代器.md) - * [生成器](01-Python/02-函数篇/06-生成器.md) - * [递归](01-Python/02-函数篇/07-递归.md) - * [对象篇](01-Python/03-对象篇/README.md) - * [面向对象](01-Python/03-对象篇/01-面向对象.md) - * [继承](01-Python/03-对象篇/02-继承.md) - * [多态](01-Python/03-对象篇/03-多态.md) - * [封装](01-Python/03-对象篇/04-封装.md) - * [方法转换](01-Python/03-对象篇/05-方法转换.md) - * [魔术方法](01-Python/03-对象篇/06-魔术方法.md) - * [反射](01-Python/03-对象篇/07-反射.md) - * [异常处理](01-Python/03-对象篇/08-异常处理.md) - * [模块篇](01-Python/04-模块篇/README.md) - * [模块](01-Python/04-模块篇/01-模块.md) - * [包](01-Python/04-模块篇/02-包.md) - * [正则表达式](01-Python/04-模块篇/03-正则表达式.md) - * [序列化](01-Python/04-模块篇/04-序列化.md) - * [os模块](01-Python/04-模块篇/05-os模块.md) - * [random模块](01-Python/04-模块篇/06-random模块.md) - * [sys模块](01-Python/04-模块篇/07-sys模块.md) - * [wsgiref模块](01-Python/04-模块篇/08-wsgiref模块.md) - * [网络篇](01-Python/05-网络篇/README.md) - * [网络编程](01-Python/05-网络篇/01-网络编程.md) - * [Socket](01-Python/05-网络篇/02-Socket.md) - * [Socket实现QQ聊天](01-Python/05-网络篇/03-Socket实现QQ聊天.md) - * [Socket实现远程执行命令](01-Python/05-网络篇/04-Socket实现远程执行命令.md) - * [粘包](01-Python/05-网络篇/05-粘包.md) - * [Socketserver实现多并发](01-Python/05-网络篇/06-Socketserver实现多并发.md) - * [并发篇](01-Python/06-并发篇/README.md) - * [进程与线程](01-Python/06-并发篇/01-进程与线程.md) - * [多线程](01-Python/06-并发篇/02-多线程.md) - * [多进程](01-Python/06-并发篇/03-多进程.md) - * [多进程实例及回调函数](01-Python/06-并发篇/04-多进程实例及回调函数.md) - * [协程](01-Python/06-并发篇/05-协程.md) - * [IO多路复用](01-Python/06-并发篇/06-IO多路复用.md) - * [实现线程池](01-Python/06-并发篇/07-实现线程池.md) - * [内存篇](01-Python/07-内存篇/README.md) - * [对象机制](01-Python/07-内存篇/01-对象机制.md) - * [对象的创建](01-Python/07-内存篇/02-对象的创建.md) - * [整数对象](01-Python/07-内存篇/03-整数对象.md) - * [字符串对象](01-Python/07-内存篇/04-字符串对象.md) - * [List对象](01-Python/07-内存篇/05-List对象.md) - * [Dict对象](01-Python/07-内存篇/06-Dict对象.md) - * [Tuple对象](01-Python/07-内存篇/07-Tuple对象.md) - * [垃圾回收](01-Python/07-内存篇/08-垃圾回收.md) - * [元类](01-Python/07-内存篇/09-元类.md) - * [番外篇](01-Python/08-番外篇/README.md) - * [Python - 第三方库之PyMySQL](01-Python/08-番外篇/04-Python - 第三方库之PyMySQL.md) - * [Python - 第三方库之MySQLdb](01-Python/08-番外篇/05-Python - 第三方库之MySQLdb.md) - * [Python - 第三方库之SQlAlchemy](01-Python/08-番外篇/06-Python - 第三方库之SQlAlchemy.md) -* [Go](02-Go/README.md) - * [Golang - 语言基础](02-Go/Golang - 语言基础.md) -* [MySQL](03-MySQL/README.md) - * [MySQL - 库操作](03-MySQL/01-MySQL - 库操作.md) - * [MySQL - 数据类型](03-MySQL/02-MySQL - 数据类型.md) - * [MySQL - 存储引擎](03-MySQL/03-MySQL - 存储引擎.md) - * [MySQL - 表操作](03-MySQL/04-MySQL - 表操作.md) - * [MySQL - 数据操作](03-MySQL/05-MySQL - 数据操作.md) - * [MySQL - 索引](03-MySQL/06-MySQL - 索引.md) - * [MySQL - 视图](03-MySQL/07-MySQL - 视图.md) - * [MySQL - 存储过程与函数](03-MySQL/08-MySQL - 存储过程与函数.md) - * [MySQL - 触发器](03-MySQL/09-MySQL - 触发器.md) - * [MySQL - 事务](03-MySQL/10-MySQL - 事务.md) - * [MySQL - SQL注入](03-MySQL/11-MySQL - SQL注入.md) - * [SQL优化](03-MySQL/SQL优化.md) -* [前端](04-前端/README.md) - * [Web开发 - HTML-head](04-前端/02-Web开发 - HTML-head.md) - * [Web开发 - HTML-body](04-前端/03-Web开发 - HTML-body.md) - * [Web开发 - CSS](04-前端/05-Web开发 - CSS.md) - * [Web开发 - JavaScript](04-前端/06-Web开发 - JavaScript.md) - * [Web开发 - BOM](04-前端/07-Web开发 - BOM.md) - * [Web开发 - DOM](04-前端/08-Web开发 - DOM.md) - * [Web开发 - jQuery](04-前端/09-Web开发 - jQuery.md) - * [Web开发 - Ajax](04-前端/11-Web开发 - Ajax.md) - * [Vue](04-前端/Vue/README.md) - * [Vue - 介绍](04-前端/Vue/01-Vue - 介绍.md) - * [Vue - 实例](04-前端/Vue/02-Vue - 实例.md) - * [Vue - 模板语法](04-前端/Vue/03-Vue - 模板语法.md) - * [Vue - 计算属性和侦听器](04-前端/Vue/04-Vue - 计算属性和侦听器.md) - * [Vue - Class与Style 绑定](04-前端/Vue/05-Vue - Class与Style 绑定.md) - * [Vue - 条件渲染](04-前端/Vue/06-Vue - 条件渲染.md) - * [Vue - 列表渲染](04-前端/Vue/07-Vue - 列表渲染.md) - * [Vue - 事件处理](04-前端/Vue/08-Vue - 事件处理.md) - * [Vue - 组件基础](04-前端/Vue/09-Vue - 组件基础.md) - * [Vue - Vue-cli](04-前端/Vue/10-Vue - Vue-cli.md) -* [Web框架](05-Web框架/README.md) - * [HTTP基础](05-Web框架/01-HTTP基础.md) - * [REST](05-Web框架/02-REST.md) - * [ORM简介](05-Web框架/03-ORM简介.md) - * [Django](05-Web框架/Django/README.md) - * [Web框架简介](05-Web框架/Django/01-Web框架简介.md) - * [Django - Django初识](05-Web框架/Django/02-Django - Django初识.md) - * [Django - Settings](05-Web框架/Django/03-Django - Settings.md) - * [Django - Urls](05-Web框架/Django/04-Django - Urls.md) - * [Django - Views](05-Web框架/Django/05-Django - Views.md) - * [Django - Model](05-Web框架/Django/06-Django - Model.md) - * [Django - Model Fields](05-Web框架/Django/07-Django - Model Fields.md) - * [Django - Model Field Options](05-Web框架/Django/08-Django - Model Field Options.md) - * [Django - Model QuerySet API](05-Web框架/Django/09-Django - Model QuerySet API.md) - * [Django - Model Making queries](05-Web框架/Django/10-Django - Model Making queries.md) - * [Django - Forms](05-Web框架/Django/11-Django - Forms.md) - * [Django - Template](05-Web框架/Django/12-Django - Template.md) - * [Django - Template Language](05-Web框架/Django/13-Django - Template Language.md) - * [Django - Middleware](05-Web框架/Django/14-Django - Middleware.md) - * [Django - Sessions](05-Web框架/Django/15-Django - Sessions.md) - * [Django - Authentication System](05-Web框架/Django/16-Django - Authentication System.md) - * [Django - 源码之startproject](05-Web框架/Django/50-Django - 源码之startproject.md) - * [Django - 源码之runserver](05-Web框架/Django/51-Django - 源码之runserver.md) - * [Django - 源码之middleware](05-Web框架/Django/52-Django - 源码之middleware.md) - * [Django - 源码之url](05-Web框架/Django/53-Django - 源码之url.md) - * [Django - 源码之admin](05-Web框架/Django/54-Django - 源码之admin.md) - * [Django - Django命令整理](05-Web框架/Django/Django - Django命令整理.md) - * [Django-Rest-Framework](05-Web框架/Django-Rest-Framework/README.md) - * [Quickstart](05-Web框架/Django-Rest-Framework/Quickstart.md) - * [Tutorial 1 Serialization](05-Web框架/Django-Rest-Framework/Tutorial 1 Serialization.md) - * [Tutorial 2 Requests and Responses](05-Web框架/Django-Rest-Framework/Tutorial 2 Requests and Responses.md) - * [Tutorial 3 Class-based Views](05-Web框架/Django-Rest-Framework/Tutorial 3 Class-based Views.md) - * [Tutorial 4 Authentication & Permissions](05-Web框架/Django-Rest-Framework/Tutorial 4 Authentication & Permissions.md) - * [Tutorial 5 Relationships & Hyperlinked APIs](05-Web框架/Django-Rest-Framework/Tutorial 5 Relationships & Hyperlinked APIs.md) - * [Tutorial 6 ViewSets & Routers](05-Web框架/Django-Rest-Framework/Tutorial 6 ViewSets & Routers.md) - * [Tutorial 7 Schemas & client libraries](05-Web框架/Django-Rest-Framework/Tutorial 7 Schemas & client libraries.md) - * [Flask](05-Web框架/Flask/README.md) - * [Flask - 源码简要说明](05-Web框架/Flask/01-Flask - 源码简要说明.md) - * [Flask - 源码之开始](05-Web框架/Flask/02-Flask - 源码之开始.md) - * [Flask - 源码之配置](05-Web框架/Flask/03-Flask - 源码之配置.md) - * [Flask - 源码之路由](05-Web框架/Flask/04-Flask - 源码之路由.md) - * [Flask - 源码之视图](05-Web框架/Flask/05-Flask - 源码之视图.md) - * [Flask - 源码之蓝图](05-Web框架/Flask/06-Flask - 源码之蓝图.md) - * [Flask - 源码之本地线程](05-Web框架/Flask/07-Flask - 源码之本地线程.md) - * [Flask - 源码之上下文](05-Web框架/Flask/08-Flask - 源码之上下文.md) - * [Flask - 源码之信号](05-Web框架/Flask/09-Flask - 源码之信号.md) - * [Flask - 扩展](05-Web框架/Flask/10-Flask - 扩展.md) - * [DBUtils](05-Web框架/Flask/DBUtils.md) - * [virtualenv基本使用](05-Web框架/Flask/virtualenv基本使用.md) - * [Tornado](05-Web框架/Tornado/README.md) -* [Redis](06-Redis/README.md) - * [Redis - 简介](06-Redis/01-Redis - 简介.md) - * [Redis - 配置参数说明](06-Redis/02-Redis - 配置参数说明.md) - * [Redis - 基础命令](06-Redis/03-Redis - 基础命令.md) - * [Redis - 数据库](06-Redis/04-Redis - 数据库.md) - * [Redis - 事务](06-Redis/05-Redis - 事务.md) - * [Redis - 主从复制](06-Redis/06-Redis - 主从复制.md) - * [Redis - 集群](06-Redis/07-Redis - 集群.md) - * [Redis - Sentinel](06-Redis/08-Redis - Sentinel.md) - * [Python操作MongoDB](06-Redis/Python操作MongoDB.md) - * [Python操作Redis](06-Redis/Python操作Redis.md) -* [设计模式](07-设计模式/README.md) - * [六大原则](07-设计模式/01-六大原则.md) - * [单例模式](07-设计模式/02-单例模式.md) -* [算法](08-算法/README.md) - * [算法基础](08-算法/01-算法基础.md) - * [数组](08-算法/02-数组.md) - * [栈](08-算法/03-栈.md) - * [链表](08-算法/03-链表.md) - * [树](08-算法/04-树.md) - * [队列](08-算法/04-队列.md) - * [哈希表](08-算法/05-哈希表.md) - * [双指针](08-算法/06-双指针.md) - * [并查集](08-算法/07-并查集.md) - * [前缀树](08-算法/08-前缀树.md) - * [KMP&RK](08-算法/09-KMP&RK.md) - * [跳表](08-算法/10-跳表.md) - * [剪枝](08-算法/11-剪枝.md) - * [Python时间复杂度](08-算法/Python时间复杂度.md) - * [优先队列](08-算法/优先队列.md) - * [力扣题解](08-算法/力扣题解/README.md) - * [双端队列](08-算法/双端队列.md) - * [排序合集](08-算法/排序合集.md) - * [树](08-算法/树.md) - * [链表](08-算法/链表.md) -* [Linux](09-Linux/README.md) - * [Command](09-Linux/Command/README.md) - * [常用命令](09-Linux/Command/01-常用命令.md) - * [vim](09-Linux/Command/vim.md) - * [Docker](09-Linux/Docker/README.md) - * [Git](09-Linux/Git/README.md) - * [Git&GitHub](09-Linux/Git/01-Git&GitHub.md) - * [Git基础命令](09-Linux/Git/02-Git基础命令.md) - * [GitHub Pages&Gitbook&Travis CI持续构建博客](09-Linux/Git/GitHub Pages&Gitbook&Travis CI持续构建博客.md) - * [Travis CI](09-Linux/Git/Travis CI.md) - * [RabbitMQ](09-Linux/RabbitMQ.md) -* [Read](Read/README.md) diff --git a/assets/add token.png b/assets/add-token.png similarity index 100% rename from assets/add token.png rename to assets/add-token.png diff --git a/assets/choose repositories.png b/assets/choose-repositories.png similarity index 100% rename from assets/choose repositories.png rename to assets/choose-repositories.png diff --git "a/04-\345\211\215\347\253\257/Vue/components.png" b/assets/components.png similarity index 100% rename from "04-\345\211\215\347\253\257/Vue/components.png" rename to assets/components.png diff --git a/assets/Copy Token.png b/assets/copy-token.png similarity index 100% rename from assets/Copy Token.png rename to assets/copy-token.png diff --git "a/04-\345\211\215\347\253\257/Vue/my-project.png" b/assets/my-project.png similarity index 100% rename from "04-\345\211\215\347\253\257/Vue/my-project.png" rename to assets/my-project.png diff --git a/assets/New personal access token.png b/assets/new-personal-access-token.png similarity index 100% rename from assets/New personal access token.png rename to assets/new-personal-access-token.png diff --git a/assets/TCP communication.png b/assets/tcp-communication.png similarity index 100% rename from assets/TCP communication.png rename to assets/tcp-communication.png diff --git "a/04-\345\211\215\347\253\257/Vue/vue.min.js" b/assets/vue.min.js similarity index 100% rename from "04-\345\211\215\347\253\257/Vue/vue.min.js" rename to assets/vue.min.js diff --git a/book.json b/book.json deleted file mode 100644 index bc6129528..000000000 --- a/book.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "title": "blog", - "author": "Lyon", - "description": "my notes", - "extension": null, - "generator": "site", - "links": { - "sharing": { - "all": null, - "facebook": null, - "google": null, - "twitter": null, - "weibo": null - } - }, - "pdf": { - "fontSize": 18, - "footerTemplate": null, - "headerTemplate": null, - "margin": { - "bottom": 36, - "left": 62, - "right": 62, - "top": 36 - }, - "pageNumbers": false, - "paperSize": "a4" - }, - "plugins": [ - "advanced-emoji", - "toggle-chapters", - "theme-comscore", - "splitter", - "github", - "github-buttons@2.1.0", - "-sharing", - "-lunr", - "-search", - "search-plus", - "anchor-navigation-ex-toc@0.0.9", - "editlink", - "fontsettings", - "copy-code-button", - "lightbox" - ], - "pluginsConfig": { - "github": { - "url": "https://github.com/lyonyang/blogs" - }, - "github-buttons": { - "repo": "lyonyang/blogs", - "types": [ - "star" - ], - "size": "small" - }, - "editlink": { - "base": "https://github.com/lyonyang/blogs/blob/master", - "label": "编辑本页" - }, - "anchor-navigation-ex-toc": { - "showLevel": false, - "mode": "float", - "float": { - "showLevelIcon": false, - "level1Icon": "fa fa-hand-o-right", - "level2Icon": "fa fa-hand-o-right", - "level3Icon": "fa fa-hand-o-right" - } - } - } -} diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index cc241f3f9..000000000 --- a/deploy.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -git config user.name "Lyon" -git config user.email "547903993@qq.com" -git config --global core.quotepath false - -git checkout -b gitbook -git status -git add . -git commit -m "[Travis] Update SUMMARY.md" -git push -f "https://${GH_TOKEN}@${GH_REF}" gitbook:gitbook -gitbook install -gitbook build . -if [ $? -ne 0 ];then - exit 1 -fi -cd _book -sed -i '/a href.*\.md/s#\.md#.html#g;/a href.*README\.html/s#README\.html##g' SUMMARY.html -git init -git checkout --orphan gh-pages -git status -sleep 5 -git add . -git commit -m "Update gh-pages" -git remote add origin git@github.com:LyonYang/blogs.git -git push -f "https://${GH_TOKEN}@${GH_REF}" gh-pages:gh-pages diff --git "a/08-\347\256\227\346\263\225/02-\346\225\260\347\273\204.md" b/docs/.nojekyll similarity index 100% rename from "08-\347\256\227\346\263\225/02-\346\225\260\347\273\204.md" rename to docs/.nojekyll diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..1756f2db8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# Welcome to Lyon's blog! + +[![Build Status](https://travis-ci.org/lyonyang/blogs.svg?branch=master)](https://travis-ci.org/lyonyang/blogs) +[![Author](https://img.shields.io/badge/author-Lyon-%2342b983)]() +[![Python Versions](https://img.shields.io/badge/python-2.x%2C3.x-%2342b983)](https://www.python.org/) +[![Django Version](https://img.shields.io/badge/django-1.11-%2342b983)](https://docs.djangoproject.com/en/1.11/) +[![Flask Version](https://img.shields.io/badge/flask-1.0-%2342b983)](http://flask.pocoo.org/docs/1.0/) +[![Tronado Version](https://img.shields.io/badge/tornado-6.1-%2342b983)](https://www.tornadoweb.org/en/stable/) +[![License](https://img.shields.io/badge/license-apache%202.0-%2342b983)](https://github.com/lyonyang/blogs/blob/master/LICENSE) + + +## 介绍 🐙 + +致力构建一个高质量的后端技术图谱 ![octocat](https://github.githubassets.com/images/icons/emoji/octocat.png) + +## 关于我 🤩 + +> 个人主页 : [Blogs](https://lyonyang.github.io/blogs/) + +> GitHub : [https://github.com/lyonyang](https://github.com/lyonyang) + +> QQ : [547903993](http://wpa.qq.com/msgrd?v=3&uin=547903993&site=qq&menu=yes) + +> 微信 : [Happy547903993]() + +## 感谢 🚀 + +感谢以下老哥们提供的宝贵意见与指正, 一起进步 + +1. **Jesse** + diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 000000000..938bb7d40 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,126 @@ + +* 开始 + * [介绍](/README.md) + + +* [**Python**](https://attack-on-backend.github.io/python/) + +* [**Tornado**](https://attack-on-backend.github.io/tornado/) + +* [**算法**](https://attack-on-backend.github.io/algorithm/) + +* Flask + * [Flask - 源码介绍](/flask/introduction.md) + * [Flask - 源码之开始](/flask/start.md) + * [Flask - 源码之配置](/flask/settings.md) + * [Flask - 源码之路由](/flask/router.md) + * [Flask - 源码之视图](/flask/views.md) + * [Flask - 源码之蓝图](/flask/blueprint.md) + * [Flask - 源码之本地线程](/flask/local-threading.md) + * [Flask - 源码之上下文](/flask/context.md) + * [Flask - 源码之信号](/flask/signal.md) + * [Flask - 扩展](/flask/ext.md) + * [Flask - DBUtils](/flask/dbutils.md) + * [Flask - virtualenv](/flask/virtualenv.md) + +* Django + * [Web框架简介](/django/web-framework-introduction.md) + * [HTTP基础](/django/http.md) + * [REST](/django/rest.md) + * [Django - QuickStart](/django/quickstart.md) + * [Django - Settings](/django/settings.md) + * [Django - Urls](/django/urls.md) + * [Django - Model](/django/model.md) + * [Django - Model Fields](/django/model-fields.md) + * [Django - Model Field Options](/django/model-field-options.md) + * [Django - Model QuerySet API](/django/model-queryset-api.md) + * [Django - Model Making Queries](/django/model-making-queries.md) + * [Django - Forms](/django/forms.md) + * [Django - Template](/django/template.md) + * [Django - Template Language](/django/template-language.md) + * [Django - Middleware](/django/middleware.md) + * [Django - Sessions](/django/sessions.md) + * [Django - Authentication System](/django/authentication-system.md) + * [Django - 源码之startproject](/django/source-startproject.md) + * [Django - 源码之runserver](/django/source-runserver.md) + * [Django - 源码之middleware](/django/source-middleware.md) + * [Django - 源码之url](/django/source-url.md) + * [Django - 源码之admin](/django/source-admin.md) + * [Django - Commands](/django/commands.md) + +* Django-REST-Framework + * [Quickstart](/drf/quickstart.md) + * [Serialization](/drf/serialization.md) + * [Requests and Responses](/drf/requests-and-responses.md) + * [Class-based Views](/drf/class-based-views.md) + * [Authentication & Permissions](/drf/authentication-permissions.md) + * [Relationships & Hyperlinked APIs](/drf/relationships-hyperlinked-api.md) + * [ViewSets & Routers](/drf/viewsets-routers.md) + * [Schemas & client libraries](/drf/schemas-client-libraries.md) + +* Golang + + * [Go - 语言基础](/go/basic.md) + +* MySQL + + * [MySQL - 开始](/mysql/introduction.md) + * [MySQL - 库操作](/mysql/database.md) + * [MySQL - 数据类型](/mysql/datatype.md) + * [MySQL - 存储引擎](/mysql/storage-engine.md) + * [MySQL - 表操作](/mysql/table.md) + * [MySQL - 数据操作](/mysql/data.md) + * [MySQL - 索引](/mysql/index.md) + * [MySQL - 视图](/mysql/view.md) + * [MySQL - 存储过程和函数](/mysql/stored-procedures-and-functions.md) + * [MySQL - 触发器](/mysql/trigger.md) + * [MySQL - 事务](/mysql/transaction.md) + * [MySQL - SQL注入](/mysql/sql-injection.md) + * [MySQL - SQL优化](/mysql/sql-optimization.md) + +* Redis + + * [Redis - 简介](/redis/introduction.md) + * [Redis - 配置](/redis/settings.md) + * [Redis - 命令](/redis/command.md) + * [Redis - 数据库](/redis/database.md) + * [Redis - 事务](/redis/transaction.md) + * [Redis - 主从复制](/redis/slave.md) + * [Redis - 集群](/redis/cluster.md) + * [Redis - 哨兵](/redis/sentinel.md) + * [Redis - Python操作](/redis/py-redis.md) + +* 前端 + * [HTML(一)](/front-end/head.md) + * [HTML(二)](/front-end/body.md) + * [CSS](/front-end/css.md) + * [JavaScript](/front-end/javascript.md) + * [BOM](/front-end/bom.md) + * [DOM](/front-end/dom.md) + * [jQuery](/front-end/jquery.md) + * [Ajax](/front-end/ajax.md) + +* Vue + * [Vue - 介绍](/vue/introduction.md) + * [Vue - 实例](/vue/object.md) + * [Vue - 模板语法](/vue/template.md) + * [Vue - 计算属性和侦听器](/vue/compute-properties-and-listeners.md) + * [Vue - Class与Style绑定](/vue/cls-style.md) + * [Vue - 条件渲染](/vue/conditional-rendering.md) + * [Vue - 列表渲染](/vue/list-rendering.md) + * [Vue - 事件处理](/vue/event.md) + * [Vue - 组件基础](/vue/component.md) + * [Vue - vue-cli](/vue/vue-cli.md) + +* 设计模式 + * [六大原则](/design-pattern/six-principles.md) + * [单例模式](/design-pattern/singleton.md) + +* Linux + * [常用命令](/linux/commands.md) + * [Vim](/linux/vim.md) + * [Git&GitHub](/linux/git-and-github.md) + * [Git](/linux/git.md) + * [GitBook](/linux/gitbook.md) + * [Travis CI](/linux/travis-ci.md) + * [RabbitMQ](/linux/rabbitmq.md) \ No newline at end of file diff --git "a/08-\347\256\227\346\263\225/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" "b/docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" similarity index 97% rename from "08-\347\256\227\346\263\225/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" rename to "docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" index b69810fa6..564dcbedb 100644 --- "a/08-\347\256\227\346\263\225/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" +++ "b/docs/algorithm/01-\347\256\227\346\263\225\345\237\272\347\241\200.md" @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 **定义** @@ -33,7 +33,7 @@ 算法的每一步都必须是可行的 , 也就是说 , 每一步都能够通过执行有限次数完成 -## 设计要求 🍀 +## 设计要求 解决一个问题的途径可以有非常多种 , 掌握好的算法 , 对我们解决问题很有帮助 , 而一个好的算法应该具备以下要求 @@ -62,7 +62,7 @@ **设计算法应该尽量满足时间效率和存储量低的要求** -## 效率的度量方法 🍀 +## 效率的度量方法 **事后统计方法** @@ -87,7 +87,7 @@ 抛开与计算机硬件 , 软件有关的因素 , 一个程序的运行时间 , 依赖于算法的好坏和问题的输入规模 -## 时间复杂度 🍀 +## 时间复杂度 在进行算法分析时 , 语句总的执行次数`T(n)`是关于问题规模`n`的函数 , 进而分析`T(n)`随`n`的变化情况并确定`T(n)`的数量级 @@ -95,7 +95,7 @@ 我们用大写`O()` 来体现算法时间复杂度的记法 , 称之为`大O记法` -### 推导大O阶 🍀 +### 推导大O阶 分析一个算法的时间复杂度 , 推导大O阶时有以下方法 : @@ -105,7 +105,7 @@ 由此得到的结果就是大O阶 -### 常数阶 🍀 +### 常数阶 首先介绍顺序结构的时间复杂度 , 现有如下算法 @@ -121,7 +121,7 @@ print(sum) # 执行一次 对于分支结构而言 , 无论是真还是假 , 执行的次数都是恒定的 , 不会随着n的变大而发生变化 , 所以单纯的分支结构 (不包含在循环结构中) , 其时间复杂度也是`O(1)` -### 线性阶 🍀 +### 线性阶 线性阶的循环结构会复杂很多 , 要确定某个算法的阶次 , 我们常常需要确定某个特定语句或某个语句集运行的次数 ; 因此 , 我们要分析算法的复杂度 , 关键就是要分析循环结构的运行情况 , 如下 : @@ -132,7 +132,7 @@ for i in range(n): 上面代码中 , `print`语句会执行`n`次 , 所以它的算法复杂度为`O(n)` -### 对数阶 🍀 +### 对数阶 ```python count = 1 @@ -142,7 +142,7 @@ while count < n: 上面代码中 , 每次`count`乘以2之后 , 就距离`n`更近了一分 , 也就是说 , 有多少个2想乘后大于`n` , 则会退出循环 , 由`2^x = n` 可以得到`x = log2n` , 所以这个循环的时间复杂度为`O(logn)` -### 平方阶 🍀 +### 平方阶 下面例子是一个循环嵌套 @@ -170,7 +170,7 @@ for i in range(n): `O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2") < O(n!) < O(n")` -## 空间复杂度 🍀 +## 空间复杂度 算法的空间复杂度通过计算算法所需的存储空间实现 , 算法空间复杂度的计算公式记作 : `S(n) = O(f(n))` , 其中`n`为问题的规模 , `f(n)`为语句关于`n`所占存储空间的函数 diff --git a/09-Linux/Command/README.md "b/docs/algorithm/02-\346\225\260\347\273\204.md" similarity index 100% rename from 09-Linux/Command/README.md rename to "docs/algorithm/02-\346\225\260\347\273\204.md" diff --git "a/08-\347\256\227\346\263\225/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" "b/docs/algorithm/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" similarity index 100% rename from "08-\347\256\227\346\263\225/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" rename to "docs/algorithm/02-\346\225\260\347\273\204\343\200\201\346\240\210\343\200\201\351\230\237\345\210\227.md.bak" diff --git "a/08-\347\256\227\346\263\225/03-\346\240\210.md" "b/docs/algorithm/03-\346\240\210.md" similarity index 100% rename from "08-\347\256\227\346\263\225/03-\346\240\210.md" rename to "docs/algorithm/03-\346\240\210.md" diff --git "a/08-\347\256\227\346\263\225/03-\351\223\276\350\241\250.md" "b/docs/algorithm/03-\351\223\276\350\241\250.md" similarity index 100% rename from "08-\347\256\227\346\263\225/03-\351\223\276\350\241\250.md" rename to "docs/algorithm/03-\351\223\276\350\241\250.md" diff --git "a/08-\347\256\227\346\263\225/04-\346\240\221.md" "b/docs/algorithm/04-\346\240\221.md" similarity index 100% rename from "08-\347\256\227\346\263\225/04-\346\240\221.md" rename to "docs/algorithm/04-\346\240\221.md" diff --git "a/08-\347\256\227\346\263\225/04-\351\230\237\345\210\227.md" "b/docs/algorithm/04-\351\230\237\345\210\227.md" similarity index 100% rename from "08-\347\256\227\346\263\225/04-\351\230\237\345\210\227.md" rename to "docs/algorithm/04-\351\230\237\345\210\227.md" diff --git "a/08-\347\256\227\346\263\225/05-\345\223\210\345\270\214\350\241\250.md" "b/docs/algorithm/05-\345\223\210\345\270\214\350\241\250.md" similarity index 100% rename from "08-\347\256\227\346\263\225/05-\345\223\210\345\270\214\350\241\250.md" rename to "docs/algorithm/05-\345\223\210\345\270\214\350\241\250.md" diff --git "a/08-\347\256\227\346\263\225/06-\345\217\214\346\214\207\351\222\210.md" "b/docs/algorithm/06-\345\217\214\346\214\207\351\222\210.md" similarity index 100% rename from "08-\347\256\227\346\263\225/06-\345\217\214\346\214\207\351\222\210.md" rename to "docs/algorithm/06-\345\217\214\346\214\207\351\222\210.md" diff --git "a/08-\347\256\227\346\263\225/07-\345\271\266\346\237\245\351\233\206.md" "b/docs/algorithm/07-\345\271\266\346\237\245\351\233\206.md" similarity index 100% rename from "08-\347\256\227\346\263\225/07-\345\271\266\346\237\245\351\233\206.md" rename to "docs/algorithm/07-\345\271\266\346\237\245\351\233\206.md" diff --git "a/08-\347\256\227\346\263\225/08-\345\211\215\347\274\200\346\240\221.md" "b/docs/algorithm/08-\345\211\215\347\274\200\346\240\221.md" similarity index 100% rename from "08-\347\256\227\346\263\225/08-\345\211\215\347\274\200\346\240\221.md" rename to "docs/algorithm/08-\345\211\215\347\274\200\346\240\221.md" diff --git "a/08-\347\256\227\346\263\225/09-KMP&RK.md" b/docs/algorithm/09-KMP&RK.md similarity index 100% rename from "08-\347\256\227\346\263\225/09-KMP&RK.md" rename to docs/algorithm/09-KMP&RK.md diff --git "a/08-\347\256\227\346\263\225/10-\350\267\263\350\241\250.md" "b/docs/algorithm/10-\350\267\263\350\241\250.md" similarity index 100% rename from "08-\347\256\227\346\263\225/10-\350\267\263\350\241\250.md" rename to "docs/algorithm/10-\350\267\263\350\241\250.md" diff --git "a/08-\347\256\227\346\263\225/11-\345\211\252\346\236\235.md" "b/docs/algorithm/11-\345\211\252\346\236\235.md" similarity index 100% rename from "08-\347\256\227\346\263\225/11-\345\211\252\346\236\235.md" rename to "docs/algorithm/11-\345\211\252\346\236\235.md" diff --git "a/08-\347\256\227\346\263\225/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" "b/docs/algorithm/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" similarity index 100% rename from "08-\347\256\227\346\263\225/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" rename to "docs/algorithm/Python\346\227\266\351\227\264\345\244\215\346\235\202\345\272\246.md" diff --git "a/08-\347\256\227\346\263\225/README.md" b/docs/algorithm/README.md similarity index 100% rename from "08-\347\256\227\346\263\225/README.md" rename to docs/algorithm/README.md diff --git "a/08-\347\256\227\346\263\225/\344\274\230\345\205\210\351\230\237\345\210\227.md" "b/docs/algorithm/\344\274\230\345\205\210\351\230\237\345\210\227.md" similarity index 100% rename from "08-\347\256\227\346\263\225/\344\274\230\345\205\210\351\230\237\345\210\227.md" rename to "docs/algorithm/\344\274\230\345\205\210\351\230\237\345\210\227.md" diff --git "a/08-\347\256\227\346\263\225/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" "b/docs/algorithm/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" similarity index 100% rename from "08-\347\256\227\346\263\225/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" rename to "docs/algorithm/\345\212\233\346\211\243\351\242\230\350\247\243/README.md" diff --git "a/08-\347\256\227\346\263\225/\345\217\214\347\253\257\351\230\237\345\210\227.md" "b/docs/algorithm/\345\217\214\347\253\257\351\230\237\345\210\227.md" similarity index 100% rename from "08-\347\256\227\346\263\225/\345\217\214\347\253\257\351\230\237\345\210\227.md" rename to "docs/algorithm/\345\217\214\347\253\257\351\230\237\345\210\227.md" diff --git "a/08-\347\256\227\346\263\225/\346\216\222\345\272\217\345\220\210\351\233\206.md" "b/docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" similarity index 98% rename from "08-\347\256\227\346\263\225/\346\216\222\345\272\217\345\220\210\351\233\206.md" rename to "docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" index 80ba9a4de..7fa55859f 100644 --- "a/08-\347\256\227\346\263\225/\346\216\222\345\272\217\345\220\210\351\233\206.md" +++ "b/docs/algorithm/\346\216\222\345\272\217\345\220\210\351\233\206.md" @@ -9,7 +9,7 @@ -## 前言 🍀 +## 前言 排序问题是我们学习编程过程中最常见的 , 以Python中的列表为例 , 进行排序算法分析 , @@ -31,7 +31,7 @@ def cal_time(func): 这个装饰器只是为了进行简单的时间测试 , 因为影响一个算法的执行时间实在是太多 , 但对于我们学习算法确是够了 -## 冒泡排序 🍀 +## 冒泡排序 工作流程 : @@ -84,7 +84,7 @@ bubble_sort_2 running time: 0.002001523971557617 secs. ''' ``` -## 选择排序 🍀 +## 选择排序 工作流程 : @@ -115,7 +115,7 @@ select_sort running time: 18.08176565170288 secs. ''' ``` -## 插入排序 🍀 +## 插入排序 工作流程 : @@ -152,7 +152,7 @@ insert_sort running time: 18.230905055999756 secs. ''' ``` -## 快速排序 🍀 +## 快速排序 工作流程 : @@ -214,7 +214,7 @@ quick_sort running time: 0.10507440567016602 secs. ''' ``` -## 堆排序 🍀 +## 堆排序 **堆分类** @@ -318,7 +318,7 @@ heap_sort(li) heapq.nlargest(100, li) ``` -## 归并排序 🍀 +## 归并排序 假设现在的列表分成两段有序 , 如何将其合成为一个有序的列表 @@ -371,7 +371,7 @@ random.shuffle(li) merge_sort(li) ``` -## 希尔排序 🍀 +## 希尔排序 希尔排序是一种分组插入排序算法 @@ -405,7 +405,7 @@ shell_sort(li) print(li) ``` -## 计数排序 🍀 +## 计数排序 现有一个列表 , 列表中的数范围都在0到100之间 , 列表长度大约为100万 , 设计算法在O(n)时间复杂度内将列表进行排序 @@ -445,7 +445,7 @@ count_sort running time: 0.024517059326171875 secs. ''' ``` -## 桶排序 🍀 +## 桶排序 桶排序的基本思想是将一个数据表分割成许多桶 , 然后每个桶各自排序 , 有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序 @@ -494,7 +494,7 @@ radix_sort running time: 0.3001980781555176 secs. ''' ``` -## 小结 🍀 +## 小结 | 排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 复杂性 | | ---- | --------- | --------- | --------- | -------------------------- | ---- | ---- | diff --git "a/08-\347\256\227\346\263\225/\346\240\221.md" "b/docs/algorithm/\346\240\221.md" similarity index 100% rename from "08-\347\256\227\346\263\225/\346\240\221.md" rename to "docs/algorithm/\346\240\221.md" diff --git "a/08-\347\256\227\346\263\225/\351\223\276\350\241\250.md" "b/docs/algorithm/\351\223\276\350\241\250.md" similarity index 100% rename from "08-\347\256\227\346\263\225/\351\223\276\350\241\250.md" rename to "docs/algorithm/\351\223\276\350\241\250.md" diff --git "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/02-\345\215\225\344\276\213\346\250\241\345\274\217.md" b/docs/design-pattern/singleton.md similarity index 95% rename from "07-\350\256\276\350\256\241\346\250\241\345\274\217/02-\345\215\225\344\276\213\346\250\241\345\274\217.md" rename to docs/design-pattern/singleton.md index 5000c3edf..110a9d4da 100644 --- "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/02-\345\215\225\344\276\213\346\250\241\345\274\217.md" +++ b/docs/design-pattern/singleton.md @@ -1,4 +1,4 @@ -# 单例模式 🍀 +# 单例模式 @@ -9,13 +9,13 @@ -## 定义 🍀 +## 定义 单例模式 ( `Singleton Pattern` ) 确保某一个类只有一个实例 , 而且自行实例化并向整个系统提供这个实例 ( `Ensure a class has only one instance, and provide a global point of access to it.` ) -## 场景 🍀 +## 场景 在一个系统中 , 要求一个类有且仅有一个对象 , 如果出现多个对象就会出现 "不良反应" , 可以采用单例模式 @@ -28,11 +28,11 @@ 我们可能最多见的就是需要共享数据 , 比如在项目中的配置数据 , 又比如 Web 框架中的路由 -## 实例 🍀 +## 实例 要实现单例模式 , 即保证一个类仅有一个实例 , 并提供一个访问它的全局访问点 -### Module 🍀 +### Module Python 中的 module 天生就是单例 , 至于为什么 , 你应该去看看 [compiled-python-file](https://docs.python.org/3.6/tutorial/modules.html#compiled-python-files) @@ -51,7 +51,7 @@ singleton = Singleton() from singleton import singleton ``` -### \_\_new__ 🍀 +### \_\_new__ Python 中的对象将有 `__new__` 来开辟空间创建实例 @@ -71,7 +71,7 @@ class Singleton(object): Python 是支持多线程的 , 所以为了线程安全 , 加上锁 -### Metaclass 🍀 +### Metaclass 使用元类来实现单例模式 , 实际上就是控制 `class()` 的行为 , 也就是 `__call__` 魔术方法 @@ -95,7 +95,7 @@ singleton = Singleton() 元类创建类本身就是线程安全的 , 所以你并不需要担心抢占资源的问题 -### 类装饰器 🍀 +### 类装饰器 ```python def singleton(cls): @@ -115,7 +115,7 @@ class Singleton: 使用类装饰器 , 实际上就是把类转换成一个函数对象 , 因此跟实例的创建关系不大 , 因为从始至终也就实例化了一次 , 而且它是线程安全的 -### 类方法 🍀 +### 类方法 通过我们自定义的方法来获取对象 , 而不通过实例化的途径 diff --git "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/01-\345\205\255\345\244\247\345\216\237\345\210\231.md" b/docs/design-pattern/six-principles.md similarity index 98% rename from "07-\350\256\276\350\256\241\346\250\241\345\274\217/01-\345\205\255\345\244\247\345\216\237\345\210\231.md" rename to docs/design-pattern/six-principles.md index df538d571..91343fa22 100644 --- "a/07-\350\256\276\350\256\241\346\250\241\345\274\217/01-\345\205\255\345\244\247\345\216\237\345\210\231.md" +++ b/docs/design-pattern/six-principles.md @@ -9,11 +9,11 @@ -## 前言 🍀 +## 前言 设计模式是一个我们编写程序的标准 , 也就是一种规范 , 有时候不能够盲目的追求规范而不理会真实情景 , 因为它可能会适得其反 -## 单一职责原则 🍀 +## 单一职责原则 单一职责原则 (`Single Responsibility Principle` , 简称 `SRP` ) @@ -65,7 +65,7 @@ class Phone(object): 在面向对象中, 除了继承, 还有另一种方式来实现抽象, 那就是组合, 但是组合是一种强耦合关系, 所以不到迫不得已还是不要使用组合的好 -## 里氏替换原则 🍀 +## 里氏替换原则 里氏替换原则 (`Liskov substitution Principle` , 简称 `LSP` ) @@ -124,7 +124,7 @@ class SubTwo(Foo): 覆盖就意味着它的外观是没有任何变化的 , 使用起来也没有变化 , 但是它其中的内容却已经被改变 ; 重载则是它的名字还是一样的 , 但是也仅仅是名字 , 它的其他都已经被重新塑造 ; 就比如 , 我们写了一个方法( `Python` 注解形式) `a(n: int, m: str) -> str` , 子类覆盖你所看到的还是 `a(n: int, m: str) -> str` , 而重载 `a(n: tuple) -> str` , 它只是名字还叫 `a` , 但是它的参数等等已经发生了改变 -## 依赖倒置原则 🍀 +## 依赖倒置原则 依赖倒置原则 (`Dependence Inversion Principle` , 简称 `DIP` ) @@ -202,7 +202,7 @@ class Driver(object): 依赖倒置原则的本职就是通过抽象 , 使各个类或模块的实现彼此独立 , 不互相影响 , 实现模块间的松耦合 -## 接口隔离原则 🍀 +## 接口隔离原则 接口隔离原则 (`Interface Segregation Principle` , 简称 `ISP` ) @@ -281,7 +281,7 @@ class Worker(object): 在进行接口隔离时 , 粒度大小不能过大 , 也不能过小 ; 定义太大 , 会降低灵活性 , 无法提供定制服务 , 给整体项目带来无法预料的风险 ; 定义太小 , 则会造成接口数量过多 , 使设计复杂化 -## 迪米特法则 🍀 +## 迪米特法则 迪米特法则 (`Law of Demeter` , 简称 `LoD` ) 也称最少知识原则 (`Least Knowledge Principle` , 简称 `LKP`) @@ -334,7 +334,7 @@ class Fans(object): 总之 , 迪米特法则的作用在就是解耦 , 但是解耦的程度需要我们格外小心 -## 开闭原则 🍀 +## 开闭原则 开闭原则 diff --git "a/05-Web\346\241\206\346\236\266/Django/README.md" b/docs/django/README.md similarity index 100% rename from "05-Web\346\241\206\346\236\266/Django/README.md" rename to docs/django/README.md diff --git "a/05-Web\346\241\206\346\236\266/Django/16-Django - Authentication System.md" b/docs/django/authentication-system.md similarity index 96% rename from "05-Web\346\241\206\346\236\266/Django/16-Django - Authentication System.md" rename to docs/django/authentication-system.md index 1d852c68a..1cb37fabd 100644 --- "a/05-Web\346\241\206\346\236\266/Django/16-Django - Authentication System.md" +++ b/docs/django/authentication-system.md @@ -9,11 +9,11 @@ -## 介绍 🍀 +## 介绍 Django为我们提供了一个认证系统 , 它提供了认证 (*authentiaction*) 和授权功能 (*authorization*) , 这两种功能在某些地方时耦合的 -## User对象 🍀 +## User对象 [`User`](https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.models.User)对象是认证系统的核心 , 它们通常表示与你的站点进行交互的用户 , 并用于启用限制访问 , 注册用户信息和给创建者关联内容等 @@ -29,7 +29,7 @@ Django为我们提供了一个认证系统 , 它提供了认证 (*authentiaction 完整参考见 : [`full API documentation`](https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.models.User) -### 创建 users 🍀 +### 创建 users 创建users最直接的方法时使用`create_user()`函数 , 如下 : @@ -46,7 +46,7 @@ Django为我们提供了一个认证系统 , 它提供了认证 (*authentiaction 如果我们安装了admin , 我们可以交互式地创建users , 见 : [create users interactively](https://docs.djangoproject.com/en/1.11/topics/auth/default/#auth-admin) -### 创建 superusers 🍀 +### 创建 superusers 我们可以使用如下命令创建一个超级用户 : @@ -54,7 +54,7 @@ Django为我们提供了一个认证系统 , 它提供了认证 (*authentiaction $ python manage.py createsuperuser --username=joe --email=joe@example.com ``` -### 修改密码 🍀 +### 修改密码 Django不会在user模型上存储原始的 (明文) 密码 , 而只是一个哈希值 (完整见 : [documentation of how passwords are managed](https://docs.djangoproject.com/en/1.11/topics/auth/passwords/) ) , 因此 , 不要试图直接操作用户的密码属性 , 这就是为什么创建用户时使用帮助函数的原因 @@ -77,7 +77,7 @@ Django不会在user模型上存储原始的 (明文) 密码 , 而只是一个哈 注意 : 更改用户密码将会注销所有会话 , 详见 : [Session invalidation on password change](https://docs.djangoproject.com/en/1.11/topics/auth/default/#session-invalidation-on-password-change) -### 用户认证 🍀 +### 用户认证 使用`authenticate()`来验证一组凭证 , 它接收关键字参数`credentials` , 默认为`username` 和`password` @@ -100,7 +100,7 @@ else: # No backend authenticated the credentials ``` - ## 权限和授权 🍀 + ## 权限和授权 Django本身提供了一个简单的权限系统 , 它提供了一种为特定用户和用户组分配权限的方法 @@ -127,13 +127,13 @@ myuser.user_permissions.remove(permission, permission, ...) myuser.user_permissions.clear() ``` -### 默认权限 🍀 +### 默认权限 当`django.contrib.auth`在你的`INSTALLED_APPS`配置中列出时 , 它将确保为你安装的应用中的每个Django模型创建3个默认的权限 , 即add , change和delete 当你运行`manage.py migrate` 时, 将创建这些权限 ; 在`django.contrib.auth`添加`INSTALLED_APPS`之后 , 首次运行`migrate`时 , 将为所有先前安装的模型创建默认权限 , 以及当时安装的任何新模型 ; 之后 , 每次运行`manage.py migrate` , 它将为新的模型创建默认权限(创建权限的函数与`post_migrate`信号连接) -### Groups 🍀 +### Groups `django.contrib.auth.models.Group`模型是一种对用户进行分类的通用方式 , 通过这种方式你可以引用权限或其他标签都这些用户 ; 一个用户可以属于任意多个组 @@ -141,7 +141,7 @@ myuser.user_permissions.clear() 除了权限之外 , 组还是给分类用户分配标签,添加功能的便捷方法 ; 例如 , 你可以创建一个组`Special users` , 只有在该组中的用户才能够访问会员的页面 -### 编程方式创建权限 🍀 +### 编程方式创建权限 虽然我们可以在模型的Meta类中自定义权限 , 但是你也可以直接创建权限 , 例如 , 你可以在`myapp`中为`BlogPost`模型创建`can_publish`权限 : @@ -160,7 +160,7 @@ permission = Permission.objects.create( 然后该权限可以通过`user_permissions`属性或者通过`Group`的`permissions`属性分配给用户 -### 权限缓存 🍀 +### 权限缓存 [`ModelBackend`](https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.backends.ModelBackend)在第一次需要访问User对象的权限时会对权限进行缓存 , 由于对新添加的权限并不会立即检查 , 所以这种做法对`request-response`循环是非常有利的 (例如在admin中) , 如果你想要在添加新的权限后马上在测试或视图检查他们 , 最简单的解决办法是从数据库中重新获取User , 如下 : @@ -196,7 +196,7 @@ def user_gains_perms(request, user_id): ... ``` -## Web请求中的认证 🍀 +## Web请求中的认证 Django使用[Sessions](https://docs.djangoproject.com/en/1.11/topics/http/sessions/)和[Middleware](https://docs.djangoproject.com/en/1.11/ref/middleware/)来拦截[request objects](https://docs.djangoproject.com/en/1.11/ref/request-response/#django.http.HttpRequest)到认证系统中 @@ -213,7 +213,7 @@ else: ... ``` -### 登录用户 🍀 +### 登录用户 如果你有一个经过身份验证的用户 , 你想把它附带到当前的会话中 , 可以通过`login()`函数完成 @@ -246,7 +246,7 @@ def my_view(request): ... ``` -### 选择验证后端 🍀 +### 选择验证后端 用户登录时 , 用户的ID和用于身份验证的后端保存在用户的会话中 , 这允许相同的身份验证后端在将来的请求中获取用户的详细信息 . 保存会话中的认证后端选择如下 : @@ -255,7 +255,7 @@ def my_view(request): 3. 如果只有一个 , 则使用`AUTHENTICATION_BACKENDS`中的后端 4. 否则 , 触发异常 -### 登出用户 🍀 +### 登出用户 要登出一个已经通过`django.contrib.auth.login()`登入的用户 , 可以在视图中使用`django.contrib.auth.logout()` @@ -279,9 +279,9 @@ def logout_view(request): 调用`logou()`时 , 当前请求的会话数据将被彻底清楚 , 这是为了防止另外一个人使用相同的Web浏览器登入并访问前一个用户的会话数据 , 如果你想在用户登出之后可以立即访问放入会话中的数据 , 则需要在调用`django.conruib.auth.logout()`之后放入 -### 限制访问页面 🍀 +### 限制访问页面 -#### 原始方式 🍀 +#### 原始方式 限制访问页面的简单原始方法时检查`request.user.is_authenticated` , 并重定向到登录页面 : @@ -306,7 +306,7 @@ def my_view(request): # ... ``` -#### login_required 🍀 +#### login_required 一个比较快捷的方式 , 可以使用`login_required()`装饰器 @@ -364,7 +364,7 @@ def set_password(request): return render(request, 'set_password.html', content) ``` -#### LoginRequiredMixin 🍀 +#### LoginRequiredMixin 当你使用基于类的视图时 , 可以使用`LoginRequireMixin`实现与`login_required`相同的行为 , 这个mixin应该位于继承列表中最左侧的位置 @@ -380,7 +380,7 @@ class MyView(LoginRequiredMixin, View): redirect_field_name = 'redirect_to' ``` -#### user_passes_test 🍀 +#### user_passes_test 为了快捷 , 你可以使用`user_passes_test`装饰器 , 返回False时执行重定向 @@ -409,7 +409,7 @@ def my_view(request): ... ``` -#### UserPassesTestMixin 🍀 +#### UserPassesTestMixin 当使用基于类的视图时 , 可以使用`UserPassesTestMixin` , 与user_passes_test类似 @@ -445,7 +445,7 @@ class MyView(TestMixin1, TestMixin2, View): ... ``` -#### permission_required 🍀 +#### permission_required ```python permission_required(perm, login_url=None, raise_exception=False): @@ -471,7 +471,7 @@ def my_view(request): 装饰器也可以采取可迭代的权限 , 这种情况下 , 用户必须具有所有权限才能访问视图 -#### PermissionRequiredMixin 🍀 +#### PermissionRequiredMixin 对基于类的视图应用权限进行检查 , 可以使用`PermissionRequiredMixin` @@ -503,7 +503,7 @@ has_permission(): ''' ``` -## 在admin中管理用户 🍀 +## 在admin中管理用户 如果`django.contrib.auth`和`django.contrib.admin`这两个你都安装了 , 将可以通过admin方便地查看和管理用户 , 组和权限 ; 可以像其他任何Django模型一样创建和删除用户 , 可以创建组 , 并分配权限给用户和组 , admin中还会保存和显示对用户模型编辑的日志 diff --git "a/05-Web\346\241\206\346\236\266/Django/Django - Django\345\221\275\344\273\244\346\225\264\347\220\206.md" b/docs/django/commands.md similarity index 98% rename from "05-Web\346\241\206\346\236\266/Django/Django - Django\345\221\275\344\273\244\346\225\264\347\220\206.md" rename to docs/django/commands.md index 94ab3cc6a..036808036 100644 --- "a/05-Web\346\241\206\346\236\266/Django/Django - Django\345\221\275\344\273\244\346\225\264\347\220\206.md" +++ b/docs/django/commands.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 django-admin是用于管理Django的命令行工具集 , 此外在每个Django项目中会自动为我们生成一个manage.py , 它与django-admin相同 , 但是会帮我们处理以下几件事情 : @@ -34,7 +34,7 @@ $ python manage.py [options] 后续省略开头的python进行示例 -## 基础命令 🍀 +## 基础命令 ```shell # 显示使用信息和每个应用的命令列表 @@ -104,7 +104,7 @@ $ manage.py migrate --verbosity 2 # --verbosity {0,1,2,3}, -v {0,1,2,3} ''' ``` -## 代码执行命令 🍀 +## 代码执行命令 ```python # 不带参数 diff --git "a/05-Web\346\241\206\346\236\266/Django/11-Django - Forms.md" b/docs/django/forms.md similarity index 97% rename from "05-Web\346\241\206\346\236\266/Django/11-Django - Forms.md" rename to docs/django/forms.md index 29740c00e..bf4fe4e3a 100644 --- "a/05-Web\346\241\206\346\236\266/Django/11-Django - Forms.md" +++ b/docs/django/forms.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 表单在网页中主要负责数据采集功能 , 比如我们可以利用表单来采集访问者的某些信息 , 例如 : 名字 , email地址 , 留言簿等等 @@ -121,7 +121,7 @@ GET还不适合密码表单 , 因为这意味着你的密码将出现在URL中, 不过GET方式适合网页搜索这样的表单 , 因为这种表示一个GET请求的URL可以很容易地作为书签 , 分享和重新提交 -## Forms In Django 🍀 +## Forms In Django Django的表单功能可以简化自动化大部分工作 , 而且还可以比大部分程序员自己所编写的代码更安全 @@ -147,7 +147,7 @@ Django会处理表单工作中的三个显著不同的部分 : 每个字段类型都有一个合适的默认 [Widget class](https://docs.djangoproject.com/en/1.11/ref/forms/widgets/) , 在我们需要时可以将其覆盖 -## 实例化、处理和渲染 🍀 +## 实例化、处理和渲染 在Django中渲染一个对象时 , 我们通常 : @@ -161,7 +161,7 @@ Django会处理表单工作中的三个显著不同的部分 : 当我们实例化表单时 , 我们可以选择让它为空还是预先填充它 -## 构建一个表单 🍀 +## 构建一个表单 ### HTML Form @@ -183,7 +183,7 @@ Django会处理表单工作中的三个显著不同的部分 : 这是一个非常简单的表单 , 实际应用中 , 一个表单可能包含几十上百个字段 , 其中大部分需要预填充 , 而且我们预料到用户将来回编辑 , 提交几次才能完成操作 ; 即使在提交表单之前 , 我们也可能需要在浏览器中进行一些验证 , 我们可能想要使用更复杂的字段 , 这样可以让用户做一些事情 , 例如从日历中选择日期等等 , 那么这个时候 , 让Django来为我们完成大部分工作是很容易的 -### Django Form 🍀 +### Django Form 上面我们已经构建好了一个HTML表单 , 那么现在我们就需要来构建一个Django表单了 @@ -206,7 +206,7 @@ Form的实例具有一个`is_valid()` 方法 , 它会为所有的字段执行验 注意 : 它不包含`
` 标签 , 以及提交按钮 , 所以我们必须自己在模板中提供它们 -### 视图 🍀 +### 视图 发送回Django网站的表单数据由视图处理 , 通常是发布表单的相同视图 , 这允许我们重用一些相同的逻辑 @@ -243,7 +243,7 @@ def get_name(request): 如果`is_valid()` 返回True , 我们就可以在`cleaned_data`属性中找到所有合法的表单数据 , 我们就可以使用这些数据去更新数据库或进行其他操作 , 然后将HTTP重定向发送给浏览器 , 告诉它下一步怎么走 -### 模板 🍀 +### 模板 我们不需要在模板中做很多的工作 , 最简单的例子 : @@ -265,13 +265,13 @@ HTML 5 输入类型和浏览器验证 如果你的表单包含`URLField` , `EmailField` 或其它整数字段类型 , Djanog将使用url , email 和 number这样的HTML 5 输入类型 ; 默认情况下 , 浏览器可能会对这些字段进行它们自身的验证 , 这些验证可能比Django的验证更严格 , 如果你想禁用这个行为 , 可以通过设置form标签的`novalidate` 属性 , 或者指定一个不同的字段 , 如`TextInput` -## More about Django Form classes 🍀 +## More about Django Form classes 所有的表单类都被创建为`django.forms.Form` 的子类 , 包括Django Admin中的 [ModelForm](https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/) 实际上 , 如果你的表单打算直接用来添加和编辑Django的模型 , [ModelForm](https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/) 可以节省你的许多时间 , 精力和代码 , 因为它将根据Model类中构建一个表单以及相应的字段和属性 -### 绑定和未绑定 🍀 +### 绑定和未绑定 绑定和未绑定的表单实例之间的区别如下 : @@ -280,11 +280,11 @@ HTML 5 输入类型和浏览器验证 表单的`is_bound` 属性将告诉你一个表示是否具有绑定的数据 -### 字段 🍀 +### 字段 前面的例子中 , 我们仅使用了一个字段 , 当然字段还有很多 , 我们可以在 [Form fields](https://docs.djangoproject.com/en/1.11/ref/forms/fields/) 中找到完整的列表 -### 窗口小部件 🍀 +### 窗口小部件 每个表单字段都有一个对应的 [Widget class](https://docs.djangoproject.com/en/1.11/ref/forms/widgets/) , 它对应一个HTML表单小部件 , 例如 : `` @@ -297,7 +297,7 @@ HTML 5 输入类型和浏览器验证 '' ``` -### 字段数据 🍀 +### 字段数据 不管提交的是什么数据 , 一旦通过调用`is_valid()` 成功验证后 , 验证后的表单数据将位于`form.cleaned_data` 字典中 , 并且这些数据已经为你转换好Python类型 @@ -324,9 +324,9 @@ if form.is_valid(): 关于Django中如何发送邮件的更多信息 , 见[Sending email](https://docs.djangoproject.com/en/1.11/topics/email/) -## 使用表单模板 🍀 +## 使用表单模板 -### 表单渲染选项 🍀 +### 表单渲染选项 对于`/
``` -## < div > and < span > 🍀 +## < div > and < span > HTML可以通过< div > 和 < span >将元素组合起来 @@ -189,7 +189,7 @@ HTML可以通过< div > 和 < span >将元素组合起来 ``` -## < form > 🍀 +## < form > < form >为表单标签 , 表单是一个包含表单元素的区域 @@ -213,7 +213,7 @@ password: ``` -## < input > 🍀 +## < input > < input >标签规定了用户可以在其中输入数据的输入字段 , 输入字段可以通过多种方式改变 , 取决于type属性 @@ -238,7 +238,7 @@ password: 以上简单的介绍了几个属性 , 更多input元素属性可以通过访问[W3school](http://www.w3school.com.cn/tags/tag_input.asp)进行学习 -## < lable > 🍀 +## < lable > < label > 标签为input元素定义标注(标记) @@ -254,7 +254,7 @@ lable元素不会向用户呈现任何特殊效果 , 不过当鼠标点击时 , ``` -## < textarea > 🍀 +## < textarea > 用于定义多行文本 @@ -267,7 +267,7 @@ lable元素不会向用户呈现任何特殊效果 , 不过当鼠标点击时 , ``` -## < fieldset > 🍀 +## < fieldset > < fieldset >标签用于将表单内容的一部分打包 , 生成一组相关表单的字段 , 浏览器会以特殊方式来显示 , 它们可能有特殊的边界 , 3D效果 , 或者甚至可创建一个子表单来处理这些元素 diff --git "a/04-\345\211\215\347\253\257/07-Web\345\274\200\345\217\221 - BOM.md" b/docs/front-end/bom.md similarity index 99% rename from "04-\345\211\215\347\253\257/07-Web\345\274\200\345\217\221 - BOM.md" rename to docs/front-end/bom.md index 725a14716..01a0210ba 100644 --- "a/04-\345\211\215\347\253\257/07-Web\345\274\200\345\217\221 - BOM.md" +++ b/docs/front-end/bom.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 由于JavaScript的出现就是为了能在浏览器中运行 , BOM , 即浏览器对象模型(Browser Object Model) , BOM使JavaScript有能力与浏览器进行"对话" @@ -19,7 +19,7 @@ 下面就开始介绍浏览器对象啦 -## Window 🍀 +## Window 所有的浏览器都支持`window`对象 , 它表示浏览器窗口 , 所有JavaScript全局对象 , 函数以及变量均自动成为window对象的成员 , 也就是说Window对象是客户端JavaScript最高层对象之一 @@ -84,7 +84,7 @@ alert('window inner size:' + window.innerWidth + 'x' + window.innerHeight); // 直接在浏览器中consle下执行 ``` -## document 🍀 +## document 每个载入浏览器的HTML文档都会成为document对象 , document对象使我们可以从脚本中对HTML页面中的所有元素进行访问 @@ -121,7 +121,7 @@ document对象方法 document.title = '努力学习JavaScript!'; ``` -## navigator 🍀 +## navigator navigator对象包含有关浏览器的信息 , 所有浏览器中都支持 , navigator对象的实例是唯一的 , 它是Window对象的子对象 , 所以可以用Window对象的navigator属性来引用它 , 即`window.navigator` , 当然也可以直接`navigator` @@ -159,7 +159,7 @@ alert('appName = ' + navigator.appName + '\n' + 'userAgent = ' + navigator.userAgent); ``` -## screen 🍀 +## screen screen对象中存放着有关显示浏览器屏幕的信息 , 可用Window对象中的screen属性直接引用 , 即`window.screen` , 或者`screen` , 所有浏览器都支持 @@ -187,7 +187,7 @@ screen对象属性 alert('screen size = ' + screen.width + ' x ' + screen.height); ``` -## history 🍀 +## history history对象最初设计来表示窗口的浏览历史 , 但出于隐私方面的原因 , history对象不在允许脚本访问已经访问过的实际URL , 唯一保持使用的功能只有[back()](http://www.w3school.com.cn/jsref/met_his_back.asp)、[forward()](http://www.w3school.com.cn/jsref/met_his_forward.asp) 和 [go()](http://www.w3school.com.cn/jsref/met_his_go.asp) 方法 @@ -215,7 +215,7 @@ history.back() // 返回结果:undefined ``` -## location 🍀 +## location location对象包含有关当前URL的信息 , location对象是Window对象的一部分 , 可通过`window.location`属性来访问 , 或者`location` diff --git "a/04-\345\211\215\347\253\257/05-Web\345\274\200\345\217\221 - CSS.md" b/docs/front-end/css.md similarity index 97% rename from "04-\345\211\215\347\253\257/05-Web\345\274\200\345\217\221 - CSS.md" rename to docs/front-end/css.md index 9d9d6e1a1..6109e7d5c 100644 --- "a/04-\345\211\215\347\253\257/05-Web\345\274\200\345\217\221 - CSS.md" +++ b/docs/front-end/css.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 CSS指的是**层叠样式表(Cascading Style Sheets)** , 用于定义如何显示HTML元素 @@ -49,7 +49,7 @@ CSS是在HTML 4 开始使用的 , 是为了更好的渲染HTML元素而引入的 ``` -## 选择器 🍀 +## 选择器 CSS规则由两个主要的部分构成 : 选择器 , 以及一条或多条声明 , 如下 : @@ -59,7 +59,7 @@ selector {declaration1;declaration2;... declarationN} 选择器的种类有很多 , 下面就开始介绍各种选择器 -### 元素选择器 🍀 +### 元素选择器 元素选择器又称标签选择器 @@ -69,7 +69,7 @@ selector {declaration1;declaration2;... declarationN} div {background-color:red;} ``` -### Class选择器 🍀 +### Class选择器 class选择器用于描述一组元素的样式 , class可以在多个元素中使用 @@ -84,7 +84,7 @@ p.center {text-align:center;} 类名的第一个字符不能使用数字 , 它无法在Mozilla或Firefox中起作用/ -### ID选择器 🍀 +### ID选择器 ID选择器可以为标有特定id的HTML元素指定特定的样式 @@ -98,7 +98,7 @@ CSS中ID选择器以"#"来定义 ID属性不要以数字开头 , 数字开头的ID在Mozilla/Firefox浏览器中不起作用 -### 属性选择器 🍀 +### 属性选择器 选择拥有某些属性的元素 , 也可设置特定属性 @@ -113,7 +113,7 @@ p[class="important warning"] {color: red;} p[class~="important"] {color: red;} ``` -### 后代选择器 🍀 +### 后代选择器 后代选择器又称包含选择器 , 后代选择器可以选择作为**某元素后代**的元素 @@ -122,7 +122,7 @@ p[class~="important"] {color: red;} h1 em {color:red;} ``` -### 子元素选择器 🍀 +### 子元素选择器 与后代选择器相比 , 子元素选择器只能选择作为某元素子元素中的元素 @@ -131,7 +131,7 @@ h1 em {color:red;} h1 > strong {color:red;} ``` -### 相邻兄弟选择器 🍀 +### 相邻兄弟选择器 相邻兄弟选择器可以选择紧接在另一元素后的元素 , 且二者有相同父元素 @@ -140,7 +140,7 @@ h1 > strong {color:red;} h1 + p {margin-top:50px;} ``` -### 伪类 🍀 +### 伪类 伪类用于向某些选择器添加特殊的效果 @@ -160,7 +160,7 @@ a.red : visited {color: #FF0000}
CSS Syntax ``` -### 伪元素 🍀 +### 伪元素 伪元素用于向某些选择器设置特殊效果 @@ -196,9 +196,9 @@ input,div,p { background-color:red; }

``` -## 常用属性 🍀 +## 常用属性 -### background 🍀 +### background **背景色** @@ -270,7 +270,7 @@ body { } ``` -### border 🍀 +### border border属性允许规定元素边框的样式 , 宽度和颜色 @@ -284,7 +284,7 @@ p.aside {border-style: solid dotted dashed double;} 更多边框样式及边框相关 , [在我这里](http://www.w3school.com.cn/css/css_border.asp) -### margin 🍀 +### margin margin属性可以用来设置外边距 , 接受任何长度单位 , 百分数甚至负值 @@ -316,7 +316,7 @@ p {margin: 1px;} /* 等价于 1px 1px 1px 1px */ - [margin-bottom](http://www.w3school.com.cn/cssref/pr_margin-bottom.asp) - [margin-left](http://www.w3school.com.cn/cssref/pr_margin-left.asp) -### padding 🍀 +### padding padding属性可以设置内边距 , 接受长度值或百分比值 , 但不允许使用负值 @@ -335,7 +335,7 @@ h1 {padding: 10px 0.25em 2ex 20%;} - [padding-bottom](http://www.w3school.com.cn/cssref/pr_padding-bottom.asp) - [padding-left](http://www.w3school.com.cn/cssref/pr_padding-left.asp) -### display 🍀 +### display display属性规定元素应该生成的框的类型 @@ -355,7 +355,7 @@ display属性常用的值 : 更多[display属性](http://www.w3school.com.cn/cssref/pr_class_display.asp) -### cursor 🍀 +### cursor cursor属性规定要显示的光标的类型 @@ -421,7 +421,7 @@ cursor属性规定要显示的光标的类型 ``` -### position 🍀 +### position CSS有三种基本的定位机制 : 普通流 , 浮动和绝对定位 ; 除非专门指定 , 否则所有框都在普通流中定位 @@ -511,7 +511,7 @@ position属性有以下属性值 : -### float 🍀 +### float float属性可以实现浮动的框 @@ -549,7 +549,7 @@ img { ``` -### opacity 🍀 +### opacity opacity属性用于定义透明效果 , opacity属性能设置的值从0.0到1.0 , 值越小则越透明 diff --git "a/04-\345\211\215\347\253\257/08-Web\345\274\200\345\217\221 - DOM.md" b/docs/front-end/dom.md similarity index 97% rename from "04-\345\211\215\347\253\257/08-Web\345\274\200\345\217\221 - DOM.md" rename to docs/front-end/dom.md index a79fd4a34..0269d0710 100644 --- "a/04-\345\211\215\347\253\257/08-Web\345\274\200\345\217\221 - DOM.md" +++ b/docs/front-end/dom.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 上一篇中了解了BOM , 其存在是为了控制浏览器的行为 , 而这一篇所说的DOM则是为了操作HTML和XML文档出现的接口 @@ -19,7 +19,7 @@ DOM全称为`Document Object Model` , 也就是文档对象模型 , DOM是W3C( 同样的 , 通过JavaScript可以来操作DOM -## DOM查找 🍀 +## DOM查找 **直接查找** @@ -63,11 +63,11 @@ var first = test.firstElementChild; var last = test.lastElementChild; ``` -## DOM修改 🍀 +## DOM修改 对于DOM的修改操作有很多, 比如内容 , 样式 , 属性等等 , 都是可以用DOM进行修改的 -### 内容 🍀 +### 内容 ```javascript innerHTML // 设置或获取位于对象起始和结束标签内的HTML,符合W3C标准 @@ -89,7 +89,7 @@ p.innerHTML = 'ABC RED XYZ'; //

...

的内部结构已修改 ``` -### 属性 🍀 +### 属性 ```javascript attribute // 获取所有标签属性 @@ -108,7 +108,7 @@ atr.nodeValue="democlass"; document.getElementById('n1').setAttributeNode(atr); ``` -### Class 🍀 +### Class ```javascript className // 获取所有类名 @@ -116,7 +116,7 @@ classList.remove(cls) // 删除指定类 classList.add(cls) // 添加类 ``` -### 标签 🍀 +### 标签 **创建标签** @@ -144,7 +144,7 @@ xxx.appendChild(tag) xxx.insertBefore(tag,xxx[1]) ``` -### 样式 🍀 +### 样式 ```javascript var obj = document.getElementById('i1') @@ -174,7 +174,7 @@ obj.style.backgroundColor = "red"; ``` -### 位置 🍀 +### 位置 ```javascript document.documentElement.offsetHeight // 总文档高度 @@ -210,13 +210,13 @@ document.documentElement代指文档根节点 ``` -### 表单 🍀 +### 表单 ```javascript document.geElementById('form').submit() ``` -### 其他 🍀 +### 其他 ```javascript console.log // 输出框 @@ -233,7 +233,7 @@ setTimeout // 单次定时器 clearTimeout // 清除单次定时器 ``` -## DOM事件 🍀 +## DOM事件 **鼠标事件** diff --git "a/04-\345\211\215\347\253\257/02-Web\345\274\200\345\217\221 - HTML-head.md" b/docs/front-end/head.md similarity index 94% rename from "04-\345\211\215\347\253\257/02-Web\345\274\200\345\217\221 - HTML-head.md" rename to docs/front-end/head.md index add7a674e..d568f317b 100644 --- "a/04-\345\211\215\347\253\257/02-Web\345\274\200\345\217\221 - HTML-head.md" +++ b/docs/front-end/head.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 **HTML是什么 ?** @@ -61,7 +61,7 @@ file.html └── ``` -## < !DOCTYPE html > 🍀 +## < !DOCTYPE html > `DOCTYPE`告诉浏览器使用什么样的html或xhtml规范来解析html文档 @@ -69,17 +69,17 @@ file.html 如果html文档添加了声明 , 那么compatMode就是`CSS1Compat(标准兼容模式已开启,称为严格模式)` , 即按照W3C的标准解析渲染页面 , 这样所有的浏览器显示的就是一个样子了 -## < html > 🍀 +## < html > < html > 元素是HTML页面的根元素 , HTML文档由嵌套的HTML元素构成 一般包括< head >与< body > , 如下 -### < head > 🍀 +### < head > < head >元素包含了所有的头部标签元素 , 在< head >元素中可以插入脚本(Script) , 样式(CSS) , 及各种meta信息 -#### < meta > 🍀 +#### < meta > meta标签描述了一些基本的元数据 @@ -117,7 +117,7 @@ meta标签描述了一些基本的元数据 ``` -#### < title > 🍀 +#### < title > 定义网页头部信息 , 在HTML/XHTML文档中是必须的 @@ -140,7 +140,7 @@ title元素 : ``` -#### < base > 🍀 +#### < base > < base > 标签描述了基本的链接地址/链接目标 , 该标签作为HTML文档中所有的链接默认链接 @@ -150,7 +150,7 @@ title元素 : ``` -#### < link > 🍀 +#### < link > < link > 标签定义了文档与外部资源之间的关系 , 通常用于链接到样式表 : @@ -163,7 +163,7 @@ title元素 : ``` -#### < style > 🍀 +#### < style > < style > 标签定义HTML文档的样式文件引用地址 , 在< style > 元素中也可以直接添加样式来渲染HTML文档 @@ -184,7 +184,7 @@ title元素 : ``` -#### < script > 🍀 +#### < script > < script > 标签用于加载脚本文件 , 也可直接写脚本代码 @@ -197,7 +197,7 @@ title元素 : ``` -### < body > 🍀 +### < body > body元素是html文档中的主体 , 表示网页的主题部分 , 也就是用户可以看到的内容 , 可以包含文本 , 图片 , 音频 , 视频等各种内容 diff --git "a/04-\345\211\215\347\253\257/06-Web\345\274\200\345\217\221 - JavaScript.md" b/docs/front-end/javascript.md similarity index 99% rename from "04-\345\211\215\347\253\257/06-Web\345\274\200\345\217\221 - JavaScript.md" rename to docs/front-end/javascript.md index 88b21cf56..193214e39 100644 --- "a/04-\345\211\215\347\253\257/06-Web\345\274\200\345\217\221 - JavaScript.md" +++ b/docs/front-end/javascript.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 JavaScript是属于网络的脚本语言 , 是因特网上最流行的脚本语言 @@ -49,7 +49,7 @@ JavaScript被数百万计的网页用来改进设计 , 验证表单 , 检测浏 多行注释 : `/* ... */` , (CSS注释也是如此) -## 变量 🍀 +## 变量 在JavaScript中 , 变量的声明默认表示声明的全局变量 , 局部变量必须以`var`开头 @@ -75,7 +75,7 @@ JavaScript被数百万计的网页用来改进设计 , 验证表单 , 检测浏 **注意 : JavaScript中严格区分大小写 , 并且以 ";" 号结束** -## 数据类型 🍀 +## 数据类型 JavaScript中的数据类型有字符串 , 数字 , 布尔 , 数组 , 对象 , Null , Undefined @@ -188,7 +188,7 @@ JavaScript中的数据类型有字符串 , 数字 , 布尔 , 数组 , 对象 , N 要获取对象的属性 , 只需用`对象变量.属性名`即可 , 也可`对象变量[属性名]` -## 语句与异常 🍀 +## 语句与异常 1. **条件语句** @@ -256,7 +256,7 @@ JavaScript中的数据类型有字符串 , 数字 , 布尔 , 数组 , 对象 , N // throw Error(e) 主动抛出异常,如Python中的raise ``` -## 函数 🍀 +## 函数 1. **基本函数** @@ -385,7 +385,7 @@ JavaScript中的数据类型有字符串 , 数字 , 布尔 , 数组 , 对象 , N } ``` -## 其他 🍀 +## 其他 1. **序列化** diff --git "a/04-\345\211\215\347\253\257/09-Web\345\274\200\345\217\221 - jQuery.md" b/docs/front-end/jquery.md similarity index 97% rename from "04-\345\211\215\347\253\257/09-Web\345\274\200\345\217\221 - jQuery.md" rename to docs/front-end/jquery.md index ea8b86b93..3de97a6e7 100644 --- "a/04-\345\211\215\347\253\257/09-Web\345\274\200\345\217\221 - jQuery.md" +++ b/docs/front-end/jquery.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 为了使写更少的代码完成更多的功能 , JavaScript (helper) 库应运而生 , 这些JavaScript库常被成为JavaScript框架 @@ -57,7 +57,7 @@ jQuery库是一个JavaScript文件 , 所以可以直接使用` ``` -## $符号 🍀 +## $符号 `$`是jQuery符号 , jQuery把所有功能全部封装在一个全局变量`jQuery`中 , 而`$`也是一个合法的变量名 , 它是变量jQuery的别名 @@ -93,9 +93,9 @@ var variable = DOM对象 - 选择符(selector)"查询"和"查找" HTML 元素 - jQuery 的 action() 执行对元素的操作 -## jQuery查找 🍀 +## jQuery查找 -### 选择器 🍀 +### 选择器 1. **ID** @@ -220,7 +220,7 @@ var variable = DOM对象 ``` -### 筛选器 🍀 +### 筛选器 ```javascript $('#i1').next()         // 下一个同级标签 @@ -242,9 +242,9 @@ last()                       // 匹配找到的最后一个元素 hasClass(class)              // 是否含有指定的类 ``` -## jQuery操作 🍀 +## jQuery操作 -### 属性 🍀 +### 属性 ```javascript // 专门用于做自定义属性 @@ -256,7 +256,7 @@ $(..).prop('checked') $(..).prop('checked', true) ``` -### CSS 🍀 +### CSS ```javascript $('t1').css('样式名称', '样式值') @@ -273,7 +273,7 @@ $('i1').outerHeight() // 获取边框 + 纯高度 + ? $('i1').outerHeight(true) // 获取边框 + 纯高度 + ? ``` -### 文本操作 🍀 +### 文本操作 ```javascript $(..).text() // 获取文本内容 @@ -284,7 +284,7 @@ $(..).val()            // 获取值 $(..).val(..)          // 设置值 ``` -### 文档处理 🍀 +### 文档处理 ```javascript // 内部插入 @@ -303,7 +303,7 @@ empty()                            // 匹配空元素 clone()                            // 克隆 ``` -### 事件 🍀 +### 事件 ```javascript jQuery: @@ -372,7 +372,7 @@ $(function(){ ``` -### 效果 🍀 +### 效果 ```javascript // 基本 @@ -425,7 +425,7 @@ jQuery.fx.interval ``` -### 扩展 🍀 +### 扩展 - [jQuery.fn.extend(object)](http://jquery.cuishifeng.cn/jQuery.fn.extend.html) - [jQuery.extend(object)](http://jquery.cuishifeng.cn/jQuery.extend_object.html) diff --git "a/02-Go/Golang - \350\257\255\350\250\200\345\237\272\347\241\200.md" b/docs/go/basic.md similarity index 100% rename from "02-Go/Golang - \350\257\255\350\250\200\345\237\272\347\241\200.md" rename to docs/go/basic.md diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..65828d5a5 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,72 @@ + + + + + Document + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git "a/09-Linux/Command/01-\345\270\270\347\224\250\345\221\275\344\273\244.md" b/docs/linux/commands.md similarity index 97% rename from "09-Linux/Command/01-\345\270\270\347\224\250\345\221\275\344\273\244.md" rename to docs/linux/commands.md index f31140aeb..acaade402 100644 --- "a/09-Linux/Command/01-\345\270\270\347\224\250\345\221\275\344\273\244.md" +++ b/docs/linux/commands.md @@ -9,7 +9,7 @@ -## ls 🍀 +## ls ```shell [root@lyonyang ~]# ls [aAdfFhilnrRSt] 目录名称 @@ -37,7 +37,7 @@ --time=(atime,ctime) : 输出访问时间或改变权限属性时间(ctime)而非内容梗概时间(modification time) ``` -## cp 🍀 +## cp ```shell [root@lyonyang ~]# cp [-adfilprsu] 源文件(source) 目标文件(destination) @@ -55,7 +55,7 @@ # 注意 : 如果源文件有两个以上,则最后一个目的文件一定要是"目录"才行 ``` -## rm 🍀 +## rm ```shell [root@lyonyang ~]# rm [-fir] 文件或目录 @@ -65,7 +65,7 @@ -r : 递归删除,最常用在目录的删除,此参数异常危险 ``` -## mv 🍀 +## mv ```shell [root@lyonyang ~]# mv [-fiu] source destination @@ -76,7 +76,7 @@ -u : 若目标文件已经存在,且source比较新,才会更新 ``` -## cat 🍀 +## cat ```shell [root@lyonyang ~]# cat [-AbenTv] @@ -91,7 +91,7 @@ tac 命令与 cat 命令恰好相反 -## nl 🍀 +## nl ```shell [root@lyonyang ~]# nl [-bnw] 文件 @@ -106,7 +106,7 @@ tac 命令与 cat 命令恰好相反 -w : 行号字段占用的位数 ``` -## touch 🍀 +## touch ```shell [root@lyonyang ~]# touch [-acdmt] 文件 @@ -118,7 +118,7 @@ tac 命令与 cat 命令恰好相反 -t : 后面可以接欲修改的时间而不用目前的时间,格式为[YYMMDDhhmm] ``` -## which 🍀 +## which ```shell [root@lyonyang ~]# which [-a] command @@ -126,7 +126,7 @@ tac 命令与 cat 命令恰好相反 -a : 将所有由 PATH 目录中可以找到的命令均列出 , 而不知第一个被找到的命令名称 ``` -## whereis 🍀 +## whereis ```shell [root@lyonyang ~]# whereis [-bmsu] 文件或目录名 @@ -137,7 +137,7 @@ tac 命令与 cat 命令恰好相反 -u : 查找不在上述三个选项当中的其他特殊文件 ``` -## locate 🍀 +## locate 该命令如果查找新文件 , 需要更新数据库 @@ -150,7 +150,7 @@ tac 命令与 cat 命令恰好相反 -r : 后面可接正则表达式的显示方式 ``` -## find 🍀 +## find ```shell [root@lyonyang ~]# find [PATH] [option] [action] @@ -191,7 +191,7 @@ tac 命令与 cat 命令恰好相反 压缩与打包 -## gzip,zcat 🍀 +## gzip,zcat ```shell [root@lyonyang ~]# gzip [-cdtv#] 文件名 @@ -203,7 +203,7 @@ tac 命令与 cat 命令恰好相反 -# : 压缩等级,-1最快,但是压缩比最差,-9最慢,但是压缩比最好,默认是-6 ``` -## bzip2,bzcat 🍀 +## bzip2,bzcat ```shell [root@lyonyang ~]# bzip2 [-cdkzv#] 文件名 @@ -216,7 +216,7 @@ tac 命令与 cat 命令恰好相反 -# : 与gzip一样 ``` -## dump 🍀 +## dump ```shell [root@lyonyang ~]# dump [-Suvj] [-level] [-f 备份文件] 待备份数据 @@ -231,7 +231,7 @@ tac 命令与 cat 命令恰好相反 -w : 列出在/etc/fstab里面的具有dump设置的分区是否有备份过 ``` -## restore 🍀 +## restore ```shell [root@lyonyang ~]# restore -t [-f dumpfile] [-h] # 查看dump文件 @@ -249,7 +249,7 @@ tac 命令与 cat 命令恰好相反 -D : 与-C进行搭配,可以查出后面接的挂载点与dump内有不同的文件 ``` -## tar 🍀 +## tar ```shell [root@lyonyang ~]# tar [-j|-z] [cv] [-f 新建的文件名] filename # 打包与压缩 @@ -278,7 +278,7 @@ tac 命令与 cat 命令恰好相反 解压缩 : `tar -jxv -f filename.tar.bz2 -C 欲解压缩的目录` - ## dd 🍀 + ## dd ```shell [root@lyonyang ~]# dd if="input file" of="output file" bs="block size" count="number" @@ -289,7 +289,7 @@ bs : 规划的一个block的大小,若未指定则默认为512bytes(一个扇区 count : 多少个bs的意思 ``` -## ps 🍀 +## ps ```shell [root@lyonyang ~]# ps aux # 查看系统所有的进程数据 @@ -305,7 +305,7 @@ j : 工作的格式 -f : 做一个更为完整的输出 ``` -## free 🍀 +## free ```shell [root@lyonyang ~]# free [-b|-k|-m|-g] [-t] @@ -314,7 +314,7 @@ j : 工作的格式 -t : 在输出的最终结果中显示物理内存与swap的总量 ``` -## uname 🍀 +## uname ```shell [root@lyonyang ~]# uname [-asrmpi] @@ -327,7 +327,7 @@ j : 工作的格式 -i : 硬件的平台 ``` -## netstat 🍀 +## netstat ```shell [root@lyonyang ~]# netstat -[atunlp] @@ -340,7 +340,7 @@ j : 工作的格式 -p : 列出该网络服务的进程PID ``` -## rpm 🍀 +## rpm ```shell [root@lyonyang ~]# rpm -ivh package_name @@ -363,7 +363,7 @@ j : 工作的格式 -qf : 由后面接的文件名称找出该文件属于哪一个已安装的软件 ``` -## more 🍀 +## more 功能描述: 分页显示文件内容 命令所在路径: `/usr/bin/more` @@ -376,7 +376,7 @@ j : 工作的格式 q或Q 退出 ``` -## less 🍀 +## less 功能描述: 分页显示文件内容(可向上翻页) 命令所在路径: `/usr/bin/less` @@ -384,7 +384,7 @@ q或Q 退出 语法: less [文件名] -## head 🍀 +## head 功能描述: 显示文件前面几行 命令所在路径: `/usr/bin/head` @@ -394,7 +394,7 @@ q或Q 退出 # 参数 : -n : 指定行数 -## tail 🍀 +## tail 功能描述: 显示文件后面几行 命令所在路径: `/usr/bin/tail` diff --git a/09-Linux/Git/01-Git&GitHub.md b/docs/linux/git-and-github.md similarity index 95% rename from 09-Linux/Git/01-Git&GitHub.md rename to docs/linux/git-and-github.md index 98e56a697..cfcbf1a53 100644 --- a/09-Linux/Git/01-Git&GitHub.md +++ b/docs/linux/git-and-github.md @@ -9,13 +9,13 @@ -## 1. Git简介 🍀 +## 1. Git简介 -### 1.1. Git 🍀 +### 1.1. Git > Git是一个免费并且开源的分布式版本控制系统 , 被设计用来快速 , 高效的管理一切从小到大的项目 -### 1.2. 版本控制 🍀 +### 1.2. 版本控制 我们所开发的软件在其整个生命周期 , 都需要进行不断的改进 , 比如 , 日常写bug改bug , 对业务的增删改查等 , 如果没有一个工具来帮助我们控制 , 那么日常开发将会是多么的坑 ... 一不小心删了个文件 , 一不小心提交了错误代码 , 一不小心整个项目就没了 ... @@ -33,7 +33,7 @@ 诞生于2005年 , 又`Linux`系统的创始人`Linus`开发而成 , 主要是因为在2005年`Andrew Tridgell` (大名鼎鼎的Samba的作者) 社团对`BitKeeper`进行反向工程 , 于是激怒了`BitKeeper`软件的所有者`BitMover`公司 , 收回了对Linux社区免费使用`BitKeeper`的授权 , 导致Linus第二个伟大作品 —— `分布式`版本控制系统`Git` -### 1.3. 集中式与分布式 🍀 +### 1.3. 集中式与分布式 **集中式** @@ -55,7 +55,7 @@ 不过随着Git与Github的普及 , 使用分布式的开发者会占绝大多数 ; 只要规则指定得当 , 分布式同样能够像集中式那样进行管理 -## 2. GitHub简介 🍀 +## 2. GitHub简介 **Github 与 Git 是两回事** @@ -65,7 +65,7 @@ 也就是说 , `Github`上公开的软件源代码全都是由Git进行管理 -### 2.1. GitHub提供的主要功能 🍀 +### 2.1. GitHub提供的主要功能 - **Git 仓库** @@ -88,7 +88,7 @@ 开发者向 GitHub 的仓库推送更改或功能添加后 , 可以通过 Pull Request 功能向别人的仓库提出申请 , 请求对方合并 Pull Request 送出后 , 目标仓库的管理者等人将能够查看 Pull Request 的内容及其中包含的代码更改 -## 3. 安装Git 🍀 +## 3. 安装Git **在 Linux 上** @@ -102,7 +102,7 @@ $ sudo apt-get install git 安装成功后我们可以在开始菜单里找到 `Git Bash` , 或者查看鼠标右键菜单中是否有`Git Bash Here` -### 3.1. 初始设置 🍀 +### 3.1. 初始设置 **显示当前配置** @@ -123,13 +123,13 @@ $ git config --global user.email "lyon@xxxxx" # lyon@xxx为邮箱地址 $ git config --global color.ui auto ``` -## 4. GitHub准备工作 🍀 +## 4. GitHub准备工作 -### 4.1. 创建账户 🍀 +### 4.1. 创建账户 进入注册页面完成注册 , [点我跳转注册页面](https://github.com/join?source=header-home) -### 4.2. 设置SSH Key 🍀 +### 4.2. 设置SSH Key GitHub上连接已有的仓库时的认证 , 是通过使用SSH的公开密钥认证方式进行的 , 所以我们需要创建一个SSH Key , 并将其添加至GitHub中 @@ -152,7 +152,7 @@ Enter same passphrase again: # 输入密码 └── qwe.ppk ``` -### 4.3. 添加公钥 🍀 +### 4.3. 添加公钥 创建好密钥之后 , 我们需要在GitHub中添加公有密钥 , 这样就可以使用私有密钥进行认证了 @@ -175,7 +175,7 @@ Hi hirocastest! You've successfully authenticated, but GitHub does not provide shell access. ``` -## 5. 创建仓库 🍀 +## 5. 创建仓库 点击头像旁边的 "+" 下拉框 , 选择`New repository` diff --git "a/09-Linux/Git/02-Git\345\237\272\347\241\200\345\221\275\344\273\244.md" b/docs/linux/git.md similarity index 96% rename from "09-Linux/Git/02-Git\345\237\272\347\241\200\345\221\275\344\273\244.md" rename to docs/linux/git.md index 0a3cbcb5a..a520cc564 100644 --- "a/09-Linux/Git/02-Git\345\237\272\347\241\200\345\221\275\344\273\244.md" +++ b/docs/linux/git.md @@ -9,21 +9,21 @@ -## 1. 前言 🍀 +## 1. 前言 -### 1.1. 工作区 🍀 +### 1.1. 工作区 对于Git来说 , 版本库位于工作区根目录下的`.git`目录中 , 且仅此一处 , 在工作区的子目录下则没有任何其他跟踪文件或目录 而工作区就是我们进行版本控制的某个文件夹 , 我们初始化之后就可以利用Git来进行管理了 -### 1.2. 暂存区 🍀 +### 1.2. 暂存区 在`.git` 目录中有一个`index`文件 , 这个文件就是暂存区(stage) , 当我们执行`git add`命令时 , 我们的修改并不是直接添加到了master分支 , 而是添加到了暂存区 , 我们需要继续执行`git commit`命令才能将修改从暂存区移到master分支 , 这样才算完成了一次提交 -## 2. 基本操作 🍀 +## 2. 基本操作 -### 2.1. 初始化仓库 🍀 +### 2.1. 初始化仓库 我们要使用Git进行版本管理 , 必须先初始化仓库 @@ -41,7 +41,7 @@ Initialized empty Git repository in /Users/github-book 初始化成功后 , 会自动生成`.get`目录 , 这个`.git` 存储着管理当前目录内容所需的仓库数据 , 在Git中 , 我们将这个目录的内容称为 "附属于该仓库的工作区" -### 2.2. 查看仓库状态 🍀 +### 2.2. 查看仓库状态 `git status` 命令用于显示Git仓库的状态 , 工作区和仓库在被操作的过程中 , 状态会不断发生变化 , 在Git操作过程中经常用`git status`命令查看 "当前状态" @@ -54,7 +54,7 @@ Initial commit nothing to commit (create/copy files and use "git add" to track) # 没有可提交的内容 ``` -### 2.3. 增删文件 🍀 +### 2.3. 增删文件 我们一般创建一个GitHub远程仓库时 , 都会选择不自动初始化 , 而是自己来建立`README.md`文件作为管理对象 , 为第一次提交做前期准备 @@ -120,7 +120,7 @@ nothing to commit, working tree clean ``` -### 2.4. 查看日志 🍀 +### 2.4. 查看日志 `git log` 命令可以查看以往仓库中提交的日志 , 包括可以查看什么人在什么时候进行了提交或合并 , 以及操作前后有怎样的差别 @@ -168,7 +168,7 @@ $ git log -p $ git log -p README.md ``` -### 2.5. 查看更改前后区别 🍀 +### 2.5. 查看更改前后区别 `git diff`命令可以查看工作区 , 暂存区 , 最新提交至今的差别 @@ -197,7 +197,7 @@ index e69de29..88b52b3 100644 如果工作区和暂存区的状态并无差别 , 那么我们在执行`git commit`命令之前先执行`git diff HEAD`命令 , 查看本次提交与上次提交之间有什么差别 , HEAD是指向当前分支中最新一次提交的指针 , 这是一个好习惯 -## 3. 分支操作 🍀 +## 3. 分支操作 在进行多个并行作业时 , 我们会用到分支 , 而在我们日常开发中 , 基本都会采用并发作业的方式 ; 在这类并行开发的过程中 , 往往同时存在多个最新代码状态 @@ -211,7 +211,7 @@ index e69de29..88b52b3 100644 通过灵活的使用分支 , 可以让多个人同时高效地进行并行开发 , 并且使开发过程更加的安全 -### 3.1. 显示分支 🍀 +### 3.1. 显示分支 `git branch`命令可以将分支名列表显示 , 同时可以确认当前所在分支 @@ -222,7 +222,7 @@ $ git branch 可以看到`master`分支左侧标有 `*` 号 , 这表示我们当前所在分支 , 也就是说 , 我们正在`master`分支下进行开发 -### 3.2. 创建分支 🍀 +### 3.2. 创建分支 `git checkout -b`命令可以用于我们以当前`master`分支为基础 , 创建一个新的分支 , 并切换到新建分支 @@ -257,7 +257,7 @@ $ git commit -m "Add feature-A" # 提交 1 file changed, 5 insertions(+) ``` -### 3.3. 切换分支 🍀 +### 3.3. 切换分支 我们在`feature-A`分支下对文件进行了修改后 , 可以使用`git checkout [branch]`切分支 @@ -279,7 +279,7 @@ $ git checkout - # 从master分支继续切换回feature-A分支 Switched to branch 'feature-A' ``` -### 3.4. 合并分支 🍀 +### 3.4. 合并分支 通过合并分支 , 可以将一个分支的内容合并到另一个分支中去 , 比如我们上面修改了`feature-A`分支 , 现在将其与`master`分支进行合并 @@ -333,9 +333,9 @@ $ git log --graph ``` -## 4. 版本回溯 🍀 +## 4. 版本回溯 -### 4.1. 回溯 🍀 +### 4.1. 回溯 我们可以使用`git rest --hard`命令让仓库的`HEAD` , 暂存区 , 工作区回溯到指定状态 , 只要提供目标时间点的哈希值 , 就可以完全恢复至该时间点的状态 @@ -360,7 +360,7 @@ a905c70 HEAD@{1}: merge feature-A: Merge made by the 'recursive' strategy. 只要我们不进行Git的GC(Garbage Collection, 垃圾回收) , 就可以通过日志随意调去近期的历史状态 , 这样我们就可以在过去未来中自由穿梭 -### 4.2. 消除冲突 🍀 +### 4.2. 消除冲突 合并分支有时会出现冲突的情况 , 我们创建一个新的分支来进行说明 @@ -503,7 +503,7 @@ Deleted branch feature-B (was 89be876). 这样我们解决分支冲突问题 -### 4.3. 分支管理 🍀 +### 4.3. 分支管理 通常 , 合并分支时 , 如果课可能 , Git会使用`Fast forward`模式 , 但是这种模式下 , 一旦我们删除分支后 , 那么分支的信息也随之被丢掉了 , 所以为了保留历史的分支信息 , 我们可以强制禁用`Fast forward`模式 diff --git "a/09-Linux/Git/GitHub Pages&Gitbook&Travis CI\346\214\201\347\273\255\346\236\204\345\273\272\345\215\232\345\256\242.md" b/docs/linux/gitbook.md similarity index 96% rename from "09-Linux/Git/GitHub Pages&Gitbook&Travis CI\346\214\201\347\273\255\346\236\204\345\273\272\345\215\232\345\256\242.md" rename to docs/linux/gitbook.md index 3bb495e06..179db8447 100644 --- "a/09-Linux/Git/GitHub Pages&Gitbook&Travis CI\346\214\201\347\273\255\346\236\204\345\273\272\345\215\232\345\256\242.md" +++ b/docs/linux/gitbook.md @@ -11,7 +11,7 @@ -## 1. 开始 🍀 +## 1. 开始 如今程序猿没个个人站点或是博客 , 都不好意思出门了 @@ -19,11 +19,11 @@ 那么 , 开始吧 -### 1.1. 搭建准备 🍀 +### 1.1. 搭建准备 实际上 , 没什么要准备的 ... GitHub账号就不用说了 , 创建一个新的仓库吧 , 来存放个人博客笔记文件 -### 1.2. 搭建要求 🍀 +### 1.2. 搭建要求 本博客 (更形象点 : 一本书) , 文件必须为`.md`文件 , 也就是`MarkDown` 文件 , 所以如果你不是这种格式 , 那么我建议你开始使用 , [Markdown语法说明](https://www.appinn.com/markdown/) , 当然还有`.rst` , 也就是`reStructuredText` 文件也是可以的 , 但是本文仅说明关于Markdown的构建 @@ -36,7 +36,7 @@ 本地Markdown编辑可以使用Typora , VSCode , Atom等等 , 本人用的是Typora , 支持功能比较多 -### 1.3. 搭建说明 🍀 +### 1.3. 搭建说明 本博客使用GitBook进行构建 , 如Hexo一样 , 我们并不是使用GitBook所有 , 而是仅仅使用它的一个功能 : GitBook和Hexo都能帮我们把Markdown格式文件转换成html文件 , 并且是附带了样式的html文件 @@ -48,7 +48,7 @@ 那么就开始了 -## 2. 配置文件 🍀 +## 2. 配置文件 创建仓库就不说了 , 创建完成之后 , 我们就先开始添加配置文件了 , 这是构建的重中之重 @@ -66,7 +66,7 @@ └── README.md -- 必须要有 ``` -### 2.1. .travis.yml 🍀 +### 2.1. .travis.yml 当Travis CI发现你的仓库有更新时 , 就会来你仓库找到这个配置文件 , 并执行它 @@ -92,7 +92,7 @@ script: 注意GH_REF修改成各自的GitHub用户名和仓库 -### 2.2. deploy.sh 🍀 +### 2.2. deploy.sh 该文件是`.travis.yml`中需要执行的脚本 @@ -122,7 +122,7 @@ git remote add origin git@github.com:用户名/仓库.git git push -f "https://${GH_TOKEN}@${GH_REF}" gh-pages:gh-pages ``` -### 2.3. summary_create.sh 🍀 +### 2.3. summary_create.sh ``` #!/bin/bash @@ -143,7 +143,7 @@ sed 's#00README.md#README.md#g' tmp_SUMMARY.md|grep -v "SUMMARY](SUMMARY"|awk ' sed -ri 's#(\S+* \[)[0-9]+-(.*$)#\1\2#g' SUMMARY.md ``` -### 2.4. book.json 🍀 +### 2.4. book.json GitBook样式文件 , 也就是生成html附带的样式 @@ -222,7 +222,7 @@ GitBook样式文件 , 也就是生成html附带的样式 } ``` -### 2.5. README.md 🍀 +### 2.5. README.md 项目根目录下必须要有README.md文件 , 并且所有需要构建的文件下 , 也必须要有README.md文件 @@ -241,11 +241,11 @@ GitBook样式文件 , 也就是生成html附带的样式 README.md中的内容就是页面上目录显示的内容 -### 2.6. SUMMARY.md 🍀 +### 2.6. SUMMARY.md 必须要有 , 这个GitBook构建需要的 , 上面summary_create.sh脚本就是来自动帮我们生成目录的 -## 3. 添加Personal access tokens 🍀 +## 3. 添加Personal access tokens 配置文件添加完成之后 , 我们就要将Travis CI 与GitHub建立连接了 @@ -267,7 +267,7 @@ README.md中的内容就是页面上目录显示的内容 这个token是用来配置Travis的环境变量的 -## 4. 配置Travis CI 🍀 +## 4. 配置Travis CI 首先我们进入 : [https://travis-ci.org/](https://travis-ci.org/) diff --git a/09-Linux/RabbitMQ.md b/docs/linux/rabbitmq.md similarity index 98% rename from 09-Linux/RabbitMQ.md rename to docs/linux/rabbitmq.md index 45007de40..4fd0533bb 100644 --- a/09-Linux/RabbitMQ.md +++ b/docs/linux/rabbitmq.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 RabbitMQ 是一个实现了 AMQP 协议标准的开源消息代理和队列服务器 , 和 Beanstalkd 不同的是 , 它是企业级消息系统 , 自带了集群 , 管理 , 插件系统等特性 , 在高可用性 , 可扩展性性 , 易用性等方面做得很好 , 现在被互联网公司广泛使用 @@ -25,7 +25,7 @@ $ sudo apt-get install rabbitmq-server -yq $ pip install pika ``` -## AMQP 🍀 +## AMQP AMQP (Advanced Message Queuing Protocol , 高级消息队列协议) 是一个异步消息传递所使用的应用层协议规范 , 它的设计初衷是为了摆脱商业 MQ 高额费用和不同 MQ 供应商的接口不统一的问题 , 所以一开始就设计成开放标准 , 以解决企业复杂的消息队列需求问题 @@ -50,7 +50,7 @@ AMQP 工作流程如下 : 3. 扇形交换机 : 将消息路由给绑定到它身上的所有队列 , 且不理会绑定的路由建 , 扇形交换机用来处理消息的广播路由 4. 头交换机 : 一般用不到 , 允许匹配 AMQP 的头而非路由建 , 和直接交换机差不多 , 但是性能差很多 -## 简单示例 🍀 +## 简单示例 发布者 diff --git a/09-Linux/Git/Travis CI.md b/docs/linux/travis-ci.md similarity index 97% rename from 09-Linux/Git/Travis CI.md rename to docs/linux/travis-ci.md index 231c669e5..486fce565 100644 --- a/09-Linux/Git/Travis CI.md +++ b/docs/linux/travis-ci.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 [`Travis CI`](http://travis-ci.org/) 是一款免费服务 , 专门托管面向开源开发组织的CI (Continuous Integration , 持续集成) @@ -21,7 +21,7 @@ CI是XP (Extreme Programming , 极限编程) 的实践之一 , 近年来人们 更多支持语言 , [Support language](https://docs.travis-ci.com/) -## 编写配置文件 🍀 +## 编写配置文件 我们如果想要仓库使用`Travis CI` , 一般情况下 , 我们只需要在仓库中添加`.travis.yml`这样一个`Travis CI`专用的文件 , `Travis CI` 就与GitHub集成了 @@ -50,7 +50,7 @@ script: # 指定要运行的脚本 关于各种语言的配置参考 , 点击进入[语言参考](https://docs.travis-ci.com/user/languages) -## 检测配置文件 🍀 +## 检测配置文件 `Travis CI`专门提供了`Travis WebLint`提供用户检测`.travis.yml`文件是否存在问题 , 检测时只需要指定仓库即可 @@ -77,7 +77,7 @@ script: # 指定要运行的脚本 这样 , 在我们查看README.md时 , 就能够通过图片观察测试是否通过了 ; 绿色的图片就表示仓库内代码顺利通过测试 , 灰色的图片表示仓库没有通过测试 , 证明仓库可能存在某种问题 , 这样既可以显示仓库的健全性 , 又可以防止自己遗漏`Travis CI的结果` -## 生成Access Token 🍀 +## 生成Access Token 与GitHub继承之后 , 此时Travis已经开始监控了 , 但是它却没有访问权限 , 所以我们需要生成一个`Personal access tokens` diff --git a/09-Linux/Command/vim.md b/docs/linux/vim.md similarity index 98% rename from 09-Linux/Command/vim.md rename to docs/linux/vim.md index f0c4ea2d2..873c869bf 100644 --- a/09-Linux/Command/vim.md +++ b/docs/linux/vim.md @@ -9,23 +9,23 @@ -## 介绍 🍀 +## 介绍 基本上 vi 共分为 3 种模式 , 分别是一般模式 , 编辑模式与命令行模式 -### 一般模式 🍀 +### 一般模式 以 vi 代开一个文件就直接进入一般模式了 (这是默认的模式) , 在这个模式中 , 你可以使用上下左右按键来移动光标 , 你可以删除字符或删除整行 , 也可以复制 , 粘贴你的文件数据 -### 编辑模式 🍀 +### 编辑模式 在一般模式中可以进行删除 , 复制 , 粘贴等的操作 , 但是却无法编辑文件内容 , 要等待你按下 `"i,I,o,O,a,A,r,R"` 等任何一个字母之后才进入编辑模式 , 通常在 Linux 中 , 按下这些按键时 , 在界面的左下方会出现 INSERT 或 REPLACE 的字样 , 此时才可以进行编辑 , 而如果要回到一般模式时 , 则必须要按下 `[Esc]` 键即可退出编辑模式 -### 命令行模式 🍀 +### 命令行模式 在一般模式中 , 输入 `":,/,?"` 3 个中的任何一个按钮 , 就可以将光标移动到最下面那一行 , 在这个模式当中 , 可以提供你查找数据的操作 , 而读取 , 保存 , 大量替换字符 , 离开 vi , 显示行号等的操作则是在此模式下完成的 -## 简单使用 🍀 +## 简单使用 1. 使用 vi 进入一般模式 @@ -39,9 +39,9 @@ 4. 在一般模式中输入 `":wq"` 保存后离开 vi -## 按键说明 🍀 +## 按键说明 -### 第一部分 🍀 +### 第一部分 一般模式可用的按键说明 , 光标移动 , 复制粘贴 , 查找替换等 @@ -110,7 +110,7 @@ | [Ctrl]+r | 重做上一个动作 (常用) | | . | 不要怀疑 , 这就是小数点 , 意思是重复前一个动作的意思 . 如果你想要重复删除、重复贴上等等动作 , 按下小数点 `.` 就好了 (常用) | -### 第二部分 🍀 +### 第二部分 一般模式切换到编辑模式的可用的按钮说明 @@ -122,7 +122,7 @@ | r, R | 进入取代模式(Replace mode) : r 只会取代光标所在的那一个字符一次;R会一直取代光标所在的文字 , 直到按下 ESC 为止 (常用) | | [Esc] | 退出编辑模式 , 回到一般模式中(常用) | -### 第三部分 🍀 +### 第三部分 一般模式切换到命令行模式的可用的按钮说明 diff --git "a/03-MySQL/05-MySQL - \346\225\260\346\215\256\346\223\215\344\275\234.md" b/docs/mysql/data.md similarity index 97% rename from "03-MySQL/05-MySQL - \346\225\260\346\215\256\346\223\215\344\275\234.md" rename to docs/mysql/data.md index a307262db..c49ceecf9 100644 --- "a/03-MySQL/05-MySQL - \346\225\260\346\215\256\346\223\215\344\275\234.md" +++ b/docs/mysql/data.md @@ -9,13 +9,13 @@ -## 介绍 🍀 +## 介绍 DML操作是指对数据库中表记录的操作 , 即数据操作 主要包括表记录的插入(insert) , 更新(update) , 删除(delete) 和查询(select) , 是开发人员日常使用最频繁的操作 -## 插入记录 🍀 +## 插入记录 语法 : @@ -68,7 +68,7 @@ INSERT INTO tablename(field1,field2,...,fieldn) VALUES ``` -## 更新记录 🍀 +## 更新记录 语法 : @@ -91,7 +91,7 @@ UPDATE t1,t2,...,tn SET t1.field1=expr1,tn.fieldn=exprn [WHERE CONDITION]; ``` -## 删除记录 🍀 +## 删除记录 语法 : @@ -159,7 +159,7 @@ mysql> SELECT * FROM dept; PS : 不管是单表还是多表 , 不加where条件会把表的所有记录删除 , 所以操作时一定要小心 -## 查询记录 🍀 +## 查询记录 语法 : @@ -176,7 +176,7 @@ mysql> SELECT ename,hiredate,sal,deptno FROM emp; ``` -### 查询不重复的记录 🍀 +### 查询不重复的记录 ```mysql mysql> SELECT DISTINCT deptno FROM emp; @@ -189,7 +189,7 @@ mysql> SELECT DISTINCT deptno FROM emp; ``` -### 条件查询 🍀 +### 条件查询 ```mysql mysql> SELECT * FROM emp WHERE deptno=1; @@ -202,7 +202,7 @@ mysql> SELECT * FROM emp WHERE deptno=1; ``` -### 排序和限制 🍀 +### 排序和限制 ```mysql -- 按工资高低进行显示 @@ -240,7 +240,7 @@ mysql> SELECT * FROM emp ORDER BY sal LIMIT 2; PS : limit属于MySQL扩展SQL92后的语法 , 在其他数据库上并不能通用 -### 聚合 🍀 +### 聚合 用于进行汇总操作 @@ -314,7 +314,7 @@ mysql> SELECT sum(sal),max(sal),min(sal) FROM emp; ``` -### 表连接 🍀 +### 表连接 同时显示多个表中的字段 , 分为内连接和外连接 @@ -370,7 +370,7 @@ mysql> SELECT ename,deptname FROM emp,dept WHERE emp.deptno=dept.deptno; -### 子查询 🍀 +### 子查询 查询时 , 需要的条件是另一个select语句的结果 @@ -412,7 +412,7 @@ PS : - 表连接在很多情况下用于优化子查询 -### 记录联合 🍀 +### 记录联合 `union` 和`union all` 关键字可以实现 , 将多个表的数据按照一定的查询条件查询出来后 , 将结果合并到一起显示 diff --git "a/03-MySQL/01-MySQL - \345\272\223\346\223\215\344\275\234.md" b/docs/mysql/database.md similarity index 98% rename from "03-MySQL/01-MySQL - \345\272\223\346\223\215\344\275\234.md" rename to docs/mysql/database.md index 74b12271e..d050b9dd0 100644 --- "a/03-MySQL/01-MySQL - \345\272\223\346\223\215\344\275\234.md" +++ b/docs/mysql/database.md @@ -9,7 +9,7 @@ -## SQL介绍 🍀 +## SQL介绍 SQL是Structured Query Language(结构化查询语言)的缩写 , SQL是转为数据库而建立的操作命令集 , 是一种功能齐全的数据库语言 @@ -30,7 +30,7 @@ SQL语句主要可以划分为一下3个类别 : - SQL语句可拆行操作 -## 数据库操作 🍀 +## 数据库操作 在MySQL数据中有如下默认数据库 @@ -43,7 +43,7 @@ SQL语句主要可以划分为一下3个类别 : | sys | 包含了一系列视图、函数和存储过程 | -### 查看数据库 🍀 +### 查看数据库 ```mysql SHOW DATABASES; 查看所有数据库 @@ -74,7 +74,7 @@ mysql> show create database mysql; ``` -### 创建数据库 🍀 +### 创建数据库 ```mysql CREATE DATABASE dbname DEFAULT CHARSET utf8 COLLATE utf8_general_ci; 创建字符串为utf-8的数据库 @@ -101,13 +101,13 @@ mysql> SHOW DATABASES; ``` -### 删除数据库 🍀 +### 删除数据库 ```mysql DROP DATABASE dbname; 删除数据库 ``` -### 使用数据库 🍀 +### 使用数据库 ```mysql USE dbname; 进入数据库 @@ -138,7 +138,7 @@ mysql> SELECT DATABASE(); ``` -### 用户管理 🍀 +### 用户管理 ```mysql CREATE USER 'lyon'@'%' IDENTIFIED BY '123'; 创建`lyon`用户,允许任意IP访问,密码为`123` @@ -214,7 +214,7 @@ mysql> SELECT HOST,USER FROM USER; ``` -### 授权管理 🍀 +### 授权管理 ```mysql SHOW GRANTS FOR 'username'@'IP'; 查看用户权限 diff --git "a/03-MySQL/02-MySQL - \346\225\260\346\215\256\347\261\273\345\236\213.md" b/docs/mysql/datatype.md similarity index 98% rename from "03-MySQL/02-MySQL - \346\225\260\346\215\256\347\261\273\345\236\213.md" rename to docs/mysql/datatype.md index 377ffa025..27d4142be 100644 --- "a/03-MySQL/02-MySQL - \346\225\260\346\215\256\347\261\273\345\236\213.md" +++ b/docs/mysql/datatype.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 MySQL中定义数据字段的类型对数据库的优化是非常重要的 @@ -21,7 +21,7 @@ MySQL支持多种数据类型 , 主要包括 : 本篇内容 , 以MySQL 5.0 版本为例 , 因为不同的版本可能有所差异 , 不过差异不大 -## 数值类型 🍀 +## 数值类型 MySQL支持所有标准SQL中的数值类型 , 其中包括严格数值类型 , 以及近似数值数据类型 , 并在此基础上做了扩展 , 增加了TINYINT , MEDIUMINT , BIGINT 3种长度不同的整型 , 并增加了BIT类型 , 用来存放位数据 @@ -107,7 +107,7 @@ mysql> SELECT IF(2 = FALSE, 'true', 'false'); ``` -## 日期时间类型 🍀 +## 日期时间类型 MySQL中有多种数据类型可以用于日期和时间的表示 , 这些数据类型的主要区别如下 : @@ -138,7 +138,7 @@ MySQL中有多种数据类型可以用于日期和时间的表示 , 这些数据 | YEAR | 0000 | -## 字符串类型 🍀 +## 字符串类型 MySQL包括了CHAR , VARCHAR , BINARY , VARBINARY , BLOB , TEXT , ENUM 和 SET等多种字符串类型 diff --git "a/03-MySQL/06-MySQL - \347\264\242\345\274\225.md" b/docs/mysql/index.md similarity index 97% rename from "03-MySQL/06-MySQL - \347\264\242\345\274\225.md" rename to docs/mysql/index.md index 62b0ec6f6..75d350a37 100644 --- "a/03-MySQL/06-MySQL - \347\264\242\345\274\225.md" +++ b/docs/mysql/index.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 索引是数据库中最常用也是最重要的手段之一 , 是数据库中专门用于帮助用户快速查询数据的一种数据结构 , 类似于字典中的目录 , 查找字典内容时可以根据目录查找到数据的存放位置 , 然后直接获取值即可 @@ -26,7 +26,7 @@ 下面对比较常用的两个索引类型进行说明 -## B-Tree索引与HASH索引 🍀 +## B-Tree索引与HASH索引 **B-Tree索引** @@ -41,13 +41,13 @@ HASH索引相对简单 , 只有Memory/Heap引擎支持 HASH索引适用于 **Key - Value**查询 , 通过HASH索引要比通过B-Tree索引查询更迅速 , 但是HASH**不适用范围查询** , 例如 : < , > , <= , >=这类操作 ; 如果使用Memory/Heap引擎并且where条件中不使用 "=" 今夕in个索引列 , 那么不会用到索引 , Memory/Heap引擎只有在 "=" 的条件下才会使用索引 -## MySQL索引管理 🍀 +## MySQL索引管理 索引的功能就是为了加速查找和约束 , 下面对常用索引进行介绍 查看索引 : `SHOW INDEX FROM tablename \G;` -### 普通索引 🍀 +### 普通索引 普通索引仅有一个功能 , 就是加速查找 @@ -88,7 +88,7 @@ ALTER TABLE tablename DROP INDEX column_name; ``` -### 唯一索引 🍀 +### 唯一索引 唯一索引有两个功能 : 加速查找和唯一约束(可含NULL) @@ -129,7 +129,7 @@ ALTER TABLE tablename DROP INDEX column_name; ``` -### 主键索引 🍀 +### 主键索引 主键有两个功能 : 加速查找和唯一约束(不可NULL) @@ -166,7 +166,7 @@ ALTER TABLE tablename MODIFY column_name column_type, drop primary key; ``` -### 组合索引 🍀 +### 组合索引 组合索引是将n个列组合成一个索引 , 专门用于组合搜索 , 其效率大于索引合并 @@ -205,7 +205,7 @@ MySQL只需要通过索引就可以返回查询所需要的数据 , 而不必在 当你对一个sql 使用explain statement 查看一个sql的执行计划时 , 在EXPLAIN的Extra列出现Using Index提示时 , 就说明该select查询使用了覆盖索引 -## 使用索引 🍀 +## 使用索引 使用索引可以加速查找 , 但是如果以错误的方式使用 , 即使建立索引也不会生效 diff --git a/03-MySQL/README.md b/docs/mysql/introduction.md similarity index 100% rename from 03-MySQL/README.md rename to docs/mysql/introduction.md diff --git "a/03-MySQL/11-MySQL - SQL\346\263\250\345\205\245.md" b/docs/mysql/sql-injection.md similarity index 95% rename from "03-MySQL/11-MySQL - SQL\346\263\250\345\205\245.md" rename to docs/mysql/sql-injection.md index aac13345d..b3f372694 100644 --- "a/03-MySQL/11-MySQL - SQL\346\263\250\345\205\245.md" +++ b/docs/mysql/sql-injection.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 `SQL注入`(SQL Injection)就是利用某些数据的外部接口将用户数据插入到实际的数据库操作语言(SQL)当中 , 从而达到入侵数据库乃至操作系统的目的 , 它的产生主要是由于程序对用户输入的数据没有进行严格的过滤 , 导致非法数据库查询语句的执行 @@ -74,11 +74,11 @@ http://127.0.0.1/injection/user.php?username=angel'# "or" 是利用逻辑运算 , 注释符是根据MySQL的特性 , 这个比逻辑运算简单多了 , 两者都实现了SQL注入效果 -## 应对措施 🍀 +## 应对措施 对于SQL注入隐患 , 后果可想而知 , 轻则获得数据信息 , 重则可以将数据进行非法更改 , 一下则是常用的防范方法 -### PrepareStatemen + Bind - Variable 🍀 +### PrepareStatemen + Bind - Variable MySQL服务器端并不存在共享池的概念 , 所以在MySQL上使用绑定变量(Bind Variable) , 最大的好处主要是为了避免SQL注入 , 增加安全性 @@ -87,7 +87,7 @@ MySQL服务器端并不存在共享池的概念 , 所以在MySQL上使用绑定 需要注意 , PrepareStatement语句是由JDBC驱动来支持的 , 他仅仅做了简单的替换和转义 , 斌不是MySQL提供了PreparedStatement的特性 -### 使用应用程序提供的转换函数 🍀 +### 使用应用程序提供的转换函数 很多应用程序接口都提供了对特殊字符进行转换的函数 , 恰当地使用这些函数 , 可以防止应用程序用户输入使应用程序生成不期望的语句 @@ -98,13 +98,13 @@ MySQL服务器端并不存在共享池的概念 , 所以在MySQL上使用绑定 - Ruby DBI : 使用paceholders 或者quote()方法 -### 自己定义函数进行检验 🍀 +### 自己定义函数进行检验 如果现有的转换函数任然不能满足要求 , 则需要自己编写函数进行输入校验 目前最好的解决方法就是 , 对用用户提交或者可能改变的数据进行简单分类 , 分别应用正则表达式来对用户提供的输入数据进行严格的检测和验证 -### Python中的pymysql模块 🍀 +### Python中的pymysql模块 通过Python的pymysql模块来进行SQL的执行 , 在pymysql模块内部会自动把 " ' "(单引号做一个特殊的处理 , 来预防上述的错误) diff --git "a/03-MySQL/SQL\344\274\230\345\214\226.md" b/docs/mysql/sql-optimization.md similarity index 100% rename from "03-MySQL/SQL\344\274\230\345\214\226.md" rename to docs/mysql/sql-optimization.md diff --git "a/03-MySQL/03-MySQL - \345\255\230\345\202\250\345\274\225\346\223\216.md" b/docs/mysql/storage-engine.md similarity index 98% rename from "03-MySQL/03-MySQL - \345\255\230\345\202\250\345\274\225\346\223\216.md" rename to docs/mysql/storage-engine.md index 7147b3141..621e7cf10 100644 --- "a/03-MySQL/03-MySQL - \345\255\230\345\202\250\345\274\225\346\223\216.md" +++ b/docs/mysql/storage-engine.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 插件式存储引擎是MySQL数据库最重要的特性之一 , 用户可以根据应用的需要选择如何存储和索引数据 , 是否使用实务等 , MySQL默认支持多种**存储引擎(表类型)** , 用户还可以按照自己的需要定制和使用自己的存储引擎 @@ -54,7 +54,7 @@ SHOW VARIABLES LIKE 'have%'; 下面重点介绍最常使用的4中存储引擎 : MyISAM , InnoDB , MEMORY 和 MERGE -## MyISAM 🍀 +## MyISAM MyISAM**不支持事务** , 也不支持外键 , 其优势是访问的速度快 , 对事务完整性没有要求或者以SELECT , INSERT为主的应用基本上都可以使用这个引擎来创建表 @@ -67,14 +67,14 @@ MyISAM类型的表可能会损坏 , 原因可能是多种多样的 , 损坏后 MyISAM另一个与众不同的地方是 , 它的**缓冲池只缓存(cache)索引文件** **, 而不缓存数据文件** , 这与大多数的数据库都不相同 -## InnoDB 🍀 +## InnoDB InnoDB存储引擎提供了具有提交 , 回滚和崩溃恢复能力的事务安全 , 但是对比MyISAM , InnoDB写的处理效率差一些 , 并且会占用更多的磁盘空间以保留数据和索引 InnoDB是MySQL数据库最为常用的存储引擎 , 其不同于其他存储引擎的表的特点如下 -### 自动增长列 🍀 +### 自动增长列 InnoDB表的自增列可以手工插入 , 但是插入的值如果是空或者0 , 则实际插入的将是自动增长后的值 @@ -139,7 +139,7 @@ mysql> SELECT LAST_INSERT_ID(); 但是对于MyISAM表 , 自增列可以是组合索引的其他列 -### 外键约束 🍀 +### 外键约束 MySQL支持外键的存储引擎只有InnoDB , 在创建外键的时候 , 要求父表必须有对应的索引 , 子表在创建外键的时候也会自动创建对应的索引 @@ -220,7 +220,7 @@ No query specified ``` -### 存储方式 🍀 +### 存储方式 InnoDB存储表和索引有两种方式 : @@ -232,7 +232,7 @@ InnoDB存储表和索引有两种方式 : 深入了解InnoDB存储引擎的工作 , 原理 , 实现和应用 , 可以参考《MySQL技术内幕 : InnoDB存储引擎》一书 -## MEMORY 🍀 +## MEMORY MEMORY存储引擎使用存在于内存中的内容来创建表 @@ -279,7 +279,7 @@ No query specified MEMORY类型的存储引擎主要用于那些内容变化不频繁的代码表 , 或者作为统计操作的中间结果表 , 便于高效地对中间结果进行分析并得到最终的统计结果 ; 对存储引擎为MEMORY的表进行更新操作要谨慎 , 因为数据并没有实际写入到磁盘中 , 所以一定要对下次重新启动服务后如何获得这些修改后的数据有所考虑 -## MERGE 🍀 +## MERGE MERGE存储是一组MyISAM表的组合 , 这些MyISAM表必须结构完全相同 , MERGE表本身并没有数据 , 对MERGE类型的表可以进行查询 , 更新 , 删除操作 , 这些操作实际上是对内部的MyISAM表进行的 @@ -288,7 +288,7 @@ MERGE存储是一组MyISAM表的组合 , 这些MyISAM表必须结构完全相同 通常我们使用MERGE表来透明地对多个表进行查询和更新操作 , 而对按照时间记录的操作日志则可以透明地进行插入操作 -## TokuDB 🍀 +## TokuDB 前面的都是MySQL自带的存储引擎 , 除了这些之外 , 还有一些常见的第三方存储引擎 , 在某些特定应用中也有广泛使用 , 比如列式存储引擎Infobright , 高写性能高压缩的TokuDB就是其中非常有代表性的两种 diff --git "a/03-MySQL/08-MySQL - \345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md" b/docs/mysql/stored-procedures-and-functions.md similarity index 97% rename from "03-MySQL/08-MySQL - \345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md" rename to docs/mysql/stored-procedures-and-functions.md index 14e83df3d..ca7fd7d33 100644 --- "a/03-MySQL/08-MySQL - \345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md" +++ b/docs/mysql/stored-procedures-and-functions.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 存储过程和函数是实现经过编译并存储在数据库中的一段SQL语句的集合 , 调用存储过程和函数可以简化应用开发人员的很多工作 , 减少数据在数据库和应用服务器之间的传输 , 对于提高数据处理的效率是有好处的 @@ -23,7 +23,7 @@ 在对存储过程或函数进行操作时 , 需要首先确认用户是否具有相应的权限 ' 例如 : 创建存储过程或者函数需要CREATE ROUTINE权限 , 修改或者删除存储过程或者函数需要ALTER ROUTINE权限 , 执行存储过程或者函数需要EXECUTE权限 -## 创建存储过程或函数 🍀 +## 创建存储过程或函数 语法 : @@ -86,7 +86,7 @@ CALL sp_name([parameter[,...]]) 事务 -## 修改存储过程或函数 🍀 +## 修改存储过程或函数 语法 : @@ -100,7 +100,7 @@ ALTER {PRCEDURE|FUNCTION} sp_name [characteristic...] -## 删除存储过程或函数 🍀 +## 删除存储过程或函数 一次只能删除一个存储过程或者函数 , 删除过程或者函数需要有该过程或者函数的ALTER ROUTINE权限 @@ -118,7 +118,7 @@ Query OK, 0 rows affected (0.00 sec) ``` -## 查看存储过程或函数 🍀 +## 查看存储过程或函数 1. 查看状态 @@ -139,7 +139,7 @@ Query OK, 0 rows affected (0.00 sec) ``` -## 变量的使用 🍀 +## 变量的使用 存储过程和和函数可以使用变量 , 而且在MySQL 5.1版本中 , 变量不区分大小写 @@ -188,7 +188,7 @@ DECLARE v_payments DECIMAL(5,2); -- SUM OF PAYMENTS MADE PREVIOUSLY ``` -## 条件处理 🍀 +## 条件处理 用于定义在处理过程中遇到问题时相应的处理步骤 @@ -247,7 +247,7 @@ Query OK, 0 rows affected (0.10 sec) ``` -## 光标 🍀 +## 光标 在存储过程或函数中 , 可以使用光标对结果集进行循环处理 @@ -300,7 +300,7 @@ Query OK, 0 rows affected (0.02 sec) ``` -## 流程控制 🍀 +## 流程控制 1. IF语句 @@ -385,7 +385,7 @@ Query OK, 0 rows affected (0.02 sec) -## 内置函数 🍀 +## 内置函数 对于一些内置函数 , 就直接看官方的吧 https://dev.mysql.com/doc/refman/5.7/en/functions.html diff --git "a/03-MySQL/04-MySQL - \350\241\250\346\223\215\344\275\234.md" b/docs/mysql/table.md similarity index 98% rename from "03-MySQL/04-MySQL - \350\241\250\346\223\215\344\275\234.md" rename to docs/mysql/table.md index 131be3611..5a005dccb 100644 --- "a/03-MySQL/04-MySQL - \350\241\250\346\223\215\344\275\234.md" +++ b/docs/mysql/table.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 该部分语句属于DDL语句 , 对表的定义 , 结构的修改 @@ -17,7 +17,7 @@ DDL语句更多地由数据库管理员(DBA)使用 , 开发人员一般很少使用 -## 创建表 🍀 +## 创建表 ```mysql -- 创建数据库 @@ -69,7 +69,7 @@ No query specified ``` -## 删除表 🍀 +## 删除表 ```mysql mysql> DROP TABLE tb; @@ -77,9 +77,9 @@ Query OK, 0 rows affected (0.21 sec) ``` -## 修改表 🍀 +## 修改表 -### 修改表类型 🍀 +### 修改表类型 语法 : @@ -116,7 +116,7 @@ mysql> DESC emp; ``` -### 增加表字段 🍀 +### 增加表字段 语法 : @@ -157,7 +157,7 @@ mysql> DESC emp; ``` -### 删除表字段 🍀 +### 删除表字段 语法 : @@ -193,7 +193,7 @@ mysql> DESC emp; ``` -### 字段改名 🍀 +### 字段改名 语法 : @@ -225,7 +225,7 @@ mysql> DESC emp; PS : change 和 modify都可以修改表类型 , 不同的是change后面需要写两次列名 ; 并且change可以修改列名 , modify则不能 -### 修改字段排列顺序 🍀 +### 修改字段排列顺序 前面介绍的字段增加和修改语法(ADD/CHANGE/MODIFY)中, 都有一个可选项 `[FIRST|AFTER col_name]` ,这个选项可以用来修改字段在表中的位置 , ADD增加的新字段默认是加在表的最后位置 , 而CHANGE/MODIFY默认都不会改变字段的位置 @@ -286,7 +286,7 @@ mysql> DESC emp; **PS :** CHANGE/FIRST|AFTER COLUMN 这些关键字都属于MySQL在标准SQL上的扩展 , 在其他数据库上不一定适用 -### 更改表名 🍀 +### 更改表名 语法 : @@ -315,7 +315,7 @@ mysql> DESC emp1; ``` -### 默认值 🍀 +### 默认值 语法 : @@ -366,7 +366,7 @@ mysql> DESC emp1; ``` -## 语句合集 🍀 +## 语句合集 > 创建表 diff --git "a/03-MySQL/10-MySQL - \344\272\213\345\212\241.md" b/docs/mysql/transaction.md similarity index 99% rename from "03-MySQL/10-MySQL - \344\272\213\345\212\241.md" rename to docs/mysql/transaction.md index 233799293..92cba7bd5 100644 --- "a/03-MySQL/10-MySQL - \344\272\213\345\212\241.md" +++ b/docs/mysql/transaction.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 事务就是满足 `ACID` 特性的一组操作 @@ -18,7 +18,7 @@ 在 `MySQL` 中默认自动提交 (Autocommit) , 如果需要通过明确的 `COMMIT` 和 `ROLLBACK` 来提交和回滚事务 , 那么就需要通过明确的事务控制命令来开始事务 -## ACID 🍀 +## ACID ### 原子性 (Atomicity) @@ -36,7 +36,7 @@ 一旦事务提交 , 则其所做的修改就会永久保存到数据库中 , 此时即使系统崩溃 , 修改的数据也不会丢失 -## 并发问题 🍀 +## 并发问题 在没有并发的情况下 , 事务串行执行 , 隔离性一定能够满足 , 但是在并发的情况下 , 多个事务并行执行 , 事务的隔离性就无法得到保证 @@ -66,7 +66,7 @@ 产生并发不一致问题的主要原因是破坏了事务之间的隔离性 , 要解决这个问题就是要通过并发控制来保证隔离性 , 使一个事务的执行不受其他事务的干扰 , 从而避免造成过数据的不一致性 , 而实现并发控制的一个非常重要的技术就是封锁 -## 封锁 🍀 +## 封锁 ### 封锁粒度 @@ -147,7 +147,7 @@ SELECT ... LOCK IN SHARE MODE; SELECT .. FOR UPDATE; ``` -## 隔离级别 🍀 +## 隔离级别 并发控制可以通过封锁来实现 , 但是封锁需要用户自己控制如何加锁 , 以及加锁和解锁的时机 , 相当复杂 , 所以绝大多数数据库以及开发工具都提供了事务的隔离级别 , 让用户以一种更轻松的方式处理并发一致性问题 diff --git "a/03-MySQL/09-MySQL - \350\247\246\345\217\221\345\231\250.md" b/docs/mysql/trigger.md similarity index 95% rename from "03-MySQL/09-MySQL - \350\247\246\345\217\221\345\231\250.md" rename to docs/mysql/trigger.md index 68c016e30..28b0e327f 100644 --- "a/03-MySQL/09-MySQL - \350\247\246\345\217\221\345\231\250.md" +++ b/docs/mysql/trigger.md @@ -9,11 +9,11 @@ -## 介绍 🍀 +## 介绍 触发器是与表有关的数据库对象 , 在满足定义条件时触发 , 并执行触发器中定义的语句集合 -### 创建触发器 🍀 +### 创建触发器 语法 : @@ -80,7 +80,7 @@ END $$ ``` -### 删除触发器 🍀 +### 删除触发器 语法 : @@ -97,7 +97,7 @@ Query OK, 0 rows affected (0.00 sec) ``` -### 查看触发器 🍀 +### 查看触发器 1. 可通过执行`SHOW TRIGGERS \G;` 命令查看触发器的状态 , 语法等信息 , 但是因为不能查询指定的触发器 , 所以每次都返回所有的触发器信息 , 使用不方便 @@ -109,7 +109,7 @@ Query OK, 0 rows affected (0.00 sec) ``` -### 使用触发器 🍀 +### 使用触发器 触发器的语句有以下两个限制 diff --git "a/03-MySQL/07-MySQL - \350\247\206\345\233\276.md" b/docs/mysql/view.md similarity index 97% rename from "03-MySQL/07-MySQL - \350\247\206\345\233\276.md" rename to docs/mysql/view.md index d83633226..09ec20b58 100644 --- "a/03-MySQL/07-MySQL - \350\247\206\345\233\276.md" +++ b/docs/mysql/view.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 视图是一种虚拟存在的表 , 对于使用视图的用户来说基本上是透明的 @@ -21,11 +21,11 @@ - 安全 : 使用视图的用户只能访问他们被允许查询的结果集 , 对表的权限管理并不能限制到某个行某个列 , 但是通过视图就可以简单的实现 - 数据独立 : 一旦视图的结构确定了 , 可以屏蔽表结构变化对用户的影响 , 源表增加列对视图没有影响 ; 源表修改列名 , 则可以通过修改视图来解决 , 不会造成对访问者的影响 -## 视图操作 🍀 +## 视图操作 视图的操作包括创建或者修改视图 , 删除视图 , 以及查看视图定义 -### 创建视图 🍀 +### 创建视图 创建视图需要有`CREATE VIEW `的权限 , 并且对于查询涉及的列有`SELECT`权限 ; 如果使用`CREATE OR REPLACE `或者`ALTER`修改视图 , 那么还需要该视图的DROP权限 @@ -59,7 +59,7 @@ MySQL视图的定义有一些限制,例如,在FROM关键字后面不能包含子 2. 一个致命的问题 : 视图是存放在数据库中的 , 如果我们程序中的sql过分依赖于数据库中存放的视图 , 那么意味着 , 一旦sql需要修改且涉及到视图的部分 , 则必须去数据库中进行修改 , 而通常在公司中数据库有专门的DBA负责 , 你要想完成修改 , 必须付出大量的沟通成本DBA可能才会帮你完成修改 , 极其的不方便 -### 修改视图 🍀 +### 修改视图 语法 : @@ -157,7 +157,7 @@ ERROR 1369 (HY000): CHECK OPTION failed 'sakila.payment_view2' ``` -### 删除视图 🍀 +### 删除视图 用户可以一次删除一个或者多个视图 , 前提是必须有该视图的DROP权限 @@ -172,7 +172,7 @@ DROP VIEW [IF EXISTS] view_name [,view_name]...[RESTRICT|CASCADE]; ``` -### 查看视图 🍀 +### 查看视图 从MySQL 5.1开始 , 使用SHOW TABLES命令的时候不仅显示表的名字 , 同时也会显示视图的名字 , 而不存在单独显示视图的SHOW VIEWS命令 , 如下 : diff --git "a/06-Redis/Python\346\223\215\344\275\234MongoDB.md" "b/docs/redis/Python\346\223\215\344\275\234MongoDB.md" similarity index 97% rename from "06-Redis/Python\346\223\215\344\275\234MongoDB.md" rename to "docs/redis/Python\346\223\215\344\275\234MongoDB.md" index d18dce160..1a2f8b1d2 100644 --- "a/06-Redis/Python\346\223\215\344\275\234MongoDB.md" +++ "b/docs/redis/Python\346\223\215\344\275\234MongoDB.md" @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 MongoDB 是一个基于分布式文件存储的数据库 , 由 C++ 编写 @@ -24,7 +24,7 @@ MongoDB 是一个介于关系型数据库和非关系型数据库 (NoSQL) 之间 5. 内置聚合工具 , 可以通过 MapReduce 等方式进行复杂的统计核并行计算 6. MongoDB 在 3.0 之后增加了高性能 , 可伸缩 , 支持压缩和文档级锁的数据存储引擎 WiredTiger -## MongoDB概念 🍀 +## MongoDB概念 | SQL术语/概念 | MongoDB术语/概念 | 解释/说明 | | ------------ | ---------------- | -------------------------------------- | @@ -36,7 +36,7 @@ MongoDB 是一个介于关系型数据库和非关系型数据库 (NoSQL) 之间 | table joins | | 表连接 , MongoDB 不支持 | | primary key | primary key | 主键 , MongoDB 自动将_id字段设置为主键 | - ### 数据库 🍀 + ### 数据库 在 MongoDB 中 , 多个文档组成集合 , 多个集合可以组成数据库 @@ -52,7 +52,7 @@ MongoDB 是一个介于关系型数据库和非关系型数据库 (NoSQL) 之间 2. local: 这个数据库永远都不可以复制 , 且一台服务器上的所有本地集合都可以存储在这个数据库中 3. config: MongoDB用于分片设置时 , 分片信息会存储在config数据库中 -### 集合 🍀 +### 集合 集合就是一组文档 , 如果将MongoDB中的一个文档比喻为关系型数据的一行 , 那么一个集合就是相当于一张表 @@ -70,7 +70,7 @@ MongoDB 是一个介于关系型数据库和非关系型数据库 (NoSQL) 之间 集合名不能以 "system." 开头 , 这是为系统集合保留的前缀 ; 用户创建的集合名字不能含有保留字符 , 有些驱动程序的确支持在集合名里面包含 , 这是因为某些系统生成的集合中包含该字符 ; 除非你要访问这种系统创建的集合 , 否则千万不要在名字里出现$ ; -### 文档 🍀 +### 文档 文档是MongoDB的核心概念 , 文档就是键值对的一个有序集 `{'msg':'hello','foo':3}` , 类似于python中的有序字典 @@ -90,7 +90,7 @@ MongoDB 是一个介于关系型数据库和非关系型数据库 (NoSQL) 之间 PS : 把数据库名添加到集合名前 , 得到集合的完全限定名 , 即命名空间 , 如 : 如果要使用 test 数据库中的 `coll.posts` 集合 , 这个集合的命名空间就是 `test.coll.ports` , 命名空间的长度不得超过121个字节 , 且在实际使用中应该小于100个字节 -## 连接MongoDB 🍀 +## 连接MongoDB ```python >>> from pymongo import MongoClient @@ -102,7 +102,7 @@ PS : 把数据库名添加到集合名前 , 得到集合的完全限定名 , 即 >>> client = MongoClient('mongodb://localhost:27017/') ``` -## 获取数据库 🍀 +## 获取数据库 ```python >>> db = client.test_database @@ -110,7 +110,7 @@ PS : 把数据库名添加到集合名前 , 得到集合的完全限定名 , 即 >>> db = client['test-database'] ``` -## 获取集合 🍀 +## 获取集合 ```python >>> collection = db.test_collection @@ -119,7 +119,7 @@ PS : 把数据库名添加到集合名前 , 得到集合的完全限定名 , 即 关于 MongoDB 中的集合和数据库一个重要注意事项是 , 它们是延迟创建的 , 上面的命令实际上都没有在MongoDB 服务器上执行任何操作 , 而是当第一个文档插入到集合和数据库中时 , 才创建集合和数据库 -## 文档 🍀 +## 文档 MongoDB中的数据使用JSON样式的文档表示(并存储)。在Pymono中 , 我们使用字典来表示文档 , 如下 : @@ -131,7 +131,7 @@ MongoDB中的数据使用JSON样式的文档表示(并存储)。在Pymono中 , ... "date": datetime.datetime.utcnow()} ``` -### 插入文档 🍀 +### 插入文档 **单条插入** @@ -158,7 +158,7 @@ ObjectId('...') [ObjectId('...'), ObjectId('...')] ``` -### 查询文档 🍀 +### 查询文档 查看数据库中所有集合 @@ -256,7 +256,7 @@ def get(post_id): u'text': u'Another post!'} ``` -### 计数查询 🍀 +### 计数查询 获取查询结果总条数 @@ -267,7 +267,7 @@ def get(post_id): 2 ``` -### 范围查询 🍀 +### 范围查询 ```python >>> d = datetime.datetime(2009, 11, 12, 12) @@ -286,7 +286,7 @@ def get(post_id): u'text': u'Another post!'} ``` -### 索引 🍀 +### 索引 创建索引 diff --git "a/06-Redis/07-Redis - \351\233\206\347\276\244.md" b/docs/redis/cluster.md similarity index 98% rename from "06-Redis/07-Redis - \351\233\206\347\276\244.md" rename to docs/redis/cluster.md index 83165ec0a..066408b51 100644 --- "a/06-Redis/07-Redis - \351\233\206\347\276\244.md" +++ b/docs/redis/cluster.md @@ -9,11 +9,11 @@ -## 介绍 🍀 +## 介绍 Redis 集群是 Redis 提供的分布式数据库方案 , 集群通过分片 (sharding) 来进行数据共享 , 并提供复制和故障转移功能 -## 节点 🍀 +## 节点 一个 Redis 集群通常由多个节点 (node) 组成 , 在刚开始的时候 , 每个节点都是相互独立的 , 它们都处于一个只包含自己的集群当中 , 要组建一个真正可工作的集群 , 我们必须将各个独立的节点连接起来 , 构成一个包含多个节点的集群 @@ -31,7 +31,7 @@ CLUSTER NODES 一个节点就是一个运行在集群模式下的 Redis 服务器 , Redis 服务器在启动时会根据 `cluster-enabled` 配置选项是否为 `yes` 来决定是否开启服务器的集群模式 -## 槽指派 🍀 +## 槽指派 Redis 集群通过分片的方式来保存数据库中的键值对 : 集群的整个数据被分为 16384 个槽 (slot) , 数据库中的每个键都属于这 16384 个槽的其中一个 , 集群中的每个节点可以处理 0 个或最多 16384 个槽 @@ -50,7 +50,7 @@ CLUSTER ADDSLOTS [slot ...] 如果需要将已经指派给某个节点的槽改为指派给另一个节点 , 可以执行重新分片操作 -## 复制与故障转移 🍀 +## 复制与故障转移 Redis 集群中的节点分为主节点 (master) 和从节点 (slave) , 其中主节点用于处理槽 , 而从节点则用于复制某个节点 , 并在被复制的主节点下线时 , 代替下线主节点继续处理命令请求 @@ -81,7 +81,7 @@ CLUSTER REPLICATE 新的主节点是通过选举产生的 -## 消息 🍀 +## 消息 集群中的各个节点通过发送和接收消息来进行通信 , 我们称发送消息的节点为发送者 , 接收消息的节点为接收者 diff --git "a/06-Redis/03-Redis - \345\237\272\347\241\200\345\221\275\344\273\244.md" b/docs/redis/command.md similarity index 98% rename from "06-Redis/03-Redis - \345\237\272\347\241\200\345\221\275\344\273\244.md" rename to docs/redis/command.md index 804736134..f429644a8 100644 --- "a/06-Redis/03-Redis - \345\237\272\347\241\200\345\221\275\344\273\244.md" +++ b/docs/redis/command.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 Redis 命令用于在 Redis 服务上执行操作 @@ -44,7 +44,7 @@ $ redis-cli -h host -p port -a password $ redis-cli -h 127.0.0.1 -p 3769 ``` -## 键命令 🍀 +## 键命令 Redis 键命令用于管理 Redis 的键 @@ -85,7 +85,7 @@ Redis 键相关基本命令 : | 15 | `RENAMENX key newkey` , 仅当 newkey 不存在时 , 将 key 改名为 newkey | | 16 | `TYPE key` , 返回 key 所储存的值的类型 | - ## 字符串命令 🍀 + ## 字符串命令 Redis 字符串数据类型的相关命令用于管理 Redis 字符串值 @@ -128,7 +128,7 @@ redis 127.0.0.1:6379> GET w3ckey "redis" | 19 | `DECRBY key decrement` , key 所储存的值减去给定的减量值(decrement) | | 20 | `APPEND key value` , 如果 key 已经存在并且是一个字符串 , APPEND 命令将 value 追加到 key 原来的值的末尾 | -## 哈希命令 🍀 +## 哈希命令 语法 @@ -172,7 +172,7 @@ Redis Hash 相关基本命令 : | 13 | `HVALS key` , 获取哈希表中所有值 | | 14 | HSCAN key cursor [MATCH pattern] \[COUNT count] 迭代哈希表中的键值对 | - ## 列表命令 🍀 + ## 列表命令 语法 @@ -218,7 +218,7 @@ Redis List 相关基本命令如下 : | 16 | `RPUSH key value1 [value2]` , 在列表中添加一个或多个值 | | 17 | `RPUSHX key value` , 为已存在的列表添加值 | -## 集合命令 🍀 +## 集合命令 Redis 中 集合是通过哈希表实现的 , 所以添加 , 删除 , 查找的复杂度都是 `O(1)` @@ -266,7 +266,7 @@ Redis Set 相关基本命令如下 : | 14 | `SUNIONSTORE destination key1 [key2]` , 所有给定集合的并集存储在 destination 集合中 | | 15 | `SSCAN key cursor [MATCH pattern] [COUNT count]` ,迭代集合中的元素 | - ## 有序集合命令 🍀 + ## 有序集合命令 有序集合的成员是唯一的,但分数(score)却可以重复 @@ -324,7 +324,7 @@ Redis Sorted Set 相关基本命令如下 : | 19 | `ZUNIONSTORE destination numkeys key [key ...]` , 计算给定的一个或多个有序集的并集 , 并存储在新的 key 中 | | 20 | `ZSCAN key cursor [MATCH pattern] [COUNT count]` , 迭代有序集合中的元素(包括元素成员和元素分值) | -## HyperLogLog命令 🍀 +## HyperLogLog命令 Redis HyperLogLog 是用来做基数统计的算法 , HyperLogLog 的优点是 , 在输入元素的数量或者体积非常非常大时 , 计算基数所需的空间总是固定的 , 并且是很小的 diff --git "a/06-Redis/04-Redis - \346\225\260\346\215\256\345\272\223.md" b/docs/redis/database.md similarity index 98% rename from "06-Redis/04-Redis - \346\225\260\346\215\256\345\272\223.md" rename to docs/redis/database.md index b83fb6d62..7f9608917 100644 --- "a/06-Redis/04-Redis - \346\225\260\346\215\256\345\272\223.md" +++ b/docs/redis/database.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 Redis 服务器默认会创建 16 个数据库 , 该值由服务器配置的 `databases` 选项决定 , 默认为16 , 查看方式如下 : @@ -29,7 +29,7 @@ OK 127.0.0.1:6379> ``` -## 生存时间 🍀 +## 生存时间 通过 `EXPIRE` 命令或者 `PEXPIRE` 命令 , 客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间 (Time To Live , TTL) , 在经过指定的秒数或者毫秒数之后 , 服务器就会自动删除生存时间为 0 的键 @@ -46,7 +46,7 @@ OK (nil) ``` -## 过期时间 🍀 +## 过期时间 与 `EXPIRE` 命令和 `REXPIRE` 命令类似 , 客户端可以通过 `EXPIREAT` 命令或 `PEXPIREAT` 命令 , 以秒或者毫秒精度给数据库中的某个键设置过期时间 (Expire Time) @@ -107,13 +107,13 @@ PEXPIREAT # 命令用于将键key的过期时间设置为times Redis 过期键删除策略 : 通过配合使用惰性删除和定期删除两种策略 , 服务器可以很好地在合理使用 CPU 时间和避免浪费内存空间之间取得平衡 -## 持久化 🍀 +## 持久化 Redis 是一个键值对数据库服务器 , 服务器中通常包含着任意个非空数据库 , 而每个非空数据库中又可以包含任意个键值对 , 为了方便起见 , 我们将服务器中的非空数据库以及他们的键值对统称为数据库状态 因为 Redis 是内存数据库 , 它将自己的数据库状态储存在内存里面 , 所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面 , 那么一旦服务器进程退出 , 服务器中的数据库状态也会消失不见 , 因此为了解决这个问题 , Redis 提供了持久化功能 -### RDB持久化 🍀 +### RDB持久化 RDB 持久化功能可以将 Redis 在内存中的数据库状态保存到磁盘里面 , 避免数据意外丢失 @@ -145,7 +145,7 @@ RDB 持久化功能所生成的 RDB 文件是一个经过压缩的二进制文 注意 : 在载入或生成 RDB 文件时 , 只会载入未过期的键 , 而过期的键会被直接忽略 -### AOF持久化 🍀 +### AOF持久化 除了 RDB 持久化功能之外 , Redis 还提供了 AOF (Append Only File) 持久化功能 , 与 RDB 持久化通过保存数据库中的键值对来记录数据库状态不同 , AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态的 @@ -188,7 +188,7 @@ Redis 读取 AOF 文件并还原数据库状态的详细步骤如下 : 注意 : 默认如果 AOF 持久化功能开启 , 那么将优先使用 AOF -## 发布订阅 🍀 +## 发布订阅 Redis 发布订阅(pub/sub)是一种消息通信模式 : 发送者(pub)发送消息 , 订阅者(sub)接收消息 diff --git "a/06-Redis/01-Redis - \347\256\200\344\273\213.md" b/docs/redis/introduction.md similarity index 96% rename from "06-Redis/01-Redis - \347\256\200\344\273\213.md" rename to docs/redis/introduction.md index cd3f4c0cc..249e39f5b 100644 --- "a/06-Redis/01-Redis - \347\256\200\344\273\213.md" +++ b/docs/redis/introduction.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 `key-value` 存储系统 @@ -30,7 +30,7 @@ Redis 与其他 key - value 缓存产品有以下三个特点 : - 原子 – Redis的所有操作都是原子性的 , 同时Redis还支持对几个操作全并后的原子性执行 - 丰富的特性 – Redis还支持 `publish/subscribe` , 通知 key 过期等等特性 -## 安装 🍀 +## 安装 方式一 @@ -69,13 +69,13 @@ redis> get foo "bar" ``` -## 配置 🍀 +## 配置 Redis 的配置文件位于 Redis 安装目录下 , 文件名为 `redis.conf` 我们可以通过 `CONFIG` 命令查看或者设置配置项 -### 查看配置 🍀 +### 查看配置 语法 @@ -98,7 +98,7 @@ redis 127.0.0.1:6379> CONFIG GET loglevel redis 127.0.0.1:6379> CONFIG GET * ``` -### 修改配置 🍀 +### 修改配置 你可以通过修改 `redis.conf` 文件或使用 `CONFIG SET` 命令来修改配置 @@ -119,11 +119,11 @@ redis 127.0.0.1:6379> CONFIG GET loglevel 2) "notice" ``` -## 数据类型 🍀 +## 数据类型 Redis支持五种数据类型 : `string (字符串)` , `hash (哈希)` , `list (列表)` , `set (集合)` 及 `zset(sorted set : 有序集合)` -### String 🍀 +### String string 是 Redis 最基本的类型 , 你可以理解成与 Memcached 一模一样的类型 , 一个 key 对应一个 value @@ -144,7 +144,7 @@ redis 127.0.0.1:6379> GET name **注意 : **一个键最大能存储 512 MB -### Hash 🍀 +### Hash Redis hash 是一个键值对集合 @@ -169,7 +169,7 @@ redis 127.0.0.1:6379> 每个 hash 可以存储 `2^(32-1)` 键值对 , 相当于 40 多亿 -### List 🍀 +### List Redis 列表是简单的字符串列表 , 按照插入顺序排序 , 你可以添加一个元素导列表的头部 (左边) 或者尾部 (右边) @@ -191,7 +191,7 @@ redis 127.0.0.1:6379> 列表最多可存储 `2^(32-1)` 元素 (4294967295 , 每个列表可存储40多亿) -### Set 🍀 +### Set Redis 的 Set 是 string 类型的无序集合 @@ -227,7 +227,7 @@ redis 127.0.0.1:6379> smembers redis.net.cn 集合中最大的成员数为 `2^(32-1)` (4294967295, 每个集合可存储40多亿个成员) -### zset 🍀 +### zset Redis zset 和 Set 一样也是string类型元素的集合 , 且不允许重复的成员 diff --git "a/06-Redis/Python\346\223\215\344\275\234Redis.md" b/docs/redis/py-redis.md similarity index 98% rename from "06-Redis/Python\346\223\215\344\275\234Redis.md" rename to docs/redis/py-redis.md index 468fd6505..2212228d1 100644 --- "a/06-Redis/Python\346\223\215\344\275\234Redis.md" +++ b/docs/redis/py-redis.md @@ -9,13 +9,13 @@ -## 安装redis-py 🍀 +## 安装redis-py ```shell $ pip install redis ``` -## 创建Redis接口对象 🍀 +## 创建Redis接口对象 创建 Redis 接口对象实例 , 将通过实例对 Redis 进行操作 @@ -30,7 +30,7 @@ r = redis.Redis(host='127.0.0.1', port=6379) r = redis.StrictRedis(host='127.0.0.1', port=6379) ``` -## 使用连接池 🍀 +## 使用连接池 通过连接池管理 Redis 对象 @@ -42,7 +42,7 @@ pool = redis.ConnectionPool(host='127.0.0.1', port=6379) r = redis.Redis(connection_pool=pool) ``` -## String操作 🍀 +## String操作 ```python def set(name, value, ex=None, px=None, nx=False, xx=False): @@ -162,7 +162,7 @@ def append(key, value): """ ``` -## Hash操作 🍀 +## Hash操作 ```python def hset(name, key, value): @@ -268,7 +268,7 @@ def hscan_iter(name, match=None, count=None): """  ``` -## List操作 🍀 +## List操作 ```python def lpush(name,values): @@ -405,7 +405,7 @@ for item in list_iter('pp'): print(item) ``` -## Set操作 🍀 +## Set操作 ```python def sadd(name,values): @@ -485,7 +485,7 @@ def sscan(name, cursor=0, match=None, count=None): """ ``` -## Zset操作 🍀 +## Zset操作 ```python def zadd(name, *args, **kwargs): @@ -615,7 +615,7 @@ def zscan(name, cursor=0, match=None, count=None, score_cast_func=float): """ ``` -## 其他操作 🍀 +## 其他操作 ```python def delete(*names): @@ -669,7 +669,7 @@ def scan(cursor=0, match=None, count=None): """ ``` -## 管道 🍀 +## 管道 Pipelines 是基 Redis 类的子类 , 它支持在单个请求中缓冲多个命令到服务器 , 它们可以通过减少客户端和服务器之间来回 TCP 数据包的数量来显着地提高命令组的性能 @@ -763,7 +763,7 @@ Pipelines 是基 Redis 类的子类 , 它支持在单个请求中缓冲多个命 [True] ``` -## 利用管道实现计数器 🍀 +## 利用管道实现计数器 ```python import redis @@ -788,7 +788,7 @@ with conn.pipeline() as pipe: pipe.execute() ``` -## 发布订阅 🍀 +## 发布订阅 发布者 : 服务器 @@ -841,7 +841,7 @@ obj = RedisHelper() obj.public('hello') ``` -## Sentinel 🍀 +## Sentinel redis重的sentinel主要用于在redis主从复制中 , 如果master顾上 , 则自动将slave替换成master diff --git a/06-Redis/08-Redis - Sentinel.md b/docs/redis/sentinel.md similarity index 97% rename from 06-Redis/08-Redis - Sentinel.md rename to docs/redis/sentinel.md index 9b8890335..9f4488183 100644 --- a/06-Redis/08-Redis - Sentinel.md +++ b/docs/redis/sentinel.md @@ -9,11 +9,11 @@ -## 介绍 🍀 +## 介绍 Sentinel (哨岗 , 哨兵) 是 Redis 的高可用性 (high availability) 解决方案 : 由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器 , 并在被监视的主服务器进入下线状态时 , 自动将下线主服务器属下的某个从服务器升级为新的主服务器 , 然后由新的主服务器代替已下线的主服务器继续处理命令请求 -## 启动并初始化Sentinel 🍀 +## 启动并初始化Sentinel 启动一个 Sentinel 可以使用命令 : @@ -35,7 +35,7 @@ $ redis-sentinel /path/to/your/sentinel.conf --sentinel 4. 根据给定的配置文件 , 初始化 Sentinel 的监视主服务器列表 5. 创建连向主服务器的网络连接 -## 初始化服务器 🍀 +## 初始化服务器 因为 Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器 , 所以启动 Sentinel 的第一步 , 就是初始化一个普通的 Redis 服务器 ; 不过 , 因为 Sentinel 执行的工作和普通 Redis 服务器执行的工作不同 , 所以 Sentinel 的初始化过程和普通 Redis 服务器的初始化过程并不完全相同 @@ -69,7 +69,7 @@ Sentinel 默认会以每十秒一次的频率 , 通过命令连接向被监视 - 一方面是关于主服务器本身的信息 , 包括 `run_id` 域记录的服务器运行 ID , 以及 role 域记录的服务器角色 - 另一方面是关于主服务器属下所有从服务器的信息 , 每个从服务器都由一个 "slave" 字符串开头的行记录 , 每行的 `ip=` 域记录了从服务器的 IP 地址 , 而 `port=` 域则记录了从服务器的端口号 , 根据这些 IP 地址和端口号 , Sentinel 无须用户提供从服务器的地址信息 , 就可以自动发现从服务器 -## 检测主观下线状态 🍀 +## 检测主观下线状态 在默认情况下 , Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例 (包括主服务器 , 从服务器 , 其他 Sentinel 在内) 发送 PING 命令 , 并通过实例返回的 PING 命令回复来判断实例是否在线 @@ -77,11 +77,11 @@ Sentinel 配置文件中的 `down-after-milliseconds` 毫秒内 , 连续向 Sent 用户设置的 `down-after-milliseconds` 选项的值 , 不仅会被 Senitinel 用来判断主服务器的主观下线状态 , 还会被用于判断主服务器属下的所有从服务器 , 以及所有同样监视这个主 服务器的其他 Sentinel 的主观下线状态 -## 检测客观下线状态 🍀 +## 检测客观下线状态 当 Sentinel 将一个主服务器判断为主观下线之后 , 为了确认这个主服务器是否真的下线了 , 它会向同样监视这一主服务器的其他 Sentinel 进行询问 , 看它们是否也认为主服务器已经进入了下线状态 (可以是主观下线或者客观下线) , 当 Sentinel 从其他 Sentinel 那里接收到足够数量的已下线判断之后 , Sentinel 就会将从服务器判定为客观下线 , 并对主服务器执行故障转移操作 -## 选举领头 Sentinel 🍀 +## 选举领头 Sentinel 当一个主服务器被判断为客观下线时 , 监视这个下线主服务器的各个 Sentinel 会进行协商 , 选举出一个领头 Sentinel , 并由领头 Sentinel 对下线主服务器执行故障转移操作 diff --git "a/06-Redis/02-Redis - \351\205\215\347\275\256\345\217\202\346\225\260\350\257\264\346\230\216.md" b/docs/redis/settings.md similarity index 99% rename from "06-Redis/02-Redis - \351\205\215\347\275\256\345\217\202\346\225\260\350\257\264\346\230\216.md" rename to docs/redis/settings.md index 250c2df2b..412782f76 100644 --- "a/06-Redis/02-Redis - \351\205\215\347\275\256\345\217\202\346\225\260\350\257\264\346\230\216.md" +++ b/docs/redis/settings.md @@ -9,7 +9,7 @@ -## 参数说明 🍀 +## 参数说明 `redis.conf` 配置项说明如下 : diff --git "a/06-Redis/06-Redis - \344\270\273\344\273\216\345\244\215\345\210\266.md" b/docs/redis/slave.md similarity index 97% rename from "06-Redis/06-Redis - \344\270\273\344\273\216\345\244\215\345\210\266.md" rename to docs/redis/slave.md index d1b964f68..854873217 100644 --- "a/06-Redis/06-Redis - \344\270\273\344\273\216\345\244\215\345\210\266.md" +++ b/docs/redis/slave.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 在 Redis 中 , 用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项 , 让一个服务器去复制 (replicate) 另一个服务器 , 我们称呼被复制的服务器为主服务器 (master) , 而对主服务器进行复制的服务器则被称为从服务器 (slave) @@ -23,7 +23,7 @@ Redis 的复制功能分为同步 (sync) 和命令传播 (command propagate) -## 同步 🍀 +## 同步 当客户端向从服务器发送 SLAVEOF 命令 , 要求从服务器复制主服务器时 , 从服务器首先需要执行同步操作 , 也就是 , 将从服务器的数据库状态更新至主服务器当前所处的数据库状态 @@ -34,13 +34,13 @@ Redis 的复制功能分为同步 (sync) 和命令传播 (command propagate) 3. 当主服务器的 BGSAVE 命令执行完毕时 , 主服务器会将 BGSAVE 命令生成的 RDB 文件发送给从服务器 , 从服务器接收并载入这个 RDB 文件 , 将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态 4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器 , 从服务器执行这些写命令 , 将自己的数据库状态更新至主服务器数据库当前所处的状态 -## 命令传播 🍀 +## 命令传播 在同步操作完毕之后 , 主从服务器两者的数据库将达到一致状态 , 但这种一致并不是一成不变的 , 每当主服务器执行客户端发送的写命令时 , 主服务器的数据库就有可能会被修改 , 并导致主从服务器状态不一致 为了让主从服务器再次回到一致状态 , 主服务器需要对从服务器执行命令传播操作 : 主服务器会将自己执行的写命令 , 也即造成主服务器不一致的那条写命令 , 发送给从服务器执行 , 当从服务器执行了相同的写命令之后 , 主从服务器将再次回到一致状态 -## PSYNC 🍀 +## PSYNC 由于 SYNC 命令在处理断线重复制情况下时效率低下 , Redis 从 2.8 版本开始 , 使用 PSYNC 命令代替 SYNC 命令来执行复制时的同步操作 diff --git "a/06-Redis/05-Redis - \344\272\213\345\212\241.md" b/docs/redis/transaction.md similarity index 97% rename from "06-Redis/05-Redis - \344\272\213\345\212\241.md" rename to docs/redis/transaction.md index ec9b703b3..23aa4b796 100644 --- "a/06-Redis/05-Redis - \344\272\213\345\212\241.md" +++ b/docs/redis/transaction.md @@ -9,7 +9,7 @@ -## 介绍 🍀 +## 介绍 Redis 事务可以一次执行多个命令 , 并且带有以下两个重要的保证 : @@ -22,7 +22,7 @@ Redis 事务可以一次执行多个命令 , 并且带有以下两个重要的 - 命令入队 - 执行事务 -## 实例 🍀 +## 实例 以下是一个事务的例子 , 它先以 `MULTI` 开始一个事务 , 然后将多个命令入队到事务中 , 最后由 `EXEC` 命令触发事务 , 一并执行事务中的所有命令 : @@ -74,7 +74,7 @@ redis 127.0.0.1:7000> exec 如果在 set b bbb 处失败 , set a 已成功不会回滚 , set c 还会继续执行 -## Redis 事务命令 🍀 +## Redis 事务命令 下表列出了 redis 事务的相关命令 : diff --git "a/04-\345\211\215\347\253\257/Vue/05-Vue - Class\344\270\216Style \347\273\221\345\256\232.md" b/docs/vue/cls-style.md similarity index 95% rename from "04-\345\211\215\347\253\257/Vue/05-Vue - Class\344\270\216Style \347\273\221\345\256\232.md" rename to docs/vue/cls-style.md index eac3bb0f6..865bdc738 100644 --- "a/04-\345\211\215\347\253\257/Vue/05-Vue - Class\344\270\216Style \347\273\221\345\256\232.md" +++ b/docs/vue/cls-style.md @@ -1,6 +1,6 @@ # Vue - Class与Style绑定 -# 介绍 🍀 +# 介绍 操作元素的 class 列表和内联样式是数据绑定的一个常见需求 , 因为它们都是属性 , 所以我们可以用 `v-bind` 处理它们 : 只需要通过表达式计算出字符串结果即可 , 不过 , 字符串拼接麻烦且易错 , 因此 , 在将 `v-bind` 用于 `class` 和 `style` 时 , Vue.js 做了专门的增强 , 表达式结果的类型除了字符串之外 , 还可以是对象或数组 @@ -13,9 +13,9 @@ -## 绑定Class 🍀 +## 绑定Class -### 对象语法 🍀 +### 对象语法 我们可以传给 `v-bind:class` 一个对象 , 以动态地切换 class : @@ -86,7 +86,7 @@ computed: { } ``` -### 数组语法 🍀 +### 数组语法 我们可以把一个数组传给 `v-bind:class` , 以应用一个 class 列表 : @@ -121,7 +121,7 @@ data: {
``` -### 组件上使用 🍀 +### 组件上使用 > 这个章节假设你已经对 Vue 组件有一定的了解 , 当然你也可以先跳过这里 , 稍后再回过头来看 @@ -159,9 +159,9 @@ HTML 将被渲染为:

Hi

``` -## 绑定内联样式 🍀 +## 绑定内联样式 -### 对象语法 🍀 +### 对象语法 `v-bind:style` 的对象语法十分直观——看着非常像 CSS , 但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case , 记得用单引号括起来) 来命名 : @@ -193,7 +193,7 @@ data: { 同样的 , 对象语法常常结合返回对象的计算属性使用 -### 数组语法 🍀 +### 数组语法 `v-bind:style` 的数组语法可以将多个样式对象应用到同一个元素上 : @@ -201,11 +201,11 @@ data: {
``` -### 自动添加前缀 🍀 +### 自动添加前缀 当 `v-bind:style` 使用需要添加[浏览器引擎前缀](https://developer.mozilla.org/zh-CN/docs/Glossary/Vendor_Prefix)的 CSS 属性时 , 如 `transform` , Vue.js 会自动侦测并添加相应的前缀 -### 多重值 🍀 +### 多重值 > 2.3.0+ diff --git "a/04-\345\211\215\347\253\257/Vue/09-Vue - \347\273\204\344\273\266\345\237\272\347\241\200.md" b/docs/vue/component.md similarity index 96% rename from "04-\345\211\215\347\253\257/Vue/09-Vue - \347\273\204\344\273\266\345\237\272\347\241\200.md" rename to docs/vue/component.md index b0995c0c8..b8637d409 100644 --- "a/04-\345\211\215\347\253\257/Vue/09-Vue - \347\273\204\344\273\266\345\237\272\347\241\200.md" +++ b/docs/vue/component.md @@ -9,7 +9,7 @@ -## 基本示例 🍀 +## 基本示例 这里有一个 Vue 组件的示例 : @@ -39,7 +39,7 @@ new Vue({ el: '#components-demo' }) 因为组件是可复用的 Vue 实例 , 所以它们与 `new Vue` 接收相同的选项 , 例如 `data`、`computed`、`watch`、`methods` 以及生命周期钩子等 , 仅有的例外是像 `el` 这样根实例特有的选项 -## 组件的复用 🍀 +## 组件的复用 你可以将组件进行任意次数的复用 : @@ -53,7 +53,7 @@ new Vue({ el: '#components-demo' }) 注意当点击按钮时 , 每个组件都会各自独立维护它的 `count` , 因为你每用一次组件 , 就会有一个它的新**实例**被创建 -### 组件的data 🍀 +### 组件的data 当我们定义这个 `` 组件时 , 你可能会发现它的 `data` 并不是像这样直接提供一个对象 : @@ -73,11 +73,11 @@ data: function () { } ``` -## 组件的组织 🍀 +## 组件的组织 通常一个应用会以一棵嵌套的组件树的形式来组织 : -![Component Tree](http://oux34p43l.bkt.clouddn.com/components.png) +![Component Tree](/asserts/components.png) 例如 , 你可能会有页头、侧边栏、内容区等组件 , 每个组件又包含了其它的像导航链接、博文之类的组件 @@ -91,7 +91,7 @@ Vue.component('my-component-name', { 全局注册的组件可以用在其被注册之后的任何 (通过 `new Vue`) 新创建的 Vue 根实例 , 也包括其组件树中的所有子组件的模板中 -## Prop 🍀 +## Prop 早些时候 , 我们提到了创建一个博文组件的事情 , 问题是如果你不能向这个组件传递某一篇博文的标题或内容之类的我们想展示的数据的话 , 它是没有办法使用的 , 这也正是 prop 的由来 @@ -143,7 +143,7 @@ new Vue({ [Prop详细](https://vuejs.org/v2/guide/components-props.html) -## 单个根元素 🍀 +## 单个根元素 当构建一个 `` 组件时 , 你的模板最终会包含的东西远不止一个标题 : @@ -206,7 +206,7 @@ Vue.component('blog-post', { 现在 , 不论何时为 `post` 对象添加一个新的属性 , 它都会自动地在 `` 内可用。 -## 通过事件向父级组件发送消息 🍀 +## 通过事件向父级组件发送消息 在我们开发 `` 组件时 , 它的一些功能可能要求我们和父级组件进行沟通 , 例如我们可能会引入一个可访问性的功能来放大博文的字号 , 同时让页面的其它部分保持默认的字号 @@ -278,7 +278,7 @@ Vue.component('blog-post', { > ``` -### 使用事件抛出一个值 🍀 +### 使用事件抛出一个值 有的时候用一个事件来抛出一个特定的值是非常有用的 , 例如我们可能想让 ``组件决定它的文本要放大多少 , 这时可以使用 `$emit` 的第二个参数来提供这个值 : @@ -316,7 +316,7 @@ methods: { } ``` -### 组件上使用 v-model 🍀 +### 组件上使用 v-model 自定义事件也可以用于创建支持 `v-model` 的自定义输入组件 , 记住 : @@ -367,7 +367,7 @@ Vue.component('custom-input', { ``` -## 通过插槽分发内容 🍀 +## 通过插槽分发内容 和 HTML 元素一样 , 我们经常需要向一个组件传递内容 , 像这样 : @@ -394,7 +394,7 @@ Vue.component('alert-box', { 更多插槽相关 : [插槽](https://vuejs.org/v2/guide/components-slots.html) -## 动态组件 🍀 +## 动态组件 有的时候 , 在不同组件之间进行动态切换是非常有用的 , 比如在一个多标签的界面里 : @@ -412,7 +412,7 @@ Home component - 已注册组件的名字 , 或 - 一个组件的选项对象 -## 解析 DOM 模板时的注意事项 🍀 +## 解析 DOM 模板时的注意事项 有些 HTML 元素 , 诸如 `
    `、`
      `、`` 和 `` 和 `