[
  {
    "path": ".gitignore",
    "content": "# Node rules:\n## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n## Dependency directory\n## Commenting this out is preferred by some people, see\n## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git\nnode_modules\n\n# Book build output\n_book\n\n# eBook build output\n*.epub\n*.mobi\n*.pdf"
  },
  {
    "path": "Article/Linux/Linux命令-文件管理模块.md",
    "content": "\n## 文件管理\n\n#### 1.查看文件信息：`ls`\n\n\n**简介：**\n\n`ls` 是英文单词 list 的简写，其功能为列出目录的内容，是用户最常用的命令之一。\n\nLinux 文件或者目录名称最长可以有 265 个字符，“.” 代表当前目录，“..” 代表上一级目录，以 “.” 开头的文件为隐藏文件，需要用 -a 参数才能显示。\n\n\n**ls常用参数：**\n\n| 参数 | 含义 |\n| --- | --- |\n| -a | 显示指定目录下所有子目录与文件，包括隐藏文件 |\n| -l | 以列表方式显示文件的详细信息 |\n| -h | 配合 -l 以人性化的方式显示文件大小 |\n\n\n**ls 匹配通配符：**\n\n与 DOS 下的文件操作类似，在 Unix/Linux 系统中，也同样允许使用特殊字符来同时引用多个文件名，这些特殊字符被称为通配符。\n\n| 通配符 | 含义 |\n| ------- | ----- |\n| * | 文件代表文件名中所有字符 |\n| ls te* | 查找以 `te` 开头的文件 |\n| ls *html | 查找结尾为 `html` 的文件 |\n| ？ | 代表文件名中任意一个字符 |\n| ls ?.c | 只找第一个字符任意，后缀为 `.c` 的文件 |\n| ls a.? | 只找只有 3 个字符，前 2 字符为 `a.` ，最后一个字符任意的文件 |\n| [] | `\"[”` 和 `“]”` 将字符组括起来，表示可以匹配字符组中的任意一个。`“-”` 用于表示字符范围。 |\n| [abc] | 匹配 a、b、c 中的任意一个 |\n| [a-f] | 匹配从 a 到 f 范围内的的任意一个字符 |\n| ls [a-f]* | 找到从 a 到 f 范围内的的任意一个字符开头的文件 |\n| ls a-f | 查找文件名为 a-f 的文件,当 `“-”` 处于方括号之外失去通配符的作用 |\n| \\ | 如果要使通配符作为普通字符使用，可以在其前面加上转义字符。`“?”` 和 `“*”` 处于方括号内时不用使用转义字符就失去通配符的作用。 |\n| ls \\*a | 查找文件名为 `*a` 的文件 |\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/ls.jpeg)\n\n\n#### 2. 输出重定向命令：`>`\n\n**简介：**\n\nLinux 允许将命令执行结果重定向到一个文件，本应显示在终端上的内容保存到指定文件中。\n\n如：ls > test.txt ( test.txt 如果不存在，则创建，存在则覆盖其内容 )\n\n注意： `> 输出重定向会覆盖原来的内容， >> 输出重定向则会追加到文件的尾部。`\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/%E9%87%8D%E5%AE%9A%E5%90%91.jpeg)\n\n\n\n#### 3. 分屏显示：`more`\n\n**简介：**\n\n查看内容时，在信息过长无法在一屏上显示时，会出现快速滚屏，使得用户无法看清文件的内容，此时可以使用 `more` 命令，每次只显示一页，按下空格键可以显示下一页，按下 `q` 键退出显示，按下 `h` 键可以获取帮助。\n\n\n\n\n\n#### 4. 管道：`|`\n\n**简介：**\n\n管道：一个命令的输出可以通过管道做为另一个命令的输入。\n\n管道我们可以理解现实生活中的管子，管子的一头塞东西进去，另一头取出来，这里 `|` 的左右分为两端，左端塞东西(写)，右端取东西(读)。\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/%E7%AE%A1%E9%81%93.png)\n\n\n#### 5. 清屏：`clear`\n\n`clear` 作用为清除终端上的显示(类似于 DOS 的 cls 清屏功能)，也可使用快捷键：Ctrl + l ( “l” 为字母 “L” 的小写 )。\n\n\n\n\n#### 6. 切换工作目录：`cd`\n\n\n**简介：**\n\n在使用 Unix/Linux 的时候，经常需要更换工作目录。`cd` 命令可以帮助用户切换工作目录。`Linux 所有的目录和文件名大小写敏感`\n\n`cd` 后面可跟绝对路径，也可以跟相对路径。如果省略目录，则默认切换到当前用户的主目录。\n\n\n**cd 常用命令：**\n\n| 命令 | 含义 |\n| --- | --- |\n| `cd` | 切换到当前用户的主目录(/home/用户目录)，用户登陆的时候，默认的目录就是用户的主目录。 |\n| `cd ~` | 切换到当前用户的主目录(/home/用户目录) |\n| `cd .` | 切换到当前目录 |\n| `cd ..` | 切换到上级目录 |\n| `cd -` | 可进入上次所在的目录 |\n\n\n注意：\n\n* 如果路径是从根路径开始的，则路径的前面需要加上 “ / ”，如 “ /mnt ”，通常进入某个目录里的文件夹，前面不用加 “ / ”。\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/cd.png)\n\n\n\n\n#### 7. 显示当前路径：`pwd`\n\n**简介：**\n\n使用 `pwd` 命令可以显示当前的工作目录，该命令很简单，直接输入 `pwd` 即可，后面不带参数。\n\n\n\n\n\n\n\n\n#### 8. 创建目录：`mkdir`\n\n**简介：**\n\n通过 `mkdir` 命令可以创建一个新的目录。参数 -p 可递归创建目录。\n\n需要注意的是新建目录的名称不能与当前目录中已有的目录或文件同名，并且目录创建者必须对当前目录具有写权限。\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/mkdir.png)\n\n\n#### 9. 删除目录：`rmdir`\n\n**简介：**\n\n可使用 `rmdir` 命令删除一个目录。必须离开目录，并且目录必须为空目录，不然提示删除失败。\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/rmdir.png)\n\n\n#### 10. 删除文件：`rm`\n\n**简介：**\n\n可通过 `rm` 删除文件或目录。使用 `rm` 命令要小心，因为文件删除后不能恢复。为了防止文件误删，可以在 `rm` 后使用 `-i` 参数以逐个确认要删除的文件。\n\n\n**`rm` 常用参数：**\n\n| 参数 | 含义 |\n| --- | --- |\n| -i | 以进行交互式方式执行 |\n| -f | 强制删除，忽略不存在的文件，无需提示 |\n| -r | 递归地删除目录下的内容，删除文件夹时必须加此参数 |\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/rm.png)\n\n\n\n#### 11. 建立链接文件：`ln`\n\n\n**简介：**\n\n\nLinux 链接文件类似于 Windows 下的快捷方式。\n\n链接文件分为软链接和硬链接。\n\n软链接：软链接不占用磁盘空间，源文件删除则软链接失效。\n\n硬链接：硬链接只能链接普通文件，不能链接目录。\n\n使用格式：\n\n```\nln 源文件 链接文件\nln -s 源文件 链接文件\n```\n\n\n如果`没有-s`选项代表建立一个硬链接文件，两个文件占用相同大小的硬盘空间，即使删除了源文件，链接文件还是存在，所以-s选项是更常见的形式。\n\n注意：如果软链接文件和源文件不在同一个目录，源文件要使用绝对路径，不能使用相对路径。\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/ln.png)\n\n\n\n\n\n#### 12. 查看或者合并文件内容：`cat`\n\n\n**简介：**\n\n查看文件内容\n\n\n\n\n#### 13. 文本搜索：`grep`\n\n\n**简介：**\n\nLinux 系统中 grep 命令是一种强大的文本搜索工具，grep 允许对文本文件进行模式查找。如果找到匹配模式， grep 打印包含模式的所有行。\n\ngrep一般格式为：\n\n```\ngrep [-选项] ‘搜索内容串’文件名\n```\n\n\n在 grep 命令中输入字符串参数时，最好引号或双引号括起来。例如：grep‘a ’1.txt。\n\n\n**`grep` 常用参数：**\n\n| 选项 | 含义 |\n| --- | --- |\n| -v | 显示不包含匹配文本的所有行（相当于求反） |\n| -n | 显示匹配行及行号 |\n| -i | 忽略大小写 |\n\ngrep 搜索内容串可以是正则表达式。\n\n\n**grep 常用正则表达式：**\n\n| 参数 | 含义 |\n| --- | --- |\n| ^a | 行首,搜寻以 m 开头的行；grep -n '^a' 1.txt |\n| ke$ | 行尾,搜寻以 ke 结束的行；grep -n 'ke$' 1.txt |\n| [Ss]igna[Ll] | 匹配 [] 里中一系列字符中的一个；搜寻匹配单词signal、signaL、Signal、SignaL的行；grep -n '[Ss]igna[Ll]' 1.txt |\n| . | (点)匹配一个非换行符的字符；匹配 e 和 e 之间有任意一个字符，可以匹配 eee，eae，eve，但是不匹配 ee，eaae；grep -n 'e.e' 1.txt |\n\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/grep.png)\n\n\n\n#### 14. 查找文件：`find`\n\n**简介：**\n\nfind 命令功能非常强大，通常用来在特定的目录下搜索符合条件的文件，也可以用来搜索特定用户属主的文件。\n\n**常用用法：**\n\n| 命令 | 含义 |\n| --- | --- |\n| find ./ -name test.sh | 查找当前目录下所有名为test.sh的文件 |\n| find ./ -name '*.sh' | 查找当前目录下所有后缀为.sh的文件 |\n| find ./ -name \"[A-Z]*\" | 查找当前目录下所有以大写字母开头的文件 |\n| find /tmp -size 2M | 查找在/tmp 目录下等于2M的文件 |\n| find /tmp -size +2M | 查找在/tmp 目录下大于2M的文件 |\n| find /tmp -size -2M | 查找在/tmp 目录下小于2M的文件 |\n| find ./ -size +4k -size -5M | 查找当前目录下大于4k，小于5M的文件 |\n| find ./ -perm 0777 | 查找当前目录下权限为 777 的文件或目录 |\n\n\n\n\n\n#### 1.15 拷贝文件：`cp`\n\n**简介：**\n\n`cp` 命令的功能是将给出的文件或目录复制到另一个文件或目录中，相当于 DOS 下的 copy 命令。\n\n**常用参数说明：**\n\n| 选项 | 含义 |\n| --- | --- |\n| -a | 该选项通常在复制目录时使用，它保留链接、文件属性，并递归地复制目录，简单而言，保持文件原有属性。 |\n| -f | 已经存在的目标文件而不提示 |\n| -i | 交互式复制，在覆盖目标文件之前将给出提示要求用户确认 |\n| -r | 若给出的源文件是目录文件，则cp将递归复制该目录下的所有子目录和文件，目标文件必须为一个目录名。 |\n| -v | 显示拷贝进度 |\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/find.png)\n\n\n#### 16. 移动文件：`mv`\n\n**简介：**\n\n用户可以使用 `mv` 命令来移动文件或目录，也可以给文件或目录重命名。\n\n**常用参数说明：**\n\n| 选项 | 含义 |\n| --- | --- |\n| -f | 禁止交互式操作，如有覆盖也不会给出提示 |\n| -i | 确认交互方式操作，如果mv操作将导致对已存在的目标文件的覆盖，系统会询问是否重写，要求用户回答以避免误覆盖文件 |\n| -v | 显示移动进度 |\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/mv.png)\n\n\n#### 17. 归档管理：`tar`\n\n\n**简介：**\n\n计算机中的数据经常需要备份，tar 是 Unix/Linux 中最常用的备份工具，此命令可以把一系列文件归档到一个大文件中，也可以把档案文件解开以恢复数据。其实说白了，就是打包。\n\n\n\n**`tar` 使用格式：**\n\n```\ntar [参数] 打包文件名 文件\n```\n\n\n**`tar` 常用参数：**\n\ntar 命令很特殊，其参数前面可以使用“-”，也可以不使用。\n\n| 参数 | 含义 |\n| --- | --- |\n| -c | 生成档案文件，创建打包文件 |\n| -v | 列出归档解档的详细过程，显示进度 |\n| -f | 指定档案文件名称，f后面一定是.tar文件，所以必须放选项最后 |\n| -t | 列出档案中包含的文件 |\n| -x | 解开档案文件 |\n\n注意：除了f需要放在参数的最后，其它参数的顺序任意。\n\n\n\n\n\n\n#### 18. 文件压缩解压：`gzip`\n\n**简介：**\n\ntar 与 gzip 命令结合使用实现文件打包、压缩。 tar 只负责打包文件，但不压缩，用 gzip 压缩 tar 打包后的文件，其扩展名一般用xxxx.tar.gz。\n\n**`gzip` 使用格式如下：**\n\n```\ngzip  [选项]  被压缩文件\n```\n\n**常用选项：**\n\n| 选项 | 含义 |\n| --- | --- |\n| -d | 解压 |\n| -r | 压缩所有子目录 |\n\n\ntar这个命令并没有压缩的功能，它只是一个打包的命令，但是在tar命令中增加一个选项(-z)可以调用gzip实现了一个压缩的功能，实行一个先打包后压缩的过程。\n\n压缩用法：tar cvzf 压缩包包名 文件1 文件2 ...\n\n```-z ：指定压缩包的格式为：file.tar.gz```\n\n\n解压用法： tar zxvf 压缩包包名\n\n```-z:指定压缩包的格式为：file.tar.gz```\n\n\n解压到指定目录：-C （大写字母“C”）\n\n\n\n\n#### 19. 文件压缩解压：`bzip2`\n\n**简介：**\n\ntar与bzip2命令结合使用实现文件打包、压缩(用法和gzip一样)。\n\ntar只负责打包文件，但不压缩，用bzip2压缩tar打包后的文件，其扩展名一般用xxxx.tar.gz2。\n\n在tar命令中增加一个选项(-j)可以调用bzip2实现了一个压缩的功能，实行一个先打包后压缩的过程。\n\n压缩用法：tar -jcvf 压缩包包名 文件...(tar jcvf bk.tar.bz2 *.c)\n\n解压用法：tar -jxvf 压缩包包名 (tar jxvf bk.tar.bz2)\n\n\n\n\n#### 20. 文件压缩解压：`zip` 、`unzip`\n\n通过zip压缩文件的目标文件不需要指定扩展名，默认扩展名为zip。\n\n压缩文件：zip [-r] 目标文件(没有扩展名) 源文件\n\n解压文件：unzip -d 解压后目录文件 压缩文件\n\n\n\n\n\n#### 21. 查看命令位置：`which`\n\n**简介：**\n\n查看命令的路径\n\n![](http://p1ceh5usj.bkt.clouddn.com/linux/%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E6%A8%A1%E5%9D%97/which.png)\n\n\n\n\n"
  },
  {
    "path": "Article/Linux/Linux命令-用户,权限管理模块.md",
    "content": "## 用户，权限管理\n\n用户是 Unix/Linux 系统工作中重要的一环，用户管理包括用户与组账号的管理。\n\n在 Unix/Linux 系统中，不论是由本机或是远程登录系统，每个系统都必须拥有一个账号，并且对于不同的系统资源拥有不同的使用权限。\n\nUnix/Linux 系统中的 root 账号通常用于系统的维护和管理，它对 Unix/Linux 操作系统的所有部分具有不受限制的访问权限。\n\n在 Unix/Linux 安装的过程中，系统会自动创建许多用户账号，而这些默认的用户就称为“标准用户”。\n\n在大多数版本的 Unix/Linux 中，都不推荐直接使用 root 账号登录系统。\n\n#### 1.多用户系统\n\n> 什么是多用户呢？\n\n「多用户」指允许多个用户（逻辑上的账户），同时使用的操作系统或应用软件。\n\n而 Linux 就是多用户操作系统，允许多个用户通过远程登录的方式访问一台机器并同时进行使用，相互之间互不影响。\n\n> 那我们经常使用的 Windows 是不是多用户操作系统呢？\n\nWindows 系列的话，Windows 1.x、2.x、3.x（不含NT 3.x）、9x、Me 均为单用户操作系统，其中 9x 虽然有多用户的雏形但基本形同虚设，Windows Me 是最后一个非 NT 内核的 Windows 系统，同样不具备实用性的多用户设计。\n\n有人会问，  Windows 不是可以创建多个账号吗？为什么不是多用户操作系统呢？\n\n其实 Windows 的多用户不是真正的多用户，就好比你在家里远程登录了你公司的电脑，你公司的电脑会立刻进入到锁屏状态，而且被人是不可以操作的。这就说明不能多账号同时操作一台电脑了。\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python0/WhyStudyPython.md",
    "content": "###  Python 越来越火爆\n\nPython 在诞生之初，因为其功能不好，运转功率低，不支持多核，根本没有并发性可言，在计算功能不那么好的年代，一直没有火爆起来，甚至很多人根本不知道有这门语言。\n\n随着时代的发展，物理硬件功能不断提高，而软件的复杂性也不断增大，开发效率越来越被企业重视。因此就有了不一样的声音，在软件开发的初始阶段，性能并没有开发效率重要，没必然为了节省不到 1ms 的时间却让开发量增加好几倍，这样划不过来。也就是开发效率比机器效率更为重要，那么 Python 就逐渐得到越来越多开发者的亲睐了。\n\n在 12-14 年，云计算升温，大量创业公司和互联网巨头挤进云计算领域，而最著名的云核算开源渠道 OpenStack 就是基于 Python 开发的。\n\n随后几年的备受关注的人工智能，机器学习首选开发语言也是 Python。\n\n至此，Python 已经成为互联网开发的焦点。在「Top 10 的编程语言走势图」可以看到，Python 已经跃居第三位，而且在 2017 年还成为了最受欢迎的语言。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-06-084240.png)\n\n### Python 开发薪资高\n\nPython 开发人员是收入最高的开发人员之一，特别是在数据科学，机器学习和 Web 开发方面。\n\n在北上广深一线城市上，Python 开发的薪资都达到了 2w+ 。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-06-090549.jpg)\n\n### Python 容易入门且功能强大\n\n如果你是一名初学者，学习 Python 就是你最好的选择，因为它容易学，功能强大，很容易就能构建 Web 应用，非常适合初学者作为入门的开发语言。\n\nPython 还一度被爆纳入高考，收编到小学课本。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-06-091204.png)\n\n如果你有一定的编程语言基础，学习 Python 也是不错的选择，因为 Python 很可能就是未来开发的主流方向，多学一门语言，多一个防身技能。而且 Python 有强大的功能库，能非常快速的开发工具，为你的本职开发工作提供护航。\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python1/IDE.md",
    "content": "# 四、集成开发环境（IDE）: PyCharm #\n\n我本人一直是建议在学习周期使用文本编辑器或者是[Sublime Text](http://www.sublimetext.com/) 这个工具来写 Python 程序的，因为这样有利于我们了解整个流程。\n\n当然，如果你有一定的编程基础，是可以使用集成的开发环境的，这样可以提高效率。这时，你可以选择 PyCharm ，PyCharm 是由 JetBrains 打造的一款 Python IDE，支持 macOS、 Windows、 Linux 系统。\n\nPyCharm 下载地址 : [https://www.jetbrains.com/pycharm/download/](https://www.jetbrains.com/pycharm/download/)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python1/Installation.md",
    "content": "# 二、Python 的安装 #\n\n因为 Python 是跨平台的，它可以运行在 Windows、Mac 和各种 Linux/Unix 系统上。目前，Python 有两个版本，一个是 2.x 版，一个是 3.x版，这两个版本是不兼容的。本草根安装的是 3.6.1 版本的。\n\n至于在哪里下载，草根我建议大家最好直接官网下载，随时下载下来的都是最新版本。官网地址：[https://www.python.org/](https://www.python.org/)\n\n## 1、windows 系统下安装配置 ##\n\n如果是 windows 系统，下载完后，直接安装，不过这里记得勾上Add Python 3.6 to PATH，然后点 「Install Now」 即可完成安装。\n\n这里要注意了，记得把「Add Python 3.6 to Path」勾上，勾上之后就不需要自己配置环境变量了，如果没勾上，就要自己手动配置。\n\n![Python安装.png](http://upload-images.jianshu.io/upload_images/2136918-2bf6591f0a12e80b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如果你一时手快，忘记了勾上 「Add Python 3.6 to Path」，那也不要紧，只需要手动配置一下环境变量就好了。\n\n在命令提示框中 cmd  上输入 ：\n\n```\npath=%path%;C:\\Python \n```\n\n特别特别注意： `C:\\Python` 是 Python 的安装目录，如果你的安装目录是其他地方，就得填上你对应的目录。\n\n安装完成后，打开命令提示符窗口，敲入 python 后，出现下面的情况，证明 Python 安装成功了。\n\n![运行python.png](http://upload-images.jianshu.io/upload_images/2136918-817c22f802e8cfce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n而你看到提示符 `>>>` 就表示我们已经在 Python 交互式环境中了，可以输入任何 Python 代码，回车后会立刻得到执行结果。\n\n\n## 2、Mac 系统下安装配置 ##\n\nMAC 系统一般都自带有 Python2.x 版本的环境，不过现在都不用 2.x 的版本了，所以建议你在 https://www.python.org/downloads/mac-osx/ 上下载最新版安装。\n\n安装完成之后，如何配置环境变量呢？\n\n先查看当前环境变量：\n\n``` \necho $PATH\n```\n\n然后打开 ``` ~/.bash_profile(没有请新建) ```\n\n``` \nvi ~/.bash_profile\n``` \n\n我装的是 Python3.7 ，Python 执行路径为：`/Library/Frameworks/Python. Framework/Versions/3.7/bin` 。于是写入\n\n```\nexport PATH=\"/Library/Frameworks/Python. Framework/Versions/3.7/bin:$PATH\"\n```\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-22-084149.png)\n\n最后保存退出，激活运行一下文件：\n\n```\nsource ~/.bash_profile\n```\n \n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python1/Introduction.md",
    "content": "# 一、Python 简介 #\n\nPython 是著名的“龟叔” Guido van Rossum 在 1989 年圣诞节期间，为了打发无聊的圣诞节而编写的一个编程语言。牛人就是牛人，为了打发无聊时间竟然写了一个这么牛皮的编程语言。\n\n现在，全世界差不多有 600 多种编程语言，但流行的编程语言也就那么 20 来种。不知道你有没有听说过 TIOBE 排行榜。\n\n这是 2017 年 2 月编程语言排行榜 TOP20 榜单：\n\n![2 月编程语言排行榜 TOP20 榜单.png](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-22-080118.jpg)\n\n还有就是 Top 10 编程语言 TIOBE 指数走势：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-22-080145.jpg)\n\n总的来说，这几种编程语言各有千秋，但不难看出，最近几年 Python 的发展非常的快，特别最近流行的机器学习，数据分析，更让 python 快速的发展起来。\n\nPython 是高级编程语言，它有一个特点就是能快速的开发。Python 为我们提供了非常完善的基础代码库，覆盖了网络、文件、GUI、数据库、文本等大量内容，被形象地称作“内置电池（batteries included）”。用 Python 开发，许多功能不必从零编写，直接使用现成的即可。而且 Python 还能开发网站，多大型网站就是用 Python 开发的，例如 YouTube、Instagram，还有国内的豆瓣。很多大公司，包括 Google、Yahoo 等，甚至 NASA（美国航空航天局）都大量地使用 Python。\n\n当然，任何编程语言有有点，也有缺点，Python 也不例外。那么 Python 有哪些缺点呢？\n\n第一个缺点就是运行速度慢，和C程序相比非常慢，因为Python是解释型语言，你的代码在执行时会一行一行地翻译成CPU能理解的机器码，这个翻译过程非常耗时，所以很慢。而C程序是运行前直接编译成CPU能执行的机器码，所以非常快。\n\n第二个缺点就是代码不能加密。如果要发布你的 Python 程序，实际上就是发布源代码。像 JAVA , C 这些编译型的语言，都没有这个问题，而解释型的语言，则必须把源码发布出去。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python1/Preface.md",
    "content": "# 前言 #\n\n每个编程语言的学习，第一个程序都是先向世界问好，Python 也不例外，这节我们先写下第一个 Python 程序 —— Hello World 。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-16-%E7%AC%AC%E4%B8%80%E4%B8%AA%20Python%20%E7%A8%8B%E5%BA%8F.png)\n\n"
  },
  {
    "path": "Article/PythonBasis/python1/The_first_procedure.md",
    "content": "# 三、第一个 Python 程序 #\n\n好了，说了那么多，现在我们可以来写一下第一个 Python 程序了。\n\n一开始写 Python 程序，个人不太建议用专门的工具来写，不方便熟悉语法，所以这里我先用 [Sublime Text](http://www.sublimetext.com/) 来写，后期可以改为用 PyCharm 。\n\n第一个 Python 程序当然是打印 Hello Python 啦。\n\n如果你没编程经验，什么都不懂，没关系，第一个 Python 程序，只要跟着做，留下个印象，尝试一下就好。\n\n新建一个文件，命名为 `HelloPython.py` , 注意，这里是以 `.py` 为后缀的文件。\n\n然后打开文件，输入 `print('Hello Python')`\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-075948.jpg)\n\n\n最后就可以打开命令行窗口，把当前目录切换到 HelloPython.py 所在目录，就可以运行这个程序了，下面就是运行的结果。\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-075956.jpg)\n\n\n当然，如果你是使用  [Sublime Text](http://www.sublimetext.com/) ，并且在安装 Python 的时候配置好了环境变量，直接按 Ctrl + B 就可以运行了，运行结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-080018.jpg)\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/1.md",
    "content": "# 一、Python 的 Magic Method #\n\n在 Python 中，所有以 \"__\" 双下划线包起来的方法，都统称为\"魔术方法\"。比如我们接触最多的 `__init__` 。\n\n魔术方法有什么作用呢？\n\n使用这些魔术方法，我们可以构造出优美的代码，将复杂的逻辑封装成简单的方法。\n\n那么一个类中有哪些魔术方法呢？\n\n我们可以使用 Python 内置的方法 `dir()` 来列出类中所有的魔术方法.示例如下：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    pass\n\n\nif __name__ == '__main__':\n    print(dir(User()))\n```\n\n输出的结果：\n\n```\n['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']\n```\n\n可以看到，一个类的魔术方法还是挺多的，不过我们只需要了解一些常见和常用的魔术方法就好了。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/2.md",
    "content": "# 二、构造(`__new__`)和初始化(`__init__`) #\n\n通过之前的学习，我们已经知道定义一个类时，我们经常会通过 `__init__(self)` 的方法在实例化对象的时候，对属性进行设置。\n\n比如下面的例子：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def __init__(self, name, age):\n        self.name = name;\n        self.age = age;\n\nuser=User('两点水',23)\n```\n\n实际上，创建一个类的过程是分为两步的，一步是创建类的对象，还有一步就是对类进行初始化。\n\n`__new__` 是用来创建类并返回这个类的实例, 而`__init__` 只是将传入的参数来初始化该实例.`__new__` 在创建一个实例的过程中必定会被调用,但 `__init__` 就不一定，比如通过 pickle.load 的方式反序列化一个实例时就不会调用 `__init__` 方法。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python%E7%B1%BB%E5%88%9B%E5%BB%BA%E7%9A%84%E8%BF%87%E7%A8%8B.png)\n\n`def __new__(cls)` 是在 `def __init__(self)` 方法之前调用的，作用是返回一个实例对象。还有一点需要注意的是：`__new__` 方法总是需要返回该类的一个实例，而 `__init__`  不能返回除了 `None` 的任何值\n\n具体的示例：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def __new__(cls, *args, **kwargs):\n        # 打印 __new__方法中的相关信息\n        print('调用了 def __new__ 方法')\n        print(args)\n        # 最后返回父类的方法\n        return super(User, cls).__new__(cls)\n\n    def __init__(self, name, age):\n        print('调用了 def __init__ 方法')\n        self.name = name\n        self.age = age\n\n\nif __name__ == '__main__':\n    usr = User('两点水', 23)\n```\n\n看看输出的结果：\n\n```txt\n调用了 def __new__ 方法\n('两点水', 23)\n调用了 def __init__ 方法\n```\n\n通过打印的结果来看，我们就可以知道一个类创建的过程是怎样的了，先是调用了 `__new__` 方法来创建一个对象，把参数传给 `__init__` 方法进行实例化。\n\n其实在实际开发中，很少会用到 `__new__` 方法，除非你希望能够控制类的创建。通常讲到 `__new__` ，都是牵扯到 `metaclass`(元类)的。\n\n当然当一个对象的生命周期结束的时候，析构函数 `__del__` 方法会被调用。但是这个方法是 Python 自己对对象进行垃圾回收的。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/3.md",
    "content": "# 三、属性的访问控制 #\n\n之前也有讲到过，Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性，然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。\n\n|方法|说明|\n| ---| --- |\n|`__getattr__(self, name)`|该方法定义了你试图访问一个不存在的属性时的行为。因此，重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。|\n|`__setattr__(self, name, value)`|定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意，实现 `__setattr__` 时要避免\"无限递归\"的错误，|\n|`__delattr__(self, name)`|`__delattr__` 与 `__setattr__` 很像，只是它定义的是你删除属性时的行为。实现 `__delattr__` 是同时要避免\"无限递归\"的错误|\n|`__getattribute__(self, name)`|`__getattribute__` 定义了你的属性被访问时的行为，相比较，`__getattr__` 只有该属性不存在时才会起作用。因此，在支持 `__getattribute__ `的 Python 版本,调用`__getattr__` 前必定会调用 `__getattribute__``__getattribute__` 同样要避免\"无限递归\"的错误。|\n\n通过上面的方法表可以知道，在进行属性访问控制定义的时候你可能会很容易的引起一个错误，可以看看下面的示例：\n\n```python\ndef __setattr__(self, name, value):\n    self.name = value\n    # 每当属性被赋值的时候， ``__setattr__()`` 会被调用，这样就造成了递归调用。\n    # 这意味这会调用 ``self.__setattr__('name', value)`` ，每次方法会调用自己。这样会造成程序崩溃。\n\ndef __setattr__(self, name, value):\n    # 给类中的属性名分配值\n    self.__dict__[name] = value  \n    # 定制特有属性\n```\n\n上面方法的调用具体示例如下：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def __getattr__(self, name):\n        print('调用了 __getattr__ 方法')\n        return super(User, self).__getattr__(name)\n\n    def __setattr__(self, name, value):\n        print('调用了 __setattr__ 方法')\n        return super(User, self).__setattr__(name, value)\n\n    def __delattr__(self, name):\n        print('调用了 __delattr__ 方法')\n        return super(User, self).__delattr__(name)\n\n    def __getattribute__(self, name):\n        print('调用了 __getattribute__ 方法')\n        return super(User, self).__getattribute__(name)\n\n\nif __name__ == '__main__':\n    user = User()\n    # 设置属性值，会调用 __setattr__\n    user.attr1 = True\n    # 属性存在,只有__getattribute__调用\n    user.attr1\n    try:\n        # 属性不存在, 先调用__getattribute__, 后调用__getattr__\n        user.attr2\n    except AttributeError:\n        pass\n    # __delattr__调用\n    del user.attr1\n\n```\n\n输出的结果：\n\n```txt\n调用了 __setattr__ 方法\n调用了 __getattribute__ 方法\n调用了 __getattribute__ 方法\n调用了 __getattr__ 方法\n调用了 __delattr__ 方法\n```\n"
  },
  {
    "path": "Article/PythonBasis/python10/4.md",
    "content": "# 四、对象的描述器 #\n\n一般来说，一个描述器是一个有“绑定行为”的对象属性 (object attribute)，它的访问控制被描述器协议方法重写。\n\n这些方法是 `__get__()`, `__set__()` , 和 `__delete__()`  。\n\n有这些方法的对象叫做描述器。\n\n默认对属性的访问控制是从对象的字典里面 (`__dict__`) 中获取 (get) , 设置 (set) 和删除 (delete) 。\n\n举例来说， `a.x` 的查找顺序是, `a.__dict__['x']` , 然后 `type(a).__dict__['x']` , 然后找 `type(a)` 的父类 ( 不包括元类 (metaclass) ).如果查找到的值是一个描述器, Python 就会调用描述器的方法来重写默认的控制行为。\n\n这个重写发生在这个查找环节的哪里取决于定义了哪个描述器方法。\n\n注意, 只有在新式类中时描述器才会起作用。在之前的篇节中已经提到新式类和旧式类的，有兴趣可以查看之前的篇节来看看，至于新式类最大的特点就是所有类都继承自 type 或者 object 的类。\n\n在面向对象编程时，如果一个类的属性有相互依赖的关系时，使用描述器来编写代码可以很巧妙的组织逻辑。在 Django 的 ORM 中,models.Model 中的 InterField 等字段, 就是通过描述器来实现功能的。\n\n我们先看下下面的例子：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def __init__(self, name='两点水', sex='男'):\n        self.sex = sex\n        self.name = name\n\n    def __get__(self, obj, objtype):\n        print('获取 name 值')\n        return self.name\n\n    def __set__(self, obj, val):\n        print('设置 name 值')\n        self.name = val\n\n\nclass MyClass(object):\n    x = User('两点水', '男')\n    y = 5\n\n\nif __name__ == '__main__':\n    m = MyClass()\n    print(m.x)\n\n    print('\\n')\n\n    m.x = '三点水'\n    print(m.x)\n\n    print('\\n')\n\n    print(m.x)\n\n    print('\\n')\n\n    print(m.y)\n\n```\n\n输出的结果如下：\n\n```txt\n获取 name 值\n两点水\n\n\n设置 name 值\n获取 name 值\n三点水\n\n\n获取 name 值\n三点水\n\n\n5\n\n```\n\n通过这个例子，可以很好的观察到这 `__get__()` 和  `__set__()` 这些方法的调用。\n\n再看一个经典的例子\n\n我们知道，距离既可以用单位\"米\"表示,也可以用单位\"英尺\"表示。\n现在我们定义一个类来表示距离,它有两个属性: 米和英尺。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\n\nclass Meter(object):\n    def __init__(self, value=0.0):\n        self.value = float(value)\n\n    def __get__(self, instance, owner):\n        return self.value\n\n    def __set__(self, instance, value):\n        self.value = float(value)\n\n\nclass Foot(object):\n    def __get__(self, instance, owner):\n        return instance.meter * 3.2808\n\n    def __set__(self, instance, value):\n        instance.meter = float(value) / 3.2808\n\n\nclass Distance(object):\n    meter = Meter()\n    foot = Foot()\n\n\nif __name__ == '__main__':\n    d = Distance()\n    print(d.meter, d.foot)\n    d.meter = 1\n    print(d.meter, d.foot)\n    d.meter = 2\n    print(d.meter, d.foot)\n\n```\n \n输出的结果：\n\n```txt\n0.0 0.0\n1.0 3.2808\n2.0 6.5616\n```\n\n在上面例子中,在还没有对 Distance 的实例赋值前, 我们认为 meter 和 foot 应该是各自类的实例对象, 但是输出却是数值。这是因为 `__get__` 发挥了作用.\n\n我们只是修改了 meter ,并且将其赋值成为 int ，但 foot 也修改了。这是 `__set__` 发挥了作用.\n\n描述器对象 (Meter、Foot) 不能独立存在, 它需要被另一个所有者类 (Distance) 所持有。描述器对象可以访问到其拥有者实例的属性，比如例子中 Foot 的 `instance.meter` 。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/5.md",
    "content": "# 五、自定义容器（Container） #\n\n经过之前编章的介绍，我们知道在 Python 中，常见的容器类型有: dict, tuple, list, string。其中也提到过可容器和不可变容器的概念。其中 tuple, string 是不可变容器，dict, list 是可变容器。\n\n可变容器和不可变容器的区别在于，不可变容器一旦赋值后，不可对其中的某个元素进行修改。当然具体的介绍，可以看回之前的文章，有图文介绍。\n\n那么这里先提出一个问题，这些数据结构就够我们开发使用吗？\n\n不够的时候，或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候，该怎么办呢？\n\n这个时候就需要自定义容器了，那么具体我们该怎么做呢？\n\n|功能|说明|\n|------|------|\n|自定义不可变容器类型|需要定义 `__len__` 和 `__getitem__` 方法|\n|自定义可变类型容器|在不可变容器类型的基础上增加定义 `__setitem__` 和 `__delitem__` |\n|自定义的数据类型需要迭代|需定义 `__iter__` |\n|返回自定义容器的长度|需实现 `__len__(self)` |\n|自定义容器可以调用 `self[key]` ，如果 key 类型错误，抛出TypeError ，如果没法返回key对应的数值时,该方法应该抛出ValueError|需要实现 `__getitem__(self, key)`|\n|当执行 \t`self[key] = value` 时|调用是 `__setitem__(self, key, value)`这个方法|\n|当执行 `del self[key]` 方法 |其实调用的方法是 `__delitem__(self, key)`|\n|当你想你的容器可以执行 `for x in container:` 或者使用 `iter(container)` 时|需要实现 `__iter__(self)` ，该方法返回的是一个迭代器|\n\n来看一下使用上面魔术方法实现 Haskell 语言中的一个数据结构：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass FunctionalList:\n    ''' 实现了内置类型list的功能,并丰富了一些其他方法: head, tail, init, last, drop, take'''\n\n    def __init__(self, values=None):\n        if values is None:\n            self.values = []\n        else:\n            self.values = values\n\n    def __len__(self):\n        return len(self.values)\n\n    def __getitem__(self, key):\n        return self.values[key]\n\n    def __setitem__(self, key, value):\n        self.values[key] = value\n\n    def __delitem__(self, key):\n        del self.values[key]\n\n    def __iter__(self):\n        return iter(self.values)\n\n    def __reversed__(self):\n        return FunctionalList(reversed(self.values))\n\n    def append(self, value):\n        self.values.append(value)\n\n    def head(self):\n        # 获取第一个元素\n        return self.values[0]\n\n    def tail(self):\n        # 获取第一个元素之后的所有元素\n        return self.values[1:]\n\n    def init(self):\n        # 获取最后一个元素之前的所有元素\n        return self.values[:-1]\n\n    def last(self):\n        # 获取最后一个元素\n        return self.values[-1]\n\n    def drop(self, n):\n        # 获取所有元素，除了前N个\n        return self.values[n:]\n\n    def take(self, n):\n        # 获取前N个元素\n        return self.values[:n]\n\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/6.md",
    "content": "# 六、运算符相关的魔术方法 #\n\n\n运算符相关的魔术方法实在太多了,j就大概列举下面两类：\n\n\n## 1、比较运算符 ##\n\n|魔术方法|说明|\n|-----|-----|\n|`__cmp__(self, other)`|如果该方法返回负数，说明 `self < other`;  返回正数，说明 `self > other`; 返回 0 说明 `self == other `。强烈不推荐来定义 `__cmp__` , 取而代之, 最好分别定义 `__lt__`, `__eq__` 等方法从而实现比较功能。 `__cmp__` 在 Python3 中被废弃了。|\n|`__eq__(self, other)`|定义了比较操作符 == 的行为|\n|`__ne__(self, other)`|定义了比较操作符 != 的行为|\n|`__lt__(self, other)`|定义了比较操作符 < 的行为|\n|`__gt__(self, other)`|定义了比较操作符 > 的行为|\n|`__le__(self, other)`|定义了比较操作符 <= 的行为|\n|`__ge__(self, other)`|定义了比较操作符 >= 的行为|\n\n\n来看个简单的例子就能理解了：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass Number(object):\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        print('__eq__')\n        return self.value == other.value\n\n    def __ne__(self, other):\n        print('__ne__')\n        return self.value != other.value\n\n    def __lt__(self, other):\n        print('__lt__')\n        return self.value < other.value\n\n    def __gt__(self, other):\n        print('__gt__')\n        return self.value > other.value\n\n    def __le__(self, other):\n        print('__le__')\n        return self.value <= other.value\n\n    def __ge__(self, other):\n        print('__ge__')\n        return self.value >= other.value\n\n\nif __name__ == '__main__':\n    num1 = Number(2)\n    num2 = Number(3)\n    print('num1 == num2 ? --------> {} \\n'.format(num1 == num2))\n    print('num1 != num2 ? --------> {} \\n'.format(num1 == num2))\n    print('num1 < num2 ? --------> {} \\n'.format(num1 < num2))\n    print('num1 > num2 ? --------> {} \\n'.format(num1 > num2))\n    print('num1 <= num2 ? --------> {} \\n'.format(num1 <= num2))\n    print('num1 >= num2 ? --------> {} \\n'.format(num1 >= num2))\n\n```\n\n输出的结果为：\n\n```txt\n__eq__\nnum1 == num2 ? --------> False\n\n__eq__\nnum1 != num2 ? --------> False\n\n__lt__\nnum1 < num2 ? --------> True\n\n__gt__\nnum1 > num2 ? --------> False\n\n__le__\nnum1 <= num2 ? --------> True\n\n__ge__\nnum1 >= num2 ? --------> False\n\n```\n\n## 2、算术运算符 ##\n\n|魔术方法|说明|\n|-----|-----|\n|`__add__(self, other)`|实现了加号运算|\n|`__sub__(self, other)`|实现了减号运算|\n|`__mul__(self, other)`|实现了乘法运算|\n|`__floordiv__(self, other)`|实现了 // 运算符|\n|`___div__(self, other)`|实现了/运算符. 该方法在 Python3 中废弃. 原因是 Python3 中，division 默认就是 true division|\n|`__truediv__(self, other)`|实现了 true division. 只有你声明了 `from __future__ import division` 该方法才会生效|\n|`__mod__(self, other)`|实现了 % 运算符, 取余运算|\n|`__divmod__(self, other)`|实现了 divmod() 內建函数|\n|`__pow__(self, other)`|实现了 `**` 操作. N 次方操作|\n|`__lshift__(self, other)`|实现了位操作 `<<`|\n|`__rshift__(self, other)`|实现了位操作 `>>`|\n|`__and__(self, other)`|实现了位操作 `&`|\n|`__or__(self, other)`|实现了位操作 `|`|\n|`__xor__(self, other)`|实现了位操作 `^`|\n\n\n可以关注下公众号：\n\n这个公号可能很少更新，但是一更新，就是把整理的一系列文章更新上去。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-WechatIMG697.jpeg)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python10/Preface.md",
    "content": "# 前言 #\n\n有时候修改文章，真的修改到想死。真的很耗时间，很烦的。\n\n好吧，每次都是安慰自己，快完结了，快更新完了。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python%20%E7%9A%84%20Magic%20Method.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python11/1.md",
    "content": "# 一、枚举类的使用 #\n\n实际开发中，我们离不开定义常量，当我们需要定义常量时，其中一个办法是用大写变量通过整数来定义，例如月份：\n\n```python\nJAN = 1\nFEB = 2\nMAR = 3\n...\nNOV = 11\nDEC = 12\n```\n\n当然这样做简单快捷，缺点是类型是 `int` ，并且仍然是变量。\n\n那有没有什么好的方法呢？\n\n这时候我们定义一个 class 类型，每个常量都是 class 里面唯一的实例。\n\n正好 Python 提供了 Enum 类来实现这个功能如下：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nfrom enum import Enum\n\nMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))\n\n# 遍历枚举类型\nfor name, member in Month.__members__.items():\n    print(name, '---------', member, '----------', member.value)\n\n# 直接引用一个常量\nprint('\\n', Month.Jan)\n\n```\n\n输出的结果如下：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python3%20%E6%9E%9A%E4%B8%BE%E7%B1%BB%E5%9E%8B%E7%9A%84%E4%BD%BF%E7%94%A8.png)\n\n我们使用 `Enum` 来定义了一个枚举类。\n\n上面的代码，我们创建了一个有关月份的枚举类型 Month ，这里要注意的是构造参数，第一个参数 Month 表示的是该枚举类的类名，第二个 tuple 参数，表示的是枚举类的值；当然，枚举类通过 `__members__` 遍历它的所有成员的方法。\n\n注意的一点是 ， `member.value` 是自动赋给成员的 `int` 类型的常量，默认是从 1 开始的。\n\n**而且 Enum 的成员均为单例（Singleton），并且不可实例化，不可更改**\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python11/2.md",
    "content": "# 二、Enum 的源码 #\n\n通过上面的实例可以知道通过 `__members__`  可以遍历枚举类的所有成员。\n\n那有没有想过为什么呢？\n\n当你看到那段代码的时候，有没有想过为什么通过  `__members__`  就能遍历枚举类型的所有成员出来？\n\n\n我们可以先来大致看看 Enum 的源码是如何实现的；\n\nEnum 在模块 enum.py 中，先来看看 Enum 类的片段\n\n```python\nclass Enum(metaclass=EnumMeta):\n    \"\"\"Generic enumeration.\n    Derive from this class to define new enumerations.\n    \"\"\"\n```\n\n可以看到，Enum 是继承元类 EnumMeta 的；再看看 EnumMeta 的相关片段\n\n```python\nclass EnumMeta(type):\n    \"\"\"Metaclass for Enum\"\"\"\n    @property\n    def __members__(cls):\n        \"\"\"Returns a mapping of member name->value.\n        This mapping lists all enum members, including aliases. Note that this\n        is a read-only view of the internal mapping.\n        \"\"\"\n        return MappingProxyType(cls._member_map_)\n```\n\n首先 `__members__` 方法返回的是一个包含一个 Dict 既 Map 的 MappingProxyType，并且通过 @property 将方法 `__members__(cls)` 的访问方式改变为了变量的的形式，那么就可以直接通过 `__members__` 来进行访问了\n\n"
  },
  {
    "path": "Article/PythonBasis/python11/3.md",
    "content": "# 三、自定义类型的枚举 #\n\n但有些时候我们需要控制枚举的类型，那么我们可以 Enum 派生出自定义类来满足这种需要。通过修改上面的例子：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\nfrom enum import Enum, unique\n\nEnum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))\n\n\n# @unique 装饰器可以帮助我们检查保证没有重复值\n@unique\nclass Month(Enum):\n    Jan = 'January'\n    Feb = 'February'\n    Mar = 'March'\n    Apr = 'April'\n    May = 'May'\n    Jun = 'June'\n    Jul = 'July'\n    Aug = 'August'\n    Sep = 'September '\n    Oct = 'October'\n    Nov = 'November'\n    Dec = 'December'\n\n\nif __name__ == '__main__':\n    print(Month.Jan, '----------',\n          Month.Jan.name, '----------', Month.Jan.value)\n    for name, member in Month.__members__.items():\n        print(name, '----------', member, '----------', member.value)\n\n```\n\n\n输出的结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python3%20%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%9E%8B%E7%9A%84%E6%9E%9A%E4%B8%BE%E7%B1%BB.png)\n\n\n\n通过上面的例子，可以知道枚举模块定义了具有迭代 (interator) 和比较(comparison) 功能的枚举类型。 它可以用来为值创建明确定义的符号，而不是使用具体的整数或字符串。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python11/4.md",
    "content": "# 四、枚举的比较 #\n\n因为枚举成员不是有序的，所以它们只支持通过标识(identity) 和相等性 (equality) 进行比较。下面来看看 `==` 和 `is` 的使用：\n\n```python\n\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\nfrom enum import Enum\n\n\nclass User(Enum):\n    Twowater = 98\n    Liangdianshui = 30\n    Tom = 12\n\n\nTwowater = User.Twowater\nLiangdianshui = User.Liangdianshui\n\nprint(Twowater == Liangdianshui, Twowater == User.Twowater)\nprint(Twowater is Liangdianshui, Twowater is User.Twowater)\n\ntry:\n    print('\\n'.join('  ' + s.name for s in sorted(User)))\nexcept TypeError as err:\n    print(' Error : {}'.format(err))\n\n```\n\n输出的结果：\n\n```txt\n\nFalse True\nFalse True\n Error : '<' not supported between instances of 'User' and 'User'\n\n```\n\n可以看看最后的输出结果，报了个异常，那是因为大于和小于比较运算符引发 TypeError 异常。也就是 `Enum` 类的枚举是不支持大小运算符的比较的。\n\n那么能不能让枚举类进行大小的比较呢？\n\n当然是可以的，使用 IntEnum 类进行枚举，就支持比较功能。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\nimport enum\n\n\nclass User(enum.IntEnum):\n    Twowater = 98\n    Liangdianshui = 30\n    Tom = 12\n\n\ntry:\n    print('\\n'.join(s.name for s in sorted(User)))\nexcept TypeError as err:\n    print(' Error : {}'.format(err))\n\n\n```\n\n看看输出的结果：\n\n```txt\nTom\nLiangdianshui\nTwowater\n```\n\n通过输出的结果可以看到，枚举类的成员通过其值得大小进行了排序。也就是说可以进行大小的比较。\n"
  },
  {
    "path": "Article/PythonBasis/python11/Preface.md",
    "content": "# 前言 #\n\n2019年10月14日16:59:38 看了一下，还有五个章节就修改完基础部分了。\n\n干就完事了。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E6%9E%9A%E4%B8%BE%E7%B1%BB.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python12/1.md",
    "content": "# 一、Python 中类也是对象 #\n\n在了解元类之前，我们先进一步理解 Python 中的类，在大多数编程语言中，类就是一组用来描述如何生成一个对象的代码段。在 Python 中这一点也是一样的。\n\n这点在学习类的章节也强调过了，下面可以通过例子回忆一下：\n\n```python\nclass ObjectCreator(object):\n    pass\n\n\nmObject = ObjectCreator()\nprint(mObject)\n```\n\n输出结果：\n\n```\n<__main__.ObjectCreator object at 0x00000000023EE048>\n```\n\n但是，Python 中的类有一点跟大多数的编程语言不同，在 Python 中，可以把类理解成也是一种对象。对的，这里没有写错，就是对象。\n\n为什么呢？\n\n因为只要使用关键字 `class` ，Python 解释器在执行的时候就会创建一个对象。\n\n如：\n\n```python\nclass ObjectCreator(object):\n    pass\n```\n\n当程序运行这段代码的时候，就会在内存中创建一个对象，名字就是ObjectCreator。这个对象（类）自身拥有创建对象（类实例）的能力，而这就是为什么它是一个类的原因。\n\n但是，它的本质仍然是一个对象，于是我们可以对它做如下的操作：\n\n```python\nclass ObjectCreator(object):\n    pass\n\n\ndef echo(ob):\n    print(ob)\n\n\nmObject = ObjectCreator()\nprint(mObject)\n\n# 可以直接打印一个类，因为它其实也是一个对象\nprint(ObjectCreator)\n# 可以直接把一个类作为参数传给函数（注意这里是类，是没有实例化的）\necho(ObjectCreator)\n# 也可以直接把类赋值给一个变量\nobjectCreator = ObjectCreator\nprint(objectCreator)\n```\n\n输出的结果如下：\n\n```\n<__main__.ObjectCreator object at 0x000000000240E358>\n<class '__main__.ObjectCreator'>\n<class '__main__.ObjectCreator'>\n<class '__main__.ObjectCreator'>\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python12/2.md",
    "content": "# 二、使用 `type()` 动态创建类 #\n\n因为类也是对象，所以我们可以在程序运行的时候创建类。\n\nPython 是动态语言。\n\n**动态语言和静态语言最大的不同，就是函数和类的定义，不是编译时定义的，而是运行时动态创建的。**\n\n在之前，我们先了了解下 `type()` 函数。\n\n\n首先我们新建一个 `hello.py` 的模块，然后定义一个 Hello 的 class ，\n\n```python\nclass Hello(object):\n    def hello(self, name='Py'):\n        print('Hello,', name)\n```\n\n然后在另一个模块中引用 hello 模块，并输出相应的信息。\n\n其中 `type()` 函数的作用是可以查看一个类型和变量的类型。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nfrom com.twowater.hello import Hello\n\nh = Hello()\nh.hello()\n\nprint(type(Hello))\nprint(type(h))\n\n```\n\n输出的结果是怎样的呢？\n\n```\nHello, Py\n<class 'type'>\n<class 'com.twowater.hello.Hello'>\n```\n\n上面也提到过，`type()` 函数可以查看一个类型或变量的类型，`Hello` 是一个 `class` ，它的类型就是 `type` ，而 `h` 是一个实例，它的类型就是 `com.twowater.hello.Hello`。\n\n前面的 `com.twowater` 是我的包名，`hello` 模块在该包名下。\n\n在这里还要细想一下，上面的例子中，我们使用 `type()` 函数查看一个类型或者变量的类型。\n\n其中查看了一个 `Hello` class 的类型，打印的结果是： `<class 'type'>` 。\n\n**其实 \t`type()` 函数不仅可以返回一个对象的类型，也可以创建出新的类型。**\n\nclass 的定义是运行时动态创建的，而创建 class 的方法就是使用 `type()` 函数。\n\n比如我们可以通过 `type()` 函数创建出上面例子中的 `Hello` 类，具体看下面的代码：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef printHello(self, name='Py'):\n    # 定义一个打印 Hello 的函数\n    print('Hello,', name)\n\n\n# 创建一个 Hello 类\nHello = type('Hello', (object,), dict(hello=printHello))\n\n# 实例化 Hello 类\nh = Hello()\n# 调用 Hello 类的方法\nh.hello()\n# 查看 Hello class 的类型\nprint(type(Hello))\n# 查看实例 h 的类型\nprint(type(h))\n\n```\n\n输出的结果如下：\n\n```\nHello, Py\n<class 'type'>\n<class '__main__.Hello'>\n```\n\n在这里，需先了解下通过 `type()` 函数创建 class 对象的参数说明：\n\n1、class 的名称，比如例子中的起名为 `Hello`\n\n2、继承的父类集合，注意 Python 支持多重继承，如果只有一个父类，tuple 要使用单元素写法；例子中继承 object 类，因为是单元素的 tuple ，所以写成 `(object,)`\n\n3、class 的方法名称与函数绑定；例子中将函数 `printHello` 绑定在方法名 `hello` 中\n\n具体的模式如下：\n\n```python\ntype(类名, 父类的元组（针对继承的情况，可以为空），包含属性的字典（名称和值）)\n```\n\n好了，了解完具体的参数使用之外，我们看看输出的结果，可以看到，通过 `type()` 函数创建的类和直接写 class 是完全一样的。\n\n这是因为Python 解释器遇到 class 定义时，仅仅是扫描一下 class 定义的语法，然后调用 `type()` 函数创建出 class 的。\n\n不过一般的情况下，我们都是使用 `class ***...` 的方法来定义类的，不过 `type()` 函数也可以让我们创建出类来。\n\n也就是说，动态语言本身支持运行期动态创建类，这和静态语言有非常大的不同，要在静态语言运行期创建类，必须构造源代码字符串再调用编译器，或者借助一些工具生成字节码实现，本质上都是动态编译，会非常复杂。\n\n**可以看到，在 Python 中，类也是对象，你可以动态的创建类。**\n\n其实这也就是当你使用关键字 class 时 Python 在幕后做的事情，而这就是通过元类来实现的。 \n\n"
  },
  {
    "path": "Article/PythonBasis/python12/3.md",
    "content": "# 三、什么是元类 #\n\n通过上面的介绍，终于模模糊糊的带到元类这里来了。可是我们到现在还不知道元类是什么鬼东西。\n\n我们创建类的时候，大多数是为了创建类的实例对象。\n\n那么元类呢？\n\n**元类就是用来创建类的。也可以换个理解方式就是：元类就是类的类。**\n\n通过上面 `type()` 函数的介绍，我们知道可以通过 `type()` 函数创建类：\n\n```python\t\nMyClass = type('MyClass', (), {})\n```\n\n**实际上 `type()` 函数是一个元类。**\n\n`type()` 就是 Python 在背后用来创建所有类的元类。\n\n那么现在我们也可以猜到一下为什么 `type()` 函数是 type 而不是 Type呢？\n\n这可能是为了和 str 保持一致性，str 是用来创建字符串对象的类，而 int 是用来创建整数对象的类。\n\ntype 就是创建类对象的类。\n\n你可以通过检查 `__class__` 属性来看到这一点。\n\nPython 中所有的东西，注意喔，这里是说所有的东西，他们都是对象。\n\n这包括整数、字符串、函数以及类。它们全部都是对象，而且它们都是从一个类创建而来。\n\n```python\n# 整形\nage = 23\nprint(age.__class__)\n# 字符串\nname = '两点水'\nprint(name.__class__)\n\n\n# 函数\ndef fu():\n    pass\n\n\nprint(fu.__class__)\n\n\n# 实例\nclass eat(object):\n    pass\n\n\nmEat = eat()\n\nprint(mEat.__class__)\n```\n\n输出的结果如下：\n\n```\n<class 'int'>\n<class 'str'>\n<class 'function'>\n<class '__main__.eat'>\n```\n\n可以看到，上面的所有东西，也就是所有对象都是通过类来创建的，那么我们可能会好奇，`__class__` 的 `__class__` 会是什么呢？\n\n**换个说法就是，创建这些类的类是什么呢？**\n\n我们可以继续在上面的代码基础上新增下面的代码：\n\n```python\nprint(age.__class__.__class__)\nprint(name.__class__.__class__)\nprint(fu.__class__.__class__)\nprint(mEat.__class__.__class__)\n```\n\n输出的结果如下：\n\n```\n<class 'type'>\n<class 'type'>\n<class 'type'>\n<class 'type'>\n```\n\n认真观察，再理清一下，上面输出的结果是我们把整形 `age` ,字符创 `name` ,函数 `fu` 和对象实例 `mEat` 里  `__class__` 的 `__class__` 打印出来的结果。\n\n也可以说是他们类的类打印结果。发现打印出来的 class 都是 type 。\n\n一开始也提到了，元类就是类的类。\n\n也就是元类就是负责创建类的一种东西。\n\n你也可以理解为，元类就是负责生成类的。\n\n**而 type 就是内建的元类。也就是 Python 自带的元类。**\n\n"
  },
  {
    "path": "Article/PythonBasis/python12/4.md",
    "content": "# 四、自定义元类 #\n\n到现在，我们已经知道元类是什么鬼东西了。\n\n那么，从始至终我们还不知道元类到底有啥用。\n\n只是了解了一下元类。\n\n在了解它有啥用的时候，我们先来了解下怎么自定义元类。\n\n因为只有了解了怎么自定义才能更好的理解它的作用。\n\n首先我们来了解下 `__metaclass__` 属性\n\nmetaclass，直译为元类，简单的解释就是：\n\n当我们定义了类以后，就可以根据这个类创建出实例，所以：先定义类，然后创建实例。\n\n但是如果我们想创建出类呢？\n\n那就必须根据metaclass创建出类，所以：先定义metaclass，然后创建类。\n\n连接起来就是：先定义metaclass，就可以创建类，最后创建实例。\n\n所以，metaclass 允许你创建类或者修改类。\n\n换句话说，你可以把类看成是 metaclass 创建出来的“实例”。\n\n```python\nclass MyObject(object):\n    __metaclass__ = something…\n[…]\n```\n\n如果是这样写的话，Python 就会用元类来创建类 MyObject。\n\n当你写下 `class MyObject(object)`，但是类对象 MyObject 还没有在内存中创建。P\n\nython 会在类的定义中寻找 `__metaclass__` 属性，如果找到了，Python 就会用它来创建类 MyObject，如果没有找到，就会用内建的 type 函数来创建这个类。如果还不怎么理解，看下下面的流程图：\n\n![__metaclass__的介绍](https://user-gold-cdn.xitu.io/2017/9/6/06c5a4390887abd3d79401848742f5ce)\n\n\n再举个实例：\n\n```python\nclass Foo(Bar):\n    pass\n```\n\n它的判断流程是怎样的呢？\n\n首先判断 Foo 中是否有 `__metaclass__` 这个属性？如果有，Python 会在内存中通过 `__metaclass__` 创建一个名字为 Foo 的类对象（注意，这里是类对象）。如果 Python 没有找到`__metaclass__` ，它会继续在 Bar（父类）中寻找`__metaclass__` 属性，并尝试做和前面同样的操作。如果 Python在任何父类中都找不到 `__metaclass__` ，它就会在模块层次中去寻找 `__metaclass__` ，并尝试做同样的操作。如果还是找不到` ` `__metaclass__` ,Python 就会用内置的 type 来创建这个类对象。\n\n其实 `__metaclass__` 就是定义了 class 的行为。类似于 class 定义了 instance 的行为，metaclass 则定义了 class 的行为。可以说，class 是 metaclass 的 instance。\n\n\n现在，我们基本了解了 `__metaclass__` 属性，但是，也没讲过如何使用这个属性，或者说这个属性可以放些什么？\n\n答案就是：可以创建一个类的东西。那么什么可以用来创建一个类呢？type，或者任何使用到 type 或者子类化 type 的东东都可以。\n\n**元类的主要目的就是为了当创建类时能够自动地改变类。**\n\n通常，你会为API 做这样的事情，你希望可以创建符合当前上下文的类。假想一个很傻的例子，你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到，但其中一种就是通过在模块级别设定`__metaclass__` 。采用这种方法，这个模块中的所有类都会通过这个元类来创建，我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。\n\n幸运的是，`__metaclass__` 实际上可以被任意调用，它并不需要是一个正式的类。所以，我们这里就先以一个简单的函数作为例子开始。\n\n```python\n# 元类会自动将你通常传给‘type’的参数作为自己的参数传入\ndef upper_attr(future_class_name, future_class_parents, future_class_attr):\n    '''返回一个类对象，将属性都转为大写形式'''\n    #  选择所有不以'__'开头的属性\n    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))\n```\n\n\n```python\n# 将它们转为大写形式\nuppercase_attr = dict((name.upper(), value) for name, value in attrs)\n \n# 通过'type'来做类对象的创建\nreturn type(future_class_name, future_class_parents, uppercase_attr)\n \n__metaclass__ = upper_attr  \n#  这会作用到这个模块中的所有类\n \nclass Foo(object):\n    # 我们也可以只在这里定义__metaclass__，这样就只会作用于这个类中\n    bar = 'bip'\n```\n\n```python\nprint hasattr(Foo, 'bar')\n# 输出: False\nprint hasattr(Foo, 'BAR')\n# 输出:True\n \nf = Foo()\nprint f.BAR\n# 输出:'bip'\n```\n\n用 class 当做元类的做法：\n\n```python\n# 请记住，'type'实际上是一个类，就像'str'和'int'一样\n# 所以，你可以从type继承\nclass UpperAttrMetaClass(type):\n    # __new__ 是在__init__之前被调用的特殊方法\n    # __new__是用来创建对象并返回之的方法\n    # 而__init__只是用来将传入的参数初始化给对象\n    # 你很少用到__new__，除非你希望能够控制对象的创建\n    # 这里，创建的对象是类，我们希望能够自定义它，所以我们这里改写__new__\n    # 如果你希望的话，你也可以在__init__中做些事情\n    # 还有一些高级的用法会涉及到改写__call__特殊方法，但是我们这里不用\n    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):\n        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))\n        uppercase_attr = dict((name.upper(), value) for name, value in attrs)\n        return type(future_class_name, future_class_parents, uppercase_attr)\n\n```\n\n但是，这种方式其实不是 OOP。我们直接调用了 type，而且我们没有改写父类的 `__new__` 方法。现在让我们这样去处理:\n\n```python\n\nclass UpperAttrMetaclass(type):\n    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):\n        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))\n        uppercase_attr = dict((name.upper(), value) for name, value in attrs)\n \n        # 复用type.__new__方法\n        # 这就是基本的OOP编程，没什么魔法\n        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)\n```\n\n你可能已经注意到了有个额外的参数 `upperattr_metaclass` ，这并没有什么特别的。类方法的第一个参数总是表示当前的实例，就像在普通的类方法中的 self 参数一样。当然了，为了清晰起见，这里的名字我起的比较长。但是就像 self 一样，所有的参数都有它们的传统名称。因此，在真实的产品代码中一个元类应该是像这样的：\n\n```python\nclass UpperAttrMetaclass(type):\n    def __new__(cls, name, bases, dct):\n        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')\n        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)\n        return type.__new__(cls, name, bases, uppercase_attr)\n\n```\n\n如果使用 super 方法的话，我们还可以使它变得更清晰一些，这会缓解继承（是的，你可以拥有元类，从元类继承，从 type 继承）\n\n```python\nclass UpperAttrMetaclass(type):\n    def __new__(cls, name, bases, dct):\n        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))\n        uppercase_attr = dict((name.upper(), value) for name, value in attrs)\n        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)\n```\n\n通常我们都会使用元类去做一些晦涩的事情，依赖于自省，控制继承等等。确实，用元类来搞些“黑暗魔法”是特别有用的，因而会搞出些复杂的东西来。但就元类本身而言，它们其实是很简单的：\n\n* 拦截类的创建\n* 修改类\n* 返回修改之后的类\n\n"
  },
  {
    "path": "Article/PythonBasis/python12/5.md",
    "content": "# 五、使用元类 #\n\n终于到了使用元类了，可是一般来说，我们根本就用不上它，就像Python 界的领袖 Tim Peters 说的：\n\n> 元类就是深度的魔法，99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类，那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么，而且根本不需要解释为什么要用元类。\n\n元类的主要用途是创建 API。一个典型的例子是 Django ORM。它允许你像这样定义：\n\n```python\nclass Person(models.Model):\n    name = models.CharField(max_length=30)\n    age = models.IntegerField()\n```\n\n但是如果你这样做的话：\n\n```python\nguy  = Person(name='bob', age='35')\nprint guy.age\n```\n\n这并不会返回一个 IntegerField 对象，而是会返回一个 int，甚至可以直接从数据库中取出数据。\n\n这是有可能的，因为 models.Model 定义了 `__metaclass__` ， 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂 hook。\n\nDjango 框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的 API 将其化简，通过这个 API 重新创建代码，在背后完成真正的工作。\n\nPython 中的一切都是对象，它们要么是类的实例，要么是元类的实例，除了 type。type 实际上是它自己的元类，在纯 Python 环境中这可不是你能够做到的，这是通过在实现层面耍一些小手段做到的。\n\n参考：\n\n[https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python](https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python)\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python12/Preface.md",
    "content": "# 前言 #\n\nPython 界的领袖 Tim Peters 说的：\n\n> 元类就是深度的魔法，99% 的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类，那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么，而且根本不需要解释为什么要用元类。\n\n\n所以，这篇文章，认真阅读一遍就好了。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E5%85%83%E7%B1%BB.png)\n\n"
  },
  {
    "path": "Article/PythonBasis/python13/1.md",
    "content": "# 线程与进程 #\n\n线程与进程是操作系统里面的术语，简单来讲，每一个应用程序都有一个自己的进程。\n\n操作系统会为这些进程分配一些执行资源，例如内存空间等。\n\n在进程中，又可以创建一些线程，他们共享这些内存空间，并由操作系统调用，以便并行计算。\n\n我们都知道现代操作系统比如 Mac OS X，UNIX，Linux，Windows 等可以同时运行多个任务。\n\n打个比方，你一边在用浏览器上网，一边在听敲代码，一边用 Markdown 写博客，这就是多任务，至少同时有 3 个任务正在运行。\n\n当然还有很多任务悄悄地在后台同时运行着，只是桌面上没有显示而已。\n\n对于操作系统来说，一个任务就是一个进程（Process），比如打开一个浏览器就是启动一个浏览器进程，打开 PyCharm 就是一个启动了一个 PtCharm 进程，打开 Markdown 就是启动了一个 Md 的进程。 \n\n虽然现在多核 CPU 已经非常普及了。\n\n可是由于 CPU 执行代码都是顺序执行的，这时候我们就会有疑问，单核 CPU 是怎么执行多任务的呢？\n\n其实就是操作系统轮流让各个任务交替执行，任务 1 执行 0.01 秒，切换到任务 2 ，任务 2 执行 0.01 秒，再切换到任务 3 ，执行 0.01秒……这样反复执行下去。\n\n表面上看，每个任务都是交替执行的，但是，由于 CPU的执行速度实在是太快了，我们肉眼和感觉上没法识别出来，就像所有任务都在同时执行一样。\n\n真正的并行执行多任务只能在多核 CPU 上实现，但是，由于任务数量远远多于 CPU 的核心数量，所以，操作系统也会自动把很多任务轮流调度到每个核心上执行。\n\n\n有些进程不仅仅只是干一件事的啊，比如浏览器，我们可以播放时视频，播放音频，看文章，编辑文章等等，其实这些都是在浏览器进程中的子任务。在一个进程内部，要同时干多件事，就需要同时运行多个“子任务”，我们把进程内的这些“子任务”称为线程（Thread）。\n\n由于每个进程至少要干一件事，所以，一个进程至少有一个线程。\n\n当然，一个进程也可以有多个线程，多个线程可以同时执行，多线程的执行方式和多进程是一样的，也是由操作系统在多个线程之间快速切换，让每个线程都短暂地交替运行，看起来就像同时执行一样。\n\n那么在 Python 中我们要同时执行多个任务怎么办？\n\n有两种解决方案：\n\n一种是启动多个进程，每个进程虽然只有一个线程，但多个进程可以一块执行多个任务。\n\n还有一种方法是启动一个进程，在一个进程内启动多个线程，这样，多个线程也可以一块执行多个任务。\n\n当然还有第三种方法，就是启动多个进程，每个进程再启动多个线程，这样同时执行的任务就更多了，当然这种模型更复杂，实际很少采用。\n\n总结一下就是，多任务的实现有3种方式：\n\n* 多进程模式；\n* 多线程模式；\n* 多进程+多线程模式。\n\n同时执行多个任务通常各个任务之间并不是没有关联的，而是需要相互通信和协调，有时，任务 1 必须暂停等待任务 2 完成后才能继续执行，有时，任务 3 和任务 4 又不能同时执行，所以，多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。\n\n因为复杂度高，调试困难，所以，不是迫不得已，我们也不想编写多任务。\n\n但是，有很多时候，没有多任务还真不行。\n\n想想在电脑上看电影，就必须由一个线程播放视频，另一个线程播放音频，否则，单线程实现的话就只能先把视频播放完再播放音频，或者先把音频播放完再播放视频，这显然是不行的。\n\n"
  },
  {
    "path": "Article/PythonBasis/python13/2.md",
    "content": "# 多线程编程 #\n\n其实创建线程之后，线程并不是始终保持一个状态的，其状态大概如下：\n\n* New 创建\n* Runnable 就绪。等待调度\n* Running 运行\n* Blocked 阻塞。阻塞可能在 Wait Locked Sleeping\n* Dead 消亡\n\n线程有着不同的状态，也有不同的类型。大致可分为：\n\n* 主线程\n* 子线程\n* 守护线程（后台线程）\n* 前台线程\n\n简单了解完这些之后，我们开始看看具体的代码使用了。\n\n## 1、线程的创建 ##\n\nPython 提供两个模块进行多线程的操作，分别是 `thread` 和 `threading`\n\n前者是比较低级的模块，用于更底层的操作，一般应用级别的开发不常用。\n\n因此，我们使用 `threading` 来举个例子：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport time\nimport threading\n\n\nclass MyThread(threading.Thread):\n    def run(self):\n        for i in range(5):\n            print('thread {}, @number: {}'.format(self.name, i))\n            time.sleep(1)\n\n\ndef main():\n    print(\"Start main threading\")\n\n    # 创建三个线程\n    threads = [MyThread() for i in range(3)]\n    # 启动三个线程\n    for t in threads:\n        t.start()\n\n    print(\"End Main threading\")\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\n运行结果：\n\n```txt\nStart main threading\nthread Thread-1, @number: 0\nthread Thread-2, @number: 0\nthread Thread-3, @number: 0\nEnd Main threading\nthread Thread-2, @number: 1\nthread Thread-1, @number: 1\nthread Thread-3, @number: 1\nthread Thread-1, @number: 2\nthread Thread-3, @number: 2\nthread Thread-2, @number: 2\nthread Thread-2, @number: 3\nthread Thread-3, @number: 3\nthread Thread-1, @number: 3\nthread Thread-3, @number: 4\nthread Thread-2, @number: 4\nthread Thread-1, @number: 4\n```\n\n注意喔，这里不同的环境输出的结果肯定是不一样的。\n\n## 2、线程合并（join方法） ##\n\n上面的示例打印出来的结果来看，主线程结束后，子线程还在运行。那么我们需要主线程要等待子线程运行完后，再退出，要怎么办呢？\n\n这时候，就需要用到 `join` 方法了。\n\n在上面的例子，新增一段代码，具体如下：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport time\nimport threading\n\n\nclass MyThread(threading.Thread):\n    def run(self):\n        for i in range(5):\n            print('thread {}, @number: {}'.format(self.name, i))\n            time.sleep(1)\n\n\ndef main():\n    print(\"Start main threading\")\n\n    # 创建三个线程\n    threads = [MyThread() for i in range(3)]\n    # 启动三个线程\n    for t in threads:\n        t.start()\n\n    # 一次让新创建的线程执行 join\n    for t in threads:\n        t.join()\n\n    print(\"End Main threading\")\n\n\nif __name__ == '__main__':\n    main()\n\n```\n\n\n从打印的结果，可以清楚看到，相比上面示例打印出来的结果，主线程是在等待子线程运行结束后才结束的。\n\n```txt\nStart main threading\nthread Thread-1, @number: 0\nthread Thread-2, @number: 0\nthread Thread-3, @number: 0\nthread Thread-1, @number: 1\nthread Thread-3, @number: 1\nthread Thread-2, @number: 1\nthread Thread-2, @number: 2\nthread Thread-1, @number: 2\nthread Thread-3, @number: 2\nthread Thread-2, @number: 3\nthread Thread-1, @number: 3\nthread Thread-3, @number: 3\nthread Thread-3, @number: 4\nthread Thread-2, @number: 4\nthread Thread-1, @number: 4\nEnd Main threading\n\n```\n## 3、线程同步与互斥锁 ##\n\n使用线程加载获取数据，通常都会造成数据不同步的情况。当然，这时候我们可以给资源进行加锁，也就是访问资源的线程需要获得锁才能访问。\n\n其中 `threading` 模块给我们提供了一个 Lock 功能。\n\n```python\nlock = threading.Lock()\n```\n\n在线程中获取锁\n\n```python\nlock.acquire()\n```\n\n使用完成后，我们肯定需要释放锁\n\n```python\nlock.release()\n```\n\n当然为了支持在同一线程中多次请求同一资源，Python 提供了可重入锁（RLock）。RLock 内部维护着一个 Lock 和一个 counter 变量，counter 记录了 acquire 的次数，从而使得资源可以被多次 require。直到一个线程所有的 acquire 都被 release，其他的线程才能获得资源。\n\n那么怎么创建重入锁呢？也是一句代码的事情：\n\n```python\nr_lock = threading.RLock()\n```\n\n## 4、Condition 条件变量 ##\n\n实用锁可以达到线程同步，但是在更复杂的环境，需要针对锁进行一些条件判断。\n\nPython 提供了 Condition 对象。\n\n**使用 Condition 对象可以在某些事件触发或者达到特定的条件后才处理数据，Condition 除了具有 Lock 对象的 acquire 方法和 release 方法外，还提供了 wait 和 notify 方法。**\n\n线程首先 acquire 一个条件变量锁。如果条件不足，则该线程 wait，如果满足就执行线程，甚至可以 notify 其他线程。其他处于 wait 状态的线程接到通知后会重新判断条件。\n\n其中条件变量可以看成不同的线程先后 acquire 获得锁，如果不满足条件，可以理解为被扔到一个（ Lock 或 RLock ）的 waiting 池。直到其他线程 notify 之后再重新判断条件。不断的重复这一过程，从而解决复杂的同步问题。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Condition.png)\n\n该模式常用于生产者消费者模式，具体看看下面在线购物买家和卖家的示例：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport threading, time\n\n\nclass Consumer(threading.Thread):\n    def __init__(self, cond, name):\n        # 初始化\n        super(Consumer, self).__init__()\n        self.cond = cond\n        self.name = name\n\n    def run(self):\n        # 确保先运行Seeker中的方法\n        time.sleep(1)\n        self.cond.acquire()\n        print(self.name + ': 我这两件商品一起买，可以便宜点吗')\n        self.cond.notify()\n        self.cond.wait()\n        print(self.name + ': 我已经提交订单了，你修改下价格')\n        self.cond.notify()\n        self.cond.wait()\n        print(self.name + ': 收到，我支付成功了')\n        self.cond.notify()\n        self.cond.release()\n        print(self.name + ': 等待收货')\n\n\nclass Producer(threading.Thread):\n    def __init__(self, cond, name):\n        super(Producer, self).__init__()\n        self.cond = cond\n        self.name = name\n\n    def run(self):\n        self.cond.acquire()\n        # 释放对琐的占用，同时线程挂起在这里，直到被 notify 并重新占有琐。\n        self.cond.wait()\n        print(self.name + ': 可以的，你提交订单吧')\n        self.cond.notify()\n        self.cond.wait()\n        print(self.name + ': 好了，已经修改了')\n        self.cond.notify()\n        self.cond.wait()\n        print(self.name + ': 嗯，收款成功，马上给你发货')\n        self.cond.release()\n        print(self.name + ': 发货商品')\n\n\ncond = threading.Condition()\nconsumer = Consumer(cond, '买家（两点水）')\nproducer = Producer(cond, '卖家（三点水）')\nconsumer.start()\nproducer.start()\n\n```\n\n输出的结果如下：\n\n```txt\n买家（两点水）: 我这两件商品一起买，可以便宜点吗\n卖家（三点水）: 可以的，你提交订单吧\n买家（两点水）: 我已经提交订单了，你修改下价格\n卖家（三点水）: 好了，已经修改了\n买家（两点水）: 收到，我支付成功了\n买家（两点水）: 等待收货\n卖家（三点水）: 嗯，收款成功，马上给你发货\n卖家（三点水）: 发货商品\n```\n\n## 5、线程间通信 ##\n\n如果程序中有多个线程，这些线程避免不了需要相互通信的。那么我们怎样在这些线程之间安全地交换信息或数据呢？\n\n从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 `Queue` 对象，这些线程通过使用 `put()` 和 `get()` 操作来向队列中添加或者删除元素。\n\n```python\n# -*- coding: UTF-8 -*-\nfrom queue import Queue\nfrom threading import Thread\n\nisRead = True\n\n\ndef write(q):\n    # 写数据进程\n    for value in ['两点水', '三点水', '四点水']:\n        print('写进 Queue 的值为：{0}'.format(value))\n        q.put(value)\n\n\ndef read(q):\n    # 读取数据进程\n    while isRead:\n        value = q.get(True)\n        print('从 Queue 读取的值为：{0}'.format(value))\n\n\nif __name__ == '__main__':\n    q = Queue()\n    t1 = Thread(target=write, args=(q,))\n    t2 = Thread(target=read, args=(q,))\n    t1.start()\n    t2.start()\n```\n\n输出的结果如下：\n\n```txt\n写进 Queue 的值为：两点水\n写进 Queue 的值为：三点水\n从 Queue 读取的值为：两点水\n写进 Queue 的值为：四点水\n从 Queue 读取的值为：三点水\n从 Queue 读取的值为：四点水\n```\n\nPython 还提供了 Event 对象用于线程间通信，它是由线程设置的信号标志，如果信号标志位真，则其他线程等待直到信号接触。\n\nEvent 对象实现了简单的线程通信机制，它提供了设置信号，清楚信号，等待等用于实现线程间的通信。\n\n* 设置信号\n\n使用 Event 的 `set()` 方法可以设置 Event 对象内部的信号标志为真。Event 对象提供了 `isSe()` 方法来判断其内部信号标志的状态。当使用 event 对象的 `set()` 方法后，`isSet()` 方法返回真\n\n* 清除信号\n\n使用 Event 对象的 `clear()` 方法可以清除 Event 对象内部的信号标志，即将其设为假，当使用 Event 的 clear 方法后，isSet() 方法返回假\n\n*  等待\n\nEvent 对象 wait 的方法只有在内部信号为真的时候才会很快的执行并完成返回。当 Event 对象的内部信号标志位假时，则 wait 方法一直等待到其为真时才返回。\n\n示例：\n\n```python\n# -*- coding: UTF-8 -*-\n\nimport threading\n\n\nclass mThread(threading.Thread):\n    def __init__(self, threadname):\n        threading.Thread.__init__(self, name=threadname)\n\n    def run(self):\n        # 使用全局Event对象\n        global event\n        # 判断Event对象内部信号标志\n        if event.isSet():\n            event.clear()\n            event.wait()\n            print(self.getName())\n        else:\n            print(self.getName())\n            # 设置Event对象内部信号标志\n            event.set()\n\n# 生成Event对象\nevent = threading.Event()\n# 设置Event对象内部信号标志\nevent.set()\nt1 = []\nfor i in range(10):\n    t = mThread(str(i))\n    # 生成线程列表\n    t1.append(t)\n\nfor i in t1:\n    # 运行线程\n    i.start()\n\n```\n\n\n输出的结果如下：\n\n```txt\n1\n0\n3\n2\n5\n4\n7\n6\n9\n8\n```\n\n\n## 6、后台线程 ##\n\n默认情况下，主线程退出之后，即使子线程没有 join。那么主线程结束后，子线程也依然会继续执行。如果希望主线程退出后，其子线程也退出而不再执行，则需要设置子线程为后台线程。Python 提供了 `setDeamon` 方法。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python13/3.md",
    "content": "# 进程 #\n\nPython 中的多线程其实并不是真正的多线程，如果想要充分地使用多核 CPU 的资源，在 Python 中大部分情况需要使用多进程。\n\nPython 提供了非常好用的多进程包 multiprocessing，只需要定义一个函数，Python 会完成其他所有事情。\n\n借助这个包，可以轻松完成从单进程到并发执行的转换。multiprocessing 支持子进程、通信和共享数据、执行不同形式的同步，提供了 Process、Queue、Pipe、Lock 等组件。\n\n\n## 1、类 Process ##\n\n创建进程的类：`Process([group [, target [, name [, args [, kwargs]]]]])`\n\n* target 表示调用对象\n* args 表示调用对象的位置参数元组\n* kwargs表示调用对象的字典\n* name为别名\n* group实质上不使用\n\n下面看一个创建函数并将其作为多个进程的例子：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport multiprocessing\nimport time\n\n\ndef worker(interval, name):\n    print(name + '【start】')\n    time.sleep(interval)\n    print(name + '【end】')\n\n\nif __name__ == \"__main__\":\n    p1 = multiprocessing.Process(target=worker, args=(2, '两点水1'))\n    p2 = multiprocessing.Process(target=worker, args=(3, '两点水2'))\n    p3 = multiprocessing.Process(target=worker, args=(4, '两点水3'))\n\n    p1.start()\n    p2.start()\n    p3.start()\n\n    print(\"The number of CPU is:\" + str(multiprocessing.cpu_count()))\n    for p in multiprocessing.active_children():\n        print(\"child   p.name:\" + p.name + \"\\tp.id\" + str(p.pid))\n    print(\"END!!!!!!!!!!!!!!!!!\")\n\n```\n\n输出的结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E5%A4%9A%E8%BF%9B%E7%A8%8B%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.gif)\n\n\n## 2、把进程创建成类 ##\n\n当然我们也可以把进程创建成一个类，如下面的例子，当进程 p 调用 start() 时，自动调用 run() 方法。\n\n\n```python\n# -*- coding: UTF-8 -*-\n\nimport multiprocessing\nimport time\n\n\nclass ClockProcess(multiprocessing.Process):\n    def __init__(self, interval):\n        multiprocessing.Process.__init__(self)\n        self.interval = interval\n\n    def run(self):\n        n = 5\n        while n > 0:\n            print(\"当前时间: {0}\".format(time.ctime()))\n            time.sleep(self.interval)\n            n -= 1\n\n\nif __name__ == '__main__':\n    p = ClockProcess(3)\n    p.start()\n\n```\n\n输出结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E5%88%9B%E5%BB%BA%E8%BF%9B%E7%A8%8B%E7%B1%BB.gif)\n\n## 3、daemon 属性 ##\n\n想知道 daemon 属性有什么用，看下下面两个例子吧，一个加了 daemon 属性，一个没有加，对比输出的结果：\n\n没有加 deamon 属性的例子：\n\n```python\n# -*- coding: UTF-8 -*-\nimport multiprocessing\nimport time\n\n\ndef worker(interval):\n    print('工作开始时间：{0}'.format(time.ctime()))\n    time.sleep(interval)\n    print('工作结果时间：{0}'.format(time.ctime()))\n\n\nif __name__ == '__main__':\n    p = multiprocessing.Process(target=worker, args=(3,))\n    p.start()\n    print('【EMD】')\n\n```\n\n输出结果：\n\n```txt\n【EMD】\n工作开始时间：Mon Oct  9 17:47:06 2017\n工作结果时间：Mon Oct  9 17:47:09 2017\n```\n\n在上面示例中，进程 p 添加 daemon 属性：\n\n```python\n# -*- coding: UTF-8 -*-\n\nimport multiprocessing\nimport time\n\n\ndef worker(interval):\n    print('工作开始时间：{0}'.format(time.ctime()))\n    time.sleep(interval)\n    print('工作结果时间：{0}'.format(time.ctime()))\n\n\nif __name__ == '__main__':\n    p = multiprocessing.Process(target=worker, args=(3,))\n    p.daemon = True\n    p.start()\n    print('【EMD】')\n```\n\n输出结果：\n\n```txt\n【EMD】\n```\n\n\n根据输出结果可见，如果在子进程中添加了 daemon 属性，那么当主进程结束的时候，子进程也会跟着结束。所以没有打印子进程的信息。\n\n\n## 4、join 方法 ##\n\n结合上面的例子继续，如果我们想要让子线程执行完该怎么做呢？\n\n那么我们可以用到 join 方法，join 方法的主要作用是：阻塞当前进程，直到调用 join 方法的那个进程执行完，再继续执行当前进程。\n\n因此看下加了 join 方法的例子：\n\n```python\nimport multiprocessing\nimport time\n\n\ndef worker(interval):\n    print('工作开始时间：{0}'.format(time.ctime()))\n    time.sleep(interval)\n    print('工作结果时间：{0}'.format(time.ctime()))\n\n\nif __name__ == '__main__':\n    p = multiprocessing.Process(target=worker, args=(3,))\n    p.daemon = True\n    p.start()\n    p.join()\n    print('【EMD】')\n```\n\n输出的结果：\n\n```txt\n工作开始时间：Tue Oct 10 11:30:08 2017\n工作结果时间：Tue Oct 10 11:30:11 2017\n【EMD】\n```\n\n## 5、Pool ##\n\n如果需要很多的子进程，难道我们需要一个一个的去创建吗？\n\n当然不用，我们可以使用进程池的方法批量创建子进程。\n\n例子如下：\n\n```python\n# -*- coding: UTF-8 -*-\n\nfrom multiprocessing import Pool\nimport os, time, random\n\n\ndef long_time_task(name):\n    print('进程的名称：{0} ；进程的PID: {1} '.format(name, os.getpid()))\n    start = time.time()\n    time.sleep(random.random() * 3)\n    end = time.time()\n    print('进程 {0} 运行了 {1} 秒'.format(name, (end - start)))\n\n\nif __name__ == '__main__':\n    print('主进程的 PID：{0}'.format(os.getpid()))\n    p = Pool(4)\n    for i in range(6):\n        p.apply_async(long_time_task, args=(i,))\n    p.close()\n    # 等待所有子进程结束后在关闭主进程\n    p.join()\n    print('【End】')\n```\n\n输出的结果如下：\n\n```txt\n主进程的 PID：7256\n进程的名称：0 ；进程的PID: 1492\n进程的名称：1 ；进程的PID: 12232\n进程的名称：2 ；进程的PID: 4332\n进程的名称：3 ；进程的PID: 11604\n进程 2 运行了 0.6500370502471924 秒\n进程的名称：4 ；进程的PID: 4332\n进程 1 运行了 1.0830621719360352 秒\n进程的名称：5 ；进程的PID: 12232\n进程 5 运行了 0.029001712799072266 秒\n进程 4 运行了 0.9720554351806641 秒\n进程 0 运行了 2.3181326389312744 秒\n进程 3 运行了 2.5331451892852783 秒\n【End】\n```\n\n这里有一点需要注意： `Pool` 对象调用 `join()` 方法会等待所有子进程执行完毕，调用 `join()` 之前必须先调用 `close()` ，调用`close()` 之后就不能继续添加新的 Process 了。\n\n请注意输出的结果，子进程 0，1，2，3是立刻执行的，而子进程 4 要等待前面某个子进程完成后才执行，这是因为 Pool 的默认大小在我的电脑上是 4，因此，最多同时执行 4 个进程。这是 Pool 有意设计的限制，并不是操作系统的限制。如果改成：\n\n```python\np = Pool(5)\n```\n\n就可以同时跑 5 个进程。\n\n\n\n## 6、进程间通信 ##\n\nProcess 之间肯定是需要通信的，操作系统提供了很多机制来实现进程间的通信。Python 的 multiprocessing 模块包装了底层的机制，提供了Queue、Pipes 等多种方式来交换数据。\n\n以 Queue 为例，在父进程中创建两个子进程，一个往 Queue 里写数据，一个从 Queue 里读数据：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nfrom multiprocessing import Process, Queue\nimport os, time, random\n\n\ndef write(q):\n    # 写数据进程\n    print('写进程的PID:{0}'.format(os.getpid()))\n    for value in ['两点水', '三点水', '四点水']:\n        print('写进 Queue 的值为：{0}'.format(value))\n        q.put(value)\n        time.sleep(random.random())\n\n\ndef read(q):\n    # 读取数据进程\n    print('读进程的PID:{0}'.format(os.getpid()))\n    while True:\n        value = q.get(True)\n        print('从 Queue 读取的值为：{0}'.format(value))\n\n\nif __name__ == '__main__':\n    # 父进程创建 Queue，并传给各个子进程\n    q = Queue()\n    pw = Process(target=write, args=(q,))\n    pr = Process(target=read, args=(q,))\n    # 启动子进程 pw\n    pw.start()\n    # 启动子进程pr\n    pr.start()\n    # 等待pw结束:\n    pw.join()\n    # pr 进程里是死循环，无法等待其结束，只能强行终止\n    pr.terminate()\n\n```\n\n输出的结果为：\n\n```txt\n读进程的PID:13208\n写进程的PID:10864\n写进 Queue 的值为：两点水\n从 Queue 读取的值为：两点水\n写进 Queue 的值为：三点水\n从 Queue 读取的值为：三点水\n写进 Queue 的值为：四点水\n从 Queue 读取的值为：四点水\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python13/Preface.md",
    "content": "# 前言 #\n\n学编程，谁没有为线程折腾过啊。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E8%8D%89%E6%A0%B9%E5%AD%A6Python%EF%BC%88%E5%8D%81%E4%B8%89%EF%BC%89%20%E7%BA%BF%E7%A8%8B%E5%92%8C%E8%BF%9B%E7%A8%8B.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python14/1.md",
    "content": "# 初识 Python 正则表达式\n\n正则表达式是一个特殊的字符序列，用于判断一个字符串是否与我们所设定的字符序列是否匹配，也就是说检查一个字符串是否与某种模式匹配。\n\nPython 自 1.5 版本起增加了re 模块，它提供 Perl 风格的正则表达式模式。re 模块使 Python 语言拥有全部的正则表达式功能。\n\n下面通过实例，一步一步来初步认识正则表达式。\n\n比如在一段字符串中寻找是否含有某个字符或某些字符，通常我们使用内置函数来实现，如下：\n\n```python\n# 设定一个常量\na = '两点水|twowater|liangdianshui|草根程序员|ReadingWithU'\n\n# 判断是否有 “两点水” 这个字符串，使用 PY 自带函数\n\nprint('是否含有“两点水”这个字符串：{0}'.format(a.index('两点水') > -1))\nprint('是否含有“两点水”这个字符串：{0}'.format('两点水' in a))\n```\n\n输出的结果如下：\n\n```txt\n是否含有“两点水”这个字符串：True\n是否含有“两点水”这个字符串：True\n```\n\n那么，如果使用正则表达式呢？\n\n刚刚提到过，Python 给我们提供了 re 模块来实现正则表达式的所有功能，那么我们先使用其中的一个函数：\n\n```python\nre.findall(pattern, string[, flags])\n```\n\n该函数实现了在字符串中找到正则表达式所匹配的所有子串，并组成一个列表返回,具体操作如下：\n\n```python\n\nimport re\n\n# 设定一个常量\na = '两点水|twowater|liangdianshui|草根程序员|ReadingWithU'\n\n# 正则表达式\n\nfindall = re.findall('两点水', a)\nprint(findall)\n\nif len(findall) > 0:\n    print('a 含有“两点水”这个字符串')\nelse:\n    print('a 不含有“两点水”这个字符串')\n\n```\n\n输出的结果：\n\n```txt\n['两点水']\na 含有“两点水”这个字符串\n```\n\n从输出结果可以看到，可以实现和内置函数一样的功能，可是在这里也要强调一点，上面这个例子只是方便我们理解正则表达式，这个正则表达式的写法是毫无意义的。为什么这样说呢？\n\n因为用 Python 自带函数就能解决的问题，我们就没必要使用正则表达式了，这样做多此一举。而且上面例子中的正则表达式设置成为了一个常量，并不是一个正则表达式的规则，正则表达式的灵魂在于规则，所以这样做意义不大。\n\n那么正则表达式的规则怎么写呢？先不急，我们一步一步来，先来一个简单的，找出字符串中的所有小写字母。首先我们在 `findall` 函数中第一个参数写正则表达式的规则，其中\t`[a-z]` 就是匹配任何小写字母，第二个参数只要填写要匹配的字符串就行了。具体如下：\n\n```python\n\nimport re\n\n# 设定一个常量\na = '两点水|twowater|liangdianshui|草根程序员|ReadingWithU'\n\n# 选择 a 里面的所有小写英文字母\n\nre_findall = re.findall('[a-z]', a)\n\nprint(re_findall)\n\n```\n\n输出的结果：\n\n```txt\n['t', 'w', 'o', 'w', 'a', 't', 'e', 'r', 'l', 'i', 'a', 'n', 'g', 'd', 'i', 'a', 'n', 's', 'h', 'u', 'i', 'e', 'a', 'd', 'i', 'n', 'g', 'i', 't', 'h']\n```\n\n这样我们就拿到了字符串中的所有小写字母了。\n"
  },
  {
    "path": "Article/PythonBasis/python14/2.md",
    "content": "# 字符集\n\n\n好了，通过上面的几个实例我们初步认识了 Python 的正则表达式，可能你就会问，正则表达式还有什么规则，什么字母代表什么意思呢？\n\n其实，这些都不急，在本章后面会给出对应的正则表达式规则列表，而且这些东西在网上随便都能 Google 到。所以现在，我们还是进一步加深对正则表达式的理解，讲一下正则表达式的字符集。\n\n字符集是由一对方括号 “[]” 括起来的字符集合。使用字符集，可以匹配多个字符中的一个。\n\n举个例子，比如你使用 `C[ET]O` 匹配到的是 CEO 或 CTO ，也就是说 `[ET]` 代表的是一个 E 或者一个 T 。像上面提到的 `[a-z]` ,就是所有小写字母中的其中一个，这里使用了连字符 “-” 定义一个连续字符的字符范围。当然，像这种写法，里面可以包含多个字符范围的，比如：`[0-9a-fA-F]` ,匹配单个的十六进制数字，且不分大小写。注意了，字符和范围定义的先后顺序对匹配的结果是没有任何影响的。\n\n其实说了那么多，只是想证明，字符集一对方括号 “[]” 里面的字符关系是\"或（OR）\"关系，下面看一个例子：\n\n```Python\n\nimport re\na = 'uav,ubv,ucv,uwv,uzv,ucv,uov'\n\n# 字符集\n\n# 取 u 和 v 中间是 a 或 b 或 c 的字符\nfindall = re.findall('u[abc]v', a)\nprint(findall)\n# 如果是连续的字母，数字可以使用 - 来代替\nl = re.findall('u[a-c]v', a)\nprint(l)\n\n# 取 u 和 v 中间不是 a 或 b 或 c 的字符\nre_findall = re.findall('u[^abc]v', a)\nprint(re_findall)\n\n```\n\n输出的结果：\n\n```txt\n['uav', 'ubv', 'ucv', 'ucv']\n['uav', 'ubv', 'ucv', 'ucv']\n['uwv', 'uzv', 'uov']\n```\n\n在例子中，使用了取反字符集，也就是在左方括号 “[” 后面紧跟一个尖括号 “^”，就会对字符集取反。需要记住的一点是，取反字符集必须要匹配一个字符。比如：`q[^u]` 并不意味着：匹配一个 q，后面没有 u 跟着。它意味着：匹配一个 q，后面跟着一个不是 u 的字符。具体可以对比上面例子中输出的结果来理解。\n\n我们都知道，正则表达式本身就定义了一些规则，比如 `\\d`,匹配所有数字字符,其实它是等价于 [0-9]，下面也写了个例子，通过字符集的形式解释了这些特殊字符。\n\n```Python\nimport re\n\na = 'uav_ubv_ucv_uwv_uzv_ucv_uov&123-456-789'\n\n# 概括字符集\n\n# \\d 相当于 [0-9] ,匹配所有数字字符\n# \\D 相当于 [^0-9] ， 匹配所有非数字字符\nfindall1 = re.findall('\\d', a)\nfindall2 = re.findall('[0-9]', a)\nfindall3 = re.findall('\\D', a)\nfindall4 = re.findall('[^0-9]', a)\nprint(findall1)\nprint(findall2)\nprint(findall3)\nprint(findall4)\n\n# \\w 匹配包括下划线的任何单词字符，等价于 [A-Za-z0-9_]\nfindall5 = re.findall('\\w', a)\nfindall6 = re.findall('[A-Za-z0-9_]', a)\nprint(findall5)\nprint(findall6)\n\n```\n\n输出结果：\n\n```txt\n['1', '2', '3', '4', '5', '6', '7', '8', '9']\n['1', '2', '3', '4', '5', '6', '7', '8', '9']\n['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']\n['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']\n['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']\n```\n"
  },
  {
    "path": "Article/PythonBasis/python14/3.md",
    "content": "# 数量词\n\n来，继续加深对正则表达式的理解，这部分理解一下数量词，为什么要用数量词，想想都知道，如果你要匹配几十上百的字符时，难道你要一个一个的写，所以就出现了数量词。\n\n数量词的词法是：{min,max} 。min 和 max 都是非负整数。如果逗号有而 max 被忽略了，则 max 没有限制。如果逗号和 max 都被忽略了，则重复 min 次。比如，`\\b[1-9][0-9]{3}\\b`,匹配的是 1000 ~ 9999 之间的数字( “\\b” 表示单词边界），而 `\\b[1-9][0-9]{2,4}\\b`，匹配的是一个在 100 ~ 99999 之间的数字。\n\n下面看一个实例，匹配出字符串中 4 到 7 个字母的英文\n\n```Python\nimport re\n\na = 'java*&39android##@@python'\n\n# 数量词\n\nfindall = re.findall('[a-z]{4,7}', a)\nprint(findall)\n```\n\n输出结果：\n\n```txt\n['java', 'android', 'python']\n```\n\n注意，这里有贪婪和非贪婪之分。那么我们先看下相关的概念：\n\n\n贪婪模式：它的特性是一次性地读入整个字符串，如果不匹配就吐掉最右边的一个字符再匹配，直到找到匹配的字符串或字符串的长度为 0 为止。它的宗旨是读尽可能多的字符，所以当读到第一个匹配时就立刻返回。\n\n懒惰模式：它的特性是从字符串的左边开始，试图不读入字符串中的字符进行匹配，失败，则多读一个字符，再匹配，如此循环，当找到一个匹配时会返回该匹配的字符串，然后再次进行匹配直到字符串结束。\n\n上面例子中的就是贪婪的，如果要使用非贪婪，也就是懒惰模式，怎么呢？\n\n如果要使用非贪婪，则加一个 `?` ，上面的例子修改如下：\n\n```Python\nimport re\n\na = 'java*&39android##@@python'\n\n# 贪婪与非贪婪\n\nre_findall = re.findall('[a-z]{4,7}?', a)\nprint(re_findall)\n\n```\n\n输出结果如下：\n\n```txt\n['java', 'andr', 'pyth']\n```\n\n从输出的结果可以看出，android 只打印除了 andr ，Python  只打印除了 pyth ，因为这里使用的是懒惰模式。\n\n当然，还有一些特殊字符也是可以表示数量的，比如：\n\n\n> `?`：告诉引擎匹配前导字符 0 次或 1 次\n>\n> `+`：告诉引擎匹配前导字符 1 次或多次\n>\n> `*`：告诉引擎匹配前导字符 0 次或多次\n\n\n把这部分的知识点总结一下,就是下面这个表了:\n\n| 贪   婪 | 惰   性 | 描   述                       |\n| ------- | ------- | ----------------------------- |\n| ？      | ？？    | 零次或一次出现，等价于{0,1}   |\n| +       | +？     | 一次或多次出现 ，等价于{1,}   |\n| *       | *？     | 零次或多次出现   ，等价于{0,} |\n| {n}     | {n}？   | 恰好 n 次出现                 |\n| {n,m}   | {n,m}？ | 至少 n 次枝多 m 次出现        |\n| {n,}    | {n,}？  | 至少 n 次出现                 |\n"
  },
  {
    "path": "Article/PythonBasis/python14/4.md",
    "content": "# 边界匹配符和组\n\n将上面几个点，就用了很大的篇幅了，现在介绍一些边界匹配符和组的概念。\n\n一般的边界匹配符有以下几个：\n\n| 语法 | 描述                                             |\n| ---- | ------------------------------------------------ |\n| ^    | 匹配字符串开头（在有多行的情况中匹配每行的开头） |\n| $    | 匹配字符串的末尾(在有多行的情况中匹配每行的末尾) |\n| \\A   | 仅匹配字符串开头                                 |\n| \\Z   | 仅匹配字符串末尾                                 |\n| \\b   | 匹配 \\w 和 \\W 之间                               |\n| \\B   | [^\\b]                                            |\n\n分组，被括号括起来的表达式就是分组。分组表达式 `(...)` 其实就是把这部分字符作为一个整体，当然，可以有多分组的情况，每遇到一个分组，编号就会加 1 ，而且分组后面也是可以加数量词的。\n\n此处本应有例子，考虑到篇幅问题，就不贴了\n"
  },
  {
    "path": "Article/PythonBasis/python14/5.md",
    "content": "# re.sub\n\n实战过程中，我们很多时候需要替换字符串中的字符，这时候就可以用到 `def sub(pattern, repl, string, count=0, flags=0)  ` 函数了，re.sub 共有五个参数。其中三个必选参数：pattern, repl, string ; 两个可选参数：count, flags .\n\n具体参数意义如下：\n\n| 参数    | 描述                                                          |\n| ------- | ------------------------------------------------------------- |\n| pattern | 表示正则中的模式字符串                                        |\n| repl    | repl，就是replacement，被替换的字符串的意思                   |\n| string  | 即表示要被处理，要被替换的那个 string 字符串                  |\n| count   | 对于pattern中匹配到的结果，count可以控制对前几个group进行替换 |\n| flags   | 正则表达式修饰符                                              |\n\n具体使用可以看下下面的这个实例，注释都写的很清楚的了，主要是注意一下，第二个参数是可以传递一个函数的，这也是这个方法的强大之处，例如例子里面的函数 `convert` ,对传递进来要替换的字符进行判断，替换成不同的字符。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport re\n\na = 'Python*Android*Java-888'\n\n# 把字符串中的 * 字符替换成 & 字符\nsub1 = re.sub('\\*', '&', a)\nprint(sub1)\n\n# 把字符串中的第一个 * 字符替换成 & 字符\nsub2 = re.sub('\\*', '&', a, 1)\nprint(sub2)\n\n\n# 把字符串中的 * 字符替换成 & 字符,把字符 - 换成 |\n\n# 1、先定义一个函数\ndef convert(value):\n    group = value.group()\n    if (group == '*'):\n        return '&'\n    elif (group == '-'):\n        return '|'\n\n\n# 第二个参数，要替换的字符可以为一个函数\nsub3 = re.sub('[\\*-]', convert, a)\nprint(sub3)\n```\n\n输出的结果：\n\n```txt\nPython&Android&Java-888\nPython&Android*Java-888\nPython&Android&Java|888\n```\n"
  },
  {
    "path": "Article/PythonBasis/python14/6.md",
    "content": "# re.match  和 re.search\n\n**re.match 函数**\n\n语法：\n\n```python\nre.match(pattern, string, flags=0)\n```\n\nre.match 尝试从字符串的起始位置匹配一个模式，如果不是起始位置匹配成功的话，match() 就返回 none。\n\n\n**re.search 函数**\n\n语法：\n\n```Python\nre.search(pattern, string, flags=0)\n```\n\nre.search 扫描整个字符串并返回第一个成功的匹配。\n\nre.match 和 re.search 的参数，基本一致的，具体描述如下：\n\n| 参数    | 描述                                                     |\n| ------- | -------------------------------------------------------- |\n| pattern | 匹配的正则表达式                                         |\n| string  | 要匹配的字符串                                           |\n| flags   | 标志位，用于控制正则表达式的匹配方式，如：是否区分大小写 |\n\n那么它们之间有什么区别呢？\n\nre.match 只匹配字符串的开始，如果字符串开始不符合正则表达式，则匹配失败，函数返回 None；而 re.search 匹配整个字符串，直到找到一个匹配。这就是它们之间的区别了。\n\nre.match 和 re.search 在网上有很多详细的介绍了，可是再个人的使用中，还是喜欢使用  re.findall\n\n看下下面的实例，可以对比下 re.search 和 re.findall 的区别，还有多分组的使用。具体看下注释，对比一下输出的结果：  \n\n示例：\n\n```python\n\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\n# 提取图片的地址\n\nimport re\n\na = '<img src=\"https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg\">'\n\n# 使用 re.search\nsearch = re.search('<img src=\"(.*)\">', a)\n# group(0) 是一个完整的分组\nprint(search.group(0))\nprint(search.group(1))\n\n# 使用 re.findall\nfindall = re.findall('<img src=\"(.*)\">', a)\nprint(findall)\n\n# 多个分组的使用（比如我们需要提取 img 字段和图片地址字段）\nre_search = re.search('<(.*) src=\"(.*)\">', a)\n# 打印 img\nprint(re_search.group(1))\n# 打印图片地址\nprint(re_search.group(2))\n# 打印 img 和图片地址，以元祖的形式\nprint(re_search.group(1, 2))\n# 或者使用 groups\nprint(re_search.groups())\n\n```\n\n输出的结果：\n\n```txt\n<img src=\"https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg\">\nhttps://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg\n['https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg']\nimg\nhttps://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg\n('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')\n('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')\n```\n\n\n最后，正则表达式是非常厉害的工具，通常可以用来解决字符串内置函数无法解决的问题，而且正则表达式大部分语言都是有的。python 的用途很多，但在爬虫和数据分析这连个模块中都是离不开正则表达式的。所以正则表达式对于学习 Python 来说，真的很重要。最后，附送一些常用的正则表达式和正则表达式和 Python 支持的正则表达式元字符和语法文档。\n\ngithub：https://github.com/TwoWater/Python/blob/master/python14/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md\n\n欢迎大家 start ，https://github.com/TwoWater/Python 一下，这是草根学 Python 系列博客的库。也可以关注我的微信公众号：\n\n![http://twowater.com.cn/images/20171204192251900.gif](http://twowater.com.cn/images/20171204192251900.gif)\n"
  },
  {
    "path": "Article/PythonBasis/python14/Preface.md",
    "content": "# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E4%BA%86%E8%A7%A3%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python14/常用的正则表达式.md",
    "content": "\n#\n\n# 常用的正则表达式\n\n## 校验数字的表达式\n\n```\n1、  数字：^[0-9]*$\n2、  n位的数字：^\\d{n}$\n3、  至少n位的数字：^\\d{n,}$\n4、  m-n位的数字：^\\d{m,n}$\n5、  零和非零开头的数字：^(0|[1-9][0-9]*)$\n6、  非零开头的最多带两位小数的数字：^([1-9][0-9]*)+(.[0-9]{1,2})?$\n7、  带1-2位小数的正数或负数：^(\\-)?\\d+(\\.\\d{1,2})?$\n8、  正数、负数、和小数：^(\\-|\\+)?\\d+(\\.\\d+)?$\n9、  有两位小数的正实数：^[0-9]+(.[0-9]{2})?$\n10、 有1~3位小数的正实数：^[0-9]+(.[0-9]{1,3})?$\n11、 非零的正整数：^[1-9]\\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\\+?[1-9][0-9]*$\n12、 非零的负整数：^\\-[1-9][]0-9\"*$ 或 ^-[1-9]\\d*$\n13、 非负整数：^\\d+$ 或 ^[1-9]\\d*|0$\n14、 非正整数：^-[1-9]\\d*|0$ 或 ^((-\\d+)|(0+))$\n15、 非负浮点数：^\\d+(\\.\\d+)?$ 或 ^[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0$\n16、 非正浮点数：^((-\\d+(\\.\\d+)?)|(0+(\\.0+)?))$ 或 ^(-([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*))|0?\\.0+|0$\n17、 正浮点数：^[1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*$ 或 ^(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*))$\n18、 负浮点数：^-([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*)$ 或 ^(-(([0-9]+\\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\\.[0-9]+)|([0-9]*[1-9][0-9]*)))$\n19、 浮点数：^(-?\\d+)(\\.\\d+)?$ 或 ^-?([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0)$\n```\n\n\n## 校验字符的表达式\n\n```\n1、  汉字：^[\\u4e00-\\u9fa5]{0,}$\n2、  英文和数字：^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$\n3、  长度为3-20的所有字符：^.{3,20}$\n4、  由26个英文字母组成的字符串：^[A-Za-z]+$\n5、  由26个大写英文字母组成的字符串：^[A-Z]+$\n6、  由26个小写英文字母组成的字符串：^[a-z]+$\n7、  由数字和26个英文字母组成的字符串：^[A-Za-z0-9]+$\n8、  由数字、26个英文字母或者下划线组成的字符串：^\\w+$ 或 ^\\w{3,20}$\n9、  中文、英文、数字包括下划线：^[\\u4E00-\\u9FA5A-Za-z0-9_]+$\n10、 中文、英文、数字但不包括下划线等符号：^[\\u4E00-\\u9FA5A-Za-z0-9]+$ 或 ^[\\u4E00-\\u9FA5A-Za-z0-9]{2,20}$\n11、 可以输入含有^%&',;=?$\\\"等字符：[^%&',;=?$\\x22]+\n12、 禁止输入含有~的字符：[^~\\x22]+\n```\n\n## 特殊需求表达式\n\n```\n1、  Email地址：^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$\n2、  域名：[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?\n3、  InternetURL：[a-zA-z]+://[^\\s]* 或 ^http://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$\n4、  手机号码：^(13[0-9]|14[0-9]|15[0-9]|166|17[0-9]|18[0-9]|19[8|9])\\d{8}$\n5、  电话号码(\"XXX-XXXXXXX\"、\"XXXX-XXXXXXXX\"、\"XXX-XXXXXXX\"、\"XXX-XXXXXXXX\"、\"XXXXXXX\"和\"XXXXXXXX)：^(\\(\\d{3,4}-)|\\d{3.4}-)?\\d{7,8}$\n6、  国内电话号码(0511-4405222、021-87888822)：\\d{3}-\\d{8}|\\d{4}-\\d{7}\n7、  18位身份证号码(数字、字母x结尾)：^((\\d{18})|([0-9x]{18})|([0-9X]{18}))$\n8、  帐号是否合法(字母开头，允许5-16字节，允许字母数字下划线)：^[a-zA-Z][a-zA-Z0-9_]{4,15}$\n9、  密码(以字母开头，长度在6~18之间，只能包含字母、数字和下划线)：^[a-zA-Z]\\w{5,17}$\n10、 强密码(必须包含大小写字母和数字的组合，不能使用特殊字符，长度在8-10之间)：^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$  \n11、 日期格式：^\\d{4}-\\d{1,2}-\\d{1,2}\n12、 一年的12个月(01～09和1～12)：^(0?[1-9]|1[0-2])$\n13、 一个月的31天(01～09和1～31)：^((0?[1-9])|((1|2)[0-9])|30|31)$\n14、 钱的输入格式：\n      a.有四种钱的表示形式我们可以接受:\"10000.00\" 和 \"10,000.00\", 和没有 \"分\" 的 \"10000\" 和 \"10,000\"：^[1-9][0-9]*$\n      b.这表示任意一个不以0开头的数字,但是,这也意味着一个字符\"0\"不通过,所以我们采用下面的形式：^(0|[1-9][0-9]*)$\n      c.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号：^(0|-?[1-9][0-9]*)$\n      d.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分：^[0-9]+(.[0-9]+)?$\n      e.必须说明的是,小数点后面至少应该有1位数,所以\"10.\"是不通过的,但是 \"10\" 和 \"10.2\" 是通过的：^[0-9]+(.[0-9]{2})?$\n      f.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样：^[0-9]+(.[0-9]{1,2})?$\n      g.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样：^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$\n      h.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须：^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$\n15、 xml文件：^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\\\.[x|X][m|M][l|L]$\n16、 中文字符的正则表达式：[\\u4e00-\\u9fa5]\n17、 双字节字符：[^\\x00-\\xff]    (包括汉字在内，可以用来计算字符串的长度(一个双字节字符长度计2，ASCII字符计1))\n18、 空白行的正则表达式：\\n\\s*\\r    (可以用来删除空白行)\n19、 HTML标记的正则表达式：<(\\S*?)[^>]*>.*?</\\1>|<.*? />    (网上流传的版本太糟糕，上面这个也仅仅能部分，对于复杂的嵌套标记依旧无能为力)\n20、 首尾空白字符的正则表达式：^\\s*|\\s*$或(^\\s*)|(\\s*$)    (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)，非常有用的表达式)\n21、 腾讯QQ号：[1-9][0-9]{4,}    (腾讯QQ号从10000开始)\n22、 中国邮政编码：[1-9]\\d{5}(?!\\d)    (中国邮政编码为6位数字)\n23、 IP地址：\\d+\\.\\d+\\.\\d+\\.\\d+    (提取IP地址时有用)\n24、 IP地址：((?:(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d?\\\\d)\\\\.){3}(?:25[0-5]|2[0-4]\\\\d|[01]?\\\\d?\\\\d))  \n25、PV6 地址： (([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\n26、URL 链接： ((http|ftp|https)://)(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?\n\n```\n"
  },
  {
    "path": "Article/PythonBasis/python15/1.md",
    "content": "网络上介绍 Python 闭包的文章已经很多了，本文将通过解决一个需求问题来了解闭包。\n\n\n这个需求是这样的，我们需要一直记录自己的学习时间，以分钟为单位。就好比我学习了 2 分钟，就返回 2 ，然后隔了一阵子，我学习了 10 分钟，那么就返回 12 ，像这样把学习时间一直累加下去。\n\n\n面对这个需求，我们一般都会创建一个全局变量来记录时间，然后用一个方法来新增每次的学习时间，通常都会写成下面这个形式：\n\n```Python\ntime = 0\n\ndef insert_time(min):\n    time = time + min\n    return  time\n\nprint(insert_time(2))\nprint(insert_time(10))\n```\n\n认真想一下，会不会有什么问题呢？\n\n其实，这个在 Python 里面是会报错的。会报如下错误：\n\n```\nUnboundLocalError: local variable 'time' referenced before assignment\n```\n\n那是因为，在 Python 中，如果一个函数使用了和全局变量相同的名字且改变了该变量的值，那么该变量就会变成局部变量，那么就会造成在函数中我们没有进行定义就引用了，所以会报该错误。\n\n如果确实要引用全局变量，并在函数中对它进行修改，该怎么做呢？\n\n我们可以使用 `global` 关键字,具体修改如下：\n\n```Python\ntime = 0\n\n\ndef insert_time(min):\n    global  time\n    time = time + min\n    return  time\n\nprint(insert_time(2))\nprint(insert_time(10))\n```\n\n输出结果如下：\n\n```\n2\n12\n```\n\n可是啊，这里使用了全局变量，我们在开发中能尽量避免使用全局变量的就尽量避免使用。因为不同模块，不同函数都可以自由的访问全局变量，可能会造成全局变量的不可预知性。比如程序员甲修改了全局变量 `time` 的值，然后程序员乙同时也对 `time` 进行了修改，如果其中有错误，这种错误是很难发现和更正的。\n\n\n全局变量降低了函数或模块之间的通用性，不同的函数或模块都要依赖于全局变量。同样，全局变量降低了代码的可读性，阅读者可能并不知道调用的某个变量是全局变量。\n\n那有没有更好的方法呢？\n\n这时候我们使用闭包来解决一下，先直接看代码：\n\n```python\ntime = 0\n\n\ndef study_time(time):\n    def insert_time(min):\n        nonlocal  time\n        time = time + min\n        return time\n\n    return insert_time\n\n\nf = study_time(time)\nprint(f(2))\nprint(time)\nprint(f(10))\nprint(time)\n```\n\n输出结果如下:\n\n```\n2\n0\n12\n0\n```\n\n这里最直接的表现就是全局变量 `time` 至此至终都没有修改过,这里还是用了 `nonlocal` 关键字,表示在函数或其他作用域中使用外层(非全局)变量。那么上面那段代码具体的运行流程是怎样的。我们可以看下下图：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python%20%E9%97%AD%E5%8C%85%E8%A7%A3%E5%86%B3.png)\n\n\n这种内部函数的局部作用域中可以访问外部函数局部作用域中变量的行为，我们称为： 闭包。更加直接的表达方式就是，当某个函数被当成对象返回时，夹带了外部变量，就形成了一个闭包。k\n\n\n闭包避免了使用全局变量，此外，闭包允许将函数与其所操作的某些数据（环境）关连起来。而且使用闭包，可以使代码变得更加的优雅。而且下一篇讲到的装饰器，也是基于闭包实现的。\n\n\n到这里，就会有一个问题了，你说它是闭包就是闭包了？有没有什么办法来验证一下这个函数就是闭包呢？\n\n\n有的，所有函数都有一个 ` __closure__` 属性，如果函数是闭包的话，那么它返回的是一个由 cell 组成的元组对象。cell 对象的 cell_contents 属性就是存储在闭包中的变量。\n\n我们打印出来体验一下：\n\n```Python\ntime = 0\n\n\ndef study_time(time):\n    def insert_time(min):\n        nonlocal  time\n        time = time + min\n        return time\n\n    return insert_time\n\n\nf = study_time(time)\nprint(f.__closure__)\nprint(f(2))\nprint(time)\nprint(f.__closure__[0].cell_contents)\nprint(f(10))\nprint(time)\nprint(f.__closure__[0].cell_contents)\n```\n\n打印的结果为：\n\n```\n(<cell at 0x0000000000410C48: int object at 0x000000001D6AB420>,)\n2\n0\n2\n12\n0\n12\n```\n\n从打印结果可见，传进来的值一直存储在闭包的 cell_contents 中,因此，这也就是闭包的最大特点，可以将父函数的变量与其内部定义的函数绑定。就算生成闭包的父函数已经释放了，闭包仍然存在。\n\n闭包的过程其实好比类（父函数）生成实例（闭包），不同的是父函数只在调用时执行，执行完毕后其环境就会释放，而类则在文件执行时创建，一般程序执行完毕后作用域才释放，因此对一些需要重用的功能且不足以定义为类的行为，使用闭包会比使用类占用更少的资源，且更轻巧灵活。\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python16/1.md",
    "content": "上一篇文章将通过解决一个需求问题来了解了闭包，本文也将一样，通过慢慢演变一个需求，一步一步来了解 Python 装饰器。\n\n\n首先有这么一个输出员工打卡信息的函数：\n\n```Python\ndef punch():\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\n\npunch()\n```\n\n输出的结果如下：\n\n```\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n```\n\n然后，产品反馈，不行啊，怎么上班打卡没有具体的日期，加上打卡的具体日期吧，这应该很简单，分分钟解决啦。好吧，那就直接添加打印日期的代码吧，如下：\n\n```Python\nimport time\n\n\ndef punch():\n    print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\n\npunch()\n```\n\n输出结果如下：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n```\n\n这样改是可以，可是这样改是改变了函数的功能结构的，本身这个函数定义的时候就是打印某个员工的信息和提示打卡成功，现在增加打印日期的代码，可能会造成很多代码重复的问题。比如，还有一个地方只需要打印员工信息和打卡成功就行了，不需要日期，那么你又要重写一个函数吗？而且打印当前日期的这个功能方法是经常使用的，是可以作为公共函数给各个模块方法调用的。当然，这都是作为一个整体项目来考虑的。\n\n既然是这样，我们可以使用函数式编程来修改这部分的代码。因为通过之前的学习，我们知道 Python 函数有两个特点，函数也是一个对象，而且函数里可以嵌套函数，那么修改一下代码变成下面这个样子：\n\n```Python\nimport time\n\n\ndef punch():\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\n\ndef add_time(func):\n    print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n    func()\n\n\nadd_time(punch)\n```\n\n输出结果：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n```\n\n\n这样是不是发现，这样子就没有改动 `punch` 方法，而且任何需要用到打印当前日期的函数都可以把函数传进 `add_time` 就可以了，就比如这样：\n\n```Python\nimport time\n\n\ndef punch():\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\n\ndef add_time(func):\n    print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n    func()\n\n\ndef holiday():\n    print('天气太冷，今天放假')\n\n\nadd_time(punch)\nadd_time(holiday)\n\n```\n\n打印结果：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n2018-01-09\n天气太冷，今天放假\n```\n\n使用函数编程是不是很方便，但是，我们每次调用的时候，我们都不得不把原来的函数作为参数传递进去，还能不能有更好的实现方式呢？有的，就是本文要介绍的装饰器，因为装饰器的写法其实跟闭包是差不多的，不过没有了自由变量，那么这里直接给出上面那段代码的装饰器写法，来对比一下，装饰器的写法和函数式编程有啥不同。\n\n```Python\nimport time\n\n\ndef decorator(func):\n    def punch():\n        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n        func()\n\n    return punch\n\n\ndef punch():\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\n\nf = decorator(punch)\nf()\n```\n\n输出的结果：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n```\n\n通过代码，能知道装饰器函数一般做这三件事：\n\n1. 接收一个函数作为参数\n2. 嵌套一个包装函数, 包装函数会接收原函数的相同参数，并执行原函数，且还会执行附加功能\n3. 返回嵌套函数\n\n\n可是，认真一看这代码，这装饰器的写法怎么比函数式编程还麻烦啊。而且看起来比较复杂，甚至有点多此一举的感觉。\n\n那是因为我们还没有用到装饰器的 “语法糖” ，我们看上面的代码可以知道， Python 在引入装饰器 （Decorator） 的时候，没有引入任何新的语法特性，都是基于函数的语法特性。这也就说明了装饰器不是 Python 特有的，而是每个语言通用的一种编程思想。只不过 Python 设计出了 `@` 语法糖，让 定义装饰器，把装饰器调用原函数再把结果赋值为原函数的对象名的过程变得更加简单，方便，易操作，所以 Python 装饰器的核心可以说就是它的语法糖。\n\n那么怎么使用它的语法糖呢？很简单，根据上面的写法写完装饰器函数后，直接在原来的函数上加 `@` 和装饰器的函数名。如下：\n\n```Python\nimport time\n\n\ndef decorator(func):\n    def punch():\n        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n        func()\n\n    return punch\n\n@decorator\ndef punch():\n    print('昵称：两点水  部门：做鸭事业部 上班打卡成功')\n\npunch()\n```\n\n输出结果：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n```\n\n那么这就很方便了，方便在我们的调用上，比如例子中的，使用了装饰器后，直接在原本的函数上加上装饰器的语法糖就可以了，本函数也无虚任何改变，调用的地方也不需修改。\n\n不过这里一直有个问题，就是输出打卡信息的是固定的，那么我们需要通过参数来传递，装饰器该怎么写呢？装饰器中的函数可以使用 `*args` 可变参数，可是仅仅使用 `*args` 是不能完全包括所有参数的情况，比如关键字参数就不能了，为了能兼容关键字参数，我们还需要加上 `**kwargs` 。\n\n因此，装饰器的最终形式可以写成这样：\n\n```Python\nimport time\n\n\ndef decorator(func):\n    def punch(*args, **kwargs):\n        print(time.strftime('%Y-%m-%d', time.localtime(time.time())))\n        func(*args, **kwargs)\n\n    return punch\n\n\n@decorator\ndef punch(name, department):\n    print('昵称：{0}  部门：{1} 上班打卡成功'.format(name, department))\n\n\n@decorator\ndef print_args(reason, **kwargs):\n    print(reason)\n    print(kwargs)\n\n\npunch('两点水', '做鸭事业部')\nprint_args('两点水', sex='男', age=99)\n```\n\n输出结果如下：\n\n```\n2018-01-09\n昵称：两点水  部门：做鸭事业部 上班打卡成功\n2018-01-09\n两点水\n{'sex': '男', 'age': 99}\n```\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/Grammar.md",
    "content": "# 一、Python 语法的简要说明 #\n\n每种语言都有自己的语法，不管是自然语言（英语，中文）还是计算机编程语言。\n\nPython 也不例外，它也有自己的语法规则，然后编辑器或者解析器根据符合语法的程序代码转换成 CPU 能够执行的机器码，然后执行。\n\nPython 的语法比较简单，采用缩进方式。\n\n![Python语法.png](http://upload-images.jianshu.io/upload_images/2136918-b9b072c2587cc89e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n如上面的代码截图，以 # 开头的语句是注释，其他每一行都是一个语句，当语句以冒号 : 结尾时，缩进的语句视为代码块。\n\n要注意的是 Python 程序是大小写敏感的，如果写错了大小写，程序会报错。\n\n更多的说明可以看看之前的文章：[Python代码规范中的简明概述](https://www.readwithu.com/codeSpecification/codeSpecification_first.html)\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/Preface.md",
    "content": "# 前言 #\n\n最近要开始新的项目，工作又开始忙起来了，不过还是每天要抽时间来写博客，但不可能做到日更，因为一篇博客，写的时间还是挺长的。[Gitbook](https://www.readwithu.com/) 同时更新喔。\n\n注：看到以前矫情的话语，一下子就想把它给删掉。可以刚刚按了删除键才发现，删了之后，不知道写什么了。就瞬间撤销了。这一章节中改动了挺多东西的，也新增了很多例子。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%8F%98%E9%87%8F.png)\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/StringCoding.md",
    "content": "# 四、 字符串的编码问题 #\n\n我们都知道计算机只能处理数字，如果要处理文本，就必须先把文本转换为数字才能处理。最早的计算机在设计时采用8个比特（bit）作为一个字节（byte），所以，一个字节能表示的最大的整数就是255（二进制11111111=十进制255），0 - 255被用来表示大小写英文字母、数字和一些符号，这个编码表被称为ASCII编码，比如大写字母 A 的编码是 65，小写字母 z 的编码是 122。\n\n如果要表示中文，显然一个字节是不够的，至少需要两个字节，而且还不能和 ASCII 编码冲突，所以，中国制定了 GB2312 编码，用来把中文编进去。\n\n类似的，日文和韩文等其他语言也有这个问题。为了统一所有文字的编码，Unicode 应运而生。Unicode 把所有语言都统一到一套编码里，这样就不会再有乱码问题了。\n\nUnicode 通常用两个字节表示一个字符，原有的英文编码从单字节变成双字节，只需要把高字节全部填为 0 就可以。\n\n因为 Python 的诞生比 Unicode 标准发布的时间还要早，所以最早的Python 只支持 ASCII 编码，普通的字符串 'ABC' 在 Python 内部都是 ASCII 编码的。\n\nPython 在后来添加了对 Unicode 的支持，以 Unicode 表示的字符串用`u'...'`表示。\n\n不过在最新的 Python 3 版本中，字符串是以 Unicode 编码的，也就是说，Python 的字符串支持多语言。就像上面的例子一样，我的代码中没有加`u'...'`，也能正常显示。\n\n不过由于 Python 源代码也是一个文本文件，所以，当你的源代码中包含中文的时候，在保存源代码时，就需要务必指定保存为 UTF-8 编码。当Python 解释器读取源代码时，为了让它按 UTF-8 编码读取，我们通常在文件开头写上这两行：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n```\n\n第一行注释是为了告诉 Linux/OS X 系统，这是一个 Python 可执行程序，Windows 系统会忽略这个注释；\n\n第二行注释是为了告诉 Python 解释器，按照 UTF-8 编码读取源代码，否则，你在源代码中写的中文输出可能会有乱码。\n\n申明了 UTF-8 编码并不意味着你的 .py 文件就是 UTF-8 编码的，必须并且要确保文本编辑器正在使用 UTF-8 without BOM 编码\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/Type_conversion.md",
    "content": "# 五、基本数据类型转换 #\n\nPython 中基本数据类型转换的方法有下面几个。\n\n|方法|说明|\n|-----|------|\n|int(x [,base ])  |       将x转换为一个整数  |\n|float(x )    |           将x转换到一个浮点数  |\n|complex(real [,imag ])|  创建一个复数  |\n|str(x ) |                将对象 x 转换为字符串  |\n|repr(x ) |               将对象 x 转换为表达式字符串  |\n|eval(str )  |            用来计算在字符串中的有效 Python 表达式,并返回一个对象  |\n|tuple(s )  |             将序列 s 转换为一个元组  |\n|list(s )   |             将序列 s 转换为一个列表  |\n|chr(x )   |              将一个整数转换为一个字符  |\n|unichr(x )  |            将一个整数转换为 Unicode 字符  |\n|ord(x )     |            将一个字符转换为它的整数值  |\n|hex(x )     |            将一个整数转换为一个十六进制字符串  |\n|oct(x )     |            将一个整数转换为一个八进制字符串  |\n\n注：在 Python 3 里，只有一种整数类型 int，表示为长整型，没有 python2 中的 Long。\n\n这里我们可以尝试一下这些函数方法。\n\n比如 `int()` 函数，将符合规则的字符串类型转化为整数 。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-091547.png)\n\n输出结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-091648.png)\n\n注意这里是符合规则的字符串类型，如果是文字形式等字符串是不可以被 `int()` 函数强制转换的。\n\n还有小数形式的字符串也是不能用  `int()`  函数转换的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-064739.png)\n\n这样转换会报错。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-064811.png)\n\n但这并不是意味着浮点数不能转化为整数，而是小数形式的字符串不能强转为字符串。\n\n浮点数还是可以通过 `int()`  函数转换的。\n\n比如：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-065336.png)\n\n输出结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-065407.png)\n\n但是你会发现，结果是 88 ，后面小数点的 0.88 被去掉了。\n\n这是因为  `int()`  函数是将数据转为整数。如果是浮点数转为整数，那么  `int()`  函数就会做取整处理，只取整数部分。所以输出的结果为 88 。\n\n其余的方法就不一一列举了，只要多用，多试，这些方法都会慢慢熟悉的。还有如果是初学者，完全可以每个方法都玩一下，写一下，随便写，然后运行看结果，反正你的电脑又不会因为这样而玩坏的。\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/Type_of_data.md",
    "content": "# 三、Python 的基本数据类型 #\n\n## 1、字符串 ##\n\n字符串英文 string ，是 python 中随处可见的数据类型，字符串的识别也非常的简单，就是用「引号」括起来的。\n\n引号包括单引号 `' '` ，双引号 `\" \"` 和 三引号 `''' '''` ，比如 `'abc'` ，`\"123\"` 等等。\n\n这里请注意，单引号 `''`  或双引号 `\"\"`  本身只是一种表示方式，不是字符串的一部分，因此，字符串 `'abc'` 只有 a，b，c 这 3 个字符。\n\n如果善于思考的你，一定会问？\n\n为什么要有单引号 `' '` ，双引号 `\" \"` 和 三引号 `''' '''` 啊，直接定死一个不就好了，搞那么麻烦，那么多规则表达同一个东西干嘛？\n\n对，一般来说一种语法只用一个规则来表示是最好的，竟然现在字符串有三种不同的表示，证明是有原因的。\n\n那么我们先来看下这三种方式，来定义同样内容的字符串，再把它打印出来，看看是怎样的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-071320.png)\n\n打印出来的结果是一样的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-071403.png)\n\n那如果我们的字符串不是 `两点水`，是 `两'点'水` 这样呢？\n\n这样就直接报错了。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-071800.png)\n\n但是要注意，用单引号 `' '` 不行，用双引号 `\" \"` 是可以的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-072459.png)\n\n打印的结果也跟预想的一样：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-072523.png)\n\n至于三引号，也是一样的，如果字符串内容里面含有双引号，也是会报同样的错误的。那么这时候你就可以用三引号了。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-072701.png)\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-072829.png)\n\n那么用单引号，双引号定义的字符串就不能表示这样的内容吗？\n\n并不是的，你可以使用转义字符。\n\n比如单引号，你可以使用 `\\'` 来表示，双引号可以使用 `\\\"`  来表示。\n\n注意，这里的是反斜杠 `\\`, 不是斜杆 `/` 。\n\n了解了之后，直接程序测试一下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-073544.png)\n\n运行结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-073601.png)\n\n最后，也提一下， 三引号 `''' '''` 是直接可以分行的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-074157.png)\n\n运行结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-074209.png)\n\n\n\n\n\n\n## 2、整数 ##\n\n整数英文为 integer 。代码中的整数跟我们平常认识的整数一样，包括正整数、负整数和零，是没有小数点的数字。\n\nPython 可以处理任意大小的整数，例如：`1`，`100`，`-8080`，`0`，等等。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-075017.png)\n\n运行结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-075046.png)\n\n当然，要注意了，如果数字你用引号括起来了，那就属于字符串，而不属于整数。比如 `'100'` , 这 100 是字符串，不是整数。\n\n在现实世界中，整数我们通常会做计算，因此代码世界也是一样，整数可以直接加减乘除。\n\n比如：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-075748.png)\n\n程序运行结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-29-075806.png)\n\n这里提示下大家，看看上面的例子，有没有发现什么？\n\n看下 `int4` 打印出来的结果，是 `0.5` , 是一个小数。\n\n而我们上面对整数的定义是什么？\n\n是没有小数点的数字。\n\n因此 `int4` 肯定不是整数。\n\n这里我们可以使用 `type()` 函数来查看下类型。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-032745.png)\n\n结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-032826.png)\n\n可以看到 `int4` 是 float 类型，而 `int1` ,`int2`,`int3` 都是 int 整数类型。\n\n那么 float  是什么类型呢？\n\nfloat 是浮点数类型，是我们下面会说到的。\n\n在说浮点数之前，各位可以看下 Python 的算术运算符有哪些，有个印象。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-034538.png)\n\n\n\n\n\n## 3、浮点数 ##\n\n浮点数的英文名是 float ，是指带小数的数字。\n\n浮点数跟整数有很多类似的地方，但是浮点数是最折磨人的，也是最难让人捉摸透的。\n\n就好比世界级的大佬 Herb Sutter 说的：「世上的人可以分为3类：一种是知道自己不懂浮点运算的；一种是以为自己懂浮点运算的；最后一种是极少的专家级人物，他们想知道自己是否有可能，最终完全理解浮点运算。」\n\n为什么这么说呢？\n\n看下面的例子 ，像整数一样，只是基本的浮点数加法运算。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-081702.png)\n\n可是运算结果，对于初学者来说，可能会接受不了。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-30-081922.png)\n\n对于第一个还好，`0.55+0.41` 等于 0.96 ，运算结果完全一致。可是后面两个，你会发现怎么出现了那么多个零。\n\n这是因为计算机对浮点数的表达本身是不精确的。保存在计算机中的是二进制数，二进制对有些数字不能准确表达，只能非常接近这个数。\n\n所以我们在对浮点数做运算和比较大小的时候要小心。\n\n\n\n\n## 4、布尔值 ##\n\n布尔值和布尔代数的表示完全一致，一个布尔值只有 `True` 、 `False `两种值，要么是 `True`，要么是 `False`，在 Python 中，可以直接用 True、False 表示布尔值（请注意大小写），也可以通过布尔运算计算出来。\n\n布尔值可以用 `and`、`or` 和 `not` 运算。\n\n`and` 运算是与运算，只有所有都为 True，and 运算结果才是 True。\n\n`or` 运算是或运算，只要其中有一个为 True，or 运算结果就是 True。\n\n`not` 运算是非运算，它是一个单目运算符，把 True 变成 False，False 变成 True。\n\n\n\n## 5、空值 ##\n\n基本上每种编程语言都有自己的特殊值——空值，在 Python 中，用 None 来表示\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/Variable.md",
    "content": "# 六、Python 中的变量 #\n\n## 1、变量的创建和赋值 ##\n\n在 Python 程序中，变量是用一个变量名表示，可以是任意数据类型，变量名必须是大小写英文、数字和下划线（_）的组合，且不能用数字开头，比如：\n\n```python\na=88\n```\n\n这里的 `a` 就是一个变量，代表一个整数，注意一点是 Python 是不用声明数据类型的。在 Python 中 `=` 是赋值语句，跟其他的编程语言也是一样的，因为 Python 定义变量时不需要声明数据类型，因此可以把任意的数据类型赋值给变量，且同一个变量可以反复赋值，而且可以是不同的数据类型。\n\n![Python 中的变量.png](http://upload-images.jianshu.io/upload_images/2136918-69affa6da83f1dfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这种变量本身类型不固定的语言称之为动态语言，与之对应的是静态语言。静态语言在定义变量时必须指定变量类型，如果赋值的时候类型不匹配，就会报错。例如 Java 是静态语言。\n\n\n## 2、变量的指向问题 ##\n\n我们来看下这段代码，发现最后打印出来的变量 b 是 `Hello Python` 。\n\n![Python变量指向.png](http://upload-images.jianshu.io/upload_images/2136918-052a908c25fcfc49.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这主要是变量 a 一开始是指向了字符串 `Hello Python` ，`b=a` 创建了变量 b ,变量 b 也指向了a 指向的字符串 `Hello Python`，最后 `a=123`，把 变量 a 重新指向了 `123`，所以最后输出变量 b 是 `Hello Python`\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-%E5%8F%98%E9%87%8F%E7%9A%84%E6%8C%87%E5%90%91.png)\n\n \n\n## 3、多个变量赋值 ##\n\nPython 允许同时为多个变量赋值。例如：\n\n```python\na = b = c = 1\n```\n\n以上实例，创建一个整型对象，值为 1，三个变量被分配到相同的内存空间上。\n\n当然也可以为多个对象指定多个变量。例如：\n\n```python\na, b, c = 1, 2, \"liangdianshui\"\n```\n\n以上实例，两个整型对象 1 和 2 的分配给变量 a 和 b，字符串对象 \"liangdianshui\" 分配给变量 c。\n\n"
  },
  {
    "path": "Article/PythonBasis/python2/print.md",
    "content": "# 二、print() 函数 #\n\n这里先说一下 `print()` 函数，如果你是新手，可能对函数不太了解，没关系，在这里你只要了解它的组成部分和作用就可以了，后面函数这一块会详细说明的。\n\n`print()` 函数由两部分构成 ：\n\n1.  指令：print\n2. 指令的执行对象，在 print 后面的括号里的内容\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-074454.png)\n\n而 `print()` 函数的作用是让计算机把你给它的指令结果，显示在屏幕的终端上。这里的指令就是你在 `print()` 函数里的内容。\n\n比如在上一章节中，我们的第一个 Python 程序，打印 `print('Hello Python')`\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-080241.png)\n\n它的执行流程如下：\n\n1. 向解释器发出指令，打印 'Hello Python' \n2. 解析器把代码解释为计算器能读懂的机器语言\n3. 计算机执行完后就打印结果\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-083751.png)\n\n可能这里有人会问，为什么要加单引号，直接  `print(Hello Python)` 不行吗？\n\n如果你写代码过程中，有这样的疑问，直接写一下代码，自己验证一下是最好的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-17-094034.png)\n\n显然，去掉单引号后，运行结果标红了（报错），证明这是不可以的。\n\n主要是因为这不符合 Python 的语法规则，去掉单引号后， Python 解释器根本没法看懂你写的是什么。\n\n所以就报 ` SyntaxError: invalid syntax` 的错误，意思是：语法错误。说明你的语句不合规则。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python3/List.md",
    "content": "# 一、List（列表） #\n\n## 1、什么是 List （列表）\n\nList （列表）是 Python 内置的一种数据类型。 它是一种有序的集合，可以随时添加和删除其中的元素。\n\n那为什么要有 List （列表）呢？\n\n我们用一个例子来说明。\n\n现在有一个团队要出去玩，要先报名。如果用我们之前学过的知识，那么就是用一个字符串变量把他们都记录起来。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-080527.png)\n\n但是这样太麻烦了，而且也不美观。\n\n在编程中，一定要学会偷懒，避免「重复性工作」。如果有一百个成员，那么你及时是复制粘贴，也会把你写烦。\n\n这时候就可以使用列表了。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-080835.png)\n\n就这样，一行代码就可以存放 N 多个名字了。\n\n\n## 2、怎么创建 List（列表） ##\n\n从上面的例子可以分析出，列表的格式是这样的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-081342.png)\n\n其实列表就是用中括号 `[]` 括起来的数据，里面的每一个数据就叫做元素。每个元素之间使用逗号分隔。\n\n而且列表的数据元素不一定是相同的数据类型。\n\n比如：\n\n```python\nlist1=['两点水','twowter','liangdianshui',123]\n```\n\n这里有字符串类型，还有整数类型。\n\n我们尝试把他打印出来，看看打印的结果是怎样的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-081912.png)\n\n结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-081951.png)\n\n\n## 3、如何访问 List（列表）中的值 ##\n\n就像一开始的例子，我们有时候不需要把全部人员的姓名都打印出来，有时候我们需要知道第 3 个报名的人是谁？前两名报名的是谁？\n\n那么怎么从列表中取出来呢？\n\n换种问法就是，怎么去访问列表中的值？\n\n这时候我们可以通过列表的下标索引来访问列表中的值，同样你也可以使用方括号的形式截取字符。\n\n例如：\n\n```python\nname = ['一点水', '两点水', '三点水', '四点水', '五点水']\n\n# 通过索引来访问列表\nprint(name[2])\n# 通过方括号的形式来截取列表中的数据\nprint(name[0:2])\n```\n\n输出的结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-090321.png)\n\n可见，我们需要知道知道 `name` 这个列表中第三个报名的是谁？只需要用 `name[2]`  就可以了。\n\n这里你会问，为什么是 2 ，不是 3 呢？\n\n这是因为在编程世界中，都是从 0 开始的，而不是我们生活习惯中从 1 开始。\n\n所以需要知道第三个是谁？\n\n那就是  `name[2]`  就可以了。\n\n从例子来看，我们还把 `name[0:2]` 的结果打印出来了。\n\n从打印结果来看，只打印了第一，第二个元素内容。\n\n这里可能会有疑问？\n\n为什么不是打印前三个啊，不是说 2 就是第 3 个吗？\n\n那是因为这是**左闭右开**区间的。\n\n所以 `name[0:2]` 的意思就是从第 0 个开始取，取到第 2 个，但是不包含第 2 个。\n\n还是那句话，为了更好的理解，可以多去尝试，多去玩编程。\n\n所以你可以尝试下下面的各种方式：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-091524.png)\n\n看看输出的结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-091624.png)\n\n根据输出的结果和上面讲到的知识，就很容易理解其中的一些用法了。\n\n\n\n\n    ## 4、怎么去更新 List（列表） ##\n\n还是一开始的例子，我们用代码记录了报名人的名字，那后面可能会有新人加入，也有可能会发现一开始写错名字了，想要修改。\n\n这时候怎么办呢？\n\n这时候可以通过索引对列表的数据项进行修改或更新，也可以使用 append() 方法来添加列表项。\n\n```python\nname = ['一点水', '两点水', '三点水', '四点水', '五点水']\n\n\n# 通过索引对列表的数据项进行修改或更新\nname[1]='2点水'\nprint(name)\n\n# 使用 append() 方法来添加列表项\nname.append('六点水')\nprint(name)\n```\n\n输出的结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-092406.png)\n\n\n\n\n\n## 5、怎么删除 List（列表） 里面的元素 ##\n\n那既然这样，肯定会有人中途退出的。\n\n那么我们就需要在列表中，把他的名字去掉。\n\n这时候使用 del 语句来删除列表的的元素\n\n```python\nname = ['一点水', '两点水', '三点水', '四点水', '五点水']\n\nprint(name)\n\n# 使用 del 语句来删除列表的的元素\ndel name[3]\nprint(name)\n```\n\n输出的结果:\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-092705.png)\n\n你看输出的结果，列表中已经没有了 `四点水` 这个数据了。证明已经删除成功了。\n\n\n\n\n\n\n## 6、List（列表）运算符 ##\n\n列表对 `+`  和 `*`  的操作符与字符串相似。`+` 号用于组合列表，`*`  号用于重复列表。\n\n|Python 表达式|结果|描述|\n|-----------|-----|-----|\n|len([1, 2, 3])|3|计算元素个数|\n|[1, 2, 3] + [4, 5, 6]|\t[1, 2, 3, 4, 5, 6]|\t组合|\n|['Hi!'] * 4|['Hi!', 'Hi!', 'Hi!', 'Hi!']|复制|\n|3 in [1, 2, 3]|True|元素是否存在于列表中|\n|for x in [1, 2, 3]: print x,|1 2 3|迭代|\n\n\n## 7、List （列表）函数&方法 ##\n\n|函数&方法|描述|\n|----|----|\n|len(list)|列表元素个数|\n|max(list)|返回列表元素最大值|\n|min(list)|返回列表元素最小值|\n|list(seq)|将元组转换为列表|\n|list.append(obj)|在列表末尾添加新的对象|\n|list.count(obj)|统计某个元素在列表中出现的次数|\n|list.extend(seq)|在列表末尾一次性追加另一个序列中的多个值（用新列表扩展原来的列表）|\n|list.index(obj)|从列表中找出某个值第一个匹配项的索引位置|\n|list.insert(index, obj)|将对象插入列表|\n|list.pop(obj=list[-1])|移除列表中的一个元素（默认最后一个元素），并且返回该元素的值|\n|list.remove(obj)|移除列表中的一个元素（参数是列表中元素），并且不返回任何值|\n|list.reverse()|反向列表中元素|\n|list.sort([func])|对原列表进行排序|\n\n\n## 8、实例 ##\n\n\n最后通过一个例子来熟悉了解 List 的操作\n\n例子：\n\n```python\n#-*-coding:utf-8-*-\n#-----------------------list的使用----------------------------------\n\n# 1.一个产品，需要列出产品的用户，这时候就可以使用一个 list 来表示\nuser=['liangdianshui','twowater','两点水']\nprint('1.产品用户')\nprint(user)\n\n# 2.如果需要统计有多少个用户，这时候 len() 函数可以获的 list 里元素的个数\nlen(user)\nprint('\\n2.统计有多少个用户')\nprint(len(user))\n\n# 3.此时，如果需要知道具体的用户呢？可以用过索引来访问 list 中每一个位置的元素，索引是0从开始的\nprint('\\n3.查看具体的用户')\nprint(user[0]+','+user[1]+','+user[2])\n\n# 4.突然来了一个新的用户，这时我们需要在原有的 list 末尾加一个用户\nuser.append('茵茵')\nprint('\\n4.在末尾添加新用户')\nprint(user)\n\n# 5.又新增了一个用户，可是这个用户是 VIP 级别的学生，需要放在第一位，可以通过 insert 方法插入到指定的位置\n# 注意：插入数据的时候注意是否越界，索引不能超过 len(user)-1\nuser.insert(0,'VIP用户')\nprint('\\n5.指定位置添加用户')\nprint(user)\n\n# 6.突然发现之前弄错了，“茵茵”就是'VIP用户'，因此，需要删除“茵茵”；pop() 删除 list 末尾的元素\nuser.pop()\nprint('\\n6.删除末尾用户')\nprint(user)\n\n# 7.过了一段时间，用户“liangdianshui”不玩这个产品，删除了账号\n# 因此需要要删除指定位置的元素，用pop(i)方法，其中i是索引位置\nuser.pop(1)\nprint('\\n7.删除指定位置的list元素')\nprint(user)\n\n# 8.用户“两点水”想修改自己的昵称了\nuser[2]='三点水'\nprint('\\n8.把某个元素替换成别的元素')\nprint(user)\n\n# 9.单单保存用户昵称好像不够好，最好把账号也放进去\n# 这里账号是整数类型，跟昵称的字符串类型不同，不过 list 里面的元素的数据类型是可以不同的\n# 而且 list 元素也可以是另一个 list\nnewUser=[['VIP用户',11111],['twowater',22222],['三点水',33333]]\nprint('\\n9.不同元素类型的list数据')\nprint(newUser)\n\n```\n\n![list的使用](http://upload-images.jianshu.io/upload_images/2136918-65d31cae9f8bb34d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python3/Preface.md",
    "content": "# 前言 #\n\n之前我们学习了字符串，整数，浮点数几种基本数据类型，现在我们接着学习两种新的数据类型，列表（List）和元组（tuple）。\n\n注： [https://www.readwithu.com/](https://www.readwithu.com/) 同步更新。\n\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-01-List%20%E5%92%8C%20Tuple.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python3/tuple.md",
    "content": "# 二、tuple（元组） #\n\n## 1、什么是元组 （tuple） ##\n\n上一节刚说了一个有序列表 List ，现在说另一种有序列表叫元组：tuple 。\n\ntuple 和 List 非常类似，但是 tuple 一旦初始化就不能修改。\n也就是说元组（tuple）是不可变的，那么不可变是指什么意思呢？\n\n元组（tuple） 不可变是指当你创建了 tuple 时候，它就不能改变了，也就是说它也没有 append()，insert() 这样的方法，但它也有获取某个索引值的方法，但是不能赋值。\n\n那么为什么要有 tuple 呢？\n\n那是因为 tuple 是不可变的，所以代码更安全。\n\n所以建议能用 tuple 代替 list 就尽量用 tuple 。\n\n\n\n## 2、怎样创建元组（tuple） ##\n\n元组创建很简单，只需要在括号中添加元素，并使用逗号隔开即可。\n\n```python\ntuple1=('两点水','twowter','liangdianshui',123,456)\ntuple2='两点水','twowter','liangdianshui',123,456\n```\n\n创建空元组\n\n```python\ntuple3=()\n```\n\n元组中只包含一个元素时，需要在元素后面添加逗号\n\n```python\ntuple4=(123,)\n```\n\n如果不加逗号，创建出来的就不是 元组（tuple），而是指 ```123``` 这个数了。\n\n\n这是因为括号 () 既可以表示元组（tuple），又可以表示数学公式中的小括号，这就产生了歧义。\n\n所以如果只有一个元素时，你不加逗号，计算机就根本没法识别你是要进行整数或者小数运算还是表示元组。\n\n因此，Python 规定，这种情况下，按小括号进行计算，计算结果自然是 ```123``` ，而如果你要表示元组的时候，就需要加个逗号。\n\n具体看下图 tuple4 和 tuple5 的输出值\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-093847.jpg)\n\n\n\n\n\n\n\n## 3、如何访问元组（tuple） ##\n\n元组下标索引也是从 0 开始，元组（tuple）可以使用下标索引来访问元组中的值。\n\n```python\n#-*-coding:utf-8-*-\n\ntuple1=('两点水','twowter','liangdianshui',123,456)\ntuple2='两点水','twowter','liangdianshui',123,456\n\nprint(tuple1[1])\nprint(tuple2[0])\n```\n\n输出的结果：\n\n![访问 tuple](http://upload-images.jianshu.io/upload_images/2136918-edfb7c9ebc7d5ab0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n\n\n\n## 4、修改元组 （tuple） ##\n\n可能看到这个小标题有人会疑问，上面不是花了一大段来说 tuple 是不可变的吗？\n\n这里怎么又来修改 tuple （元组） 了。\n\n那是因为元组中的元素值是不允许修改的，但我们可以对元组进行连接组合，还有通过修改其他列表的值从而影响 tuple 的值。\n\n具体看下面的这个例子：\n\n```python\n#-*-coding:utf-8-*-\nlist1=[123,456]\ntuple1=('两点水','twowater','liangdianshui',list1)\nprint(tuple1)\nlist1[0]=789\nlist1[1]=100\nprint(tuple1)\n```\n\n输出的结果：\n```\n('两点水', 'twowater', 'liangdianshui', [123, 456])\n('两点水', 'twowater', 'liangdianshui', [789, 100])\n```\n\n\n可以看到，两次输出的 tuple 值是变了的。我们看看 tuple1 的存储是怎样的。\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-%E4%BF%AE%E6%94%B9tuple%E6%B5%81%E7%A8%8B%E5%9B%BE.png)\n\n\n可以看到，tuple1 有四个元素，最后一个元素是一个 List ，List 列表里有两个元素。\n\n当我们把 List 列表中的两个元素 `124` 和 `456` 修改为 `789` 和 `100` 的时候，从输出来的 tuple1 的值来看，好像确实是改变了。\n\n但其实变的不是 tuple 的元素，而是 list 的元素。\n\ntuple 一开始指向的 list 并没有改成别的 list，所以，tuple 所谓的“不变”是说，tuple 的每个元素，指向永远不变。注意是 tupe1 中的第四个元素还是指向原来的 list ，是没有变的，我们修改的只是列表 List 里面的元素。\n\n\n\n## 5、删除 tuple （元组） ##\n\ntuple 元组中的元素值是不允许删除的，但我们可以使用 del 语句来删除整个元组\n\n```python\n#-*-coding:utf-8-*-\n\ntuple1=('两点水','twowter','liangdianshui',[123,456])\nprint(tuple1)\ndel tuple1\n```\n\n## 6、tuple （元组）运算符 ##\n\n与字符串一样，元组之间可以使用 `+` 号和 `*` 号进行运算。这就意味着他们可以组合和复制，运算后会生成一个新的元组。\n\n|Python 表达式|结果|描述|\n|-----------|-----|-----|\n|len((1, 2, 3))|3|计算元素个数|\n|(1, 2, 3) + (4, 5, 6)|(1, 2, 3, 4, 5, 6)|连接|\n|('Hi!',) * 4|('Hi!', 'Hi!', 'Hi!', 'Hi!')|复制|\n|3 in (1, 2, 3)|True|元素是否存在|\n|for x in (1, 2, 3):  print(x)|1 2 3|迭代|\n\n## 7、元组内置函数 ##\n\n|方法|描述|\n|----|----|\n|len(tuple)|计算元组元素个数|\n|max(tuple)|返回元组中元素最大值|\n|min(tuple)|返回元组中元素最小值|\n|tuple(seq)|将列表转换为元组|\n\n\n## 8、实例 ##\n\n最后跟列表一样，来一个实例，大家也可以多尝试，去把元组的各种玩法玩一遍。\n\n```python\nname1 = ('一点水', '两点水', '三点水', '四点水', '五点水')\n\nname2 = ('1点水', '2点水', '3点水', '4点水', '5点水')\n\nlist1 = [1, 2, 3, 4, 5]\n\n# 计算元素个数\nprint(len(name1))\n# 连接,两个元组相加\nprint(name1 + name2)\n# 复制元组\nprint(name1 * 2)\n# 元素是否存在 (name1 这个元组中是否含有一点水这个元素)\nprint('一点水' in name1)\n# 元素的最大值\nprint(max(name2))\n# 元素的最小值\nprint(min(name2))\n# 将列表转换为元组\nprint(tuple(list1))\n```\n\n输出的结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-08-31-101523.png)\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python4/Dict.md",
    "content": "# 一、字典(Dictionary) #\n\n## 1、什么是 dict（字典） ##\n\n上一章节，我们学习了列表（List） 和 元组（tuple） 来表示有序集合。\n\n而我们在讲列表（list）的时候，我们用了列表（list） 来存储用户的姓名。\n\n```python\nname = ['一点水', '两点水', '三点水', '四点水', '五点水']\n```\n\n那么如果我们为了方便联系这些童鞋，要把电话号码也添加进去，该怎么做呢？\n\n用 list 可以这样子解决：\n\n```python\nname = [['一点水', '131456780001'], ['两点水', '131456780002'], ['三点水', '131456780003'], ['四点水', '131456780004'], ['五点水', '131456780005']]\n```\n\n但是这样很不方便，我们把电话号码记录下来，就是为了有什么事能及时联系上这些童鞋。\n\n如果用列表来存储这些，列表越长，我们查找起来耗时就越长。\n\n这时候就可以用 dict （字典）来表示了，Python 内置了 字典（dict），dict 全称 dictionary，如果学过 Java ，字典就相当于 JAVA 中的 map，使用键-值（key-value）存储，具有极快的查找速度。\n\n```python\nname = {'一点水': '131456780001', '两点水': '131456780002', '三点水': '131456780003', '四点水': '131456780004', '五点水': '131456780005'}\n```\n\n\n\n## 2、dict （字典）的创建 ##\n\n字典是另一种可变容器模型，且可存储任意类型对象。\n\n字典的每个键值(key=>value)对用冒号(:)分割，每个对之间用逗号(,)分割，整个字典包括在花括号({})中 ,格式如下所示：\n\n```python\ndict = {key1 : value1, key2 : value2 }\n```\n\n注意：键必须是唯一的，但值则不必。值可以取任何数据类型，但键必须是不可变的。\n\n创建 dict（字典）实例：\n\n```python\ndict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}\ndict2={'abc':1234,1234:'abc'}\n```\n\n\n\n## 3、访问 dict （字典） ##\n\n我们知道了怎么创建列表了，回归到一开始提出到的问题，为什么使用字典能让我们很快的找出某个童鞋的电话呢？\n\n\n\n```python\nname = {'一点水': '131456780001', '两点水': '131456780002', '三点水': '131456780003', '四点水': '131456780004', '五点水': '131456780005'}\n\nprint(name['两点水'])\n```\n\n\n输出的结果：\n\n```\n131456780002\n```\n\n可以看到，如果你知道某个人的名字，也就是 key 值， 就能很快的查找到他对应的电话号码，也就是 Value 。\n\n这里需要注意的一点是：如果字典中没有这个键，是会报错的。\n\n\n\n## 4、修改 dict （字典） ##\n\n向字典添加新内容的方法是增加新的键/值对，修改或删除已有键/值对\n\n```python\n#-*-coding:utf-8-*-\ndict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}\nprint(dict1)\n# 新增一个键值对\ndict1['jack']='444444'\nprint(dict1)\n# 修改键值对\ndict1['liangdianshui']='555555'\nprint(dict1)\n```\n\n输出的结果：\n\n```\n{'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}\n{'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333', 'jack': '444444'}\n{'liangdianshui': '555555', 'twowater': '222222', '两点水': '333333', 'jack': '444444'}\n```\n\n## 5、删除 dict （字典） ##\n\n通过 `del` 可以删除 dict （字典）中的某个元素，也能删除 dict （字典）\n\n通过调用 `clear()` 方法可以清除字典中的所有元素\n\n```python\n#-*-coding:utf-8-*-\ndict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333'}\nprint(dict1)\n# 通过 key 值，删除对应的元素\ndel dict1['twowater']\nprint(dict1)\n# 删除字典中的所有元素\ndict1.clear()\nprint(dict1)\n# 删除字典\ndel dict1\n```\n\n输出的结果：\n\n```\n{'liangdianshui': '111111', 'twowater': '222222', '两点水': '333333'}\n{'liangdianshui': '111111', '两点水': '333333'}\n{}\n```\n\n## 6、 dict （字典）使用时注意的事项 ##\n\n(1) dict （字典）是不允许一个键创建两次的，但是在创建 dict （字典）的时候如果出现了一个键值赋予了两次，会以最后一次赋予的值为准\n\n例如：\n\n```python\n#-*-coding:utf-8-*-\ndict1={'liangdianshui':'111111' ,'twowater':'222222' ,'两点水':'333333','twowater':'444444'}\nprint(dict1)\nprint(dict1['twowater'])\n```\n\n输出的结果：\n\n```\n{'liangdianshui': '111111', 'twowater': '444444', '两点水': '333333'}\n444444\n```\n\n\n(2) dict （字典）键必须不可变，可是键可以用数字，字符串或元组充当，但是就是不能使用列表\n\n例如：\n\n```python\n#-*-coding:utf-8-*-\ndict1={'liangdianshui':'111111' ,123:'222222' ,(123,'tom'):'333333','twowater':'444444'}\nprint(dict1)\n```\n\n输出结果：\n\n```\n{'liangdianshui': '111111', 123: '222222', (123, 'tom'): '333333', 'twowater': '444444'}\n```\n\n(3) dict 内部存放的顺序和 key 放入的顺序是没有任何关系\n\n和 list 比较，dict 有以下几个特点：\n\n* 查找和插入的速度极快，不会随着key的增加而变慢\n\n* 需要占用大量的内存，内存浪费多\n\n而list相反：\n\n* 查找和插入的时间随着元素的增加而增加\n\n* 占用空间小，浪费内存很少\n\n\n## 7、dict （字典） 的函数和方法 ##\n\n|方法和函数|描述|\n|---------|--------|\n|len(dict)|计算字典元素个数|\n|str(dict)|输出字典可打印的字符串表示|\n|type(variable)|返回输入的变量类型，如果变量是字典就返回字典类型|\n|dict.clear()|删除字典内所有元素|\n|dict.copy()|返回一个字典的浅复制|\n|dict.values()|以列表返回字典中的所有值|\n|popitem()|随机返回并删除字典中的一对键和值|\n|dict.items()|以列表返回可遍历的(键, 值) 元组数组|\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python4/Preface.md",
    "content": "# 前言 #\n\n上一篇文章出现了个明显的知识点错误，不过感谢有个网友的提出，及时进行了修改。也希望各位多多包涵。\n\n>注：(2019年09月01日15:28:00) 在修改文章的时候，发现自己两年前写的像屎一样, 忍不住还在群里吐槽一番。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-01-072923.png)\n\n\n# 目录 #\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-01-Dict%20%E5%92%8C%20Set.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python4/Set.md",
    "content": "# 二、set #\n\npython 的 set 和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素。\n\nset 和 dict 类似，但是 set 不存储 value 值的。\n\n\n## 1、set 的创建 ##\n\n创建一个 set，需要提供一个 list 作为输入集合\n\n```python\nset1=set([123,456,789])\nprint(set1)\n```\n\n输出结果：\n\n```\n{456, 123, 789}\n```\n\n传入的参数 `[123,456,789]` 是一个 list，而显示的 `{456, 123, 789}` 只是告诉你这个 set 内部有 456, 123, 789 这 3 个元素，显示的顺序跟你参数中的 list 里的元素的顺序是不一致的，这也说明了 set 是无序的。\n\n还有一点，我们观察到输出的结果是在大括号中的，经过之前的学习，可以知道，tuple (元组) 使用小括号，list (列表) 使用方括号, dict (字典) 使用的是大括号，dict 也是无序的，只不过 dict 保存的是 key-value 键值对值，而 set 可以理解为只保存 key 值。\n\n回忆一下，在 dict （字典） 中创建时，有重复的 key ，会被后面的 key-value 值覆盖的，而 重复元素在 set 中自动被过滤的。\n\n\n```python\nset1=set([123,456,789,123,123])\nprint(set1)\n```\n\n输出的结果：\n\n```\n{456, 123, 789}\n```\n\n## 2、set 添加元素 ##\n\n通过 add(key) 方法可以添加元素到 set 中，可以重复添加，但不会有效果\n\n```python\nset1=set([123,456,789])\nprint(set1)\nset1.add(100)\nprint(set1)\nset1.add(100)\nprint(set1)\n```\n\n输出结果：\n```\n{456, 123, 789}\n{456, 123, 100, 789}\n{456, 123, 100, 789}\n```\n\n## 3、set 删除元素 ##\n\n通过 remove(key) 方法可以删除 set 中的元素\n\n```python\nset1=set([123,456,789])\nprint(set1)\nset1.remove(456)\nprint(set1)\n```\n\n输出的结果：\n\n```\n{456, 123, 789}\n{123, 789}\n```\n\n\n## 4、set 的运用 ##\n\n因为 set 是一个无序不重复元素集，因此，两个 set 可以做数学意义上的 union(并集), intersection(交集), difference(差集) 等操作。\n\n![set集合运算](http://upload-images.jianshu.io/upload_images/2136918-733b1d1071f772bd?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n例子：\n\n```python\nset1=set('hello')\nset2=set(['p','y','y','h','o','n'])\nprint(set1)\nprint(set2)\n\n# 交集 (求两个 set 集合中相同的元素)\nset3=set1 & set2\nprint('\\n交集 set3:')\nprint(set3)\n# 并集 （合并两个 set 集合的元素并去除重复的值）\nset4=set1 | set2\nprint('\\n并集 set4:')\nprint(set4)\n# 差集\nset5=set1 - set2\nset6=set2 - set1\nprint('\\n差集 set5:')\nprint(set5)\nprint('\\n差集 set6:')\nprint( set6)\n\n\n# 去除海量列表里重复元素，用 hash 来解决也行，只不过感觉在性能上不是很高，用 set 解决还是很不错的\nlist1 = [111,222,333,444,111,222,333,444,555,666]  \nset7=set(list1)\nprint('\\n去除列表里重复元素 set7:')\nprint(set7)\n\n```\n\n运行的结果：\n\n```\n{'h', 'l', 'e', 'o'}\n{'h', 'n', 'o', 'y', 'p'}\n\n交集 set3:\n{'h', 'o'}\n\n并集 set4:\n{'h', 'p', 'n', 'e', 'o', 'y', 'l'}\n\n差集 set5:\n{'l', 'e'}\n\n差集 set6:\n{'p', 'y', 'n'}\n\n去除列表里重复元素 set7:\n{555, 333, 111, 666, 444, 222}\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python5/Cycle.md",
    "content": "# 二、循环语句 #\n\n\n\n## 1、什么是循环语句 ##\n\n一般编程语言都有循环语句，为什么呢？\n\n那就问一下自己，我们弄程序是为了干什么？\n\n那肯定是为了方便我们工作，优化我们的工作效率啊。\n\n而计算机和人类不同，计算机不怕苦也不怕累，也不需要休息，可以一直做。\n\n你要知道，计算机最擅长就是做重复的事情。\n\n所以这时候需要用到循环语句，循环语句允许我们执行一个语句或语句组多次。\n\n循环语句的一般形式如下：\n\n![python循环语句](http://upload-images.jianshu.io/upload_images/2136918-eaaae2fbfec3330f?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n在 Python 提供了 for 循环和 while 循环。\n\n这里又有一个问题了，如果我想让他运行了一百次之后停止，那该怎么做呢？\n\n这时候需要用到一些控制循环的语句：\n\n|循环控制语句|描述|\n|------|------|\n|break|在语句块执行过程中终止循环，并且跳出整个循环|\n|continue|在语句块执行过程中终止当前循环，跳出该次循环，执行下一次循环|\n|pass|pass 是空语句，是为了保持程序结构的完整性|\n\n这些控制语句是为了让我们告诉程序什么时候停止，什么时候不运行这次循环。\n\n\n\n\n## 2、 for 循环语句 ##\n\n我们先来看下 for 循环语句。\n\n它的流程图基本如下：\n\n\n![for循环的流程图](http://upload-images.jianshu.io/upload_images/2136918-a0728c1c488238af?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n基本的语法格式：\n\n```python\nfor iterating_var in sequence:\n   statements(s)\n```\n\n那么我们根据他的基本语法格式，随便写个例子测试一下：\n\n\n```python\nfor letter in 'Hello 两点水':\n    print(letter)\n```\n\n输出的结果如下：\n\n```txt\nH\ne\nl\nl\no\n\n两\n点\n水\n```\n\n从打印结果来看，它就是把字符串 `Hello 两点水`  一个一个字符的打印出来。\n\n那如果我们把字符串换为字典 dict 呢？\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-094741.png)\n\n你会发现只打印了字典 dict 中的每一个 key 值。\n\n很多时候，我都是建议大家学到一个新的知识点，都多去尝试。\n\n你尝试一遍，自己观察出来的结论，好过别人说十遍。\n\n如果你不知道怎么去试？\n\n可以根据我们的例子举一反三，比如上面的 for 循环，试了字符串，字典，那我们之前学的基本数据类型还有什么呢？\n\n不记得可以再返回去看看，可以把所有的基本类型都拿去尝试一下。\n\n比如，你试了之后，会发现整数和浮点数是不可以直接放在 for 循环里面的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-095313.png)\n\n\n\n\n\n\n## 3、 range() 函数 ##\n\nfor 循环还常常和 range() 函数搭配使用的。\n\n如果不知道 range() 函数 , 我们直接通过一段程序来理解。\n\n```python\nfor i in range(3):\n    print(i)\n```\n\n打印的结果为：\n\n```\n0\n1\n2\n```\n\n可见，打印了  0 到 3 。\n\n使用 range(x) 函数，就可以生成一个从 0 到 x-1 的整数序列。\n\n如果是 `range(a,b)`  函数，你可以生成了一个左闭右开的整数序列。\n\n其实例子中的  `range(3)` 可以写成 `range(0,3)`, 结果是一样的。\n\n其实使用 range() 函数，我们更多是为了把一段代码重复运行 n 次。\n\n这里提个问题，你仔细观察 range()  函数，上面说到的不管是 1 个参数的，还是 2 个参数的都有什么共同的特点？\n\n不知道你们有没有发现，他都是每次递增 1 的。\n\n`range(3)` 就是 0 ，1，2  ，每次递增 1 。\n\n`range(3,6)`  就是 3 ，4 ，5 ，也是每次递增 1 的。\n\n那能不能每次不递增 1 呢？\n\n比如我想递增 2 呢？\n\n在程序的编写中，肯定会遇到这样的需求的。而 python 发展至今，range 函数肯定也会有这种功能。\n\n所以 range 函数还有一个三个参数的。\n\n比如  `range(0,10,2) ` , 它的意思是：从 0 数到 10（不取 10 ），每次间隔为 2 。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-09-065854.png)\n\n\n \n \n \n\n## 4、While 循环语句 ##\n\nWhile 循环和 for 循环的作用是一样的。\n\n我们先来看看 While 循环语句的样子。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-083137.png)\n\n程序输出的结果是：\n\n```txt\n5050\n```\n\n这个例子是计算 1 到 100 所有整数的和。\n\n\n\n## 5、for 循环和 whlie 循环的区别 ##\n\n之前也提到过了，如果一种语法能表示一个功能，那没必要弄两种语法来表示。\n\n竟然都是循环，for 循环和 while 循环肯定有他们的区别的。\n\n那什么时候才使用 for 循环和 while 循环呢？\n\n* for 循环主要用在迭代可迭代对象的情况。\n\n* while 循环主要用在需要满足一定条件为真，反复执行的情况。\n（死循环+break 退出等情况。）\n\n* 部分情况下，for 循环和 while 循环可以互换使用。\n\n例如：\n\n```python\nfor i in range(0, 10):\n    print(i)\n\n\ni = 0\nwhile i < 10:\n    print(i)\n    i = i + 1\n```\n\n虽然打印的结果是一样的，但是细细品味你会发现，他们执行的顺序和知道的条件是不同的。\n\n\n\n## 6、嵌套循环 ##\n\n循环语句和条件语句一样，都是可以嵌套的。\n\n具体的语法如下：\n\n**for 循环嵌套语法**\n\n```python\nfor iterating_var in sequence:\n   for iterating_var in sequence:\n      statements(s)\n   statements(s)\n```\n\n**while 循环嵌套语法**\n\n```python\nwhile expression:\n   while expression:\n      statement(s)\n   statement(s)\n```\n\n除此之外，你也可以在循环体内嵌入其他的循环体，如在 while 循环中可以嵌入 for 循环， 反之，你可以在 for 循环中嵌入 while 循环\n\n比如：\n\n当我们需要判断 sum 大于 1000 的时候，不在相加时，可以用到 break ，退出整个循环。\n\n```python\ncount = 1\nsum = 0\nwhile (count <= 100):\n    sum = sum + count\n    if ( sum > 1000):  #当 sum 大于 1000 的时候退出循环\n        break\n    count = count + 1\nprint(sum)\n```\n\n输出的结果：\n\n```txt\n1035\n```\n\n有时候，我们只想统计 1 到 100 之间的奇数和，那么也就是说当 count 是偶数，也就是双数的时候，我们需要跳出当次的循环，不想加，这时候可以用到 break\n\n```python\ncount = 1\nsum = 0\nwhile (count <= 100):\n    if ( count % 2 == 0):  # 双数时跳过输出\n        count = count + 1\n        continue\n    sum = sum + count\n    count = count + 1\nprint(sum)\n```\n\n输出的语句：\n\n```txt\n2500\n```\n\n还有：\n\n```python\nfor num in range(10,20):  # 迭代 10 到 20 之间的数字\n   for i in range(2,num): # 根据因子迭代\n      if num%i == 0:      # 确定第一个因子\n         j=num/i          # 计算第二个因子\n         print ('%d 是一个合数' % num)\n         break            # 跳出当前循环\n   else:                  # 循环的 else 部分\n      print ('%d 是一个质数' % num)\n```\n\n输出的结果：\n\n```txt\n10 是一个合数\n11 是一个质数\n12 是一个合数\n13 是一个质数\n14 是一个合数\n15 是一个合数\n16 是一个合数\n17 是一个质数\n18 是一个合数\n19 是一个质数\n```\n\n\n当然，这里还用到了  `for … else`  语句。\n\n其实 for 循环中的语句和普通的没有区别，else 中的语句会在循环正常执行完（即 for 不是通过 break 跳出而中断的）的情况下执行。\n\n当然有 `for … else`  ，也会有   `while … else` 。他们的意思都是一样的。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python5/Example.md",
    "content": "# 三、条件语句和循环语句综合实例 #\n\n## 1、打印九九乘法表 ##\n\n```python\n# -*- coding: UTF-8 -*-\n\n# 打印九九乘法表\nfor i in range(1, 10):\n        for j in range(1, i+1):\n            # 打印语句中，大括号及其里面的字符 (称作格式化字段) 将会被 .format() 中的参数替换,注意有个点的\n            print('{}x{}={}\\t'.format(i, j, i*j), end='')  \n        print()\n```\n\n输出的结果:\n\n```txt\n1x1=1   \n2x1=2   2x2=4   \n3x1=3   3x2=6   3x3=9   \n4x1=4   4x2=8   4x3=12  4x4=16  \n5x1=5   5x2=10  5x3=15  5x4=20  5x5=25  \n6x1=6   6x2=12  6x3=18  6x4=24  6x5=30  6x6=36  \n7x1=7   7x2=14  7x3=21  7x4=28  7x5=35  7x6=42  7x7=49  \n8x1=8   8x2=16  8x3=24  8x4=32  8x5=40  8x6=48  8x7=56  8x8=64  \n9x1=9   9x2=18  9x3=27  9x4=36  9x5=45  9x6=54  9x7=63  9x8=72  9x9=81 \n```\n\n\n## 2、判断是否是闰年 ##\n\n```python\n\n# 判断是否是闰年\n\nyear = int(input(\"请输入一个年份: \"))\nif (year % 4) == 0 and (year % 100) != 0 or (year % 400) == 0:\n    print('{0} 是闰年' .format(year))\nelse:\n     print('{0} 不是闰年' .format(year))\n\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python5/If.md",
    "content": "# 一、条件语句 #\n\n\n## 1、什么是条件语句 ##\n\n\nPython 条件语句跟其他语言基本一致的，都是通过一条或多条语句的执行结果（ True 或者 False ）来决定执行的代码块。\n\nPython 程序语言指定任何非 0 和非空（null）值为 True，0 或者 null 为 False。\n\n执行的流程图如下：\n\n![if语句流程图](http://upload-images.jianshu.io/upload_images/2136918-4ee2486190450a1a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n\n## 2、if 语句的基本形式 ##\n\nPython 中，if 语句的基本形式如下：\n\n```python\nif 判断条件：\n    执行语句……\nelse：\n    执行语句……\n```\n\n之前的章节也提到过，Python 语言有着严格的缩进要求，因此这里也需要注意缩进，也不要少写了冒号 `:` 。\n\nif 语句的判断条件可以用>（大于）、<(小于)、==（等于）、>=（大于等于）、<=（小于等于）来表示其关系。\n\n例如：\n\n```python\n# -*-coding:utf-8-*-\n\nresults=59\n\nif results>=60:\n    print ('及格')\nelse :\n    print ('不及格')\n\n```\n\n输出的结果为：\n\n```txt\n不及格\n```\n\n上面也说到，非零数值、非空字符串、非空 list 等，判断为 True，否则为 False。因此也可以这样写：\n\n```python\nnum = 6\nif num :\n    print('Hello Python')\n```\n\n输出的结果如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-072713.png)\n\n可见，把结果打印出来了。\n\n那如果我们把 `num ` 改为空字符串呢？\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-072941.png)\n\n很明显，空字符串是为 False 的，不符合条件语句，因此不会执行到  `print('Hello Python')`  这段代码。\n\n还有再啰嗦一点，提醒一下，在条件判断代码中的冒号 `:` 后、下一行内容是一定要缩进的。不缩进是会报错的。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-073432.png)\n\n冒号和缩进是一种语法。它会帮助 Python 区分代码之间的层次，理解条件执行的逻辑及先后顺序。\n\n\n\n## 3、if 语句多个判断条件的形式 ##\n\n有些时候，我们的判断语句不可能只有两个，有些时候需要多个，比如上面的例子中大于 60 的为及格，那我们还要判断大于 90 的为优秀，在 80 到 90 之间的良好呢？\n\n这时候需要用到 if 语句多个判断条件，\n\n用伪代码来表示：\n\n```python\nif 判断条件1:\n    执行语句1……\nelif 判断条件2:\n    执行语句2……\nelif 判断条件3:\n    执行语句3……\nelse:\n    执行语句4……\n```\n\n实例：\n\n```python\n# -*-coding:utf-8-*-\n\nresults = 89\n\nif results > 90:\n    print('优秀')\nelif results > 80:\n    print('良好')\nelif results > 60:\n    print ('及格')\nelse :\n    print ('不及格')\n\n```\n\n输出的结果：\n\n```txt\n良好\n```\n\n\n\n## 4、if 语句多个条件同时判断 ##\n\n有时候我们会遇到多个条件的时候该怎么操作呢？\n\n比如说要求 java 和 python 的考试成绩要大于 80 分的时候才算优秀，这时候该怎么做？\n\n这时候我们可以结合 `or` 和  `and` 来使用。\n\nor （或）表示两个条件有一个成立时判断条件成功\n \nand （与）表示只有两个条件同时成立的情况下，判断条件才成功。\n\n例如：\n\n```python\n# -*-coding:utf-8-*-\n\njava = 86\npython = 68\n\nif java > 80 and  python > 80:\n    print('优秀')\nelse :\n    print('不优秀')\n\nif ( java >= 80  and java < 90 )  or ( python >= 80 and python < 90):\n    print('良好')\n\n```\n\n输出结果：\n\n```txt\n不优秀\n良好\n```\n\n注意：if 有多个条件时可使用括号来区分判断的先后顺序，括号中的判断优先执行，此外 and 和 or 的优先级低于 >（大于）、<（小于）等判断符号，即大于和小于在没有括号的情况下会比与或要优先判断。\n\n## 5、if 嵌套 ##\n\nif 嵌套是指什么呢？\n\n就跟字面意思差不多，指 if 语句中可以嵌套 if 语句。\n\n比如上面说到的例子，也可以用 if 嵌套来写。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-07-080557.png)\n\n当然这只是为了说明 if 条件语句是可以嵌套的。如果是这个需求，我个人还是不太建议这样使用 if 嵌套的，因为这样代码量多了，而且嵌套太多，也不方便阅读代码。\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python5/Preface.md",
    "content": "# 前言 #\n\n通常都听到别人说，计算机很牛逼，很聪明，其实计算机一点都不聪明，光是你要跟他沟通，都会气 shi 你，聪明的是在写程序的你。\n\n写程序就是跟计算机沟通，告诉它要做什么。\n\n竟然是这样，那么肯定缺少不了一些沟通逻辑。比如你要告诉计算机在什么情况下做什么？或者在哪个时间点做什么？\n\n这都需要用到逻辑判断。这一章节，主要就是说这个。\n\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-09-09-%E6%9D%A1%E4%BB%B6%E8%AF%AD%E5%8F%A5%E5%92%8C%E5%BE%AA%E7%8E%AF%E8%AF%AD%E5%8F%A5.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/1.md",
    "content": "# 一、Python 自定义函数的基本步骤 #\n\n\n\n\n## 1、什么是函数 ##\n\n函数，其实我们一开始学 Python 的时候就接触过。\n\n不过我们使用的大多数都是 Python 的内置函数。\n\n比如基本每个章节都会出现的 `print()`  函数。\n\n而现在，我们主要学习的是自定义函数。\n\n**各位有没有想过为什么需要函数呢？**\n\n如果要想回答这个问题，我们需要先了解函数是什么？\n\n函数就是组织好的，可重复使用的，用来实现单一，或相关联功能的代码段。\n\n没错，函数其实就是把代码抽象出来的代码段。\n\n那为什么要抽象出来呢？\n\n**方便我们使用，方便我们重复使用。**\n\n**函数的本质就是我们把一些数据喂给函数，让他内部消化，然后吐出你想要的东西，至于他怎么消化的，我们不需要知道，它内部解决。**\n\n怎么理解这句话呢？\n\n举个例子，好比每次用到的 print 函数，我们都知道这个函数的作用是可以把我们的数据输出到控制台，让我们看到。所以 `print('两点水')` , 我们想打印 `两点水` 出来，就把 `两点水` 这个数据喂给  `print` 函数，然后他就直接把结果打印到控制台上了。\n\n\n\n\n\n\n\n## 2、怎么自定义函数 ##\n\n怎么自定义函数？\n\n要知道怎么定义函数，就要知道函数的组成部分是怎样的。\n\n```python\ndef 函数名(参数1，参数2....参数n):\n    函数体\n    return 语句\n```\n\n这就是 Python 函数的组成部分。\n\n所以自定义函数，基本有以下规则步骤：\n\n* 函数代码块以 def 关键词开头，后接函数标识符名称和圆括号()\n* 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数\n* 函数的第一行语句可以选择性地使用文档字符串（用于存放函数说明）\n* 函数内容以冒号起始，并且缩进\n* return [表达式] 结束函数，选择性地返回一个值给调用方。不带表达式的 return 相当于返回 None。\n\n语法示例：\n\n```python\ndef functionname( parameters ):\n   \"函数_文档字符串\"\n   function_suite\n   return [expression]\n```\n\n实例：\n\n1. def 定义一个函数，给定一个函数名 sum \n2. 声明两个参数 num1 和 num2\n3. 函数的第一行语句进行函数说明：两数之和\n4. 最终 return 语句结束函数，并返回两数之和\n\n```python\ndef sum(num1,num2):\n\t\"两数之和\"\n\treturn num1+num2\n\n# 调用函数\nprint(sum(5,6))\n```\n\n输出结果：\n\n```python\n11\n```\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/2.md",
    "content": "# 二、函数返回值 #\n\n通过上面的学习，可以知道通过 return [表达式] 语句用于退出函数，选择性地向调用方返回一个表达式。\n\n**不带参数值的 return 语句返回 None。**\n\n具体示例：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef sum(num1,num2):\n\t# 两数之和\n\tif not (isinstance (num1,(int ,float)) and isinstance (num2,(int ,float))):\n\t\traise TypeError('参数类型错误')\n\treturn num1+num2\n\nprint(sum(1,2))\n```\n\n返回结果：\n\n```txt\n3\n```\n\n这个示例，还通过内置函数`isinstance()`进行数据类型检查，检查调用函数时参数是否是整形和浮点型。如果参数类型不对，会报错，提示 `参数类型错误`,如图：\n\n![检查函数参数是否正确](http://upload-images.jianshu.io/upload_images/2136918-cfe5907d67e912d8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n当然，函数也可以返回多个值，具体实例如下：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef  division ( num1, num2 ):\n\t# 求商与余数\n         a = num1 % num2\n         b = (num1-a) / num2\n         return b , a\n\nnum1 , num2 = division(9,4)\ntuple1 = division(9,4)\n\nprint (num1,num2)\nprint (tuple1)\n```\n\n输出的值：\n\n```txt\n2.0 1\n(2.0, 1)\n```\n\n认真观察就可以发现，尽管从第一个输出值来看，返回了多个值，实际上是先创建了一个元组然后返回的。\n\n回忆一下，元组是可以直接用逗号来创建的，观察例子中的 ruturn ，可以发现实际上我们使用的是逗号来生成一个元组。\n\nPython 语言中的函数返回值可以是多个，而其他语言都不行，这是Python 相比其他语言的简便和灵活之处。\n\n**Python 一次接受多个返回值的数据类型就是元组。**\n\n不知道此刻你还记不记得元组的相关知识，如果不记得，建议现在立刻写几个例子回忆一下，比如如何获取元组的第一个元素出来。\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/3.md",
    "content": "# 三、函数的参数 #\n\n\n\n\n## 1、函数的参数类型 ##\n\n设置与传递参数是函数的重点，而 Python 的函数对参数的支持非常的灵活。\n\n主要的参数类型有：默认参数、关键字参数（位置参数）、不定长参数。\n\n下面我们将一一了解这几种参数。\n\n\n\n\n## 2、默认参数 ##\n\n有时候，我们自定义的函数中，如果调用的时候没有设置参数，需要给个默认值，这时候就需要用到默认值参数了。\n\n默认参数，只要在构造函数参数的时候，给参数赋值就可以了\n\n例如：\n\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_user_info( name , age , sex = '男' ):\n    # 打印用户信息\n    print('昵称：{}'.format(name) , end = ' ')\n    print('年龄：{}'.format(age) , end = ' ')\n    print('性别：{}'.format(sex))\n    return;\n\n# 调用 print_user_info 函数\n\nprint_user_info( '两点水' , 18 , '女')\nprint_user_info( '三点水' , 25 )\n```\n\n输出结果：\n\n```txt\n昵称：两点水 年龄：18 性别：女\n昵称：三点水 年龄：25 性别：男\n```\n\n从输出结果可以看到，当你设置了默认参数的时候，在调用函数的时候，不传该参数，就会使用默认值。\n\n但是这里需要注意的一点是：**只有在形参表末尾的那些参数可以有默认参数值**，也就是说你不能在声明函数形参的时候，先声明有默认值的形参而后声明没有默认值的形参。\n\n这是因为赋给形参的值是根据位置而赋值的。例如，def func(a, b=1) 是有效的，但是 def func(a=1, b) 是 无效 的。 \n\n默认值参数就这样结束了吗？\n\n还没有的，细想一下，如果参数中是一个可修改的容器比如一个 lsit （列表）或者 dict （字典），那么我们使用什么来作为默认值呢？\n\n我们可以使用 None 作为默认值。就像下面这个例子一样：\n\n```python\n# 如果 b 是一个 list ，可以使用 None 作为默认值\ndef print_info( a , b = None ):\n    if b is None :\n        b=[]\n    return;\n```\n\n认真看下例子，会不会有这样的疑问呢？在参数中我们直接 `b=[]` 不就行了吗？\n\n也就是写成下面这个样子：\n\n```python\ndef print_info( a , b = [] ):\n    return;\n```\n对不对呢？\n\n运行一下也没发现错误啊，可以这样写吗？\n\n这里需要特别注意的一点：**默认参数的值是不可变的对象，比如None、True、False、数字或字符串**，如果你像上面的那样操作，当默认值在其他地方被修改后你将会遇到各种麻烦。\n\n这些修改会影响到下次调用这个函数时的默认值。\n\n示例如下：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_info( a , b = [] ):\n    print(b)\n    return b ;\n\nresult = print_info(1)\n\nresult.append('error')\n\nprint_info(2)\n```\n\n输出的结果：\n\n```txt\n[]\n['error']\n```\n\n认真观察，你会发现第二次输出的值根本不是你想要的，因此切忌不能这样操作。\n\n\n还有一点，有时候我就是不想要默认值啊，只是想单单判断默认参数有没有值传递进来，那该怎么办？\n\n我们可以这样做：\n\n```python\n_no_value =object()\n\ndef print_info( a , b = _no_value ):\n    if b is _no_value :\n        print('b 没有赋值')\n    return;\n```\n\n这里的 `object` 是 python 中所有类的基类。 你可以创建 `object` 类的实例，但是这些实例没什么实际用处，因为它并没有任何有用的方法， 也没有任何实例数据(因为它没有任何的实例字典，你甚至都不能设置任何属性值)。 你唯一能做的就是测试同一性。也正好利用这个特性，来判断是否有值输入。\n\n\n\n\n## 3、关键字参数（位置参数） ##\n\n一般情况下，我们需要给函数传参的时候，是要按顺序来的，如果不对应顺序，就会传错值。\n\n不过在 Python 中，可以通过参数名来给函数传递参数，而不用关心参数列表定义时的顺序，这被称之为关键字参数。\n\n使用关键参数有两个优势 ：\n\n* 由于我们不必担心参数的顺序，使用函数变得更加简单了。\n\n* 假设其他参数都有默认值，我们可以只给我们想要的那些参数赋值\n\n具体看例子：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_user_info( name ,  age  , sex = '男' ):\n    # 打印用户信息\n    print('昵称：{}'.format(name) , end = ' ')\n    print('年龄：{}'.format(age) , end = ' ')\n    print('性别：{}'.format(sex))\n    return;\n\n# 调用 print_user_info 函数\n\nprint_user_info( name = '两点水' ,age = 18 , sex = '女')\nprint_user_info( name = '两点水' ,sex = '女', age = 18 )\n\n```\n\n输出的值：\n\n```txt\n昵称：两点水 年龄：18 性别：女\n昵称：两点水 年龄：18 性别：女\n```\n\n\n\n\n\n## 4、不定长参数 ##\n\n或许有些时候，我们在设计函数的时候，我们有时候无法确定传入的参数个数。\n\n那么我们就可以使用不定长参数。\n\nPython 提供了一种元组的方式来接受没有直接定义的参数。这种方式在参数前边加星号 `*` 。\n\n如果在函数调用时没有指定参数，它就是一个空元组。我们也可以不向函数传递未命名的变量。\n\n例如：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_user_info( name ,  age  , sex = '男' , * hobby):\n    # 打印用户信息\n    print('昵称：{}'.format(name) , end = ' ')\n    print('年龄：{}'.format(age) , end = ' ')\n    print('性别：{}'.format(sex) ,end = ' ' )\n    print('爱好：{}'.format(hobby))\n    return;\n\n# 调用 print_user_info 函数\nprint_user_info( '两点水' ,18 , '女', '打篮球','打羽毛球','跑步')\n\n```\n\n输出的结果：\n\n```python\n昵称：两点水 年龄：18 性别：女 爱好：('打篮球', '打羽毛球', '跑步')\n```\n\n通过输出的结果可以知道，`*hobby`是可变参数，且 hobby 其实就是一个 tuple （元祖）\n\n\n可变长参数也支持关键字参数（位置参数），没有被定义的关键参数会被放到一个字典里。\n\n这种方式即是在参数前边加 `**`,更改上面的示例如下：\n\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_user_info( name ,  age  , sex = '男' , ** hobby ):\n    # 打印用户信息\n    print('昵称：{}'.format(name) , end = ' ')\n    print('年龄：{}'.format(age) , end = ' ')\n    print('性别：{}'.format(sex) ,end = ' ' )\n    print('爱好：{}'.format(hobby))\n    return;\n\n# 调用 print_user_info 函数\nprint_user_info( name = '两点水' , age = 18 , sex = '女', hobby = ('打篮球','打羽毛球','跑步'))\n\n```\n\n输出的结果：\n\n```txt\n昵称：两点水 年龄：18 性别：女 爱好：{'hobby': ('打篮球', '打羽毛球', '跑步')}\t\n```\n\n通过对比上面的例子和这个例子，可以知道，`*hobby`是可变参数，且 hobby其实就是一个 tuple （元祖），`**hobby`是关键字参数，且 hobby 就是一个 dict （字典）\n\n\n\n## 5、只接受关键字参数 ##\n\n关键字参数使用起来简单，不容易参数出错，那么有些时候，我们定义的函数希望某些参数强制使用关键字参数传递，这时候该怎么办呢？\n\n将强制关键字参数放到某个`*`参数或者单个`*`后面就能达到这种效果,比如：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef print_user_info( name , *, age  , sex = '男' ):\n    # 打印用户信息\n    print('昵称：{}'.format(name) , end = ' ')\n    print('年龄：{}'.format(age) , end = ' ')\n    print('性别：{}'.format(sex))\n    return;\n\n# 调用 print_user_info 函数\nprint_user_info( name = '两点水' ,age = 18 , sex = '女' )\n\n# 这种写法会报错，因为 age ，sex 这两个参数强制使用关键字参数\n#print_user_info( '两点水' , 18 , '女' )\nprint_user_info('两点水',age='22',sex='男')\n```\n\n通过例子可以看，如果 `age` , `sex` 不使用关键字参数是会报错的。\n\n很多情况下，使用强制关键字参数会比使用位置参数表意更加清晰，程序也更加具有可读性。使用强制关键字参数也会比使用 `**kw` 参数更好且强制关键字参数在一些更高级场合同样也很有用。\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/4.md",
    "content": "# 四、函数传值问题 #\n\n先看一个例子：\n\n```python\n# -*- coding: UTF-8 -*-\ndef chagne_number( b ):\n    b = 1000\n\nb = 1\nchagne_number(b)\nprint( b )\n```\n\n最后输出的结果为：\n\n```txt\n1\n```\n\n先看看运行的结果？\n\n想一下为什么打印的结果是 1 ，而不是 1000 ？\n\n其实把问题归根结底就是，为什么通过函数 `chagne_number` 没有更改到 b 的值？\n\n这个问题很多编程语言都会讲到，原理解释也是差不多的。\n\n这里主要是函数参数的传递中，传递的是类型对象，之前也介绍了 Python 中基本的数据类型等。而这些类型对象可以分为可更改类型和不可更改的类型\n\n**在 Python 中，字符串，整形，浮点型，tuple 是不可更改的对象，而 list ， dict 等是可以更改的对象。**\n\n例如：\n\n**不可更改的类型**：变量赋值 `a = 1`，其实就是生成一个整形对象 1 ，然后变量 a 指向 1，当 `a = 1000` 其实就是再生成一个整形对象 1000，然后改变 a 的指向，不再指向整形对象 1 ，而是指向 1000，最后 1 会被丢弃\n\n**可更改的类型**：变量赋值 `a = [1,2,3,4,5,6]` ，就是生成一个对象 list ，list 里面有 6 个元素，而变量 a 指向 list ，`a[2] = 5`则是将 list a 的第三个元素值更改,这里跟上面是不同的，并不是将 a 重新指向，而是直接修改 list 中的元素值。\n\n![指向问题](http://upload-images.jianshu.io/upload_images/2136918-31b1031d75e1cec9?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n这也将影响到函数中参数的传递了：\n\n**不可更改的类型**：类似 c++ 的值传递，如 整数、字符串、元组。如fun（a），传递的只是 a 的值，没有影响 a 对象本身。比如在 fun（a）内部修改 a 的值，只是修改另一个复制的对象，不会影响 a 本身。\n\n**可更改的类型**：类似 c++ 的引用传递，如 列表，字典。如 fun（a），则是将 a 真正的传过去，修改后 fun 外部的 a 也会受影响\n\n因此，在一开始的例子中，`b = 1`,创建了一个整形对象 1 ，变量 b 指向了这个对象，然后通过函数 chagne_number 时，按传值的方式复制了变量 b ，传递的只是 b 的值，并没有影响到 b 的本身。具体可以看下修改后的实例，通过打印的结果更好的理解。\n\n```python\n# -*- coding: UTF-8 -*-\ndef chagne_number( b ):\n    print('函数中一开始 b 的值：{}' .format( b ) )\n    b = 1000\n    print('函数中 b 赋值后的值：{}' .format( b ) )\n\n\nb = 1\nchagne_number( b )\nprint( '最后输出 b 的值：{}' .format( b )  )\n\n\n```\n\n打印的结果：\n\n```txt\n函数中一开始 b 的值：1\n函数中 b 赋值后的值：1000\n最后输出 b 的值：1\n```\n\n当然，如果参数中的是可更改的类型，那么调用了这个函数后，原来的值也会被更改，具体实例如下：\n\n```python\n# -*- coding: UTF-8 -*-\n\ndef chagne_list( b ):\n    print('函数中一开始 b 的值：{}' .format( b ) )\n    b.append(1000)\n    print('函数中 b 赋值后的值：{}' .format( b ) )\n\n\nb = [1,2,3,4,5]\nchagne_list( b )\nprint( '最后输出 b 的值：{}' .format( b )  )\n```\n\n输出的结果：\n\n```txt\n函数中一开始 b 的值：[1, 2, 3, 4, 5]\n函数中 b 赋值后的值：[1, 2, 3, 4, 5, 1000]\n最后输出 b 的值：[1, 2, 3, 4, 5, 1000]\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/5.md",
    "content": "# 五、匿名函数 #\n\n有没有想过定义一个很短的回调函数，但又不想用 `def` 的形式去写一个那么长的函数，那么有没有快捷方式呢？\n\n答案是有的。\n\npython 使用 lambda 来创建匿名函数，也就是不再使用 def 语句这样标准的形式定义一个函数。\n\n匿名函数主要有以下特点：\n\n* lambda 只是一个表达式，函数体比 def 简单很多。\n* lambda 的主体是一个表达式，而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。\n* lambda 函数拥有自己的命名空间，且不能访问自有参数列表之外或全局命名空间里的参数。\n\n**基本语法**\n\n```python\nlambda [arg1 [,arg2,.....argn]]:expression\n```\n\n示例：\n\n```python\n# -*- coding: UTF-8 -*-\n\nsum = lambda num1 , num2 : num1 + num2;\n\nprint( sum( 1 , 2 ) )\n\n```\n\n输出的结果：\n\n```txt\n3\n```\n\n注意：**尽管 lambda 表达式允许你定义简单函数，但是它的使用是有限制的。 你只能指定单个表达式，它的值就是最后的返回值。也就是说不能包含其他的语言特性了， 包括多个语句、条件表达式、迭代以及异常处理等等。**\n\n匿名函数中，有一个特别需要注意的问题，比如，把上面的例子改一下：\n\n```python\n# -*- coding: UTF-8 -*-\n\nnum2 = 100\nsum1 = lambda num1 : num1 + num2 ;\n\nnum2 = 10000\nsum2 = lambda num1 : num1 + num2 ;\n\nprint( sum1( 1 ) )\nprint( sum2( 1 ) )\n```\n\n你会认为输出什么呢？第一个输出是 101，第二个是 10001，结果不是的，输出的结果是这样：\n\n```txt\n10001\n10001\n```\n\n**这主要在于 lambda 表达式中的 num2 是一个自由变量，在运行时绑定值，而不是定义时就绑定，这跟函数的默认值参数定义是不同的。所以建议还是遇到这种情况还是使用第一种解法。**\n\n"
  },
  {
    "path": "Article/PythonBasis/python6/Preface.md",
    "content": "# 前言 #\n\n函数这个章节内容有点多，对于新手，也有些不好理解。建议各位多看几篇，多敲几次代码。\n\n最后这是我的个人微信号，大家可以添加一下，交个朋友，一起讨论。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-07-070041.jpg)\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-07-%E5%87%BD%E6%95%B0.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python7/1.md",
    "content": "# 一、迭代 #\n\n什么叫做迭代？\n\n比如在 Java 中，我们通过 List 集合的下标来遍历 List 集合中的元素，在 Python 中，给定一个 list 或 tuple，我们可以通过 for 循环来遍历这个 list 或 tuple ，这种遍历就是迭代。\n\n可是，Python 的 `for` 循环抽象程度要高于 Java 的 `for` 循环的，为什么这么说呢？因为 Python 的 `for` 循环不仅可以用在 list 或tuple 上，还可以作用在其他可迭代对象上。\n\n也就是说，只要是可迭代的对象，无论有没有下标，都是可以迭代的。\n\n比如：\n\n```python\n\n# -*- coding: UTF-8 -*-\n\n# 1、for 循环迭代字符串\nfor char in 'liangdianshui' :\n    print ( char , end = ' ' )\n\nprint('\\n')\n\n# 2、for 循环迭代 list\nlist1 = [1,2,3,4,5]\nfor num1 in list1 :\n    print ( num1 , end = ' ' )\n\nprint('\\n')\n\n# 3、for 循环也可以迭代 dict （字典）\ndict1 = {'name':'两点水','age':'23','sex':'男'}\n\nfor key in dict1 :    # 迭代 dict 中的 key\n    print ( key , end = ' ' )\n\nprint('\\n')\n\nfor value in dict1.values() :   # 迭代 dict 中的 value\n\tprint ( value , end = ' ' )\n\nprint ('\\n')\n\n# 如果 list 里面一个元素有两个变量，也是很容易迭代的\nfor x , y in [ (1,'a') , (2,'b') , (3,'c') ] :\n\tprint ( x , y )\n\n```\n\n输出的结果如下：\n\n```txt\nl i a n g d i a n s h u i \n\n1 2 3 4 5 \n\nname age sex \n\n两点水 23 男 \n\n1 a\n2 b\n3 c\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python7/2.md",
    "content": "# 二、Python 迭代器 #\n\n上面简单的介绍了一下迭代，迭代是 Python 最强大的功能之一，是访问集合元素的一种方式。现在正式进入主题：迭代器，迭代器是一个可以记住遍历的位置的对象。\n\n迭代器对象从集合的第一个元素开始访问，直到所有的元素被访问完结束。\n\n迭代器只能往前不会后退。\n\n迭代器有两个基本的方法：iter() 和 next(),且字符串，列表或元组对象都可用于创建迭代器，迭代器对象可以使用常规 for 语句进行遍历，也可以使用 next() 函数来遍历。\n\n具体的实例：\n\n```python\n# 1、字符创创建迭代器对象\nstr1 = 'liangdianshui'\niter1 = iter ( str1 )\n\n# 2、list对象创建迭代器\nlist1 = [1,2,3,4]\niter2 = iter ( list1 )\n\n# 3、tuple(元祖) 对象创建迭代器\ntuple1 = ( 1,2,3,4 )\niter3 = iter ( tuple1 )\n\n# for 循环遍历迭代器对象\nfor x in iter1 :\n    print ( x , end = ' ' )\n\nprint('\\n------------------------')\n \n# next() 函数遍历迭代器\nwhile True :\n    try :\n        print ( next ( iter3 ) )\n    except StopIteration :\n        break\n\n```\n\n最后输出的结果：\n\n```txt\nl i a n g d i a n s h u i \n------------------------\n1\n2\n3\n4\n```\n"
  },
  {
    "path": "Article/PythonBasis/python7/3.md",
    "content": "# 三、list 生成式（列表生成式） #\n\n\n## 1、创建 list 的方式 ##\n\n之前经过我们的学习，都知道如何创建一个 list ，可是有些情况，用赋值的形式创建一个 list 太麻烦了，特别是有规律的 list ，一个一个的写，一个一个赋值，太麻烦了。比如要生成一个有 30 个元素的 list ，里面的元素为 1 - 30 。我们可以这样写：\n\n```python\n# -*- coding: UTF-8 -*-\n\nlist1=list ( range (1,31) )\nprint(list1)\n```\n\n输出的结果：\n\n```txt\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]\n```\n\n这个其实在之前也有提到过，打印九九乘法表，用这个方法其实就几句代码就可以了，具体可以看之前的这个章节：[条件语句和循环语句综合实例](../python5/Example.md)\n\n但是，如果用到 list 生成式，可以一句代码就生成九九乘法表了。\n\n你没听错，就是一句代码。\n\n具体实现：\n\n```python\nprint('\\n'.join([' '.join ('%dx%d=%2d' % (x,y,x*y)  for x in range(1,y+1)) for y in range(1,10)]))\n```\n\n最后输出的结果：\n\n```txt\n1x1= 1\n1x2= 2 2x2= 4\n1x3= 3 2x3= 6 3x3= 9\n1x4= 4 2x4= 8 3x4=12 4x4=16\n1x5= 5 2x5=10 3x5=15 4x5=20 5x5=25\n1x6= 6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36\n1x7= 7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49\n1x8= 8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64\n1x9= 9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81\n```\n\n不过，这里我们先要了解如何创建 list 生成式\n\n## 2、list 生成式的创建 ##\n\n首先，list 生成式的语法为：\n\n```python\n[expr for iter_var in iterable] \n[expr for iter_var in iterable if cond_expr]\n```\n\n第一种语法：首先迭代 iterable 里所有内容，每一次迭代，都把 iterable 里相应内容放到iter_var 中，再在表达式中应用该 iter_var 的内容，最后用表达式的计算值生成一个列表。\n\n第二种语法：加入了判断语句，只有满足条件的内容才把 iterable 里相应内容放到 iter_var 中，再在表达式中应用该 iter_var 的内容，最后用表达式的计算值生成一个列表。\n\n其实不难理解的，因为是 list 生成式，因此肯定是用 [] 括起来的，然后里面的语句是把要生成的元素放在前面，后面加 for 循环语句或者 for 循环语句和判断语句。\n\n例子：\n\n```python\n# -*- coding: UTF-8 -*-\nlist1=[x * x for x in range(1, 11)]\nprint(list1)\n```\n\n输出的结果：\n\n```txt\n[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]\n```\n\n可以看到，就是把要生成的元素 x * x 放到前面，后面跟 for 循环，就可以把 list 创建出来。那么 for 循环后面有 if 的形式呢？又该如何理解：\n\n```python\n# -*- coding: UTF-8 -*-\nlist1= [x * x for x in range(1, 11) if x % 2 == 0]\nprint(list1)\n```\n\n输出的结果：\n\n```txt\n[4, 16, 36, 64, 100]\n```\n\n这个例子是为了求 1 到 10 中偶数的平方根，上面也说到， `x * x` 是要生成的元素，后面那部分其实就是在 for 循环中嵌套了一个 if 判断语句。\n\n那么有了这个知识点，我们也可以猜想出，for 循环里面也嵌套 for 循环。具体示例：\n\n```python\n# -*- coding: UTF-8 -*-\nlist1= [(x+1,y+1) for x in range(3) for y in range(5)] \nprint(list1)\n```\n\n输出的结果：\n\n```txt\n[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]\n```\n\n其实知道了 list 生成式是怎样组合的，就不难理解这个东西了。因为 list 生成式只是把之前学习的知识点进行了组合，换成了一种更简洁的写法而已。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python7/4.md",
    "content": "# 四、生成器 #\n\n## 1、为什么需要生成器 ##\n\n通过上面的学习，可以知道列表生成式，我们可以直接创建一个列表。\n\n但是，受到内存限制，列表容量肯定是有限的。而且，创建一个包含 1000 万个元素的列表，不仅占用很大的存储空间，如果我们仅仅需要访问前面几个元素，那后面绝大多数元素占用的空间都白白浪费了。\n\n**所以，如果列表元素可以按照某种算法推算出来，那我们是否可以在循环的过程中不断推算出后续的元素呢？**\n\n这样就不必创建完整的 list，从而节省大量的空间。\n\n**在 Python 中，这种一边循环一边计算的机制，称为生成器：generator。**\n\n在 Python 中，使用了 yield 的函数被称为生成器（generator）。\n\n跟普通函数不同的是，生成器是一个返回迭代器的函数，只能用于迭代操作，更简单点理解生成器就是一个迭代器。\n\n在调用生成器运行的过程中，每次遇到 yield 时函数会暂停并保存当前所有的运行信息，返回 yield 的值。并在下一次执行 next()方法时从当前位置继续运行。\n\n那么如何创建一个生成器呢？\n\n\n## 2、生成器的创建 ##\n\n最简单最简单的方法就是把一个列表生成式的 `[]` 改成 `()`\n\n```python\n# -*- coding: UTF-8 -*-\ngen= (x * x for x in range(10))\nprint(gen)\n```\n\n输出的结果：\n\n```txt\n<generator object <genexpr> at 0x0000000002734A40>\n```\n\n创建 List 和 generator 的区别仅在于最外层的 `[]` 和 `()` 。\n\n但是生成器并不真正创建数字列表， 而是返回一个生成器，这个生成器在每次计算出一个条目后，把这个条目“产生” ( yield ) 出来。\n\n生成器表达式使用了“惰性计算” ( lazy evaluation，也有翻译为“延迟求值”，我以为这种按需调用 call by need 的方式翻译为惰性更好一些)，只有在检索时才被赋值（ evaluated ），所以在列表比较长的情况下使用内存上更有效。\n\n\n那么竟然知道了如何创建一个生成器，那么怎么查看里面的元素呢？\n\n## 3、遍历生成器的元素 ##\n\n按我们的思维，遍历用 for 循环，对了，我们可以试试：\n\n```python\n# -*- coding: UTF-8 -*-\ngen= (x * x for x in range(10))\n\nfor num  in  gen :\n\tprint(num)\n```\n\n没错，直接这样就可以遍历出来了。当然，上面也提到了迭代器，那么用 next() 可以遍历吗？当然也是可以的。\n\n\n## 4、以函数的形式实现生成器 ##\n\n上面也提到，创建生成器最简单最简单的方法就是把一个列表生成式的 `[]` 改成 `()`。为啥突然来个以函数的形式来创建呢？\n\n其实生成器也是一种迭代器，但是你只能对其迭代一次。\n\n这是因为它们并没有把所有的值存在内存中，而是在运行时生成值。你通过遍历来使用它们，要么用一个“for”循环，要么将它们传递给任意可以进行迭代的函数和结构。\n\n而且实际运用中，大多数的生成器都是通过函数来实现的。那么我们该如何通过函数来创建呢？\n\n先不急，来看下这个例子：\n\n```python\n# -*- coding: UTF-8 -*-\ndef my_function():\n    for i in range(10):\n        print ( i )\n\nmy_function()\n```\n\n输出的结果：\n\n```txt\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n```\n\n如果我们需要把它变成生成器，我们只需要把 `print ( i )` 改为 `yield i` 就可以了，具体看下修改后的例子：\n\n```python\n# -*- coding: UTF-8 -*-\ndef my_function():\n    for i in range(10):\n        yield i\n\nprint(my_function())\n```\n\n输出的结果：\n\n```txt\n<generator object my_function at 0x0000000002534A40>\n```\n\n但是，这个例子非常不适合使用生成器，发挥不出生成器的特点，生成器的最好的应用应该是：你不想同一时间将所有计算出来的大量结果集分配到内存当中，特别是结果集里还包含循环。因为这样会耗很大的资源。\n\n比如下面是一个计算斐波那契数列的生成器：\n\n```python\n# -*- coding: UTF-8 -*-\ndef fibon(n):\n    a = b = 1\n    for i in range(n):\n        yield a\n        a, b = b, a + b\n\n# 引用函数\nfor x in fibon(1000000):\n    print(x , end = ' ')\n```\n\n运行的效果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-07-%E8%AE%A1%E7%AE%97%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97%E7%9A%84%E7%94%9F%E6%88%90%E5%99%A8.gif)\n\n你看，运行一个这么大的参数，也不会说有卡死的状态，因为这种方式不会使用太大的资源。这里，最难理解的就是 generator 和函数的执行流程不一样。函数是顺序执行，遇到 return 语句或者最后一行函数语句就返回。而变成 generator 的函数，在每次调用 next() 的时候执行，遇到 yield语句返回，再次执行时从上次返回的 yield 语句处继续执行。\n\n比如这个例子：\n\n```python\n# -*- coding: UTF-8 -*-\ndef odd():\n    print ( 'step 1' )\n    yield ( 1 )\n    print ( 'step 2' )\n    yield ( 3 )\n    print ( 'step 3' )\n    yield ( 5 )\n\no = odd()\nprint( next( o ) )\nprint( next( o ) )\nprint( next( o ) )\n```\n\n输出的结果：\n\n```txt\nstep 1\n1\nstep 2\n3\nstep 3\n5\n```\n\n可以看到，odd 不是普通函数，而是 generator，在执行过程中，遇到 yield 就中断，下次又继续执行。执行 3 次 yield 后，已经没有 yield 可以执行了，如果你继续打印 `print( next( o ) ) ` ,就会报错的。所以通常在 generator 函数中都要对错误进行捕获。\n\n## 5、打印杨辉三角 ##\n\n通过学习了生成器，我们可以直接利用生成器的知识点来打印杨辉三角：\n\n```python\n# -*- coding: UTF-8 -*-\ndef triangles( n ):         # 杨辉三角形\n    L = [1]\n    while True:\n        yield L\n        L.append(0)\n        L = [ L [ i -1 ] + L [ i ] for i in range (len(L))]\n\nn= 0\nfor t in triangles( 10 ):   # 直接修改函数名即可运行\n    print(t)\n    n = n + 1\n    if n == 10:\n        break\n```\n\n输出的结果为：\n\n```txt\n[1]\n[1, 1]\n[1, 2, 1]\n[1, 3, 3, 1]\n[1, 4, 6, 4, 1]\n[1, 5, 10, 10, 5, 1]\n[1, 6, 15, 20, 15, 6, 1]\n[1, 7, 21, 35, 35, 21, 7, 1]\n[1, 8, 28, 56, 70, 56, 28, 8, 1]\n[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]\n```\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python7/5.md",
    "content": "# 五、迭代器和生成器综合例子 #\n\n因为迭代器和生成器基本是互通的，因此有些知识点需要综合在一起\n\n## 1、反向迭代 ##\n\n反向迭代，应该也是常有的需求了，比如从一开始迭代的例子里，有个输出 list 的元素，从 1 到 5 的\n\n```python\nlist1 = [1,2,3,4,5]\nfor num1 in list1 :\n    print ( num1 , end = ' ' )\n```\n\n那么我们从 5 到 1 呢？这也很简单， Python 中有内置的函数 `reversed()`\n\n```python\nlist1 = [1,2,3,4,5]\nfor num1 in reversed(list1) :\n    print ( num1 , end = ' ' )\n```\n\n方向迭代很简单，可是要注意一点就是：**反向迭代仅仅当对象的大小可预先确定或者对象实现了 `__reversed__()` 的特殊方法时才能生效。 如果两者都不符合，那你必须先将对象转换为一个列表才行**\n\n其实很多时候我们可以通过在自定义类上实现 `__reversed__()` 方法来实现反向迭代。不过有些知识点在之前的篇节中还没有提到，不过可以相应的看下，有编程基础的，学完上面的知识点应该也能理解的。\n\n```python\n# -*- coding: UTF-8 -*-\n\nclass Countdown:\n    def __init__(self, start):\n        self.start = start\n\n    def __iter__(self):\n    \t# Forward iterator\n        n = self.start\n        while n > 0:\n            yield n\n            n -= 1\n\n    def __reversed__(self):\n    \t# Reverse iterator\n        n = 1\n        while n <= self.start:\n            yield n\n            n += 1\n\nfor rr in reversed(Countdown(30)):\n    print(rr)\nfor rr in Countdown(30):\n    print(rr)\n```\n\n输出的结果是 1 到 30 然后 30 到 1 ，分别是顺序打印和倒序打印\n\n## 2、同时迭代多个序列 ##\n\n你想同时迭代多个序列，每次分别从一个序列中取一个元素。你遇到过这样的需求吗？\n\n为了同时迭代多个序列，使用 zip() 函数，具体示例：\n\n```python\n# -*- coding: UTF-8 -*-\n\nnames = ['laingdianshui', 'twowater', '两点水']\nages = [18, 19, 20]\nfor name, age in zip(names, ages):\n     print(name,age)\n```\n\n输出的结果：\n\n```txt\nlaingdianshui 18\ntwowater 19\n两点水 20\n```\n\n其实 zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器，其中 x 来自 a，y 来自 b。 一旦其中某个序列到底结尾，迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致。注意理解这句话喔，也就是说如果 a ， b 的长度不一致的话，以最短的为标准，遍历完后就结束。\n\n利用 `zip()` 函数，我们还可把一个 key 列表和一个 value 列表生成一个 dict （字典）,如下：\n\n```python\n# -*- coding: UTF-8 -*-\n\nnames = ['laingdianshui', 'twowater', '两点水']\nages = [18, 19, 20]\n\ndict1= dict(zip(names,ages))\n\nprint(dict1)\n\n```\n\n\n输出如下结果：\n\n```python\n{'laingdianshui': 18, 'twowater': 19, '两点水': 20}\n```\n\n这里提一下， `zip()` 是可以接受多于两个的序列的参数，不仅仅是两个。"
  },
  {
    "path": "Article/PythonBasis/python7/Preface.md",
    "content": "# 前言 #\n\n这篇内容挺多的，而且比内容不好理解。或许新手看完后，还会一脸懵逼，不过这是正常的，如果你看完后，是迷糊的，那么建议你继续学习后面的内容，等学完，再回来看几次。\n\n注：这也是我第二次修改内容没有改过的章节。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-07-%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%92%8C%E7%94%9F%E6%88%90%E5%99%A8.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/1.md",
    "content": "# 一、面向对象的概念 #\n\n\n\n## 1、面向对象的两个基本概念 ##\n\n编程语言中，一般有两种编程思维，面向过程和面向对象。\n\n面向过程，看重的是解决问题的过程。\n\n这好比我们解决日常生活问题差不多，分析解决问题的步骤，然后一步一步的解决。\n\n而面向对象是一种抽象，抽象是指用分类的眼光去看世界的一种方法。\n\nPython 就是一门面向对象的语言, \n\n如果你学过 Java ，就知道 Java 的编程思想就是：万事万物皆对象。Python 也不例外，在解决实际问题的过程中，可以把构成问题事务分解成各个对象。\n\n面向对象都有两个基本的概念，分别是类和对象。\n\n* **类**\n\n用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。\n\n* **对象**\n\n通过类定义的数据结构实例\n\n\n\n\n\n## 2、面向对象的三大特性 ##\n\n面向对象的编程语言，也有三大特性，继承，多态和封装性。\n\n* **继承**\n\n即一个派生类（derived class）继承基类（base class）的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。\n\n例如：一个 Dog 类型的对象派生自 Animal 类，这是模拟\"是一个（is-a）\"关系（例图，Dog 是一个 Animal ）。\n\n* **多态**\n\n它是指对不同类型的变量进行相同的操作，它会根据对象（或类）类型的不同而表现出不同的行为。\n\n* **封装性**\n\n“封装”就是将抽象得到的数据和行为（或功能）相结合，形成一个有机的整体（即类）；封装的目的是增强安全性和简化编程，使用者不必了解具体的实现细节，而只是要通过外部接口，一特定的访问权限来使用类的成员。\n\n\n**如果你是初次接触面向对象的编程语言，看到这里还一脸懵逼，不要紧，这是正常的。下面我们会通过大量的例子逐步了解 Python 的面向对象的知识。**\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/2.md",
    "content": "# 二、类的定义和调用 #\n\n\n\n## 1、怎么理解类？ ##\n\n类是什么？\n\n个人认为理解类，最简单的方式就是：类是一个变量和函数的集合。\n\n可以看下下面的这张图。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2020-03-09-014706.jpg)\n\n这张图很好的诠释了类，就是把变量和函数包装在一起。\n\n当然我们包装也不是毫无目的的包装，我们会把同性质的包装在一个类里，这样就方便我们重复使用。\n\n所以学到现在，你会发现很多编程的设计，都是为了我们能偷懒，重复使用。\n\n\n\n\n\n\n## 2、怎么定义类 ##\n\n知道了类是什么样子的，我们接下来就要学习怎么去定义类了。\n\n类定义语法格式如下：\n\n```python\nclass ClassName():\n    <statement-1>\n    .\n    .\n    .\n    <statement-N>\n```\n\n可以看到，我们是用 `class` 语句来自定义一个类的，其实这就好比我们是用 `def` 语句来定义一个函数一样。\n\n竟然说类是变量和方法的集合包，那么我们来创建一个类。\n\n```python\nclass ClassA():\n    var1 = 100\n    var2 = 0.01\n    var3 = '两点水'\n\n    def fun1():\n        print('我是 fun1')\n\n    def fun2():\n        print('我是 fun1')\n\n    def fun3():\n        print('我是 fun1')\n```\n\n你看，上面我们就定义了一个类，类名叫做 `ClassA` , 类里面的变量我们称之为属性，那么就是这个类里面有 3 个属性，分别是 `var1` , `var2` 和 `var3` 。除此之外，类里面还有 3 个类方法 `fun1()` , `fun2()` 和 `fun3()` 。\n\n\n\n\n\n## 3、怎么调用类属性和类方法 ##\n\n\n我们定义了类之后，那么我们怎么调用类里面的属性和方法呢？\n\n直接看下图：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2020-03-09-014728.jpg)\n\n这里就不文字解释了（注：做图也不容易啊，只有写过技术文章才知道，这系列文章，多耗时）\n\n好了，知道怎么调用之后，我们尝试一下：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2020-03-09-014742.jpg)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/3.md",
    "content": "# 三、类方法 #\n\n\n## 1、类方法如何调用类属性 ##\n\n通过上面我们已经会定义类了，那么这里讲一下在同一个类里，类方法如何调用类属性的。\n\n直接看个例子吧：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-110451.png)\n\n注意看，在类方法上面多了个 `@classmethod` ，这是干嘛用的呢？\n\n这是用于声明下面的函数是类函数。其实从名字就很好理解了。\n\nclass 就是类，method 就是方法。\n\n那是不是一定需要注明这个呢？\n\n答案是是的。\n\n如果你没使用，是会报错的。\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-110822.png)\n\n如果没有声明是类方法，方法参数中就没有 `cls` , 就没法通过 `cls` 获取到类属性。\n\n因此类方法，想要调用类属性，需要以下步骤： \n\n* 在方法上面，用 `@classmethod` 声明该方法是类方法。只有声明了是类方法，才能使用类属性\n* 类方法想要使用类属性，在第一个参数中，需要写上 `cls` ,  cls 是 class 的缩写，其实意思就是把这个类作为参数，传给自己，这样就可以使用类属性了。\n* 类属性的使用方式就是 `cls.变量名`\n\n\n记住喔，无论是 `@classmethod` 还是 `cls` ,都是不能省去的。\n\n省了都会报错。\n\n\n\n\n\n## 2、类方法传参 ##\n\n上面我们学习了类方法如何调用类属性，那么类方法如何传参呢？\n\n其实很简单，跟普通的函数一样，直接增加参数就好了。\n\n这个就直接上例子了：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-113458.png)\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/4.md",
    "content": "# 四、修改和增加类属性 #\n\n\n## 1、从内部增加和修改类属性 ##\n\n来，我们先来温习一下类的结构。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-034102.png)\n\n看着这个结构，提一个问题，如何修改类属性，也就是类里面的变量？\n\n从类结构来看，我们可以猜测，从类方法来修改，也就是从类内部来修改和增加类属性。\n\n看下具体的实例：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-120146.png)\n\n这里还是强调一下，例子还是要自己多写，不要只看，自己运行， 看效果。多想。\n\n\n\n\n## 2、从外部增加和修改类属性 ##\n\n我们刚刚看了通过类方法来修改类的属性，这时我们看下从外部如何修改和增加类属性。\n\n例子如下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-121135.png)\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/5.md",
    "content": "# 五、类和对象 #\n\n\n\n\n## 1、类和对象之间的关系 ##\n\n这部分内容主要讲类和对象，我们先来说说类和对象之间的关系。\n\n**类是对象的模板**\n\n我们得先有了类，才能制作出对象。\n\n类就相对于工厂里面的模具，对象就是根据模具制造出来的产品。\n\n**从模具变成产品的过程，我们就称为类的实例化。**\n\n**类实例化之后，就变成对象了。也就是相当于例子中的产品。**\n\n\n\n\n\n## 2、类的实例化 ##\n\n这里强调一下，类的实例化和直接使用类的格式是不一样的。\n\n之前我们就学过，直接使用类格式是这样的：\n\n```python\nclass ClassA():\n    var1 = '两点水'\n\n    @classmethod\n    def fun1(cls):\n        print('var1 值为：' + cls.var1)\n\n\nClassA.fun1()\n```\n\n而类的实例化是怎样的呢？\n\n是这样的，可以仔细对比一下，类的实例化和直接使用类的格式有什么不同？\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-025401.png)\n\n\n主要的不同点有：\n\n* 类方法里面没有了 `@classmethod` 声明了，不用声明他是类方法\n* 类方法里面的参数 `cls` 改为  `self`\n* 类的使用，变成了先通过 `实例名 = 类()` 的方式实例化对象，为类创建一个实例，然后再使用 `实例名.函数()` 的方式调用对应的方法 ，使用 `实例名.变量名` 的方法调用类的属性\n\n\n这里说明一下，类方法的参数为什么 `cls` 改为  `self` ？\n\n其实这并不是说一定要写这个，你改为什么字母，什么名字都可以。 \n\n不妨试一下：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-032030.png)\n\n你看，把 `self` 改为 `aaaaaaaa` 还是可以一样运行的。\n\n只不过使用  `cls` 和 `self` 是我们的编程习惯，这也是我们的编程规范。\n\n因为 cls 是 class 的缩写，代表这类 ， 而 self 代表这对象的意思。\n\n所以啊，这里我们实例化对象的时候，就使用 self 。\n\n**而且 self 是所有类方法位于首位、默认的特殊参数。**\n\n除此之外，在这里，还要强调一个概念，当你把类实例化之后，里面的属性和方法，就不叫类属性和类方法了，改为叫实例属性和实例方法，也可以叫对象属性和对象方法。\n\n为什么要这样强调呢？\n\n**因为一个类是可以创造出多个实例对象出来的。**\n\n你看下面的例子：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-034453.png)\n\n我不仅能用这个类创建 a 对象，还能创建 b 对象\n\n\n\n\n\n## 3、实例属性和类属性 ##\n\n一个类可以实例化多个对象出来。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-040408.png)\n\n根据这个图，我们探究一下实例对象的属性和类属性之间有什么关系呢？\n\n**先提出第一个问题，如果类属性改变了，实例属性会不会跟着改变呢？**\n\n还是跟以前一样，提出了问题，我们直接用程序来验证就好。\n\n看程序：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-061015.png)\n\n\n从程序运行的结果来看，**类属性改变了，实例属性会跟着改变。**\n\n这很好理解，因为我们的实例对象就是根据类来复制出来的，类属性改变了，实例对象的属性也会跟着改变。\n\n**那么相反，如果实例属性改变了，类属性会改变吗？**\n\n答案当然是不能啦。因为每个实例都是单独的个体，不能影响到类的。\n\n具体我们做下实验：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-062437.png)\n\n可以看到，**不管实例对象怎么修改属性值，对类的属性还是没有影响的。**\n\n\n\n\n## 4、实例方法和类方法 ##\n\n那这里跟上面一样，还是提出同样的问题。\n\n**如果类方法改变了，实例方法会不会跟着改变呢？**\n\n看下下面的例子：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-063242.png)\n\n这里建议我的例子，各位都要仔细看一下，自己重新敲一遍。相信为什么要这么做，这么证明。\n\n还是那句话多想，多敲。\n\n回归正题，从运行的结果来看，类方法改变了，实例方法也是会跟着改变的。\n\n在这个例子中，我们需要改变类方法，就用到了**类的重写**。\n\n我们使用了  `类.原始函数 = 新函数`  就完了类的重写了。\n\n要注意的是，这里的赋值是在替换方法，并不是调用函数。所以是不能加上括号的，也就是 `类.原始函数() = 新函数()` 这个写法是不对的。\n\n\n**那么如果实例方法改变了，类方法会改变吗？**\n\n如果这个问题我们需要验证的话，是不是要重写实例的方法，然后观察结果，看看类方法有没有改变，这样就能得出结果了。\n\n\n可是我们是不能重写实例方法。\n\n你看，会直接报错。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-064303.png)\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/6.md",
    "content": "# 六、初始化函数 #\n\n\n\n## 1、什么是初始化函数 ##\n\n初始化函数的意思是，当你创建一个实例的时候，这个函数就会被调用。\n\n比如：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-071102.png)\n\n当代码在执行 `a = ClassA()` 的语句时，就自动调用了 `__init__(self)` 函数。\n\n**而这个 `__init__(self)`  函数就是初始化函数，也叫构造函数。**\n\n初始化函数的写法是固定的格式：中间是 `init`，意思是初始化，然后前后都要有【两个下划线】，然后 `__init__()` 的括号中，第一个参数一定要写上 `self`，不然会报错。\n\n构造函数（初始化函数）格式如下：\n\n```python\ndef __init__(self,[...):\n```\n\n\n初始化函数一样可以传递参数的，例如：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-073421.png)\n\n\n\n\n## 2、析构函数 ##\n\n竟然一个在创建的时候，会调用构造函数，那么理所当然，这个当一个类销毁的时候，就会调用析构函数。\n\n析构函数语法如下：\n\n```python\ndef __del__(self,[...):\n```\n\n看下具体的示例：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-084417.png)\n\n\n\n## 3、Python 定义类的历史遗留问题 ##\n\nPython  在版本的迭代中，有一个关于类的历史遗留问题，就是新式类和旧式类的问题，具体先看以下的代码：\n\n```python\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n# 旧式类\nclass OldClass:\n    pass\n\n# 新式类\nclass NewClass(object):\n    pass\n\n```\n\n可以看到，这里使用了两者中不同的方式定义类，可以看到最大的不同就是，新式类继承了`object` 类，在 Python2 中，我们定义类的时候最好定义新式类，当然在 Python3 中不存在这个问题了，因为 Python3 中所有类都是新式类。\n\n那么新式类和旧式类有什么区别呢？\n\n运行下下面的那段代码：\n\n```python\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\n# 旧式类\nclass OldClass:\n    def __init__(self, account, name):\n        self.account = account\n        self.name = name\n\n\n# 新式类\nclass NewClass(object):\n    def __init__(self, account, name):\n        self.account = account\n        self.name = name\n\n\nif __name__ == '__main__':\n    old_class = OldClass(111111, 'OldClass')\n    print(old_class)\n    print(type(old_class))\n    print(dir(old_class))\n    print('\\n')\n    new_class = NewClass(222222, 'NewClass')\n    print(new_class)\n    print(type(new_class))\n    print(dir(new_class))\n\n```\n\n这是 python 2.7 运行的结果：\n\n```\n/Users/twowater/dev/python/test/venv/bin/python /Users/twowater/dev/python/test/com/twowater/test.py\n<__main__.OldClass instance at 0x109a50560>\n<type 'instance'>\n['__doc__', '__init__', '__module__', 'account', 'name']\n\n\n<__main__.NewClass object at 0x109a4b150>\n<class '__main__.NewClass'>\n['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'account', 'name']\n\nProcess finished with exit code 0\n\n```\n\n这是 Python 3.6 运行的结果：\n\n```\n/usr/local/bin/python3.6 /Users/twowater/dev/python/test/com/twowater/test.py\n<__main__.OldClass object at 0x1038ba630>\n<class '__main__.OldClass'>\n['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'account', 'name']\n\n\n<__main__.NewClass object at 0x103e3c9e8>\n<class '__main__.NewClass'>\n['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'account', 'name']\n\nProcess finished with exit code 0\n\n```\n\n\n仔细观察输出的结果，对比一下，就能观察出来，注意喔，Pyhton3 中输出的结果是一模一样的，因为Python3 中没有新式类旧式类的问题。\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/7.md",
    "content": "# 七、类的继承 #\n\n## 1、定义类的继承 ##\n\n说到继承，你一定会联想到继承你老爸的家产之类的。\n\n类的继承也是一样。\n\n比如有一个旧类，是可以算平均数的。然后这时候有一个新类，也要用到算平均数，那么这时候我们就可以使用继承的方式。新类继承旧类，这样子新类也就有这个功能了。\n\n通常情况下，我们叫旧类为父类，新类为子类。\n\n\n首先我们来看下类的继承的基本语法：\n\n```python\nclass ClassName(BaseClassName):\n    <statement-1>\n    .\n    .\n    .\n    <statement-N>\n```\n\n在定义类的时候，可以在括号里写继承的类，如果不用继承类的时候，也要写继承 object 类，因为在 Python 中 object 类是一切类的父类。\n\n当然上面的是单继承，Python 也是支持多继承的，具体的语法如下：\n\n```python\nclass ClassName(Base1,Base2,Base3):\n    <statement-1>\n    .\n    .\n    .\n    <statement-N>\n```\n\n多继承有一点需要注意的：若是父类中有相同的方法名，而在子类使用时未指定，python 在圆括号中父类的顺序，从左至右搜索 ， 即方法在子类中未找到时，从左到右查找父类中是否包含方法。\n\n那么继承的子类可以干什么呢？\n\n继承的子类的好处：\n* 会继承父类的属性和方法\n* 可以自己定义，覆盖父类的属性和方法\n\n## 2、调用父类的方法 ##\n\n一个类继承了父类后，可以直接调用父类的方法的，比如下面的例子，`UserInfo2` 继承自父类 `UserInfo` ，可以直接调用父类的  `get_account` 方法。\n\n```python\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nclass UserInfo(object):\n    lv = 5\n\n    def __init__(self, name, age, account):\n        self.name = name\n        self._age = age\n        self.__account = account\n\n    def get_account(self):\n        return self.__account\n\n\nclass UserInfo2(UserInfo):\n    pass\n\n\nif __name__ == '__main__':\n    userInfo2 = UserInfo2('两点水', 23, 347073565);\n    print(userInfo2.get_account())\n\n```\n\n## 3、父类方法的重写 ##\n\n当然，也可以重写父类的方法。\n\n示例：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass UserInfo(object):\n    lv = 5\n\n    def __init__(self, name, age, account):\n        self.name = name\n        self._age = age\n        self.__account = account\n\n    def get_account(self):\n        return self.__account\n\n    @classmethod\n    def get_name(cls):\n        return cls.lv\n\n    @property\n    def get_age(self):\n        return self._age\n\n\nclass UserInfo2(UserInfo):\n    def __init__(self, name, age, account, sex):\n        super(UserInfo2, self).__init__(name, age, account)\n        self.sex = sex;\n\n\nif __name__ == '__main__':\n    userInfo2 = UserInfo2('两点水', 23, 347073565, '男');\n    # 打印所有属性\n    print(dir(userInfo2))\n    # 打印构造函数中的属性\n    print(userInfo2.__dict__)\n    print(UserInfo2.get_name())\n\n```\n\n最后打印的结果：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-Python%20%E7%B1%BB%E7%9A%84%E7%BB%A7%E6%89%BF.png)\n\n这里就是重写了父类的构造函数。\n\n\n## 4、子类的类型判断 ##\n\n对于 class 的继承关系来说，有些时候我们需要判断 class 的类型，该怎么办呢？\n\n可以使用 `isinstance()` 函数,\n\n一个例子就能看懂 `isinstance()` 函数的用法了。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User1(object):\n    pass\n\n\nclass User2(User1):\n    pass\n\n\nclass User3(User2):\n    pass\n\n\nif __name__ == '__main__':\n    user1 = User1()\n    user2 = User2()\n    user3 = User3()\n    # isinstance()就可以告诉我们，一个对象是否是某种类型\n    print(isinstance(user3, User2))\n    print(isinstance(user3, User1))\n    print(isinstance(user3, User3))\n    # 基本类型也可以用isinstance()判断\n    print(isinstance('两点水', str))\n    print(isinstance(347073565, int))\n    print(isinstance(347073565, str))\n\n```\n\n输出的结果如下：\n\n```txt\nTrue\nTrue\nTrue\nTrue\nTrue\nFalse\n```\n\n可以看到 `isinstance()` 不仅可以告诉我们，一个对象是否是某种类型，也可以用于基本类型的判断。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/8.md",
    "content": "# 八、类的多态 #\n\n多态的概念其实不难理解，它是指对不同类型的变量进行相同的操作，它会根据对象（或类）类型的不同而表现出不同的行为。\n\n事实上，我们经常用到多态的性质，比如：\n\n```\n>>> 1 + 2\n3\n>>> 'a' + 'b'\n'ab'\n```\n\n可以看到，我们对两个整数进行 + 操作，会返回它们的和，对两个字符进行相同的 + 操作，会返回拼接后的字符串。\n\n也就是说，不同类型的对象对同一消息会作出不同的响应。\n\n\n看下面的实例，来了解多态：\n\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def __init__(self, name):\n        self.name = name\n\n    def printUser(self):\n        print('Hello !' + self.name)\n\n\nclass UserVip(User):\n    def printUser(self):\n        print('Hello ! 尊敬的Vip用户：' + self.name)\n\n\nclass UserGeneral(User):\n    def printUser(self):\n        print('Hello ! 尊敬的用户：' + self.name)\n\n\ndef printUserInfo(user):\n    user.printUser()\n\n\nif __name__ == '__main__':\n    userVip = UserVip('两点水')\n    printUserInfo(userVip)\n    userGeneral = UserGeneral('水水水')\n    printUserInfo(userGeneral)\n\n```\n\n输出的结果:\n\n```txt\nHello ! 尊敬的Vip用户：两点水\nHello ! 尊敬的用户：水水水\n```\n\n可以看到，userVip 和 userGeneral 是两个不同的对象，对它们调用 printUserInfo 方法，它们会自动调用实际类型的 printUser 方法，作出不同的响应。这就是多态的魅力。\n\n要注意喔，有了继承，才有了多态，也会有不同类的对象对同一消息会作出不同的相应。\n\n\n\n最后，本章的所有代码都可以在 [https://github.com/TwoWater/Python](https://github.com/TwoWater/Python) 上面找到，文章的内容和源文件都放在上面。同步更新到 Gitbooks。\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/9.md",
    "content": "# 九、类的访问控制 #\n\n\n## 1、类属性的访问控制 ##\n\n在 Java 中，有 public （公共）属性 和 private （私有）属性，这可以对属性进行访问控制。\n\n那么在 Python 中有没有属性的访问控制呢？\n\n一般情况下，我们会使用 `__private_attrs` 两个下划线开头，声明该属性为私有，不能在类地外部被使用或直接访问。在类内部的方法中使用时 `self.__private_attrs`。\n\n为什么只能说一般情况下呢？\n\n因为实际上， Python 中是没有提供私有属性等功能的。\n\n但是 Python 对属性的访问控制是靠程序员自觉的。为什么这么说呢？\n\n看看下面的示例：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-Python%20%E5%B1%9E%E6%80%A7%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6.png)\n\n仔细看图片，为什么说双下划线不是真正的私有属性呢？我们看下下面的例子，用下面的例子来验证：\n\n```python\n\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nclass UserInfo(object):\n    def __init__(self, name, age, account):\n        self.name = name\n        self._age = age\n        self.__account = account\n\n    def get_account(self):\n        return self.__account\n\n\nif __name__ == '__main__':\n    userInfo = UserInfo('两点水', 23, 347073565);\n    # 打印所有属性\n    print(dir(userInfo))\n    # 打印构造函数中的属性\n    print(userInfo.__dict__)\n    print(userInfo.get_account())\n    # 用于验证双下划线是否是真正的私有属性\n    print(userInfo._UserInfo__account)\n\n\n```\n\n输出的结果如下图：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-09-Python%E5%8F%8C%E4%B8%8B%E5%88%92%E7%BA%BF%E5%B1%9E%E6%80%A7.png)\n\n\n\n\n## 2、类专有的方法 ##\n\n一个类创建的时候，就会包含一些方法，主要有以下方法：\n\n类的专有方法：\n\n| 方法 | 说明 |\n| ------| ------ |\n|`__init__` |构造函数，在生成对象时调用|\n|`__del__ `| 析构函数，释放对象时使用|\n|`__repr__ `| 打印，转换|\n|`__setitem__ `| 按照索引赋值|\n|`__getitem__`| 按照索引获取值|\n|`__len__`| 获得长度|\n|`__cmp__`| 比较运算|\n|`__call__`| 函数调用|\n|`__add__`| 加运算|\n|`__sub__`| 减运算|\n|`__mul__`|乘运算|\n|`__div__`| 除运算|\n|`__mod__`| 求余运算|\n|`__pow__`|乘方|\n\n当然有些时候我们需要获取类的相关信息，我们可以使用如下的方法：\n\n* `type(obj)`：来获取对象的相应类型；\n* `isinstance(obj, type)`：判断对象是否为指定的 type 类型的实例；\n* `hasattr(obj, attr)`：判断对象是否具有指定属性/方法；\n* `getattr(obj, attr[, default])` 获取属性/方法的值, 要是没有对应的属性则返回 default 值（前提是设置了 default），否则会抛出 AttributeError 异常；\n* `setattr(obj, attr, value)`：设定该属性/方法的值，类似于 obj.attr=value；\n* `dir(obj)`：可以获取相应对象的所有属性和方法名的列表：\n\n\n\n## 3、方法的访问控制 ##\n\n其实我们也可以把方法看成是类的属性的，那么方法的访问控制也是跟属性是一样的，也是没有实质上的私有方法。一切都是靠程序员自觉遵守 Python 的编程规范。\n\n示例如下，具体规则也是跟属性一样的，\n\n```python\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nclass User(object):\n    def upgrade(self):\n        pass\n\n    def _buy_equipment(self):\n        pass\n\n    def __pk(self):\n        pass\n\n```\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python8/Preface.md",
    "content": "# 前言 #\n\n\n之前的文章都是使用[Sublime Text](http://www.sublimetext.com/)来编写 Python 的，主要是为了更好的熟悉和了解 Python ，可是开发效率不高，也不方便，从这章开始，改为使用 Pycharm 了，在之前的篇节[集成开发环境（IDE）: PyCharm](https://www.readwithu.com/python1/IDE.html)中介绍了 PyCharm ，如果如要激活软件可以通过授权服务器来激活，具体看这个网址。[JetBrains激活（http://www.imsxm.com/jetbrains-license-server.html）](http://www.imsxm.com/jetbrains-license-server.html)当然你也可以尝试破解，  [Pycharm2017.1.1破解方式](http://blog.csdn.net/zyfortirude/article/details/70800681)，不过对于软件的升级不方便。\n\n\n这篇内容非常的重要，也是我用了很多时间写的。基本上把以前写的东西都重新改了一遍。里面的代码都是我一个一个的敲的，图片也是我一个一个制作的。\n\n\n\n\n# 目录 #\n\n![面向对象](media/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1.png)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/1.md",
    "content": "# 一、Python 模块简介 #\n\n在开发过程中，随着程序代码越写越多，在一个文件里代码就会越来越长，越来越不容易维护。\n\n后面我们学习了函数，知道函数是实现一项或多项功能的一段程序，这样就更方便我们重复使用代码。\n\n紧接着，我们有学了类，类可以封装方法和变量（属性）。这样就更方便我们维护代码了。\n\n我们之前学过，类的结构是这样的:\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-08-034102.png)\n\n而我们要学的模块是这样的：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-10-175017.png)\n\n在模块中，我们不但可以直接存放变量，还能存放函数，还能存放类。\n\n不知道你们还有没有印象，我们封装函数用的是 `def` , 封装类用的是 `class` 。\n\n而我们封装模块，是不需要任何语句的。\n \n**在 Python 中，一个 .py 文件就称之为一个模块（Module）。**\n\n可以看下我之前写的例子，在 pychrome 上 ，这样一个 test.py 文件就是一个模块。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-070013.png)\n\n其实模块就是函数功能的扩展。为什么这么说呢？\n\n那是因为模块其实就是实现一项或多项功能的程序块。\n\n通过上面的定义，不难发现，函数和模块都是用来实现功能的，只是模块的范围比函数广，在模块中，可以有多个函数。\n\n然有了函数，那为啥那需要模块？\n\n最大的好处是大大提高了代码的可维护性。\n\n其次，编写代码不必从零开始。当一个模块编写完毕，就可以被其他地方引用。我们在编写程序的时候，也经常引用其他模块，包括 Python 内置的模块和来自第三方的模块。\n\n使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中，因此，我们自己在编写模块时，不必考虑名字会与其他模块冲突。但是也要注意，尽量不要与内置函数名字冲突。\n\nPython 本身就内置了很多非常有用的模块，只要安装完毕，这些模块就可以立刻使用。我们可以尝试找下这些模块，比如我的 Python 安装目录是默认的安装目录，在 C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36 ，然后找到 Lib 目录，就可以发现里面全部都是模块，没错，这些 `.py` 文件就是模块了。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-python36bin%E7%9B%AE%E5%BD%95.png)\n\n其实模块可以分为标准库模块和自定义模块，而刚刚我们看到的 Lib 目录下的都是标准库模块。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/2.md",
    "content": "# 二、模块的使用 #\n\n## 1、import ##\n\nPython 模块的使用跟其他编程语言也是类似的。你要使用某个模块，在使用之前，必须要导入这个模块。导入模块我们使用关键字 `import`。\n\n`import` 的语法基本如下：\n\n```python\nimport module1[, module2[,... moduleN]\n```\n\n比如我们使用标准库模块中的 math 模块。当解释器遇到 `import` 语句，如果模块在当前的搜索路径就会被导入。\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nimport math\n\n_author_ = '两点水'\n\nprint(math.pi)\n```\n\n输出的结果：\n\n```txt\n3.141592653589793\n```\n\n一个模块只会被导入一次，不管你执行了多少次 import。这样可以防止导入模块被一遍又一遍地执行。\n\n当我们使用 import 语句的时候，Python 解释器是怎样找到对应的文件的呢？\n\n这就涉及到 Python 的搜索路径，搜索路径是由一系列目录名组成的，Python 解释器就依次从这些目录中去寻找所引入的模块。这看起来很像环境变量，事实上，也可以通过定义环境变量的方式来确定搜索路径。搜索路径是在 Python 编译或安装的时候确定的，安装新的库应该也会修改。搜索路径被存储在sys 模块中的 path 变量 。\n\n因此，我们可以查一下路径：\n\n```python\n#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nimport sys\n\nprint(sys.path)\n```\n\n输出结果：\n\n\n```txt\n['C:\\\\Users\\\\Administrator\\\\Desktop\\\\Python\\\\Python8Code', 'G:\\\\PyCharm 2017.1.4\\\\helpers\\\\pycharm', 'C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python36\\\\python36.zip', 'C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python36\\\\DLLs', 'C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python36\\\\lib', 'C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python36', 'C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python36\\\\lib\\\\site-packages', 'C:\\\\Users\\\\Administrator\\\\Desktop\\\\Python\\\\Python8Code\\\\com\\\\Learn\\\\module\\\\sys']\n\n```\n\n## 2、from···import  ##\n\n\n有没有想过，怎么直接导入某个模块中的属性和方法呢？\n\nPython 中，导入一个模块的方法我们使用的是 `import` 关键字，这样做是导入了这个模块，这里需要注意了，这样做只是导入了模块，并没有导入模块中具体的某个属性或方法的。而我们想直接导入某个模块中的某一个功能，也就是属性和方法的话，我们可以使用 `from···import` 语句。\n\n语法如下：\n\n```python\nfrom modname import name1[, name2[, ... nameN]]\n```\n\n看完简介后可能会想， `from···import`  和 `import` 方法有啥区别呢？\n\n想知道区别是什么，观察下面两个例子：\n\n `import` 导入 sys 模块，然后使用 version 属性\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-from%C2%B7%C2%B7%C2%B7import%E5%92%8C%20import%E7%9A%84%E5%8C%BA%E5%88%AB1.png)\n\n`from···import` 直接导入 version 属性\n\n![from···import和 import的区别2](media/from%C2%B7%C2%B7%C2%B7import%E5%92%8C%20import%E7%9A%84%E5%8C%BA%E5%88%AB2-2.png)\n\n\n\n\n## 3、from ··· import *  ##\n\n通过上面的学习，我们知道了 `from sys import version` 可以直接导入 version 属性。\n\n但是如果我们想使用其他的属性呢？\n\n比如使用 sys 模块中的 `executable` ，难道又要写多一句 `from sys import executable` ,两个还好，如果三个，四个呢？\n\n难道要一直这样写下去？\n\n这时候就需要 `from ··· import *` 语句了，这个语句可以把某个模块中的所有方法属性都导入。比如：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\nfrom sys import *\n\nprint(version)\nprint(executable)\n\n```\n\n输出的结果为：\n\n```txt\n3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)]\nC:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\python.exe\n```\n\n注意：这提供了一个简单的方法来导入一个模块中的所有方法属性。然而这种声明不该被过多地使用。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/3.md",
    "content": "# 三、主模块和非主模块 #\n\n## 1、主模块和非主模块的定义 ##\n\n在 Python 函数中，如果一个函数调用了其他函数完成一项功能，我们称这个函数为主函数，如果一个函数没有调用其他函数，我们称这种函数为非主函数。主模块和非主模块的定义也类似，如果一个模块被直接使用，而没有被别人调用，我们称这个模块为主模块，如果一个模块被别人调用，我们称这个模块为非主模块。\n\n## 2、__name__ 属性 ##\n\n在 Python  中，有主模块和非主模块之分，当然，我们也得区分他们啊。那么怎么区分主模块和非主模块呢？\n\n这就需要用到 `__name__`  属性了，这个 `——name——` 属性值是一个变量，且这个变量是系统给出的。利用这个变量可以判断一个模块是否是主模块。如果一个属性的值是 `__main__` ,那么就说明这个模块是主模块，反之亦然。但是要注意了：** 这个 `__main__` 属性只是帮助我们判断是否是主模块，并不是说这个属性决定他们是否是主模块，决定是否是主模块的条件只是这个模块有没有被人调用**\n\n具体看示例：\n\n首先创建了模块 lname ，然后判断一下是否是主模块，如果是主模块就输出 `main` 不是，就输出 `not main` ，首先直接运行该模块，由于该模块是直接使用，而没有被人调用，所以是主模块，因此输出了 `main` ，具体看下图：\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-name%E5%B1%9E%E6%80%A7%E5%8C%BA%E5%88%86%E6%A8%A1%E5%9D%971.png)\n\n\n然后又创建一个 user_lname 模块，里面只是简单的导入了 lname 模块，然后执行，输出的结果是 `not main` ，因为 lname 模块被该模块调用了，所以不是主模块，输出结果如图：\n\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-name%E5%B1%9E%E6%80%A7%E5%8C%BA%E5%88%86%E6%A8%A1%E5%9D%972.png)\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/4.md",
    "content": "# 四、包 #\n\n包，其实在上面的一些例子中，都创建了不同的包名了，具体可以仔细观察。\n\n在一开始模块的简介中提到，使用模块可以避免函数名和变量名冲突。\n\n相同名字的函数和变量完全可以分别存在不同的模块中，因此，我们自己在编写模块时，不必考虑名字会与其他模块冲突。但是也要注意，尽量不要与内置函数名字冲突。\n\n但是这里也有个问题，如果不同的人编写的模块名相同怎么办？\n\n为了避免模块名冲突，Python 又引入了按目录来组织模块的方法，称为包（Package）。\n\n比如最开始的例子，就引入了包，这样子做就算有相同的模块名，也不会造成重复，因为包名不同，其实也就是路径不同。如下图，引入了包名后， lname.py 其实变成了 com.Learn.module.nameattributes.lname\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-Python%20%E5%8C%85.png)\n\n仔细观察的人，基本会发现，每一个包目录下面都会有一个 `__init__.py` 的文件，为什么呢？\n\n因为这个文件是必须的，否则，Python 就把这个目录当成普通目录，而不是一个包 。 `__init__.py` 可以是空文件，也可以有Python代码，因为 `__init__.py` 本身就是一个模块，而它对应的模块名就是它的包名。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/5.md",
    "content": "# 五、作用域 #\n\n学习过 Java 的同学都知道，Java 的类里面可以给方法和属性定义公共的（ public ）或者是私有的 （ private ）,这样做主要是为了我们希望有些函数和属性能给别人使用或者只能内部使用。\n\n 通过学习 Python 中的模块，其实和 Java 中的类相似，那么我们怎么实现在一个模块中，有的函数和变量给别人使用，有的函数和变量仅仅在模块内部使用呢？\n\n在 Python 中，是通过 `_` 前缀来实现的。正常的函数和变量名是公开的（public），可以被直接引用，比如：abc，ni12，PI等；类似`__xxx__`这样的变量是特殊变量，可以被直接引用，但是有特殊用途，比如上面的 `__name__` 就是特殊变量，还有 `__author__` 也是特殊变量，用来标明作者。\n\n注意，我们自己的变量一般不要用这种变量名；类似 `_xxx` 和 `__xxx` 这样的函数或变量就是非公开的（private），不应该被直接引用，比如 `_abc` ，`__abc` 等；\n\n**这里是说不应该，而不是不能。因为 Python 种并没有一种方法可以完全限制访问 private 函数或变量，但是，从编程习惯上不应该引用 private 函数或变量。**\n\n比如：\n\n```python\n#!/usr/bin/env python3\n# -*- coding: UTF-8 -*-\n\ndef _diamond_vip(lv):\n    print('尊敬的钻石会员用户，您好')\n    vip_name = 'DiamondVIP' + str(lv)\n    return vip_name\n\n\ndef _gold_vip(lv):\n    print('尊敬的黄金会员用户，您好')\n    vip_name = 'GoldVIP' + str(lv)\n    return vip_name\n\n\ndef vip_lv_name(lv):\n    if lv == 1:\n        print(_gold_vip(lv))\n    elif lv == 2:\n        print(_diamond_vip(lv))\n\n\nvip_lv_name(2)\n\n```\n\n输出的结果：\n\n```txt\n尊敬的钻石会员用户，您好\nDiamondVIP2\n```\n\n在这个模块中，我们公开 `vip_lv_name`  方法函数，而其他内部的逻辑分别在 `_diamond_vip` 和 `_gold_vip` private 函数中实现，因为是内部实现逻辑，调用者根本不需要关心这个函数方法，它只需关心调用 `vip_lv_name`  的方法函数，所以用 private 是非常有用的代码封装和抽象的方法\n\n一般情况下，外部不需要引用的函数全部定义成 private，只有外部需要引用的函数才定义为 public。\n\n------------------------\n\n最后扯淡，欢迎加我微信：`thinktoday2019`, 进入微信 Python 讨论群。\n\n\n"
  },
  {
    "path": "Article/PythonBasis/python9/Preface.md",
    "content": "# 前言 #\n\n学习到这里，可以说 Python 基础学习基本接近尾声了。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-14-%E6%A8%A1%E5%9D%97%E4%B8%8E%E5%8C%85.png)\n\n\n"
  },
  {
    "path": "Article/advanced/Mac中使用virtualenv和virtualenvwrapper.md",
    "content": "\n## Virtualenv\n\n### 介绍\n\n在使用 `Python` 开发的过程中，工程一多，难免会碰到不同的工程依赖不同版本的库的问题；亦或者是在开发过程中不想让物理环境里充斥各种各样的库，引发未来的依赖灾难。\n\n因此，我们需要对于不同的工程使用不同的虚拟环境来保持开发环境以及宿主环境的清洁。而 `virtualenv`就是一个可以帮助我们管理不同 `Python` 环境的绝好工具。`virtualenv` 可以在系统中建立多个不同并且相互不干扰的虚拟环境。\n\n### 安装\n\n```\n pip3 install virtualenv\n```\n\n这样就成功了\n\n### 使用\n\n#### 创建\n\n假如我们想要用`scrapy`去爬取某个网站的信息，我们不想再宿主环境总安装scrapy以及requests这些包，那我们就可以使用virtualenv了。\n\n假设我们把这个虚拟环境放在`~/workspaces/project_env/spider/`目录下\n\n```\n virtualenv ~/workspaces/project_env/spider/\n```\n\n这样虚拟环境就创建好了，我们可以看到在这个目录下油三个目录被建立\n\n*   bin：包含一些在这个虚拟环境中可用的命令，以及开启虚拟环境的脚本 `activate`\n*   include：包含虚拟环境中的头文件，包括 `Python` 的头文件\n*   lib：这里面就是一些依赖库\n\n#### 激活\n\n```\n source ~/workspaces/project_env/spider/bin/activate\n```\n\n此时我们就已经在虚拟环境中了\n\n可以安装一下requests这个模块\n\n```\n pip install requests\n```\n\n可以看到很快就成功\n\n#### 退出虚拟环境\n\n```\n deactivate\n```\n\n## virtualenvwrapper\n\n### 介绍\n\n我们刚才了解了`virtualenv`，我觉得比较麻烦，每次开启虚拟环境之前要去虚拟环境所在目录下的 `bin` 目录下 `source`一下 `activate`，这就需要我们记住每个虚拟环境所在的目录。\n\n一种可行的解决方案是，将所有的虚拟环境目录全都集中起来，比如放到 `~/virtualenvs/`，并对不同的虚拟环境使用不同的目录来管理。`virtualenvwrapper` 正是这样做的。并且，它还省去了每次开启虚拟环境时候的 `source` 操作，使得虚拟环境更加好用。\n\n### 安装\n\n```\n pip install virtualwrapper\n```\n\n这样我们就安装好了可以管理虚拟环境的神器\n\n### 使用\n\n#### 配置\n\n首先需要对`virtualenvwrapper`进行配置:\n\n*   需要指定一个环境变量，叫做`WORKON_HOME`，它是用来存放各种虚拟环境目录的目录\n*   需要export vitualenvwrapper这个模块存放的位置\n*   需要运行一下它的初始化工具 `virtualenvwrapper.sh`，可通过`which virtualenvwrapper.sh`查看位置，我的在`/usr/local/bin/`\n\n由于每次都需要执行这两步操作，我们可以将其写入终端的配置文件中。\n\n如果使用 `bash`，则添加到 `~/.bashrc` 中\n\n如果使用 `zsh`，则添加到 `~/.zshrc` 中\n\n这样每次启动终端的时候都会自动运行，终端启动之后 `virtualenvwrapper` 就可以用啦\n\n```\n export WORKON_HOME='~/Workspaces/Envs'\n\n export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3\n\n source /usr/local/bin/virtualenvwrapper.sh\n```\n\n\n**创建虚拟机**\n\n```\nmkvirtualenv env\n```\n\n创建虚拟环境完成后，会自动切换到创建的虚拟环境中\n\n当然也可以指定虚拟机的 python 版本\n\n```\nmkvirtualenv env -p C:\\python27\\python.exe\n```\n\n**列出虚拟环境列表**\n\n```\nworkon 或者 lsvirtualenv\n```\n\n**启动/切换虚拟环境**\n\n使用 workon [virtual-name] 即可切换到对应的虚拟环境\n\n```\nworkon [虚拟环境名称]\n```\n\n\n**删除虚拟环境**\n\n```\nrmvirtualenv [虚拟环境名称]\n```\n\n**离开虚拟环境，和 virutalenv 一样的命令**\n\n```\ndeactivate\n```\n"
  },
  {
    "path": "Article/advanced/Python实现收发邮件.md",
    "content": "在实际开发中，当你收到一个需求的时候，比如要做一个「收发邮件」的功能。\n\n如果你完全没有印象，没有思路，可以直接 Google 搜索的。\n\n因为我们不可能对每个知识点都了解，不了解不可耻，但要懂得怎么去找资料了解。\n\n强调一下，\n\n用 Google 搜索。\n\n用 Google 搜索。\n\n用 Google 搜索。\n\n恕我直言，百度搜索是真的辣鸡。\n\n那我们怎么去找资料呢？\n\n首先我们可以直接 Google 「Python 收发邮件」，就可以得到这么一个结果。\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-15-065554.png)\n\n这种常用的需求，基本只要看前几个就能知道大概的思路了。\n\n可以看到，用 Python 实现邮件的收发，主要用到 smtplib 和 email这两个模块。\n\n\n至于这些模块怎么用，直接看 [Python 官方文档](https://docs.python.org/3.7/library/smtplib.html?highlight=smtplib)\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-15-065957.png)\n\n真的，没有任何教程比官方文档资料还全。\n\n不过我们可以总结一下这些内容。\n\n**1、SMTP 发送邮件**\n\nPython 发送邮件主要步骤如下：\n\n* `import smtplib` \n    - 导入 `smtplib` 模块，主要用于构造传输服务的\n*  `server = smtplib.SMTP()`\n    - SMTP 是 smtplib 模块中的一个类（class），实例化这个类，方便我们调用他里面的方法。\n    - SMTP (Simple Mail Transfer Protocol)翻译过来是“简单邮件传输协议”的意思，SMTP 协议是由源服务器到目的地服务器传送邮件的一组规则。\n*  `server.connect(host, port)`\n     - 连接（connect）指定的服务器\n     - host 是指定连接的邮箱服务器，你可以指定服务器的域名。\n     - port 服务器的端口号\n     - 这些怎么找到，好比 qq 邮箱，在「设置」那里就有相关的开关和说明。\n     - ![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-15-090427.png)\n     - 点相关的说明，你就能看到对应的服务器地址和端口号了\n     - ![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-10-15-090619.png)\n*  `server.login(username, password)`\n    - 登录的账号密码 \n* `server.sendmail(from_addr, to_addr, msg)`\n    - 发送邮件，发送邮件一般是谁发给了谁，发送了什么？总结为也就是三个参数，发送者，接受者，发送邮件的内容。\n    - from_addr：邮件发送地址\n    - to_addr：邮件收件人地址\n    - msg ： 发送邮件的内容，邮件内容需要用到 `email` 模块。通过 `email` 模块我们可以定义邮件的主题，收件人信息，发件人信息等等。\n* `server.quit()`\n    - 退出服务\n\n    直接看下例子，给 QQ 邮箱发送一个邮件：\n    \n    \n\n"
  },
  {
    "path": "Article/advanced/advanced.md",
    "content": "* [使用Python虚拟环境](/Article/advanced/使用Python虚拟环境.md)\n* [Mac中使用virtualenv和virtualenvwrapper](/Article/advanced/Mac中使用virtualenv和virtualenvwrapper.md)\n"
  },
  {
    "path": "Article/advanced/使用Python虚拟环境.md",
    "content": "python 的虚拟环境可以为一个 python 项目提供独立的解释环境、依赖包等资源，既能够很好的隔离不同项目使用不同 python 版本带来的冲突，而且还能方便项目的发布。\n\n# virtualenv #\n\n[virtualenv](http://pypi.python.org/pypi/virtualenv)可用于创建独立的 Python 环境，它会创建一个包含项目所必须要的执行文件。\n\n**安装 virtualenv**\n\n```\n$ pip install virtualenv\n```\n\n\n**配置 pip 安装第三方库的镜像源地址**\n\n我们都知道，国内连接国外的服务器都会比较慢，有时候设置下载经常出现超时的情况。这时可以尝试使用国内优秀的[豆瓣源](https://pypi.douban.com/simple)镜像来安装。\n\n使用豆瓣源安装 virtualenv\n\n```\npip install -i https://pypi.douban.com/simple virtualenv\n```\n\n**virtualenv使用方法**\n\n如下命令表示在当前目录下创建一个名叫 env 的目录（虚拟环境），该目录下包含了独立的 Python 运行程序,以及 pip副本用于安装其他的 packge\n\n```\nvirtualenv env\n```\n\n\n当然在创建 env 的时候可以选择 Python 解释器，例如：\n\n```\nvirtualenv -p /usr/local/bin/python3 venv\n```\n\n默认情况下，虚拟环境会依赖系统环境中的 site packages，就是说系统中已经安装好的第三方 package 也会安装在虚拟环境中，如果不想依赖这些 package，那么可以加上参数 `--no-site-packages` 建立虚拟环境\n\n```\nvirtualenv --no-site-packages [虚拟环境名称]\n```\n\n**启动虚拟环境**\n\n```\ncd ENV\nsource ./bin/activate\n```\n\n注意此时命令行会多一个`(ENV)`，ENV为虚拟环境名称，接下来所有模块都只会安装到这个虚拟的环境中去。\n\n**退出虚拟环境**\n\n```\ndeactivate\n```\n\n如果想删除虚拟环境，那么直接运行`rm -rf venv/`命令即可。\n\n**在虚拟环境安装 Python packages**\n\nVirtualenv 附带有 pip 安装工具，因此需要安装的 packages 可以直接运行：\n\n```\npip install [套件名称]\n```\n\n# Virtualenvwrapper\n\nVirtualenvwrapper 是一个虚拟环境管理工具，它能够管理创建的虚拟环境的位置，并能够方便地查看虚拟环境的名称以及切换到指定的虚拟环境。\n\n\n**安装（确保virtualenv已经安装）**\n\n```\npip install virtualenvwrapper\n```\n\n或者使用豆瓣源\n\n```\npip install -i https://pypi.douban.com/simple virtualenvwrapper-win\n```\n\n注：\n\n安装需要在非虚拟环境下进行\n\n**创建虚拟机**\n\n```\nmkvirtualenv env\n```\n\n创建虚拟环境完成后，会自动切换到创建的虚拟环境中\n\n当然也可以指定虚拟机的 python 版本\n\n```\nmkvirtualenv env -p C:\\python27\\python.exe\n```\n\n**列出虚拟环境列表**\n\n```\nworkon 或者 lsvirtualenv\n```\n\n**启动/切换虚拟环境**\n\n使用 workon [virtual-name] 即可切换到对应的虚拟环境\n\n```\nworkon [虚拟环境名称]\n```\n\n\n**删除虚拟环境**\n\n```\nrmvirtualenv [虚拟环境名称]\n```\n\n**离开虚拟环境，和 virutalenv 一样的命令**\n\n```\ndeactivate\n```\n"
  },
  {
    "path": "Article/codeSpecification/codeSpecification_Preface.md",
    "content": "# 前言 #\n\n本来不应该把这个章节放在那面前面的，因为还没进行学习之前，直接看这个章节，会感觉有很多莫名其妙的东西。\n\n但是把这个章节放在前面的用意，只是让大家预览一下，有个印象，而且在以后的学习中，也方便大家查阅。\n\n# 目录 #\n\n![](http://twowaterimage.oss-cn-beijing.aliyuncs.com/2019-07-20-Python%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83.png)\n\n"
  },
  {
    "path": "Article/codeSpecification/codeSpecification_first.md",
    "content": "# 一、简明概述\n\n## 1、编码\n\n* 如无特殊情况, 文件一律使用 UTF-8 编码\n* 如无特殊情况, 文件头部必须加入`#-*-coding:utf-8-*-`标识\n\n## 2、代码格式\n\n### 2.1、缩进\n\n* 统一使用 4 个空格进行缩进\n\n### 2.2、行宽\n\n每行代码尽量不超过 80 个字符(在特殊情况下可以略微超过 80 ，但最长不得超过 120)\n\n理由：\n\n* 这在查看 side-by-side 的 diff 时很有帮助\n* 方便在控制台下查看代码\n* 太长可能是设计有缺陷\n\n### 2.3、引号\n\n简单说，自然语言使用双引号，机器标示使用单引号，因此 __代码里__ 多数应该使用 __单引号__\n\n * ***自然语言*** **使用双引号** `\"...\"`  \n   例如错误信息；很多情况还是 unicode，使用`u\"你好世界\"`\n * ***机器标识*** **使用单引号** `'...'`\n   例如 dict 里的 key\n * ***正则表达式*** **使用原生的双引号** `r\"...\"`\n * ***文档字符串 (docstring)*** **使用三个双引号** `\"\"\"......\"\"\"`\n\n### 2.4、空行\n\n* 模块级函数和类定义之间空两行；\n* 类成员函数之间空一行；\n\n```python\nclass A:\n\n    def __init__(self):\n        pass\n\n    def hello(self):\n        pass\n\n\ndef main():\n    pass   \n```\n\n* 可以使用多个空行分隔多组相关的函数\n* 函数中可以使用空行分隔出逻辑相关的代码\n\n\n## 3、import 语句\n\n* import 语句应该分行书写\n\n```python\n# 正确的写法\nimport os\nimport sys\n\n# 不推荐的写法\nimport sys,os\n\n# 正确的写法\nfrom subprocess import Popen, PIPE\n```\n* import语句应该使用 __absolute__ import\n\n```python\n# 正确的写法\nfrom foo.bar import Bar\n\n# 不推荐的写法\nfrom ..bar import Bar\n```\n\n* import语句应该放在文件头部，置于模块说明及docstring之后，于全局变量之前；\n* import语句应该按照顺序排列，每组之间用一个空行分隔\n\n```python\nimport os\nimport sys\n\nimport msgpack\nimport zmq\n\nimport foo\n```\n\n* 导入其他模块的类定义时，可以使用相对导入\n\n```python\nfrom myclass import MyClass\n```\n\n* 如果发生命名冲突，则可使用命名空间\n\n```python\nimport bar\nimport foo.bar\n\nbar.Bar()\nfoo.bar.Bar()\n```\n\n## 4、空格\n\n* 在二元运算符两边各空一格`[=,-,+=,==,>,in,is not, and]`:\n\n```python\n# 正确的写法\ni = i + 1\nsubmitted += 1\nx = x * 2 - 1\nhypot2 = x * x + y * y\nc = (a + b) * (a - b)\n\n# 不推荐的写法\ni=i+1\nsubmitted +=1\nx = x*2 - 1\nhypot2 = x*x + y*y\nc = (a+b) * (a-b)\n```\n\n* 函数的参数列表中，`,`之后要有空格\n\n```python\n# 正确的写法\ndef complex(real, imag):\n    pass\n\n# 不推荐的写法\ndef complex(real,imag):\n    pass\n```\n\n* 函数的参数列表中，默认值等号两边不要添加空格\n\n```python\n# 正确的写法\ndef complex(real, imag=0.0):\n    pass\n\n# 不推荐的写法\ndef complex(real, imag = 0.0):\n    pass\n```\n\n* 左括号之后，右括号之前不要加多余的空格\n\n```python\n# 正确的写法\nspam(ham[1], {eggs: 2})\n\n# 不推荐的写法\nspam( ham[1], { eggs : 2 } )\n```\n\n* 字典对象的左括号之前不要多余的空格\n\n```python\n# 正确的写法\ndict['key'] = list[index]\n\n# 不推荐的写法\ndict ['key'] = list [index]\n```\n\n* 不要为对齐赋值语句而使用的额外空格\n\n```python\n# 正确的写法\nx = 1\ny = 2\nlong_variable = 3\n\n# 不推荐的写法\nx             = 1\ny             = 2\nlong_variable = 3\n```\n\n## 5、换行\n\nPython 支持括号内的换行。这时有两种情况。\n\n1) 第二行缩进到括号的起始处\n\n```python\nfoo = long_function_name(var_one, var_two,\n                         var_three, var_four)\n```\n\n2) 第二行缩进 4 个空格，适用于起始括号就换行的情形\n\n```python\ndef long_function_name(\n        var_one, var_two, var_three,\n        var_four):\n    print(var_one)\n```\n\n使用反斜杠`\\`换行，二元运算符`+` `.`等应出现在行末；长字符串也可以用此法换行\n\n```python\nsession.query(MyTable).\\\n        filter_by(id=1).\\\n        one()\n\nprint 'Hello, '\\\n      '%s %s!' %\\\n      ('Harry', 'Potter')\n```\n\n禁止复合语句，即一行中包含多个语句：\n\n```python\n# 正确的写法\ndo_first()\ndo_second()\ndo_third()\n\n# 不推荐的写法\ndo_first();do_second();do_third();\n```\n\n`if/for/while`一定要换行：\n\n```python\n# 正确的写法\nif foo == 'blah':\n    do_blah_thing()\n\n# 不推荐的写法\nif foo == 'blah': do_blash_thing()\n```\n\n## 6、docstring\ndocstring 的规范中最其本的两点：\n\n1. 所有的公共模块、函数、类、方法，都应该写 docstring 。私有方法不一定需要，但应该在 def 后提供一个块注释来说明。\n2. docstring 的结束\"\"\"应该独占一行，除非此 docstring 只有一行。\n\n```python\n\"\"\"Return a foobar\nOptional plotz says to frobnicate the bizbaz first.\n\"\"\"\n\n\"\"\"Oneline docstring\"\"\"\n```\n"
  },
  {
    "path": "Article/codeSpecification/codeSpecification_second.md",
    "content": "# 二、注释\n\n## 1、注释\n\n### 1.1、块注释\n“#”号后空一格，段落件用空行分开（同样需要“#”号）\n\n```python\n# 块注释\n# 块注释\n#\n# 块注释\n# 块注释\n```\n\n### 1.2、行注释\n至少使用两个空格和语句分开，注意不要使用无意义的注释\n\n```python\n# 正确的写法\nx = x + 1  # 边框加粗一个像素\n\n# 不推荐的写法(无意义的注释)\nx = x + 1 # x加1\n```\n\n### 1.3、建议\n\n* 在代码的关键部分(或比较复杂的地方), 能写注释的要尽量写注释\n\n* 比较重要的注释段, 使用多个等号隔开, 可以更加醒目, 突出重要性\n\n```python\napp = create_app(name, options)\n\n\n# =====================================\n# 请勿在此处添加 get post等app路由行为 !!!\n# =====================================\n\n\nif __name__ == '__main__':\n    app.run()\n```\n\n## 2、文档注释（Docstring）\n作为文档的Docstring一般出现在模块头部、函数和类的头部，这样在python中可以通过对象的\\_\\_doc\\_\\_对象获取文档.\n编辑器和IDE也可以根据Docstring给出自动提示.\n\n* 文档注释以 \"\"\" 开头和结尾, 首行不换行, 如有多行, 末行必需换行, 以下是Google的docstring风格示例\n\n```python\n# -*- coding: utf-8 -*-\n\"\"\"Example docstrings.\n\nThis module demonstrates documentation as specified by the `Google Python\nStyle Guide`_. Docstrings may extend over multiple lines. Sections are created\nwith a section header and a colon followed by a block of indented text.\n\nExample:\n    Examples can be given using either the ``Example`` or ``Examples``\n    sections. Sections support any reStructuredText formatting, including\n    literal blocks::\n\n        $ python example_google.py\n\nSection breaks are created by resuming unindented text. Section breaks\nare also implicitly created anytime a new section starts.\n\"\"\"\n```\n\n* 不要在文档注释复制函数定义原型, 而是具体描述其具体内容, 解释具体参数和返回值等\n\n```python\n#  不推荐的写法(不要写函数原型等废话)\ndef function(a, b):\n    \"\"\"function(a, b) -> list\"\"\"\n    ... ...\n\n\n#  正确的写法\ndef function(a, b):\n    \"\"\"计算并返回a到b范围内数据的平均值\"\"\"\n    ... ...\n```\n\n* 对函数参数、返回值等的说明采用numpy标准, 如下所示\n\n```python\ndef func(arg1, arg2):\n    \"\"\"在这里写函数的一句话总结(如: 计算平均值).\n\n    这里是具体描述.\n\n    参数\n    ----------\n    arg1 : int\n        arg1的具体描述\n    arg2 : int\n        arg2的具体描述\n\n    返回值\n    -------\n    int\n        返回值的具体描述\n\n    参看\n    --------\n    otherfunc : 其它关联函数等...\n\n    示例\n    --------\n    示例使用doctest格式, 在`>>>`后的代码可以被文档测试工具作为测试用例自动运行\n\n    >>> a=[1,2,3]\n    >>> print [x + 3 for x in a]\n    [4, 5, 6]\n    \"\"\"\n```\n\n\n* 文档注释不限于中英文, 但不要中英文混用\n\n* 文档注释不是越长越好, 通常一两句话能把情况说清楚即可\n\n* 模块、公有类、公有方法, 能写文档注释的, 应该尽量写文档注释\n\n\n"
  },
  {
    "path": "Article/codeSpecification/codeSpecification_third.md",
    "content": "# 三、命名规范\n\n##  1、模块\n* 模块尽量使用小写命名，首字母保持小写，尽量不要用下划线(除非多个单词，且数量不多的情况)\n\n```python\n# 正确的模块名\nimport decoder\nimport html_parser\n\n# 不推荐的模块名\nimport Decoder\n```\n\n## 2、类名\n* 类名使用驼峰(CamelCase)命名风格，首字母大写，私有类可用一个下划线开头\n\n```Python\nclass Farm():\n    pass\n\nclass AnimalFarm(Farm):\n    pass\n\nclass _PrivateFarm(Farm):\n    pass\n```\n\n* 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.\n\n## 3、函数\n\n* 函数名一律小写，如有多个单词，用下划线隔开\n\n```python\ndef run():\n    pass\n\ndef run_with_env():\n    pass\n```\n\n* 私有函数在函数前加一个下划线_\n\n```python\nclass Person():\n\n    def _private_func():\n        pass\n```\n\n## 4、变量名\n\n* 变量名尽量小写, 如有多个单词，用下划线隔开\n\n```python\nif __name__ == '__main__':\n    count = 0\n    school_name = ''\n```\n\n* 常量采用全大写，如有多个单词，使用下划线隔开\n\n```python\nMAX_CLIENT = 100\nMAX_CONNECTION = 1000\nCONNECTION_TIMEOUT = 600\n```\n\n## 5、常量 ##\n\n* 常量使用以下划线分隔的大写命名\n\n```python\nMAX_OVERFLOW = 100\n\nClass FooBar:\n\n    def foo_bar(self, print_):\n        print(print_)\n\n```"
  },
  {
    "path": "Article/django/Django.md",
    "content": "# Django\n\nPython 下有许多款不同的 Web 框架。Django 是重量级选手中最有代表性的一位。许多成功的网站和 APP 都基于 Django。\n\n如果对自己的基础有点信息的童鞋，可以尝试通过国外的 ![Django 博客从搭建到部署系列教程](https://simpleisbetterthancomplex.com/series/2017/09/04/a-complete-beginners-guide-to-django-part-1.html) 进行入门，这个教程讲的非常的详细，而且还有很多有趣的配图。不过可能因为墙的原因，很多人会访问不到，就算访问到了，也因为是英语的，不会进行耐心的阅读学习。因此我打算翻译这个教程。\n\n* [一个完整的初学者指南Django-part1](/Article/django/一个完整的初学者指南Django-part1.md)\n* [一个完整的初学者指南Django-part2](/Article/django/一个完整的初学者指南Django-part2.md)\n\n后面经一个朋友说，这个教程已经有人在翻译了，因此我也不翻译了，不过感觉我的翻译还是挺好的，因为不是直译的，是通过了解后，用自己的语言再次表达出来。\n\n这里有上面这个教程翻译计划的 [Github](https://github.com/wzhbingo/django-beginners-guide) 以及 [博客](https://www.cloudcrossing.xyz/post/20/)，觉得哪个看得舒服，就选哪个进行学习。\n"
  },
  {
    "path": "Article/django/一个完整的初学者指南Django-part1.md",
    "content": ">源自：https://simpleisbetterthancomplex.com/series/2017/09/04/a-complete-beginners-guide-to-django-part-1.html\n\n### 一个完整的初学者指南Django - 第1部分\n\n ![一个完整的初学者指南Django  - 第1部分](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/featured.jpg)\n\n\n ![Python 3.6.2](https://img.shields.io/badge/python-3.6.2-brightgreen.svg) ![Django 1.11.4](https://img.shields.io/badge/django-1.11.4-brightgreen.svg)\n\n\n#### 介绍\n\n![欢迎班](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/Pixton_Comic_Welcome_Class.png)\n\n\n今天我将开始一个关于 Django 基础知识的新系列教程。这是一个完整的 Django 初学者指南。材料分为七个部分。我们将从安装，开发环境准备，模型，视图，模板，URL 到更高级主题（如迁移，测试和部署）来探索所有基本概念。\n\n\n我想做一些不同的事情。一个教程，易于遵循，信息丰富和有趣的阅读。因此我想出了在文章中创建一些漫画的想法来说明一些概念和场景。希望你喜欢这种阅读方式！\n\n\n但在我们开始之前......\n\n\n我想通过孔夫子的名言来开始我们的课程：\n\n> 我听见了，我就忘了\n> \n> 我看见了，我就记得了\n> \n> 我去做了，我就理解了\n\n![孔子名言](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/Pixton_Comic_Confucius_Quote.png)\n\n所以，一定要动手！不要只阅读教程。让我们一起来实操，这样你将通过做和练会学习到更多的知识。\n\n* * *\n\n#### 为什么选择 Django？\n\n\nDjango 是一个用 Python 编写的 Web 框架。这个 Web 框架支持动态网站，应用程序和服务开发。它提供了一组工具和功能，可解决许多与 Web 开发相关的常见问题，例如安全功能，数据库访问，会话，模板处理，URL 路由，国际化，本地化等等。\n\n\n使用诸如 Django 之类的 Web 框架，能使我们能够以标准化的方式快速开发安全可靠的Web 应用程序，从而无需重新开发。\n\n\n那么，Django 有什么特别之处呢？\n\n对于初学者来说，这是一个 Python Web 开源框架，这意味着您可以从各种各样的开源库中受益。在[python软件资料库(pypi)](https://pypi.python.org/pypi) 中托管了超过 **11.6万** 个的包（按照 2017 年 9 月 6 日的数据）。如果你需要解决一个特定问题的时候，可能已经有相关的库给你使用。\n\nDjango 是用 Python 编写的最流行的 Web 框架之一。它可以提供各种功能，例如用于开发和测试的独立 Web 服务器，缓存，中间件系统，ORM，模板引擎，表单处理，基于 Python 单元测试工具的接口。Django 还附带了电池，提供内置应用程序，如认证系统，具有自动生成`CRUD`(增删改除)操作页面的管理界面，生成订阅文档（RSS / Atom），站点地图等。甚至在 Django 中建立了一个地理信息系统（GIS）框架。\n\n而且 Django 的开发得到了 [Django软件基金会的](https://www.djangoproject.com/foundation/)支持，并且由 JetBrains 和 Instagram 等公司赞助。Django 到目前为止，已经持续开发维护超过12年了，这足以证明它是一个成熟，可靠和安全的 Web 框架。\n\n##### 谁在使用Django？\n\n为什么要知道谁在使用 Django 呢？\n\n因为这能很好的让我们了解和知道它能做些什么？\n\n在使用 Django 的最大网站中，有：[Instagram](https://instagram.com/)， [Disqus](https://disqus.com/)，[Mozilla](https://www.mozilla.org/)， [Bitbucket](https://bitbucket.org/)，[Last.fm](https://www.last.fm/)， [National Geographic](http://www.nationalgeographic.com/)。\n\n当然，远远不止上面列举的这些，你可以看下 [Django Sites](https://www.djangosites.org/) 数据库，它们提供了超过 **5000** 个 Django 支持的 Web站点的列表。\n\n顺便说一下，去年在 Django Under The Hood 2016 大会上，Django 核心开发人员Carl Meyer 和 Instagram 员工就[如何在规模上使用Django](https://www.youtube.com/watch?v=lx5WQjXLlq8) 以及它如何支持其增长展开了一次演讲。这是一个长达一个小时的谈话，如果你有兴趣的话，可以去了解下。\n\n\n* * *\n\n#### 安装\n\n如果我们想开始使用 Django ，那么我们需要安装一些应用程序，包括安装 **Python**，**Virtualenv** 和 **Django**。\n\n![基本设置](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/Pixton_Comic_Basic_Setup.png)\n\n\n一开始，强烈建议使用虚拟环境，虽然不是强制性的，可是这对于初学者来说，是一个很好的开端.\n\n\n在使用 Django 开发 Web 站点或 Web 项目时，必须安装外部库以支持开发是非常常见的。使用虚拟环境，您开发的每个项目都会有其独立的环境。所以依赖关系不会发生冲突。它还允许您维护在不同 Django 版本上运行的本地计算机项目。\n\n\n##### 安装Python 3.6.2\n\n我们想要做的第一件事是安装最新的  Python 发行版，它是 **Python 3.6.2**。至少在我写这篇教程的时候是这样。如果有更新的版本，请与它一起使用。接下来的步骤应该保持大致相同。\n\n我们将使用 Python 3，因为最重要的 Python 库已经移植到 Python 3，并且下一个主要的 Django 版本（2.x）不再支持 Python 2。所以 Python 3 是很有必要的。\n\n在 Mac 中，最好的安装方法就是 [Homebrew](https://brew.sh/)。如果您还没有在Mac 上安装它，请在 **终端** 运行以下命令：\n\n```\n/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"\n```\n\n如果您没有安装**命令行工具`(Command Line Tools)`**，则 Homebrew 安装可能需要更长一点时间。但它会自动处理，所以不用担心。请坐下来等到安装完成。\n\n当您看到以下消息时，您会知道安装何时完成：\n\n\n```\n==> Installation successful!\n\n==> Homebrew has enabled anonymous aggregate user behaviour analytics.\nRead the analytics documentation (and how to opt-out) here:\n  https://docs.brew.sh/Analytics.html\n\n==> Next steps:\n- Run `brew help` to get started\n- Further documentation:\n    https://docs.brew.sh\n```\n\n请运行以下命令来安装Python 3：\n\n```\nbrew install python3\n```\n\n\n由于 macOS 已经安装了Python 2，所以在安装 Python 3 之后，您将可以使用这两个版本。\n\n要运行 Python 2，请使用`python`终端中的命令。对于 Python 3，请`python3`改用。\n\n我们可以通过在终端中输入来测试安装：\n\n```\npython3 --version\nPython 3.6.2\n```\n\n![macOS测试Python 3](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/test-python.png)\n\n到此时，Python 已经安装完成了。进入下一步：虚拟环境！\n\n##### 安装 Virtualenv\n\n接下来这一步，我们将通过 **pip**(一个管理和安装Python包的工具)来安装**Virtualenv**。\n\n\n请注意，Homebrew 已经为您的 Python 3.6.2 安装了 `pip3`。\n\n在终端中，执行下面的命令：\n\n```\nsudo pip3 install virtualenv\n```\n\n![pip3安装virtualenv](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/pip-virtualenv.png)\n\n\n到目前为止，我们执行的操作都是在系统环境下的。不过，从这一刻起，我们安装的所有东西，包括 Django 本身，都将安装在虚拟环境中。\n\n\n你可以这样想像一下：对于每个 diango 项目，我们都会为它创建一个虚拟环境。这就好比每个 Django 项目都是一个独立的沙盒，你可以在这个沙盒里随意的玩，安装软件包，卸载软件包，不管怎么对系统环境都不会有任何影响，也不会对其他项目有影响。\n\n\n我个人喜欢在我的电脑上创建一个 **Development** 的文件夹，然后在这个文件夹下存放我的所有项目。当然，你也可以根据下面的步骤来创建你个人的目录。\n\n\n通常，我会在我的 **Development** 文件夹中创建一个项目名称的新文件夹。竟然这是我们的第一个项目，就直接将项目名称起为 **myproject**。\n\n```\nmkdir myproject\ncd myproject\n```\n\n![创建myproject文件夹](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/myproject.png)\n\n\n该文件夹将存储与 Django 项目相关的所有文件，包括其虚拟环境。\n\n接下来，我们将开始创建我们第一个虚拟环境和安装 Django。\n\n在 **myproject** 文件夹中，我们创建一个基于 python 3 的虚拟环境。\n\n```\nvirtualenv venv -p python3\n```\n\n![VIRTUALENV](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/venv.png)\n\n如上图所示，我们的虚拟环境已创建完成。那么我们将如何使用它呢？\n\n\n当然，我们先开启虚拟环境啦，可以通过以下命令来激活一下虚拟环境：\n\n\n ```\n source venv/bin/activate\n ```\n\n如果你在命令行的前面看到 **（venv）**，就说明，虚拟环境激活成功，现在已经进入到虚拟环境里面了。如下图所示：\n\n\n![Virtualenv激活](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/activate.png)\n\n\n那么这里面到底发生了什么呢？\n\n\n其实这里我们首先创建了名为 **venv** 的特殊文件夹，这个文件夹里面有 python 的副本，当我们激活 **venv** 环境之后，运行 `Python` 命令时，它使用的是存储在 **venv** 里面 `Python` 环境 ，而不是我们装在操作系统上的。\n\n\n如果在该环境下，我们使用 **PIP** 安装 python 软件包，比如 Django ，那么它是被安装在 **venv** 的虚拟环境上的。\n\n\n这里有一点需要注意的，当我们启动了 **venv** 这个虚拟环境后，我们使用命令 `python` 就能调用 python 3.6.2 ，而且也仅仅使用 `pip`（而不是`pip3`）来安装软件包。\n\n\n那么如果我们想退出 **venv** 虚拟环境，该如何操作呢？\n\n只要运行以下命令就可以：\n\n```\ndeactivate\n```\n\n不过，现在我们先不退出虚拟环境 **venv** ，保持着虚拟环境的激活状态，开始下一步操作。\n\n\n\n\n##### 安装Django 1.11.4\n\n现在我们来安装以下 Django 1.11.4 ，因为我们已经开启了虚拟环境 **venv** ，因此，这操作会非常的简单。我们将运行下面的命令来安装 Django ：\n\n```\npip install django\n```\n\n![pip安装django](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/mac/pip-django.png)\n\n\n安装成功了，现在一切都准备就绪了！\n\n\n![结束安装](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/Pixton_Comic_End_Installation.png)\n\n* * *\n\n#### 开始一个新项目\n\n要开始一个新的 Django项目，运行下面的命令：\n\n到目前为止，我们终于可以开始一个新的 Django 项目了，运行下面的命令，创建一个 Django 项目：\n\n```\ndjango-admin startproject myproject\n```\n\n命令行工具 **django-admin** 会在安装 Django 的时候一起安装的。\n\n\n当我们运行了上面的命令之后，系统就会自动的为 Django 项目生成基础的文件。\n\n\n我们可以打开 **myproject** 目录，可以看到具体的文件结构如下所示：\n\n\n```\nmyproject/                  <-- higher level folder\n |-- myproject/             <-- django project folder\n |    |-- myproject/\n |    |    |-- __init__.py\n |    |    |-- settings.py\n |    |    |-- urls.py\n |    |    |-- wsgi.py\n |    +-- manage.py\n +-- venv/                  <-- virtual environment folder\n```\n\n\n可以看到，一个初始 Django 的项目由五个文件组成：\n\n\n*   **manage.py**：**django-admin** 是命令行工具的快捷方式。它用于运行与我们项目相关的管理命令。我们将使用它来运行开发服务器，运行测试，创建迁移等等。\n*   **__init__.py**：这个空文件告诉 Python 这个文件夹是一个 Python 包。\n*   **settings.py**：这个文件包含了所有的项目配置。我们会一直使用到这个文件。\n*   **urls.py**：这个文件负责映射我们项目中的路由和路径。例如，如果您想在 URL `/about/` 中显示某些内容，则必须先将其映射到此处。\n*   **wsgi.py**：该文件是用于部署简单的网关接口。现在我们暂时不用关心它的内容。\n\n\n\nDjango 自带有一个简单的 Web 服务器。在开发过程中非常方便，所以我们不需要安装其他任何软件即可以在本地运行项目。我们可以通过执行命令来运行它：\n\n```\npython manage.py runserver\n```\n\n\n现在在 Web 浏览器中打开以下 URL：**http://127.0.0.1:8000**，您应该看到以下页面：\n\n\n![有效！](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/it-worked.png)\n\n\n这里提醒一点，如果你需要停止服务器，可以 `Control + C` 点击停止开发服务器。\n\n* * *\n\n#### Django 的应用\n\n\n在 Django 哲学中，我们有两个重要的概念：\n\n*   **app**：是一个可以执行某些操作的 Web 应用程序。一个应用程序通常由一组 models(数据库表)，views(视图)，templates(模板)，tests(测试) 组成。\n*   **project**：是配置和应用程序的集合。一个项目可以由多个应用程序或一个应用程序组成。\n\n请注意，如果没有一个 project，你就无法运行 Django 应用程序。像博客这样的简单网站可以完全在单个应用程序中编写，例如可以将其命名为 blog或 weblog。\n\n![Django应用程序](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/Pixton_Comic_Django_Apps.png)\n\n\n当然这是组织源代码的一种方式，现在刚入门，判断确定什么是不是应用程序这些还不太重要。包括如何组织代码等，现在都不是担心这些问题的时候。现在，首先让我们先熟悉了解 Django 的 API 和基础知识。\n\n好了，为了更好的了解，我们先来创建一个简单的论坛项目，那么我们要创建一个应用程序，首先要进入到 **manage.py** 文件所在的目录并执行以下命令：\n\n```\ndjango-admin startapp boards\n```\n\n\n请注意，这次我们使用了命令 **startapp**。\n\n这会给我们以下的目录结构：\n\n```\nmyproject/\n |-- myproject/\n |    |-- boards/                <-- our new django app!\n |    |    |-- migrations/\n |    |    |    +-- __init__.py\n |    |    |-- __init__.py\n |    |    |-- admin.py\n |    |    |-- apps.py\n |    |    |-- models.py\n |    |    |-- tests.py\n |    |    +-- views.py\n |    |-- myproject/\n |    |    |-- __init__.py\n |    |    |-- settings.py\n |    |    |-- urls.py\n |    |    |-- wsgi.py\n |    +-- manage.py\n +-- venv/\n```\n\n\n所以，我们先来看看每个文件的功能：\n\n*   **migrations /**：在这个文件夹中，Django 会存储一些文件以跟踪您在 **models.py** 文件中创建的更改，目的是为了保持数据库和 **models.py** 同步。\n*   **admin.py**：这是 Django应用程序一个名为 **Django Admin** 的内置配置文件。\n*   **apps.py**：这是应用程序本身的配置文件。\n*   **models.py**：这里是我们定义 Web 应用程序实体的地方。models  由 Django 自动转换为数据库表。\n*   **tests.py**：该文件用于为应用程序编写单元测试。\n*   **views.py**：这是我们处理Web应用程序请求(request)/响应(resopnse)周期的文件。\n\n现在我们创建了我们的第一个应用程序，让我们来配置一下项目以便启用这个应用程序。\n\n\n为此，请打开**settings.py**并尝试查找`INSTALLED_APPS`变量：\n\n**settings.py**\n\n```\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n]\n```\n\n正如你所看到的，Django 已经安装了6个内置的应用程序。它们提供大多数Web应用程序所需的常用功能，如身份验证，会话，静态文件管理（图像，JavaScript，CSS等）等。\n\n我们将会在本系列教程中探索这些应用程序。但现在，先不管它们，只需将我们的应用程序 boards 添加到 `INSTALLED_APPS` 列表即可：\n\n```\nINSTALLED_APPS = [\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n\n    'boards',\n]\n```\n\n使用上个漫画中的正方形和圆形的比喻，黄色圆圈将成为我们的 **boards** 应用程序，而 **django.contrib.admin**，**django.contrib.auth** 等将成为红色圆圈。\n\n* * *\n\n#### Hello, World!\n\n\n现在我们先来写一个我们的第一个 **视图（view）** ，那么，现在我们来看看该如何使用 Django 来创建一个新的页面吧。\n\n\n打开 **boards** 应用程序中的 **views.py** 文件，并添加下面的代码：\n\n**views.py**\n\n```python\nfrom django.http import HttpResponse\n\ndef home(request):\n    return HttpResponse('Hello, World!')\n```\n\n**视图（view）** 是接收 `HttpRequest` 对象并返回 `HttpResponse`对象的 Python 函数。接收 request 作为参数并返回 response 作为结果。这个过程是需要我们记住的。\n\n\n因此，就像我们上面的代码，我们定义了一个简单的视图，命名为 `home` ，然后我们简单的返回了一个字符串 **Hello，World！**\n\n\n那么我们直接运行就可以了吗？\n\n并不是的，我们还没有告诉 Django 什么时候调用这个 **视图（view）** 呢？这就需要我们在 **urls.py** 文件中完成：\n\n\n**urls.py**\n\n```Python\nfrom django.conf.urls import url\nfrom django.contrib import admin\n\nfrom boards import views\n\nurlpatterns = [\n    url(r'^/code>, views.home, name='home'),\n    url(r'^admin/', admin.site.urls),\n]\n```\n\n\n如果您将上面的代码段与您的 **urls.py** 文件进行比较，您会注意到我添加了以下的代码：`url(r'^$', views.home, name='home')` 并使用我们的应用程序 **boards** 中导入了 **views** 模块。`from boards import views`\n\n可能这里大家还是会有很多疑问，不过先这样做，在后面我们会详细探讨这些概念。\n\n但是现在，Django 使用**正则表达式**来匹配请求的URL。对于我们的 **home** 视图，我使用的是`^$`正则表达式，它将匹配空白路径，这是主页（此URL：**http://127.0.0.1:8000**）。如果我想匹配URL **http://127.0.0.1:8000/homepage/**，那么我们 url 的正则表达式就应该这样写：`url(r'^homepage/$', views.home, name='home')`。\n\n运行项目，让我们看看会发生什么：\n\n```\npython manage.py runserver\n```\n\n\n在 Web 浏览器中，打开 http://127.0.0.1:8000 ：\n\n\n![你好，世界！](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-1/hello-world.png)\n\n\n这样我们就看到了我们刚刚创建的第一个界面了。\n\n* * *\n\n#### 总结\n\n这是本系列教程的第一部分。在本教程中，我们学习了如何安装最新的 Python 版本以及如何设置开发环境。我们还介绍了虚拟环境，并开始了我们第一个 Django 项目，并已创建了我们的初始应用程序。\n\n我希望你喜欢第一部分！第二部将涉及模型，视图，模板和网址。我们将一起探索所有的Django 基础知识！\n\n就这样我们可以保持在同一页面上，我在 GitHub 上提供了源代码。项目的当前状态可以在发布**release tag v0.1-lw**下找到。下面的链接将带你到正确的地方：\n\n[https://github.com/sibtc/django-beginners-guide/tree/v0.1-lw](https://github.com/sibtc/django-beginners-guide/tree/v0.1-lw)\n"
  },
  {
    "path": "Article/django/一个完整的初学者指南Django-part2.md",
    "content": " ![一个完整的初学者指南Django  - 第2部分](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/featured.jpg)\n\n\n ![Python 3.6.2](https://img.shields.io/badge/python-3.6.2-brightgreen.svg) ![Django 1.11.4](https://img.shields.io/badge/django-1.11.4-brightgreen.svg)\n\n\n#### 介绍\n\n\n欢迎来到 Django 教程的第二部分！在上一课中，我们安装了项目所需要的一切软件，希望你们在学习这篇文章之前，安装了 Python 3.6，并且在虚拟环境中运行Django 1.11。因为，在本篇文章中，我们将继续在这个项目中编写我们的代码。\n\n\n在这一篇文章中，可能不会有太多的代码操作，主要是讨论分析项目。在下一篇中，我们就开始学习 Django 的基础知识，包括模型（models），管理后台（admin），视图（views），模板（templates）和 路由（URL）。\n\n\n在这里，还是跟第一篇一样，建议大家多动手。\n\n* * *\n\n#### 论坛项目\n\n\n每个人的学习习惯都是不同的，不知道你们是怎样的，就我个人而言，通过看实例和一些代码片段，可以让我学的更多，学的更快。但是，有些时候当我们看到 `Class A`和`Class B` ，或者是 `foo(bar)` 这样的例子的时候，我们是很难理解这些概念的。\n\n\n所以在我们进入模型（models），创建视图（views） 这些有趣的代码实操之前，我们还是需要花点时间，简单的讨论一下我们将怎样设计，开发这个项目。\n\n\n但是如果你已经有 web 开发经验的，而且觉得讲的太细了，那么你可以快速的浏览一下，然后进入到 【模型（models）】那一块中。\n\n如果你对 Web 开发并不熟悉，那么我强烈建议你认真阅读下去。这里会介绍 web 应用程序开发的建模和设计，因为对于 web 开发来说，敲代码只是其中的一部分，模型的设计也是很重要的。\n\n\n![火箭科学](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/Pixton_Comic_Rocket_Science.png)\n\n\n##### 用例图\n\n\n我们的项目本身是一个论坛系统，整个项目来说就是维护几个【论坛板块（boards）】 ，然后在每个板块里面，用户可以通过创建【主题（Topic）】并且在主题中讨论。\n\n\n一般情况下，只有管理员才能创建【论坛板块（boards）】，那么在用户这方面，我们就需要分为普通用户和管理员用户了，而且他们拥有的权限是不同的，管理员用户可以创建 【论坛板块（boards）】，【主题（Topic）】以及讨论回复，而普通用户只能发布【主题（Topic）】以及讨论回复。具体每个用户角色的功能分配如下图：\n\n\n\n> 图1：Web Board 核心功能的用例图\n\n\n![用例图](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/use-case-diagram.png)\n\n\n\n##### 类图\n\n\n从上面的用例图中，我们可以开始思考我们项目中的**实体类**有哪些了。这些实体是我们要创建的模型，它与我们的 Django 应用非常密切。\n\n\n如果要实现上面我们说到的论坛，那么我们至少需要以下的几个模型：**Board**，**Topic**，**Post**和**User**。\n\n* **Board** : 版块\n* **Topic** : 主题\n* **Post** : 帖子（用户评论与回复）\n* **User** : 用户\n\n\n> 图2：Web Board 类图\n\n\n![基本类图](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/basic-class-diagram.png)\n\n\n上面我们只是说了需要有几个模型，并没有提到模型与模型之间是怎么关联的。\n\n\n通过上面的图片我们可以知道，主题（Topic）与版块（Board） 之间是有关联的，就好比我们需要知道这个主题（Topic） 是属于哪一个版块的（Board），因此我们需要一个字段，也就是可以通过外键爱关联它们。\n\n\n同样的，一个帖子（Post） 也是需要确定它是那个主题的，当然，用户和主题（Topic）和帖子（Post） 之间也是有联系的，因为我们需要确认是谁发的帖子，是谁回复评论了内容。\n\n\n竟然知道了模型之间的联系了，那么我们也必须要考虑这些模型应该存放哪些信息。就目前而言，我们的模型可以设计成这样：\n\n\n> 图3：类（模型）之间关系的类图\n\n\n![类图](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram.png)\n\n\n\n这个类图强调的是模型之间的关系，当然最后这些线条和箭头都会用字段来进行表示。\n\n**Board（版块模型）** ：Board 中有 **name** 和 **description** 这两个字段，name 是唯一的，主要是为了避免两个名称重复。description 则是用于描述把这个版块来用干什么的。\n\n\n**Topic（主题模型）** ：subject 表示主题内容，last_update 用来定义话题的排序，starter 用来识别谁发起的话题，board 用于指定它属于哪个版块\n\n\n**Post（帖子模型）** ： message 字段，用于存储回复的内容，created_at 创建的时间，在排序时候用（最先发表的帖子排最前面），updated_at 更新时间，告诉用户是否更新了内容，同时，还需要有对应的 User 模型的引用，Post 由谁创建的和谁更新的。\n\n\n**User（用户模型）** ：这里有 username ，password，email 和 is_superuser 四个字段。\n\n\n这里值得注意的是，我们在 Django 应用中，不需要创建 User 用户模型，因为在 Django 的 contrib 中已经内置了 User 模型，我们可以直接拿来使用，就没必要重新创建了。\n\n\n认真观察的童鞋应该看到了，上面的模型关系图中，模型与模型之间的对应关系有数字 1，0..* 等等的字段，这是代表什么意思呢？\n\n\n如下图，`1` 代表一个 Topic 必须与一个  Board 相关联，`0..*` 代表 Board 下面可能会有多个和 0 个 Topic ，也就是一对多的关系。\n\n\n![类图板和主题协会](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-board-topic.png)\n\n\n这里也是一样，`1` 代表一个 Post 只有一个  Topic ，`1..*` 代表一个 Topic 下面可能会有 1 个和多个个 Post ，也就是说，一个主题最少一个一个帖子。\n\n\n\n![类图主题和帖子关联](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-topic-post.png)\n\n\n`1` 代表一个 Topic 有且至于一个  User ，`0..*` 代表一个 User（用户） 可能拥有多个 Topic ，也可能没有。\n\n\n![类图主题和用户关联](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-topic-user.png)\n\n\nPost（帖子） 和 User（用户）的关系也是类似，一个 Post 必须有一个 User ，而一个 User 可能没有也可能有多个 Post。这里的 Post ，用户发布了之后是可以进行修改的，也就是更新（updated_by），当然如果又被修改，updated_by 就是为空了。\n\n\n![类图邮政和用户协会](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-post-user.png)一\n\n\n当然，如果你觉得上面的图看起来很复杂，那么你也可以不需要强调模型与模型之间的关系，直接强调字段就可以了，如下图：\n\n\n> 图4：强调类（模型）属性（字段）的类图\n\n\n![类图属性](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-attributes.png)\n\n\n其实这种表达图和前面那个显示箭头和线的表达图，要表达的内容是一样的。不过使用这种表达方式可能更符合 Django  Modles API 的设计。\n\n\n好了，现在已经够 UML 了！为了绘制本节介绍的图表，我使用的是 [StarUML](http://staruml.io/) 工具。\n\n\n##### 原型图\n\n\n花了一些时间来设计我们的程序模型，后面我们也需要设计一下我们的网页原型图。只有这样，才能更好的让我们清楚的知道自己将要干什么？\n\n\n![线框漫画](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/Pixton_Comic_Wireframes.png)\n\n\n\n首先，是我们的主页，在主页中，我们会显示我们所有的版块：\n\n\n> 图5：主页显示所有的版块信息\n\n\n![线框板](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/wireframe-boards.png)\n\n\n同样的，当用户点进了版块信息，进入到版块页面，那么版块页面也将显示该版块下的所有主题：\n\n\n>图6：版块下的所有主题信息\n\n![线框主题](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/wireframe-topics.png)\n\n\n通过观察图片，细心的你，可能会发现，用户在这个页面有两条可以走的路线。第一条就是点击 “new topic” 来创建新的主题，第二条就是点击已经存在的主题进入相关的主题进行讨论回复。\n\n\n\n“new topic” 的界面如下 ：\n\n\n![线框新主题](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/wireframe-new-topic.png)\n\n\n而，进入了相关的主题后，应该显示具体的帖子信息和用户的一些回复信息：\n\n\n![线框帖子](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/wireframe-posts.png)\n\n\n\n如果用户点击 “Reply” 的按钮，他们将看到下面的页面，并以相反的顺序（最新的第一个）对帖子进行显示：\n\n![线框回复](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/wireframe-reply.png)\n\n\n那么这些图是用什么来绘制的呢？你可以使用 [draw.io](https://draw.io/) ，而且他是完全免费的。\n\n\n* * *\n\n#### 模型（Models）\n\n\n上一部分，设计了我们 Web 应用的数据库还有界面原型设计。在模型（Models）这一部分中，我们将在 Django 中创建我们数据库的模型类：**Board** ，**Topic** 和 **Post** 。\n\n\n这里是不是有个疑问，明明我们设计数据库的时候是有 **User** 的，为什么我们不用创建它的模型类呢？是不是写漏了？\n\n\n并不是，那是因为 **User** 这个模型类，已经内置在 Django 应用程序中的，**User** 模型就在 **django.contrib.auth** 中。在 settings.py 中，`INSTALLED_APPS` 就配置了**django.contrib.auth**。\n\n\n好了，现在我们将根据我们上面设计的数据库模型来完成我们项目 **boards** 下的 models.py 文件中的所有操作。\n\n\n> **boards/models.py**\n\n```python\nfrom django.db import models\nfrom django.contrib.auth.models import User\n\nclass Board(models.Model):\n    name = models.CharField(max_length=30, unique=True)\n    description = models.CharField(max_length=100)\n\nclass Topic(models.Model):\n    subject = models.CharField(max_length=255)\n    last_updated = models.DateTimeField(auto_now_add=True)\n    board = models.ForeignKey(Board, related_name='topics')\n    starter = models.ForeignKey(User, related_name='topics')\n\nclass Post(models.Model):\n    message = models.TextField(max_length=4000)\n    topic = models.ForeignKey(Topic, related_name='posts')\n    created_at = models.DateTimeField(auto_now_add=True)\n    updated_at = models.DateTimeField(null=True)\n    created_by = models.ForeignKey(User, related_name='posts')\n    updated_by = models.ForeignKey(User, null=True, related_name='+')\n```\n\n可以看到，创建的所有模型类，**Board** ， **Topic** 和 **Post** 都是 **django.db.models.Model** 的子类，它们都将会转化成数据表。而 **django.db.models.Field** 的子类（Django 内置的核心类）的实例都会转化为数据表中的列。\n\n\n上面可以看到的 `CharField`，`DateTimeField` 等，都是 **django.db.models.Field** 的子类，在 Django 项目中都可以直接使用它们。\n\n\n在这里，我们仅仅使用了 `CharField`，`TextField`，`DateTimeField`，和 `ForeignKey` 字段来定义我们的模型（Models） 。当然，在 Django 中，不仅仅只是提供了这些字段，还提供了更多，更广泛的选择来代表不同类型的数据，比如还有：`IntegerField`，`BooleanField`， `DecimalField`。我们会根据不同的需求来使用它们。\t\n \n\n有些字段是需要参数的，就好比 `CharField` ，我们都设定了一个 `max_length` , 设置一个最大长度。当我们设定了这个字段后，就会作用于数据的。\n\n\n在 `Board` 模型（Model）中，在 `name` 字段中，我们也设置了参数 `unique=True`，顾名思义，这是为了在数据库中，保证该字段的唯一性。\n\n\n在 `Post` 模型中，`created_at` 字段有一个可选参数，`auto_now_add` 设置为 `True`。这是为了指明 Django 在创建 `Post` 对象的时候，`created_at` 使用的是当前的日期和时间。\n\n\n创建模型与模型之间关系的其中一种方法就是使用 `ForeignKey` 字段，使用这个字段，会自动创建模型与模型之间的联系，而且会在数据库中也创建它们的关系。使用 `ForeignKey` 会有一个参数，来表明他与那个模型之间的联系。 例如：\n\n\n在 `Topic` 模型中，`models.ForeignKey(Board, related_name='topics')`，第一个参数是代表关联的表格（主表），在默认情况下，外键存储的是主表的主键（Primary Key）。第二个参数 `related_name` 是定义一个名称，用于反向查询的。Django 会自动创建这种反向关系。 虽然 `related_name` 是可选参数，但是如果我们不为它设置一个名称的，Django 会默认生成名称 `(class_name)_set` 。例如，在 `Board` 模型中，`Topic` 实例将在该 `topic_set` 属性下可用。而我们只是将其重新命名为`topics`，使用起来更加自然。\n\n\n在 `Post` 模型中，`updated_by` 字段设置`related_name='+'`。这指示 Django 我们不需要这种反向关系。\n\n\n下面这张图可以很好地看到设计图和源码之间的比较，其中绿线就表示了我们是如何处理反向关系的。\n\n\n![类图模型定义](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/class-diagram-django-models.png)\n\n\n可能到这一步，你会问：“主键呢？”好像我们都没有定义主键啊。对，如果我们没有为模型（Models）指定主键，那么 Django 会自动生成它。\n\n\n##### 迁移模型（Migrating the Models）\n\n\n到这一步，我们要开始告诉 Django 如何创建数据库，这样方便我们更好的使用。\n\n\n打开**终端** ，激活虚拟环境，进入到 **manage.py** 文件所在的文件夹，然后运行以下命令：\n\n\n```\npython manage.py makemigrations\n```\n\n这时，你会看到这样的输出信息：\n\n\n```\nMigrations for 'boards':\n  boards/migrations/0001_initial.py\n    - Create model Board\n    - Create model Post\n    - Create model Topic\n    - Add field topic to post\n    - Add field updated_by to post\n```\n\n\u0001\n此时，Django 在 **boards / migrations** 目录内创建了一个名为**0001_initial.py** 的文件。它代表了我们应用程序模型的当前状态。在下一步中，Django 将使用该文件来创建表和列。\n\n\n迁移文件被翻译成 SQL 语句。如果您熟悉 SQL，则可以运行以下命令来检查将在数据库中执行的 SQL 指令：\n\n```\npython manage.py sqlmigrate boards 0001\n```\n\n\n如果你不熟悉 SQL，也不用担心。在本系列教程中，我们不会直接使用 SQL。所有的工作都将使用 Django ORM 来完成，它是一个与数据库进行通信的抽象层。\n\n好了，下一步我们将把我们的迁移文件应用到我们的数据库中：\n\n\n```\npython manage.py migrate</code>\n```\n\n\n输出应该是这样的：\n\n```\nOperations to perform:\n  Apply all migrations: admin, auth, boards, contenttypes, sessions\nRunning migrations:\n  Applying contenttypes.0001_initial... OK\n  Applying auth.0001_initial... OK\n  Applying admin.0001_initial... OK\n  Applying admin.0002_logentry_remove_auto_add... OK\n  Applying contenttypes.0002_remove_content_type_name... OK\n  Applying auth.0002_alter_permission_name_max_length... OK\n  Applying auth.0003_alter_user_email_max_length... OK\n  Applying auth.0004_alter_user_username_opts... OK\n  Applying auth.0005_alter_user_last_login_null... OK\n  Applying auth.0006_require_contenttypes_0002... OK\n  Applying auth.0007_alter_validators_add_error_messages... OK\n  Applying auth.0008_alter_user_username_max_length... OK\n  Applying boards.0001_initial... OK\n  Applying sessions.0001_initial... OK\n```\n\n\n\n因为这是我们第一次迁移数据库，所以该 `migrate` 命令还应用了 Django contrib 应用中现有的迁移文件，这些文件列于 `settings.py` 中的 `INSTALLED_APPS` 。\n\n\n而 `Applying boards.0001_initial... OK` 就是指我们在上一步中生成的迁移文件。\n\n\n好了，此时！我们的数据库已经可以使用了。\n\n\n![SQLite的](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/Pixton_Comic_SQLite.png)\n\n\n> **注意：** 需要注意的是 **SQLite** 是一个数据库。SQLite 被许多公司用于成千上万的产品，如所有 Android 和 iOS 设备，所有主要的 Web 浏览器，Windows 10，MacOS 等。\n>\n> 当然，它也不是适合所有的应用场景。SQLite 不能与 MySQL，PostgreSQL 或 Oracle 等数据库进行比较。大容量网站，密集型的应用程序，大数据集，高并发性，这些使用使用 SQLite 可能会导致很多问题。\n>\n> 在我们开发的项目中，我们将使用 SQLite ，因为它很方便，我们不需要安装其他任何东西。当我们将项目部署到生产环境时，我们将切换到 PostgreSQL 。因为这对于简单的网站是不错的选择。但这里有一点要注意，对于复杂的网站，建议在开发和生产中使用相同的数据库。\n\n\n##### Models API\n\n\n使用 Python 开发的一个重要优点是交互式 shell。我几乎一直都在使用它。这是一个可以快速尝试和测试实验的方法。\n\n你可以使用 **manage.py** 加载我们的项目来启动 Python shell ：\n\n启动命令：\n\n```\npython manage.py shell\n```\n\n可以看到这样的输出：\n\n```\nPython 3.6.2 (default, Jul 17 2017, 16:44:45)\n[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n(InteractiveConsole)\n>>>\n```\n\n\n在我们使用 `python manage.py shell` 之外，我们也可以将项目添加到`sys.path`并加载 Django。这意味着我们可以在项目中导入我们的模型(models) 和任何其他资源。\n\n我们从导入 **Board** 类开始：\n\n```\nfrom boards.models import Board\n```\n\n如果我们需要创建 **Board** 对象，我们可以执行以下操作：\n\n```\nboard = Board(name='Django', description='This is a board about Django.')\n```\n\n此时我们只是创建了这个对象，并没有保存到数据库的，因此我们可以调用 `save` 方法，将这个对象保存在数据库中。\n\n\n```\nboard.save()\n```\n\n该 `save` 方法 ，在创建对象和更新对象中都可以使用，这里 Django 会创建一个新的对象，因为 **Board** 实例是没有 **id** 这个字段的，因此保存后，Django 会自动设置一个 ID ：\n\n\n```\nboard.id\n1\n```\n\n\n其他的字段你也可以当作属性来访问就好了，比如：\n\n```\nboard.name\n'Django'\n```\n\n```\nboard.description\n'This is a board about Django.'\n```\n\n\n要更新一个值，我们可以这样做：\n\n\n```\nboard.description = 'Django discussion board.'\nboard.save()\n```\n\n\n每个 Django 模型 (Models) 都带有一个特殊的属性; 我们称之为 **Model Manager（模型管理器）**。我们可以通过 Python 属性 `objects` 来访问它。它主要用于在数据库中执行查询。例如，我们可以使用它来直接创建一个新的**Board** 对象：\n\n```\nboard = Board.objects.create(name='Python', description='General discussion about Python.')\n```\n\n```\nboard.id\n2\n```\n\n```\nboard.name\n'Python'\n```\n\n所以，结合之前的操作，我们现在有两个 boards 对象。我们可以使用`objects` 列出数据库中所有现有的 boards ：\n\n\n```\nBoard.objects.all()\n<QuerySet [<Board: Board object>, <Board: Board object>]>\n```\n\n\n结果是一个 **QuerySet** 。稍后我们会进一步了解它。基本上，它是来自数据库的对象列表。通过输出结果，可以看到我们有两个对象，但我们只能读取 **Board对象** 。这是因为我们没有在 **Board** 模型中定义 `__str__` 方法。\n\n\n该 `__str__` 方法是一个对象的字符串表示。我们可以使用 Board 的名称来表示它。\n\n\n首先，退出交互式控制台：\n\n\n```\nexit()\n```\n\n\n现在编辑 **boards** 应用程序中的 **models.py** 文件：\n\n\n```\nclass Board(models.Model):\n    name = models.CharField(max_length=30, unique=True)\n    description = models.CharField(max_length=100)\n\n    def __str__(self):\n        return self.name\n```\n\n\n让我们再次尝试查询。再次打开交互式控制台：\n\n\n```\nfrom boards.models import Board\n\nBoard.objects.all()\n<QuerySet [<Board: Django>, <Board: Python>]>\n```\n\n\n仔细对比上面的，看下区别？\n\n可以看到上面那个是 object ，而这里是我们定义的字符串。\n\n\n我们可以将这个 **QuerySet** 看作一个列表。假设我们想遍历它并打印每个 Board（版块） 的描述：\n\n\n```\nboards_list = Board.objects.all()\nfor board in boards_list:\n    print(board.description)\n```\n\n\n结果是：\n\n\n```\nDjango discussion board.\nGeneral discussion about Python.\n```\n\n\n当然，我们也可以使用 **Model Manager（模型管理器）** 来查询数据库，如果查询其中的一个，我们可以使用 `get` 的方法：\n\n\n```\ndjango_board = Board.objects.get(id=1)\n\ndjango_board.name\n'Django'\n```\n\n当然我们要小心这种情况，因为很容易发生内存溢出的。比如我们试图去查询一个不存在的对象，就好比我们数据库只有两个 Board 对象，如果你查询 `id=3`，那么它会引发一个异常：\n\n\n```\nboard = Board.objects.get(id=3)\n\nboards.models.DoesNotExist: Board matching query does not exist.\n```\n\n当然，在 `get` 方法中，参数可以是该模型下的字段，最好是使用唯一的标识字段。否则会返回多个对象，会导致异常的。\n\n\n```\nBoard.objects.get(name='Django')\n<Board: Django>\n```\n\n\n请注意，查询是区分大小写的，小写 “django” 是不匹配的：\n\n\n```\nBoard.objects.get(name='django')\nboards.models.DoesNotExist: Board matching query does not exist.\n```\n\n\n##### 模型操作摘要\n\n下面的表格是我们在本章节中学到的方法和操作。代码示例使用 **Board** 模型作为参考示例。大写的 **Board** 代表类，小写的 **board** 是指 **Board** 的实例对象。\n\n\n| 描述 | 代码示例 |\n| --- | --- |\n| 创建一个对象并没有保存 | `board = Board()` |\n| 保存一个对象（创建或更新） | `board.save()` |\n| 在数据库中创建并保存一个对象 | `Board.objects.create(name='...', description='...')` |\n| 列出所有对象 | `Board.objects.all()` |\n| 获取由字段标识的单个对象 | `Board.objects.get(id=1)` |\n\n\n在下一节中，我们将开始编写视图并在 HTML 页面中显示我们的版块页面。\n\n\n* * *\n\n#### Views, Templates 和静态文件\n\n\n回顾一下，我们之前做的。我们已经可以在应用程序的主页上显示 ”Hello ，World！“ 的界面了。\n\n\n> **MyProject/urls.py**\n\n```\nfrom django.conf.urls import url\nfrom django.contrib import admin\n\nfrom boards import views\n\nurlpatterns = [\n    url(r'^/code>, views.home, name='home'),\n    url(r'^admin/', admin.site.urls),\n]\n```\n\n> **boards/views.py**\n\n```\nfrom django.http import HttpResponse\n\ndef home(request):\n    return HttpResponse('Hello, World!')\n```\n\n好了，现在我们需要修改这个主页，如果你不记得我们的主页要做成什么样子，可以看看之前我们已经设计好的原型界面图。我们在主页上，要做的是在表格中显示一些版块的名单和其他的一些信息。\n\n\n首先我们要做的是：导入 **Board** 模型，然后获取所有的存在的版块（boards）信息\n\n\n> **boards/views.py**\n\n\n```\nfrom django.http import HttpResponse\nfrom .models import Board\n\ndef home(request):\n    boards = Board.objects.all()\n    boards_names = list()\n\n    for board in boards:\n        boards_names.append(board.name)\n\n    response_html = '<br>'.join(boards_names)\n\n    return HttpResponse(response_html)\n```\n\n\n\n然后我们运行，就会看到这个简单的 HTML 页面：\n\n\n\n![主页HttpResponse](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-httpresponse.png)\n\n\n但是，一般情况下，我们是不会通过这种方式去渲染 HTML ，在 **views.py** 中，我们只需要获取 **boards** 的集合，至于 HTML 渲染那部分的代码，我们应该在 Django 的 templates 目录下完成。\n\n\n##### Django 模板引擎设置\n\n竟然我们要将 **views.py** 里渲染 HTML 的代码分离，那么我们首先要在 **baords** 的同目录下，创建一个名为 **templates** 的文件夹。\n\n\n```\nmyproject/\n |-- myproject/\n |    |-- boards/\n |    |-- myproject/\n |    |-- templates/   <-- here!\n |    +-- manage.py\n +-- venv/\n```\n\n在我们创建的 **templates** 文件夹中，我们创建一个名为 **home.html** 的 HTML 文件\n\n> **templates/home.html**\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Boards</title>\n  </head>\n  <body>\n    <h1>Boards</h1>\n\n    {% for board in boards %}\n      {{ board.name }} <br>\n    {% endfor %}\n\n  </body>\n</html>\n```\n\n**home.html** 的文件内容如上面的一样，是一些原始的 HTML 标签代码和 Django 语言上的代码：`{% for ... in ... %}` ，`{{ variable }}`。上面的代码中展示了如何使用 for 循环遍历 list 对象。\n\n到此，我们的 HTML 页面已经完成了，可是我们还没有告诉 Django 在哪里能找到我们应用中的 `templates` 文件夹里的 HTML。\n\n\n首先，我们在 Django 中绑定一下我们的 `templates` ,打开我们 ** myproject** 项目中的 **settings.py** 文件，搜索 `TEMPLATES` 变量然后在 `DIRS`设置 ：`os.path.join(BASE_DIR, 'templates')`\n\n具体如下：\n\n```python\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [\n            os.path.join(BASE_DIR, 'templates')\n        ],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n```\n\n这样设计就好比相当于在你的项目中的完整路径下，在加上 \"/templates\"\n\n那是不是跟我们预想的一样呢？我们可以通过 python shell 进行调试：\n\n```\npython manage.py shell\n```\n\n```\nfrom django.conf import settings\n\nsettings.BASE_DIR\n'/Users/vitorfs/Development/myproject'\n\nimport os\n\nos.path.join(settings.BASE_DIR, 'templates')\n'/Users/vitorfs/Development/myproject/templates'\n```\n\n\n可以看到，目录就是指向我们在上面创建的 **templates** 文件夹\n\n此时，我们只是绑定了 **templates** 文件夹的路径，Django 并没有绑定我们 **home.html** ，我们可以在 **views.py** 中绑定：\n\n```\nfrom django.shortcuts import render\nfrom .models import Board\n\ndef home(request):\n    boards = Board.objects.all()\n    return render(request, 'home.html', {'boards': boards})\n```\n\n\n\n运行后，HTML 的页面是这样的：\n\n\n![主板渲染](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-render.png)\n\n我们可以改进HTML模板来代替使用表格：\n\n> **templates/home.html**\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Boards</title>\n  </head>\n  <body>\n    <h1>Boards</h1>\n\n    <table border=\"1\">\n      <thead>\n        <tr>\n          <th>Board</th>\n          <th>Posts</th>\n          <th>Topics</th>\n          <th>Last Post</th>\n        </tr>\n      </thead>\n      <tbody>\n        {% for board in boards %}\n          <tr>\n            <td>\n              {{ board.name }}<br>\n              <small style=\"color: #888\">{{ board.description }}</small>\n            </td>\n            <td>0</td>\n            <td>0</td>\n            <td></td>\n          </tr>\n        {% endfor %}\n      </tbody>\n    </table>\n  </body>\n</html>\n```\n\n\n![主板渲染](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-render-2.png)\n\n\n##### 测试主页\n\n\n![测试漫画](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/Pixton_Comic_Testing.png)\n\n测试这部分会在这系列教程中会不断的重复探讨。\n\n\n现在让我们来写第一个测试，首先在应用程序 **boards** 中找到 **tests.py** \n\n> **boards/tests.py** \n\n```\nfrom django.core.urlresolvers import reverse\nfrom django.test import TestCase\n\nclass HomeTests(TestCase):\n    def test_home_view_status_code(self):\n        url = reverse('home')\n        response = self.client.get(url)\n        self.assertEquals(response.status_code, 200)\n```\n\n这是一个非常简单的测试用例，但非常的有用。我们在测试的是响应状态码，如果是 200 意味着成功。\n\n\n我们可以在控制台中检查响应码：\n\n![回应200](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/test-homepage-view-status-code-200.png)\n\n\n如果出现未捕获的异常，语法错误或其他任何情况，Django 会返回状态代码**500**，这意味着**服务器错误**。现在，想象我们的应用程序有 100 个界面（view）。如果我们为所有视图（view）编写了这个简单的测试，只需一个命令，我们就可以测试所有视图是否返回成功代码，这样用户就不会在任何地方看到任何错误消息。如果没有自动化测试，我们需要逐一检查每个页面。\n\n要执行 Django 的测试套件：\n\n```\npython manage.py test\n```\n\n```\nCreating test database for alias 'default'...\nSystem check identified no issues (0 silenced).\n.\n----------------------------------------------------------------------\nRan 1 test in 0.041s\n\nOK\nDestroying test database for alias 'default'...\n```\n\n现在我们可以测试 Django 是否为请求的 URL 返回了正确的视图函数。这也是一个有用的测试，因为随着开发的进展，您会发现 **urls.py** 模块可能变得非常庞大而复杂。URL 配置全部是关于解析正则表达式的。有些情况下我们有一个非常宽容的URL，所以 Django 最终可能返回错误的视图函数。\n\n以下是我们如何做到的：\n\n> **boards/tests.py**\n\n```\nfrom django.core.urlresolvers import reverse\nfrom django.urls import resolve\nfrom django.test import TestCase\nfrom .views import home\n\nclass HomeTests(TestCase):\n    def test_home_view_status_code(self):\n        url = reverse('home')\n        response = self.client.get(url)\n        self.assertEquals(response.status_code, 200)\n\n    def test_home_url_resolves_home_view(self):\n        view = resolve('/')\n        self.assertEquals(view.func, home)\n```\n\n\n\n在第二个测试中，我们正在使用 `resolve` 功能。Django 使用它来将请求的 URL与 **urls.py** 模块中列出的 URL 列表进行匹配。该测试将确保使用 `/`根 URL ，是否返回主视图（home view）。\n\n再次测试：\n\n```\npython manage.py test\n```\n\n```\nCreating test database for alias 'default'...\nSystem check identified no issues (0 silenced).\n..\n----------------------------------------------------------------------\nRan 2 tests in 0.027s\n\nOK\nDestroying test database for alias 'default'...\n```\n\n\n要查看有关测试执行的更多详细信息，请将 **verbosity** 设置为更高级别：\n\n```\npython manage.py test --verbosity=2\n```\n\n```\nCreating test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...\nOperations to perform:\n  Synchronize unmigrated apps: messages, staticfiles\n  Apply all migrations: admin, auth, boards, contenttypes, sessions\nSynchronizing apps without migrations:\n  Creating tables...\n    Running deferred SQL...\nRunning migrations:\n  Applying contenttypes.0001_initial... OK\n  Applying auth.0001_initial... OK\n  Applying admin.0001_initial... OK\n  Applying admin.0002_logentry_remove_auto_add... OK\n  Applying contenttypes.0002_remove_content_type_name... OK\n  Applying auth.0002_alter_permission_name_max_length... OK\n  Applying auth.0003_alter_user_email_max_length... OK\n  Applying auth.0004_alter_user_username_opts... OK\n  Applying auth.0005_alter_user_last_login_null... OK\n  Applying auth.0006_require_contenttypes_0002... OK\n  Applying auth.0007_alter_validators_add_error_messages... OK\n  Applying auth.0008_alter_user_username_max_length... OK\n  Applying boards.0001_initial... OK\n  Applying sessions.0001_initial... OK\nSystem check identified no issues (0 silenced).\ntest_home_url_resolves_home_view (boards.tests.HomeTests) ... ok\ntest_home_view_status_code (boards.tests.HomeTests) ... ok\n\n----------------------------------------------------------------------\nRan 2 tests in 0.017s\n\nOK\nDestroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...\n```\n\n详细程度决定了将要打印到控制台的通知和调试信息量; 0 是无输出，1 是正常输出，2 是详细输出。\n\n##### 静态文件设置\n\n静态文件是指 CSS，JavaScript，字体，图像或者是我们用来组成用户界面的任何其他资源。\n\n事实上，Django 不提供这些文件。但在开发过程中，我们又会用到，因此 Django 提供了一些功能来帮助我们管理静态文件。这些功能可在配置文件（settings.py）中 `INSTALLED_APPS` 里的 **django.contrib.staticfiles** 。\n\n有了这么多的前端组件库，我们没有理由继续渲染基本的 HTML 。我们可以轻松地将Bootstrap 4 添加到我们的项目中。Bootstrap 是一个用 HTML，CSS 和JavaScript 开发的开源工具包。\n\n在项目根目录中，除**boards**，**templates** 和 **myproject** 文件夹外，我们还需要创建一个名为 **static** 的文件夹，并在 **static** 文件夹内创建另一个名为 **css** 文件夹：\n\n```\nmyproject/\n |-- myproject/\n |    |-- boards/\n |    |-- myproject/\n |    |-- templates/\n |    |-- static/       <-- here\n |    |    +-- css/     <-- and here\n |    +-- manage.py\n +-- venv/\n```\n\n到 [getbootstrap.com](https://getbootstrap.com/docs/4.0/getting-started/download/#compiled-css-and-js) 下载最新版本：\n\n![Bootstrap下载](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/bootstrap-download.png)\n\n下载 **Compiled CSS and JS** 的版本。\n\n解压从 Bootstrap 网站下载的 **bootstrap-4.0.0-beta-dist.zip** 文件，将文件 **css / bootstrap.min.css** 复制到我们项目的css文件夹中：\n\n```\nmyproject/\n |-- myproject/\n |    |-- boards/\n |    |-- myproject/\n |    |-- templates/\n |    |-- static/\n |    |    +-- css/\n |    |         +-- bootstrap.min.css    <-- here\n |    +-- manage.py\n +-- venv/\n```\n\n还是一样的问题，我们需要将 Django 中的 **settings.py** 里配置一下静态文件的目录。在 `STATIC_URL` 添加以下内容： \n\n```\nSTATIC_URL = '/static/'\n\nSTATICFILES_DIRS = [\n    os.path.join(BASE_DIR, 'static'),\n]\n```\n\n\n这里可以回忆一下，`TEMPLATES` 配置目录的路径，操作是差不多的。\n\n\n现在我们必须在模板中加载静态文件（Bootstrap CSS文件）：\n\n> **templates/home.html**\n\n```\n{% load static %}<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Boards</title>\n    <link rel=\"stylesheet\" href=\"{% static 'css/bootstrap.min.css' %}\">\n  </head>\n  <body>\n    <!-- body suppressed for brevity ... -->\n  </body>\n</html>\n```\n\n\n\n首先，我们在 html 的开头加载静态文件：`{% load static %}`\n\n\n`{% static %}` 是用于告诉资源文件存在的路径，在这是，`{% static 'css/bootstrap.min.css' %}` 就会返回 **/static/css/bootstrap.min.css** ，相当于 **http://127.0.0.1:8000/static/css/bootstrap.min.css**\n\n\n这个 `{% static %}` 标签将会和 **settings.py** 的 `STATIC_URL` 组成最终的 URL。怎么理解这句话呢？\n\n例如，我们在静态文件托管在 **https://static.example.com/** ，然后我们设置了这个属性：`STATIC_URL=https://static.example.com/`，然后 `{% static 'css/bootstrap.min.css' %}` 返回的是 ：**https://static.example.com/css/bootstrap.min.css**。\n\n\n如果还不能理解，放心，你现在只需要了解和记住相关的过程就行了，后面正式开发上线的时候，会继续开展这部分的内容。\n\n\n刷新页面 **127.0.0.1:8000** 我们可以看到它是这个样子的：\n\n![Boards主页Bootstrap](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-bootstrap.png)\n\n现在我们可以编辑模板，以利用Bootstrap CSS：\n\n现在我们可以利用 Bootstrap CSS 来编辑我们的模板页面了：\n\n\n\n```\n{% load static %}<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Boards</title>\n    <link rel=\"stylesheet\" href=\"{% static 'css/bootstrap.min.css' %}\">\n  </head>\n  <body>\n    <div class=\"container\">\n      <ol class=\"breadcrumb my-4\">\n        <li class=\"breadcrumb-item active\">Boards</li>\n      </ol>\n      <table class=\"table\">\n        <thead class=\"thead-inverse\">\n          <tr>\n            <th>Board</th>\n            <th>Posts</th>\n            <th>Topics</th>\n            <th>Last Post</th>\n          </tr>\n        </thead>\n        <tbody>\n          {% for board in boards %}\n            <tr>\n              <td>\n                {{ board.name }}\n                <small class=\"text-muted d-block\">{{ board.description }}</small>\n              </td>\n              <td class=\"align-middle\">0</td>\n              <td class=\"align-middle\">0</td>\n              <td></td>\n            </tr>\n          {% endfor %}\n        </tbody>\n      </table>\n    </div>\n  </body>\n</html>\n```\n\n\n\n修改后变成这样子：\n\n![Boards主页Bootstrap](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-bootstrap-2.png)\n\n\n到目前为止，我们使用交互式控制台（`python manage.py shell`）添加新的版块（board）。但是这样很不方便，因此我们需要一个更好的方式来做这个。在下一节中，我们将为网站管理员实施一个管理界面来管理它。\n\n* * *\n\n#### Django Admin简介\n\n当我们开始一个新项目时，Django 在 `INSTALLED_APPS` 中已经配置了 **Django Admin** 。\n\n![Django Admin漫画](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/Pixton_Comic_Django_Admin.png)\n\nDjango Admin 的一个很好的用例就是，在博客中，它可以被作者用来编写和发布文章。另一个例子是电子商务网站，工作人员可以创建，编辑，删除产品。\n\n目前，我们将配置 Django Admin 来维护我们的应用程序的版块模块。\n\n我们首先创建一个管理员帐户：\n\n```\npython manage.py createsuperuser\n```\n\n按照说明操作：\n\n```\nUsername (leave blank to use 'vitorfs'): admin\nEmail address: admin@example.com\nPassword:\nPassword (again):\nSuperuser created successfully.\n```\n\n现在在浏览器中打开 URL：**http://127.0.0.1:8000/admin/**\n\n![Django管理员登录](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin-login.png)\n\n输入 **用户名** 和 **密码** ：\n\n![Django Admin](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin.png)\n\n\n在这里，它已经配置了一些功能，我们也可以添加**用户**和**组**来管理权限。\n\n\n那么我们如何在这个管理后台中管理版块（Board）里面的内容呢？\n\n其实很简单，在 **board** 目录下，**admin.py** 中添加以下代码：\n\n\n> **boards/admin.py**\n\n```\nfrom django.contrib import admin\nfrom .models import Board\n\nadmin.site.register(Board)\n```\n\n\n保存以下，然后刷新网页：\n\n![Django管理委员会](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin-boards.png)\n\n点击 **Boards** 链接就能查看现有版块列表：\n\n![Django管理委员会名单](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin-boards-list.png)\n\n我们可以通过点击 **Add Board** 按钮添加一个新的版块：\n\n![Django管理委员会添加](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin-boards-add.png)\n\n点击 **SAVE** 按钮：\n\n![Django管理委员会名单](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/django-admin-boards-list-2.png)\n\n我们可以检查一切是否正常，打开 **http://127.0.0.1:8000** URL：\n\n![董事会主页](https://simpleisbetterthancomplex.com/media/series/beginners-guide/1.11/part-2/boards-homepage-bootstrap-3.png)\n\n* * *\n\n#### 结论\n\n在本教程中，我们探讨了许多新概念。我们为我们的项目定义了一些要求，创建了第一个模型，迁移了数据库，开始玩 Models API。我们创建了第一个视图并编写了一些单元测试。我们还配置了 Django 模板引擎，静态文件，并将 Bootstrap 4 库添加到项目中。最后，我们简要介绍了 Django Admin 界面。\n\n\n该项目的源代码在 GitHub 上,你可以在下面的链接中找到本章节的代码：\n\n[https://github.com/sibtc/django-beginners-guide/tree/v0.2-lw](https://github.com/sibtc/django-beginners-guide/tree/v0.2-lw)"
  },
  {
    "path": "Article/supplement/Python关键字yield.md",
    "content": "> 原文：http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained\n>\n> 注：这是一篇 stackoverflow 上一个火爆帖子的译文\n\n## 问题 ##\n\nPython 关键字 yield 的作用是什么？用来干什么的？\n\n比如，我正在试图理解下面的代码:\n\n```Python\ndef node._get_child_candidates(self, distance, min_dist, max_dist):\n    if self._leftchild and distance - max_dist < self._median:\n        yield self._leftchild\n    if self._rightchild and distance + max_dist >= self._median:\n        yield self._rightchild\n```\n\n\n下面的是调用:\n\n\n```python\nresult, candidates = list(), [self]\nwhile candidates:\n    node = candidates.pop()\n    distance = node._get_dist(obj)\n    if distance <= max_dist and distance >= min_dist:\n        result.extend(node._values)\n    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))\nreturn result\n```\n\n当调用 ```_get_child_candidates``` 的时候发生了什么？返回了一个列表？返回了一个元素？被重复调用了么？ 什么时候这个调用结束呢？\n\n\n## 回答\n\n为了理解什么是 yield ,你必须理解什么是生成器。在理解生成器之前，让我们先走近迭代。\n\n**可迭代对象**\n\n当你建立了一个列表，你可以逐项地读取这个列表，这叫做一个可迭代对象:\n\n```Python\n>>> mylist = [1, 2, 3]\n>>> for i in mylist :\n...    print(i)\n1\n2\n3\n```\n\nmylist 是一个可迭代的对象。当你使用一个列表生成式来建立一个列表的时候，就建立了一个可迭代的对象:\n\n```python\n>>> mylist = [x*x for x in range(3)]\n>>> for i in mylist :\n...    print(i)\n0\n1\n4\n```\n\n所有你可以使用 ```for .. in ..``` 语法的叫做一个迭代器：列表，字符串，文件……你经常使用它们是因为你可以如你所愿的读取其中的元素，但是你把所有的值都存储到了内存中，如果你有大量数据的话这个方式并不是你想要的。\n\n**生成器**\n\n生成器是可以迭代的，但是你 只可以读取它一次 ，因为它并不把所有的值放在内存中，它是实时地生成数据:\n\n```python\n>>> mygenerator = (x*x for x in range(3))\n>>> for i in mygenerator :\n...    print(i)\n0\n1\n4\n```\n\n看起来除了把 [] 换成 () 外没什么不同。但是，你不可以再次使用 ```for i in mygenerator``` , 因为生成器只能被迭代一次：先计算出0，然后继续计算1，然后计算4，一个跟一个的…\n\n**yield 关键字**\n\nyield 是一个类似 return 的关键字，只是这个函数返回的是个生成器。\n\n```python\n>>> def createGenerator() :\n...    mylist = range(3)\n...    for i in mylist :\n...        yield i*i\n...\n>>> mygenerator = createGenerator() # create a generator\n>>> print(mygenerator) # mygenerator is an object!\n<generator object createGenerator at 0xb7555c34>\n>>> for i in mygenerator:\n...     print(i)\n0\n1\n4\n```\n\n这个例子没什么用途，但是它让你知道，这个函数会返回一大批你只需要读一次的值.\n\n为了精通 yield ,你必须要理解：当你调用这个函数的时候，函数内部的代码并不立马执行 ，这个函数只是返回一个生成器对象，这有点蹊跷不是吗。\n\n那么，函数内的代码什么时候执行呢？当你使用for进行迭代的时候.\n\n现在到了关键点了！\n\n第一次迭代中你的函数会执行，从开始到达 yield 关键字，然后返回 yield 后的值作为第一次迭代的返回值. 然后，每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次，再返回那个值，直到没有可以返回的。\n\n如果生成器内部没有定义 yield 关键字，那么这个生成器被认为成空的。这种情况可能因为是循环进行没了，或者是没有满足 if/else 条件。\n\n**回到你的代码**\n\n生成器:\n\n```Python\n# Here you create the method of the node object that will return the generator\ndef node._get_child_candidates(self, distance, min_dist, max_dist):\n\n  # Here is the code that will be called each time you use the generator object :\n\n  # If there is still a child of the node object on its left\n  # AND if distance is ok, return the next child\n  if self._leftchild and distance - max_dist < self._median:\n            yield self._leftchild\n\n  # If there is still a child of the node object on its right\n  # AND if distance is ok, return the next child\n  if self._rightchild and distance + max_dist >= self._median:\n                yield self._rightchild\n\n  # If the function arrives here, the generator will be considered empty\n  # there is no more than two values : the left and the right children\n```\n\n\n调用者:\n\n```Python\n# Create an empty list and a list with the current object reference\nresult, candidates = list(), [self]\n\n# Loop on candidates (they contain only one element at the beginning)\nwhile candidates:\n\n    # Get the last candidate and remove it from the list\n    node = candidates.pop()\n\n    # Get the distance between obj and the candidate\n    distance = node._get_dist(obj)\n\n    # If distance is ok, then you can fill the result\n    if distance <= max_dist and distance >= min_dist:\n        result.extend(node._values)\n\n    # Add the children of the candidate in the candidates list\n    # so the loop will keep running until it will have looked\n    # at all the children of the children of the children, etc. of the candidate\n    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))\n\nreturn result\n```\n\n这个代码包含了几个小部分：\n\n* 我们对一个列表进行迭代，但是迭代中列表还在不断的扩展。它是一个迭代这些嵌套的数据的简洁方式，即使这样有点危险，因为可能导致无限迭代。 `candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))` 穷尽了生成器的所有值，但 while 不断地在产生新的生成器，它们会产生和上一次不一样的值，既然没有作用到同一个节点上.\n* `extend()` 是一个迭代器方法，作用于迭代器，并把参数追加到迭代器的后面。\n\n\n通常我们传给它一个列表参数:\n\n\n```Python\n>>> a = [1, 2]\n>>> b = [3, 4]\n>>> a.extend(b)\n>>> print(a)\n[1, 2, 3, 4]\n```\n\n\n但是在你的代码中的是一个生成器，这是不错的，因为：\n\n* 你不必读两次所有的值\n* 你可以有很多子对象，但不必叫他们都存储在内存里面。\n\n\n并且这很奏效，因为 Python 不关心一个方法的参数是不是个列表。Python 只希望它是个可以迭代的，所以这个参数可以是列表，元组，字符串，生成器... 这叫做 `duck typing`,这也是为何 Python 如此棒的原因之一，但这已经是另外一个问题了...\n\n你可以在这里停下，来看看生成器的一些高级用法:\n\n**控制生成器的穷尽**\n\n```Python\n>>> class Bank(): # let's create a bank, building ATMs\n...    crisis = False\n...    def create_atm(self) :\n...        while not self.crisis :\n...            yield \"$100\"\n>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want\n>>> corner_street_atm = hsbc.create_atm()\n>>> print(corner_street_atm.next())\n$100\n>>> print(corner_street_atm.next())\n$100\n>>> print([corner_street_atm.next() for cash in range(5)])\n['$100', '$100', '$100', '$100', '$100']\n>>> hsbc.crisis = True # crisis is coming, no more money!\n>>> print(corner_street_atm.next())\n<type 'exceptions.StopIteration'>\n>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs\n>>> print(wall_street_atm.next())\n<type 'exceptions.StopIteration'>\n>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty\n>>> print(corner_street_atm.next())\n<type 'exceptions.StopIteration'>\n>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business\n>>> for cash in brand_new_atm :\n...    print cash\n$100\n$100\n$100\n$100\n$100\n$100\n$100\n$100\n$100\n...\n```\n\n\n对于控制一些资源的访问来说这很有用。\n\n**Itertools,你最好的朋友**\n\nitertools 包含了很多特殊的迭代方法。是不是曾想过复制一个迭代器?串联两个迭代器？把嵌套的列表分组？不用创造一个新的列表的 zip/map?\n\n只要 import itertools\n\n需要个例子？让我们看看比赛中4匹马可能到达终点的先后顺序的可能情况:\n\n```python\n>>> horses = [1, 2, 3, 4]\n>>> races = itertools.permutations(horses)\n>>> print(races)\n<itertools.permutations object at 0xb754f1dc>\n>>> print(list(itertools.permutations(horses)))\n[(1, 2, 3, 4),\n (1, 2, 4, 3),\n (1, 3, 2, 4),\n (1, 3, 4, 2),\n (1, 4, 2, 3),\n (1, 4, 3, 2),\n (2, 1, 3, 4),\n (2, 1, 4, 3),\n (2, 3, 1, 4),\n (2, 3, 4, 1),\n (2, 4, 1, 3),\n (2, 4, 3, 1),\n (3, 1, 2, 4),\n (3, 1, 4, 2),\n (3, 2, 1, 4),\n (3, 2, 4, 1),\n (3, 4, 1, 2),\n (3, 4, 2, 1),\n (4, 1, 2, 3),\n (4, 1, 3, 2),\n (4, 2, 1, 3),\n (4, 2, 3, 1),\n (4, 3, 1, 2),\n (4, 3, 2, 1)]\n```\n\n**了解迭代器的内部机理**\n\n迭代是一个实现可迭代对象(实现的是 `__iter__()` 方法)和迭代器(实现的是 `__next__()` 方法)的过程。可迭代对象是你可以从其获取到一个迭代器的任一对象。迭代器是那些允许你迭代可迭代对象的对象。\n"
  },
  {
    "path": "README.md",
    "content": "IT 行业相对于一般传统行业，发展更新速度更快，一旦停止了学习，很快就会被行业所淘汰，但是，我们要清楚：淘汰的永远只是那些初级水平的从业者，过硬技术的从业者永远都是稀缺的。因此对于学习，我们还是要踏踏实实的。\n\n\n自学 Python ，也是一样，不要一开始因为头脑发热就不停地收藏各种资料网站，购买各种书籍，下载了大量的教学视频，过了几天，学习的热情开始褪去，再过几个星期，终于完成了学习课程 —— 《从入门到放弃》。所以，学习 Python 需要一步一个脚印，踏踏实实地学。\n\n\n\n# FQ\n\n在讲 Python 如何入门之前，个人建议最好每个人都有自己的 FQ 工具，多使用 Google 搜索，多去看一下墙外的世界，多看 Github 上的开源项目。\n\n至于如何 FQ ，这里提供一下我用过的工具：[FQ工具集](/Res/FQ.md)\n\n\n\n# Python 学习资源集\n\n相信很多人学习某门编程语言的时候，都会找各种学习资料。说句实话，资料太多，反而没用，根据自己的学习习惯，个人能力选择一门资源坚持学就好了。\n\n因为每个人的需求不同，这里整理了一批 Python 比较好的学习资料：\n\n* [Python 博客网站资源](/Res/Python博客网站资源.md)\n\n还有一些有趣的网站：\n\n* [一个可以看执行过程的网站](http://www.pythontutor.com/visualize.html#mode=edit)\n\n\n\n# Python 入门\n\n对于入门，主要是掌握基本的语法和熟悉编程规范，因此大部分的教程基本一致的，所以还是建议选好适合自己的一个教程，坚持学下去。\n\n在 Python 入门中，本人编写了一系列的 《草根学 Python 》 文章， 是基于 Python 3.6 写的 Python 入门系列教程，为了更好的阅读，把它整理在 [GitBook](https://www.readwithu.com/) 上，希望对各位入门 Python 有所帮助。\n\n>注：2018 年 02 月 27 日，基础知识入门部分已经完成了的。因近期读者反映有些图片没法打开了（之前图片放在七牛云，用的是临时链接，最近七牛云把这个给关闭了，导致图片没法打开），且自己对之前的内容有些不满意，决定在 2019 年 7 月 7 日开始进行了再次修改。但这里做个说明，因为之前写的时候用的是 windows 电脑，现在用 mac ，所以有些例子是 windows 的截图，有些是 mac 的截图，不要觉得奇怪。\n\n**主要目录如下：**\n\n* [为什么学Python?](/Article/PythonBasis/python0/WhyStudyPython.md)\n* [Python代码规范](/Article/codeSpecification/codeSpecification_Preface.md)\n  - [简明概述](/Article/codeSpecification/codeSpecification_first.md)\n  - [注释](/Article/codeSpecification/codeSpecification_second.md)\n  - [命名规范](/Article/codeSpecification/codeSpecification_third.md)\n* [第一个Python程序](/Article/PythonBasis/python1/Preface.md)\n  - [Python 简介](/Article/PythonBasis/python1/Introduction.md)\n  - [Python 的安装](/Article/PythonBasis/python1/Installation.md)\n  - [第一个 Python 程序](/Article/PythonBasis/python1/The_first_procedure.md)\n  - [集成开发环境（IDE）: PyCharm](/Article/PythonBasis/python1/IDE.md)\n* [基本数据类型和变量](/Article/PythonBasis/python2/Preface.md)\n  - [Python 语法的简要说明](/Article/PythonBasis/python2/Grammar.md)\n  - [print() 函数](/Article/PythonBasis/python2/print.md)\n  - [Python 的基本数据类型](/Article/PythonBasis/python2/Type_of_data.md)\n  - [字符串的编码问题](/Article/PythonBasis/python2/StringCoding.md)\n  - [基本数据类型转换](/Article/PythonBasis/python2/Type_conversion.md)\n  - [Python 中的变量](/Article/PythonBasis/python2/Variable.md)\n* [List 和 Tuple](/Article/PythonBasis/python3/Preface.md)\n  - [List（列表）](/Article/PythonBasis/python3/List.md)\n  - [tuple（元组）](/Article/PythonBasis/python3/tuple.md)\n* [ Dict 和 Set](/Article/PythonBasis/python4/Preface.md)\n  - [字典(Dictionary)](/Article/PythonBasis/python4/Dict.md)\n  - [set](/Article/PythonBasis/python4/Set.md)\n* [条件语句和循环语句](/Article/PythonBasis/python5/Preface.md)\n  - [条件语句](/Article/PythonBasis/python5/If.md)\n  - [循环语句](/Article/PythonBasis/python5/Cycle.md)\n  - [条件语句和循环语句综合实例](/Article/PythonBasis/python5/Example.md)\n* [函数](/Article/PythonBasis/python6/Preface.md)\n  - [Python 自定义函数的基本步骤](/Article/PythonBasis/python6/1.md)\n  - [函数返回值](/Article/PythonBasis/python6/2.md)\n  - [函数的参数](/Article/PythonBasis/python6/3.md)\n  - [函数传值问题](/Article/PythonBasis/python6/4.md)\n  - [匿名函数](/Article/PythonBasis/python6/5.md)\n* [迭代器和生成器](/Article/PythonBasis/python7/Preface.md)\n  - [迭代](/Article/PythonBasis/python7/1.md)\n  - [Python 迭代器](/Article/PythonBasis/python7/2.md)\n  - [list 生成式（列表生成式）](/Article/PythonBasis/python7/3.md)\n  - [生成器](/Article/PythonBasis/python7/4.md)\n  - [迭代器和生成器综合例子](/Article/PythonBasis/python7/5.md)\n* [面向对象](/Article/PythonBasis/python8/Preface.md)\n  - [面向对象的概念](/Article/PythonBasis/python8/1.md)\n  - [类的定义和调用](/Article/PythonBasis/python8/2.md)\n  - [类方法](/Article/PythonBasis/python8/3.md)\n  - [修改和增加类属性](/Article/PythonBasis/python8/4.md)\n  - [类和对象](/Article/PythonBasis/python8/5.md)\n  - [初始化函数](/Article/PythonBasis/python8/6.md)\n  - [类的继承](/Article/PythonBasis/python8/7.md)\n  - [类的多态](/Article/PythonBasis/python8/8.md)\n  - [类的访问控制](/Article/PythonBasis/python8/9.md)\n* [模块与包](/Article/PythonBasis/python9/Preface.md)\n  - [Python 模块简介](/Article/PythonBasis/python9/1.md)\n  - [模块的使用](/Article/PythonBasis/python9/2.md)\n  - [主模块和非主模块](/Article/PythonBasis/python9/3.md)\n  - [包](/Article/PythonBasis/python9/4.md)\n  - [作用域](/Article/PythonBasis/python9/5.md)\n* [Python 的 Magic Method](/Article/PythonBasis/python10/Preface.md)\n  - [Python 的 Magic Method](/Article/PythonBasis/python10/1.md)\n  - [构造(`__new__`)和初始化(`__init__`)](/Article/PythonBasis/python10/2.md)\n  - [属性的访问控制](/Article/PythonBasis/python10/3.md)\n  - [对象的描述器](/Article/PythonBasis/python10/4.md)\n  - [自定义容器（Container）](/Article/PythonBasis/python10/5.md)\n  - [运算符相关的魔术方法](/Article/PythonBasis/python10/6.md)\n* [枚举类](/Article/python11/PythonBasis/Preface.md)\n  - [枚举类的使用](/Article/PythonBasis/python11/1.md)\n  - [Enum 的源码](/Article/PythonBasis/python11/2.md)\n  - [自定义类型的枚举](/Article/PythonBasis/python11/3.md)\n  - [枚举的比较](/Article/PythonBasis/python11/4.md)\n* [元类](/Article/PythonBasis/python12/Preface.md)\n  - [Python 中类也是对象](/Article/PythonBasis/python12/1.md)\n  - [使用 `type()` 动态创建类](/Article/PythonBasis/python12/2.md)\n  - [什么是元类](/Article/PythonBasis/python12/3.md)\n  - [自定义元类](/Article/PythonBasis/python12/4.md)\n  - [使用元类](/Article/PythonBasis/python12/5.md)\n* [线程与进程](/Article/PythonBasis/python13/Preface.md)\n  - [线程与进程](/Article/PythonBasis/python13/1.md)\n  - [多线程编程](/Article/PythonBasis/python13/2.md)\n  - [进程](/Article/PythonBasis/python13/3.md)\n* [一步一步了解正则表达式](/Article/PythonBasis/python14/Preface.md)\n    - [初识 Python 正则表达式](/Article/PythonBasis/python14/1.md)\n    - [字符集](/Article/PythonBasis/python14/2.md)\n    - [数量词](/Article/PythonBasis/python14/3.md)\n    - [边界匹配符和组](/Article/PythonBasis/python14/4.md)\n    - [re.sub](/Article/PythonBasis/python14/5.md)\n    - [re.match 和 re.search](/Article/PythonBasis/python14/6.md)\n* [闭包](/Article/PythonBasis/python15/1.md)\n* [装饰器](/Article/PythonBasis/python16/1.md)\n\n\n\n\n# 知识点补漏\n* [Python 关键字 yield](/Article/supplement/Python关键字yield.md)\n\n\n\n# Python 进阶\n\n* [使用Python虚拟环境](/Article/advanced/使用Python虚拟环境.md)\n* [Mac中使用virtualenv和virtualenvwrapper](/Article/advanced/Mac中使用virtualenv和virtualenvwrapper.md)\n\n\n\n\n# HTML 和 CSS 入门\n\n\n# JavaScript 入门\n\n\n\n# Django\n\nPython 下有许多款不同的 Web 框架。Django 是重量级选手中最有代表性的一位。许多成功的网站和 APP 都基于 Django。\n\n如果对自己的基础有点信息的童鞋，可以尝试通过国外的 [Django 博客从搭建到部署系列教程](https://simpleisbetterthancomplex.com/series/2017/09/04/a-complete-beginners-guide-to-django-part-1.html) 进行入门，这个教程讲的非常的详细，而且还有很多有趣的配图。不过可能因为墙的原因，很多人会访问不到，就算访问到了，也因为是英语的，不会进行耐心的阅读学习。因此我打算翻译这个教程。\n\n* [一个完整的初学者指南Django-part1](/Article/django/一个完整的初学者指南Django-part1.md)\n* [一个完整的初学者指南Django-part2](/Article/django/一个完整的初学者指南Django-part2.md)\n\n\n持续更新....\n\n\n可以关注我的公众号，实时了解更新情况。\n\n<img src=\"http://twowaterimage.oss-cn-beijing.aliyuncs.com/2020-09-15-121312.jpg\" width=\"50%\" height=\"50%\">\n\n\n\n\n"
  },
  {
    "path": "Res/FQ.md",
    "content": "\n# FQ\n\n\n* **蓝灯**\n\n    简单粗暴，下载安装即可用，每月免费 5g ，也可以 购买专业版，一年两百多。其实如果不用来看视频，够用的了。\n\n    传送门： [蓝灯官方论坛](https://github.com/getlantern/forum) ，[蓝灯镜像下载地址](http://s3.amazonaws.com/urtuz53txrmk9/index.html)\n\n\n* **XX-Net**\n\n    基于 Google 免费的云服务搭建，这里有个矛盾就 是：搭建前你得 FQ。也就是说，我要下载 FQ 软件，可是  下载这个 FQ 软件就要 FQ 。\n\n    所以这里你可以先用蓝灯 FQ 后，再搭建这个\n\n    传送门：[官方中文文档](https://github.com/XX-net/XX-Net/wiki/%E4%B8%AD%E6%96%87%E6%96%87%E6%A1%A3)，[基于 Chrome 浏览器的搭建步骤](https://github.com/XX-net/XX-Net/wiki/%E4%BD%BF%E7%94%A8Chrome%E6%B5%8F%E8%A7%88%E5%99%A8)\n\n\n* **自己搭建 ssr**\n\n    当然，最好的还是自己能搭建 ss/ssr 服务器，这里有个适合初学者的教程。\n\n    传送门：[教程地址](../Res/自己搭建ss:ssr服务器.md)\n\n\n\n"
  },
  {
    "path": "Res/Python博客网站资源.md",
    "content": "# Python 博客网站资源\n\n\n* **本人编写的 Python 系列文章**\n\n    本草根编写的 Python 文章，里面有各种例子代码\n\n    传送门： [Gitbook](https://www.readwithu.com/)，[github](https://github.com/TwoWater/Python)\n\n* **Python 在线手册**\n\n    Python 在线手册站，收集整理了大量 Python 流行技术文档和教程。更多时候我们用来查询 Api 。\n\n    传送门：[官方地址](http://docs.pythontab.com)\n\n\n* **Python轻松入门**\n\n    网易云课程，Python 轻松入门\n\n    传送门： [官方地址](https://study.163.com/course/introduction.htm?courseId=1003655001&utm_campaign=share&utm_content=courseIntro&utm_medium=iphoneShare&utm_source=weixing)\n\n\n* **廖雪峰 Python 教程**\n\n    最最最经典的 Python 入门文章。\n\n    传送门：[官方地址](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000)\n\n\n* **Python 基础教程**\n\n    详细的记录 Python 各个知识点的用法讲解，很适合小白入门。\n\n    传送门：[官方地址](http://www.runoob.com/python/python-tutorial.html)\n\n\n* **莫烦Python**\n\n\n    传送门：[官方地址](https://morvanzhou.github.io/tutorials/python-basic/)\n\n* **The Hitchhiker’s Guide to Python**\n\n     传送门：[官方地址](http://docs.python-guide.org/en/latest/)\n\n\n"
  },
  {
    "path": "Res/自己搭建ss:ssr服务器.md",
    "content": "# 客户端下载\n\n第一次电脑系统使用SSR/SS客户端时，需要安装NET Framework 4.0，不然无法正常使用，[微软官网下载](https://www.microsoft.com/zh-cn/download/details.aspx?id=17718)。\n\nNET Framework 4.0是SSR/SS的运行库，没有这个SSR/SS客户端无法正常运行。有的电脑系统可能会自带NET Framework 4.0。\n\n* Windows SSR客户端 [下载地址](https://github.com/shadowsocksr-backup/shadowsocksr-csharp/releases) [备用下载地址](https://nofile.io/f/6Jm7WJCyOVv/ShadowsocksR-4.7.0-win.7z)\n\n* MAC SSR客户端 [下载地址](https://github.com/shadowsocksr-backup/ShadowsocksX-NG/releases) [备用下载地址](https://nofile.io/f/jgMWFwCBonU#ab0d3c3b6ac54482)\n\n* Linux客户端一键安装配置 [使用脚本](https://github.com/the0demiurge/CharlesScripts/blob/master/charles/bin/ssr) (使用方法见注释) 或者采用图形界面的[linux ssr客户端](https://github.com/erguotou520/electron-ssr/releases)\n\n* 安卓 SSR客户端 [下载地址](https://github.com/shadowsocksr-backup/shadowsocksr-android/releases) [备用下载地址](https://nofile.io/f/rvTJoj0h5GC/shadowsocksr-release.apk)\n\n* 苹果手机SSR客户端：Potatso Lite、Potatso、shadowrocket都可以作为SSR客户端，但这些软件目前已经在国内的app商店下架，可以用美区的appid账号来下载。但是，如果你配置的SSR账号兼容SS客户端，或者协议选择origin且混淆选择plain，那么你可以选择苹果SS客户端软件（即协议和混淆可以不填），APP商店里面有很多，比如：openwingy、superwingy、bestwingy、wingy+、greatwingy等。\n\n**有了客户端后我们需要自己搭建服务器创建ss/ssr账号才能翻墙。提供ss/ssr免费账号 有需求的人可以使用。**\n\n# 搭建教程\n\n### 为什么不怕被封ip？因为vultr可以随时删除和创建服务器，新服务器就是新的ip，所以不怕被封ip。\n\n\n**教程很简单，整个教程分三步：**\n\n第一步：购买VPS服务器\n\n第二步：一键部署VPS服务器\n\n第三步：一键加速VPS服务器 （谷歌BBR加速，推荐）\n\n* * *\n\n**第一步：购买VPS服务器**\n\nVPS服务器需要选择国外的，首选国际知名的vultr，速度不错、稳定且性价比高，按小时计费，能够随时开通和删除服务器，新服务器即是新ip。\n\nvultr注册地址： [http://www.vultr.com/?ref=7048874](http://www.vultr.com/?ref=7048874) （全球15个服务器位置可选，KVM框架，最低2.5美元/月）\n\n[![img](https://camo.githubusercontent.com/e5b4fc7834baffafe9883ac40cc7f296e62f9741/68747470733a2f2f7777772e76756c74722e636f6d2f6d656469612f62616e6e65725f322e706e67)](https://www.vultr.com/?ref=7048874)\n\n虽然是英文界面，但是现在的浏览器都有网页翻译功能，鼠标点击右键，选择网页翻译即可翻译成中文。\n\n注册并邮件激活账号，充值后即可购买服务器。充值方式是paypal（首选）或支付宝，使用paypal有银行卡（包括信用卡）即可。paypal注册地址：[https://www.paypal.com](https://www.paypal.com/) （paypal是国际知名的第三方支付服务商，注册一下账号，绑定银行卡即可购买国外商品）\n\n2.5美元/月的服务器配置信息：单核 512M内存 20G SSD硬盘 带宽峰值100M 500G流量/月\n\n5美元/月的服务器配置信息： 单核 1G内存 25G SSD硬盘 带宽峰值100M 1000G流量/月\n\n10美元/月的服务器配置信息： 单核 2G内存 40G SSD硬盘 带宽峰值100M 2000G流量/月\n\n20美元/月的服务器配置信息： 2cpu 4G内存 60G SSD硬盘 带宽峰值100M 3000G流量/月\n\n40美元/月的服务器配置信息： 4cpu 8G内存 100G SSD硬盘 带宽峰值100M 4000G流量/月\n\n**vultr实际上是折算成小时来计费的，比如服务器是5美元1个月，那么每小时收费为5/30/24=0.0069美元 会自动从账号中扣费，只要保证账号有钱即可。如果你部署的服务器实测后速度不理想，你可以把它删掉（destroy），重新换个地区的服务器来部署，方便且实用。因为新的服务器就是新的ip，所以当ip被墙时这个方法很有用。当ip被墙时，为了保证新开的服务器ip和原先的ip不一样，先开新服务器，开好后再删除旧服务器即可。**\n\n计费从你开通服务器开始算的，不管你有没有使用，即使服务器处于关机状态仍然会计费，如果你没有开通服务器就不算。比如你今天早上开通了服务器，但你有事情，晚上才部署，那么这段时间是会计费的。同理，如果你早上删掉服务器，第二天才开通新的服务器，那么这段时间是不会计费的。在账号的Billing选项里可以看到账户余额。\n\n温馨提醒：同样的服务器位置，不同的宽带类型和地区所搭建的账号的翻墙速度会不同，这与中国电信、中国联通、中国移动国际出口带宽和线路不同有关，所以以实测为准。可以先选定一个服务器位置来按照教程进行搭建，熟悉搭建方法，当账号搭建完成并进行了bbr加速后，测试下速度自己是否满意，如果满意那就用这个服务器位置的服务器。如果速度不太满意，就一次性开几台不同的服务器位置的服务器，然后按照同样的方法来进行搭建并测试，选择最优的，之后把其它的服务器删掉，按小时计费测试成本可以忽略。\n\n如图：\n\n[![img](https://raw.githubusercontent.com/Alvin9999/pac2/master/pp100.png)](https://raw.githubusercontent.com/Alvin9999/pac2/master/pp100.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/pac2/master/pp101.png)](https://raw.githubusercontent.com/Alvin9999/pac2/master/pp101.png)\n\n**vps服务器系统推荐选择CentOS 6.X64位的系统（系统版本不要选centos7！centos7默认的防火墙会阻止ssr的正常连接！）。完成购买后，找到系统的密码记下来，部署服务器时需要用到。**\n\n如图：\n[![img](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B01.png)](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B01.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B02.png)](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B02.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B04.png)](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B04.png)\n\n**不要选centos7系统！点击图中的CentOS几个字，会弹出centos6，然后选中centos6！entos7默认的防火墙可能会干扰ssr的正常连接！**\n\n> 接下来这一步是开启vps的ipv6 ip，选填项。如果你的电脑系统可以用ipv6，那么可以勾选此项。大多数用户没有这个需求，但有一些用户可能会用到，所以补充了这部分内容。\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/ssripv6-01.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/ssripv6-01.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B05.png)](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B05.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B06.png)](https://raw.githubusercontent.com/Alvin9999/crp_up/master/pac%E6%95%99%E7%A8%8B06.png)\n\n> 如果你开启了vps的ipv6，那么在后台的settings选项可以找到服务器的ipv6 ip。在部署SSR账号时，你用ipv6 ip就行。整个部署及使用过程中，记得把电脑系统开启ipv6喔。\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/ssripv6-02.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/ssripv6-02.png)\n\n* * *\n\n**第二步：部署VPS服务器**\n\n购买服务器后，需要部署一下。因为你买的是虚拟东西，而且又远在国外，我们需要一个叫Xshell的软件来远程部署。Xshell windows版下载地址：\n\n[国外云盘1下载](http://45.32.141.248:8000/f/d91974d046/)\n\n[国外云盘2下载](https://nofile.io/f/eb5dUzYMQK4/Xshell_setup_wm.exe) 提取密码：666\n\n[国外云盘3下载](https://www.adrive.com/public/NdK3Ez/Xshell_setup_wm.exe) 密码：123\n\n如果你是苹果电脑操作系统，更简单，无需下载xshell，系统可以直接连接VPS。打开**终端**（Terminal），输入ssh root@ip 其中“ip”替换成你VPS的ip, 按回车键，然后复制粘贴密码，按回车键即可登录。粘贴密码时有可能不显示密码，但不影响， [参考设置方法](http://www.cnblogs.com/ghj1976/archive/2013/04/19/3030159.html) 如果不能用MAC自带的终端连接的话，直接网上搜“MAC连接SSH的软件”，有很多，然后通过软件来连接vps服务器就行，具体操作方式参考windows xshell。\n\n* * *\n\n部署教程：\n\n下载xshell软件并安装后，打开软件\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell11.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell11.png)\n\n选择文件，新建\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell12.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell12.png)\n\n随便取个名字，然后把你的服务器ip填上\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell13.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell13.png)\n\n连接国外ip即服务器时，软件会先后提醒你输入用户名和密码，用户名linux系统默认都是root，密码是购买服务器后的cent系统的密码。\n\n**如果开好了服务器，发现xshell死活连不上，多半是开的服务器ip被墙了，遇到这种情况，把服务器删掉，重新开个新的服务器即可，可以是同地区的也可以选择其它地区。**\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell14.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/xshell14.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/xshell2.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/xshell2.png)\n\n连接成功后，会出现如上图所示，之后就可以复制粘贴代码部署了。\n\nCentOS6/Debian6/Ubuntu14 ShadowsocksR一键部署管理脚本：\n\n* * *\n\nyum -y install wget\n\nwget -N –no-check-certificate [https://softs.fun/Bash/ssr.sh](https://softs.fun/Bash/ssr.sh) && chmod +x ssr.sh && bash ssr.sh\n\n备用脚本：\n\nyum -y install wget\n\nwget -N –no-check-certificate [https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssr.sh](https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssr.sh) && chmod +x ssr.sh && bash ssr.sh\n\n———————————————————代码分割线————————————————\n\n复制上面的代码到VPS服务器里，按回车键，脚本会自动安装，以后只需要运行这个快捷命令就可以出现下图的界面进行设置，快捷管理命令为：bash ssr.sh\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/8.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/8.png)\n\n如上图出现管理界面后，**输入数字1来安装SSR服务端**。如果输入1后不能进入下一步，那么请退出xshell，重新连接vps服务器，然后输入快捷管理命令bash ssr.sh 再尝试。\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/31.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/31.png)\n\n根据上图提示，依次输入自己想设置的**端口和密码** (**密码建议用复杂点的字母组合，端口号为1-65535之间的数字**)，回车键用于确认\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/32.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/32.png)\n\n如上图，选择想设置的**加密方式**，比如10，按回车键确认\n\n接下来是选择**协议插件**，如下图：\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/11.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/11.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/41.PNG)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/41.PNG)\n\n选择并确认后，会出现上图的界面，提示你是否选择兼容原版，这里的原版指的是SS客户端（SS客户端没有协议和混淆的选项），可以根据需求进行选择，演示选择y\n\n之后进行混淆插件的设置。\n**注意：有的地区需要把混淆设置成plain才好用。因为混淆不总是有效果，要看各地区的策略，有时候不混淆（plain）让其看起来像随机数据更好。（注意：tls 1.2_ticket_auth容易受到干扰！请选择除tls开头以外的其它混淆！！！）**\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/33.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/33.png)\n\n进行混淆插件的设置后，会依次提示你对设备数、单线程限速和端口总限速进行设置，默认值是不进行限制，个人使用的话，选择默认即可，即直接敲回车键。\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/14.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/14.png)\n\n之后代码就正式自动部署了，到下图所示的位置，提示你下载文件，输入：y\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/15.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/15.png)\n\n耐心等待一会，出现下面的界面即部署完成：\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/16.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/16.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/34.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/34.png)\n\n根据上图就可以看到自己设置的SSR账号信息，包括IP、端口、密码、加密方式、协议插件、混淆插件，这些信息需要填入你的SSR客户端。如果之后想修改账号信息，直接输入快捷管理命令：bash ssr.sh 进入管理界面，选择相应的数字来进行一键修改。例如：\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/22.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/22.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/23.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/23.png)\n\n**脚本演示结束。**\n\n此脚本是开机自动启动，部署一次即可。最后可以重启服务器确保部署生效（一般情况不重启也可以）。重启需要在命令栏里输入reboot ，输入命令后稍微等待一会服务器就会自动重启，一般重启过程需要2～5分钟，重启过程中Xshell会自动断开连接，等VPS重启好后才可以用Xshell软件进行连接。如果部署过程中卡在某个位置超过10分钟，可以用xshell软件断开，然后重新连接你的ip，再复制代码进行部署。\n\n* * *\n\n**第三步：一键加速VPS服务器**\n\n此加速教程为谷歌BBR加速,Vultr的服务器框架可以装BBR加速，加速后对速度的提升很明显，所以推荐部署加速脚本。该加速方法是开机自动启动，部署一次就可以了。\n\n按照第二步的步骤，连接服务器ip，登录成功后，在命令栏里粘贴以下代码：\n\n【谷歌BBR加速教程】\n\nyum -y install wget\n\nwget –no-check-certificate [https://github.com/teddysun/across/raw/master/bbr.sh](https://github.com/teddysun/across/raw/master/bbr.sh)\n\nchmod +x bbr.sh\n\n./bbr.sh\n\n把上面整个代码复制后粘贴进去，不动的时候按回车，然后耐心等待，最后重启vps服务器即可。\n\n演示开始，如图：\n\n复制并粘贴代码后，按回车键确认\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/18.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/18.png)\n\n如下图提示，按任意键继续部署\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/19.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/19.png)\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/20.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/20.png)\n\n部署到上图这个位置的时候，等待3～6分钟\n\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/21.png)](https://raw.githubusercontent.com/Alvin9999/PAC/master/ss/21.png)\n\n最后输入y重启服务器，如果输入y提示command not found ，接着输入reboot来重启服务器，确保加速生效，bbr加速脚本是开机自动启动，装一次就可以了。\n\n* * *\n\n购买vps服务器后，ip有了，通过部署，端口、密码、加密方式、协议和混淆也有了，最后将这些信息填入SSR客户端就可以翻墙啦。\n\n**有了账号后，打开SSR客户端，填上信息，这里以windows版的SSR客户端为例子**：\n[![img](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/42.PNG)](https://raw.githubusercontent.com/Alvin9999/PAC/master/demo/42.PNG)\n\n在对应的位置，填上服务器ip、服务器端口、密码、加密方式、协议和混淆，最后将浏览器的代理设置为（http）127.0.0.1和1080即可。账号的端口号就是你自己设置的，而要上网的浏览器的端口号是1080，固定的，谷歌浏览器可以通过 SwitchyOmega 插件来设置。\n\n启动SSR客户端后，右键SSR客户端图标，选择第一个“系统代理模式”，里面有3个子选项，选择”全局模式“，之后就可以用浏览器设置好了的代理模式（http）127.0.0.1和1080翻墙，此模式下所有的网站都会走SSR代理。（适合新手）\n\n[![ssr9000](https://user-images.githubusercontent.com/12132898/32225069-cfe6195a-be7e-11e7-99e0-e2fa98f93b1f.png)](https://user-images.githubusercontent.com/12132898/32225069-cfe6195a-be7e-11e7-99e0-e2fa98f93b1f.png)\n\n* * *\n\n**常见问题参考解决方法**：\n\n1、用了一段时间发现ssr账号用不了了\n\n首先ping一下自己的ip，看看能不能ping的通，ping不通那么就是ip被墙了，ip被墙时，xshell也会连接不上服务器，遇到这种情况重新部署一个新的服务器，新的服务器就是新的ip。关于怎么ping ip的方法，可以自行网上搜索，或者用xshell软件连接服务器来判断，连不上即是被墙了。vultr开通和删除服务器非常方便，新服务器即新ip，大多数vps服务商都没有这样的服务，一般的vps服务商可能会提供免费更换1次ip的服务。\n\n2、刚搭建好的ssr账号，ip能ping通，但是还是用不了\n\n首选排除杀毒软件的干扰，尤其是国产杀毒软件，比如360安全卫生、360杀毒软件、腾讯管家、金山卫生等。这些东西很容易干扰翻墙上网，如果你的电脑安装了这样的东西，建议至少翻墙时别用，最好卸载。其次，检查下SSR信息是否填写正确。浏览器的代理方式是否是ssr代理，即（HTTP）127.0.0.1 和1080。如果以上条件都排除，还是用不了，那么可以更换端口、加密方式、协议、混淆，或者更换服务器位置。另外，如果你的vps服务器配置的是SSR账号，即有协议和混淆且没有兼容原版(SS版），那么你必须使用SSSR客户端来使用账号，因为SS客户端没有填写协议和混淆的选项。\n\n3、有的地区需要把混淆参数设置成plain才好用。因为混淆不总是有效果，要看各地区的策略，有时候不混淆（plain）让其看起来像随机数据更好。\n\n4、电脑能用但手机用不了\n\n如果你的手机用的是SS客户端，SS客户端没有填协议和混淆的地方，如果你部署的协议和混淆的时候没有选择兼容原版（SS版），因此手机是用不了的。这个时候你把协议弄成兼容原版、混淆也设置成兼容原版即可。或者直接将协议设置成origin，混淆设置成plain。\n\n5、vps的服务器操作系统不要用的太高，太高可能会因为系统的防火墙问题导致搭建的SSR账号连不上，如果你用的centos系统，建议用centos6，不要用centos7。如果你前面不小心装了centos7系统，那么只能重装系统或者重新部署新的vps服务器。\n\n6、vultr服务商提供的vps服务器是单向流量计算，有的vps服务商是双向流量计算，单向流量计算对于用户来说更实惠。因为我们是在vps服务器上部署SSR服务端后，再用SSR客户端翻墙，所以SSR服务端就相当于中转，比如我们看一个视频，必然会产生流量，假如消耗流量80M，那么VPS服务器会产生上传80M和下载80M流量，vultr服务商只计算单向的80M流量。如果是双向计算流量，那么会计算为160M流量。\n\n7、如果你想把搭建的账号给多人使用，不用额外设置端口，因为一个账号就可以多人使用。一般10美元的服务器可以同时支持100人在线使用。\n\n如果想实现支持每个用户(端口)不同的加密方式/协议/混淆等，并且管理流量使用，可以参考多用户配置脚本：wget -N –no-check-certificate [https://softs.fun/Bash/ssrmu.sh](https://softs.fun/Bash/ssrmu.sh) && chmod +x ssrmu.sh && bash ssrmu.sh 备用脚本：wget -N –no-check-certificate [https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssrmu.sh](https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssrmu.sh) && chmod +x ssrmu.sh && bash ssrmu.sh 安装后管理命令为：bash ssrmu.sh\n注意：这个多用户配置脚本和教程内容的脚本无法共存！要想用这个脚本，把之前的脚本卸载，输入管理命令bash ssr.sh ，选择3，卸载ShadowsocksR即可卸载原脚本。\n\n8、vultr服务器每月有流量限制，超过限制后服务器不会被停止运行，但是超出的流量会被额外收费。北美和西欧地区的服务器超出流量后，多出的部分收费为0.01美元/G。新加坡和日本东京（日本）为0.025美元/G，悉尼（澳大利亚）为0.05美元/G。把vultr服务器删掉，开通新的服务器，流量会从0开始重新计算。\n\n9、vultr怎样才能申请退款呢？\n\nvultr和其他的国外商家一样，都是使用工单的形式与客服联系，如果需要退款，直接在后台点击support，选择open ticket新开一个工单，选择billing question财务问题，简单的在文本框输入你的退款理由。比如：Please refund all the balance in my account。工单提交以后一般很快就可以给你确认退款，若干个工作日后就会退回你的支付方式。（全额退款结束后，账号可能会被删除）\n\n如果英语水平不好，但是想和客服进行交流，可以用百度在线翻译，自动中文转英文和英文转中文。\n\n10、路由器也可以配置ssr，关键的是路由器刷固件，华硕路由器刷梅林改版固件最简单，下载固件直接刷，梅林改版固件自带软件中心，然后再软件中心点离线安装就可以了（原版梅林不带软件中心 [下载](http://asuswrt.lostrealm.ca/download)）。路由器刷merlin_8wan_firmware（八万）的固件就行[KoolShare固件下载](http://firmware.koolshare.cn/)\n其他的路由器也可以刷梅林。有问题或者对路由器配置ssr感兴趣的，可以在这些网站上自学：[koolshare](http://koolshare.cn/forum.php) [华硕路由爱好者社区](http://www.52asus.com/forum.php) [NAP6](https://nap6.com/portal.php)\n\n* * *\n\n>转接自：https://github.com/getlantern/forum/issues/5620\n原出处也已经被删除了\n\n\n\n"
  },
  {
    "path": "SUMMARY.md",
    "content": "# Summary\n\n* [为什么学Python?](/Article/PythonBasis/python0/WhyStudyPython.md)\n* [Python代码规范](/Article/codeSpecification/codeSpecification_Preface.md)\n  - [简明概述](/Article/codeSpecification/codeSpecification_first.md)\n  - [注释](/Article/codeSpecification/codeSpecification_second.md)\n  - [命名规范](/Article/codeSpecification/codeSpecification_third.md)\n* [第一个Python程序](/Article/PythonBasis/python1/Preface.md)\n  - [Python 简介](/Article/PythonBasis/python1/Introduction.md)\n  - [Python 的安装](/Article/PythonBasis/python1/Installation.md)\n  - [第一个 Python 程序](/Article/PythonBasis/python1/The_first_procedure.md)\n  - [集成开发环境（IDE）: PyCharm](/Article/PythonBasis/python1/IDE.md)\n* [基本数据类型和变量](/Article/PythonBasis/python2/Preface.md)\n  - [Python 语法的简要说明](/Article/PythonBasis/python2/Grammar.md)\n  - [print() 函数](/Article/PythonBasis/python2/print.md)\n  - [Python 的基本数据类型](/Article/PythonBasis/python2/Type_of_data.md)\n  - [字符串的编码问题](/Article/PythonBasis/python2/StringCoding.md)\n  - [基本数据类型转换](/Article/PythonBasis/python2/Type_conversion.md)\n  - [Python 中的变量](/Article/PythonBasis/python2/Variable.md)\n* [List 和 Tuple](/Article/PythonBasis/python3/Preface.md)\n  - [List（列表）](/Article/PythonBasis/python3/List.md)\n  - [tuple（元组）](/Article/PythonBasis/python3/tuple.md)\n* [ Dict 和 Set](/Article/PythonBasis/python4/Preface.md)\n  - [字典(Dictionary)](/Article/PythonBasis/python4/Dict.md)\n  - [set](/Article/PythonBasis/python4/Set.md)\n* [条件语句和循环语句](/Article/PythonBasis/python5/Preface.md)\n  - [条件语句](/Article/PythonBasis/python5/If.md)\n  - [循环语句](/Article/PythonBasis/python5/Cycle.md)\n  - [条件语句和循环语句综合实例](/Article/PythonBasis/python5/Example.md)\n* [函数](/Article/PythonBasis/python6/Preface.md)\n  - [Python 自定义函数的基本步骤](/Article/PythonBasis/python6/1.md)\n  - [函数返回值](/Article/PythonBasis/python6/2.md)\n  - [函数的参数](/Article/PythonBasis/python6/3.md)\n  - [函数传值问题](/Article/PythonBasis/python6/4.md)\n  - [匿名函数](/Article/PythonBasis/python6/5.md)\n* [迭代器和生成器](/Article/PythonBasis/python7/Preface.md)\n  - [迭代](/Article/PythonBasis/python7/1.md)\n  - [Python 迭代器](/Article/PythonBasis/python7/2.md)\n  - [lsit 生成式（列表生成式）](/Article/PythonBasis/python7/3.md)\n  - [生成器](/Article/PythonBasis/python7/4.md)\n  - [迭代器和生成器综合例子](/Article/PythonBasis/python7/5.md)\n* [面向对象](/Article/PythonBasis/python8/Preface.md)\n  - [面向对象的概念](/Article/PythonBasis/python8/1.md)\n  - [类的定义和调用](/Article/PythonBasis/python8/2.md)\n  - [类方法](/Article/PythonBasis/python8/3.md)\n  - [修改和增加类属性](/Article/PythonBasis/python8/4.md)\n  - [类和对象](/Article/PythonBasis/python8/5.md)\n  - [初始化函数](/Article/PythonBasis/python8/6.md)\n  - [类的继承](/Article/PythonBasis/python8/7.md)\n  - [类的多态](/Article/PythonBasis/python8/8.md)\n  - [类的访问控制](/Article/PythonBasis/python8/9.md)\n* [模块与包](/Article/PythonBasis/python9/Preface.md)\n  - [Python 模块简介](/Article/PythonBasis/python9/1.md)\n  - [模块的使用](/Article/PythonBasis/python9/2.md)\n  - [主模块和非主模块](/Article/PythonBasis/python9/3.md)\n  - [包](/Article/PythonBasis/python9/4.md)\n  - [作用域](/Article/PythonBasis/python9/5.md)\n* [Python 的 Magic Method](/Article/PythonBasis/python10/Preface.md)\n  - [Python 的 Magic Method](/Article/PythonBasis/python10/1.md)\n  - [构造(`__new__`)和初始化(`__init__`)](/Article/PythonBasis/python10/2.md)\n  - [属性的访问控制](/Article/PythonBasis/python10/3.md)\n  - [对象的描述器](/Article/PythonBasis/python10/4.md)\n  - [自定义容器（Container）](/Article/PythonBasis/python10/5.md)\n  - [运算符相关的魔术方法](/Article/PythonBasis/python10/6.md)\n* [枚举类](/Article/python11/PythonBasis/Preface.md)\n  - [枚举类的使用](/Article/PythonBasis/python11/1.md)\n  - [Enum 的源码](/Article/PythonBasis/python11/2.md)\n  - [自定义类型的枚举](/Article/PythonBasis/python11/3.md)\n  - [枚举的比较](/Article/PythonBasis/python11/4.md)\n* [元类](/Article/PythonBasis/python12/Preface.md)\n  - [Python 中类也是对象](/Article/PythonBasis/python12/1.md)\n  - [使用 `type()` 动态创建类](/Article/PythonBasis/python12/2.md)\n  - [什么是元类](/Article/PythonBasis/python12/3.md)\n  - [自定义元类](/Article/PythonBasis/python12/4.md)\n  - [使用元类](/Article/PythonBasis/python12/5.md)\n* [线程与进程](/Article/PythonBasis/python13/Preface.md)\n  - [线程与进程](/Article/PythonBasis/python13/1.md)\n  - [多线程编程](/Article/PythonBasis/python13/2.md)\n  - [进程](/Article/PythonBasis/python13/3.md)\n* [一步一步了解正则表达式](/Article/PythonBasis/python14/Preface.md)\n    - [初识 Python 正则表达式](/Article/PythonBasis/python14/1.md)\n    - [字符集](/Article/PythonBasis/python14/2.md)\n    - [数量词](/Article/PythonBasis/python14/3.md)\n    - [边界匹配符和组](/Article/PythonBasis/python14/4.md)\n    - [re.sub](/Article/PythonBasis/python14/5.md)\n    - [re.match 和 re.search](/Article/PythonBasis/python14/6.md)\n* [闭包](/Article/PythonBasis/python15/1.md)\n* [装饰器](/Article/PythonBasis/python16/1.md)\n* [知识点补漏](README.md)\n    - [Python 关键字 yield](/Article/supplement/Python关键字yield.md)\n* [**Python 进阶部分**](/Article/advanced/advanced.md)\n* [使用Python虚拟环境](/Article/advanced/使用Python虚拟环境.md)\n* [Mac中使用virtualenv和virtualenvwrapper](/Article/advanced/Mac中使用virtualenv和virtualenvwrapper.md)\n* [**Django**](/Article/django/Django.md)\n\n\n"
  },
  {
    "path": "TwoWater-Python.json",
    "content": "{\n  \"title\": \"草根学Python\",\n  \"repo\": \"TwoWater/Python\",\n  \"branch\": \"master\",\n  \"original_url\": \"https://www.gitbook.com/book/twowater/python\"\n}"
  },
  {
    "path": "book.json",
    "content": "{\n    \"plugins\": [\n        \"github\",\n        \"toggle-chapters\",\n        \"github-buttons\",\n        \"-highlight\"\n    ],\n    \"pluginsConfig\": {\n        \"github\": {\n            \"url\": \"https://github.com/TwoWater/Python\"\n        }\n    }\n}"
  }
]