Play with Py

Python 最少必备知识点总结。

Focus on programming, not the programming language.

概述

Wikipedia:

是一种广泛使用的高级编程语言,属于通用型编程语言,由吉多·范罗苏姆(Guido van Rossum,GvR)创造,第一版发布于1991年。可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP。作为一种解释型语言,Python的设计哲学强调代码的可读性和简洁的语法(尤其是使用空格缩进划分代码块,而非使用大括号或者关键词)。相比于C++或Java,Python让开发者能够用更少的代码表达想法。不管是小型还是大型程序,该语言都试图让程序的结构清晰明了。

Python 的优势

  • 简单优美

Python的设计哲学是“优雅”、“明确”、“简单”。Python开发者的哲学是“用一种方法,最好是只有一种方法来做一件事”,也因此它和拥有明显个人风格的其他语言很不一样。在设计Python语言时,如果面临多种选择,Python开发者一般会拒绝花俏的语法,而选择明确没有或者很少有歧义的语法。这些准则被称为“Python格言”。

Python 格言可以通过 import this 来查看。

  • Python是完全面向对象的语言

函数、模块、数字、字符串都是对象。并且完全支持继承、重载、派生、多重继承,有益于增强源代码的复用性。Python支持重载运算符,因此Python也支持泛型设计。

  • 高阶动态编程语言

虽然 Python 被称为脚本语言,但是其能做的事情远远超过了普通脚本语言,比如 Shell。

此外,由于是解释执行的,因此不需要程序员手动编译,也不需要关心如何确保链接库是否正确等和移植性有关的问题。

  • Python 是可扩展的

Python 除了必要的语言核心之外,还提供了丰富的 API 和工具,以便程序员能够轻松地使用 C/C++/Cython 编写扩充模块。此外,除了为了性能,使用扩展的形式还可以保护某些关键的算法实现。

Python编译器本身也可以被集成到其它需要脚本语言的程序内,因此常被称为胶水语言。

  • 可以跨平台实现

Python是一门跨平台的脚本语言,Python规定了一个Python语法规则,根据该规则可编写Python解释器。目前已知的 Python 解释器有:

  • CPython,官方的解释器。需要区别于其他解释器的时候才以CPython称呼。这是最常用的Python版本。
  • Jython(原名JPython;Java语言实现的Python,现已正式发布)。Jython可以直接调用Java的各种函数库。
  • PyPy(使用Python语言写的Python,JIT 技术)
  • IronPython(面向.NET和ECMA CLI的Python实现)。IronPython能够直接调用.net平台的各种函数库。可以将Python程序编译成.net程序。
  • ZhPy(周蟒,支持使用繁/简中文语句编写程序的Python语言)。
  • 丰富强大的第三方库

可以说用 Python 很多时间就是在学习用这些库来解决实际问题。

Python 的劣势

由于解释性语言的原因,Python 代码的运行速度慢相比编译型语言(C++/Java等)会慢一些。

解决办法是使用调用其他高速语言编写的模块。

  • 不适合高并发、多线程应用场景

为了防止解释器同时执行多条 Python 字节码指令,Python 存在全局解释锁(Global Interpreter Lock,GIL)。

因此,Python 不适合 CPU 密集型(CPU-bound)的并发编程。

Python 的应用场景

  • Web 应用开发

    • 通过 mod_wsgi 模块,Apache 可以运行 Python 编写的 Web 程序。
    • 使用 Gunicorn 作为 Web 服务器,也能够运行 Python 编写的 Web 程序。
    • Python 定义了 WSGI 标准应用接口来协调 HTTP 服务器与基于 Python 的 Web 程序之间的沟通。
    • 常用的 Web 开发框架有:
      • Django:开源Web开发框架,它鼓励快速开发,并遵循MVC设计,开发周期短。
      • Flask:轻量级的Web框架。
      • Pyramid:轻量,同时有可以规模化的Web框架,Pylon projects 的一部分。
      • ActiveGrid:企业级的Web2.0解决方案。
      • Karrigell:简单的Web框架,自身包含了Web服务,py脚本引擎和纯python的数据库PyDBLite。
      • Tornado:一个轻量级的Web框架,内置非阻塞式服务器,而且速度相当快。
      • webpy:一个小巧灵活的Web框架,虽然简单但是功能强大。
      • CherryPy:基于Python的Web应用程序开发框架。
      • TurboGears:基于Python的MVC风格的Web应用程序框架。
      • Zope:开源的Web应用服务器。
      • Twisted:流行的网络编程库,大型Web框架。
  • 数据分析和科学计算

    • Matplotlib:用Python实现的类matlab的第三方库,用以绘制一些高质量的数学二维图形。
    • Pandas:用于数据分析、数据建模、数据可视化的第三方库。
    • SciPy:基于Python的matlab实现,旨在实现matlab的所有功能。
    • NumPy:基于Python的科学计算第三方库,提供了矩阵,线性代数,傅立叶变换等等的解决方案。
  • 网络爬虫

    • Scrapy:开源免费的 Web 爬虫框架。
    • Beatifulsoup、pyquery 等 HTML 解析库。
    • requests 适合人类使用的网络库。
  • 自动化运维

    • SaltStack:开源配置管理软件和远程执行引擎。
    • Ansible:应用部署、配置管理、持续交付等 IT 基础设施的自动化工具。
    • Fabfile:用于管理成百上千台Linux主机的程序库。
  • GUI

    • PyGtk:基于Python的GUI程序开发GTK+库。
    • PyQt:用于Python的QT开发库。
    • WxPython:Python下的GUI编程框架,与MFC的架构相似。
  • 系统编程

Python 是大多数Linux发行版和Mac OS X 的标准系统组件,可以在终端机下直接运行Python。有一些Linux发行版的安装器使用Python语言编写,比如Ubuntu的Ubiquity安装器、Red Hat Linux和Fedora的Anaconda安装器。在RPM系列Linux发行版中,有一些系统组件就是用Python编写的。

如何系统学习 Python?

任何一种编程语言都包含两个部分:硬知识和软知识。

硬知识:指的是编程语言的语法、算法和数据结构,编程范式等。例如:变量和类型、循环语句、分支、函数、类等。

软知识:特定语言环境下的语法技巧、类库的使用、IDE 的选择等。这一部分,即使完全不了解不会使用,也不会妨碍你去编程,只是写出的程序,看上去显得“傻”了些。

起步阶段

起步阶段的核心任务是掌握硬知识,软知识做适当了解。

  • 学习硬知识

硬知识也是具有普适性的,看上去是掌握了一种语法,实际是建立了一种思维。例如:学过 Java 的程序员去学 Python 可以很快地将 Java 中学到的知识快速 map 到 Python 中来。

因此,对于刚开始学习编程的新手,一本可靠的语法书是非常重要的。它看上去可能非常枯燥乏味,但对于建立稳固的编程思维是必不可少。注意,实际学习的时候,最好只选择一种学习资料为主,并坚持看完即可。

必要的时候,可能需要阅读讲解数据结构和算法的书,这些知识对于理解和使用 Python 对象模型有着很大的帮助。

  • 学习软知识

对于软知识的学习,取决于你尝试解决问题的领域和深度。

对初学者来说,起步阶段极易走火,或者在选择 Python 版本时徘徊不觉,一会儿看 2.7 一会儿又转到 3.0,或者徜徉在类库的大海中无法自拔,Scrapy,NumPy,Django 什么都要试试,或者参与编辑器圣战、大括号缩进探究、操作系统辩论赛等无意义活动,或者整体跪舔语法糖,老想着怎么一行代码把所有的事情做完,或者去构想圣洁的性能安全通用性健壮性全部满分的解决方案。

发展阶段

发展阶段的核心任务,就是「跳出 Python,拥抱世界」—— Python 和专业知识相结合,能够解决很多实际问题。这个阶段能走到什么程度,更多地取决于自己的专业知识。

选择应用分支进行深入,每个应用方向,都不是仅仅知道 Python 语法就能解决的问题。

拿爬虫举例,如果你对计算机网络,HTTP 协议,HTML,文本编码,JSON 等一无所知,你是写不出爬虫的。

此外在起步阶段的基础知识也同样重要,如果你连循环递归怎么写都要查文档,连 BFS 都不知道怎么实现,这就像工匠做石凳每次起锤都要思考锤子怎么使用一样,非常低效。

因此这个阶段,会接触大量类库——阅读文档,阅读大量书籍——读你要从事的应用领域相关的书籍,也成专业书籍。

深入阶段

这个阶段对 Python 几乎了如指掌,这时候就不能再停留在表面了,勇敢地拆开 Python 的黑盒子,深入到语言的内部,去看它的历史,读它的源码(C),才能真正理解它的设计思路。

Python 的许多最佳实践都隐藏在那些众所周知的框架和类库中,例如 Django、Tornado等等。在它们的源代码中淘金,也是个不错的选择。

另外,Python 本身是一门杂糅多种范式的动态语言,也就是说,相对于 C 的过程式、Schema 和 Haskell 等的函数式、Java 基于类的面向对象而言,它都不够纯粹。换而言之,编程语言的「道学」。在 Python 中只能有限地体悟,因此从哪些面向某种范式更加纯粹的语言出发,才能有更深刻的理解,也能了解到 Python 语言的根源。

Python 2 vs Python 3?

为了不带入过多的累赘,Python 3 在设计的时候没有考虑向下兼容。许多针对早期 Python 版本设计的程序都无法在 Python 3 上正常运行。

解决办法:

  • 新 Python 程序优先使用 Python 3,当然具体也要看相应的模块是否稳定支持 Python 3。
  • 编写兼容 Python 3 的代码,然后使用 Python2.6/Python2.7 来运行。

Python 2.6作为一个过渡版本,基本使用了Python 2.x的语法和库,同时考虑了向Python 3.0的迁移;Python 2.7被确定为最后一个Python 2.x版本,它除了支持Python 2.x语法外,还支持部分Python 3.1语法。

  • 2to3 转换工具:基于早期Python版本而能正常运行于Python 2.6并无警告的程序可以通过一个2 to 3的转换工具无缝迁移到Python 3.0。

主要变化

See: https://docs.python.org/3/whatsnew/

  • print() 函数代替 print 语句(可用 2to3 工具自动转化)

Python 2.6/2.7 中可以通过以下方式来使用 print() 函数

1
2
from __future__ import print_function
print("fish", "panda", sep=', ')
  • 新的 str 类型表示一个 Unicode 字符串,相当于 2.x 版本的 unicode 类型。而字节序列则用类似 b"abc" 的语法表示,用 bytes 类表示,相当于 2.x 的 str 类型

现在两种类型不能再隐式地自动转换,因此在 3.x 里 "aaaa" + b"bbbb" 是错误的。正确的写法是 "aaaa" + b"bbbb".decode("utf-8")

Python 2.6/2.7 也可以自动地将字节序列识别为 Unicode 字符串:

1
2
from __future__ import unicode_literals
print(repr("bbbb"))
  • 除法运算符/ 在 3.x 内总是返回浮点数

在 2.6 内会判断被除数与除数是否是整数:如果是整数会返回整数值,相当于整除;如果是浮点数则返回浮点数值。为了让 2.6 统一返回浮点数值,可以:

1
2
from __future__ import division
print(1/3)
  • 捕获异常的语法由 except exc, var 改为 except exc as var

语法 except (exc1, exc2) as var 可以同时捕获多种类型的异常。2.6/2.7 已经支持这两种语法。

  • 集合/set 新写法:{1,2,3,4}。注意 {} 仍然表示空的字典。

  • 八进制数必须写成 0o777,原来的形式 0777 不能用了;二进制必须写成 0b111。新增了一个 bin() 函数用于将一个整数转换成二进制字符串。2.6/2.7 已经支持这两种语法。

  • dict.keys(), dict.values(), dict.items(), map(), filter(), range(), zip() 不再返回列表,而是迭代器。

  • 如果两个对象之间没有定义明确的有意义的顺序,使用 <, >, <=, >= 比较它们会抛出异常。

比如 1 < "" 在 2.6/2.7 里面会返回True,而在 3 里面会抛出异常。现在 cmp(), instance.__cmp__() 函数已经被删除。

  • 可以注释函数的参数与返回值。此特性可方便IDE对源代码进行更深入的分析
1
2
def sendMail(from_: str, to: str, title: str, body: str) -> bool:
pass
  • 合并 int 与 long 类型。

  • 多个模块被改名(PEP8):

1
2
3
4
5
6
7
8
9
10
_winreg	=> winreg
ConfigParser => configparser
copy_reg => copyreg
Queue => queue
SocketServer => socketserver
repr => reprlib

StringIO 模块现在被合并到新的 io 模块内。new, md5, gopherlib 等模块被删除。2.6/2.7 已经支持新的 io 模块

httplib, BaseHTTPServer, CGIHTTPServer, SimpleHTTPServer, Cookie, cookielib 被合并到 http 包内
  • 取消了 exec 语句,只剩下 exec() 函数。2.6/2.7 已经支持 exec() 函数。
  • 字典推导式(Dictionary comprehensions){expr1: expr2 for k, v in d},这个语法等价于:
1
2
3
4
result={}
for k, v in d.items():
result[expr1]=expr2
return result
  • 集合推导式(Set Comprehensions){expr1 for x in stuff}。这个语法等价于:
1
2
3
4
result = set()
for x in stuff:
result.add(expr1)
return result

入门必备

See:https://docs.python.org/

代码组织

语法元素

标识符
  • 和大多数编程语言一样 Python 中标识符(命名)的组成由由非数字开头的英文(区分大小写)、数字以及下划线组成
  • 标识符中的下划线 _ 及其数量有特殊含义:
    • 单下划线开头 _foo 代表不能直接访问的类属性,需通过类提供的接口进行访问,也不能用 from xxx import * 而导入
    • 双下划线开头 __foo 代表类的私有成员
    • 双下划线开头和结尾 __foo__ 是 Python 里特殊方法专用的标识,如 init() 代表类的构造函数(类似 PHP 魔术方法)
缩进

Python 与其他语言最大区别:使用严格一致的缩进层级来区别代码块(模块/函数/逻辑判断)。缩进属于 Python 的语法。

语句
  • 单行多语句使用 ; 分隔
1
print 'hello';print('python');
  • 多行单语句使用 \ 串联;其中语句中包含 [], {}() 时不需要使用多行连接符
1
2
3
4
5
6
7
8
9
10
11
a = b + \
c + \
d

workdays = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
]
引号

Python 中单引号 ' 表示单词;双引号 " 表示 字符串(不能换行);三引号 """/''' 表示段落(可以换行)。

注释

单行注释用 #“,多行注释既可以用单三引号对 ''' ... '''“, 也可以用双三引号对 """ ... """

模块

  • 什么是模块?

    • 模块即封装了任何 Python 代码的 .py 文件
    • 模块代码既可以是定义/声明 也可以是可执行的代码
  • 模块导入方式

    • 导入模块即导入模块文件的文件名:import m1[, m2[, ... mN]]
    • 不管同一个模块执行了多少次 import 一个模块只会被导入一次
    • 从一个模块中导入一个指定的部分(可以是子模块)到执行这个声明的模块的全局符号表(命名空间)中:

      1
      from module_name import def_name[, def_name[, ... def_name]]
    • 把一个模块的所有内容全都导入到当前的命名空间(不推荐经常使用):

      1
      from module_name import *
  • 导入模块时的搜索路径顺序/优先级

    • 当前目录
    • 当前 SHELL 中 PYTHONPATH 环境变量指定的每个目录
    • 系统默认路径(UNIX下,默认路径一般为 /usr/local/lib/python/)
  • 模块导入原理

    • 模块搜索路径存储在 system 模块的 sys.path 变量中,变量里包含当前目录,PYTHONPATH 和由安装过程决定的默认目录。
    • 模块被导入后会生成字节码文件 .pyc,如果要导入模块的 .pyc 文件存在,则直接将 .pyc 读入内存 而无需先编译 .py 文件。

基础语法

变量赋值

基本概念:

- 变量存储在内存中的值,创建变量时会在内存中开辟一个空间
- 每个变量在内存中创建,都包括变量的标识,名称和数据这些信息。
- Python 中变量是没有类型的 变量仅是对类型(对象)的引用(指针)一个变量可以指向任何类型
- 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中

变量赋值:

- Python 中的变量赋值不需要类型声明
- 每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建
- 等号 `=` 用来给变量赋值:等号 `=` 运算符左边是一个变量名,等号 `=` 运算符右边是存储在变量中的值
1
2
3
4
5
6
7
8
9
10
# 多变量赋值
## 创建一个整型对象,值为1,a/b/c 三个变量被分配到相同的内存空间上
a = b = c = 1
print a, b, c, id(a), id(b), id(c)

## 创建一个整型对象,值为2,分配给变量 d
## 创建一个布尔对象,值为 False,分配给变量 e
## 创建一个字符串对象,值为 yo,分配给变量 f
d, e, f = 2, False, 'yo'
print d, e, f, hex(id(d)), hex(id(e)), hex(id(f))

基本类型

  • Python 中类型即对象
  • Python 中数据类型是不允许改变的 如果改变了数据类型的值 将重新分配内存空间
  • 使用 del 语句可以删除对象的引用 当引用计数为 0 时 可以理解为删除了对象
1
2
3
a = b = c = 1
del b, c
print a
数字
  • Python 2 中长整型也可以使用小写 L,为避免与数字 1 混淆 建议使用大写 L 来表示长整型
  • 复数由实数部分和虚数部分构成,可以用a + bj,或者complex(a,b)表示,复数的实部a和虚部b都是浮点型
字符串
  • Python 没有专门的单字符类型,单字符在 Python 中也是作为一个字符串使用
  • 三个单引号和三个双引号的使用基本没什么区别 同时存在的原因是可以在很多时候互相抵消自身的转义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
s1 = '~~hello~~'
s2 = "~~~~python~~~~"
print "可以使用方括号来截取字符串:%s %s" % (s1[2:], s2[3:10])

# 字符串运算
## 连接字符串
s1 + s2
## 重复输出字符串
s1 * 2
## 通过索引获取字符串中字符
s1[1]
## 截取字符串中的一部分
s1[2:7]
## 成员运算符
'H' in s1
'h' not in s1
## 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符
print r'\a 是响铃转义符'
print R'\e 是转义转义符'
## Unicode 字符串
print u'Hello\u0020Unicode'

流程控制

条件分支
  • Python 中任何非 0 和非空(null)值为 true,0 或者 null 为 false
  • Python 不支持 ! 符号进行布尔取反,只能使用 not: if (! False) => Syntax Error’
  • Python 不支持 switch
1
2
3
4
5
6
7
8
9
10
11
12
13
if True: print "True"
if (not False and True): print "False"
if 1 < 2:
print "1 is smaller than 2, so easy."

if 1 < 2:
print 1
elif 3 < 4:
print 2
elif (0 < 1 and 1 < 2) or (3 < 4 or 4 < 5):
print 3
else:
print "nothing-burger"
循环/迭代
  • Python 没有 do…while 循环
  • 循环中 else 语句会在循环正常执行完 即循环不是通过 break 跳出而中断的情况下执行
  • break 语句,就像在 C 语言中,强制性跳出最小封闭循环 如果是嵌套循环,break 语句将停止执行最深层的整个循环
  • continue 语句跳出本次循环,而 break 跳出整个循环
  • Python 中 pass 是空语句,临时暂停本次循环 为了保持程序结构的完整性 一般用做占位语句
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
i = 0
while i < 10:
i += 1
if i % 2 == 0:
continue
elif False:
break

print "i =", i
else:
print "i is bigger than 10 now"

# for 可以遍历 List、Tuple、Dict、String
for char in 'python':
if char == 't':
pass
print "current loop is passed temporarily, but will be loop again"
print 'char =', char
else:
print 'EOF'

langs = ['python', 'golang', 'php']
for k, v in enumerate(langs, start=1):
print k, v
for k, v in {'python': 6, 'golang': 6, 'php': 8}.iteritems(): # Py2
#for k, v in {'python': 6, 'golang': 6, 'php': 8}.items(): # Py3
print k, v

函数

可更改与不可更改类型对象
  • immutable: 变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a
  • mutable: 变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了
可更改与不可更改函数参数
  • immutable:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身
  • mutable:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
  • python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象
函数参数
  • 必备参数:必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样
  • 关键字参数:使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值
  • 缺省参数:调用函数时,缺省参数的值如果没有传入,则被认为是默认值
  • 不定长参数:一个函数可能处理比当初声明时更多的参数 这些参数叫做不定长参数 类型为元组;不定长参数声明的命名不作为关键词参数
其他特点
  • return 可选;不含 return 或只有一个 return 关键字的函数默认返回 None
  • 函数必须先定义再调用,否则会报错
1
2
3
4
5
6
7
8
9
10
11
def person(name, age = 18, *extra_info_tuple):
print "this is %s, whose age is %d" % (name, age)

print type(extra_info_tuple), extra_info_tuple

person('cjli')
person('lcj', 24)
person(age=20, name="cj")
#person(age=22, name="li", 'boy', 'programmer') # Syntax Error
#person(age=22, name="li", extra_info_tuple=('boy', 'programmer')) # Syntax Error
person("li", 21, 'boy', 'programmer')
匿名函数
  • python 使用比 def 更简洁的 lambda 表达式来创建匿名函数
  • lambda 表达式中仅能封装有限的函数逻辑
  • lambda 函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数
  • 虽然 lambda 函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率
1
2
sum = lambda arg1, arg2: arg1 + arg2
print sum(1, 2)
变量作用域
  • 一个程序的所有的变量并不是在哪个位置都可以访问的 访问权限决定于这个变量是在哪里赋值的
  • 如果一个局部变量和一个全局变量重名,则局部变量会覆盖全局变量
  • 变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称
  • Python 默认任何在函数内赋值的变量都是局部的,如果要给函数内的全局变量赋值,必须使用 global 语句,这样 Python 才不会在局部命名空间里寻找这个变量
1
2
3
4
5
6
7
8
s = 0    # 全局变量
def sum(a, b):
# global s
s = a + b # 局部变量
print s
return s
sum(11, 22)
print s

OOP

面向对象是 Python 的主要编程范式。

继承
  • 如果在子类中需要父类的构造方法就需要显示的调用父类的构造方法,或者不重写父类的构造方法
  • 在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量
单下划线、双下划线、头尾双下划线说明
  • _foo: 以单下划线开头的表示的是 protected 类型的成员 只能允许其本身与子类进行访问,不能用于 from module import *
  • __foo: 双下划线开头的表示的是 private 类型的成员, 只能是允许这个类本身进行访问
  • __foo__: 双下划线开头和结尾表示的是特殊成员,一般是系统定义名字(类似 init() 等魔术方法)
GC
  • 含义

Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充,垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下,解释器会暂停下来,试图清理所有未引用的循环。

  • 循环引用

垃圾回收机制不仅针对引用计数为 0 的对象,同样也可以处理循环引用的情况。

循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。

例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import inspect

class Encapsulation:
'1'
pass

# `object` here is necessary when child class call parent class using `super()`
class Inheritance(object):
'2'

def __init__(self, *params):
print 'construct method'

pass

def to_be_overrided(self):
print 'override status: 1'

class Polymorphism:
'3'
pass

class PythonOOP(Encapsulation, Inheritance, Polymorphism):
'This is class description... query me via `ClassName.__doc__`'

class_var = '''
This is class variable, shared by all instances of a class
'''

_protected_attribute = 'protected'
__private_attribute = 'private'

increment = 0

params = ()

def __init__(self, *params):
super(PythonOOP, self).__init__()

self.println('''
Method __init__() is a special method, which is called class constructor or initialization method that Python calls when you create a new instance of this class.
''')

self.params = params
PythonOOP.increment += 1

def __protected_method(self):
pass

def __private_method(self):
pass

def to_be_overrided(self):
print 'override status: 2'

def what_is_self_in_class(self):
self.println('''
The first argument to every method of class, stand for instance of current class as memory address. (`self.__class__` is the class name of instance `self`)
Named of `self` by convention, we can use other words like `this`, `instance` etc instead.
Python automatically adds the `self` argument to the method parameters list, but it's no need to include it when you call the methods.
''')

def what_is_instance_variable(self):
instance_var = '''
A variable that is defined inside a method and belongs only to the current instance of a class.
'''

self.println(instance_var)

def what_is_class_method(self):
self.println('''
A special kind of function that is defined in a class definition.
First parameter of every class method is necessary and is instance self.
''')

def what_is_class_data_member(self):
self.println('''
Class Data Member = Class Variable + Instance Variable
''')

def println(self, info):
method = inspect.stack()[1][3]
method = method if method.startswith('__') else method.replace('_', ' ')

print '-', method, '?', info

def what_is_garbage_colletion(self):
self.println('''
Python deletes unneeded objects (built-in types or class instances) automatically to free the memory space.
The process by which Python periodically reclaims blocks of memory that no longer are in use is termed Garbage Collection (GC).

Python's garbage collector runs during program execution and is triggered when an object's reference count reaches zero.
An object's reference count changes as the number of aliases that point to it changes.

An object's reference count increases when it is assigned a new name or placed in a container (list, tuple, or dictionary).
The object's reference count decreases when it's deleted with `del`, its reference is reassigned, or its reference goes out of scope.
When an object's reference count reaches zero, Python collects it automatically.

''')

a = 40 # Create object <40>
b = a # Increase ref. count of <40>
c = [b] # Increase ref. count of <40>

del a # Decrease ref. count of <40>
b = 100 # Decrease ref. count of <40>
c[0] = -1 # Decrease ref. count of <40>

def python_class_special_inner_attributes(self):
self.println('''
__dict__: Dictionary containing the class's namespace/attributes.
=> %s
__doc__: Class documentation string or none, if undefined.
=> %s
__name__: Class name.
=> %s
__module__: Module name in which the class is defined. This attribute is "__main__" in interactive mode.
=> %s
__bases__: A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.
=> %s
''' % (
PythonOOP.__dict__,
PythonOOP.__doc__,
PythonOOP.__name__,
PythonOOP.__module__,
PythonOOP.__bases__,
))

def underline_in_attributes_name(self):
self.println('''
单下划线、双下划线、头尾双下划线说明:
- _foo: 以单下划线开头的表示的是 protected 类型的成员 只能允许其本身与子类进行访问,不能用于 `from module import *`
- __foo: 双下划线开头的表示的是 private 类型的成员, 只能是允许这个类本身进行访问
- __foo__: 双下划线开头和结尾表示的是特殊成员,一般是系统定义名字(类似 __init__() 等魔术方法)
''')

def __str__(this):
return 'this is object to string'

def __repr__(this):
return 'Evaluable string representation for python interpreter'

def __cmp__(this, x):
return 'Compare object ...'

def __add__(this, other):
return 'This is called "Operator Overloading", add two object like 1 + 1 = ', 1+1

def __del__(this):
print 'One instance of class ', this.__class__.__name__, ' is no longer needed.'

def explain(self):
print '-' * 20, ' ' * 2, ' start explain ', ' ' * 2, '-' * 20

print issubclass(PythonOOP, Inheritance)
print isinstance(self, Inheritance)

#methods = dir(self) # include class attributes
methods = inspect.getmembers(PythonOOP, inspect.ismethod)
excludes = [
'static_method',
'class_method',
'explain',
'println',
]

for method in methods:
if (not str(method[0]).startswith('__')) and (method[0] not in excludes):
getattr(self, method[0])()

print '-' * 20, ' ' * 2, ' end explain ', ' ' * 2, '-' * 20

def static_method(var):
print var

def class_method(klass, var):
print klass.__name__, var

static_method = staticmethod(static_method)
class_method = classmethod(class_method)

# Create instance of class PythonOOP
# !!! no `new` keyword
pyoop = PythonOOP('nothing burger')

# Call method
pyoop.explain()

# Accessing Attributes
print pyoop.class_var
print pyoop.__class__

#print PythonOOP.__doc__
#print PythonOOP.__dict__
#print PythonOOP.__name__
#print PythonOOP.__module__
#print PythonOOP.__bases__

# CURD
pyoop.tmp = 'Custom Attribute 0'
pyoop.tmp = 'Custom Attribute 1'
print pyoop.tmp
del pyoop.tmp

# Related Helpers
print hasattr(pyoop, 'params')
setattr(pyoop, 'foo', 'bar')
delattr(pyoop, 'foo')

shadow = PythonOOP()
print PythonOOP.increment

print pyoop.__repr__()
print pyoop.__str__()
#print pyoop == shadow
print pyoop + shadow

# Method Overriding
pyoop.to_be_overrided()

# Class Method and Statis Method
PythonOOP.class_method('class method 1')
PythonOOP().class_method('instance method 1')
PythonOOP.static_method('static method 2')
PythonOOP().static_method('instance method 2')
new-style 和 old-style 类

只有 Python 2 才有 old-style 类,Python 3 默认都是 new-style 类。

old-style 类表现形式为类定义既无继承关系也无 object 关键字:

1
2
3
4
5
6
7
8
9
10
# Old-Style
class A:
pass

# New-Style 1
class B(A):
pass
# New-Style 2
class C(object):
pass

数据结构

List/Tuple/Dict

  • 元组几乎只是元素不能修改的列表,因此元组只能整个删除
  • 元组中只包含一个元素时,需要在元素后面添加逗号,否则类型并非元组
  • 字典是一种可变容器 K-V 模型,值可存储任意类型对象
  • 字典中键必须是不可变的,因此数字/字符串/元组均可作为字典的键/而列表和字典则不行
  • 字典中键一般是唯一的,但如果有重复 最后的一个键值对会替换前面的
1
2
3
4
5
6
7
8
9
10
l = [1, 'a']    # 列表
l.append(True)
l.append(False)
del l[3]
# t = (1, 2, 3) # 元组
print l, l[-2], len(l), l * 2, 1 in l, l[1:3], list((1, 2, 3)), [00, 11] + [11, 22], l.count(1)

print type((1)), type((1,))
d = {'a': 1, 'b': 2, 'b': 3, (1, 2): 'tuple'} # 字典
print type(d), d, d['a']

类型转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int(x [,base ])         将x转换为一个整数  
long(x [,base ]) 将x转换为一个长整数
float(x ) 将x转换到一个浮点数
complex(real [,imag ]) 创建一个复数
str(x ) 将对象 x 转换为字符串
repr(x ) 将对象 x 转换为表达式字符串
eval(str ) 用来计算在字符串中的有效Python表达式,并返回一个对象
tuple(s ) 将序列 s 转换为一个元组
list(s ) 将序列 s 转换为一个列表
chr(x ) 将一个整数转换为一个字符
unichr(x ) 将一个整数转换为Unicode字符
ord(x ) 将一个字符转换为它的整数值
hex(x ) 将一个整数转换为一个十六进制字符串
oct(x ) 将一个整数转换为一个八进制字符串

异常处理

Docs:

  • Python 中异常是一个事件,该事件会在程序执行过程中发生,并影响程序的正常执行
  • Python 中异常是一个对象,表示一个错误
  • 当 Python 脚本发生异常时我们需要捕获处理它,否则程序会终止执行

  • 当开始一个try语句后,python就在当前程序的上下文中作标记,这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常

  • 如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)
  • 如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)
  • 如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句
  • try 子句内无论是否发生异常都将执行到 finally 内部(如果有定义)

  • 使用 except 可以不带任何异常类型,但这不是一个很好的方式,我们不能通过该程序识别出具体的异常信息,因为它捕获所有的异常

  • 不带任何异常类型 except 语句,必须只能出现一个,且位于所有的 except 语句的后面

  • 一个 except 语句可以带多种异常类型,统一写到 except 后面的第一个元组参数

  • finally 必须位于 try…except…else 代码块的末尾

  • raise 表达式除了可以手动抛出异常之外,单独的 raise 语句还可以重新抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
try:
try:
fh = open('.ssssss/1.swp', 'w')
#fh = open('1.swp', 'w')
fh.write('test exception')
except IOError, err:
print 'Oops! IO Error - 1', '\n', type(err), err
except IOError as err:
print 'Oops! IO Error - 2', '\n', type(err), err
except (IOError, OSError, RuntimeWarning), err:
print 'Oops! write failed.', '\n', type(err), err
except:
print 'Oops! write failed and I know nothing.'
else:
print 'write success'
fh.close()
finally:
print 'I am Finally, I will be here always.'
raise UserWarning("Warning: 18 forbidden!", 18)
except UserWarning, w:
print w.args
# raise # a simpler form of the raise statement allows you to re-raise the exception

class NetworkTimeout(RuntimeError):
def __init__(self, args):
self.value = "Network timeouted after " + args
def __str__(self):
return repr(self.value)
#return self.value
try:
raise NetworkTimeout("10 seconds")
except NetworkTimeout, e:
print e

如何深入?

  • 多编程实践,多看文档

以解决某个具体问题为出发点,直接上手一个小项目,在见招拆招的过程中提高学习的效率。

  • 多思考概念背后的本质、原理
  • Python 优秀开源项目源码学习
  • Python 源码分析

Q&A

Python 2.x 编码说明

  • 以上两种声明格式均可
  • coding=utf-8= 号两边不要空格
  • Python2.X 源码中如果没有指定编码 执行过程中遇到 utf-8 编码会报错: ‘SyntaxError: Non-ASCII character ‘\xe4’ in file xxx.py … but no encoding declared; see http://…’
  • Python3.X 源码文件默认使用utf-8编码 无需手动指定
  • 同时注意编辑器在保存源文件时的编码一致性问题

Python 模块导入有哪些方式?

常规导入

  • 基本:import sys

  • 一次性导入多个模块:import os, sys, time (节省空间但违背了 Python 风格指南——每个导入语句单独成行)

  • 导入并重命名模块:import subprocecss as process

  • 导入子模块:import urllib.error

from 导入

  • 导入一个模块或库中的某个部分:from functools import lru_cache

这种导入方式可以直接使用 lru_cache,而按常规导入则必须通过 functools.lru_cache 才能调用起来

  • 导入模块全部部分:from os import *

这种做法在少数情况下是挺方便的,但是这样也会打乱你的命名空间。问题在于,你可能定义了一个与导入模块中名称相同的变量或函数,这时如果你试图使用os模块中的同名变量或函数,实际使用的将是你自己定义的内容。因此,你最后可能会碰到一个相当让人困惑的逻辑错误。标准库中我唯一推荐全盘导入的模块只有Tkinter。

  • 一次性从某个模块导入多个部分:
1
2
3
4
5
6
7
8
9
10
11
from os import path, walk, unlink
from os import uname, remove

# Or 使用圆括号
from os import (path, walk, unlink, uname,
remove, rename
)

# Or 使用续行符
from os import path, walk, unlink, uname, \
remove, rename

相对导入(PEP 328)

说明:终端模式不支持相对导入。

使用句点来决定如何相对导入其他包或模块。这么做的原因是为了避免偶然情况下导入标准库中的模块产生冲突。

相对导入适用于你最终要放入包中的代码。如果你编写了很多相关性强的代码,那么应该采用这种导入方式。你会发现PyPI上有很多流行的包也是采用了相对导入。还要注意一点,如果你想要跨越多个文件层级进行导入,只需要使用多个句点即可。不过,PEP 328建议相对导入的层级不要超过两层。

1
2
3
4
from . import subpakage1
from .. import subpakage2

from .module_x import xxx as yyy

如果你想在自己的代码中使用这个模块,那么你必须将其添加至Python的导入检索路径(import search path)。最简单的做法如下:

1
2
3
4
import sys

sys.path.append('/path/to/folder/containing/my-package')
import my_package

可选导入

如果你希望优先使用某个模块或包,但是同时也想在没有这个模块或包的情况下有备选,你就可以使用可选导入这种方式。这样做可以导入支持某个软件的多种版本或者实现性能提升。

1
2
3
4
5
6
7
8
9
try:
# For Python 3
from http.client import responses
except ImportError: # For Python 2.5-2.7
try:
from httplib import responses # NOQA
except ImportError: # For Python 2.4
from BaseHTTPServer import BaseHTTPRequestHandler as _BHRH
responses = dict([(k, v[0]) for k, v in _BHRH.responses.items()])

局部导入

当你在局部作用域中导入模块时,你执行的就是局部导入。如果你在Python脚本文件的顶部导入一个模块,那么你就是在将该模块导入至全局作用域,这意味着之后的任何函数或方法都可能访问该模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys  # global scope

def square_root(a):
# This import is into the square_root functions local scope
import math
return math.sqrt(a)

def my_pow(base_num, power):
return math.pow(base_num, power)

if __name__ == '__main__':
print(square_root(49))
print(my_pow(2, 3))

使用局部作用域的好处之一,是你使用的模块可能需要很长时间才能导入,如果是这样的话,将其放在某个不经常调用的函数中或许更加合理,而不是直接在全局作用域中导入。老实说,我几乎从没有使用过局部导入,主要是因为如果模块内部到处都有导入语句,会很难分辨出这样做的原因和用途。根据约定,所有的导入语句都应该位于模块的顶部。

注意事项

  • 循环导入(Circular Imports)

如果你创建两个模块,二者相互导入对方,那么就会出现循环导入。

此时如果你运行任意一个模块,都会引发 AttributeError。这是因为这两个模块都在试图导入对方。简单来说,模块a想要导入模块b,但是因为模块b也在试图导入模块a(这时正在执行),模块a将无法完成模块b的导入。我看过一些解决这个问题的破解方法(hack),但是一般来说,你应该做的是重构代码,避免发生这种情况。

  • 覆盖导入(Shadowed Imports)

当你创建的模块与标准库中的模块同名时,如果你导入这个模块,就会出现覆盖导入。

当你运行这个文件的时候,Python 解释器首先在当前运行脚本所处的的文件夹中查找名叫 math 的模块。

super() argument 1 must be type, not classobj

super() and all subclass/superclass stuff only works with new-style classes.
I recommend you get in the habit of always typing that (object) on any class definition to make sure it is a new-style class.

1
2
3
4
5
6
7
8
class OldStyle:
pass

class NewStyle(object):
pass

print type(OldStyle) # prints: <type 'classobj'>
print type(NewStyle) # prints <type 'type'>

Note that in Python 3.x, all classes are new-style.
You can still use the syntax from the old-style classes but you get a new-style class. So, in Python 3.x you won’t have this problem.

macOS 安装 MySQL-python 出错?

1
2
3
4
5
6
sudo pip install MySQL-python

# ...
IndexError: string index out of range
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/tmp/pip-install-NDWkMK/MySQL-python/

这是因为必须要先安装 MySQL Server:brew install mysql

参考