├── .gitattributes ├── .gitignore ├── Advanced-Features ├── README.md ├── context.md ├── generator.md └── iterator.md ├── Basic ├── README.md ├── character_encoding.md └── input_output.md ├── Class ├── README.md ├── class_and_object.md ├── inheritance_and_polymorphism.md ├── magic_method.md ├── metaclass.md ├── method.md ├── property.md ├── singleton.md ├── slots.md └── super.md ├── Conclusion ├── README.md ├── reference_material.md └── resource_recommendation.md ├── Datatypes ├── README.md ├── dict.md ├── list.md ├── set.md ├── string.md └── tuple.md ├── Exception └── README.md ├── File-Directory ├── README.md ├── binary_file_io.md ├── os.md └── text_file_io.md ├── Function ├── README.md ├── func_definition.md └── func_parameter.md ├── Functional ├── README.md ├── anonymous_func.md ├── closure.md ├── decorator.md ├── high_order_func.md ├── map_reduce_filter.md └── partial.md ├── HTTP ├── HTTP.md ├── README.md └── Requests.md ├── LICENSE ├── PYTHONroadmap.png ├── Process-Thread-Coroutine ├── README.md ├── coroutine.md ├── process.md ├── thread.md └── threadlocal.md ├── README.md ├── Regular-Expressions ├── README.md └── re.md ├── SUMMARY.md ├── Standard-Modules ├── README.md ├── argparse.md ├── base64.md ├── collections.md ├── datetime.md ├── hashlib.md ├── hmac.md └── itertools.md ├── Testing └── README.md ├── Third-Party-Modules ├── README.md ├── celery.md └── click.md ├── book.json ├── config.json ├── cover.png └── cover ├── background.jpg ├── cover.png └── logo.png /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md linguist-language=Python 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/* 3 | *.py[cod] 4 | .Ulysses* 5 | _book 6 | node_modules/* 7 | -------------------------------------------------------------------------------- /Advanced-Features/README.md: -------------------------------------------------------------------------------- 1 | # 高级特性 2 | 3 | 本章主要介绍: 4 | 5 | - [迭代器](./iterator.md) 6 | - [生成器](./generator.md) 7 | - [上下文管理器](./context.md) 8 | 9 | 10 | -------------------------------------------------------------------------------- /Advanced-Features/context.md: -------------------------------------------------------------------------------- 1 | # 上下文管理器 2 | 3 | 什么是上下文?其实我们可以简单地把它理解成环境。从一篇文章中抽出一句话,让你来理解,我们会说这是断章取义。为什么?因为我们压根就没考虑到这句话的上下文是什么。编程中的上下文也与此类似,比如『进程上下文』,指的是一个进程在执行的时候,CPU 的所有寄存器中的值、进程的状态以及堆栈上的内容等,当系统需要切换到其他进程时,系统会保留当前进程的上下文,也就是运行时的环境,以便再次执行该进程。 4 | 5 | 迭代器有[迭代器协议(Iterator Protocol)](./iterator.md),上下文管理器(Context manager)也有上下文管理协议(Context Management Protocol)。 6 | 7 | - **上下文管理器协议**,是指要实现对象的 `__enter__()` 和 `__exit__()` 方法。 8 | - **上下文管理器**也就是支持上下文管理器协议的对象,也就是实现了 `__enter__()` 和 `__exit__()` 方法。 9 | 10 | 这里先构造一个简单的上下文管理器的例子,以理解 `__enter__()` 和 `__exit__()` 方法。 11 | 12 | ```python 13 | from math import sqrt, pow 14 | 15 | class Point(object): 16 | def __init__(self, x, y): 17 | print 'initialize x and y' 18 | self.x, self.y = x, y 19 | 20 | def __enter__(self): 21 | print "Entering context" 22 | return self 23 | 24 | def __exit__(self, type, value, traceback): 25 | print "Exiting context" 26 | 27 | def get_distance(self): 28 | distance = sqrt(pow(self.x, 2) + pow(self.y, 2)) 29 | return distance 30 | ``` 31 | 32 | 上面的代码定义了一个 `Point` 类,并实现了 `__enter__()` 和 `__exit__()` 方法,我们还定义了 `get_distance` 方法,用于返回点到原点的距离。 33 | 34 | 通常,我们使用 `with` 语句调用上下文管理器: 35 | 36 | ```python 37 | with Point(3, 4) as pt: 38 | print 'distance: ', pt.get_distance() 39 | 40 | # output 41 | initialize x and y # 调用了 __init__ 方法 42 | Entering context # 调用了 __enter__ 方法 43 | distance: 5.0 # 调用了 get_distance 方法 44 | Exiting context # 调用了 __exit__ 方法 45 | ``` 46 | 47 | 上面的 `with` 语句执行过程如下: 48 | 49 | - Point(3, 4) 生成了一个上下文管理器; 50 | - 调用上下文管理器的 `__enter__()` 方法,并将 `__enter__()` 方法的返回值赋给 as 字句中的变量 pt; 51 | - 执行**语句体**(指 with 语句包裹起来的代码块)内容,输出 distance; 52 | - 不管执行过程中是否发生异常,都执行上下文管理器的 `__exit__()` 方法。`__exit__()` 方法负责执行『清理』工作,如释放资源,关闭文件等。如果执行过程没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 `__exit__(None, None, None) `;如果执行过程中出现异常,则使用 `sys.exc_info` 得到的异常信息为参数调用 `__exit__(exc_type, exc_value, exc_traceback)`; 53 | - 出现异常时,如果 `__exit__(type, value, traceback)` 返回 False 或 None,则会重新抛出异常,让 with 之外的语句逻辑来处理异常;如果返回 True,则忽略异常,不再对异常进行处理; 54 | 55 | 上面的 with 语句执行过程没有出现异常,我们再来看出现异常的情形: 56 | 57 | ```python 58 | with Point(3, 4) as pt: 59 | pt.get_length() # 访问了对象不存在的方法 60 | 61 | # output 62 | initialize x and y 63 | Entering context 64 | Exiting context 65 | --------------------------------------------------------------------------- 66 | AttributeError Traceback (most recent call last) 67 | in () 68 | 1 with Point(3, 4) as pt: 69 | ----> 2 pt.get_length() 70 | 71 | AttributeError: 'Point' object has no attribute 'get_length' 72 | ``` 73 | 74 | 在我们的例子中,`__exit__` 方法返回的是 None(如果没有 return 语句那么方法会返回 None)。因此,with 语句抛出了那个异常。我们对 `__exit__` 方法做一些改动,让它返回 True。 75 | 76 | ```python 77 | from math import sqrt, pow 78 | 79 | class Point(object): 80 | def __init__(self, x, y): 81 | print 'initialize x and y' 82 | self.x, self.y = x, y 83 | 84 | def __enter__(self): 85 | print "Entering context" 86 | return self 87 | 88 | def __exit__(self, type, value, traceback): 89 | print "Exception has been handled" 90 | print "Exiting context" 91 | return True 92 | 93 | def get_distance(self): 94 | distance = sqrt(pow(self.x, 2) + pow(self.y,2 )) 95 | return distance 96 | 97 | with Point(3, 4) as pt: 98 | pt.get_length() # 访问了对象不存在的方法 99 | 100 | # output 101 | initialize x and y 102 | Entering context 103 | Exception has been handled 104 | Exiting context 105 | ``` 106 | 107 | 可以看到,由于 `__exit__` 方法返回了 True,因此没有异常会被 with 语句抛出。 108 | 109 | # 内建对象使用 with 语句 110 | 111 | 除了自定义上下文管理器,Python 中也提供了一些内置对象,可直接用于 with 语句中,比如最常见的文件操作。 112 | 113 | 传统的文件操作经常使用 `try/finally` 的方式,比如: 114 | 115 | ```python 116 | file = open('somefile', 'r') 117 | try: 118 | for line in file: 119 | print line 120 | finally: 121 | file.close() # 确保关闭文件 122 | ``` 123 | 124 | 将上面的代码改用 with 语句: 125 | 126 | ```python 127 | with open('somefile', 'r') as file: 128 | for line in file: 129 | print line 130 | ``` 131 | 132 | 可以看到,通过使用 with,代码变得很简洁,而且即使处理过程发生异常,with 语句也会确保我们的文件被关闭。 133 | 134 | # contextlib 模块 135 | 136 | 除了在类中定义 `__enter__` 和 `__exit__` 方法来实现上下文管理器,我们还可以通过生成器函数(也就是带有 yield 的函数)结合装饰器来实现上下文管理器,Python 中自带的 contextlib 模块就是做这个的。 137 | 138 | contextlib 模块提供了三个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。其中,contextmanager 是一个装饰器,用于装饰生成器函数,并返回一个上下文管理器。需要注意的是,被装饰的生成器函数只能产生一个值,否则会产生 RuntimeError 异常。 139 | 140 | 下面我们看一个简单的例子: 141 | 142 | ```python 143 | from contextlib import contextmanager 144 | 145 | @contextmanager 146 | def point(x, y): 147 | print 'before yield' 148 | yield x * x + y * y 149 | print 'after yield' 150 | 151 | with point(3, 4) as value: 152 | print 'value is: %s' % value 153 | 154 | # output 155 | before yield 156 | value is: 25 157 | after yield 158 | ``` 159 | 160 | 可以看到,yield 产生的值赋给了 as 子句中的 value 变量。 161 | 162 | 另外,需要强调的是,虽然通过使用 contextmanager 装饰器,我们可以不必再编写 `__enter__` 和 `__exit__` 方法,但是『获取』和『清理』资源的操作仍需要我们自己编写:『获取』资源的操作定义在 yield 语句之前,『释放』资源的操作定义在 yield 语句之后。 163 | 164 | # 小结 165 | 166 | - 上下文管理器是支持上下文管理协议的对象,也就是实现了 `__enter__` 和 `__exit__` 方法。 167 | - 通常,我们使用 `with` 语句调用上下文管理器。with 语句尤其适用于对资源进行访问的场景,确保执行过程中出现异常情况时也可以对资源进行回收,比如自动关闭文件等。 168 | - `__enter__` 方法在 with 语句体执行前调用,with 语句将该方法的返回值赋给 as 字句中的变量,如果有 as 字句的话。 169 | - `__exit__` 方法在退出`运行时上下文`时被调用,它负责执行『清理』工作,比如关闭文件,释放资源等。如果退出时没有发生异常,则 `__exit__` 的三个参数,即 type, value 和 traceback 都为 None。如果发生异常,返回 True 表示不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。 170 | 171 | # 参考资料 172 | 173 | - [编程中什么是「Context(上下文)」? - 知乎](https://www.zhihu.com/question/26387327) 174 | - [浅谈 Python 的 with 语句](https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/) 175 | - [上下文管理器 · Python进阶](https://eastlakeside.gitbooks.io/interpy-zh/content/context_managers/) 176 | 177 | 178 | -------------------------------------------------------------------------------- /Advanced-Features/generator.md: -------------------------------------------------------------------------------- 1 | 生成器 2 | ==== 3 | 4 | 生成器(generator)也是一种迭代器,在每次迭代时返回一个值,直到抛出 `StopIteration` 异常。它有两种构造方式: 5 | 6 | - 生成器表达式 7 | 8 | 和列表推导式的定义类似,生成器表达式使用 `()` 而不是 `[]`,比如: 9 | 10 | ``` 11 | numbers = (x for x in range(5)) # 注意是(),而不是[] 12 | for num in numbers: 13 | print num 14 | ``` 15 | 16 | - 生成器函数 17 | 18 | 含有 `yield` 关键字的函数,调用该函数时会返回一个生成器。 19 | 20 | 本文主要讲**生成器函数**。 21 | 22 | # 生成器函数 23 | 24 | 先来看一个简单的例子: 25 | 26 | ```python 27 | >>> def generator_function(): 28 | ... print 'hello 1' 29 | ... yield 1 30 | ... print 'hello 2' 31 | ... yield 2 32 | ... print 'hello 3' 33 | >>> 34 | >>> g = generator_function() # 函数没有立即执行,而是返回了一个生成器,当然也是一个迭代器 35 | >>> g.next() # 当使用 next() 方法,或使用 next(g) 的时候开始执行,遇到 yield 暂停 36 | hello 1 37 | 1 38 | >>> g.next() # 从原来暂停的地方继续执行 39 | hello 2 40 | 2 41 | >>> g.next() # 从原来暂停的地方继续执行,没有 yield,抛出异常 42 | hello 3 43 | Traceback (most recent call last): 44 | File "", line 1, in 45 | StopIteration 46 | ``` 47 | 48 | 可以看到,上面的函数没有使用 `return` 语句返回值,而是使用了 `yield`『生出』一个值。一个带有 `yield` 的函数就是一个生成器函数,当我们使用 `yield` 时,它帮我们自动创建了 `__iter__()` 和 `next()` 方法,而且在没有数据时,也会抛出 `StopIteration` 异常,也就是我们不费吹灰之力就获得了一个迭代器,非常简洁和高效。 49 | 50 | 带有 `yield` 的函数执行过程比较特别: 51 | 52 | - 调用该函数的时候不会立即执行代码,而是返回了一个生成器对象; 53 | - 当使用 `next()` (在 for 循环中会自动调用 `next()`) 作用于返回的生成器对象时,函数开始执行,在遇到 `yield` 的时候会『暂停』,并返回当前的迭代值; 54 | - 当再次使用 `next()` 的时候,函数会从原来『暂停』的地方继续执行,直到遇到 `yield` 语句,如果没有 `yield` 语句,则抛出异常; 55 | 56 | 整个过程看起来就是不断地 `执行->中断->执行->中断` 的过程。一开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象;然后,当我们使用 `next()` 作用于它的时候,它开始执行,遇到 `yield` 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的变量值,也就是执行时的上下文环境被保留起来;当再次使用 `next()` 的时候,从原来中断的地方继续执行,直至遇到 `yield`,如果没有 `yield`,则抛出异常。 57 | 58 | **简而言之,就是 `next` 使函数执行,`yield` 使函数暂停。** 59 | 60 | # 例子 61 | 62 | 看一个 Fibonacci 数列的例子,如果使用自定义迭代器的方法,是这样的: 63 | 64 | ```python 65 | >>> class Fib(object): 66 | ... def __init__(self): 67 | ... self.a, self.b = 0, 1 68 | ... def __iter__(self): 69 | ... return self 70 | ... def next(self): 71 | ... self.a, self.b = self.b, self.a + self.b 72 | ... return self.a 73 | ... 74 | >>> f = Fib() 75 | >>> for item in f: 76 | ... if item > 10: 77 | ... break 78 | ... print item 79 | ... 80 | 1 81 | 1 82 | 2 83 | 3 84 | 5 85 | 8 86 | ``` 87 | 88 | 而使用生成器的方法,是这样的: 89 | 90 | ```python 91 | >>> def fib(): 92 | ... a, b = 0, 1 93 | ... while True: 94 | ... a, b = b, a + b 95 | ... yield a 96 | ... 97 | >>> f = fib() 98 | >>> for item in f: 99 | ... if item > 10: 100 | ... break 101 | ... print item 102 | ... 103 | 1 104 | 1 105 | 2 106 | 3 107 | 5 108 | 8 109 | ``` 110 | 111 | 可以看到,使用生成器的方法非常简洁,不用自定义 `__iter__()` 和 `next()` 方法。 112 | 113 | 另外,在处理大文件的时候,我们可能无法一次性将其载入内存,这时可以通过构造固定长度的缓冲区,来不断读取文件内容。有了 `yield`,我们就不用自己实现读文件的迭代器了,比如下面的实现: 114 | 115 | ```python 116 | def read_in_chunks(file_object, chunk_size=1024): 117 | """Lazy function (generator) to read a file piece by piece. 118 | Default chunk size: 1k.""" 119 | while True: 120 | data = file_object.read(chunk_size) 121 | if not data: 122 | break 123 | yield data 124 | 125 | f = open('really_big_file.dat') 126 | for piece in read_in_chunks(f): 127 | process_data(piece) 128 | ``` 129 | 130 | # 进阶使用 131 | 132 | 我们除了能对生成器进行迭代使它返回值外,还能: 133 | 134 | - 使用 `send()` 方法给它发送消息; 135 | - 使用 `throw()` 方法给它发送异常; 136 | - 使用 `close()` 方法关闭生成器; 137 | 138 | ## send() 方法 139 | 140 | 看一个简单的例子: 141 | 142 | ``` 143 | >>> def generator_function(): 144 | ... value1 = yield 0 145 | ... print 'value1 is ', value1 146 | ... value2 = yield 1 147 | ... print 'value2 is ', value2 148 | ... value3 = yield 2 149 | ... print 'value3 is ', value3 150 | ... 151 | >>> g = generator_function() 152 | >>> g.next() # 调用 next() 方法开始执行,返回 0 153 | 0 154 | >>> g.send(2) 155 | value1 is 2 156 | 1 157 | >>> g.send(3) 158 | value2 is 3 159 | 2 160 | >>> g.send(4) 161 | value3 is 4 162 | Traceback (most recent call last): 163 | File "", line 1, in 164 | StopIteration 165 | ``` 166 | 167 | 在上面的代码中,我们先调用 `next()` 方法,使函数开始执行,代码执行到 `yield 0` 的时候暂停,返回了 0;接着,我们执行了 `send()` 方法,它会恢复生成器的运行,并将发送的值赋给上次中断时 yield 表达式的执行结果,也就是 value1,这时控制台打印出 value1 的值,并继续执行,直到遇到 yield 后暂停,此时返回 1;类似地,再次执行 `send()` 方法,将值赋给 value2。 168 | 169 | 简单地说,`send()` 方法就是 `next()` 的功能,加上传值给 `yield`。 170 | 171 | ## throw() 方法 172 | 173 | 除了可以给生成器传值,我们还可以给它传异常,比如: 174 | 175 | ```python 176 | >>> def generator_function(): 177 | ... try: 178 | ... yield 'Normal' 179 | ... except ValueError: 180 | ... yield 'Error' 181 | ... finally: 182 | ... print 'Finally' 183 | ... 184 | >>> g = generator_function() 185 | >>> g.next() 186 | 'Normal' 187 | >>> g.throw(ValueError) 188 | 'Error' 189 | >>> g.next() 190 | Finally 191 | Traceback (most recent call last): 192 | File "", line 1, in 193 | StopIteration 194 | ``` 195 | 196 | 可以看到,`throw()` 方法向生成器函数传递了 `ValueError` 异常,此时代码进入 except ValueError 语句,遇到 yield 'Error',暂停并返回 Error 字符串。 197 | 198 | 简单的说,`throw()` 就是 `next()` 的功能,加上传异常给 `yield`。 199 | 200 | ## close() 方法 201 | 202 | 我们可以使用 `close()` 方法来关闭一个生成器。生成器被关闭后,再次调用 next() 方法,不管能否遇到 yield 关键字,都会抛出 StopIteration 异常,比如: 203 | 204 | ```python 205 | >>> def generator_function(): 206 | ... yield 1 207 | ... yield 2 208 | ... yield 3 209 | ... 210 | >>> g = generator_function() 211 | >>> 212 | >>> g.next() 213 | 1 214 | >>> g.close() # 关闭生成器 215 | >>> g.next() 216 | Traceback (most recent call last): 217 | File "", line 1, in 218 | StopIteration 219 | ``` 220 | 221 | # 小结 222 | 223 | - yield 把函数变成了一个生成器。 224 | - 生成器函数的执行过程看起来就是不断地 `执行->中断->执行->中断` 的过程。 225 | - 一开始,调用生成器函数的时候,函数不会立即执行,而是返回一个生成器对象; 226 | - 然后,当我们使用 `next()` 作用于它的时候,它开始执行,遇到 `yield` 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中断的位置和所有的数据,也就是执行时的上下文环境被保留起来; 227 | - 当再次使用 `next()` 的时候,从原来中断的地方继续执行,直至遇到 `yield`,如果没有 `yield`,则抛出异常。 228 | 229 | 230 | # 参考资料 231 | 232 | - [Python yield 使用浅析](https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/) 233 | - [谈谈Python的生成器 – 思诚之道](http://www.bjhee.com/python-yield.html) 234 | - [Function vs Generator in Python](https://code-maven.com/function-vs-generator-in-python) 235 | - [Lazy Method for Reading Big File in Python? - Stack Overflow](http://stackoverflow.com/questions/519633/lazy-method-for-reading-big-file-in-python) 236 | 237 | 238 | -------------------------------------------------------------------------------- /Advanced-Features/iterator.md: -------------------------------------------------------------------------------- 1 | 迭代器 (Iterator) 2 | ==== 3 | 4 | # 迭代和可迭代 5 | 6 | 迭代器这个概念在很多语言中(比如 C++,Java)都是存在的,但是不同语言实现迭代器的方式各不相同。**在 Python 中,迭代器是指遵循迭代器协议(iterator protocol)的对象。**至于什么是迭代器协议,稍后自然会说明。为了更好地理解迭代器,我先介绍和迭代器相关的两个概念: 7 | 8 | - 迭代(Iteration) 9 | - 可迭代对象(Iterable) 10 | 11 | 你可能会觉得这是在玩文字游戏,但这确实是要搞清楚的。 12 | 13 | > 当我们用一个循环(比如 for 循环)来遍历容器(比如列表,元组)中的元素时,这种遍历的过程就叫***迭代***。 14 | 15 | 在 Python 中,我们使用 `for...in...` 进行迭代。比如,遍历一个 list: 16 | 17 | ```python 18 | numbers = [1, 2, 3, 4] 19 | for num in numbers: 20 | print num 21 | ``` 22 | 23 | 像上面这种可以使用 `for` 循环进行迭代的对象,就是可迭代对象,它的定义如下: 24 | 25 | > 含有 `__iter__()` 方法或 `__getitem__()` 方法的对象称之为***可迭代对象***。 26 | 27 | 我们可以使用 Python 内置的 `hasattr()` 函数来判断一个对象是不是可迭代的: 28 | 29 | ```python 30 | >>> hasattr((), '__iter__') 31 | True 32 | >>> hasattr([], '__iter__') 33 | True 34 | >>> hasattr({}, '__iter__') 35 | True 36 | >>> hasattr(123, '__iter__') 37 | False 38 | >>> hasattr('abc', '__iter__') 39 | False 40 | >>> hasattr('abc', '__getitem__') 41 | True 42 | ``` 43 | 44 | 另外,我们也可使用 `isinstance()` 进行判断: 45 | 46 | ```python 47 | >>> from collections import Iterable 48 | 49 | >>> isinstance((), Iterable) # 元组 50 | True 51 | >>> isinstance([], Iterable) # 列表 52 | True 53 | >>> isinstance({}, Iterable) # 字典 54 | True 55 | >>> isinstance('abc', Iterable) # 字符串 56 | True 57 | >>> isinstance(100, Iterable) # 数字 58 | False 59 | ``` 60 | 61 | 可见,我们熟知的字典(dict)、元组(tuple)、集合(set)和字符串对象都是可迭代的。 62 | 63 | # 迭代器 64 | 65 | 现在,让我们看看什么是迭代器(Iterator)。上文说过,**迭代器是指遵循迭代器协议(iterator protocol)的对象。**从这句话我们可以知道,迭代器是一个对象,但比较特别,它需要遵循迭代器协议,那什么是迭代器协议呢? 66 | 67 | > ***迭代器协议(iterator protocol)***是指要实现对象的 `__iter()__` 和 `next()` 方法(注意:Python3 要实现 `__next__()` 方法),其中,`__iter()__` 方法返回迭代器对象本身,`next()` 方法返回容器的下一个元素,在没有后续元素时抛出 `StopIteration` 异常。 68 | 69 | 接下来讲讲迭代器的例子,有什么常见的迭代器呢?列表是迭代器吗?字典是迭代器吗?我们使用 `hasattr()` 进行判断: 70 | 71 | ``` 72 | >>> hasattr((1, 2, 3), '__iter__') 73 | True 74 | >>> hasattr((1, 2, 3), 'next') # 有 __iter__ 方法但是没有 next 方法,不是迭代器 75 | False 76 | >>> 77 | >>> hasattr([1, 2, 3], '__iter__') 78 | True 79 | >>> hasattr([1, 2, 3], 'next') 80 | False 81 | >>> 82 | >>> hasattr({'a': 1, 'b': 2}, '__iter__') 83 | True 84 | >>> hasattr({'a': 1, 'b': 2}, 'next') 85 | False 86 | ``` 87 | 88 | 同样,我们也可以使用 `isinstance()` 进行判断: 89 | 90 | ``` 91 | >>> from collections import Iterator 92 | >>> 93 | >>> isinstance((), Iterator) 94 | False 95 | >>> isinstance([], Iterator) 96 | False 97 | >>> isinstance({}, Iterator) 98 | False 99 | >>> isinstance('', Iterator) 100 | False 101 | >>> isinstance(123, Iterator) 102 | False 103 | ``` 104 | 105 | 可见,**虽然元组、列表和字典等对象是可迭代的,但它们却不是迭代器!**对于这些可迭代对象,可以使用 Python 内置的 `iter()` 函数获得它们的迭代器对象,看下面的使用: 106 | 107 | ``` 108 | >>> from collections import Iterator 109 | >>> isinstance(iter([1, 2, 3]), Iterator) # 使用 iter() 函数,获得迭代器对象 110 | True 111 | >>> isinstance(iter('abc'), Iterator) 112 | True 113 | >>> 114 | >>> my_str = 'abc' 115 | >>> next(my_str) # my_str 不是迭代器,不能使用 next(),因此出错 116 | --------------------------------------------------------------------------- 117 | TypeError Traceback (most recent call last) 118 | in () 119 | ----> 1 next(my_str) 120 | 121 | TypeError: str object is not an iterator 122 | >>> 123 | >>> my_iter = iter(my_str) # 获得迭代器对象 124 | >>> isinstance(my_iter, Iterator) 125 | True 126 | >>> next(my_iter) # 可使用内置的 next() 函数获得下一个元素 127 | 'a' 128 | ``` 129 | 130 | 事实上,Python 的 `for` 循环就是先通过内置函数 `iter()` 获得一个迭代器,然后再不断调用 `next()` 函数实现的,比如: 131 | 132 | ``` 133 | for x in [1, 2, 3]: 134 | print i 135 | ``` 136 | 137 | 等价于 138 | 139 | ```python 140 | # 获得 Iterator 对象 141 | it = iter([1, 2, 3]) 142 | 143 | # 循环 144 | while True: 145 | try: 146 | # 获得下一个值 147 | x = next(it) 148 | print x 149 | except StopIteration: 150 | # 没有后续元素,退出循环 151 | break 152 | ``` 153 | 154 | # 斐波那契数列迭代器 155 | 156 | 现在,让我们来自定义一个迭代器:斐波那契(Fibonacci)数列迭代器。根据迭代器的定义,我们需要实现 `__iter()__` 和 `next()` 方法(在 Python3 中是 `__next__()` 方法)。先看代码: 157 | 158 | ```python 159 | # -*- coding: utf-8 -*- 160 | 161 | from collections import Iterator 162 | 163 | class Fib(object): 164 | def __init__(self): 165 | self.a, self.b = 0, 1 166 | 167 | # 返回迭代器对象本身 168 | def __iter__(self): 169 | return self 170 | 171 | # 返回容器下一个元素 172 | def next(self): 173 | self.a, self.b = self.b, self.a + self.b 174 | return self.a 175 | 176 | def main(): 177 | fib = Fib() # fib 是一个迭代器 178 | print 'isinstance(fib, Iterator): ', isinstance(fib, Iterator) 179 | 180 | for i in fib: 181 | if i > 10: 182 | break 183 | print i 184 | 185 | if __name__ == '__main__': 186 | main() 187 | ``` 188 | 189 | 在上面的代码中,我们定义了一个 Fib 类,用于生成 Fibonacci 数列。在类的实现中,我们定义了 `__iter__` 方法,它返回对象本身,这个方法会在遍历时被 Python 内置的 `iter()` 函数调用,返回一个迭代器。类中的 `next()` 方法用于返回容器的下一个元素,当使用 `for` 循环进行遍历的时候,就会使用 Python 内置的 `next()` 函数调用对象的 `next` 方法(在 Python3 中是 `__next__` 方法)对迭代器进行遍历。 190 | 191 | 运行上面的代码,可得到如下结果: 192 | 193 | ``` 194 | isinstance(fib, Iterator): True 195 | 1 196 | 1 197 | 2 198 | 3 199 | 5 200 | 8 201 | ``` 202 | 203 | # 小结 204 | 205 | - 元组、列表、字典和字符串对象是可迭代的,但不是迭代器,不过我们可以通过 `iter()` 函数获得一个迭代器对象; 206 | - Python 的 `for` 循环实质上是先通过内置函数 `iter()` 获得一个迭代器,然后再不断调用 `next()` 函数实现的; 207 | - 自定义迭代器需要实现对象的 `__iter()__` 和 `next()` 方法(注意:Python3 要实现 `__next__()` 方法),其中,`__iter()__` 方法返回迭代器对象本身,`next()` 方法返回容器的下一个元素,在没有后续元素时抛出 `StopIteration` 异常。 208 | 209 | # 参考资料 210 | 211 | - [Callback or Iterator in Python](https://code-maven.com/callback-or-iterator-in-python) 212 | - [迭代器 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143178254193589df9c612d2449618ea460e7a672a366000) 213 | 214 | 215 | -------------------------------------------------------------------------------- /Basic/README.md: -------------------------------------------------------------------------------- 1 | # 基础 2 | 3 | 本章主要介绍两个方面的内容: 4 | 5 | - [字符编码](./character_encoding.md) 6 | - [输入和输出](./input_output.md) 7 | 8 | 其中,**字符编码**的概念很重要,不管你用的是 Python2 还是 Python3,亦或是 C++ 等其他编程语言,希望读者厘清这个概念,当遇到 UnicodeEncodeError 和 UnicodeDecodeError 时才能从容应对,而不是到处查找资料。 9 | 10 | 11 | -------------------------------------------------------------------------------- /Basic/character_encoding.md: -------------------------------------------------------------------------------- 1 | # 字符编码 2 | 3 | 字符编码是计算机编程中不可回避的问题,不管你用 Python2 还是 Python3,亦或是 C++, Java 等,我都觉得非常有必要厘清计算机中的字符编码概念。本文主要分以下几个部分介绍: 4 | 5 | - 基本概念 6 | - 常见字符编码简介 7 | - Python 的默认编码 8 | - Python2 中的字符类型 9 | - UnicodeEncodeError & UnicodeDecodeError 根源 10 | 11 | # 基本概念 12 | 13 | - 字符(Character) 14 | 15 | 在电脑和电信领域中,**字符是一个信息单位,它是各种文字和符号的总称**,包括各国家文字、标点符号、图形符号、数字等。比如,一个汉字,一个英文字母,一个标点符号等都是一个字符。 16 | 17 | - 字符集(Character set) 18 | 19 | **字符集是字符的集合**。字符集的种类较多,每个字符集包含的字符个数也不同。比如,常见的字符集有 ASCII 字符集、GB2312 字符集、Unicode 字符集等,其中,ASCII 字符集共有 128 个字符,包含可显示字符(比如英文大小写字符、阿拉伯数字)和控制字符(比如空格键、回车键);GB2312 字符集是中国国家标准的简体中文字符集,包含简化汉字、一般符号、数字等;Unicode 字符集则包含了世界各国语言中使用到的所有字符, 20 | 21 | - 字符编码(Character encoding) 22 | 23 | **字符编码,是指对于字符集中的字符,将其编码为特定的二进制数**,以便计算机处理。常见的字符编码有 ASCII 编码,UTF-8 编码,GBK 编码等。一般而言,**字符集**和**字符编码**往往被认为是同义的概念,比如,对于字符集 ASCII,它除了有「字符的集合」这层含义外,同时也包含了「编码」的含义,也就是说,**ASCII 既表示了字符集也表示了对应的字符编码**。 24 | 25 | 下面我们用一个表格做下总结: 26 | 27 | | 概念 | 概念描述 | 举例 | 28 | | --- | --- | --- | 29 | | 字符 | 一个信息单位,各种文字和符号的总称 | ‘中’, ‘a', ‘1', '$', ‘¥’, ...  | 30 | | 字符集 | 字符的集合 | ASCII 字符集, GB2312 字符集, Unicode 字符集 | 31 | | 字符编码 | 将字符集中的字符,编码为特定的二进制数 | ASCII 编码,GB2312 编码,Unicode 编码 | 32 | | 字节 | 计算机中存储数据的单元,一个 8 位(bit)的二进制数 | 0x01, 0x45, ... | 33 | 34 | # 常见字符编码简介 35 | 36 | 常见的字符编码有 ASCII 编码,GBK 编码,Unicode 编码和 UTF-8 编码等等。这里,我们主要介绍 ASCII、Unicode 和 UTF-8。 37 | 38 | ## ASCII 39 | 40 | 计算机是在美国诞生的,人家用的是英语,而在英语的世界里,不过就是英文字母,数字和一些普通符号的组合而已。 41 | 42 | 在 20 世纪 60 年代,美国制定了一套字符编码方案,规定了英文字母,数字和一些普通符号跟二进制的转换关系,被称为 ASCII (American Standard Code for Information Interchange,美国信息互换标准编码) 码。 43 | 44 | 比如,大写英文字母 A 的二进制表示是 01000001(十进制 65),小写英文字母 a 的二进制表示是 01100001 (十进制 97),空格 SPACE 的二进制表示是 00100000(十进制 32)。 45 | 46 | ## Unicode 47 | 48 | ASCII 码只规定了 128 个字符的编码,这在美国是够用的。可是,计算机后来传到了欧洲,亚洲,乃至世界各地,而世界各国的语言几乎是完全不一样的,用 ASCII 码来表示其他语言是远远不够的,所以,不同的国家和地区又制定了自己的编码方案,比如中国大陆的 GB2312 编码 和 GBK 编码等,日本的 Shift_JIS 编码等等。 49 | 50 | 虽然各个国家和地区可以制定自己的编码方案,但不同国家和地区的计算机在数据传输的过程中就会出现各种各样的乱码(mojibake),这无疑是个灾难。 51 | 52 | 怎么办?想法也很简单,就是将全世界所有的语言统一成一套编码方案,这套编码方案就叫 Unicode,**它为每种语言的每个字符设定了独一无二的二进制编码**,这样就可以跨语言,跨平台进行文本处理了,是不是很棒! 53 | 54 | Unicode 1.0 版诞生于 1991 年 10 月,至今它仍在不断增修,每个新版本都会加入更多新的字符,目前最新的版本为 2016 年 6 月 21 日公布的 9.0.0。 55 | 56 | Unicode 标准使用十六进制数字,而且在数字前面加上前缀 `U+`,比如,大写字母「A」的 unicode 编码为 `U+0041`,汉字「严」的 unicode 编码为 `U+4E25`。更多的符号对应表,可以查询 [unicode.org](http://www.unicode.org/),或者专门的[汉字对应表](http://www.chi2ko.com/tool/CJK.htm)。 57 | 58 | ## UTF-8 59 | 60 | Unicode 看起来已经很完美了,实现了大一统。但是,Unicode 却存在一个很大的问题:资源浪费。 61 | 62 | 为什么这么说呢?原来,Unicode 为了能表示世界各国所有文字,一开始用两个字节,后来发现两个字节不够用,又用了四个字节。比如,汉字「严」的 unicode 编码是十六进制数 `4E25`,转换成二进制有十五位,即 100111000100101,因此至少需要两个字节才能表示这个汉字,但是对于其他的字符,就可能需要三个或四个字节,甚至更多。 63 | 64 | 这时,问题就来了,如果以前的 ASCII 字符集也用这种方式来表示,那岂不是很浪费存储空间。比如,大写字母「A」的二进制编码为 01000001,它只需要一个字节就够了,如果 unicode 统一使用三个字节或四个字节来表示字符,那「A」的二进制编码的前面几个字节就都是 `0`,这是很浪费存储空间的。 65 | 66 | 为了解决这个问题,在 Unicode 的基础上,人们实现了 UTF-16, UTF-32 和 UTF-8。下面只说一下 UTF-8。 67 | 68 | UTF-8 (8-bit Unicode Transformation Format) 是一种针对 Unicode 的可变长度字符编码,它使用一到四个字节来表示字符,例如,ASCII 字符继续使用一个字节编码,阿拉伯文、希腊文等使用两个字节编码,常用汉字使用三个字节编码,等等。 69 | 70 | 因此,我们说,**UTF-8 是 Unicode 的实现方式之一**,其他实现方式还包括 UTF-16(字符用两个或四个字节表示)和 UTF-32(字符用四个字节表示)。 71 | 72 | # Python 的默认编码 73 | 74 | Python2 的默认编码是 ascii,Python3 的默认编码是 utf-8,可以通过下面的方式获取: 75 | 76 | - Python2 77 | 78 | ```python 79 | Python 2.7.11 (default, Feb 24 2016, 10:48:05) 80 | [GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin 81 | Type "help", "copyright", "credits" or "license" for more information. 82 | >>> import sys 83 | >>> sys.getdefaultencoding() 84 | 'ascii' 85 | ``` 86 | 87 | - Python3 88 | 89 | ```python 90 | Python 3.5.2 (default, Jun 29 2016, 13:43:58) 91 | [GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.31)] on darwin 92 | Type "help", "copyright", "credits" or "license" for more information. 93 | >>> import sys 94 | >>> sys.getdefaultencoding() 95 | 'utf-8' 96 | ``` 97 | 98 | # Python2 中的字符类型 99 | 100 | Python2 中有两种和字符串相关的类型:str 和 unicode,它们的父类是 basestring。其中,str 类型的字符串有多种编码方式,默认是 ascii,还有 gbk,utf-8 等,unicode 类型的字符串使用 `u'...'` 的形式来表示,下面的图展示了 str 和 unicode 之间的关系: 101 | 102 | ![sm.ms](https://ooo.0o0.ooo/2016/11/16/582c111e3fa73.png) 103 | 104 | 两种字符串的相互转换概括如下: 105 | 106 | - 把 UTF-8 编码表示的字符串 'xxx' 转换为 Unicode 字符串 u'xxx' 用 `decode('utf-8')` 方法: 107 | 108 | ```python 109 | >>> '中文'.decode('utf-8') 110 | u'\u4e2d\u6587' 111 | ``` 112 | 113 | - 把 u'xxx' 转换为 UTF-8 编码的 'xxx' 用 `encode('utf-8')` 方法: 114 | 115 | ```python 116 | >>> u'中文'.encode('utf-8') 117 | '\xe4\xb8\xad\xe6\x96\x87' 118 | ``` 119 | 120 | # UnicodeEncodeError & UnicodeDecodeError 根源 121 | 122 | 用 Python2 编写程序的时候经常会遇到 UnicodeEncodeError 和 UnicodeDecodeError,它们出现的根源就是**如果代码里面混合使用了 str 类型和 unicode 类型的字符串,Python 会默认使用 ascii 编码尝试对 unicode 类型的字符串编码 (encode),或对 str 类型的字符串解码 (decode),这时就很可能出现上述错误**。 123 | 124 | 下面有两个常见的场景,我们最好牢牢记住: 125 | 126 | - 在进行同时包含 str 类型和 unicode 类型的字符串操作时,Python2 一律都把 str 解码(decode)成 unicode 再运算,这时就很容易出现 UnicodeDecodeError。 127 | 128 | 让我们看看例子: 129 | 130 | ```python 131 | >>> s = '你好' # str 类型, utf-8 编码 132 | >>> u = u'世界' # unicode 类型 133 | >>> s + u # 会进行隐式转换,即 s.decode('ascii') + u 134 | Traceback (most recent call last): 135 | File "", line 1, in 136 | UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128) 137 | ``` 138 | 139 | 为了避免出错,我们就需要显示指定使用 'utf-8' 进行解码,如下: 140 | 141 | ``` 142 | >>> s = '你好' # str 类型,utf-8 编码 143 | >>> u = u'世界' 144 | >>> 145 | >>> s.decode('utf-8') + u # 显示指定 'utf-8' 进行转换 146 | u'\u4f60\u597d\u4e16\u754c' # 注意这不是错误,这是 unicode 字符串 147 | ``` 148 | 149 | - 如果函数或类等对象接收的是 str 类型的字符串,但你传的是 unicode,Python2 会默认使用 ascii 将其编码成 str 类型再运算,这时就很容易出现 UnicodeEncodeError。 150 | 151 | 让我们看看例子: 152 | 153 | ```python 154 | >>> u_str = u'你好' 155 | >>> str(u_str) 156 | Traceback (most recent call last): 157 | File "", line 1, in 158 | UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128) 159 | ``` 160 | 161 | 在上面的代码中,u_str 是一个 unicode 类型的字符串,由于 `str()` 的参数只能是 str 类型,此时 Python 会试图使用 ascii 将其编码成 ascii,也就是: 162 | 163 | ``` 164 | u_str.encode('ascii') // u_str 是 unicode 字符串 165 | ``` 166 | 167 | 上面将 unicode 类型的中文使用 ascii 编码转,肯定会出错。 168 | 169 | 再看一个使用 raw_input 的例子,注意 raw_input 只接收 str 类型的字符串: 170 | 171 | ```python 172 | >>> name = raw_input('input your name: ') 173 | input your name: ethan 174 | >>> name 175 | 'ethan' 176 | 177 | >>> name = raw_input('输入你的姓名:') 178 | 输入你的姓名: 小明 179 | >>> name 180 | '\xe5\xb0\x8f\xe6\x98\x8e' 181 | >>> type(name) 182 | 183 | 184 | >>> name = raw_input(u'输入你的姓名: ') # 会试图使用 u'输入你的姓名'.encode('ascii') 185 | Traceback (most recent call last): 186 | File "", line 1, in 187 | UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128) 188 | 189 | >>> name = raw_input(u'输入你的姓名: '.encode('utf-8')) #可以,但此时 name 不是 unicode 类型 190 | 输入你的姓名: 小明 191 | >>> name 192 | '\xe5\xb0\x8f\xe6\x98\x8e' 193 | >>> type(name) 194 | 195 | 196 | >>> name = raw_input(u'输入你的姓名: '.encode('utf-8')).decode('utf-8') # 推荐 197 | 输入你的姓名: 小明 198 | >>> name 199 | u'\u5c0f\u660e' 200 | >>> type(name) 201 | 202 | ``` 203 | 204 | 再看一个重定向的例子: 205 | 206 | ```python 207 | hello = u'你好' 208 | print hello 209 | ``` 210 | 211 | 将上面的代码保存到文件 `hello.py`,在终端执行 `python hello.py` 可以正常打印,但是如果将其重定向到文件 `python hello.py > result` 会发现 `UnicodeEncodeError`。 212 | 213 | 这是因为:输出到控制台时,print 使用的是控制台的默认编码,而重定向到文件时,print 就不知道使用什么编码了,于是就使用了默认编码 ascii 导致出现编码错误。 214 | 215 | 应该改成如下: 216 | 217 | ```python 218 | hello = u'你好' 219 | print hello.encode('utf-8') 220 | ``` 221 | 222 | 这样执行 `python hello.py > result` 就没有问题。 223 | 224 | # 小结 225 | 226 | - UTF-8 是一种针对 Unicode 的可变长度字符编码,它是 Unicode 的实现方式之一。 227 | - Unicode 字符集有多种编码标准,比如 UTF-8, UTF-7, UTF-16。 228 | - 在进行同时包含 str 类型和 unicode 类型的字符串操作时,Python2 一律都把 str 解码(decode)成 unicode 再运算。 229 | - 如果函数或类等对象接收的是 str 类型的字符串,但你传的是 unicode,Python2 会默认使用 ascii 将其编码成 str 类型再运算。 230 | 231 | # 参考资料 232 | 233 | - [字符 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6) 234 | - [UTF-8 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/UTF-8) 235 | - [字符,字节和编码 - Characters, Bytes And Encoding](http://www.regexlab.com/zh/encoding.htm) 236 | - [字符编码笔记:ASCII,Unicode和UTF-8 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html) 237 | - [字符串和编码 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819196283586a37629844456ca7e5a7faa9b94ee8000) 238 | - [python - Dangers of sys.setdefaultencoding('utf-8') - Stack Overflow](http://stackoverflow.com/questions/28657010/dangers-of-sys-setdefaultencodingutf-8) 239 | 240 | -------------------------------------------------------------------------------- /Basic/input_output.md: -------------------------------------------------------------------------------- 1 | # 输入和输出 2 | 3 | Python2 提供了 `input`,`raw_input`,`print` 等用于输入输出,但在 Python3 中发生了一些改变,`raw_input` 已经没有了,`input` 的用法发生了变化,`print` 也从原来的语句变成了一个函数。本文将对这两种情况进行介绍。 4 | 5 | ## 输入 6 | 7 | - 首先看 Python2 中的 `raw_input`,它的用法如下: 8 | 9 | ``` 10 | raw_input(prompt) 11 | ``` 12 | 13 | 其中,prompt 表示输入提示。`raw_input` 会读取控制台的输入,并返回字符串类型。 14 | 15 | 让我们看几个例子: 16 | 17 | ```python 18 | >>> name = raw_input('please enter your name: ') 19 | please enter your name: ethan # 输入一个字符串 20 | >>> name 21 | 'ethan' 22 | >>> type(name) 23 | 24 | >>> 25 | >>> num = raw_input('please enter your id: ') 26 | please enter your id: 12345 # 输入一个数值 27 | >>> num 28 | '12345' 29 | >>> type(num) 30 | 31 | >>> 32 | >>> sum = raw_input('please enter a+b: ') 33 | please enter a+b: 3+6 # 输入一个表达式 34 | >>> sum 35 | '3+6' 36 | >>> type(sum) 37 | 38 | ``` 39 | 40 | 可以看到,不管我们输入一个字符串、数值还是表达式,`raw_input` 都直接返回一个字符串。 41 | 42 | - 现在看一下 Pythn2 中的 `input`。 43 | 44 | `input` 的用法跟 `raw_input` 类似,形式如下: 45 | 46 | ```python 47 | input(prompt) 48 | ``` 49 | 50 | 事实上,`input` 本质上是使用 `raw_input` 实现的,如下: 51 | 52 | ```python 53 | def input(prompt): 54 | return (eval(raw_input(prompt))) 55 | ``` 56 | 57 | 也就是说,调用 `input` 实际上是通过调用 `raw_input` 再调用 `eval` 函数实现的。 58 | 59 | 这里的 `eval` 通常用来执行一个字符串表达式,并返回表达式的值,它的基本用法如下: 60 | 61 | ``` 62 | >>> eval('1+2') 63 | 3 64 | >>> a = 1 65 | >>> eval('a+9') 66 | 10 67 | ``` 68 | 69 | 现在,让我们看看 `input` 的用法: 70 | 71 | ```python 72 | >>> name = input('please input your name: ') 73 | please input your name: ethan # 输入字符串如果没加引号会出错 74 | Traceback (most recent call last): 75 | File "", line 1, in 76 | File "", line 1, in 77 | NameError: name 'ethan' is not defined 78 | >>> 79 | >>> name = input('please input your name: ') 80 | please input your name: 'ethan' # 添加引号 81 | >>> name 82 | 'ethan' 83 | >>> 84 | >>> num = input('please input your id: ') 85 | please input your id: 12345 # 输入数值 86 | >>> num # 注意返回的是数值类型,而不是字符串 87 | 12345 88 | >>> type(num) 89 | 90 | >>> 91 | >>> sum = input('please enter a+b: ') # 输入数字表达式,会对表达式求值 92 | please enter a+b: 3+6 93 | >>> sum 94 | 9 95 | >>> type(sum) 96 | 97 | >>> 98 | >>> sum = input('please enter a+b: ') # 输入字符串表达式,会字符串进行运算 99 | please enter a+b: '3'+'6' 100 | >>> sum 101 | '36' 102 | ``` 103 | 104 | 可以看到,使用 `input` 的时候,如果输入的是字符串,必须使用引号把它们括起来;如果输入的是数值类型,则返回的也是数值类型;如果输入的是表达式,会对表达式进行运算。 105 | 106 | - 再来看一下 Python3 中的 `input`。 107 | 108 | 事实上,Python3 中的 `input` 就是 Python2 中的 `raw_input`,也就是说,原 Python2 中的 `raw_input` 被重命名为 `input` 了。那如果我们想使用原 Python2 的 `input` 功能呢?你可以这样做: 109 | 110 | ```python 111 | eval(input()) 112 | ``` 113 | 114 | 也就是说,手动添加 `eval` 函数。 115 | 116 | ## 输出 117 | 118 | Python2 中的 `print` 是一个语句(statement),而 Python3 中的 `print` 是一个函数。 119 | 120 | ### Python2 中的 print 121 | 122 | - 简单输出 123 | 124 | 使用 `print` 最简单的方式就是直接在 `print` 后面加上数字、字符串、列表等对象,比如: 125 | 126 | ```python 127 | # Python 2.7.11 (default, Feb 24 2016, 10:48:05) 128 | >>> print 123 129 | 123 130 | >>> print 'abc' 131 | abc 132 | >>> x = 10 133 | >>> print x 134 | 10 135 | >>> d = {'a': 1, 'b': 2} 136 | >>> print d 137 | {'a': 1, 'b': 2} 138 | >>> 139 | >>> print(123) 140 | 123 141 | >>> print('abc') 142 | abc 143 | >>> print(x) 144 | 10 145 | >>> print(d) 146 | {'a': 1, 'b': 2} 147 | ``` 148 | 149 | 在 Python2 中,使用 `print` 时可以加括号,也可以不加括号。 150 | 151 | - 格式化输出 152 | 153 | 有时,我们需要对输出进行一些格式化,比如限制小数的精度等,直接看几个例子: 154 | 155 | ``` 156 | >>> s = 'hello' 157 | >>> l = len(s) 158 | >>> print('the length of %s is %d' % (s, l)) 159 | the length of hello is 5 160 | >>> 161 | >>> pi = 3.14159 162 | >>> print('%10.3f' % pi) # 字段宽度 10,精度 3 163 | 3.142 164 | >>> print('%010.3f' % pi) # 用 0 填充空白 165 | 000003.142 166 | >>> print('%+f' % pi) # 显示正负号 167 | +3.141590 168 | ``` 169 | 170 | - 换行输出 171 | 172 | print 默认是换行输出的,如果不想换行,可以在末尾加上一个 `,',比如: 173 | 174 | ```python 175 | >>> for i in range(0, 3): 176 | ... print i 177 | ... 178 | 0 179 | 1 180 | 2 181 | >>> for i in range(0, 3): 182 | ... print i, # 加了 , 183 | ... 184 | 0 1 2 # 注意会加上一个空格 185 | ``` 186 | 187 | ### Python3 中的 print 188 | 189 | 在 Python3 中使用 print 跟 Python2 差别不大,不过要注意的是在 Python3 中使用 print 必须加括号,否则会抛出 SyntaxError。 190 | 191 | 另外,如果不想 print 换行输出,可以参考下面的方式: 192 | 193 | ```python 194 | >>> for i in range(0, 3): 195 | ... print(i) 196 | ... 197 | 0 198 | 1 199 | 2 200 | >>> for i in range(0, 3): 201 | ... print(i, end='') # 加上一个 end 参数 202 | ... 203 | 012 204 | ``` 205 | 206 | # 小结 207 | 208 | - 在 Python2 中,`raw_input` 会读取控制台的输入,并返回字符串类型。 209 | - 在 Python2 中,如无特殊要求建议使用 raw_input() 来与用户交互。 210 | - 在 Python3 中,使用 `input` 处理输入,如有特殊要求,可以考虑加上 `eval`。 211 | 212 | # 参考资料 213 | 214 | - [python - What's the difference between raw_input() and input() in python3.x? - Stack Overflow](http://stackoverflow.com/questions/4915361/whats-the-difference-between-raw-input-and-input-in-python3-x) 215 | 216 | -------------------------------------------------------------------------------- /Class/README.md: -------------------------------------------------------------------------------- 1 | # 类 2 | 3 | Python 是一门面向对象编程(Object Oriented Programming, OOP)的语言,这里的**对象**可以看做是由数据(或者说特性)以及一系列可以存取、操作这些数据的方法所组成的集合。面向对象编程主要有以下特点: 4 | 5 | - 多态(Polymorphism):不同类(Class)的对象对同一消息会做出不同的响应。 6 | - 封装(Encapsulation):对外部世界隐藏对象的工作细节。 7 | - 继承(Inheritance):以已有的类(父类)为基础建立专门的类对象。 8 | 9 | 在 Python 中,元组、列表和字典等数据类型是对象,函数也是对象。那么,我们能创建自己的对象吗?答案是肯定的。跟其他 OOP 语言类似,我们使用**类**来自定义对象。 10 | 11 | 本章主要介绍以下几个方面: 12 | 13 | * [类和实例](./class_and_object.md) 14 | * [继承和多态](./inheritance_and_polymorphism.md) 15 | * [类方法和静态方法](./method.md) 16 | * [定制类和魔法方法](./magic_method.md) 17 | * [slots 魔法](./slots.md) 18 | * [使用 @property](./property.md) 19 | * [你不知道的 super](./super.md) 20 | * [元类](./metaclass.md) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Class/class_and_object.md: -------------------------------------------------------------------------------- 1 | # 类和实例 2 | 3 | 类是一个抽象的概念,我们可以把它理解为具有相同**属性**和**方法**的一组对象的集合,而实例则是一个具体的对象。我们还是先来看看在 Python 中怎么定义一个类。 4 | 5 | 这里以动物(Animal)类为例,Python 提供关键字 `class` 来声明一个类: 6 | 7 | ```python 8 | class Animal(object): 9 | pass 10 | ``` 11 | 12 | 其中,`Animal` 是类名,通常类名的首字母采用大写(如果有多个单词,则每个单词的首字母大写),后面紧跟着 `(object)`,表示该类是从哪个类继承而来的,所有类最终都会继承自 `object` 类。 13 | 14 | 类定义好了,接下来我们就可以**创建实例**了: 15 | 16 | ```python 17 | >>> animal = Animal() # 创建一个实例对象 18 | >>> animal 19 | <__main__.Animal at 0x1030a44d0> 20 | ``` 21 | 22 | 我们在创建实例的时候,还可以传入一些参数,以初始化对象的属性,为此,我们需要添加一个 `__init__` 方法: 23 | 24 | ```python 25 | class Animal(object): 26 | def __init__(self, name): 27 | self.name = name 28 | ``` 29 | 30 | 然后,在创建实例的时候,传入参数: 31 | 32 | ```python 33 | >>> animal = Animal('dog1') # 传入参数 'dog1' 34 | >>> animal.name # 访问对象的 name 属性 35 | 'dog1' 36 | ``` 37 | 38 | 我们可以把 `__init__` 理解为对象的初始化方法,它的第一个参数永远是 `self`,指向创建的实例本身。定义了 `__init__` 方法,我们在创建实例的时候,就需要传入与 `__init__` 方法匹配的参数。 39 | 40 | 接下来,我们再来添加一个方法: 41 | 42 | ```python 43 | class Animal(object): 44 | def __init__(self, name): 45 | self.name = name 46 | def greet(self): 47 | print 'Hello, I am %s.' % self.name 48 | ``` 49 | 50 | 我们添加了方法 `greet`,看看下面的使用: 51 | 52 | ```python 53 | >>> dog1 = Animal('dog1') 54 | >>> dog1.name 55 | 'dog1' 56 | >>> dog1.greet() 57 | Hello, I am dog1. 58 | ``` 59 | 60 | 现在,让我们做一下总结。我们在 Animal 类定义了两个方法:`__init__` 和 `greet`。`__init__` 是 Python 中的**特殊方法(special method)**,它用于对对象进行初始化,类似于 C++ 中的构造函数;`greet` 是我们自定义的方法。 61 | 62 | 注意到,我们在上面定义的两个**方法**有一个共同点,就是它们的第一个参数都是 `self`,指向实例本身,也就是说它们是和实例绑定的函数,这也是我们称它们为方法而不是函数的原因。 63 | 64 | # 访问限制 65 | 66 | 在某些情况下,我们希望限制用户访问对象的属性或方法,也就是希望它是私有的,对外隐蔽。比如,对于上面的例子,我们希望 `name` 属性在外部不能被访问,我们可以**在属性或方法的名称前面加上两个下划线**,即 `__`,对上面的例子做一点改动: 67 | 68 | ```python 69 | class Animal(object): 70 | def __init__(self, name): 71 | self.__name = name 72 | def greet(self): 73 | print 'Hello, I am %s.' % self.__name 74 | ``` 75 | 76 | ```python 77 | >>> dog1 = Animal('dog1') 78 | >>> dog1.__name # 访问不了 79 | --------------------------------------------------------------------------- 80 | AttributeError Traceback (most recent call last) 81 | in () 82 | ----> 1 dog1.__name 83 | 84 | AttributeError: 'Animal' object has no attribute '__name' 85 | >>> dog1.greet() # 可以访问 86 | Hello, I am dog1. 87 | ``` 88 | 89 | 可以看到,加了 `__` 的 `__name` 是不能访问的,而原来的 `greet` 仍可以正常访问。 90 | 91 | 需要注意的是,在 Python 中,以双下划线开头,并且以双下划线结尾(即 `__xxx__`)的变量是特殊变量,特殊变量是可以直接访问的。所以,不要用 `__name__` 这样的变量名。 92 | 93 | 另外,如果变量名前面只有一个下划线 `_`,表示**不要随意访问这个变量**,虽然它可以直接被访问。 94 | 95 | # 获取对象信息 96 | 97 | 当我们拿到一个对象时,我们往往会考察它的类型和方法等,比如: 98 | 99 | ``` 100 | >>> a = 123 101 | >>> type(a) 102 | int 103 | >>> b = '123' 104 | >>> type(b) 105 | str 106 | ``` 107 | 108 | 当我们拿到一个类的对象时,我们用什么去考察它呢?回到前面的例子: 109 | 110 | ```python 111 | class Animal(object): 112 | def __init__(self, name): 113 | self.name = name 114 | def greet(self): 115 | print 'Hello, I am %s.' % self.name 116 | ``` 117 | 118 | - 第 1 招:使用 `type` 119 | 120 | 使用 `type(obj)` 来获取对象的相应类型: 121 | 122 | ``` 123 | >>> dog1 = Animal('dog1') 124 | >>> type(dog1) 125 | __main__.Animal 126 | ``` 127 | 128 | - 第 2 招:使用 `isinstance` 129 | 130 | 使用 `isinstance(obj, type)` 判断对象是否为指定的 type 类型的实例: 131 | 132 | ``` 133 | >>> isinstance(dog1, Animal) 134 | True 135 | ``` 136 | 137 | - 第 3 招:使用 `hasattr/getattr/setattr` 138 | 139 | - 使用 `hasattr(obj, attr)` 判断对象是否具有指定属性/方法; 140 | - 使用 `getattr(obj, attr[, default])` 获取属性/方法的值, 要是没有对应的属性则返回 default 值(前提是设置了 default),否则会抛出 AttributeError 异常; 141 | - 使用 `setattr(obj, attr, value)` 设定该属性/方法的值,类似于 obj.attr=value; 142 | 143 | 看下面例子: 144 | 145 | ```python 146 | >>> hasattr(dog1, 'name') 147 | True 148 | >>> hasattr(dog1, 'x') 149 | False 150 | >>> hasattr(dog1, 'greet') 151 | True 152 | >>> getattr(dog1, 'name') 153 | 'dog1' 154 | >>> getattr(dog1, 'greet') 155 | > 156 | >>> getattr(dog1, 'x') 157 | --------------------------------------------------------------------------- 158 | AttributeError Traceback (most recent call last) 159 | in () 160 | ----> 1 getattr(dog1, 'x') 161 | 162 | AttributeError: 'Animal' object has no attribute 'x' 163 | >>> getattr(dog1, 'x', 'xvalue') 164 | 'xvalue' 165 | >>> setattr(dog1, 'age', 12) 166 | >>> dog1.age 167 | 12 168 | ``` 169 | 170 | - 第 4 招:使用 `dir` 171 | 172 | 使用 `dir(obj)` 可以获取相应对象的**所有**属性和方法名的列表: 173 | 174 | ```python 175 | >>> dir(dog1) 176 | ['__class__', 177 | '__delattr__', 178 | '__dict__', 179 | '__doc__', 180 | '__format__', 181 | '__getattribute__', 182 | '__hash__', 183 | '__init__', 184 | '__module__', 185 | '__new__', 186 | '__reduce__', 187 | '__reduce_ex__', 188 | '__repr__', 189 | '__setattr__', 190 | '__sizeof__', 191 | '__str__', 192 | '__subclasshook__', 193 | '__weakref__', 194 | 'age', 195 | 'greet', 196 | 'name'] 197 | ``` 198 | 199 | # 小结 200 | 201 | - 类是具有相同**属性**和**方法**的一组对象的集合,实例是一个个具体的对象。 202 | - 方法是与实例绑定的函数。 203 | - 获取对象信息可使用下面方法: 204 | - `type(obj)`:来获取对象的相应类型; 205 | - `isinstance(obj, type)`:判断对象是否为指定的 type 类型的实例; 206 | - `hasattr(obj, attr)`:判断对象是否具有指定属性/方法; 207 | - `getattr(obj, attr[, default])` 获取属性/方法的值, 要是没有对应的属性则返回 default 值(前提是设置了 default),否则会抛出 AttributeError 异常; 208 | - `setattr(obj, attr, value)`:设定该属性/方法的值,类似于 obj.attr=value; 209 | - `dir(obj)`:可以获取相应对象的**所有**属性和方法名的列表: 210 | 211 | # 参考资料 212 | 213 | - [Python:类和对象object | Hom](http://gohom.win/2015/10/20/pyObject/) 214 | 215 | 216 | -------------------------------------------------------------------------------- /Class/inheritance_and_polymorphism.md: -------------------------------------------------------------------------------- 1 | # 继承和多态 2 | 3 | 在面向对象编程中,当我们已经创建了一个类,而又想再创建一个与之相似的类,比如添加几个方法,或者修改原来的方法,这时我们不必从头开始,可以从原来的类**派生**出一个新的类,我们把原来的类称为**父类**或**基类**,而派生出的类称为**子类**,**子类**继承了**父类**的所有数据和方法。 4 | 5 | 让我们看一个简单的例子,首先我们定义一个 Animal 类: 6 | 7 | ```python 8 | class Animal(object): 9 | def __init__(self, name): 10 | self.name = name 11 | def greet(self): 12 | print 'Hello, I am %s.' % self.name 13 | ``` 14 | 15 | 现在,我们想创建一个 Dog 类,比如: 16 | 17 | ```python 18 | class Dog(object): 19 | def __init__(self, name): 20 | self.name = name 21 | def greet(self): 22 | print 'WangWang.., I am %s. ' % self.name 23 | ``` 24 | 25 | 可以看到,Dog 类和 Animal 类几乎是一样的,只是 `greet` 方法不一样,我们完全没必要创建一个新的类,而是从 Animal 类派生出一个新的类: 26 | 27 | ```python 28 | class Dog(Animal): 29 | def greet(self): 30 | print 'WangWang.., I am %s. ' % self.name 31 | ``` 32 | 33 | Dog 类是从 Animal 类继承而来的,Dog 类自动获得了 Animal 类的所有数据和方法,而且还可以对父类的方法进行修改,我们看看使用: 34 | 35 | ```python 36 | >>> animal = Animal('animal') # 创建 animal 实例 37 | >>> animal.greet() 38 | Hello, I am animal. 39 | >>> 40 | >>> dog = Dog('dog') # 创建 dog 实例 41 | >>> dog.greet() 42 | WangWang.., I am dog. 43 | ``` 44 | 45 | 我们还可以对 Dog 类添加新的方法: 46 | 47 | ```python 48 | class Dog(Animal): 49 | def greet(self): 50 | print 'WangWang.., I am %s. ' % self.name 51 | def run(self): 52 | print 'I am running.I am running' 53 | ``` 54 | 55 | 使用: 56 | 57 | ```python 58 | >>> dog = Dog('dog') 59 | >>> dog.greet() 60 | WangWang.., I am dog. 61 | >>> dog.run() 62 | I am running 63 | ``` 64 | 65 | # 多态 66 | 67 | 多态的概念其实不难理解,它是指**对不同类型的变量进行相同的操作,它会根据对象(或类)类型的不同而表现出不同的行为**。 68 | 69 | 事实上,我们经常用到多态的性质,比如: 70 | 71 | ``` 72 | >>> 1 + 2 73 | 3 74 | >>> 'a' + 'b' 75 | 'ab' 76 | ``` 77 | 78 | 可以看到,我们对两个整数进行 `+` 操作,会返回它们的和,对两个字符进行相同的 `+` 操作,会返回拼接后的字符串。也就是说,**不同类型的对象对同一消息会作出不同的响应**。 79 | 80 | 再看看类的例子: 81 | 82 | ```python 83 | class Animal(object): 84 | def __init__(self, name): 85 | self.name = name 86 | def greet(self): 87 | print 'Hello, I am %s.' % self.name 88 | 89 | class Dog(Animal): 90 | def greet(self): 91 | print 'WangWang.., I am %s.' % self.name 92 | 93 | class Cat(Animal): 94 | def greet(self): 95 | print 'MiaoMiao.., I am %s' % self.name 96 | 97 | def hello(animal): 98 | animal.greet() 99 | ``` 100 | 101 | 看看多态的使用: 102 | 103 | ```python 104 | >>> dog = Dog('dog') 105 | >>> hello(dog) 106 | WangWang.., I am dog. 107 | >>> 108 | >>> cat = Cat('cat') 109 | >>> hello(cat) 110 | MiaoMiao.., I am cat 111 | ``` 112 | 113 | 可以看到,`cat` 和 `dog` 是两个不同的对象,对它们调用 `greet` 方法,它们会自动调用实际类型的 `greet` 方法,作出不同的响应。这就是多态的魅力。 114 | 115 | # 小结 116 | 117 | - 继承可以拿到父类的所有数据和方法,子类可以重写父类的方法,也可以新增自己特有的方法。 118 | - 有了继承,才有了多态,不同类的对象对同一消息会作出不同的相应。 119 | 120 | 121 | -------------------------------------------------------------------------------- /Class/magic_method.md: -------------------------------------------------------------------------------- 1 | # 定制类和魔法方法 2 | 3 | 在 Python 中,我们可以经常看到以双下划线 `__` 包裹起来的方法,比如最常见的 `__init__`,这些方法被称为**魔法方法(magic method)或特殊方法(special method)**。简单地说,这些方法可以给 Python 的类提供特殊功能,方便我们定制一个类,比如 `__init__` 方法可以对实例属性进行初始化。 4 | 5 | 完整的特殊方法列表可在[这里](https://docs.python.org/2/reference/datamodel.html#special-method-names)查看,本文介绍部分常用的特殊方法: 6 | 7 | - `__new__` 8 | - `__str__` , `__repr__` 9 | - `__iter__` 10 | - `__getitem__` , `__setitem__` , `__delitem__` 11 | - `__getattr__` , `__setattr__` , `__delattr__` 12 | - `__call__` 13 | 14 | # new 15 | 16 | 在 Python 中,当我们创建一个类的实例时,类会先调用 `__new__(cls[, ...])` 来创建实例,然后 `__init__` 方法再对该实例(self)进行初始化。 17 | 18 | 关于 `__new__` 和 `__init__` 有几点需要注意: 19 | 20 | - `__new__` 是在 `__init__` 之前被调用的; 21 | - `__new__` 是类方法,`__init__` 是实例方法; 22 | - 重载 `__new__` 方法,需要返回类的实例; 23 | 24 | 一般情况下,我们不需要重载 `__new__` 方法。但在某些情况下,我们想**控制实例的创建过程**,这时可以通过重载 `__new_` 方法来实现。 25 | 26 | 让我们看一个例子: 27 | 28 | ```python 29 | class A(object): 30 | _dict = dict() 31 | 32 | def __new__(cls): 33 | if 'key' in A._dict: 34 | print "EXISTS" 35 | return A._dict['key'] 36 | else: 37 | print "NEW" 38 | return object.__new__(cls) 39 | 40 | def __init__(self): 41 | print "INIT" 42 | A._dict['key'] = self 43 | ``` 44 | 45 | 在上面,我们定义了一个类 `A`,并重载了 `__new__` 方法:当 `key` 在 `A._dict` 中时,直接返回 `A._dict['key']`,否则创建实例。 46 | 47 | 执行情况: 48 | 49 | ``` 50 | >>> a1 = A() 51 | NEW 52 | INIT 53 | >>> a2 = A() 54 | EXISTS 55 | INIT 56 | >>> a3 = A() 57 | EXISTS 58 | INIT 59 | ``` 60 | 61 | # str & repr 62 | 63 | 先看一个简单的例子: 64 | 65 | ```python 66 | class Foo(object): 67 | def __init__(self, name): 68 | self.name = name 69 | 70 | >>> print Foo('ethan') 71 | <__main__.Foo object at 0x10c37aa50> 72 | ``` 73 | 74 | 在上面,我们使用 print 打印一个实例对象,但如果我们想打印更多信息呢,比如把 name 也打印出来,这时,我们可以在类中加入 `__str__` 方法,如下: 75 | 76 | ```python 77 | class Foo(object): 78 | def __init__(self, name): 79 | self.name = name 80 | def __str__(self): 81 | return 'Foo object (name: %s)' % self.name 82 | 83 | >>> print Foo('ethan') # 使用 print 84 | Foo object (name: ethan) 85 | >>> 86 | >>> str(Foo('ethan')) # 使用 str 87 | 'Foo object (name: ethan)' 88 | >>> 89 | >>> Foo('ethan') # 直接显示 90 | <__main__.Foo at 0x10c37a490> 91 | ``` 92 | 93 | 可以看到,使用 print 和 str 输出的是 `__str__` 方法返回的内容,但如果直接显示则不是,那能不能修改它的输出呢?当然可以,我们只需在类中加入 `__repr__` 方法,比如: 94 | 95 | ```python 96 | class Foo(object): 97 | def __init__(self, name): 98 | self.name = name 99 | def __str__(self): 100 | return 'Foo object (name: %s)' % self.name 101 | def __repr__(self): 102 | return 'Foo object (name: %s)' % self.name 103 | 104 | >>> Foo('ethan') 105 | 'Foo object (name: ethan)' 106 | ``` 107 | 108 | 可以看到,现在直接使用 `Foo('ethan')` 也可以显示我们想要的结果了,然而,我们发现上面的代码中,`__str__` 和 `__repr__` 方法的代码是一样的,能不能精简一点呢,当然可以,如下: 109 | 110 | ```python 111 | class Foo(object): 112 | def __init__(self, name): 113 | self.name = name 114 | def __str__(self): 115 | return 'Foo object (name: %s)' % self.name 116 | __repr__ = __str__ 117 | ``` 118 | 119 | # iter 120 | 121 | 在某些情况下,我们希望实例对象可被用于 `for...in` 循环,这时我们需要在类中定义 `__iter__` 和 `next`(在 Python3 中是 `__next__`)方法,其中,`__iter__` 返回一个迭代对象,`next` 返回容器的下一个元素,在没有后续元素时抛出 `StopIteration` 异常。 122 | 123 | 看一个斐波那契数列的例子: 124 | 125 | ```python 126 | class Fib(object): 127 | def __init__(self): 128 | self.a, self.b = 0, 1 129 | 130 | def __iter__(self): # 返回迭代器对象本身 131 | return self 132 | 133 | def next(self): # 返回容器下一个元素 134 | self.a, self.b = self.b, self.a + self.b 135 | return self.a 136 | 137 | >>> fib = Fib() 138 | >>> for i in fib: 139 | ... if i > 10: 140 | ... break 141 | ... print i 142 | ... 143 | 1 144 | 1 145 | 2 146 | 3 147 | 5 148 | 8 149 | ``` 150 | 151 | # getitem 152 | 153 | 有时,我们希望可以使用 `obj[n]` 这种方式对实例对象进行取值,比如对斐波那契数列,我们希望可以取出其中的某一项,这时我们需要在类中实现 `__getitem__` 方法,比如下面的例子: 154 | 155 | ```python 156 | class Fib(object): 157 | def __getitem__(self, n): 158 | a, b = 1, 1 159 | for x in xrange(n): 160 | a, b = b, a + b 161 | return a 162 | 163 | >>> fib = Fib() 164 | >>> fib[0], fib[1], fib[2], fib[3], fib[4], fib[5] 165 | (1, 1, 2, 3, 5, 8) 166 | ``` 167 | 168 | 我们还想更进一步,希望支持 `obj[1:3]` 这种切片方法来取值,这时 `__getitem__` 方法传入的参数可能是一个整数,也可能是一个切片对象 slice,因此,我们需要对传入的参数进行判断,可以使用 `isinstance` 进行判断,改后的代码如下: 169 | 170 | ```python 171 | class Fib(object): 172 | def __getitem__(self, n): 173 | if isinstance(n, slice): # 如果 n 是 slice 对象 174 | a, b = 1, 1 175 | start, stop = n.start, n.stop 176 | L = [] 177 | for i in xrange(stop): 178 | if i >= start: 179 | L.append(a) 180 | a, b = b, a + b 181 | return L 182 | if isinstance(n, int): # 如果 n 是 int 型 183 | a, b = 1, 1 184 | for i in xrange(n): 185 | a, b = b, a + b 186 | return a 187 | ``` 188 | 189 | 现在,我们试试用切片方法: 190 | 191 | ```python 192 | >>> fib = Fib() 193 | >>> fib[0:3] 194 | [1, 1, 2] 195 | >>> fib[2:6] 196 | [2, 3, 5, 8] 197 | ``` 198 | 199 | 上面,我们只是简单地演示了 getitem 的操作,但是它还很不完善,比如没有对负数处理,不支持带 step 参数的切片操作 `obj[1:2:5]` 等等,读者有兴趣的话可以自己实现看看。 200 | 201 | `__geitem__` 用于获取值,类似地,`__setitem__` 用于设置值,`__delitem__` 用于删除值,让我们看下面一个例子: 202 | 203 | ```python 204 | class Point(object): 205 | def __init__(self): 206 | self.coordinate = {} 207 | 208 | def __str__(self): 209 | return "point(%s)" % self.coordinate 210 | 211 | def __getitem__(self, key): 212 | return self.coordinate.get(key) 213 | 214 | def __setitem__(self, key, value): 215 | self.coordinate[key] = value 216 | 217 | def __delitem__(self, key): 218 | del self.coordinate[key] 219 | print 'delete %s' % key 220 | 221 | def __len__(self): 222 | return len(self.coordinate) 223 | 224 | __repr__ = __str__ 225 | ``` 226 | 227 | 在上面,我们定义了一个 Point 类,它有一个属性 coordinate(坐标),是一个字典,让我们看看使用: 228 | 229 | ```python 230 | >>> p = Point() 231 | >>> p['x'] = 2 # 对应于 p.__setitem__('x', 2) 232 | >>> p['y'] = 5 # 对应于 p.__setitem__('y', 5) 233 | >>> p # 对应于 __repr__ 234 | point({'y': 5, 'x': 2}) 235 | >>> len(p) # 对应于 p.__len__ 236 | 2 237 | >>> p['x'] # 对应于 p.__getitem__('x') 238 | 2 239 | >>> p['y'] # 对应于 p.__getitem__('y') 240 | 5 241 | >>> del p['x'] # 对应于 p.__delitem__('x') 242 | delete x 243 | >>> p 244 | point({'y': 5}) 245 | >>> len(p) 246 | 1 247 | ``` 248 | 249 | # getattr 250 | 251 | 当我们获取对象的某个属性,如果该属性不存在,会抛出 AttributeError 异常,比如: 252 | 253 | ```python 254 | class Point(object): 255 | def __init__(self, x=0, y=0): 256 | self.x = x 257 | self.y = y 258 | 259 | >>> p = Point(3, 4) 260 | >>> p.x, p.y 261 | (3, 4) 262 | >>> p.z 263 | --------------------------------------------------------------------------- 264 | AttributeError Traceback (most recent call last) 265 | in () 266 | ----> 1 p.z 267 | 268 | AttributeError: 'Point' object has no attribute 'z' 269 | ``` 270 | 271 | 那有没有办法不让它抛出异常呢?当然有,只需在类的定义中加入 `__getattr__` 方法,比如: 272 | 273 | ```python 274 | class Point(object): 275 | def __init__(self, x=0, y=0): 276 | self.x = x 277 | self.y = y 278 | def __getattr__(self, attr): 279 | if attr == 'z': 280 | return 0 281 | 282 | >>> p = Point(3, 4) 283 | >>> p.z 284 | 0 285 | ``` 286 | 287 | 现在,当我们调用不存在的属性(比如 z)时,解释器就会试图调用 `__getattr__(self, 'z')` 来获取值,但是,上面的实现还有一个问题,当我们调用其他属性,比如 w ,会返回 None,因为 `__getattr__` 默认返回就是 None,只有当 attr 等于 'z' 时才返回 0,如果我们想让 `__getattr__` 只响应几个特定的属性,可以加入异常处理,修改 `__getattr__` 方法,如下: 288 | 289 | ```python 290 | def __getattr__(self, attr): 291 | if attr == 'z': 292 | return 0 293 | raise AttributeError("Point object has no attribute %s" % attr) 294 | ``` 295 | 296 | 这里再强调一点,`__getattr__` 只有在属性不存在的情况下才会被调用,对已存在的属性不会调用 `__getattr__`。 297 | 298 | 与 `__getattr__` 一起使用的还有 `__setattr__`, `__delattr__`,类似 `obj.attr = value`, `del obj.attr`,看下面一个例子: 299 | 300 | ```python 301 | class Point(object): 302 | def __init__(self, x=0, y=0): 303 | self.x = x 304 | self.y = y 305 | 306 | def __getattr__(self, attr): 307 | if attr == 'z': 308 | return 0 309 | raise AttributeError("Point object has no attribute %s" % attr) 310 | 311 | def __setattr__(self, *args, **kwargs): 312 | print 'call func set attr (%s, %s)' % (args, kwargs) 313 | return object.__setattr__(self, *args, **kwargs) 314 | 315 | def __delattr__(self, *args, **kwargs): 316 | print 'call func del attr (%s, %s)' % (args, kwargs) 317 | return object.__delattr__(self, *args, **kwargs) 318 | 319 | >>> p = Point(3, 4) 320 | call func set attr (('x', 3), {}) 321 | call func set attr (('y', 4), {}) 322 | >>> p.z 323 | 0 324 | >>> p.z = 7 325 | call func set attr (('z', 7), {}) 326 | >>> p.z 327 | 7 328 | >>> p.w 329 | Traceback (most recent call last): 330 | File "", line 1, in 331 | File "", line 8, in __getattr__ 332 | AttributeError: Point object has no attribute w 333 | >>> p.w = 8 334 | call func set attr (('w', 8), {}) 335 | >>> p.w 336 | 8 337 | >>> del p.w 338 | call func del attr (('w',), {}) 339 | >>> p.__dict__ 340 | {'y': 4, 'x': 3, 'z': 7} 341 | ``` 342 | 343 | # call 344 | 345 | 我们一般使用 `obj.method()` 来调用对象的方法,那能不能**直接在实例本身上调用**呢?在 Python 中,只要我们在类中定义 `__call__` 方法,就可以对实例进行调用,比如下面的例子: 346 | 347 | ```python 348 | class Point(object): 349 | def __init__(self, x, y): 350 | self.x, self.y = x, y 351 | def __call__(self, z): 352 | return self.x + self.y + z 353 | ``` 354 | 355 | 使用如下: 356 | 357 | ```python 358 | >>> p = Point(3, 4) 359 | >>> callable(p) # 使用 callable 判断对象是否能被调用 360 | True 361 | >>> p(6) # 传入参数,对实例进行调用,对应 p.__call__(6) 362 | 13 # 3+4+6 363 | ``` 364 | 365 | 可以看到,对实例进行调用就好像对函数调用一样。 366 | 367 | # 小结 368 | 369 | - `__new__` 在 `__init__` 之前被调用,用来创建实例。 370 | - `__str__` 是用 print 和 str 显示的结果,`__repr__` 是直接显示的结果。 371 | - `__getitem__` 用类似 `obj[key]` 的方式对对象进行取值 372 | - `__getattr__` 用于获取不存在的属性 obj.attr 373 | - `__call__` 使得可以对实例进行调用 374 | 375 | # 参考资料 376 | 377 | - [design patterns - Python's use of __new__ and __init__? - Stack Overflow](http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init) 378 | - [定制类](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013946328809098c1be08a2c7e4319bd60269f62be04fa000) 379 | - [__setitem__ implementation in Python for Point(x,y) class - Stack Overflow](http://stackoverflow.com/questions/15774804/setitem-implementation-in-python-for-pointx-y-class) 380 | - [Python对象的特殊属性和方法 | Hom](http://gohom.win/2015/10/09/pySpecialObjMethod/) 381 | - [介绍Python的魔术方法 - Magic Method - 旺酱的专栏 - SegmentFault](https://segmentfault.com/a/1190000007256392) 382 | 383 | 384 | -------------------------------------------------------------------------------- /Class/metaclass.md: -------------------------------------------------------------------------------- 1 | # 陌生的 metaclass 2 | 3 | Python 中的**元类(metaclass)**是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。 4 | 5 | ## 类也是对象 6 | 7 | 在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,**类也是一个对象**,因此你可以: 8 | 9 | - 把类赋值给一个变量 10 | - 把类作为函数参数进行传递 11 | - 把类作为函数的返回值 12 | - 在运行时动态地创建类 13 | 14 | 看一个简单的例子: 15 | 16 | ```python 17 | class Foo(object): 18 | foo = True 19 | 20 | class Bar(object): 21 | bar = True 22 | 23 | def echo(cls): 24 | print cls 25 | 26 | def select(name): 27 | if name == 'foo': 28 | return Foo # 返回值是一个类 29 | if name == 'bar': 30 | return Bar 31 | 32 | >>> echo(Foo) # 把类作为参数传递给函数 echo 33 | 34 | >>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls 35 | >>> cls 36 | __main__.Foo 37 | ``` 38 | 39 | ## 熟悉又陌生的 type 40 | 41 | 在日常使用中,我们经常使用 `object` 来派生一个类,事实上,在这种情况下,Python 解释器会调用 `type` 来创建类。 42 | 43 | 这里,出现了 `type`,没错,是你知道的 `type`,我们经常使用它来判断一个对象的类型,比如: 44 | 45 | ```python 46 | class Foo(object): 47 | Foo = True 48 | 49 | >>> type(10) 50 | 51 | >>> type('hello') 52 | 53 | >>> type(Foo()) 54 | 55 | >>> type(Foo) 56 | 57 | ``` 58 | 59 | **事实上,`type` 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)**。下面,我们看几个例子,来消化一下这句话。 60 | 61 | 使用 `type` 来创建类(对象)的方式如下: 62 | 63 | > type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值)) 64 | 65 | ### 最简单的情况 66 | 67 | 假设有下面的类: 68 | 69 | ```python 70 | class Foo(object): 71 | pass 72 | ``` 73 | 74 | 现在,我们不使用 `class` 关键字来定义,而使用 `type`,如下: 75 | 76 | ```python 77 | Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象 78 | ``` 79 | 80 | 上面两种方式是等价的。我们看到,`type` 接收三个参数: 81 | 82 | - 第 1 个参数是字符串 'Foo',表示类名 83 | - 第 2 个参数是元组 (object, ),表示所有的父类 84 | - 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法 85 | 86 | 在上面,我们使用 `type()` 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。 87 | 88 | 接着,我们看看使用: 89 | 90 | ```python 91 | >>> print Foo 92 | 93 | >>> print Foo() 94 | <__main__.Foo object at 0x10c34f250> 95 | ``` 96 | 97 | ### 有属性和方法的情况 98 | 99 | 假设有下面的类: 100 | 101 | ```python 102 | class Foo(object): 103 | foo = True 104 | def greet(self): 105 | print 'hello world' 106 | print self.foo 107 | ``` 108 | 109 | 用 `type` 来创建这个类,如下: 110 | 111 | ```python 112 | def greet(self): 113 | print 'hello world' 114 | print self.foo 115 | 116 | Foo = type('Foo', (object, ), {'foo': True, 'greet': greet}) 117 | ``` 118 | 119 | 上面两种方式的效果是一样的,看下使用: 120 | 121 | ```python 122 | >>> f = Foo() 123 | >>> f.foo 124 | True 125 | >>> f.greet 126 | > 127 | >>> f.greet() 128 | hello world 129 | True 130 | ``` 131 | 132 | ### 继承的情况 133 | 134 | 再来看看继承的情况,假设有如下的父类: 135 | 136 | ```python 137 | class Base(object): 138 | pass 139 | ``` 140 | 141 | 我们用 Base 派生一个 Foo 类,如下: 142 | 143 | ```python 144 | class Foo(Base): 145 | foo = True 146 | ``` 147 | 148 | 改用 `type` 来创建,如下: 149 | 150 | ```python 151 | Foo = type('Foo', (Base, ), {'foo': True}) 152 | ``` 153 | 154 | ## 什么是元类(metaclass) 155 | 156 | **元类(metaclass)是用来创建类(对象)的可调用对象。**这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述: 157 | 158 | ```python 159 | 类是实例对象的模板,元类是类的模板 160 | 161 | +----------+ +----------+ +----------+ 162 | | | | | | | 163 | | | instance of | | instance of | | 164 | | instance +------------>+ class +------------>+ metaclass| 165 | | | | | | | 166 | | | | | | | 167 | +----------+ +----------+ +----------+ 168 | ``` 169 | 170 | 我们在前面使用了 `type` 来创建类(对象),事实上,**`type` 就是一个元类**。 171 | 172 | 那么,元类到底有什么用呢?要你何用... 173 | 174 | **元类的主要目的是为了控制类的创建行为。**我们还是先来看看一些例子,以消化这句话。 175 | 176 | ## 元类的使用 177 | 178 | 先从一个简单的例子开始,假设有下面的类: 179 | 180 | ```python 181 | class Foo(object): 182 | name = 'foo' 183 | def bar(self): 184 | print 'bar' 185 | ``` 186 | 187 | 现在我们想给这个类的方法和属性名称前面加上 `my_` 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。 188 | 189 | 1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下: 190 | 191 | ```python 192 | class PrefixMetaclass(type): 193 | def __new__(cls, name, bases, attrs): 194 | # 给所有属性和方法前面加上前缀 my_ 195 | _attrs = (('my_' + name, value) for name, value in attrs.items()) 196 | 197 | _attrs = dict((name, value) for name, value in _attrs) # 转化为字典 198 | _attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法 199 | 200 | return type.__new__(cls, name, bases, _attrs) # 返回创建后的类 201 | ``` 202 | 203 | 上面的代码有几个需要注意的点: 204 | 205 | - PrefixMetaClass 从 `type` 继承,这是因为 PrefixMetaclass 是用来创建类的 206 | - `__new__` 是在 `__init__` 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下: 207 | - cls:当前准备创建的类 208 | - name:类的名字 209 | - bases:类的父类集合 210 | - attrs:类的属性和方法,是一个字典 211 | 212 | 2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。 213 | 214 | 在 Python2 中,我们只需在 Foo 中加一个 `__metaclass__` 的属性,如下: 215 | 216 | ```python 217 | class Foo(object): 218 | __metaclass__ = PrefixMetaclass 219 | name = 'foo' 220 | def bar(self): 221 | print 'bar' 222 | ``` 223 | 224 | 在 Python3 中,这样做: 225 | 226 | ```python 227 | class Foo(metaclass=PrefixMetaclass): 228 | name = 'foo' 229 | def bar(self): 230 | print 'bar' 231 | ``` 232 | 233 | 现在,让我们看看使用: 234 | 235 | ```python 236 | >>> f = Foo() 237 | >>> f.name # name 属性已经被改变 238 | --------------------------------------------------------------------------- 239 | AttributeError Traceback (most recent call last) 240 | in () 241 | ----> 1 f.name 242 | 243 | AttributeError: 'Foo' object has no attribute 'name' 244 | >>> 245 | >>> f.my_name 246 | 'foo' 247 | >>> f.my_bar() 248 | bar 249 | >>> f.echo('hello') 250 | 'hello' 251 | ``` 252 | 253 | 可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。 254 | 255 | 再来看一个继承的例子,下面是完整的代码: 256 | 257 | ```python 258 | class PrefixMetaclass(type): 259 | def __new__(cls, name, bases, attrs): 260 | # 给所有属性和方法前面加上前缀 my_ 261 | _attrs = (('my_' + name, value) for name, value in attrs.items()) 262 | 263 | _attrs = dict((name, value) for name, value in _attrs) # 转化为字典 264 | _attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法 265 | 266 | return type.__new__(cls, name, bases, _attrs) 267 | 268 | class Foo(object): 269 | __metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别 270 | name = 'foo' 271 | def bar(self): 272 | print 'bar' 273 | 274 | class Bar(Foo): 275 | prop = 'bar' 276 | ``` 277 | 278 | 其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用: 279 | 280 | ```python 281 | >>> b = Bar() 282 | >>> b.prop # 发现没这个属性 283 | --------------------------------------------------------------------------- 284 | AttributeError Traceback (most recent call last) 285 | in () 286 | ----> 1 b.prop 287 | 288 | AttributeError: 'Bar' object has no attribute 'prop' 289 | >>> b.my_prop 290 | 'bar' 291 | >>> b.my_name 292 | 'foo' 293 | >>> b.my_bar() 294 | bar 295 | >>> b.echo('hello') 296 | 'hello' 297 | ``` 298 | 299 | 我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢? 300 | 301 | 原来,当我们定义 `class Bar(Foo)` 时,Python 会首先在当前类,即 Bar 中寻找 `__metaclass__`,如果没有找到,就会在父类 Foo 中寻找 `__metaclass__`,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 `__metaclass__`,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。 302 | 303 | 这里,我们在 Foo 找到了 `__metaclass__`,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 `__metaclass__`,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。 304 | 305 | 写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~ 306 | 307 | # 小结 308 | 309 | - 在 Python 中,类也是一个对象。 310 | - 类创建实例,元类创建类。 311 | - 元类主要做了三件事: 312 | - 拦截类的创建 313 | - 修改类的定义 314 | - 返回修改后的类 315 | - 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。 316 | 317 | # 参考资料 318 | 319 | - [oop - What is a metaclass in Python? - Stack Overflow](http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python) 320 | - [深刻理解Python中的元类(metaclass) - 伯乐在线](http://blog.jobbole.com/21351/) 321 | - [使用元类 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000) 322 | - [Python基础:元类](http://www.cnblogs.com/russellluo/p/3409602.html) 323 | - [在Python中使用class decorator和metaclass](http://blog.zhangyu.so/python/2016/02/19/class-decorator-and-metaclass-in-python/) 324 | 325 | 326 | -------------------------------------------------------------------------------- /Class/method.md: -------------------------------------------------------------------------------- 1 | # 类方法和静态方法 2 | 3 | 在讲类方法和静态方法之前,先来看一个简单的例子: 4 | 5 | ```python 6 | class A(object): 7 | def foo(self): 8 | print 'Hello ', self 9 | 10 | >>> a = A() 11 | >>> a.foo() 12 | Hello, <__main__.A object at 0x10c37a450> 13 | ``` 14 | 15 | 在上面,我们定义了一个类 A,它有一个方法 foo,然后我们创建了一个对象 a,并调用方法 foo。 16 | 17 | ## 类方法 18 | 19 | 如果我们想通过类来调用方法,而不是通过实例,那应该怎么办呢? 20 | 21 | Python 提供了 `classmethod` 装饰器让我们实现上述功能,看下面的例子: 22 | 23 | ```python 24 | class A(object): 25 | bar = 1 26 | @classmethod 27 | def class_foo(cls): 28 | print 'Hello, ', cls 29 | print cls.bar 30 | 31 | >>> A.class_foo() # 直接通过类来调用方法 32 | Hello, 33 | 1 34 | ``` 35 | 36 | 在上面,我们使用了 `classmethod` 装饰方法 `class_foo`,它就变成了一个类方法,`class_foo` 的参数是 cls,代表类本身,当我们使用 `A.class_foo()` 时,cls 就会接收 A 作为参数。另外,被 `classmethod` 装饰的方法由于持有 cls 参数,因此我们可以在方法里面调用类的属性、方法,比如 `cls.bar`。 37 | 38 | ## 静态方法 39 | 40 | 在类中往往有一些方法跟类有关系,但是又不会改变类和实例状态的方法,这种方法是**静态方法**,我们使用 `staticmethod` 来装饰,比如下面的例子: 41 | 42 | ```python 43 | class A(object): 44 | bar = 1 45 | @staticmethod 46 | def static_foo(): 47 | print 'Hello, ', A.bar 48 | 49 | >>> a = A() 50 | >>> a.static_foo() 51 | Hello, 1 52 | >>> A.static_foo() 53 | Hello, 1 54 | ``` 55 | 56 | 可以看到,静态方法没有 self 和 cls 参数,可以把它看成是一个普通的函数,我们当然可以把它写到类外面,但这是不推荐的,因为这不利于代码的组织和命名空间的整洁。 57 | 58 | # 小结 59 | 60 | - 类方法使用 `@classmethod` 装饰器,可以使用类(也可使用实例)来调用方法。 61 | - 静态方法使用 `@staticmethod` 装饰器,它是跟类有关系但在运行时又不需要实例和类参与的方法,可以使用类和实例来调用。 62 | 63 | 64 | -------------------------------------------------------------------------------- /Class/property.md: -------------------------------------------------------------------------------- 1 | # 使用 @property 2 | 3 | 在使用 `@property` 之前,让我们先来看一个简单的例子: 4 | 5 | ```python 6 | class Exam(object): 7 | def __init__(self, score): 8 | self._score = score 9 | 10 | def get_score(self): 11 | return self._score 12 | 13 | def set_score(self, val): 14 | if val < 0: 15 | self._score = 0 16 | elif val > 100: 17 | self._score = 100 18 | else: 19 | self._score = val 20 | 21 | >>> e = Exam(60) 22 | >>> e.get_score() 23 | 60 24 | >>> e.set_score(70) 25 | >>> e.get_score() 26 | 70 27 | ``` 28 | 29 | 在上面,我们定义了一个 Exam 类,为了避免直接对 `_score` 属性操作,我们提供了 get_score 和 set_score 方法,这样起到了封装的作用,把一些不想对外公开的属性隐蔽起来,而只是提供方法给用户操作,在方法里面,我们可以检查参数的合理性等。 30 | 31 | 这样做没什么问题,但是我们有更简单的方式来做这件事,Python 提供了 `property` 装饰器,被装饰的方法,我们可以将其『当作』属性来用,看下面的例子: 32 | 33 | ```python 34 | class Exam(object): 35 | def __init__(self, score): 36 | self._score = score 37 | 38 | @property 39 | def score(self): 40 | return self._score 41 | 42 | @score.setter 43 | def score(self, val): 44 | if val < 0: 45 | self._score = 0 46 | elif val > 100: 47 | self._score = 100 48 | else: 49 | self._score = val 50 | 51 | >>> e = Exam(60) 52 | >>> e.score 53 | 60 54 | >>> e.score = 90 55 | >>> e.score 56 | 90 57 | >>> e.score = 200 58 | >>> e.score 59 | 100 60 | ``` 61 | 62 | 在上面,我们给方法 score 加上了 `@property`,于是我们可以把 score 当成一个属性来用,此时,又会创建一个新的装饰器 `score.setter`,它可以把被装饰的方法变成属性来赋值。 63 | 64 | 另外,我们也不一定要使用 `score.setter` 这个装饰器,这时 score 就变成一个只读属性了: 65 | 66 | ```python 67 | class Exam(object): 68 | def __init__(self, score): 69 | self._score = score 70 | 71 | @property 72 | def score(self): 73 | return self._score 74 | 75 | >>> e = Exam(60) 76 | >>> e.score 77 | 60 78 | >>> e.score = 200 # score 是只读属性,不能设置值 79 | --------------------------------------------------------------------------- 80 | AttributeError Traceback (most recent call last) 81 | in () 82 | ----> 1 e.score = 200 83 | 84 | AttributeError: can't set attribute 85 | 86 | ``` 87 | 88 | # 小结 89 | 90 | - `@property` 把方法『变成』了属性。 91 | 92 | 93 | -------------------------------------------------------------------------------- /Class/singleton.md: -------------------------------------------------------------------------------- 1 | # 单例模式 2 | 3 | **单例模式(Singleton Pattern)**是一种常用的软件设计模式,该模式的主要目的是确保**某一个类只有一个实例存在**。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。 4 | 5 | 比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。 6 | 7 | 在 Python 中,我们可以用多种方法来实现单例模式: 8 | 9 | - 使用模块 10 | - 使用 `__new__` 11 | - 使用装饰器(decorator) 12 | - 使用元类(metaclass) 13 | 14 | # 使用模块 15 | 16 | 其实,**Python 的模块就是天然的单例模式**,因为模块在第一次导入时,会生成 `.pyc` 文件,当第二次导入时,就会直接加载 `.pyc` 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做: 17 | 18 | ```python 19 | # mysingleton.py 20 | class My_Singleton(object): 21 | def foo(self): 22 | pass 23 | 24 | my_singleton = My_Singleton() 25 | ``` 26 | 27 | 将上面的代码保存在文件 `mysingleton.py` 中,然后这样使用: 28 | 29 | ``` 30 | from mysingleton import my_singleton 31 | 32 | my_singleton.foo() 33 | ``` 34 | 35 | # 使用 `__new__` 36 | 37 | 为了使类只能出现一个实例,我们可以使用 `__new__` 来控制实例的创建过程,代码如下: 38 | 39 | ```python 40 | class Singleton(object): 41 | _instance = None 42 | def __new__(cls, *args, **kw): 43 | if not cls._instance: 44 | cls._instance = super(Singleton, cls).__new__(cls, *args, **kw) 45 | return cls._instance 46 | 47 | class MyClass(Singleton): 48 | a = 1 49 | ``` 50 | 51 | 在上面的代码中,我们将类的实例和一个类变量 `_instance` 关联起来,如果 `cls._instance` 为 None 则创建实例,否则直接返回 `cls._instance`。 52 | 53 | 执行情况如下: 54 | 55 | ``` 56 | >>> one = MyClass() 57 | >>> two = MyClass() 58 | >>> one == two 59 | True 60 | >>> one is two 61 | True 62 | >>> id(one), id(two) 63 | (4303862608, 4303862608) 64 | ``` 65 | 66 | # 使用装饰器 67 | 68 | 我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下: 69 | 70 | ``` 71 | from functools import wraps 72 | 73 | def singleton(cls): 74 | instances = {} 75 | @wraps(cls) 76 | def getinstance(*args, **kw): 77 | if cls not in instances: 78 | instances[cls] = cls(*args, **kw) 79 | return instances[cls] 80 | return getinstance 81 | 82 | @singleton 83 | class MyClass(object): 84 | a = 1 85 | ``` 86 | 87 | 在上面,我们定义了一个装饰器 `singleton`,它返回了一个内部函数 `getinstance`,该函数会判断某个类是否在字典 `instances` 中,如果不存在,则会将 `cls` 作为 key,`cls(*args, **kw)` 作为 value 存到 `instances` 中,否则,直接返回 `instances[cls]`。 88 | 89 | # 使用 metaclass 90 | 91 | 元类(metaclass)可以控制类的创建过程,它主要做三件事: 92 | 93 | - 拦截类的创建 94 | - 修改类的定义 95 | - 返回修改后的类 96 | 97 | 使用元类实现单例模式的代码如下: 98 | 99 | ```python 100 | class Singleton(type): 101 | _instances = {} 102 | def __call__(cls, *args, **kwargs): 103 | if cls not in cls._instances: 104 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 105 | return cls._instances[cls] 106 | 107 | # Python2 108 | class MyClass(object): 109 | __metaclass__ = Singleton 110 | 111 | # Python3 112 | # class MyClass(metaclass=Singleton): 113 | # pass 114 | ``` 115 | 116 | # 小结 117 | 118 | - Python 的模块是天然的单例模式,这在大部分情况下应该是够用的,当然,我们也可以使用装饰器、元类等方法 119 | 120 | # 参考资料 121 | 122 | - [Creating a singleton in Python - Stack Overflow](http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) 123 | - [深入浅出单实例Singleton设计模式 | 酷 壳](http://coolshell.cn/articles/265.html) 124 | - [design patterns - Python's use of __new__ and __init__? - Stack Overflow](http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init) 125 | 126 | 127 | -------------------------------------------------------------------------------- /Class/slots.md: -------------------------------------------------------------------------------- 1 | # slots 魔法 2 | 3 | 在 Python 中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。 4 | 5 | 看下面一个简单的例子: 6 | 7 | ```python 8 | class Point(object): 9 | def __init__(self, x=0, y=0): 10 | self.x = x 11 | self.y = y 12 | 13 | >>> p = Point(3, 4) 14 | >>> p.z = 5 # 绑定了一个新的属性 15 | >>> p.z 16 | 5 17 | >>> p.__dict__ 18 | {'x': 3, 'y': 4, 'z': 5} 19 | ``` 20 | 21 | 在上面,我们创建了实例 p 之后,给它绑定了一个新的属性 z,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。 22 | 23 | 因此,为了不浪费内存,可以使用 `__slots__` 来告诉 Python 只给一个固定集合的属性分配空间,对上面的代码做一点改进,如下: 24 | 25 | ```python 26 | class Point(object): 27 | __slots__ = ('x', 'y') # 只允许使用 x 和 y 28 | 29 | def __init__(self, x=0, y=0): 30 | self.x = x 31 | self.y = y 32 | ``` 33 | 34 | 上面,我们给 `__slots__` 设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个新的属性,比如 z,就会出错了,如下: 35 | 36 | ```python 37 | >>> p = Point(3, 4) 38 | >>> p.z = 5 39 | --------------------------------------------------------------------------- 40 | AttributeError Traceback (most recent call last) 41 | in () 42 | ----> 1 p.z = 5 43 | 44 | AttributeError: 'Point' object has no attribute 'z' 45 | ``` 46 | 47 | 使用 `__slots__` 有一点需要注意的是,`__slots__` 设置的属性仅对当前类有效,对继承的子类不起效,除非子类也定义了 `__slots__`,这样,子类允许定义的属性就是自身的 slots 加上父类的 slots。 48 | 49 | # 小结 50 | 51 | - __slots__ 魔法:限定允许绑定的属性. 52 | - `__slots__` 设置的属性仅对当前类有效,对继承的子类不起效,除非子类也定义了 slots,这样,子类允许定义的属性就是自身的 slots 加上父类的 slots。 53 | 54 | # 参考资料 55 | 56 | - [slots魔法 · Python进阶](https://eastlakeside.gitbooks.io/interpy-zh/content/slots_magic/) 57 | 58 | 59 | -------------------------------------------------------------------------------- /Class/super.md: -------------------------------------------------------------------------------- 1 | # 你不知道的 super 2 | 3 | 在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 `super` 来实现,比如: 4 | 5 | ```python 6 | class Animal(object): 7 | def __init__(self, name): 8 | self.name = name 9 | def greet(self): 10 | print 'Hello, I am %s.' % self.name 11 | 12 | class Dog(Animal): 13 | def greet(self): 14 | super(Dog, self).greet() # Python3 可使用 super().greet() 15 | print 'WangWang...' 16 | ``` 17 | 18 | 在上面,Animal 是父类,Dog 是子类,我们在 Dog 类重定义了 `greet` 方法,为了能同时实现父类的功能,我们又调用了父类的方法,看下面的使用: 19 | 20 | ```python 21 | >>> dog = Dog('dog') 22 | >>> dog.greet() 23 | Hello, I am dog. 24 | WangWang.. 25 | ``` 26 | 27 | `super` 的一个最常见用法可以说是在子类中调用父类的初始化方法了,比如: 28 | 29 | ```python 30 | class Base(object): 31 | def __init__(self, a, b): 32 | self.a = a 33 | self.b = b 34 | 35 | class A(Base): 36 | def __init__(self, a, b, c): 37 | super(A, self).__init__(a, b) # Python3 可使用 super().__init__(a, b) 38 | self.c = c 39 | ``` 40 | 41 | # 深入 super() 42 | 43 | 看了上面的使用,你可能会觉得 `super` 的使用很简单,无非就是获取了父类,并调用父类的方法。其实,在上面的情况下,super 获得的类刚好是父类,但在其他情况就不一定了,**super 其实和父类没有实质性的关联**。 44 | 45 | 让我们看一个稍微复杂的例子,涉及到多重继承,代码如下: 46 | 47 | ```python 48 | class Base(object): 49 | def __init__(self): 50 | print "enter Base" 51 | print "leave Base" 52 | 53 | class A(Base): 54 | def __init__(self): 55 | print "enter A" 56 | super(A, self).__init__() 57 | print "leave A" 58 | 59 | class B(Base): 60 | def __init__(self): 61 | print "enter B" 62 | super(B, self).__init__() 63 | print "leave B" 64 | 65 | class C(A, B): 66 | def __init__(self): 67 | print "enter C" 68 | super(C, self).__init__() 69 | print "leave C" 70 | ``` 71 | 72 | 其中,Base 是父类,A, B 继承自 Base, C 继承自 A, B,它们的继承关系是一个典型的『菱形继承』,如下: 73 | 74 | ``` 75 | Base 76 | / \ 77 | / \ 78 | A B 79 | \ / 80 | \ / 81 | C 82 | ``` 83 | 84 | 现在,让我们看一下使用: 85 | 86 | ```python 87 | >>> c = C() 88 | enter C 89 | enter A 90 | enter B 91 | enter Base 92 | leave Base 93 | leave B 94 | leave A 95 | leave C 96 | ``` 97 | 98 | 如果你认为 `super` 代表『调用父类的方法』,那你很可能会疑惑为什么 enter A 的下一句不是 enter Base 而是 enter B。原因是,**`super` 和父类没有实质性的关联**,现在让我们搞清 `super` 是怎么运作的。 99 | 100 | ## MRO 列表 101 | 102 | 事实上,对于你定义的每一个类,Python 会计算出一个**方法解析顺序(Method Resolution Order, MRO)列表**,**它代表了类继承的顺序**,我们可以使用下面的方式获得某个类的 MRO 列表: 103 | 104 | ```python 105 | >>> C.mro() # or C.__mro__ or C().__class__.mro() 106 | [__main__.C, __main__.A, __main__.B, __main__.Base, object] 107 | ``` 108 | 109 | 那这个 MRO 列表的顺序是怎么定的呢,它是通过一个 [C3 线性化算法](https://www.python.org/download/releases/2.3/mro/)来实现的,这里我们就不去深究这个算法了,感兴趣的读者可以自己去了解一下,总的来说,一个类的 MRO 列表就是合并所有父类的 MRO 列表,并遵循以下三条原则: 110 | 111 | - 子类永远在父类前面 112 | - 如果有多个父类,会根据它们在列表中的顺序被检查 113 | - 如果对下一个类存在两个合法的选择,选择第一个父类 114 | 115 | ## super 原理 116 | 117 | `super` 的工作原理如下: 118 | 119 | ```python 120 | def super(cls, inst): 121 | mro = inst.__class__.mro() 122 | return mro[mro.index(cls) + 1] 123 | ``` 124 | 125 | 其中,cls 代表类,inst 代表实例,上面的代码做了两件事: 126 | 127 | - 获取 inst 的 MRO 列表 128 | - 查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1] 129 | 130 | 当你使用 `super(cls, inst)` 时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。 131 | 132 | 现在,让我们回到前面的例子。 133 | 134 | 首先看类 C 的 `__init__` 方法: 135 | 136 | ``` 137 | super(C, self).__init__() 138 | ``` 139 | 140 | 这里的 self 是当前 C 的实例,self.__class__.mro() 结果是: 141 | 142 | ``` 143 | [__main__.C, __main__.A, __main__.B, __main__.Base, object] 144 | ``` 145 | 146 | 可以看到,C 的下一个类是 A,于是,跳到了 A 的 `__init__`,这时会打印出 enter A,并执行下面一行代码: 147 | 148 | ```python 149 | super(A, self).__init__() 150 | ``` 151 | 152 | 注意,这里的 self 也是当前 C 的实例,MRO 列表跟上面是一样的,搜索 A 在 MRO 中的下一个类,发现是 B,于是,跳到了 B 的 `__init__`,这时会打印出 enter B,而不是 enter Base。 153 | 154 | 整个过程还是比较清晰的,关键是要理解 super 的工作方式,而不是想当然地认为 super 调用了父类的方法。 155 | 156 | 157 | # 小结 158 | 159 | - 事实上,`super` 和父类没有实质性的关联。 160 | - `super(cls, inst)` 获得的是 cls 在 inst 的 MRO 列表中的下一个类。 161 | 162 | # 参考资料 163 | 164 | - [调用父类方法 — python3-cookbook 2.0.0 文档](http://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html) 165 | - [理解 Python super - laike9m's blog](https://laike9m.com/blog/li-jie-python-super,70/) 166 | - [python super() - 漩涡鸣人 - 博客园](http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html) 167 | - [Python:super函数 | Hom](http://gohom.win/2016/02/23/py-super/) 168 | - [Python’s super() considered super! | Deep Thoughts by Raymond Hettinger](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/) 169 | - [Python super() inheritance and needed arguments - Stack Overflow](http://stackoverflow.com/questions/15896265/python-super-inheritance-and-needed-arguments/15896594#15896594) 170 | 171 | 172 | -------------------------------------------------------------------------------- /Conclusion/README.md: -------------------------------------------------------------------------------- 1 | # 结束语 2 | 3 | 到这里,虽然本书结束了,但对于 Python 的学习和实践还远远没结束,后面我也会持续更新本书。虽然 Python 的语法相比 C++ 等语言比较简洁,但想熟练运用,仍需在实际的项目中多多实践,而不只是停留在简单的概念学习中。 4 | 5 | 这里主要推荐 Python 相关的一些学习资源,同时也列出本书的主要参考资料。 6 | 7 | - [资源推荐](./resource_recommendation.md) 8 | - [参考资料](./reference_material.md) 9 | 10 | 11 | -------------------------------------------------------------------------------- /Conclusion/reference_material.md: -------------------------------------------------------------------------------- 1 | # 参考资料 2 | 3 | 本书的参考资料主要有: 4 | 5 | - 《Python 基础教程》 6 | - 《Python 核心编程》 7 | - [廖雪峰 python 教程](https://wizardforcel.gitbooks.io/liaoxuefeng/content/py2/1.html) 8 | - [Python进阶 · GitBook](https://www.gitbook.com/book/eastlakeside/interpy-zh/details) 9 | - [python3-cookbook 2.0.0 文档](http://python3-cookbook.readthedocs.io/zh_CN/latest/index.html) 10 | 11 | -------------------------------------------------------------------------------- /Conclusion/resource_recommendation.md: -------------------------------------------------------------------------------- 1 | # 资源推荐 2 | 3 | 这里列出了 Python 相关的一些资源,欢迎读者补充。 4 | 5 | - [vinta/awesome-python: A curated list of awesome Python frameworks, libraries, software and resources](https://github.com/vinta/awesome-python) 6 | 7 | 包含了 Python 框架、Python 库和软件的 awesome 列表。 8 | 9 | - [aosabook/500lines: 500 Lines or Less](https://github.com/aosabook/500lines) 10 | 11 | Python 神书,里面有若干个项目,每个项目都是由业内大神所写,每个项目代码在 500 行左右。 12 | 13 | - [Python Module of the Week - PyMOTW 2](https://pymotw.com/2/) 14 | 15 | 自 2007 年以来,[Doug Hellmann](http://www.doughellmann.com/) 在他的博客上发表了颇受关注的「Python Module of the Week」系列,计划每周介绍一个 Python 标准库的使用。上面的链接是介绍 Python2 中的标准库,同样也有 Python3 的:[Python 3 Module of the Week — PyMOTW 3](https://pymotw.com/3/#python-3-module-of-the-week)。 16 | 17 | - [Transforming Code into Beautiful, Idiomatic Python](https://gist.github.com/JeffPaine/6213790) 18 | 19 | 写出简洁的、优雅的 Python 代码。 20 | 21 | - [jobbole/awesome-python-cn: Python资源大全中文版](https://github.com/jobbole/awesome-python-cn) 22 | 23 | Python 资源大全,包含:Web 框架、网络爬虫、模板引擎和数据库等,由[伯乐在线](https://github.com/jobbole)更新。 24 | 25 | - [Pycoder's Weekly | A Weekly Python E-Mail Newsletter](http://pycoders.com/) 26 | 27 | 优秀的免费邮件 Python 新闻周刊。 28 | 29 | - [Python 初学者的最佳学习资源](http://python.jobbole.com/82399/) 30 | 31 | 伯乐在线翻译的 Python 学习资源。 32 | 33 | - [Full Stack Python](http://www.fullstackpython.com/) 34 | 35 | Python 资源汇总,从基础入门到各种 Web 开发框架,再到高级的 ORM,Docker 等等。 36 | 37 | - [The Hitchhiker’s Guide to Python!](http://docs.python-guide.org/en/latest/) 38 | 39 | [Requests](https://github.com/kennethreitz/requests) 作者 kennethreitz 的一本开源书籍,介绍 Python 的最佳实践。 40 | 41 | - [Welcome to Python for you and me](http://pymbook.readthedocs.io/en/latest/) 42 | 43 | 介绍 Python 的基本语法,特点等。 44 | 45 | - [District Data Labs - How to Develop Quality Python Code](https://districtdatalabs.silvrback.com/how-to-develop-quality-python-code) 46 | 47 | 开发高质量的 Python 代码。 48 | 49 | - [A "Best of the Best Practices" (BOBP) guide to developing in Python.](https://gist.github.com/sloria/7001839) 50 | 51 | Python 最佳实践。 52 | 53 | 54 | -------------------------------------------------------------------------------- /Datatypes/README.md: -------------------------------------------------------------------------------- 1 | # 常用数据类型 2 | 3 | 在介绍 Python 的常用数据类型之前,我们先看看 Python 最基本的数据结构 - **序列(sequence)**。 4 | 5 | **序列**的一个特点就是根据索引(index,即元素的位置)来获取序列中的元素,第一个索引是 0,第二个索引是 1,以此类推。 6 | 7 | 所有序列类型都可以进行某些通用的操作,比如: 8 | 9 | - 索引(indexing) 10 | - 分片(slicing) 11 | - 迭代(iteration) 12 | - 加(adding) 13 | - 乘(multiplying) 14 | 15 | 除了上面这些,我们还可以检查某个元素是否属于序列的成员,计算序列的长度等等。 16 | 17 | 说完序列,我们接下来看看 Python 中常用的数据类型,如下: 18 | 19 | - [列表(list)](./list.md) 20 | - [元组(tuple)](./tuple.md) 21 | - [字符串(string)](./string.md) 22 | - [字典(dict)](./dict.md) 23 | - [集合(set)](./set.md) 24 | 25 | 其中,**列表、元组和字符串都属于序列类型,它们可以进行某些通用的操作**,比如索引、分片等;字典属于**映射**类型,每个元素由键(key)和值(value)构成;集合是一种特殊的类型,它所包含的元素是不重复的。 26 | 27 | # 通用的序列操作 28 | 29 | ## 索引 30 | 31 | 序列中的元素可以通过索引获取,索引从 0 开始。看看下面的例子: 32 | 33 | ``` 34 | >>> nums = [1, 2, 3, 4, 5] # 列表 35 | >>> nums[0] 36 | 1 37 | >>> nums[1] 38 | 2 39 | >>> nums[-1] # 索引 -1 表示最后一个元素 40 | 5 41 | >>> s = 'abcdef' # 字符串 42 | >>> s[0] 43 | 'a' 44 | >>> s[1] 45 | 'b' 46 | >>> 47 | >>> a = (1, 2, 3) # 元组 48 | >>> a[0] 49 | 1 50 | >>> a[1] 51 | 2 52 | ``` 53 | 54 | 注意到,-1 则代表序列的最后一个元素,-2 代表倒数第二个元素,以此类推。 55 | 56 | ## 分片 57 | 58 | **索引**用于获取序列中的单个元素,而**分片**则用于获取序列的部分元素。分片操作需要提供两个索引作为边界,中间用冒号相隔,比如: 59 | 60 | ```python 61 | >>> numbers = [1, 2, 3, 4, 5, 6] 62 | >>> numbers[0:2] # 列表分片 63 | [1, 2] 64 | >>> numbers[2:5] 65 | [3, 4, 5] 66 | >>> s = 'hello, world' # 字符串分片 67 | >>> s[0:5] 68 | 'hello' 69 | >>> a = (2, 4, 6, 8, 10) # 元组分片 70 | >>> a[2:4] 71 | (6, 8) 72 | ``` 73 | 74 | **这里需要特别注意的是,分片有两个索引,第 1 个索引的元素是包含在内的,而第 2 个元素的索引则不包含在内**,也就是说,numbers[2:5] 获取的是 numbers[2], numbers[3], numbers[4],没有包括 numbers[5]。 75 | 76 | 下面列举使用分片的一些技巧。 77 | 78 | - 访问最后几个元素 79 | 80 | 假设需要访问序列的最后 3 个元素,我们当然可以像下面这样做: 81 | 82 | ```python 83 | >>> numbers = [1, 2, 3, 4, 5, 6] 84 | >>> numbers[3:6] 85 | [4, 5, 6] 86 | ``` 87 | 88 | 有没有更简洁的方法呢?想到可以使用负数形式的索引,你可能会尝试这样做: 89 | 90 | ```python 91 | >>> numbers = [1, 2, 3, 4, 5, 6] 92 | >>> numbers[-3:-1] # 实际取出的是 numbers[-3], numbers[-2] 93 | [4, 5] 94 | >>> numbers[-3:0] # 左边索引的元素比右边索引出现得晚,返回空序列 95 | [] 96 | ``` 97 | 98 | 上面的两种使用方式并不能正确获取序列的最后 3 个元素,Python 提供了一个捷径: 99 | 100 | ```python 101 | >>> numbers = [1, 2, 3, 4, 5, 6, 7, 8] 102 | >>> numbers[-3:] 103 | [6, 7, 8] 104 | >>> numbers[5:] 105 | [6, 7, 8] 106 | ``` 107 | 108 | 也就是说,如果希望分片包含最后一个元素,可将第 2 个索引置为空。 109 | 110 | **如果要复制整个序列,可以将两个索引都置为空**: 111 | 112 | ```python 113 | >>> numbers = [1, 2, 3, 4, 5, 6, 7, 8] 114 | >>> nums = numbers[:] 115 | >>> nums 116 | [1, 2, 3, 4, 5, 6, 7, 8] 117 | ``` 118 | 119 | - 使用步长 120 | 121 | 使用分片的时候,步长默认是 1,即逐个访问,我们也可以自定义步长,比如: 122 | 123 | ```python 124 | >>> numbers = [1, 2, 3, 4, 5, 6, 7, 8] 125 | >>> numbers[0:4] 126 | [1, 2, 3, 4] 127 | >>> numbers[0:4:1] # 步长为 1,不写也可以,默认为 1 128 | [1, 2, 3, 4] 129 | >>> numbers[0:4:2] # 步长为 2,取出 numbers[0], numbers[2] 130 | [1, 3] 131 | >>> numbers[::3] # 等价于 numbers[0:8:3],取出索引为 0, 3, 6 的元素 132 | [1, 4, 7] 133 | ``` 134 | 135 | 另外,步长也可以是负数,表示从右到左取元素: 136 | 137 | ```python 138 | >>> numbers = [1, 2, 3, 4, 5, 6, 7, 8] 139 | >>> numbers[0:4:-1] 140 | [] 141 | >>> numbers[4:0:-1] # 取出索引为 4, 3, 2, 1 的元素 142 | [5, 4, 3, 2] 143 | >>> numbers[4:0:-2] # 取出索引为 4, 2 的元素 144 | [5, 3] 145 | >>> numbers[::-1] # 从右到左取出所有元素 146 | [8, 7, 6, 5, 4, 3, 2, 1] 147 | >>> numbers[::-2] # 取出索引为 7, 5, 3, 1 的元素 148 | [8, 6, 4, 2] 149 | >>> numbers[6::-2] # 取出索引为 6, 4, 2, 0 的元素 150 | [7, 5, 3, 1] 151 | >>> numbers[:6:-2] # 取出索引为 7 的元素 152 | [8] 153 | ``` 154 | 155 | 这里总结一下使用分片操作的一些方法,分片的使用形式是: 156 | 157 | ```python 158 | # 左索引:右索引:步长 159 | left_index:right_index:step 160 | ``` 161 | 162 | 要牢牢记住的是: 163 | 164 | - 左边索引的元素包括在结果之中,右边索引的元素不包括在结果之中; 165 | - 当使用一个负数作为步长时,必须让左边索引大于右边索引; 166 | - 对正数步长,从左向右取元素;对负数步长,从右向左取元素; 167 | 168 | ## 加 169 | 170 | 序列可以进行「加法」操作,如下: 171 | 172 | ```python 173 | >>> [1, 2, 3] + [4, 5, 6] # 「加法」效果其实就是连接在一起 174 | [1, 2, 3, 4, 5, 6] 175 | >>> (1, 2, 3) + (4, 5, 6) 176 | (1, 2, 3, 4, 5, 6) 177 | >>> 'hello, ' + 'world!' 178 | 'hello, world!' 179 | >>> [1, 2, 3] + 'abc' 180 | Traceback (most recent call last): 181 | File "", line 1, in 182 | TypeError: can only concatenate list (not "str") to list 183 | ``` 184 | 185 | 这里需要注意的是:两种相同类型的序列才能「加法」操作。 186 | 187 | ## 乘 188 | 189 | 序列可以进行「乘法」操作,比如: 190 | 191 | ```python 192 | >>> 'abc' * 3 193 | 'abcabcabc' 194 | >>> [0] * 3 195 | [0, 0, 0] 196 | >>> [1, 2, 3] * 3 197 | [1, 2, 3, 1, 2, 3, 1, 2, 3] 198 | ``` 199 | 200 | ## in 201 | 202 | 为了检查一个值是否在序列中,可以使用 `in` 运算符,比如: 203 | 204 | ```python 205 | >>> 'he' in 'hello' 206 | True 207 | >>> 'hl' in 'hello' 208 | False 209 | >>> 10 in [6, 8, 10] 210 | True 211 | ``` 212 | 213 | # 参考资料 214 | 215 | - 《Python 基础教程》 216 | 217 | 218 | -------------------------------------------------------------------------------- /Datatypes/dict.md: -------------------------------------------------------------------------------- 1 | # 字典 2 | 3 | 字典是 Python 中唯一的映射类型,每个元素由键(key)和值(value)构成,键必须是不可变类型,比如数字、字符串和元组。 4 | 5 | # 字典基本操作 6 | 7 | 这里先介绍字典的几个基本操作,后文再介绍字典的常用方法。 8 | 9 | - 创建字典 10 | - 遍历字典 11 | - 判断键是否在字典里面 12 | 13 | ## 创建字典 14 | 15 | 字典可以通过下面的方式创建: 16 | 17 | ```python 18 | >>> d0 = {} # 空字典 19 | >>> d0 20 | {} 21 | >>> d1 = {'name': 'ethan', 'age': 20} 22 | >>> d1 23 | {'age': 20, 'name': 'ethan'} 24 | >>> d1['age'] = 21 # 更新字典 25 | >>> d1 26 | {'age': 21, 'name': 'ethan'} 27 | >>> d2 = dict(name='ethan', age=20) # 使用 dict 函数 28 | >>> d2 29 | {'age': 20, 'name': 'ethan'} 30 | >>> item = [('name', 'ethan'), ('age', 20)] 31 | >>> d3 = dict(item) 32 | >>> d3 33 | {'age': 20, 'name': 'ethan'} 34 | ``` 35 | 36 | ## 遍历字典 37 | 38 | 遍历字典有多种方式,这里先介绍一些基本的方式,后文会介绍一些高效的遍历方式。 39 | 40 | ```python 41 | >>> d = {'name': 'ethan', 'age': 20} 42 | >>> for key in d: 43 | ... print '%s: %s' % (key, d[key]) 44 | ... 45 | age: 20 46 | name: ethan 47 | >>> d['name'] 48 | 'ethan' 49 | >>> d['age'] 50 | 20 51 | >>> for key in d: 52 | ... if key == 'name': 53 | ... del d[key] # 要删除字典的某一项 54 | ... 55 | Traceback (most recent call last): 56 | File "", line 1, in 57 | RuntimeError: dictionary changed size during iteration 58 | >>> 59 | >>> for key in d.keys(): # python2 应该使用这种方式, python3 使用 list(d.keys()) 60 | ... if key == 'name': 61 | ... del d[key] 62 | ... 63 | >>> d 64 | {'age': 20} 65 | ``` 66 | 67 | 在上面,我们介绍了两种遍历方式:`for key in d` 和 `for key in d.keys()`,如果在遍历的时候,要删除键为 key 的某项,使用第一种方式会抛出 RuntimeError,使用第二种方式则不会。 68 | 69 | ## 判断键是否在字典里面 70 | 71 | 有时,我们需要判断某个键是否在字典里面,这时可以用 `in` 进行判断,如下: 72 | 73 | ```python 74 | >>> d = {'name': 'ethan', 'age': 20} 75 | >>> 'name' in d 76 | True 77 | >>> d['score'] # 访问不存在的键,会抛出 KeyError 78 | Traceback (most recent call last): 79 | File "", line 1, in 80 | KeyError: 'score' 81 | >>> 'score' in d # 使用 in 判断 key 是否在字典里面 82 | False 83 | ``` 84 | 85 | # 字典常用方法 86 | 87 | 字典有自己的一些操作方法,这里只介绍部分常用的方法: 88 | 89 | - clear 90 | - copy 91 | - get 92 | - setdefault 93 | - update 94 | - pop 95 | - popitem 96 | - keys/iterkeys 97 | - values/itervalues 98 | - items/iteritems 99 | - fromkeys 100 | 101 | ## clear 102 | 103 | clear 方法用于清空字典中的所有项,这是个原地操作,所以无返回值(或者说是 None)。 104 | 105 | 看看例子: 106 | 107 | ```python 108 | >>> d = {'name': 'ethan', 'age': 20} 109 | >>> rv = d.clear() 110 | >>> d 111 | {} 112 | >>> print rv 113 | None 114 | ``` 115 | 116 | 再看看一个例子: 117 | 118 | ```python 119 | >>> d1 = {} 120 | >>> d2 = d1 121 | >>> d2['name'] = 'ethan' 122 | >>> d1 123 | {'name': 'ethan'} 124 | >>> d2 125 | {'name': 'ethan'} 126 | >>> d1 = {} # d1 变为空字典 127 | >>> d2 128 | {'name': 'ethan'} # d2 不受影响 129 | ``` 130 | 131 | 在上面,d1 和 d2 最初对应同一个字典,而后我们使用 `d1 = {}` 使其变成一个空字典,但此时 d2 不受影响。如果希望 d1 变成空字典之后,d2 也变成空字典,则可以使用 clear 方法: 132 | 133 | ```python 134 | >>> d1 = {} 135 | >>> d2 = d1 136 | >>> d2['name'] = 'ethan' 137 | >>> d1 138 | {'name': 'ethan'} 139 | >>> d2 140 | {'name': 'ethan'} 141 | >>> d1.clear() # d1 清空之后,d2 也为空 142 | >>> d1 143 | {} 144 | >>> d2 145 | {} 146 | ``` 147 | 148 | ## copy 149 | 150 | copy 方法实现的是浅复制(shallow copy)。它具有以下特点: 151 | 152 | - 对可变对象的修改保持同步; 153 | - 对不可变对象的修改保持独立; 154 | 155 | 看看例子: 156 | 157 | ```python 158 | # name 的值是不可变对象,books 的值是可变对象 159 | >>> d1 = {'name': 'ethan', 'books': ['book1', 'book2', 'book3']} 160 | >>> d2 = d1.copy() 161 | >>> d2['name'] = 'peter' # d2 对不可变对象的修改不会改变 d1 162 | >>> d2 163 | {'books': ['book1', 'book2', 'book3'], 'name': 'peter'} 164 | >>> d1 165 | {'books': ['book1', 'book2', 'book3'], 'name': 'ethan'} 166 | >>> d2['books'].remove('book2') # d2 对可变对象的修改会影响 d1 167 | >>> d2 168 | {'books': ['book1', 'book3'], 'name': 'peter'} 169 | >>> d1 170 | {'books': ['book1', 'book3'], 'name': 'ethan'} 171 | >>> d1['books'].remove('book3') # d1 对可变对象的修改会影响 d2 172 | >>> d1 173 | {'books': ['book1'], 'name': 'ethan'} 174 | >>> d2 175 | {'books': ['book1'], 'name': 'peter'} 176 | ``` 177 | 178 | 和浅复制对应的是深复制(deep copy),它会创造出一个副本,跟原来的对象没有关系,可以通过 copy 模块的 deepcopy 函数来实现: 179 | 180 | ```python 181 | >>> from copy import deepcopy 182 | >>> d1 = {'name': 'ethan', 'books': ['book1', 'book2', 'book3']} 183 | >>> d2 = deepcopy(d1) # 创造出一个副本 184 | >>> 185 | >>> d2['books'].remove('book2') # 对 d2 的任何修改不会影响到 d1 186 | >>> d2 187 | {'books': ['book1', 'book3'], 'name': 'ethan'} 188 | >>> d1 189 | {'books': ['book1', 'book2', 'book3'], 'name': 'ethan'} 190 | >>> 191 | >>> d1['books'].remove('book3') # 对 d1 的任何修改也不会影响到 d2 192 | >>> d1 193 | {'books': ['book1', 'book2'], 'name': 'ethan'} 194 | >>> d2 195 | {'books': ['book1', 'book3'], 'name': 'ethan'} 196 | ``` 197 | 198 | ## get 199 | 200 | 当我们试图访问字典中不存在的项时会出现 KeyError,但使用 get 就可以避免这个问题。 201 | 202 | 看看例子: 203 | 204 | ```python 205 | >>> d = {} 206 | >>> d['name'] 207 | Traceback (most recent call last): 208 | File "", line 1, in 209 | KeyError: 'name' 210 | >>> print d.get('name') 211 | None 212 | >>> d.get('name', 'ethan') # 'name' 不存在,使用默认值 'ethan' 213 | 'ethan' 214 | >>> d 215 | {} 216 | ``` 217 | 218 | ## setdefault 219 | 220 | setdefault 方法用于对字典设定键值。使用形式如下: 221 | 222 | ``` 223 | dict.setdefault(key, default=None) 224 | ``` 225 | 226 | 看看例子: 227 | 228 | ```python 229 | >>> d = {} 230 | >>> d.setdefault('name', 'ethan') # 返回设定的默认值 'ethan' 231 | 'ethan' 232 | >>> d # d 被更新 233 | {'name': 'ethan'} 234 | >>> d['age'] = 20 235 | >>> d 236 | {'age': 20, 'name': 'ethan'} 237 | >>> d.setdefault('age', 18) # age 已存在,返回已有的值,不会更新字典 238 | 20 239 | >>> d 240 | {'age': 20, 'name': 'ethan'} 241 | ``` 242 | 243 | 可以看到,当键不存在的时候,setdefault 返回设定的默认值并且更新字典。当键存在的时候,会返回已有的值,但不会更新字典。 244 | 245 | ## update 246 | 247 | update 方法用于将一个字典添加到原字典,如果存在相同的键则会进行覆盖。 248 | 249 | 看看例子: 250 | 251 | ```python 252 | >>> d = {} 253 | >>> d1 = {'name': 'ethan'} 254 | >>> d.update(d1) # 将字典 d1 添加到 d 255 | >>> d 256 | {'name': 'ethan'} 257 | >>> d2 = {'age': 20} 258 | >>> d.update(d2) # 将字典 d2 添加到 d 259 | >>> d 260 | {'age': 20, 'name': 'ethan'} 261 | >>> d3 = {'name': 'michael'} # 将字典 d3 添加到 d,存在相同的 key,则覆盖 262 | >>> d.update(d3) 263 | >>> d 264 | {'age': 20, 'name': 'michael'} 265 | ``` 266 | ## items/iteritems 267 | 268 | items 方法将所有的字典项以列表形式返回,这些列表项的每一项都来自于(键,值)。我们也经常使用这个方法来对字典进行遍历。 269 | 270 | 看看例子: 271 | 272 | ```python 273 | >>> d = {'name': 'ethan', 'age': 20} 274 | >>> d.items() 275 | [('age', 20), ('name', 'ethan')] 276 | >>> for k, v in d.items(): 277 | ... print '%s: %s' % (k, v) 278 | ... 279 | age: 20 280 | name: ethan 281 | ``` 282 | 283 | iteritems 的作用大致相同,但会返回一个迭代器对象而不是列表,同样,我们也可以使用这个方法来对字典进行遍历,而且这也是推荐的做法: 284 | 285 | ```python 286 | >>> d = {'name': 'ethan', 'age': 20} 287 | >>> d.iteritems() 288 | 289 | >>> for k, v in d.iteritems(): 290 | ... print '%s: %s' % (k, v) 291 | ... 292 | age: 20 293 | name: ethan 294 | ``` 295 | 296 | ## keys/iterkeys 297 | 298 | keys 方法将字典的键以列表形式返回,iterkeys 则返回针对键的迭代器。 299 | 300 | 看看例子: 301 | 302 | ```python 303 | >>> d = {'name': 'ethan', 'age': 20} 304 | >>> d.keys() 305 | ['age', 'name'] 306 | >>> d.iterkeys() 307 | 308 | ``` 309 | 310 | ## values/itervalues 311 | 312 | values 方法将字典的值以列表形式返回,itervalues 则返回针对值的迭代器。 313 | 314 | 看看例子: 315 | 316 | ```python 317 | >>> d = {'name': 'ethan', 'age': 20} 318 | >>> d.values() 319 | [20, 'ethan'] 320 | >>> d.itervalues() 321 | 322 | ``` 323 | 324 | ## pop 325 | 326 | pop 方法用于将某个键值对从字典移除,并返回给定键的值。 327 | 328 | 看看例子: 329 | 330 | ```python 331 | >>> d = {'name': 'ethan', 'age': 20} 332 | >>> d.pop('name') 333 | 'ethan' 334 | >>> d 335 | {'age': 20} 336 | ``` 337 | 338 | ## popitem 339 | 340 | popitem 用于随机移除字典中的某个键值对。 341 | 342 | 看看例子: 343 | 344 | ```python 345 | >>> d = {'id': 10, 'name': 'ethan', 'age': 20} 346 | >>> d.popitem() 347 | ('age', 20) 348 | >>> d 349 | {'id': 10, 'name': 'ethan'} 350 | >>> d.popitem() 351 | ('id', 10) 352 | >>> d 353 | {'name': 'ethan'} 354 | ``` 355 | 356 | # 对元素为字典的列表排序 357 | 358 | 事实上,我们很少直接对字典进行排序,而是对元素为字典的列表进行排序。 359 | 360 | 比如,存在下面的 students 列表,它的元素是字典: 361 | 362 | ```python 363 | students = [ 364 | {'name': 'john', 'score': 'B', 'age': 15}, 365 | {'name': 'jane', 'score': 'A', 'age': 12}, 366 | {'name': 'dave', 'score': 'B', 'age': 10}, 367 | {'name': 'ethan', 'score': 'C', 'age': 20}, 368 | {'name': 'peter', 'score': 'B', 'age': 20}, 369 | {'name': 'mike', 'score': 'C', 'age': 16} 370 | ] 371 | ``` 372 | 373 | - 按 score 从小到大排序 374 | 375 | ```python 376 | >>> sorted(students, key=lambda stu: stu['score']) 377 | [{'age': 12, 'name': 'jane', 'score': 'A'}, 378 | {'age': 15, 'name': 'john', 'score': 'B'}, 379 | {'age': 10, 'name': 'dave', 'score': 'B'}, 380 | {'age': 20, 'name': 'peter', 'score': 'B'}, 381 | {'age': 20, 'name': 'ethan', 'score': 'C'}, 382 | {'age': 16, 'name': 'mike', 'score': 'C'}] 383 | ``` 384 | 385 | 需要注意的是,这里是按照字母的 ascii 大小排序的,所以 score 从小到大,即从 'A' 到 'C'。 386 | 387 | 388 | - 按 score 从大到小排序 389 | 390 | ```python 391 | >>> sorted(students, key=lambda stu: stu['score'], reverse=True) # reverse 参数 392 | [{'age': 20, 'name': 'ethan', 'score': 'C'}, 393 | {'age': 16, 'name': 'mike', 'score': 'C'}, 394 | {'age': 15, 'name': 'john', 'score': 'B'}, 395 | {'age': 10, 'name': 'dave', 'score': 'B'}, 396 | {'age': 20, 'name': 'peter', 'score': 'B'}, 397 | {'age': 12, 'name': 'jane', 'score': 'A'}] 398 | ``` 399 | 400 | - 按 score 从小到大,再按 age 从小到大 401 | 402 | ```python 403 | >>> sorted(students, key=lambda stu: (stu['score'], stu['age'])) 404 | [{'age': 12, 'name': 'jane', 'score': 'A'}, 405 | {'age': 10, 'name': 'dave', 'score': 'B'}, 406 | {'age': 15, 'name': 'john', 'score': 'B'}, 407 | {'age': 20, 'name': 'peter', 'score': 'B'}, 408 | {'age': 16, 'name': 'mike', 'score': 'C'}, 409 | {'age': 20, 'name': 'ethan', 'score': 'C'}] 410 | ``` 411 | 412 | - 按 score 从小到大,再按 age 从大到小 413 | 414 | ```python 415 | >>> sorted(students, key=lambda stu: (stu['score'], -stu['age'])) 416 | [{'age': 12, 'name': 'jane', 'score': 'A'}, 417 | {'age': 20, 'name': 'peter', 'score': 'B'}, 418 | {'age': 15, 'name': 'john', 'score': 'B'}, 419 | {'age': 10, 'name': 'dave', 'score': 'B'}, 420 | {'age': 20, 'name': 'ethan', 'score': 'C'}, 421 | {'age': 16, 'name': 'mike', 'score': 'C'}] 422 | ``` 423 | 424 | # 参考资料 425 | 426 | - 《Python 基础教程》 427 | - [python字典和集合 | Alex's Blog](http://codingnow.cn/language/353.html) 428 | - [Python中实现多属性排序 | 酷 壳 - CoolShell.cn](http://coolshell.cn/articles/435.html) 429 | - [HowTo/Sorting - Python Wiki](https://wiki.python.org/moin/HowTo/Sorting) 430 | - [How do I sort a list of dictionaries by values of the dictionary in Python? - Stack Overflow](http://stackoverflow.com/questions/72899/how-do-i-sort-a-list-of-dictionaries-by-values-of-the-dictionary-in-python) 431 | 432 | 433 | -------------------------------------------------------------------------------- /Datatypes/list.md: -------------------------------------------------------------------------------- 1 | # 列表 2 | 3 | 字符串和元组是不可变的,而列表是可变(mutable)的,可以对它进行随意修改。我们还可以将字符串和元组转换成一个列表,只需使用 `list` 函数,比如: 4 | 5 | ```python 6 | >>> s = 'hello' 7 | >>> list(s) 8 | ['h', 'e', 'l', 'l', 'o'] 9 | >>> a = (1, 2, 3) 10 | >>> list(a) 11 | [1, 2, 3] 12 | ``` 13 | 14 | 本文主要介绍常用的列表方法: 15 | 16 | - index 17 | - count 18 | - append 19 | - extend 20 | - insert 21 | - pop 22 | - remove 23 | - reverse 24 | - sort 25 | 26 | # index 27 | 28 | index 方法用于从列表中找出某个元素的位置,如果有多个相同的元素,则返回第一个元素的位置。 29 | 30 | 看看例子: 31 | 32 | ```python 33 | >>> numbers = [1, 2, 3, 4, 5, 5, 7, 8] 34 | >>> numbers.index(5) # 列表有两个 5,返回第一个元素的位置 35 | 4 36 | >>> numbers.index(2) 37 | 1 38 | >>> words = ['hello', 'world', 'you', 'me', 'he'] 39 | >>> words.index('me') 40 | 3 41 | >>> words.index('her') # 如果没找到元素,则会抛出异常 42 | Traceback (most recent call last): 43 | File "", line 1, in 44 | ValueError: 'her' is not in list 45 | ``` 46 | 47 | # count 48 | 49 | count 方法用于统计某个元素在列表中出现的次数。 50 | 51 | 看看例子: 52 | 53 | ```python 54 | >>> numbers = [1, 2, 3, 4, 5, 5, 6, 7] 55 | >>> numbers.count(2) # 出现一次 56 | 1 57 | >>> numbers.count(5) # 出现了两次 58 | 2 59 | >>> numbers.count(9) # 没有该元素,返回 0 60 | 0 61 | ``` 62 | 63 | # append 64 | 65 | append 方法用于在列表末尾增加新的元素。 66 | 67 | 看看例子: 68 | 69 | ```python 70 | >>> numbers = [1, 2, 3, 4, 5, 5, 6, 7] 71 | >>> numbers.append(8) # 增加 8 这个元素 72 | >>> numbers 73 | [1, 2, 3, 4, 5, 5, 6, 7, 8] 74 | >>> numbers.append([9, 10]) # 增加 [9, 10] 这个元素 75 | >>> numbers 76 | [1, 2, 3, 4, 5, 5, 6, 7, 8, [9, 10]] 77 | ``` 78 | 79 | # extend 80 | 81 | extend 方法将一个新列表的元素添加到原列表中。 82 | 83 | 看看例子: 84 | 85 | ```python 86 | >>> a = [1, 2, 3] 87 | >>> b = [4, 5, 6] 88 | >>> a.extend(b) 89 | >>> a 90 | [1, 2, 3, 4, 5, 6] 91 | >>> 92 | >>> a.extend(3) 93 | Traceback (most recent call last): 94 | File "", line 1, in 95 | TypeError: 'int' object is not iterable 96 | >>> a.extend([3]) 97 | >>> a 98 | [1, 2, 3, 4, 5, 6, 3] 99 | ``` 100 | 101 | 注意到,虽然 append 和 extend 可接收一个列表作为参数,但是 append 方法是将其作为一个元素添加到列表中,而 extend 则是将新列表的元素逐个添加到原列表中。 102 | 103 | # insert 104 | 105 | insert 方法用于将某个元素添加到某个位置。 106 | 107 | 看看例子: 108 | 109 | ```python 110 | >>> numbers = [1, 2, 3, 4, 5, 6] 111 | >>> numbers.insert(3, 9) 112 | >>> numbers 113 | [1, 2, 3, 9, 4, 5, 6] 114 | ``` 115 | 116 | # pop 117 | 118 | pop 方法用于移除列表中的一个元素(默认是最后一个),并且返回该元素的值。 119 | 120 | 看看例子: 121 | 122 | ```python 123 | >>> numbers = [1, 2, 3, 4, 5, 6] 124 | >>> numbers.pop() 125 | 6 126 | >>> numbers 127 | [1, 2, 3, 4, 5] 128 | >>> numbers.pop(3) 129 | 4 130 | >>> numbers 131 | [1, 2, 3, 5] 132 | ``` 133 | 134 | # remove 135 | 136 | remove 方法用于移除列表中的某个匹配元素,如果有多个匹配,则移除第一个。 137 | 138 | 看看例子: 139 | 140 | ```python 141 | >>> numbers = [1, 2, 3, 5, 6, 7, 5, 8] 142 | >>> numbers.remove(5) # 有两个 5,移除第 1 个 143 | >>> numbers 144 | [1, 2, 3, 6, 7, 5, 8] 145 | >>> numbers.remove(9) # 没有匹配的元素,抛出异常 146 | Traceback (most recent call last): 147 | File "", line 1, in 148 | ValueError: list.remove(x): x not in list 149 | ``` 150 | 151 | # reverse 152 | 153 | reverse 方法用于将列表中的元素进行反转。 154 | 155 | 看看例子: 156 | 157 | ```python 158 | >>> numbers = [1, 2, 3, 5, 6, 7, 5, 8] 159 | >>> numbers.reverse() 160 | >>> numbers 161 | [8, 5, 7, 6, 5, 3, 2, 1] 162 | ``` 163 | 164 | # sort 165 | 166 | sort 方法用于对列表进行排序,注意该方法会改变原来的列表,而不是返回新的排序列表,另外,sort 方法的返回值是空。 167 | 168 | 看看例子: 169 | 170 | ```python 171 | >>> a = [4, 3, 6, 8, 9, 1] 172 | >>> b = a.sort() 173 | >>> b == None # 返回值为空 174 | True 175 | >>> a 176 | [1, 3, 4, 6, 8, 9] # 原列表已经发生改变 177 | ``` 178 | 179 | 如果我们不想改变原列表,而是希望返回一个排序后的列表,可以使用 sorted 函数,如下: 180 | 181 | ```python 182 | >>> a = [4, 3, 6, 8, 9, 1] 183 | >>> b = sorted(a) # 返回一个排序后的列表 184 | >>> a 185 | [4, 3, 6, 8, 9, 1] # 原列表没有改变 186 | >>> b 187 | [1, 3, 4, 6, 8, 9] # 这是对原列表排序后的列表 188 | ``` 189 | 190 | 注意到,不管是 sort 方法还是 sorted 函数,默认排序都是升序排序。如果你想要降序排序,就需要指定排序参数了。比如,对 sort 方法,可以添加一个 reverse 关键字参数,如下: 191 | 192 | ```python 193 | >>> a = [4, 3, 6, 8, 9, 1] 194 | >>> a.sort(reverse=True) # 反向排序 195 | >>> a 196 | [9, 8, 6, 4, 3, 1] 197 | ``` 198 | 199 | 该参数对 sorted 函数同样适用: 200 | 201 | ```python 202 | >>> a = [4, 3, 6, 8, 9, 1] 203 | >>> sorted(a, reverse=True) 204 | [9, 8, 6, 4, 3, 1] 205 | ``` 206 | 207 | 除了 reverse 关键字参数,还可以指定 key 关键字参数,它为每个元素创建一个键,然后所有元素按照这个键来排序,比如我们想根据元素的长度来排序: 208 | 209 | ```python 210 | >>> s = ['ccc', 'a', 'bb', 'dddd'] 211 | >>> s.sort(key=len) # 使用 len 作为键函数,根据元素长度排序 212 | >>> s 213 | ['a', 'bb', 'ccc', 'dddd'] 214 | ``` 215 | 216 | 另外,我们还可以使用 sorted 进行多列(属性)排序。 217 | 218 | 看看例子: 219 | 220 | ```python 221 | >>> students = [ 222 | ('john', 'B', 15), 223 | ('jane', 'A', 12), 224 | ('dave', 'B', 10), 225 | ('ethan', 'C', 20), 226 | ('peter', 'B', 20), 227 | ('mike', 'C', 16) 228 | ] 229 | >>> 230 | # 对第 3 列排序 (从小到大) 231 | >>> sorted(students, key=lambda student: student[2]) 232 | [('dave', 'B', 10), 233 | ('jane', 'A', 12), 234 | ('john', 'B', 15), 235 | ('mike', 'C', 16), 236 | ('ethan', 'C', 20), 237 | ('peter', 'B', 20)] 238 | 239 | # 对第 2 列排序(从小到大),再对第 3 列从大到小排序 240 | >>> sorted(students, key=lambda student: (student[1], -student[2])) 241 | [('jane', 'A', 12), 242 | ('peter', 'B', 20), 243 | ('john', 'B', 15), 244 | ('dave', 'B', 10), 245 | ('ethan', 'C', 20), 246 | ('mike', 'C', 16)] 247 | ``` 248 | 249 | 如果你想了解更多关于排序的知识,可以参考[此文](https://wiki.python.org/moin/HowTo/Sorting)。 250 | 251 | # 小结 252 | 253 | - 列表是可变的。 254 | - 列表常用的方法有 index, count, append, extend 等。 255 | 256 | # 参考资料 257 | 258 | - 《Python 基础教程》 259 | - [HowTo/Sorting - Python Wiki](https://wiki.python.org/moin/HowTo/Sorting) 260 | 261 | 262 | -------------------------------------------------------------------------------- /Datatypes/set.md: -------------------------------------------------------------------------------- 1 | # 集合 2 | 3 | 集合(set)和字典(dict)类似,它是一组 key 的集合,但不存储 value。集合的特性就是:key 不能重复。 4 | 5 | # 集合常用操作 6 | 7 | ## 创建集合 8 | 9 | set 的创建可以使用 `{}` 也可以使用 set 函数: 10 | 11 | ```python 12 | >>> s1 = {'a', 'b', 'c', 'a', 'd', 'b'} # 使用 {} 13 | >>> s1 14 | set(['a', 'c', 'b', 'd']) 15 | >>> 16 | >>> s2 = set('helloworld') # 使用 set(),接收一个字符串 17 | >>> s2 18 | set(['e', 'd', 'h', 'l', 'o', 'r', 'w']) 19 | >>> 20 | >>> s3 = set(['.mp3', '.mp4', '.rmvb', '.mkv', '.mp3']) # 使用 set(),接收一个列表 21 | >>> s3 22 | set(['.mp3', '.mkv', '.rmvb', '.mp4']) 23 | ``` 24 | 25 | ## 遍历集合 26 | 27 | ```python 28 | >>> s = {'a', 'b', 'c', 'a', 'd', 'b'} 29 | >>> for e in s: 30 | ... print e 31 | ... 32 | a 33 | c 34 | b 35 | d 36 | ``` 37 | 38 | ## 添加元素 39 | 40 | `add()` 方法可以将元素添加到 set 中,可以重复添加,但没有效果。 41 | 42 | ```python 43 | >>> s = {'a', 'b', 'c', 'a', 'd', 'b'} 44 | >>> s 45 | set(['a', 'c', 'b', 'd']) 46 | >>> s.add('e') 47 | >>> s 48 | set(['a', 'c', 'b', 'e', 'd']) 49 | >>> s.add('a') 50 | >>> s 51 | set(['a', 'c', 'b', 'e', 'd']) 52 | >>> s.add(4) 53 | >>> s 54 | set(['a', 'c', 'b', 4, 'd', 'e']) 55 | ``` 56 | 57 | ## 删除元素 58 | 59 | `remove()` 方法可以删除集合中的元素, 但是删除不存在的元素,会抛出 KeyError,可改用 `discard()`。 60 | 61 | 看看例子: 62 | 63 | ```python 64 | >>> s = {'a', 'b', 'c', 'a', 'd', 'b'} 65 | >>> s 66 | set(['a', 'c', 'b', 'd']) 67 | >>> s.remove('a') # 删除元素 'a' 68 | >>> s 69 | set(['c', 'b', 'd']) 70 | >>> s.remove('e') # 删除不存在的元素,会抛出 KeyError 71 | Traceback (most recent call last): 72 | File "", line 1, in 73 | KeyError: 'e' 74 | >>> s.discard('e') # 删除不存在的元素, 不会抛出 KeyError 75 | ``` 76 | 77 | ## 交集/并集/差集 78 | 79 | Python 中的集合也可以看成是数学意义上的无序和无重复元素的集合,因此,我们可以对两个集合作交集、并集等。 80 | 81 | 看看例子: 82 | 83 | ```python 84 | >>> s1 = {1, 2, 3, 4, 5, 6} 85 | >>> s2 = {3, 6, 9, 10, 12} 86 | >>> s3 = {2, 3, 4} 87 | >>> s1 & s2 # 交集 88 | set([3, 6]) 89 | >>> s1 | s2 # 并集 90 | set([1, 2, 3, 4, 5, 6, 9, 10, 12]) 91 | >>> s1 - s2 # 差集 92 | set([1, 2, 4, 5]) 93 | >>> s3.issubset(s1) # s3 是否是 s1 的子集 94 | True 95 | >>> s3.issubset(s2) # s3 是否是 s2 的子集 96 | False 97 | >>> s1.issuperset(s3) # s1 是否是 s3 的超集 98 | True 99 | >>> s1.issuperset(s2) # s1 是否是 s2 的超集 100 | False 101 | ``` 102 | 103 | # 参考资料 104 | 105 | - [使用dict和set - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868193482529754158abf734c00bba97c87f89a263b000) 106 | - [python字典和集合 | Alex's Blog](http://codingnow.cn/language/353.html) 107 | - [python - Why is it possible to replace set() with {}? - Stack Overflow](http://stackoverflow.com/questions/36674083/why-is-it-possible-to-replace-set-with) 108 | 109 | 110 | -------------------------------------------------------------------------------- /Datatypes/string.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | 字符串也是一种序列,因此,通用的序列操作,比如索引,分片,加法,乘法等对它同样适用。比如: 4 | 5 | ```python 6 | >>> s = 'hello, ' 7 | >>> s[0] # 索引 8 | 'h' 9 | >>> s[1:3] # 分片 10 | 'el' 11 | >>> s + 'world' # 加法 12 | 'hello, world' 13 | >>> s * 2 # 乘法 14 | 'hello, hello, ' 15 | ``` 16 | 17 | 但需要注意的是,字符串和元组一样,也是不可变的,所以你不能对它进行赋值等操作: 18 | 19 | ```python 20 | >>> s = 'hello' 21 | >>> s[1] = 'ab' # 不能对它进行赋值 22 | Traceback (most recent call last): 23 | File "", line 1, in 24 | TypeError: 'str' object does not support item assignment 25 | ``` 26 | 27 | 除了通用的序列操作,字符串还有自己的方法,比如 join, lower, upper 等。字符串的方法特别多,这里只介绍一些常用的方法,如下: 28 | 29 | - find 30 | - split 31 | - join 32 | - strip 33 | - replace 34 | - translate 35 | - lower/upper 36 | 37 | # find 38 | 39 | find 方法用于在一个字符串中查找子串,它返回子串所在位置的最左端索引,如果没有找到,则返回 -1。 40 | 41 | 看看例子: 42 | 43 | ```python 44 | >>> motto = "to be or not to be, that is a question" 45 | >>> motto.find('be') # 返回 'b' 所在的位置,即 3 46 | 3 47 | >>> motto.find('be', 4) # 指定从起始位置开始找,找到的是第 2 个 'be' 48 | 16 49 | >>> motto.find('be', 4, 7) # 指定起始位置和终点位置,没有找到,返回 -1 50 | -1 51 | ``` 52 | 53 | # split 54 | 55 | split 方法用于将字符串分割成序列。 56 | 57 | 看看例子: 58 | 59 | ```python 60 | >>> '/user/bin/ssh'.split('/') # 使用 '/' 作为分隔符 61 | ['', 'user', 'bin', 'ssh'] 62 | >>> '1+2+3+4+5'.split('+') # 使用 '+' 作为分隔符 63 | ['1', '2', '3', '4', '5'] 64 | >>> 'that is a question'.split() # 没有提供分割符,默认使用所有空格作为分隔符 65 | ['that', 'is', 'a', 'question'] 66 | ``` 67 | 68 | 需要注意的是,如果不提供分隔符,则默认会使用所有空格作为分隔符(空格、制表符、换行等)。 69 | 70 | # join 71 | 72 | join 方法可以说是 split 的逆方法,它用于将序列中的元素连接起来。 73 | 74 | 看看例子: 75 | 76 | ```python 77 | >>> '/'.join(['', 'user', 'bin', 'ssh']) 78 | '/user/bin/ssh' 79 | >>> 80 | >>> '+'.join(['1', '2', '3', '4', '5']) 81 | '1+2+3+4+5' 82 | >>> ' '.join(['that', 'is', 'a', 'question']) 83 | 'that is a question' 84 | >>> ''.join(['h', 'e', 'll', 'o']) 85 | 'hello' 86 | >>> '+'.join([1, 2, 3, 4, 5]) # 不能是数字 87 | Traceback (most recent call last): 88 | File "", line 1, in 89 | TypeError: sequence item 0: expected string, int found 90 | ``` 91 | 92 | # strip 93 | 94 | strip 方法用于移除字符串左右两侧的空格,但不包括内部,当然也可以指定需要移除的字符串。 95 | 96 | 看看例子: 97 | 98 | ```python 99 | >>> ' hello world! '.strip() # 移除左右两侧空格 100 | 'hello world!' 101 | >>> '%%% hello world!!! ####'.strip('%#') # 移除左右两侧的 '%' 或 '#' 102 | ' hello world!!! ' 103 | >>> '%%% hello world!!! ####'.strip('%# ') # 移除左右两侧的 '%' 或 '#' 或空格 104 | 'hello world!!!' 105 | ``` 106 | 107 | # replace 108 | 109 | replace 方法用于替换字符串中的**所有**匹配项。 110 | 111 | 看看例子: 112 | 113 | ```python 114 | >>> motto = 'To be or not To be, that is a question' 115 | >>> motto.replace('To', 'to') # 用 'to' 替换所有的 'To',返回了一个新的字符串 116 | 'to be or not to be, that is a question' 117 | >>> motto # 原字符串保持不变 118 | 'To be or not To be, that is a question' 119 | ``` 120 | 121 | # translate 122 | 123 | translate 方法和 replace 方法类似,也可以用于替换字符串中的某些部分,但 **translate 方法只处理单个字符**。 124 | 125 | translate 方法的使用形式如下: 126 | 127 | ```python 128 | str.translate(table[, deletechars]); 129 | ``` 130 | 131 | 其中,table 是一个包含 256 个字符的转换表,可通过 maketrans 方法转换而来,deletechars 是字符串中要过滤的字符集。 132 | 133 | 看看例子: 134 | 135 | ```python 136 | >>> from string import maketrans 137 | >>> table = maketrans('aeiou', '12345') 138 | >>> motto = 'to be or not to be, that is a question' 139 | >>> motto.translate(table) 140 | 't4 b2 4r n4t t4 b2, th1t 3s 1 q52st34n' 141 | >>> motto 142 | 'to be or not to be, that is a question' 143 | >>> motto.translate(table, 'rqu') # 移除所有的 'r', 'q', 'u' 144 | 't4 b2 4 n4t t4 b2, th1t 3s 1 2st34n' 145 | ``` 146 | 147 | 可以看到,maketrans 接收两个参数:两个等长的字符串,表示第一个字符串的每个字符用第二个字符串对应位置的字符替代,在上面的例子中,就是 'a' 用 '1' 替代,'e' 用 '2' 替代,等等,注意,是单个字符的代替,而不是整个字符串的替代。因此,motto 中的 o 都被替换为 4,e 都被替换为 2,等等。 148 | 149 | # lower/upper 150 | 151 | lower/upper 用于返回字符串的大写或小写形式。 152 | 153 | 看看例子: 154 | 155 | ```python 156 | >>> x = 'PYTHON' 157 | >>> x.lower() 158 | 'python' 159 | >>> x 160 | 'PYTHON' 161 | >>> 162 | >>> y = 'python' 163 | >>> y.upper() 164 | 'PYTHON' 165 | >>> y 166 | 'python' 167 | ``` 168 | 169 | # 小结 170 | 171 | - 字符串是不可变对象,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回。 172 | - translate 针对单个字符进行替换。 173 | 174 | # 参考资料 175 | 176 | - 《python 基础教程》 177 | 178 | 179 | -------------------------------------------------------------------------------- /Datatypes/tuple.md: -------------------------------------------------------------------------------- 1 | # 元组 2 | 3 | 在 Python 中,**元组是一种不可变序列**,它使用圆括号来表示: 4 | 5 | ```python 6 | >>> a = (1, 2, 3) # a 是一个元组 7 | >>> a 8 | (1, 2, 3) 9 | >>> a[0] = 6 # 元组是不可变的,不能对它进行赋值操作 10 | Traceback (most recent call last): 11 | File "", line 1, in 12 | TypeError: 'tuple' object does not support item assignment 13 | ``` 14 | 15 | # 空元组 16 | 17 | 创建一个空元组可以用没有包含内容的圆括号来表示: 18 | 19 | ```python 20 | >>> a = () 21 | >>> a 22 | () 23 | ``` 24 | 25 | # 一个值的元组 26 | 27 | 创建一个值的元组需要在值后面再加一个逗号,这个比较特殊,需要牢牢记住: 28 | 29 | ```python 30 | >>> a = (12,) # 在值后面再加一个逗号 31 | >>> a 32 | (12,) 33 | >>> type(a) 34 | 35 | >>> 36 | >>> b = (12) # 只是使用括号括起来,而没有加逗号,不是元组,本质上是 b = 12 37 | >>> b 38 | 12 39 | >>> type(b) 40 | 41 | ``` 42 | 43 | # 元组操作 44 | 45 | 元组也是一种序列,因此也可以对它进行索引、分片等。由于它是不可变的,因此就没有类似列表的 append, extend, sort 等方法。 46 | 47 | # 小结 48 | 49 | - 元组是不可变的。 50 | - 创建一个值的元组需要在值后面再加一个逗号。 51 | 52 | -------------------------------------------------------------------------------- /Exception/README.md: -------------------------------------------------------------------------------- 1 | # 异常处理 2 | 3 | 我们在编写程序的时候,经常需要对异常情况做处理。比如,当一个数试图除以 0 时,我们需要捕获这个异常情况并做处理。你可能会使用类似 if/else 的条件语句来对异常情况做判断,比如,判断除法的分母是否为零,如果为零,则打印错误信息。 4 | 5 | 这在某些简单的情况下是可以的,但是,在大多数时候,我们应该使用 Python 的**异常处理**机制。这主要有两方面的好处: 6 | 7 | - 一方面,你可以选择忽略某些不重要的异常事件,或在需要的时候自己引发异常; 8 | - 另一方面,异常处理不会搞乱原来的代码逻辑,但如果使用一大堆 if/else 语句,不仅会没效率和不灵活,而且会让代码相当难读; 9 | 10 | # 异常对象 11 | 12 | Python 用**异常对象(exception object)**来表示异常情况。当程序在运行过程中遇到错误时,会引发异常。如果异常对象未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终止执行。 13 | 14 | 比如: 15 | 16 | ```python 17 | >>> 1/0 18 | Traceback (most recent call last): 19 | File "", line 1, in 20 | ZeroDivisionError: integer division or modulo by zero 21 | ``` 22 | 23 | 上面的 `ZeroDivisionError` 就是一个异常类,相应的异常对象就是该类的实例。Python 中所有的异常类都是从 `BaseException` 类派生的,常见的异常类型可以在[这里][1]查看。 24 | 25 | # 使用 try/except 捕捉异常 26 | 27 | 在编写程序的时候,如果我们知道某段代码可能会导致某种异常,而又不希望程序以堆栈跟踪的形式终止,这时我们可以根据需要添加 `try/except` 或者 `try/finally` 语句(或者它们的组合)进行处理。一般来说,有以下使用形式: 28 | 29 | ``` 30 | try...except... 31 | try...except...else... 32 | try...except...else...finally... 33 | try...except...except...else...finally... 34 | try...finally... 35 | ``` 36 | 37 | ## 基本形式 38 | 39 | 捕捉异常的基本形式是 `try...except`。 40 | 41 | 让我们看看第一个例子: 42 | 43 | ```python 44 | try: 45 | x = input('Enter x: ') 46 | y = input('Enter y: ') 47 | print x / y 48 | except ZeroDivisionError as e: 49 | print 'Error:',e 50 | 51 | print 'hello world' 52 | ``` 53 | 54 | 当 y = 0 时,看看执行结果: 55 | 56 | ```python 57 | Enter x: 3 58 | Enter y: 0 59 | Error: integer division or modulo by zero 60 | hello world 61 | ``` 62 | 63 | 可以看到,我们的程序正确捕获了`除以零`的异常,而且程序没有以堆栈跟踪的形式终止,而是继续执行后面的代码,打印出 'hello world'。 64 | 65 | ## 多个 except 子句 66 | 67 | 有时,我们的程序可能会出现多个异常,这时可以用多个 except 子句来处理这种情况。 68 | 69 | 让我们继续看第一个例子,如果 y 输入的是一个非数字的值,就会产生另外一个异常: 70 | 71 | ```python 72 | Enter x: 2 73 | Enter y: 'a' # y 的输入是一个字符 74 | ---------------------------------------------------------------------- 75 | TypeError Traceback (most recent call last) 76 | in () 77 | 2 x = input('Enter x: ') 78 | 3 y = input('Enter y: ') 79 | ----> 4 print x / y 80 | 5 except ZeroDivisionError as e: 81 | 6 print e 82 | 83 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 84 | ``` 85 | 86 | 可以看到,当 y 输入一个字符 'a' 之后,程序产生了一个 `TypeError` 异常,并且终止,这是因为我们的 except 子句只是捕获了 `ZeroDivisionError` 异常,为了能捕获`TypeError` 异常,我们可以再加一个 except 子句,完整代码如下: 87 | 88 | ```python 89 | try: 90 | x = input('Enter x: ') 91 | y = input('Enter y: ') 92 | print x / y 93 | except ZeroDivisionError as e: # 处理 ZeroDivisionError 异常 94 | print 'ZeroDivisionError:',e 95 | except TypeError as e: # 处理 TypeError 异常 96 | print 'TypeError:',e 97 | 98 | print 'hello world' 99 | ``` 100 | 101 | 当 y 输入 'a' 时,看看执行结果: 102 | 103 | ```python 104 | Enter x: 3 105 | Enter y: 'a' 106 | TypeError: unsupported operand type(s) for /: 'int' and 'str' 107 | hello world 108 | ``` 109 | 110 | ## 捕捉未知异常 111 | 112 | 事实上,在编写程序的时候,我们很难预料到程序的所有异常情况。比如,对于第一个例子,我们可以预料到一个 `ZeroDivisionError` 异常,如果细心一点,也会预料到一个 `TypeError` 异常,可是,还是有一些其他情况我们没有考虑到,比如在输入 x 的时候,我们直接按回车键,这时又会引发一个异常,程序也会随之挂掉: 113 | 114 | ```python 115 | Enter x: # 这里输入回车键 116 | Traceback (most recent call last): 117 | File "", line 2, in 118 | File "", line 0 119 | 120 | ^ 121 | SyntaxError: unexpected EOF while parsing 122 | ``` 123 | 124 | 那么,我们应该怎么在程序捕获某些难以预料的异常呢?我们在上文说过,Python 中所有的异常类都是从 `BaseException` 类派生的,也就是说,`ZeroDivisionError`、`SyntaxError` 等都是它的子类,因此,对于某些难以预料的异常,我们就可以使用 `BaseException` 来捕获,在大部分情况下,我们也可以使用 `Exception` 来捕获,因为 `Exception` 是大部分异常的父类,可以到[这里][1]查看所有异常类的继承关系。 125 | 126 | 因此,对于第一个例子,我们可以把程序做一些修改,使其更加健壮: 127 | 128 | ```python 129 | try: 130 | x = input('Enter x: ') 131 | y = input('Enter y: ') 132 | print x / y 133 | except ZeroDivisionError as e: # 捕获 ZeroDivisionError 异常 134 | print 'ZeroDivisionError:',e 135 | except TypeError as e: # 捕获 TypeError 异常 136 | print 'TypeError:',e 137 | except BaseException as e: # 捕获其他异常 138 | print 'BaseException:',e 139 | 140 | print 'hello world' 141 | ``` 142 | 143 | 注意到,我们把 `BaseException` 写在了最后一个 except 子句。如果你把它写在了第一个 except 子句,由于 `BaseException` 是所有异常的父类,那么程序的所有异常都会被第一个 except 子句捕获。 144 | 145 | ## else 子句 146 | 147 | 我们可以在 except 子句后面加一个 else 子句。当没有异常发生时,会自动执行 else 子句。 148 | 149 | 对第一个例子,加入 else 子句: 150 | 151 | ```python 152 | try: 153 | x = input('Enter x: ') 154 | y = input('Enter y: ') 155 | print x / y 156 | except ZeroDivisionError as e: 157 | print 'ZeroDivisionError:',e 158 | except TypeError as e: 159 | print 'TypeError:',e 160 | except BaseException as e: 161 | print 'BaseException:',e 162 | else: 163 | print 'no error!' 164 | 165 | print 'hello world' 166 | ``` 167 | 168 | 看看执行结果: 169 | 170 | ``` 171 | Enter x: 6 172 | Enter y: 2 173 | 3 174 | no error! 175 | hello world 176 | ``` 177 | 178 | ## finally 子句 179 | 180 | finally 子句不管有没有出现异常都会被执行。 181 | 182 | 看看例子: 183 | 184 | ```python 185 | try: 186 | x = 1/0 187 | print x 188 | finally: 189 | print 'DONE' 190 | ``` 191 | 192 | 执行结果: 193 | 194 | ``` 195 | DONE 196 | Traceback (most recent call last): 197 | File "", line 2, in 198 | ZeroDivisionError: integer division or modulo by zero 199 | ``` 200 | 201 | 再看一个例子: 202 | 203 | ```python 204 | try: 205 | x = 1/0 206 | print x 207 | except ZeroDivisionError as e: 208 | print 'ZeroDivisionError:',e 209 | finally: 210 | print 'DONE' 211 | ``` 212 | 213 | 执行结果: 214 | 215 | ``` 216 | ZeroDivisionError: integer division or modulo by zero 217 | DONE 218 | ``` 219 | 220 | ## 使用 raise 手动引发异常 221 | 222 | 有时,我们使用 except 捕获了异常,又想把异常抛出去,这时可以使用 raise 语句。 223 | 224 | 看看例子: 225 | 226 | ```python 227 | try: 228 | x = input('Enter x: ') 229 | y = input('Enter y: ') 230 | print x / y 231 | except ZeroDivisionError as e: 232 | print 'ZeroDivisionError:',e 233 | except TypeError as e: 234 | print 'TypeError:',e 235 | except BaseException as e: 236 | print 'BaseException:',e 237 | raise # 使用 raise 抛出异常 238 | else: 239 | print 'no error!' 240 | 241 | print 'hello world' 242 | ``` 243 | 244 | 运行上面代码,当 x 输入一个回车键时,错误会被打印出来,并被抛出: 245 | 246 | ```python 247 | Enter x: # 这里输入回车键 248 | BaseException: unexpected EOF while parsing (, line 0) 249 | Traceback (most recent call last): 250 | File "", line 2, in 251 | File "", line 0 252 | 253 | ^ 254 | SyntaxError: unexpected EOF while parsing 255 | ``` 256 | 257 | 上面的 raise 语句是不带参数的,它会把当前错误原样抛出。事实上,我们也创建自己的异常类,并抛出自定义的异常。 258 | 259 | **创建自定义的异常类需要从 Exception 类继承**,可以间接继承或直接继承,也就是可以继承其他的内建异常类。比如: 260 | 261 | ```python 262 | # 自定义异常类 263 | class SomeError(Exception): 264 | pass 265 | 266 | try: 267 | x = input('Enter x: ') 268 | y = input('Enter y: ') 269 | print x / y 270 | except ZeroDivisionError as e: 271 | print 'ZeroDivisionError:',e 272 | except TypeError as e: 273 | print 'TypeError:',e 274 | except BaseException as e: 275 | print 'BaseException:',e 276 | raise SomeError('invalid value') # 抛出自定义的异常 277 | else: 278 | print 'no error!' 279 | 280 | print 'hello world' 281 | ``` 282 | 283 | 运行上面代码,当 x 输入一个回车键时,错误被打印出来,并抛出我们自定义的异常: 284 | 285 | ``` 286 | Enter x: 287 | BaseException: unexpected EOF while parsing (, line 0) 288 | ---------------------------------------------------------------------- 289 | SomeError Traceback (most recent call last) 290 | in () 291 | 12 except BaseException as e: 292 | 13 print 'BaseException:',e 293 | ---> 14 raise SomeError('invalid value') 294 | 15 else: 295 | 16 print 'no error!' 296 | 297 | SomeError: invalid value 298 | ``` 299 | 300 | # 小结 301 | 302 | - Python 中所有的异常类都是从 `BaseException` 类派生的。 303 | - 通过 try/except 来捕捉异常,可以使用多个 except 子句来分别处理不同的异常。 304 | - else 子句在主 try 块没有引发异常的情况下被执行。 305 | - finally 子句不管是否发生异常都会被执行。 306 | - 通过继承 Exception 类可以创建自己的异常类。 307 | 308 | # 参考资料 309 | 310 | - 《Python 基础教程》 311 | - [Python 异常处理](http://www.runoob.com/python/python-exceptions.html) 312 | 313 | 314 | [1]: https://docs.python.org/2/library/exceptions.html#exception-hierarchy 315 | 316 | 317 | -------------------------------------------------------------------------------- /File-Directory/README.md: -------------------------------------------------------------------------------- 1 | # 文件和目录 2 | 3 | 本章主要介绍文件和目录操作,包含以下部分: 4 | 5 | * [读写文本文件](./text_file_io.md) 6 | * [读写二进制文件](./binary_file_io.md) 7 | * [os 模块](./os.md) 8 | 9 | 10 | -------------------------------------------------------------------------------- /File-Directory/binary_file_io.md: -------------------------------------------------------------------------------- 1 | # 读写二进制文件 2 | 3 | Python 不仅支持文本文件的读写,也支持二进制文件的读写,比如图片,声音文件等。 4 | 5 | # 读取二进制文件 6 | 7 | 读取二进制文件使用 'rb' 模式。 8 | 9 | 这里以图片为例: 10 | 11 | ```python 12 | with open('test.png', 'rb') as f: 13 | image_data = f.read() # image_data 是字节字符串格式的,而不是文本字符串 14 | ``` 15 | 16 | 这里需要注意的是,在读取二进制数据时,返回的数据是字节字符串格式的,而不是文本字符串。一般情况下,我们可能会对它进行编码,比如 [base64](https://en.wikipedia.org/wiki/Base64) 编码,可以这样做: 17 | 18 | ```python 19 | import base64 20 | 21 | with open('test.png', 'rb') as f: 22 | image_data = f.read() 23 | base64_data = base64.b64encode(image_data) # 使用 base64 编码 24 | print base64_data 25 | ``` 26 | 27 | 下面是执行结果的一部分: 28 | 29 | ```python 30 | iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAACGFjVEw 31 | ``` 32 | 33 | # 写入二进制文件 34 | 35 | 写入二进制文件使用 'wb' 模式。 36 | 37 | 以图片为例: 38 | 39 | ```python 40 | with open('test.png', 'rb') as f: 41 | image_data = f.read() 42 | 43 | with open('/Users/ethan/test2.png', 'wb') as f: 44 | f.write(image_data) 45 | ``` 46 | 47 | 48 | # 小结 49 | 50 | - 读取二进制文件使用 'rb' 模式。 51 | - 写入二进制文件使用 'wb' 模式。 52 | 53 | # 参考资料 54 | 55 | - [读写字节数据 — python3-cookbook 2.0.0 文档](http://python3-cookbook.readthedocs.io/zh_CN/latest/c05/p04_read_write_binary_data.html) 56 | 57 | 58 | -------------------------------------------------------------------------------- /File-Directory/os.md: -------------------------------------------------------------------------------- 1 | # os 模块 2 | 3 | Python 的 os 模块封装了常见的文件和目录操作,本文只列出部分常用的方法,更多的方法可以查看[官方文档](https://docs.python.org/3/library/os.path.html)。 4 | 5 | 下面是部分常见的用法: 6 | 7 | | 方法 | 说明 | 8 | | :-: | :-: | 9 | |  os.mkdir |  创建目录 | 10 | | os.rmdir |  删除目录 | 11 | |  os.rename |  重命名 | 12 | | os.remove | 删除文件 | 13 | |  os.getcwd | 获取当前工作路径 | 14 | | os.walk | 遍历目录 | 15 | | os.path.join | 连接目录与文件名 | 16 | | os.path.split | 分割文件名与目录 | 17 | | os.path.abspath | 获取绝对路径 | 18 | | os.path.dirname | 获取路径 | 19 | | os.path.basename | 获取文件名或文件夹名 | 20 | | os.path.splitext | 分离文件名与扩展名 | 21 | | os.path.isfile | 判断给出的路径是否是一个文件 | 22 | | os.path.isdir | 判断给出的路径是否是一个目录 | 23 | 24 | # 例子 25 | 26 | 后文的例子以下面的目录结构为参考,工作目录为 `/Users/ethan/coding/python`。 27 | 28 | ``` 29 | Users/ethan 30 | └── coding 31 | └── python 32 | ├── hello.py - 文件 33 | └── web - 目录 34 | ``` 35 | 36 | 看看例子: 37 | 38 | - os.path.abspath:获取文件或目录的绝对路径 39 | 40 | ```python 41 | $ pwd 42 | /Users/ethan/coding/python 43 | $ python 44 | >>> import os # 记得导入 os 模块 45 | >>> os.path.abspath('hello.py') 46 | '/Users/ethan/coding/python/hello.py' 47 | >>> os.path.abspath('web') 48 | '/Users/ethan/coding/python/web' 49 | >>> os.path.abspath('.') # 当前目录的绝对路径 50 | '/Users/ethan/coding/python' 51 | ``` 52 | 53 | - os.path.dirname:获取文件或文件夹的路径 54 | 55 | ```python 56 | >>> os.path.dirname('/Users/ethan/coding/python/hello.py') 57 | '/Users/ethan/coding/python' 58 | >>> os.path.dirname('/Users/ethan/coding/python/') 59 | '/Users/ethan/coding/python' 60 | >>> os.path.dirname('/Users/ethan/coding/python') 61 | '/Users/ethan/coding' 62 | ``` 63 | 64 | - os.path.basename:获取文件名或文件夹名 65 | 66 | ```python 67 | >>> os.path.basename('/Users/ethan/coding/python/hello.py') 68 | 'hello.py' 69 | >>> os.path.basename('/Users/ethan/coding/python/') 70 | '' 71 | >>> os.path.basename('/Users/ethan/coding/python') 72 | 'python' 73 | ``` 74 | 75 | - os.path.splitext:分离文件名与扩展名 76 | 77 | ```python 78 | >>> os.path.splitext('/Users/ethan/coding/python/hello.py') 79 | ('/Users/ethan/coding/python/hello', '.py') 80 | >>> os.path.splitext('/Users/ethan/coding/python') 81 | ('/Users/ethan/coding/python', '') 82 | >>> os.path.splitext('/Users/ethan/coding/python/') 83 | ('/Users/ethan/coding/python/', '') 84 | ``` 85 | 86 | - os.path.split:分离目录与文件名 87 | 88 | ```python 89 | >>> os.path.split('/Users/ethan/coding/python/hello.py') 90 | ('/Users/ethan/coding/python', 'hello.py') 91 | >>> os.path.split('/Users/ethan/coding/python/') 92 | ('/Users/ethan/coding/python', '') 93 | >>> os.path.split('/Users/ethan/coding/python') 94 | ('/Users/ethan/coding', 'python') 95 | ``` 96 | 97 | - os.path.isfile/os.path.isdir 98 | 99 | ```python 100 | >>> os.path.isfile('/Users/ethan/coding/python/hello.py') 101 | True 102 | >>> os.path.isdir('/Users/ethan/coding/python/') 103 | True 104 | >>> os.path.isdir('/Users/ethan/coding/python') 105 | True 106 | >>> os.path.isdir('/Users/ethan/coding/python/hello.py') 107 | False 108 | ``` 109 | 110 | - os.walk 111 | 112 | os.walk 是遍历目录常用的模块,它返回一个包含 3 个元素的元组:(dirpath, dirnames, filenames)。dirpath 是以 string 字符串形式返回该目录下所有的绝对路径;dirnames 是以列表 list 形式返回每一个绝对路径下的文件夹名字;filenames 是以列表 list 形式返回该路径下所有文件名字。 113 | 114 | ```python 115 | >>> for root, dirs, files in os.walk('/Users/ethan/coding'): 116 | ... print root 117 | ... print dirs 118 | ... print files 119 | ... 120 | /Users/ethan/coding 121 | ['python'] 122 | [] 123 | /Users/ethan/coding/python 124 | ['web2'] 125 | ['hello.py'] 126 | /Users/ethan/coding/python/web2 127 | [] 128 | [] 129 | ``` 130 | 131 | # 参考资料 132 | 133 | - [关于python文件操作 - Rollen Holt](http://www.cnblogs.com/rollenholt/archive/2012/04/23/2466179.html) 134 | - [操作文件和目录 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868321590543ff305fb9f9949f08d760883cc243812000) 135 | - [Python os.walk的用法与举例](http://blog.csdn.net/bagboy_taobao_com/article/details/893) 136 | 137 | 138 | -------------------------------------------------------------------------------- /File-Directory/text_file_io.md: -------------------------------------------------------------------------------- 1 | # 读写文本文件 2 | 3 | 读写文件是最常见的 IO 操作。通常,我们使用 `input` 从控制台读取输入,使用 `print` 将内容输出到控制台。实际上,我们也经常从文件读取输入,将内容写到文件。 4 | 5 | # 读文件 6 | 7 | 在 Python 中,读文件主要分为三个步骤: 8 | 9 | - 打开文件 10 | - 读取内容 11 | - 关闭文件 12 | 13 | 一般使用形式如下: 14 | 15 | ```python 16 | try: 17 | f = open('/path/to/file', 'r') # 打开文件 18 | data = f.read() # 读取文件内容 19 | finally: 20 | if f: 21 | f.close() # 确保文件被关闭 22 | ``` 23 | 24 | 注意到,我们在代码中加了 `try...finally`,这是因为,如果打开和读取文件时出现错误,文件就没有被关闭。为了确保在任何情况下,文件都能被关闭,我们加了 `try...finally`。 25 | 26 | 上面的代码中,'r' 模式表示读模式,`open` 函数的常用模式主要有: 27 | 28 | | ‘r' | 读模式 | 29 | | :-: | :-: | 30 | | ‘w' | 写模式 | 31 | | ‘a' | 追加模式 | 32 | | ‘b' | 二进制模式(可添加到其他模式中使用) | 33 | | ‘+' | 读/写模式(可添加到其他模式中使用) | 34 | 35 | 上面的读文件做法很繁琐,我们可以使用 Python 的 `with` 语句来帮我们自动调用 `close` 方法: 36 | 37 | ```python 38 | with open('/path/to/file', 'r') as f: 39 | data = f.read() 40 | ``` 41 | 42 | 可以看到,这种方式很简洁,而且还能在出现异常的情况下自动关闭文件。 43 | 44 | 通常而言,读取文件有以下几种方式: 45 | 46 | - 一次性读取所有内容,使用 `read()` 或 `readlines()`; 47 | - 按字节读取,使用 `read(size)`; 48 | - 按行读取,使用 `readline()`; 49 | 50 | ## 读取所有内容 51 | 52 | 读取所有内容可以使用 `read()` 或 `readlines()`。我们在上面已经介绍过 `read()` 了,现在,让我们看看 `readlines()`。 53 | 54 | `readlines()` 方法会把文件读入一个字符串列表,在列表中每个字符串就是一行。 55 | 56 | 假设有一个文件 data.txt,它的文件内容如下(数字之间的间隔符是'\t'): 57 | 58 | ```python 59 | 10 1 9 9 60 | 6 3 2 8 61 | 20 10 3 23 62 | 1 4 1 10 63 | 10 8 6 3 64 | 10 2 1 6 65 | ``` 66 | 67 | 我们使用 `readlines()` 将文件读入一个字符串列表: 68 | 69 | ```python 70 | with open('data.txt', 'r') as f: 71 | lines = f.readlines() 72 | line_num = len(lines) 73 | print lines 74 | print line_num 75 | ``` 76 | 77 | 执行结果: 78 | 79 | ``` 80 | ['10\t1\t9\t9\n', '6\t3\t2\t8\n', '20\t10\t3\t23\n', '1\t4\t1\t10\n', '10\t8\t6\t3\n', '10\t2\t1\t6'] 81 | 6 82 | ``` 83 | 84 | 可以看到,列表中的每个元素都是一个字符串,刚好对应文件中的每一行。 85 | 86 | ## 按字节读取 87 | 88 | 如果文件较小,一次性读取所有内容确实比较方便。但是,如果文件很大,比如有 100G,那就不能一次性读取所有内容了。这时,我们构造一个固定长度的缓冲区,来不断读取文件内容。 89 | 90 | 看看例子: 91 | 92 | ```python 93 | with open('path/to/file', 'r') as f: 94 | while True: 95 | piece = f.read(1024) # 每次读取 1024 个字节(即 1 KB)的内容 96 | if not piece: 97 | break 98 | print piece 99 | ``` 100 | 101 | 在上面,我们使用 `f.read(1024)` 来每次读取 1024 个字节(1KB) 的文件内容,将其存到 piece,再对 piece 进行处理。 102 | 103 | 事实上,我们还可以结合 yield 来使用: 104 | 105 | ```python 106 | def read_in_chunks(file_object, chunk_size=1024): 107 | """Lazy function (generator) to read a file piece by piece. 108 | Default chunk size: 1k.""" 109 | while True: 110 | data = file_object.read(chunk_size) 111 | if not data: 112 | break 113 | yield data 114 | 115 | with open('path/to/file', 'r') as f: 116 | for piece in read_in_chunks(f): 117 | print piece 118 | ``` 119 | 120 | ## 逐行读取 121 | 122 | 在某些情况下,我们希望逐行读取文件,这时可以使用 `readline()` 方法。 123 | 124 | 看看例子: 125 | 126 | ```python 127 | with open('data.txt', 'r') as f: 128 | while True: 129 | line = f.readline() # 逐行读取 130 | if not line: 131 | break 132 | print line, # 这里加了 ',' 是为了避免 print 自动换行 133 | ``` 134 | 135 | 执行结果: 136 | 137 | ``` 138 | 10 1 9 9 139 | 6 3 2 8 140 | 20 10 3 23 141 | 1 4 1 10 142 | 10 8 6 3 143 | 10 2 1 6 144 | ``` 145 | 146 | ## 文件迭代器 147 | 148 | 在 Python 中,**文件对象是可迭代的**,这意味着我们可以直接在 for 循环中使用它们,而且是逐行迭代的,也就是说,效果和 `readline()` 是一样的,而且更简洁。 149 | 150 | 看看例子: 151 | 152 | ```python 153 | with open('data.txt', 'r') as f: 154 | for line in f: 155 | print line, 156 | ``` 157 | 158 | 在上面的代码中,f 就是一个文件迭代器,因此我们可以直接使用 `for line in f`,它是逐行迭代的。 159 | 160 | 看看执行结果: 161 | 162 | ```python 163 | 10 1 9 9 164 | 6 3 2 8 165 | 20 10 3 23 166 | 1 4 1 10 167 | 10 8 6 3 168 | 10 2 1 6 169 | ``` 170 | 171 | 再看一个例子: 172 | 173 | ```python 174 | with open(file_path, 'r') as f: 175 | lines = list(f) 176 | print lines 177 | ``` 178 | 179 | 执行结果: 180 | 181 | ```python 182 | ['10\t1\t9\t9\n', '6\t3\t2\t8\n', '20\t10\t3\t23\n', '1\t4\t1\t10\n', '10\t8\t6\t3\n', '10\t2\t1\t6'] 183 | ``` 184 | 185 | 可以看到,我们可以对文件迭代器执行和普通迭代器相同的操作,比如上面使用 `list(open(filename))` 将 f 转为一个字符串列表,这样所达到的效果和使用 `readlines` 是一样的。 186 | 187 | # 写文件 188 | 189 | 写文件使用 `write` 方法,如下: 190 | 191 | ```python 192 | with open('/Users/ethan/data2.txt', 'w') as f: 193 | f.write('one\n') 194 | f.write('two') 195 | ``` 196 | 197 | - 如果上述文件已存在,则会清空原内容并覆盖掉; 198 | - 如果上述路径是正确的(比如存在 /Users/ethan 的路径),但是文件不存在(data2.txt 不存在),则会新建一个文件,并写入上述内容; 199 | - 如果上述路径是不正确的(比如将路径写成 /Users/eth ),这时会抛出 IOError; 200 | 201 | 如果我们想往已存在的文件追加内容,可以使用 'a' 模式,如下: 202 | 203 | ```python 204 | with open('/Users/ethan/data2.txt', 'a') as f: 205 | f.write('three\n') 206 | f.write('four') 207 | ``` 208 | 209 | # 小结 210 | 211 | - 推荐使用 with 语句操作文件 IO。 212 | - 如果文件较大,可以按字节读取或按行读取。 213 | - 使用文件迭代器进行逐行迭代。 214 | 215 | # 参考资料 216 | 217 | - 《Python 基础教程》 218 | - [读写文本数据 — python3-cookbook 2.0.0 文档](http://python3-cookbook.readthedocs.io/zh_CN/latest/c05/p01_read_write_text_data.html) 219 | 220 | 221 | -------------------------------------------------------------------------------- /Function/README.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | 本章讲解函数,包含以下部分: 4 | 5 | * [定义函数](./func_definition.md) 6 | * [函数参数](./func_parameter.md) 7 | 8 | -------------------------------------------------------------------------------- /Function/func_definition.md: -------------------------------------------------------------------------------- 1 | # 定义函数 2 | 3 | 在 Python 中,定义函数使用 `def` 语句。一个函数主要由三部分构成: 4 | 5 | - 函数名 6 | - 函数参数 7 | - 函数返回值 8 | 9 | 让我们看一个简单的例子: 10 | 11 | ```python 12 | def hello(name): 13 | return name 14 | 15 | >>> r = hello('ethan') 16 | >>> r 17 | 'ethan' 18 | ``` 19 | 20 | 在上面,我们定义了一个函数。函数名是 `hello`;函数有一个参数,参数名是 name;函数有一个返回值,name。 21 | 22 | 我们也可以定义一个没有参数和返回值的函数: 23 | 24 | ```python 25 | def greet(): # 没有参数 26 | print 'hello world' # 没有 return,会自动 return None 27 | 28 | >>> r = greet() 29 | hello world 30 | >>> r == None 31 | ``` 32 | 33 | 这里,函数 `greet` 没有参数,它也没有返回值(或者说是 None)。 34 | 35 | 我们还可以定义返回多个值的函数: 36 | 37 | ```python 38 | >>> def add_one(x, y, z): 39 | ... return x+1, y+1, z+1 # 有 3 个返回值 40 | ... 41 | >>> 42 | >>> result = add_one(1, 5, 9) 43 | >>> result # result 实际上是一个 tuple 44 | (2, 6, 10) 45 | >>> type(result) 46 | 47 | ``` 48 | 49 | # 小结 50 | 51 | - 如果函数没有 `return` 语句,则自动 `return None`。 52 | 53 | 54 | -------------------------------------------------------------------------------- /Function/func_parameter.md: -------------------------------------------------------------------------------- 1 | # 函数参数 2 | 3 | 在 Python 中,定义函数和调用函数都很简单,但如何定义函数参数和传递函数参数,则涉及到一些套路了。总的来说,Python 的函数参数主要分为以下几种: 4 | 5 | - 必选参数 6 | - 默认参数 7 | - 可变参数 8 | - 关键字参数 9 | 10 | # 必选参数 11 | 12 | 必选参数可以说是最常见的了,顾名思义,必选参数就是在调用函数的时候要传入数量一致的参数,比如: 13 | 14 | ```python 15 | >>> def add(x, y): # x, y 是必选参数 16 | ... print x + y 17 | ... 18 | >>> add() # 啥都没传,不行 19 | Traceback (most recent call last): 20 | File "", line 1, in 21 | TypeError: add() takes exactly 2 arguments (0 given) 22 | >>> add(1) # 只传了一个,也不行 23 | Traceback (most recent call last): 24 | File "", line 1, in 25 | TypeError: add() takes exactly 2 arguments (1 given) 26 | >>> add(1, 2) # 数量一致,通过 27 | 3 28 | ``` 29 | 30 | # 默认参数 31 | 32 | 默认参数是指在定义函数的时候提供一些默认值,如果在调用函数的时候没有传递该参数,则自动使用默认值,否则使用传递时该参数的值。 33 | 34 | 看看例子就明白了: 35 | 36 | ```python 37 | >>> def add(x, y, z=1): # x, y 是必选参数,z 是默认参数,默认值是 1 38 | ... print x + y + z 39 | ... 40 | >>> add(1, 2, 3) # 1+2+3 41 | 6 42 | >>> add(1, 2) # 没有传递 z,自动使用 z=1,即 1+2+1 43 | 4 44 | ``` 45 | 46 | 可以看到,默认参数使用起来也很简单,但有两点需要注意的是: 47 | 48 | - 默认参数要放在所有必选参数的后面 49 | - 默认参数应该使用不可变对象 50 | 51 | 比如,下面对默认参数的使用是错误的: 52 | 53 | ```python 54 | >>> def add(x=1, y, z): # x 是默认参数,必须放在所有必选参数的后面 55 | ... return x + y + z 56 | ... 57 | File "", line 1 58 | SyntaxError: non-default argument follows default argument 59 | >>> 60 | >>> def add(x, y=1, z): # y 是默认参数,必须放在所有必选参数的后面 61 | ... return x + y + z 62 | ... 63 | File "", line 1 64 | SyntaxError: non-default argument follows default argument 65 | ``` 66 | 67 | 再来看看为什么默认参数应该使用不可变对象。 68 | 69 | 我们看一个例子: 70 | 71 | ```python 72 | >>> def add_to_list(L=[]): 73 | ... L.append('END') 74 | ... return L 75 | ``` 76 | 77 | 在上面的函数中,L 是一个默认参数,默认值是 `[]`,表示空列表。 78 | 79 | 我们来看看使用: 80 | 81 | ```python 82 | >>> add_to_list([1, 2, 3]) # 没啥问题 83 | [1, 2, 3, 'END'] 84 | >>> add_to_list(['a', 'b', 'c']) # 没啥问题 85 | ['a', 'b', 'c', 'END'] 86 | >>> add_to_list() # 没有传递参数,使用默认值,也没啥问题 87 | ['END'] 88 | >>> add_to_list() # 没有传递参数,使用默认值,竟出现两个 'END' 89 | ['END', 'END'] 90 | >>> add_to_list() # 糟糕了,三个 'END' 91 | ['END', 'END', 'END'] 92 | ``` 93 | 94 | 为啥呢?我们在调用函数的时候没有传递参数,那么就默认使用 `L=[]`,经过处理,L 应该只有一个元素,怎么会出现调用函数两次,L 就有两个元素呢? 95 | 96 | 原来,L 指向了可变对象 `[]`,当你调用函数时,L 的内容发生了改变,默认参数的内容也会跟着变,也就是,当你第一次调用时,L 的初始值是 `[]`,当你第二次调用时,L 的初始值是 `['END']`,等等。 97 | 98 | 所以,为了避免不必要的错误,我们应该使用不可变对象作为函数的默认参数。 99 | 100 | # 可变参数 101 | 102 | 在某些情况下,我们在定义函数的时候,无法预估函数应该制定多少个参数,这时我们就可以使用**可变参数**了,也就是,函数的参数个数是不确定的。 103 | 104 | 看看例子: 105 | 106 | ```python 107 | >>> def add(*numbers): 108 | ... sum = 0 109 | ... for i in numbers: 110 | ... sum += i 111 | ... print 'numbers:', numbers 112 | ... return sum 113 | ``` 114 | 115 | 在上面的代码中,numbers 就是一个可变参数,参数前面有一个 `*` 号,表示是可变的。在函数内部,参数 numbers 接收到的是一个 tuple。 116 | 117 | 在调用函数时,我们可以给该函数传递任意个参数,包括 0 个参数: 118 | 119 | ```python 120 | >>> add() # 传递 0 个参数 121 | numbers: () 122 | 0 123 | >>> add(1) # 传递 1 个参数 124 | numbers: (1,) 125 | 1 126 | >>> add(1, 2) # 传递 2 个参数 127 | numbers: (1, 2) 128 | 3 129 | >>> add(1, 2, 3) # 传递 3 个参数 130 | numbers: (1, 2, 3) 131 | 6 132 | ``` 133 | 134 | 上面的 `*` 表示任意参数,实际上,它还有另外一个用法:用来给函数传递参数。 135 | 136 | 看看例子: 137 | 138 | ```python 139 | >>> def add(x, y, z): # 有 3 个必选参数 140 | ... return x + y + z 141 | ... 142 | >>> a = [1, 2, 3] 143 | >>> add(a[0], a[1], a[2]) # 这样传递参数很累赘 144 | 6 145 | >>> add(*a) # 使用 *a,相当于上面的做法 146 | 6 147 | >>> b = (4, 5, 6) 148 | >>> add(*b) # 对元组一样适用 149 | 15 150 | ``` 151 | 152 | 再看一个例子: 153 | 154 | ```python 155 | >>> def add(*numbers): # 函数参数是可变参数 156 | ... sum = 0 157 | ... for i in numbers: 158 | ... sum += i 159 | ... return sum 160 | ... 161 | >>> a = [1, 2] 162 | >>> add(*a) # 使用 *a 给函数传递参数 163 | 3 164 | >>> a = [1, 2, 3, 4] 165 | >>> add(*a) 166 | 10 167 | ``` 168 | 169 | # 关键字参数 170 | 171 | 可变参数允许你将不定数量的参数传递给函数,而**关键字参数**则允许你将不定长度的**键值对**, 作为参数传递给一个函数。 172 | 173 | 让我们看看例子: 174 | 175 | ```python 176 | >>> def add(**kwargs): 177 | return kwargs 178 | >>> add() # 没有参数,kwargs 为空字典 179 | {} 180 | >>> add(x=1) # x=1 => kwargs={'x': 1} 181 | {'x': 1} 182 | >>> add(x=1, y=2) # x=1, y=2 => kwargs={'y': 2, 'x': 1} 183 | {'y': 2, 'x': 1} 184 | ``` 185 | 186 | 在上面的代码中,kwargs 就是一个关键字参数,它前面有两个 `*` 号。kwargs 可以接收不定长度的键值对,在函数内部,它会表示成一个 dict。 187 | 188 | 和可变参数类似,我们也可以使用 `**kwargs` 的形式来调用函数,比如: 189 | 190 | ```python 191 | >>> def add(x, y, z): 192 | ... return x + y + z 193 | ... 194 | >>> dict1 = {'z': 3, 'x': 1, 'y': 6} 195 | >>> add(dict1['x'], dict1['y'], dict1['z']) # 这样传参很累赘 196 | 10 197 | >>> add(**dict1) # 使用 **dict1 来传参,等价于上面的做法 198 | 10 199 | ``` 200 | 201 | 再看一个例子: 202 | 203 | ```python 204 | >>> def sum(**kwargs): # 函数参数是关键字参数 205 | ... sum = 0 206 | ... for k, v in kwargs.items(): 207 | ... sum += v 208 | ... return sum 209 | >>> sum() # 没有参数 210 | 0 211 | >>> dict1 = {'x': 1} 212 | >>> sum(**dict1) # 相当于 sum(x=1) 213 | 1 214 | >>> dict2 = {'x': 2, 'y': 6} 215 | >>> sum(**dict2) # 相当于 sum(x=2, y=6) 216 | 8 217 | ``` 218 | 219 | # 参数组合 220 | 221 | 在实际的使用中,我们经常会同时用到必选参数、默认参数、可变参数和关键字参数或其中的某些。但是,需要注意的是,**它们在使用的时候是有顺序的,依次是必选参数、默认参数、可变参数和关键字参数**。 222 | 223 | 比如,定义一个包含上述四种参数的函数: 224 | 225 | ```python 226 | >>> def func(x, y, z=0, *args, **kwargs): 227 | print 'x =', x 228 | print 'y =', y 229 | print 'z =', z 230 | print 'args =', args 231 | print 'kwargs =', kwargs 232 | ``` 233 | 234 | 在调用函数的时候,Python 会自动按照参数位置和参数名把对应的参数传进去。让我们看看: 235 | 236 | ```python 237 | >>> func(1, 2) # 至少提供两个参数,因为 x, y 是必选参数 238 | x = 1 239 | y = 2 240 | z = 0 241 | args = () 242 | kwargs = {} 243 | >>> func(1, 2, 3) # x=1, y=2, z=3 244 | x = 1 245 | y = 2 246 | z = 3 247 | args = () 248 | kwargs = {} 249 | >>> func(1, 2, 3, 4, 5, 6) # x=1, y=2, z=3, args=(4, 5, 6), kwargs={} 250 | x = 1 251 | y = 2 252 | z = 3 253 | args = (4, 5, 6) 254 | kwargs = {} 255 | >>> func(1, 2, 4, u=6, v=7) # args = (), kwargs = {'u': 6, 'v': 7} 256 | x = 1 257 | y = 2 258 | z = 4 259 | args = () 260 | kwargs = {'u': 6, 'v': 7} 261 | >>> func(1, 2, 3, 4, 5, u=6, v=7) # args = (4, 5), kwargs = {'u': 6, 'v': 7} 262 | x = 1 263 | y = 2 264 | z = 3 265 | args = (4, 5) 266 | kwargs = {'u': 6, 'v': 7} 267 | ``` 268 | 269 | 我们还可以通过下面的形式来传递参数: 270 | 271 | ```python 272 | >>> a = (1, 2, 3) 273 | >>> b = {'u': 6, 'v': 7} 274 | >>> func(*a, **b) 275 | x = 1 276 | y = 2 277 | z = 3 278 | args = () 279 | kwargs = {'u': 6, 'v': 7} 280 | ``` 281 | 282 | # 小结 283 | 284 | - 默认参数要放在所有必选参数的后面。 285 | - 应该使用不可变对象作为函数的默认参数。 286 | - `*args` 表示可变参数,`**kwargs` 表示关键字参数。 287 | - 参数组合在使用的时候是有顺序的,依次是必选参数、默认参数、可变参数和关键字参数。 288 | - `*args` 和 `**kwargs` 是 Python 的惯用写法。 289 | 290 | # 参考资料 291 | 292 | - [args 和 *kwargs · Python进阶](https://eastlakeside.gitbooks.io/interpy-zh/content/args_kwargs/) 293 | - [函数的参数 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001374738449338c8a122a7f2e047899fc162f4a7205ea3000) 294 | 295 | 296 | -------------------------------------------------------------------------------- /Functional/README.md: -------------------------------------------------------------------------------- 1 | # 函数式编程 2 | 3 | **函数式编程(functional programming)**是一种[编程范式(Programming paradigm)](https://zh.wikipedia.org/wiki/%E7%BC%96%E7%A8%8B%E8%8C%83%E5%9E%8B),或者说编程模式,比如我们常见的过程式编程是一种编程范式,面向对象编程又是另一种编程范式。 4 | 5 | 函数式编程的一大特性就是:可以把函数当成变量来使用,比如将函数赋值给其他变量、把函数作为参数传递给其他函数、函数的返回值也可以是一个函数等等。 6 | 7 | Python 不是纯函数式编程语言,但它对函数式编程提供了一些支持。本章主要介绍 Python 中的函数式编程,主要包括以下几个方面: 8 | 9 | * [高阶函数](./high_order_func.md) 10 | * [匿名函数](./anonymous_func.md) 11 | * [map/reduce/filter](./map_reduce_filter.md) 12 | * [闭包](./closure.md) 13 | * [装饰器](./decorator.md) 14 | * [partial 函数](./partial.md) 15 | 16 | # 参考资料 17 | 18 | - [什么是函数式编程思维? - 知乎](https://www.zhihu.com/question/28292740) 19 | - [编程范型 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E7%BC%96%E7%A8%8B%E8%8C%83%E5%9E%8B) 20 | - [函数式编程初探 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2012/04/functional_programming.html) 21 | - [函数式编程 | 酷 壳 - CoolShell.cn](http://coolshell.cn/articles/10822.html) 22 | 23 | -------------------------------------------------------------------------------- /Functional/anonymous_func.md: -------------------------------------------------------------------------------- 1 | # 匿名函数 2 | 3 | 在 Python 中,我们使用 `def` 语句来定义函数,比如: 4 | 5 | ```python 6 | def double(x): 7 | return 2 * x 8 | ``` 9 | 10 | 除了用上面的方式定义函数,Python 还提供了一个关键字 `lambda`,让我们可以创建一个匿名函数,也就是没有名称的函数。它的形式如下: 11 | 12 | ``` 13 | lambda 参数: 表达式 14 | ``` 15 | 16 | 关键字 `lambda` 说明它是一个匿名函数,冒号 `:` 前面的变量是该匿名函数的参数,冒号后面是函数的返回值,注意这里不需使用 return 关键字。 17 | 18 | 我们将上面的 `double` 函数改写成一个匿名函数,如下: 19 | 20 | ``` 21 | lambda x: 2 * x 22 | ``` 23 | 24 | 那怎么调用匿名函数呢?可以直接这样使用: 25 | 26 | ```python 27 | >>> (lambda x: 2 * x)(8) 28 | 16 29 | ``` 30 | 31 | 由于匿名函数本质上是一个函数对象,也可以将其赋值给另一个变量,再由该变量来调用函数,如下: 32 | 33 | ```python 34 | >>> f = lambda x: 2 * x # 将匿名函数赋给变量 f 35 | >>> f 36 | at 0x7f835a696578> 37 | >>> f(8) 38 | 16 39 | ``` 40 | 41 | # 使用场景 42 | 43 | `lambda` 函数一般适用于创建一些临时性的,小巧的函数。比如上面的 `double` 函数,我们当然可以使用 `def` 来定义,但使用 `lambda` 来创建会显得很简洁,尤其是在高阶函数的使用中。 44 | 45 | 看一个例子: 46 | 47 | ```python 48 | def func(g, arr): 49 | return [g(x) for x in arr] 50 | ``` 51 | 52 | 现在给一个列表 [1, 2, 3, 4],利用上面的函数,对列表中的元素加 1,返回一个新的列表,你可能这样用: 53 | 54 | ```python 55 | def add_one(x): 56 | return x + 1 57 | 58 | arr = func(add_one, [1, 2, 3, 4]) 59 | ``` 60 | 61 | 这样做没什么错,可是 `add_one` 这个函数太简单了,使用 `def` 定义未免有点小题大作,我们改用 `lambda`: 62 | 63 | ``` 64 | arr = func(lambda x: x + 1, [1, 2, 3, 4]) 65 | ``` 66 | 67 | 是不是很简洁、易懂? 68 | 69 | # 小结 70 | 71 | - 匿名函数本质上是一个函数,没有函数名称,因此使用匿名函数不用担心函数名冲突; 72 | - 匿名函数一般适用于创建一些临时性的,小巧的函数; 73 | 74 | 75 | -------------------------------------------------------------------------------- /Functional/closure.md: -------------------------------------------------------------------------------- 1 | # 闭包 2 | 3 | 在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如: 4 | 5 | ```python 6 | from math import pow 7 | 8 | def make_pow(n): 9 | def inner_func(x): # 嵌套定义了 inner_func 10 | return pow(x, n) # 注意这里引用了外部函数的 n 11 | return inner_func # 返回 inner_func 12 | ``` 13 | 14 | 上面的代码中,函数 `make_pow` 里面又定义了一个内部函数 `inner_func`,然后将该函数返回。因此,我们可以使用 `make_pow` 来生成另一个函数: 15 | 16 | ``` 17 | >>> pow2 = make_pow(2) # pow2 是一个函数,参数 2 是一个自由变量 18 | >>> pow2 19 | 20 | >>> pow2(6) 21 | 36.0 22 | ``` 23 | 24 | 我们还注意到,内部函数 `inner_func` 引用了外部函数 `make_pow` 的自由变量 `n`,这也就意味着,当函数 `make_pow` 的生命周期结束之后,`n` 这个变量依然会保存在 `inner_func` 中,它被 `inner_func` 所引用。 25 | 26 | ``` 27 | >>> del make_pow # 删除 make_pow 28 | >>> pow3 = make_pow(3) 29 | Traceback (most recent call last): 30 | File "", line 1, in 31 | NameError: name 'make_pow' is not defined 32 | >>> pow2(9) # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中 33 | 81.0 34 | ``` 35 | 36 | 像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为**闭包(Closure)**。 37 | 38 | 在上面的例子中,`inner_func` 就是一个闭包,它引用了自由变量 `n`。 39 | 40 | # 闭包的作用 41 | 42 | - 闭包的最大特点就是引用了自由变量,即使生成闭包的环境已经释放,闭包仍然存在。 43 | - 闭包在运行时可以有多个实例,即使传入的参数相同。 44 | 45 | ``` 46 | >>> pow_a = make_pow(2) 47 | >>> pow_b = make_pow(2) 48 | >>> pow_a == pow_b 49 | False 50 | ``` 51 | - 利用闭包,我们还可以模拟类的实例。 52 | 53 | 这里构造一个类,用于求一个点到另一个点的距离: 54 | 55 | ```python 56 | from math import sqrt 57 | 58 | class Point(object): 59 | def __init__(self, x, y): 60 | self.x, self.y = x, y 61 | 62 | def get_distance(self, u, v): 63 | distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2) 64 | return distance 65 | 66 | >>> pt = Point(7, 2) # 创建一个点 67 | >>> pt.get_distance(10, 6) # 求到另一个点的距离 68 | 5.0 69 | ``` 70 | 71 | 用闭包来实现: 72 | 73 | ```python 74 | def point(x, y): 75 | def get_distance(u, v): 76 | return sqrt((x - u) ** 2 + (y - v) ** 2) 77 | 78 | return get_distance 79 | 80 | >>> pt = point(7, 2) 81 | >>> pt(10, 6) 82 | 5.0 83 | ``` 84 | 85 | 可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。 86 | 87 | # 常见误区 88 | 89 | 闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子: 90 | 91 | ```python 92 | def count(): 93 | funcs = [] 94 | for i in [1, 2, 3]: 95 | def f(): 96 | return i 97 | funcs.append(f) 98 | return funcs 99 | ``` 100 | 101 | 在该例子中,我们在每次 `for` 循环中创建了一个函数,并将它存到 `funcs` 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是: 102 | 103 | ``` 104 | >>> f1, f2, f3 = count() 105 | >>> f1() 106 | 3 107 | >>> f2() 108 | 3 109 | >>> f3() 110 | 3 111 | ``` 112 | 113 | 为什么呢?原因在于上面的函数 `f` 引用了变量 `i`,但函数 `f` 并非立刻执行,当 `for` 循环结束时,此时变量 `i` 的值是3,`funcs` 里面的函数引用的变量都是 3,最终结果也就全为 3。 114 | 115 | 因此,我们应**尽量避免在闭包中引用循环变量,或者后续会发生变化的变量**。 116 | 117 | 那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下: 118 | 119 | ```python 120 | def count(): 121 | funcs = [] 122 | for i in [1, 2, 3]: 123 | def g(param): 124 | f = lambda : param # 这里创建了一个匿名函数 125 | return f 126 | funcs.append(g(i)) # 将循环变量的值传给 g 127 | return funcs 128 | 129 | >>> f1, f2, f3 = count() 130 | >>> f1() 131 | 1 132 | >>> f2() 133 | 2 134 | >>> f3() 135 | 3 136 | ``` 137 | 138 | # 小结 139 | 140 | - 闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。 141 | - 闭包在运行可以有多个实例。 142 | - 尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。 143 | 144 | # 参考资料 145 | 146 | - [返回函数 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0014186131194415d50558b7a1c424f9fb52b84dc9c965c000) 147 | - [Why aren't python nested functions called closures? - Stack Overflow](http://stackoverflow.com/questions/4020419/why-arent-python-nested-functions-called-closures) 148 | 149 | 150 | -------------------------------------------------------------------------------- /Functional/decorator.md: -------------------------------------------------------------------------------- 1 | # 装饰器 2 | 3 | 我们知道,在 Python 中,我们可以像使用变量一样使用函数: 4 | 5 | - 函数可以被赋值给其他变量 6 | - 函数可以被删除 7 | - 可以在函数里面再定义函数 8 | - 函数可以作为参数传递给另外一个函数 9 | - 函数可以作为另一个函数的返回 10 | 11 | **简而言之,函数就是一个对象**。 12 | 13 | # 对一个简单的函数进行装饰 14 | 15 | 为了更好地理解装饰器,我们先从一个简单的例子开始,假设有下面的函数: 16 | 17 | ```python 18 | def hello(): 19 | return 'hello world' 20 | ``` 21 | 22 | 现在我们想增强 `hello()` 函数的功能,希望给返回加上 HTML 标签,比如 `hello world`,但是有一个要求,不改变原来 `hello()` 函数的定义。这里当然有很多种方法,下面给出一种跟本文相关的方法: 23 | 24 | ```python 25 | def makeitalic(func): 26 | def wrapped(): 27 | return "" + func() + "" 28 | return wrapped 29 | ``` 30 | 31 | 在上面的代码中,我们定义了一个函数 `makeitalic`,该函数有一个参数 `func`,它是一个函数;在 `makeitalic` 函数里面我们又定义了一个内部函数 `wrapped`,并将该函数作为返回。 32 | 33 | 现在,我们就可以不改变 `hello()` 函数的定义,给返回加上 HTML 标签了: 34 | 35 | ```python 36 | >>> hello = makeitalic(hello) # 将 hello 函数传给 makeitalic 37 | >>> hello() 38 | 'hello world' 39 | ``` 40 | 41 | 在上面,我们将 `hello` 函数传给 `makeitalic`,再将返回赋给 `hello`,此时调用 `hello()` 就得到了我们想要的结果。 42 | 43 | 不过要注意的是,由于我们将 `makeitalic` 的返回赋给了 `hello`,此时 `hello()` 函数仍然存在,但是它的函数名不再是 hello 了,而是 wrapped,正是 `makeitalic` 返回函数的名称,可以验证一下: 44 | 45 | ```python 46 | >>> hello.__name__ 47 | 'wrapped' 48 | ``` 49 | 50 | 对于这个小瑕疵,后文将会给出解决方法。 51 | 52 | 现在,我们梳理一下上面的例子,为了增强原函数 `hello` 的功能,我们定义了一个函数,它接收原函数作为参数,并返回一个新的函数,完整的代码如下: 53 | 54 | ```python 55 | def makeitalic(func): 56 | def wrapped(): 57 | return "" + func() + "" 58 | return wrapped 59 | 60 | def hello(): 61 | return 'hello world' 62 | 63 | hello = makeitalic(hello) 64 | ``` 65 | 66 | 事实上,`makeitalic` 就是一个**装饰器(decorator)**,它『装饰』了函数 `hello`,并返回一个函数,将其赋给 `hello`。 67 | 68 | 一般情况下,我们使用装饰器提供的 `@` 语法糖(Syntactic Sugar),来简化上面的写法: 69 | 70 | ```python 71 | def makeitalic(func): 72 | def wrapped(): 73 | return "" + func() + "" 74 | return wrapped 75 | 76 | @makeitalic 77 | def hello(): 78 | return 'hello world' 79 | ``` 80 | 81 | 像上面的情况,可以动态修改函数(或类)功能的函数就是装饰器。**本质上,它是一个高阶函数,以被装饰的函数(比如上面的 hello)为参数,并返回一个包装后的函数(比如上面的 wrapped)给被装饰函数(hello)**。 82 | 83 | # 装饰器的使用形式 84 | 85 | - 装饰器的一般使用形式如下: 86 | 87 | ```python 88 | @decorator 89 | def func(): 90 | pass 91 | ``` 92 | 93 | 等价于下面的形式: 94 | 95 | ```python 96 | def func(): 97 | pass 98 | func = decorator(func) 99 | ``` 100 | 101 | - 装饰器可以定义多个,离函数定义最近的装饰器先被调用,比如: 102 | 103 | ```python 104 | @decorator_one 105 | @decorator_two 106 | def func(): 107 | pass 108 | ``` 109 | 110 | 等价于: 111 | 112 | ```python 113 | def func(): 114 | pass 115 | 116 | func = decorator_one(decorator_two(func)) 117 | ``` 118 | 119 | - 装饰器还可以带参数,比如: 120 | 121 | ```python 122 | @decorator(arg1, arg2) 123 | def func(): 124 | pass 125 | ``` 126 | 127 | 等价于: 128 | 129 | ```python 130 | def func(): 131 | pass 132 | 133 | func = decorator(arg1, arg2)(func) 134 | ``` 135 | 136 | 下面我们再看一些具体的例子,以加深对它的理解。 137 | 138 | # 对带参数的函数进行装饰 139 | 140 | 前面的例子中,被装饰的函数 `hello()` 是没有带参数的,我们看看被装饰函数带参数的情况。对前面例子中的 `hello()` 函数进行改写,使其带参数,如下: 141 | 142 | ```python 143 | def makeitalic(func): 144 | def wrapped(*args, **kwargs): 145 | ret = func(*args, **kwargs) 146 | return '' + ret + '' 147 | return wrapped 148 | 149 | @makeitalic 150 | def hello(name): 151 | return 'hello %s' % name 152 | 153 | @makeitalic 154 | def hello2(name1, name2): 155 | return 'hello %s, %s' % (name1, name2) 156 | ``` 157 | 158 | 由于函数 `hello` 带参数,因此内嵌包装函数 `wrapped` 也做了一点改变: 159 | 160 | - 内嵌包装函数的参数传给了 `func`,即被装饰函数,也就是说内嵌包装函数的参数跟被装饰函数的参数对应,这里使用了 `(*args, **kwargs)`,是为了适应可变参数。 161 | 162 | 看看使用: 163 | 164 | ```python 165 | >>> hello('python') 166 | 'hello python' 167 | >>> hello2('python', 'java') 168 | 'hello python, java' 169 | ``` 170 | 171 | # 带参数的装饰器 172 | 173 | 上面的例子,我们增强了函数 `hello` 的功能,给它的返回加上了标签 `...`,现在,我们想改用标签 `...` 或 `

...

`。是不是要像前面一样,再定义一个类似 `makeitalic` 的装饰器呢?其实,我们可以定义一个函数,将标签作为参数,返回一个装饰器,比如: 174 | 175 | ```python 176 | def wrap_in_tag(tag): 177 | def decorator(func): 178 | def wrapped(*args, **kwargs): 179 | ret = func(*args, **kwargs) 180 | return '<' + tag + '>' + ret + '' 181 | return wrapped 182 | 183 | return decorator 184 | ``` 185 | 186 | 现在,我们可以根据需要生成想要的装饰器了: 187 | 188 | ```python 189 | makebold = wrap_in_tag('b') # 根据 'b' 返回 makebold 生成器 190 | 191 | @makebold 192 | def hello(name): 193 | return 'hello %s' % name 194 | 195 | >>> hello('world') 196 | 'hello world' 197 | ``` 198 | 199 | 上面的形式也可以写得更加简洁: 200 | 201 | ```python 202 | @wrap_in_tag('b') 203 | def hello(name): 204 | return 'hello %s' % name 205 | ``` 206 | 207 | 这就是带参数的装饰器,其实就是在装饰器外面多了一层包装,根据不同的参数返回不同的装饰器。 208 | 209 | # 多个装饰器 210 | 211 | 现在,让我们来看看多个装饰器的例子,为了简单起见,下面的例子就不使用带参数的装饰器。 212 | 213 | ```python 214 | def makebold(func): 215 | def wrapped(): 216 | return '' + func() + '' 217 | 218 | return wrapped 219 | 220 | def makeitalic(func): 221 | def wrapped(): 222 | return '' + func() + '' 223 | 224 | return wrapped 225 | 226 | @makebold 227 | @makeitalic 228 | def hello(): 229 | return 'hello world' 230 | ``` 231 | 232 | 上面定义了两个装饰器,对 `hello` 进行装饰,上面的最后几行代码相当于: 233 | 234 | ```python 235 | def hello(): 236 | return 'hello world' 237 | 238 | hello = makebold(makeitalic(hello)) 239 | ``` 240 | 241 | 调用函数 `hello`: 242 | 243 | ```python 244 | >>> hello() 245 | 'hello world' 246 | ``` 247 | 248 | # 基于类的装饰器 249 | 250 | 前面的装饰器都是一个函数,其实也可以基于类定义装饰器,看下面的例子: 251 | 252 | ```python 253 | class Bold(object): 254 | def __init__(self, func): 255 | self.func = func 256 | 257 | def __call__(self, *args, **kwargs): 258 | return '' + self.func(*args, **kwargs) + '' 259 | 260 | @Bold 261 | def hello(name): 262 | return 'hello %s' % name 263 | 264 | >>> hello('world') 265 | 'hello world' 266 | ``` 267 | 268 | 可以看到,类 `Bold` 有两个方法: 269 | 270 | - `__init__()`:它接收一个函数作为参数,也就是被装饰的函数 271 | - `__call__()`:让类对象可调用,就像函数调用一样,在调用被装饰函数时被调用 272 | 273 | 还可以让类装饰器带参数: 274 | 275 | ```python 276 | class Tag(object): 277 | def __init__(self, tag): 278 | self.tag = tag 279 | 280 | def __call__(self, func): 281 | def wrapped(*args, **kwargs): 282 | return "<{tag}>{res}".format( 283 | res=func(*args, **kwargs), tag=self.tag 284 | ) 285 | return wrapped 286 | 287 | @Tag('b') 288 | def hello(name): 289 | return 'hello %s' % name 290 | ``` 291 | 292 | 需要注意的是,如果类装饰器有参数,则 `__init__` 接收参数,而 `__call__` 接收 `func`。 293 | 294 | # 装饰器的副作用 295 | 296 | 前面提到,使用装饰器有一个瑕疵,就是被装饰的函数,它的函数名称已经不是原来的名称了,回到最开始的例子: 297 | 298 | ```python 299 | def makeitalic(func): 300 | def wrapped(): 301 | return "" + func() + "" 302 | return wrapped 303 | 304 | @makeitalic 305 | def hello(): 306 | return 'hello world' 307 | ``` 308 | 309 | 函数 `hello` 被 `makeitalic` 装饰后,它的函数名称已经改变了: 310 | 311 | ```python 312 | >>> hello.__name__ 313 | 'wrapped' 314 | ``` 315 | 316 | 为了消除这样的副作用,Python 中的 functools 包提供了一个 wraps 的装饰器: 317 | 318 | ```python 319 | from functools import wraps 320 | 321 | def makeitalic(func): 322 | @wraps(func) # 加上 wraps 装饰器 323 | def wrapped(): 324 | return "" + func() + "" 325 | return wrapped 326 | 327 | @makeitalic 328 | def hello(): 329 | return 'hello world' 330 | 331 | >>> hello.__name__ 332 | 'hello' 333 | ``` 334 | 335 | # 小结 336 | 337 | - 本质上,装饰器就是一个返回函数的高阶函数。 338 | - 装饰器可以动态地修改一个类或函数的功能,通过在原有的类或者函数上包裹一层修饰类或修饰函数实现。 339 | - 事实上,装饰器就是闭包的一种应用,但它比较特别,接收被装饰函数为参数,并返回一个函数,赋给被装饰函数,闭包则没这种限制。 340 | 341 | # 参考资料 342 | 343 | - [Python修饰器的函数式编程 - coolshell](http://coolshell.cn/articles/11265.html) 344 | - [How can I make a chain of function decorators in Python? - Stack Overflow](http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python) 345 | - [Python中的装饰器介绍 – 思诚之道](http://www.bjhee.com/python-decorator.html) 346 | - [python装饰器入门与提高 | 赖明星](http://mingxinglai.com/cn/2015/08/python-decorator/) 347 | 348 | 349 | -------------------------------------------------------------------------------- /Functional/high_order_func.md: -------------------------------------------------------------------------------- 1 | # 高阶函数 2 | 3 | 在函数式编程中,我们可以将函数当作变量一样自由使用。一个函数接收另一个函数作为参数,这种函数称之为**高阶函数(Higher-order Functions)**。 4 | 5 | 看一个简单的例子: 6 | 7 | ```python 8 | def func(g, arr): 9 | return [g(x) for x in arr] 10 | ``` 11 | 12 | 上面的代码中,`func` 是一个高阶函数,它接收两个参数,第 1 个参数是函数,第 2 个参数是数组,`func` 的功能是将函数 g 逐个作用于数组 arr 上,并返回一个新的数组,比如,我们可以这样用: 13 | 14 | ``` 15 | def double(x): 16 | return 2 * x 17 | 18 | def square(x): 19 | return x * x 20 | 21 | arr1 = func(double, [1, 2, 3, 4]) 22 | arr2 = func(square, [1, 2, 3, 4]) 23 | ``` 24 | 25 | 不难判断出,arr1 是 [2, 4, 6, 8],arr2 是 [1, 4, 9, 16]。 26 | 27 | # 小结 28 | 29 | - 可接收其他函数作为参数的函数称为高阶函数。 30 | 31 | 32 | -------------------------------------------------------------------------------- /Functional/map_reduce_filter.md: -------------------------------------------------------------------------------- 1 | # map/reduce/filter 2 | 3 | map/reduce/filter 是 Python 中较为常用的内建高阶函数,它们为函数式编程提供了不少便利。 4 | 5 | # map 6 | 7 | `map` 函数的使用形式如下: 8 | 9 | ``` 10 | map(function, sequence) 11 | ``` 12 | 13 | **解释**:对 sequence 中的 item 依次执行 function(item),并将结果组成一个 List 返回,也就是: 14 | 15 | ``` 16 | [function(item1), function(item2), function(item3), ...] 17 | ``` 18 | 19 | 看一些简单的例子。 20 | 21 | ```python 22 | >>> def square(x): 23 | ... return x * x 24 | 25 | >>> map(square, [1, 2, 3, 4]) 26 | [1, 4, 9, 16] 27 | 28 | >>> map(lambda x: x * x, [1, 2, 3, 4]) # 使用 lambda 29 | [1, 4, 9, 16] 30 | 31 | >>> map(str, [1, 2, 3, 4]) 32 | ['1', '2', '3', '4'] 33 | 34 | >>> map(int, ['1', '2', '3', '4']) 35 | [1, 2, 3, 4] 36 | ``` 37 | 38 | 再看一个例子: 39 | 40 | ```python 41 | def double(x): 42 | return 2 * x 43 | 44 | def triple(x): 45 | return 3 *x 46 | 47 | def square(x): 48 | return x * x 49 | 50 | funcs = [double, triple, square] # 列表元素是函数对象 51 | 52 | # 相当于 [double(4), triple(4), square(4)] 53 | value = list(map(lambda f: f(4), funcs)) 54 | 55 | print value 56 | 57 | # output 58 | [8, 12, 16] 59 | ``` 60 | 61 | 上面的代码中,我们加了 list 转换,是为了兼容 Python3,在 Python2 中 map 直接返回列表,Python3 中返回迭代器。 62 | 63 | # reduce 64 | 65 | `reduce` 函数的使用形式如下: 66 | 67 | ``` 68 | reduce(function, sequence[, initial]) 69 | ``` 70 | 71 | **解释**:先将 sequence 的前两个 item 传给 function,即 function(item1, item2),函数的返回值和 sequence 的下一个 item 再传给 function,即 function(function(item1, item2), item3),如此迭代,直到 sequence 没有元素,如果有 initial,则作为初始值调用。 72 | 73 | 也就是说: 74 | 75 | ``` 76 | reduece(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 77 | ``` 78 | 79 | 看一些例子,就能很快理解了。 80 | 81 | ```python 82 | >>> reduce(lambda x, y: x * y, [1, 2, 3, 4]) # 相当于 ((1 * 2) * 3) * 4 83 | 24 84 | >>> reduce(lambda x, y: x * y, [1, 2, 3, 4], 5) # ((((5 * 1) * 2) * 3)) * 4 85 | 120 86 | >>> reduce(lambda x, y: x / y, [2, 3, 4], 72) # (((72 / 2) / 3)) / 4 87 | 3 88 | >>> reduce(lambda x, y: x + y, [1, 2, 3, 4], 5) # ((((5 + 1) + 2) + 3)) + 4 89 | 15 90 | >>> reduce(lambda x, y: x - y, [8, 5, 1], 20) # ((20 - 8) - 5) - 1 91 | 6 92 | >>> f = lambda a, b: a if (a > b) else b # 两两比较,取最大值 93 | >>> reduce(f, [5, 8, 1, 10]) 94 | 10 95 | ``` 96 | 97 | # filter 98 | 99 | `filter` 函数用于过滤元素,它的使用形式如下: 100 | 101 | ``` 102 | filter(function, sequnce) 103 | ``` 104 | 105 | **解释**:将 function 依次作用于 sequnce 的每个 item,即 function(item),将返回值为 True 的 item 组成一个 List/String/Tuple (取决于 sequnce 的类型,python3 统一返回迭代器) 返回。 106 | 107 | 看一些例子。 108 | 109 | ```python 110 | >>> even_num = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6])) 111 | >>> even_num 112 | [2, 4, 6] 113 | >>> odd_num = list(filter(lambda x: x % 2, [1, 2, 3, 4, 5, 6])) 114 | >>> odd_num 115 | [1, 3, 5] 116 | >>> filter(lambda x: x < 'g', 'hijack') 117 | 'ac' # python2 118 | >>> filter(lambda x: x < 'g', 'hijack') 119 | # python3 120 | ``` 121 | 122 | # 小结 123 | 124 | - map/reduce/filter 为函数式编程提供了不少便利,可使代码变得更简洁; 125 | - 注意在 python2 和 python3 中,map/reduce/filter 的返回值类型有所不同,python2 返回的是基本数据类型,而 python3 则返回了迭代器; 126 | 127 | # 参考资料 128 | 129 | - [Python Tutorial: Lambda Operator, filter, reduce and map](http://www.python-course.eu/lambda.php) 130 | 131 | 132 | -------------------------------------------------------------------------------- /Functional/partial.md: -------------------------------------------------------------------------------- 1 | # partial 函数 2 | 3 | Python 提供了一个 functools 的模块,该模块为高阶函数提供支持,partial 就是其中的一个函数,该函数的形式如下: 4 | 5 | ```python 6 | functools.partial(func[,*args][, **kwargs]) 7 | ``` 8 | 9 | 这里先举个例子,看看它是怎么用的。 10 | 11 | 假设有如下函数: 12 | 13 | ```python 14 | def multiply(x, y): 15 | return x * y 16 | ``` 17 | 18 | 现在,我们想返回某个数的双倍,即: 19 | 20 | ``` 21 | >>> multiply(3, y=2) 22 | 6 23 | >>> multiply(4, y=2) 24 | 8 25 | >>> multiply(5, y=2) 26 | 10 27 | ``` 28 | 29 | 上面的调用有点繁琐,每次都要传入 `y=2`,我们想到可以定义一个新的函数,把 `y=2` 作为默认值,即: 30 | 31 | ```python 32 | def double(x, y=2): 33 | return multiply(x, y) 34 | ``` 35 | 36 | 现在,我们可以这样调用了: 37 | 38 | ``` 39 | >>> double(3) 40 | 6 41 | >>> double(4) 42 | 8 43 | >>> double(5) 44 | 10 45 | ``` 46 | 47 | 事实上,我们可以不用自己定义 `double`,利用 `partial`,我们可以这样: 48 | 49 | ```python 50 | from functools import partial 51 | 52 | double = partial(multiply, y=2) 53 | ``` 54 | 55 | `partial` 接收函数 `multiply` 作为参数,固定 `multiply` 的参数 `y=2`,并返回一个新的函数给 `double`,这跟我们自己定义 `double` 函数的效果是一样的。 56 | 57 | 所以,简单而言,`partial` 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。 58 | 59 | 需要注意的是,我们上面是固定了 `multiply` 的关键字参数 `y=2`,如果直接使用: 60 | 61 | ```python 62 | double = partial(multiply, 2) 63 | ``` 64 | 65 | 则 `2` 是赋给了 `multiply` 最左边的参数 `x`,不信?我们可以验证一下: 66 | 67 | ```python 68 | from functools import partial 69 | 70 | def subtraction(x, y): 71 | return x - y 72 | 73 | f = partial(subtraction, 4) # 4 赋给了 x 74 | >>> f(10) # 4 - 10 75 | -6 76 | ``` 77 | 78 | # 小结 79 | 80 | - partial 的功能:固定函数参数,返回一个新的函数。 81 | - 当函数参数太多,需要固定某些参数时,可以使用 `functools.partial` 创建一个新的函数。 82 | 83 | # 参考资料 84 | 85 | - [偏函数 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819893624a7edc0e3e3df4d5d852a352b037c93ec000) 86 | 87 | 88 | -------------------------------------------------------------------------------- /HTTP/HTTP.md: -------------------------------------------------------------------------------- 1 | # HTTP 协议简介 2 | 3 | **HTTP** (HyperText Transfer Protocol, 超文本传输协议)是互联网上应用最为广泛的一种网络协议,它是基于 [TCP][tcp] 的**应用层**协议,简单地说就是客户端和服务器进行通信的一种规则,它的模式非常简单,就是**客户端发起请求,服务器响应请求**,如下图所示: 4 | 5 | ![](https://ofaatpail.qnssl.com/2016-11-29-http.png-480) 6 | 7 | HTTP 最早于 1991 年发布,是 0.9 版,不过目前该版本已不再用。HTTP 目前在使用的版本主要有: 8 | 9 | - HTTP/1.0,于 1996 年 5 月发布,引入了多种功能,至今仍在使用当中。 10 | - HTTP/1.1,于 1997 年 1 月发布,持久连接被默认采用,是目前最流行的版本。 11 | - HTTP/2 ,于 2015 年 5 月发布,引入了服务器推送等多种功能,是目前最新的版本。 12 | 13 | # HTTP 请求 14 | 15 | HTTP 请求由三部分组成: 16 | 17 | - **请求行**:包含请求方法、请求地址和 HTTP 协议版本 18 | - **消息报头**:包含一系列的键值对 19 | - **请求正文(可选)**:注意和消息报头之间有一个空行 20 | 21 | 如图所示: 22 | 23 | ![](https://ooo.0o0.ooo/2016/12/05/58456e61d5d47.png) 24 | 25 | 下面是一个 HTTP GET 请求的例子: 26 | 27 | ``` 28 | GET / HTTP/1.1 29 | Host: httpbin.org 30 | Connection: keep-alive 31 | Cache-Control: max-age=0 32 | Upgrade-Insecure-Requests: 1 33 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 34 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 35 | Accept-Encoding: gzip, deflate, sdch, br 36 | Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4 37 | Cookie: _ga=GA1.2.475070272.1480418329; _gat=1 38 | ``` 39 | 40 | 上面的第一行就是一个**请求行**: 41 | 42 | ``` 43 | GET / HTTP/1.1 44 | ``` 45 | 46 | 其中,`GET` 是请求方法,表示从服务器获取资源;`/` 是一个请求地址;`HTTP/1.1` 表明 HTTP 的版本是 1.1。 47 | 48 | 请求行后面的一系列键值对就是**消息报头**: 49 | 50 | ``` 51 | Host: httpbin.org 52 | Connection: keep-alive 53 | Cache-Control: max-age=0 54 | Upgrade-Insecure-Requests: 1 55 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 56 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 57 | Accept-Encoding: gzip, deflate, sdch, br 58 | Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4 59 | Cookie: _ga=GA1.2.475034215.1480418329; _gat=1 60 | ``` 61 | 62 | 其中: 63 | 64 | - Host 是请求报头域,用于指定被请求资源的 Internet 主机和端口号,它通常从 HTTP URL 中提取出来; 65 | - Connection 表示连接状态,keep-alive 表示该连接是持久连接(persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,如果客户端和服务器发现对方有一段时间没有活动,就可以主动关闭连接; 66 | - Cache-Control 用于指定缓存指令,它的值有 no-cache, no-store, max-age 等,`max-age=秒`表示资源在本地缓存多少秒; 67 | - User-Agent 用于标识请求者的一些信息,比如浏览器类型和版本,操作系统等; 68 | - Accept 用于指定客户端希望接受哪些类型的信息,比如 text/html, image/gif 等; 69 | - Accept-Encoding 用于指定可接受的内容编码; 70 | - Accept-Language 用于指定可接受的自然语言; 71 | - Cookie 用于维护状态,可做用户认证,服务器检验等,它是浏览器储存在用户电脑上的文本片段; 72 | 73 | ## HTTP 请求方法 74 | 75 | HTTP 通过不同的请求方法以多种方式来操作指定的资源,常用的请求方法如下表: 76 | 77 | | 方法 | 描述 | 78 | | :-- | :-- | 79 | | GET | 从服务器获取指定(请求地址)的资源的信息,它通常只用于读取数据,就像数据库查询一样,不会对资源进行修改。 | 80 | | POST | 向指定资源提交数据(比如提交表单,上传文件),请求服务器进行处理。数据被包含在请求正文中,这个请求可能会创建新的资源或更新现有的资源。 | 81 | | PUT | 通过指定资源的唯一标识(在服务器上的具体存放位置),请求服务器创建或更新资源。 | 82 | | DELETE | 请求服务器删除指定资源。 | 83 | | HEAD | 与 GET 方法类似,从服务器获取资源信息,和 GET 方法不同的是,HEAD 不含有呈现数据,仅仅是 HTTP 头信息。HEAD 的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获得资源的元信息(或元数据)。 | 84 | | OPTIONS | 该方法可使服务器传回资源所支持的所有 HTTP 请求方法。 | 85 | 86 | # HTTP 响应 87 | 88 | HTTP 响应与 HTTP 请求相似,由三部分组成: 89 | 90 | - **状态行**:包含 HTTP 协议版本、状态码和状态描述,以空格分隔 91 | - **响应头**:即消息报头,包含一系列的键值对 92 | - **响应正文**:返回内容,注意和响应头之间有一个空行 93 | 94 | 如图所示: 95 | 96 | ![](https://ooo.0o0.ooo/2016/12/05/58456e62d49d6.png) 97 | 98 | 下面是一个 HTTP GET 请求的响应结果: 99 | 100 | ``` 101 | HTTP/1.1 200 OK 102 | Server: nginx 103 | Date: Tue, 29 Nov 2016 13:08:38 GMT 104 | Content-Type: application/json 105 | Content-Length: 203 106 | Connection: close 107 | Access-Control-Allow-Origin: * 108 | Access-Control-Allow-Credentials: true 109 | 110 | { 111 | "args": {}, 112 | "headers": { 113 | "Host": "httpbin.org", 114 | "User-Agent": "Paw/2.3.1 (Macintosh; OS X/10.11.3) GCDHTTPRequest" 115 | }, 116 | "origin": "13.75.42.240", 117 | "url": "https://httpbin.org/get" 118 | } 119 | ``` 120 | 121 | 上面的第一行就是一个**状态行**: 122 | 123 | ``` 124 | HTTP/1.1 200 OK 125 | ``` 126 | 127 | 其中,`200` 是状态码,表示客户端请求成功,`OK` 是相应的状态描述。 128 | 129 | 状态码是一个三位的数字,常见的状态码有以下几类: 130 | 131 | - 1XX 消息 -- 请求已被服务接收,继续处理 132 | - 2XX 成功 -- 请求已成功被服务器接收、理解、并接受 133 | - 200 OK 134 | - 201 Created 已创建 135 | - 202 Accepted 接收 136 | - 203 Non-Authoritative Information 非认证信息 137 | - 204 No Content 无内容 138 | - 3XX 重定向 -- 需要后续操作才能完成这一请求 139 | - 301 Moved Permanently 请求永久重定向 140 | - 302 Moved Temporarily 请求临时重定向 141 | - 304 Not Modified 文件未修改,可以直接使用缓存的文件 142 | - 305 Use Proxy 使用代理 143 | - 4XX 请求错误 -- 请求含有词法错误或者无法被执行 144 | - 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解 145 | - 401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用 146 | - 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因 147 | - 404 Not Found 请求的资源不存在,例如,输入了错误的URL 148 | - 5XX 服务器错误 -- 服务器在处理某个正确请求时发生错误 149 | - 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求 150 | - 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常 151 | - 504 Gateway Time-out 网关超时 152 | 153 | 状态行后面的一系列键值对就是消息报头,即响应头: 154 | 155 | ``` 156 | Server: nginx 157 | Date: Tue, 29 Nov 2016 13:08:38 GMT 158 | Content-Type: application/json 159 | Content-Length: 203 160 | Connection: close 161 | Access-Control-Allow-Origin: * 162 | Access-Control-Allow-Credentials: true 163 | ``` 164 | 165 | 其中: 166 | 167 | - Server 包含了服务器用来处理请求的软件信息,跟请求报头域 User-Agent 相对应; 168 | - Content-Type 用于指定发送给接收者(比如浏览器)的响应正文的媒体类型,比如 text/html, text/css, image/png, image/jpeg, video/mp4, application/pdf, application/json 等; 169 | - Content-Length 指明本次回应的数据长度; 170 | 171 | # 再议 POST 和 PUT 172 | 173 | 注意到,POST 和 PUT 都可用于创建或更新资源,然而,它们之间还是有比较大的区别: 174 | 175 | - POST 所对应的 URI 并非创建的资源本身,而是资源的接收者,资源本身的存放位置由服务器决定;而 PUT 所对应的 URI 是要创建或更新的资源本身,它指明了具体的存放位置 176 | 177 | 比如,往某个站点添加一篇文章,如果**使用 POST 来创建资源**,可类似这样: 178 | 179 | ``` 180 | POST /articles HTTP/1.1 181 | 182 | { 183 | "author": "ethan", 184 | "title": "hello world", 185 | "content": "hello world" 186 | } 187 | ``` 188 | 189 | 在上面,POST 对应的 URI 是 `/articles`,它是资源的接收者,而非资源的标识,如果资源被成功创建,服务器可以返回 `201 Created` 状态以及新建资源的位置,比如: 190 | 191 | ``` 192 | HTTP/1.1 201 Created 193 | Location: /articles/abcdef123 194 | ``` 195 | 196 | 我们如果知道新建资源的标识符,可以**使用 PUT 来创建资源**,比如: 197 | 198 | ``` 199 | PUT /articles/abcdef234 HTTP/1.1 200 | 201 | { 202 | "author": "peter", 203 | "title": "hello world", 204 | "content": "hello world" 205 | } 206 | ``` 207 | 208 | 在上面,PUT 对应的 URI 是 `/articles/abcdef234`,它指明了资源的存放位置,如果资源被成功创建,服务器可以返回 `201 Created` 状态以及新建资源的位置,比如: 209 | 210 | ``` 211 | HTTP/1.1 201 Created 212 | Location: /articles/abcdef234 213 | ``` 214 | 215 | - 使用 PUT 更新某一资源,需要更新资源的全部属性;而使用 POST,可以更新全部或一部分值 216 | 217 | 比如使用 PUT 更新地址为 `/articles/abcdef234` 的文章的标题,我们需要发送所有值: 218 | 219 | ``` 220 | PUT /articles/abcdef234 HTTP/1.1 221 | 222 | { 223 | "author": "peter", 224 | "title": "hello python", 225 | "content": "hello world" 226 | } 227 | ``` 228 | 229 | 而使用 POST,可以更新某个域的值: 230 | 231 | ``` 232 | POST /articles/abcdef234 HTTP/1.1 233 | 234 | { 235 | "title": "hello python" 236 | } 237 | ``` 238 | 239 | - POST 是不幂等的,PUT 是幂等的,这是一个很重要的区别 240 | 241 | > HTTP 方法的幂等性是指一次和多次请求某一个资源应该具有同样的**副作用**,注意这里是副作用,而不是返回结果。 242 | 243 | **GET** 方法用于获取资源,不会改变资源的状态,不论调用一次还是多次都没有副作用,因此它是幂等的;**DELETE** 方法用于删除资源,有副作用,但调用一次或多次都是删除同个资源,产生的副作用是相同的,因此也是幂等的;**POST** 是不幂等的,因为两次相同的 POST 请求会在服务器创建两份资源,它们具有不同的 URI;**PUT** 是幂等的,对同一 URI 进行多次 PUT 的副作用和一次 PUT 是相同的。 244 | 245 | # HTTP 特点 246 | 247 | - 客户端/服务器模式 248 | - 简单快速:客户端向服务器请求服务时,通过传送请求方法、请求地址和数据体(可选)即可 249 | - 灵活:允许传输任意类型的数据对象,通过 Content-Type 标识 250 | - 无状态:对事物处理没记忆能力 251 | 252 | # 小结 253 | 254 | - HTTP 是在网络上传输 HTML 的协议,用于浏览器和服务器的通信,默认使用 80 端口。 255 | - URL 地址用于定位资源,HTTP 中的 GET, POST, PUT, DELETE 用于操作资源,比如查询,增加,更新等。 256 | - GET, PUT, DELETE 是幂等的,POST 是不幂等的。 257 | - POST VS PUT 258 | - 使用 PUT 创建资源需要提供资源的唯一标识(具体存放位置),POST 不需要,POST 的数据存放位置由服务器自己决定 259 | - 使用 PUT 更新某一资源,需要更新资源的全部属性;而使用 POST,可以更新全部或一部分值 260 | - POST 是不幂等的,PUT 是幂等的,这是一个很重要的区别 261 | - GET 可提交的数据量受到 URL 长度的限制,HTTP 协议规范没有对 URL 长度进行限制,这个限制是特定的浏览器及服务器对它的限制。 262 | - 理论上讲,POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制,出于安全考虑,服务器软件在实现时会做一定限制。 263 | 264 | # 参考资料 265 | 266 | - [超文本传输协议 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#HTTP.2F1.1) 267 | - [HTTP 协议入门 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/08/http.html) 268 | - [HTTP幂等性概念和应用 | 酷 壳 - CoolShell.cn](http://coolshell.cn/articles/4787.html) 269 | - [Http协议详解 - 简书](http://www.jianshu.com/p/e83d323c6bcc) 270 | - [When to use PUT or POST - The RESTful cookbook](http://restcookbook.com/HTTP%20Methods/put-vs-post/) 271 | - [To PUT or POST?](https://stormpath.com/blog/put-or-post) 272 | - [全面解读HTTP Cookie](http://www.webryan.net/2011/08/wiki-of-http-cookie/) 273 | - [HTTP cookies 详解 | bubkoo](http://bubkoo.com/2014/04/21/http-cookies-explained/) 274 | - [HTTP 接口设计指北](https://github.com/bolasblack/http-api-guide) 275 | 276 | 277 | 278 | [tcp]: https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE 279 | 280 | 281 | -------------------------------------------------------------------------------- /HTTP/README.md: -------------------------------------------------------------------------------- 1 | # HTTP 服务 2 | 3 | 本章主要介绍: 4 | 5 | - [HTTP 协议](./HTTP.md) 6 | - [Requests 库的使用](./Requests.md) 7 | 8 | 9 | -------------------------------------------------------------------------------- /PYTHONroadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethan-funny/explore-python/dbb36fb53334d3d5b1c0f28878be1b6202011331/PYTHONroadmap.png -------------------------------------------------------------------------------- /Process-Thread-Coroutine/README.md: -------------------------------------------------------------------------------- 1 | # 进程、线程和协程 2 | 3 | 操作系统的设计,可以归结为三点: 4 | 5 | - 以多进程形式,允许多个任务同时运行; 6 | - 以多线程形式,允许将单个任务分成多个子任务运行; 7 | - 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。 8 | 9 | 本章主要介绍在 Python 中如何进行进程和线程编程等,主要有以下几个方面: 10 | 11 | - [进程](./process.md) 12 | - [线程](./thread.md) 13 | - [ThreadLocal](./threadlocal.md) 14 | - [协程](./coroutine.md) 15 | 16 | # 参考资料 17 | 18 | - [进程和线程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868322563729e03f6905ea94f0195528e3647887415000) 19 | - [进程与线程的一个简单解释 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html) 20 | 21 | 22 | -------------------------------------------------------------------------------- /Process-Thread-Coroutine/coroutine.md: -------------------------------------------------------------------------------- 1 | # 协程 2 | 3 | 与子程序(或者说函数)一样,**协程(coroutine)**也是一种程序组件。Donald Knuth 曾说,**子程序是协程的特例**。 4 | 5 | 一个子程序就是一次函数调用,它只有一个入口,一次返回,调用顺序是明确的。但协程的调用和子程序则大不一样,**协程允许有多个入口对程序进行中断、继续执行等操作**。 6 | 7 | Python2 可以通过 yield 来实现基本的协程,但不够强大,第三方库 [gevent](http://www.gevent.org/) 对协程提供了强大的支持。另外,Python3.5 提供了 async/await 语法来实现对协程的支持。本文只讨论通过 yield 来实现协程。 8 | 9 | 对于经典的**生产者-消费者模型**,如果用多线程来实现,我们就需要一个线程写消息,一个线程读消息,而且需要锁机制来避免对共享资源的访问冲突。 10 | 11 | 相比多线程,协程的一大特点就是**它在一个线程内执行**,既避免了多线程之间切换带来的开销,也避免了对共享资源的访问冲突。 12 | 13 | 下面,让我们看看怎么用 yield 来实现简单的**生产者-消费者模型**。 14 | 15 | ```python 16 | import time 17 | 18 | def consumer(): 19 | message = '' 20 | while True: 21 | n = yield message # yield 使函数中断 22 | if not n: 23 | return 24 | print '[CONSUMER] Consuming %s...' % n 25 | time.sleep(2) 26 | message = '200 OK' 27 | 28 | def produce(c): 29 | c.next() # 启动生成器 30 | n = 0 31 | while n < 5: 32 | n = n + 1 33 | print '[PRODUCER] Producing %s...' % n 34 | r = c.send(n) # 通过 send 切换到 consumer 执行 35 | print '[PRODUCER] Consumer return: %s' % r 36 | c.close() 37 | 38 | if __name__ == '__main__': 39 | c = consumer() 40 | produce(c) 41 | ``` 42 | 43 | 在上面的代码中,消费者 `consumer` 是一个生成器函数,我们把它作为参数传给 `produce`,其中,next 方法用于启动生成器,send 方法用于发送消息给 `consumer`,并切换到 `consumer` 执行。`consumer` 通过 yield 获取到消息,然后进行处理,又通过 yield 返回消息给 `produce`,并转到 `produce` 执行,如此反复。执行结果如下: 44 | 45 | ```python 46 | [PRODUCER] Producing 1... 47 | [CONSUMER] Consuming 1... 48 | [PRODUCER] Consumer return: 200 OK 49 | [PRODUCER] Producing 2... 50 | [CONSUMER] Consuming 2... 51 | [PRODUCER] Consumer return: 200 OK 52 | [PRODUCER] Producing 3... 53 | [CONSUMER] Consuming 3... 54 | [PRODUCER] Consumer return: 200 OK 55 | [PRODUCER] Producing 4... 56 | [CONSUMER] Consuming 4... 57 | [PRODUCER] Consumer return: 200 OK 58 | [PRODUCER] Producing 5... 59 | [CONSUMER] Consuming 5... 60 | [PRODUCER] Consumer return: 200 OK 61 | ``` 62 | 63 | # 小结 64 | 65 | - 子程序就是协程的一种特例 66 | - 协程的特点在于是一个线程内执行,没有线程之间切换的开销 67 | - 协程只有一个线程,不需多线程的锁机制 68 | - 协程的切换由用户自己管理和调度 69 | - 通过创建协程将异步编程同步化 70 | 71 | # 参考资料 72 | 73 | - [协程 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B) 74 | - [Python 线程与协程 - Yu's](http://blog.rainy.im/2016/04/07/python-thread-and-coroutine/) 75 | - [谈谈Python的生成器 – 思诚之道](http://www.bjhee.com/python-yield.html) 76 | - [协程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868328689835ecd883d910145dfa8227b539725e5ed000) 77 | 78 | -------------------------------------------------------------------------------- /Process-Thread-Coroutine/process.md: -------------------------------------------------------------------------------- 1 | # 进程 2 | 3 | **进程(process)是正在运行的程序的实例,但一个程序可能会产生多个进程**。比如,打开 Chrome 浏览器程序,它可能会产生多个进程,主程序需要一个进程,一个网页标签需要一个进程,一个插件也需要一个进程,等等。 4 | 5 | 每个进程都有自己的地址空间,内存,数据栈以及其他记录其运行状态的辅助数据,不同的进程只能使用消息队列、共享内存等进程间通讯(IPC)方法进行通信,而不能直接共享信息。 6 | 7 | # fork() 8 | 9 | 在介绍 Python 的进程编程之前,让我们先看看 Unix/Linux 中的 `fork` 函数。在 Unix/Linux 系统中,`fork` 函数被用于创建进程。这个函数很特殊,对于普通的函数,调用它一次,返回一次,但是调用 `fork` 一次,它返回两次。事实上,`fork` 函数创建了新的进程,我们把它称为子进程,子进程几乎是当前进程(即父进程)的一个拷贝:它会复制父进程的代码段,堆栈段和数据段。 10 | 11 | 对于父进程,`fork` 函数返回了子进程的进程号 pid,对于子进程,`fork` 函数则返回 `0`,这也是 `fork` 函数返回两次的原因,根据返回值,我们可以判断进程是父进程还是子进程。 12 | 13 | 下面我们看一段 C 代码,它展示了 fork 的基本使用: 14 | 15 | ```c 16 | #include 17 | #include 18 | 19 | int main(int argc, char const *argv[]) 20 | { 21 | int pid; 22 | pid = fork(); // 使用 fork 函数 23 | 24 | if (pid < 0) { 25 | printf("Fail to create process\n"); 26 | } 27 | else if (pid == 0) { 28 | printf("I am child process (%d) and my parent is (%d)\n", getpid(), getppid()); 29 | } 30 | else { 31 | printf("I (%d) just created a child process (%d)\n", getpid(), pid); 32 | } 33 | return 0; 34 | } 35 | ``` 36 | 37 | 其中,`getpid` 用于获取当前进程号,`getppid` 用于获取父进程号。 38 | 39 | 事实上,Python 的 os 模块包含了普遍的操作系统功能,该模块也提供了 `fork` 函数,把上面的代码改成用 Python 来实现,如下: 40 | 41 | ```python 42 | import os 43 | 44 | pid = os.fork() 45 | 46 | if pid < 0: 47 | print 'Fail to create process' 48 | elif pid == 0: 49 | print 'I am child process (%s) and my parent is (%s).' % (os.getpid(), os.getppid()) 50 | else: 51 | print 'I (%s) just created a child process (%s).' % (os.getpid(), pid) 52 | ``` 53 | 54 | 运行上面的代码,产生如下输出: 55 | 56 | ``` 57 | I (86645) just created a child process (86646). 58 | I am child process (86646) and my parent is (86645). 59 | ``` 60 | 61 | 需要注意的是,虽然子进程复制了父进程的代码段和数据段等,但是一旦子进程开始运行,子进程和父进程就是相互独立的,它们之间不再共享任何数据。 62 | 63 | # 多进程 64 | 65 | Python 提供了一个 [multiprocessing][mp] 模块,利用它,我们可以来编写跨平台的多进程程序,但需要注意的是 multiprocessing 在 Windows 和 Linux 平台的不一致性:一样的代码在 Windows 和 Linux 下运行的结果可能不同。因为 Windows 的进程模型和 Linux 不一样,Windows 下没有 fork。 66 | 67 | 我们先来看一个简单的例子,该例子演示了在主进程中启动一个子进程,并等待其结束,代码如下: 68 | 69 | ```python 70 | import os 71 | from multiprocessing import Process 72 | 73 | # 子进程要执行的代码 74 | def child_proc(name): 75 | print 'Run child process %s (%s)...' % (name, os.getpid()) 76 | 77 | if __name__ == '__main__': 78 | print 'Parent process %s.' % os.getpid() 79 | p = Process(target=child_proc, args=('test',)) 80 | print 'Process will start.' 81 | p.start() 82 | p.join() 83 | print 'Process end.' 84 | ``` 85 | 86 | 在上面的代码中,我们从 multiprocessing 模块引入了 Process,Process 是一个用于创建进程对象的类,其中,target 指定了进程要执行的函数,args 指定了参数。在创建了进程实例 p 之后,我们调用 start 方法开始执行该子进程,接着,我们又调用了 join 方法,该方法用于阻塞子进程以外的所有进程(这里指父进程),当子进程执行完毕后,父进程才会继续执行,它通常用于进程间的同步。 87 | 88 | 可以看到,用上面这种方式来创建进程比直接使用 `fork` 更简单易懂。现在,让我们看下输出结果: 89 | 90 | ``` 91 | Parent process 7170. 92 | Process will start. 93 | Run child process test (10075)... 94 | Process end. 95 | ``` 96 | 97 | ## multiprocessing 与平台有关 98 | 99 | ``` python 100 | import random 101 | import os 102 | from multiprocessing import Process 103 | 104 | num = random.randint(0, 100) 105 | 106 | def show_num(): 107 | print("pid:{}, num is {}".format(os.getpid(), num)) 108 | 109 | if __name__ == "__main__": 110 | print("pid:{}, num is {}".format(os.getpid(), num)) 111 | p = Process(target=show_num) 112 | p.start() 113 | p.join() 114 | ``` 115 | 116 | 在 Windows 下运行以上代码,输出的结果如下(你得到不一样的结果也是对的): 117 | 118 | ``` 119 | pid:6504, num is 25 120 | pid:6880, num is 6 121 | ``` 122 | 123 | 我们发现,num 的值是不一样的! 124 | 125 | 在 Linux 下运行以上代码,可以看到 num 的值是一样的: 126 | 127 | ``` 128 | pid:11747, num is 13 129 | pid:11748, num is 13 130 | ``` 131 | 132 | 这是因为根据不同的平台,multiprocessing支持三种启动进程的办法: 133 | #### spawn 134 | 父进程会启动一个新的解释器,子进程只会继承run()所需的资源。 135 | 不必要的文件描述符和句柄(一种指针)不会被继承。 136 | 该方法和fork,forkserver相比,启动进程较慢。 137 | 138 | 可在Unix和Windows上使用。 Windows上的默认设置。 139 | 140 | #### fork 141 | 父进程使用 os.fork() 来产生 Python 解释器分叉。 142 | 子进程在开始时实际上与父进程相同,并且会继承父进程的所有资源。 143 | 多线程的安全是有问题的。 144 | 145 | 只能在Unix上使用。Unix上的默认设置。 146 | 147 | #### forkserver 148 | 程序启动并选择forkserver启动方法时,将启动一个服务器进程。 149 | 之后每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。 150 | 分叉服务器进程是单线程的,因此使用 os.fork() 是安全的。 151 | 没有不必要的资源被继承。 152 | 153 | 可在Unix平台上使用,并支持通过Unix管道传递文件描述符。 154 | 155 | ## 使用进程池创建多个进程 156 | 157 | 在上面,我们只是创建了一个进程,如果要创建多个进程呢?Python 提供了**进程池**的方式,让我们批量创建子进程,让我们看一个简单的示例: 158 | 159 | ```python 160 | import os, time 161 | from multiprocessing import Pool 162 | 163 | def foo(x): 164 | print 'Run task %s (pid:%s)...' % (x, os.getpid()) 165 | time.sleep(2) 166 | print 'Task %s result is: %s' % (x, x * x) 167 | 168 | if __name__ == '__main__': 169 | print 'Parent process %s.' % os.getpid() 170 | p = Pool(4) # 设置进程数 171 | for i in range(5): 172 | p.apply_async(foo, args=(i,)) # 设置每个进程要执行的函数和参数 173 | print 'Waiting for all subprocesses done...' 174 | p.close() 175 | p.join() 176 | print 'All subprocesses done.' 177 | ``` 178 | 179 | 在上面的代码中,Pool 用于生成进程池,对 Pool 对象调用 apply_async 方法可以使每个进程异步执行任务,也就说不用等上一个任务执行完才执行下一个任务,close 方法用于关闭进程池,确保没有新的进程加入,join 方法会等待所有子进程执行完毕。 180 | 181 | 看看执行结果: 182 | 183 | ```python 184 | Parent process 7170. 185 | Run task 1 (pid:10320)... 186 | Run task 0 (pid:10319)... 187 | Run task 3 (pid:10322)... 188 | Run task 2 (pid:10321)... 189 | Waiting for all subprocesses done... 190 | Task 1 result is: 1 191 | Task 0 result is: 0 192 | Run task 4 (pid:10320)... 193 | Task 3 result is: 9 194 | Task 2 result is: 4 195 | Task 4 result is: 16 196 | All subprocesses done. 197 | ``` 198 | 199 | # 进程间通信 200 | 201 | 进程间的通信可以通过管道(Pipe),队列(Queue)等多种方式来实现。Python 的 multiprocessing 模块封装了底层的实现机制,让我们可以很容易地实现进程间的通信。 202 | 203 | 下面以队列(Queue)为例,在父进程中创建两个子进程,一个往队列写数据,一个从对列读数据,代码如下: 204 | 205 | ```python 206 | # -*- coding: utf-8 -*- 207 | 208 | from multiprocessing import Process, Queue 209 | 210 | # 向队列中写入数据 211 | def write_task(q): 212 | try: 213 | n = 1 214 | while n < 5: 215 | print "write, %d" % n 216 | q.put(n) 217 | time.sleep(1) 218 | n += 1 219 | except BaseException: 220 | print "write_task error" 221 | finally: 222 | print "write_task end" 223 | 224 | # 从队列读取数据 225 | def read_task(q): 226 | try: 227 | n = 1 228 | while n < 5: 229 | print "read, %d" % q.get() 230 | time.sleep(1) 231 | n += 1 232 | except BaseException: 233 | print "read_task error" 234 | finally: 235 | print "read_task end" 236 | 237 | if __name__ == "__main__": 238 | q = Queue() # 父进程创建Queue,并传给各个子进程 239 | 240 | pw = Process(target=write_task, args=(q,)) 241 | pr = Process(target=read_task, args=(q,)) 242 | 243 | pw.start() # 启动子进程 pw,写入 244 | pr.start() # 启动子进程 pr,读取 245 | pw.join() # 等待 pw 结束 246 | pr.join() # 等待 pr 结束 247 | print "DONE" 248 | ``` 249 | 250 | 执行结果如下: 251 | 252 | ```python 253 | write, 1 254 | read, 1 255 | write, 2 256 | read, 2 257 | write, 3 258 | read, 3 259 | write, 4 260 | read, 4 261 | write_task end 262 | read_task end 263 | DONE 264 | ``` 265 | 266 | # 小结 267 | 268 | - 进程是正在运行的程序的实例。 269 | - 由于每个进程都有各自的内存空间,数据栈等,所以只能使用进程间通讯(Inter-Process Communication, IPC),而不能直接共享信息。 270 | - Python 的 multiprocessing 模块封装了底层的实现机制,让我们可以更简单地编写多进程程序。 271 | 272 | # 参考资料 273 | 274 | - [多进程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868323401155ceb3db1e2044f80b974b469eb06cb43000) 275 | - [Linux下Fork与Exec使用 - hicjiajia - 博客园](http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940154.html) 276 | - [Python 中的进程、线程、协程、同步、异步、回调 - 七牛云存储 - SegmentFault](https://segmentfault.com/a/1190000001813992) 277 | - [编程中的进程、线程、协程、同步、异步、回调 · 浮生半日闲](https://wangdashuaihenshuai.github.io/2015/10/17/%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E8%BF%9B%E7%A8%8B%E3%80%81%E7%BA%BF%E7%A8%8B%E3%80%81%E5%8D%8F%E7%A8%8B%E3%80%81%E5%90%8C%E6%AD%A5%E3%80%81%E5%BC%82%E6%AD%A5%E3%80%81%E5%9B%9E%E8%B0%83/) 278 | - [python中多进程以及多线程编程的总结 - Codefly](http://sunms.codefly.top/2016/11/05/python%E4%B8%AD%E5%A4%9A%E8%BF%9B%E7%A8%8B%E4%BB%A5%E5%8F%8A%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E7%9A%84%E6%80%BB%E7%BB%93/) 279 | - [multithreading - Python multiprocessing.Pool: when to use apply, apply_async or map? - Stack Overflow](http://stackoverflow.com/questions/8533318/python-multiprocessing-pool-when-to-use-apply-apply-async-or-map) 280 | - [multiprocessing --- 基于进程的并行¶](https://docs.python.org/zh-cn/3/library/multiprocessing.html) 281 | 282 | 283 | [mp]: https://docs.python.org/2/library/multiprocessing.html 284 | -------------------------------------------------------------------------------- /Process-Thread-Coroutine/thread.md: -------------------------------------------------------------------------------- 1 | # 线程 2 | 3 | **线程(thread)是进程(process)中的一个实体,一个进程至少包含一个线程**。比如,对于视频播放器,显示视频用一个线程,播放音频用另一个线程。如果我们把进程看成一个容器,则线程是此容器的工作单位。 4 | 5 | 进程和线程的区别主要有: 6 | 7 | - 进程之间是相互独立的,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,但互不影响;而同一个进程的多个线程是内存共享的,所有变量都由所有线程共享; 8 | - 由于进程间是独立的,因此一个进程的崩溃不会影响到其他进程;而线程是包含在进程之内的,线程的崩溃就会引发进程的崩溃,继而导致同一进程内的其他线程也奔溃; 9 | 10 | # 多线程 11 | 12 | 在 Python 中,进行多线程编程的模块有两个:thread 和 threading。其中,thread 是低级模块,threading 是高级模块,对 thread 进行了封装,一般来说,我们只需使用 threading 这个模块。 13 | 14 | 下面,我们看一个简单的例子: 15 | 16 | ```python 17 | from threading import Thread, current_thread 18 | 19 | def thread_test(name): 20 | print 'thread %s is running...' % current_thread().name 21 | print 'hello', name 22 | print 'thread %s ended.' % current_thread().name 23 | 24 | if __name__ == "__main__": 25 | print 'thread %s is running...' % current_thread().name 26 | print 'hello world!' 27 | t = Thread(target=thread_test, args=("test",), name="TestThread") 28 | t.start() 29 | t.join() 30 | print 'thread %s ended.' % current_thread().name 31 | ``` 32 | 33 | 可以看到,创建一个新的线程,就是把一个函数和函数参数传给 Thread 实例,然后调用 start 方法开始执行。代码中的 current_thread 用于返回当前线程的实例。 34 | 35 | 执行结果如下: 36 | 37 | ``` 38 | thread MainThread is running... 39 | hello world! 40 | thread TestThread is running... 41 | hello test 42 | thread TestThread ended. 43 | thread MainThread ended. 44 | ``` 45 | 46 | # 锁 47 | 48 | 由于同一个进程之间的线程是内存共享的,所以当多个线程对同一个变量进行修改的时候,就会得到意想不到的结果。 49 | 50 | 让我们先看一个简单的例子: 51 | 52 | ```python 53 | from threading import Thread, current_thread 54 | 55 | num = 0 56 | 57 | def calc(): 58 | global num 59 | print 'thread %s is running...' % current_thread().name 60 | for _ in xrange(10000): 61 | num += 1 62 | print 'thread %s ended.' % current_thread().name 63 | 64 | if __name__ == '__main__': 65 | print 'thread %s is running...' % current_thread().name 66 | 67 | threads = [] 68 | for i in range(5): 69 | threads.append(Thread(target=calc)) 70 | threads[i].start() 71 | for i in range(5): 72 | threads[i].join() 73 | 74 | print 'global num: %d' % num 75 | print 'thread %s ended.' % current_thread().name 76 | ``` 77 | 78 | 在上面的代码中,我们创建了 5 个线程,每个线程对全局变量 num 进行 10000 次的 加 1 操作,这里之所以要循环 10000 次,是为了延长单个线程的执行时间,使线程执行时能出现中断切换的情况。现在问题来了,当这 5 个线程执行完毕时,全局变量的值是多少呢?是 50000 吗? 79 | 80 | 让我们看下执行结果: 81 | 82 | ```python 83 | thread MainThread is running... 84 | thread Thread-34 is running... 85 | thread Thread-34 ended. 86 | thread Thread-35 is running... 87 | thread Thread-36 is running... 88 | thread Thread-37 is running... 89 | thread Thread-38 is running... 90 | thread Thread-35 ended. 91 | thread Thread-38 ended. 92 | thread Thread-36 ended. 93 | thread Thread-37 ended. 94 | global num: 30668 95 | thread MainThread ended. 96 | ``` 97 | 98 | 我们发现 num 的值是 30668,事实上,num 的值是不确定的,你再运行一遍,会发现结果变了。 99 | 100 | 原因是因为 `num += 1` 不是一个原子操作,也就是说它在执行时被分成若干步: 101 | 102 | - 计算 num + 1,存入临时变量 tmp 中; 103 | - 将 tmp 的值赋给 num. 104 | 105 | 由于线程是交替运行的,线程在执行时可能中断,就会导致其他线程读到一个脏值。 106 | 107 | 为了保证计算的准确性,我们就需要给 `num += 1` 这个操作加上`锁`。当某个线程开始执行这个操作时,由于该线程获得了锁,因此其他线程不能同时执行该操作,只能等待,直到锁被释放,这样就可以避免修改的冲突。创建一个锁可以通过 `threading.Lock()` 来实现,代码如下: 108 | 109 | ```python 110 | from threading import Thread, current_thread, Lock 111 | 112 | num = 0 113 | lock = Lock() 114 | 115 | def calc(): 116 | global num 117 | print 'thread %s is running...' % current_thread().name 118 | for _ in xrange(10000): 119 | lock.acquire() # 获取锁 120 | num += 1 121 | lock.release() # 释放锁 122 | print 'thread %s ended.' % current_thread().name 123 | 124 | if __name__ == '__main__': 125 | print 'thread %s is running...' % current_thread().name 126 | 127 | threads = [] 128 | for i in range(5): 129 | threads.append(Thread(target=calc)) 130 | threads[i].start() 131 | for i in range(5): 132 | threads[i].join() 133 | 134 | print 'global num: %d' % num 135 | print 'thread %s ended.' % current_thread().name 136 | ``` 137 | 138 | 让我们看下执行结果: 139 | 140 | ```python 141 | thread MainThread is running... 142 | thread Thread-44 is running... 143 | thread Thread-45 is running... 144 | thread Thread-46 is running... 145 | thread Thread-47 is running... 146 | thread Thread-48 is running... 147 | thread Thread-45 ended. 148 | thread Thread-47 ended. 149 | thread Thread-48 ended. 150 | thread Thread-46 ended. 151 | thread Thread-44 ended. 152 | global num: 50000 153 | thread MainThread ended. 154 | ``` 155 | 156 | ## GIL 锁 157 | 158 | 讲到 Python 中的多线程,就不得不面对 `GIL` 锁,`GIL` 锁的存在导致 Python 不能有效地使用多线程实现多核任务,因为在同一时间,只能有一个线程在运行。 159 | 160 | `GIL` 全称是 Global Interpreter Lock,译为**全局解释锁**。早期的 Python 为了支持多线程,引入了 GIL 锁,用于解决多线程之间数据共享和同步的问题。但这种实现方式后来被发现是非常低效的,当大家试图去除 GIL 的时候,却发现大量库代码已重度依赖 GIL,由于各种各样的历史原因,GIL 锁就一直保留到现在。 161 | 162 | # 小结 163 | 164 | - 一个程序至少有一个进程,一个进程至少有一个线程。 165 | - 进程是操作系统**分配资源**(比如内存)的最基本单元,线程是操作系统能够进行**调度和分派**的最基本单元。 166 | - 在 Python 中,进行多线程编程的模块有两个:thread 和 threading。其中,thread 是低级模块,threading 是高级模块,对 thread 进行了封装,一般来说,我们只需使用 threading 这个模块。 167 | - 在执行多线程操作时,注意加锁。 168 | 169 | # 参考资料 170 | 171 | - [多线程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143192823818768cd506abbc94eb5916192364506fa5d000) 172 | - [Python的GIL是什么鬼,多线程性能究竟如何 • cenalulu's Tech Blog](http://cenalulu.github.io/python/gil-in-python/) 173 | - [python中多进程以及多线程编程的总结 - Codefly](http://sunms.codefly.top/2016/11/05/python%E4%B8%AD%E5%A4%9A%E8%BF%9B%E7%A8%8B%E4%BB%A5%E5%8F%8A%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E7%9A%84%E6%80%BB%E7%BB%93/) 174 | 175 | 176 | -------------------------------------------------------------------------------- /Process-Thread-Coroutine/threadlocal.md: -------------------------------------------------------------------------------- 1 | # ThreadLocal 2 | 3 | 我们知道,同一进程的多个线程之间是内存共享的,这意味着,当一个线程对全局变量做了修改,将会影响到其他所有线程,这是很危险的。为了避免多个线程同时修改全局变量,我们就需要对全局变量的修改加锁。 4 | 5 | 除了对全局变量的修改进行加锁,你可能也想到了可以使用线程自己的局部变量,因为局部变量只有线程自己能看见,对同一进程的其他线程是不可访问的。确实如此,让我们先看一个例子: 6 | 7 | ```python 8 | from threading import Thread, current_thread 9 | 10 | def echo(num): 11 | print current_thread().name, num 12 | 13 | def calc(): 14 | print 'thread %s is running...' % current_thread().name 15 | local_num = 0 16 | for _ in xrange(10000): 17 | local_num += 1 18 | echo(local_num) 19 | print 'thread %s ended.' % current_thread().name 20 | 21 | if __name__ == '__main__': 22 | print 'thread %s is running...' % current_thread().name 23 | 24 | threads = [] 25 | for i in range(5): 26 | threads.append(Thread(target=calc)) 27 | threads[i].start() 28 | for i in range(5): 29 | threads[i].join() 30 | 31 | print 'thread %s ended.' % current_thread().name 32 | ``` 33 | 34 | 在上面的代码中,我们创建了 5 个线程,每个线程都对自己的局部变量 local_num 进行 10000 次的加 1 操作。由于对线程局部变量的修改不会影响到其他线程,因此,我们可以看到,每个线程结束时打印的 local_num 的值都为 10000,执行结果如下: 35 | 36 | ```python 37 | thread MainThread is running... 38 | thread Thread-4 is running... 39 | Thread-4 10000 40 | thread Thread-4 ended. 41 | thread Thread-5 is running... 42 | Thread-5 10000 43 | thread Thread-5 ended. 44 | thread Thread-6 is running... 45 | Thread-6 10000 46 | thread Thread-6 ended. 47 | thread Thread-7 is running... 48 | Thread-7 10000 49 | thread Thread-7 ended. 50 | thread Thread-8 is running... 51 | Thread-8 10000 52 | thread Thread-8 ended. 53 | thread MainThread ended. 54 | ``` 55 | 56 | 上面这种**线程使用自己的局部变量**的方法虽然可以避免多线程对同一变量的访问冲突,但还是有一些问题。在实际的开发中,我们会调用很多函数,每个函数又有很多个局部变量,这时每个函数都这么传参数显然是不可取的。 57 | 58 | 为了解决这个问题,一个比较容易想到的做法就是创建一个全局字典,以线程的 ID 作为 key,线程的局部数据作为 value,这样就可以消除函数传参的问题,代码如下: 59 | 60 | ```python 61 | from threading import Thread, current_thread 62 | 63 | global_dict = {} 64 | 65 | def echo(): 66 | num = global_dict[current_thread()] # 线程根据自己的 ID 获取数据 67 | print current_thread().name, num 68 | 69 | def calc(): 70 | print 'thread %s is running...' % current_thread().name 71 | 72 | global_dict[current_thread()] = 0 73 | for _ in xrange(10000): 74 | global_dict[current_thread()] += 1 75 | echo() 76 | 77 | print 'thread %s ended.' % current_thread().name 78 | 79 | if __name__ == '__main__': 80 | print 'thread %s is running...' % current_thread().name 81 | 82 | threads = [] 83 | for i in range(5): 84 | threads.append(Thread(target=calc)) 85 | threads[i].start() 86 | for i in range(5): 87 | threads[i].join() 88 | 89 | print 'thread %s ended.' % current_thread().name 90 | ``` 91 | 92 | 看下执行结果: 93 | 94 | ``` 95 | thread MainThread is running... 96 | thread Thread-64 is running... 97 | thread Thread-65 is running... 98 | thread Thread-66 is running... 99 | thread Thread-67 is running... 100 | thread Thread-68 is running... 101 | Thread-67 10000 102 | thread Thread-67 ended. 103 | Thread-65 10000 104 | thread Thread-65 ended. 105 | Thread-68 10000 106 | thread Thread-68 ended. 107 | Thread-66 10000 108 | thread Thread-66 ended. 109 | Thread-64 10000 110 | thread Thread-64 ended. 111 | thread MainThread ended. 112 | ``` 113 | 114 | 上面的做法虽然消除了函数传参的问题,但是还是有些不完美,为了获取线程的局部数据,我们需要先获取线程 ID,另外,global_dict 是个全局变量,所有线程都可以对它进行修改,还是有些危险。 115 | 116 | 那到底如何是好? 117 | 118 | 事实上,Python 提供了 ThreadLocal 对象,它真正做到了线程之间的数据隔离,而且不用查找 dict,代码如下: 119 | 120 | ```python 121 | from threading import Thread, current_thread, local 122 | 123 | global_data = local() 124 | 125 | def echo(): 126 | num = global_data.num 127 | print current_thread().name, num 128 | 129 | def calc(): 130 | print 'thread %s is running...' % current_thread().name 131 | 132 | global_data.num = 0 133 | for _ in xrange(10000): 134 | global_data.num += 1 135 | echo() 136 | 137 | print 'thread %s ended.' % current_thread().name 138 | 139 | if __name__ == '__main__': 140 | print 'thread %s is running...' % current_thread().name 141 | 142 | threads = [] 143 | for i in range(5): 144 | threads.append(Thread(target=calc)) 145 | threads[i].start() 146 | for i in range(5): 147 | threads[i].join() 148 | 149 | print 'thread %s ended.' % current_thread().name 150 | ``` 151 | 152 | 在上面的代码中,global_data 就是 ThreadLocal 对象,你可以把它当作一个全局变量,但它的每个属性,比如 `global_data.num` 都是线程的局部变量,没有访问冲突的问题。 153 | 154 | 让我们看下执行结果: 155 | 156 | ``` 157 | thread MainThread is running... 158 | thread Thread-94 is running... 159 | thread Thread-95 is running... 160 | thread Thread-96 is running... 161 | thread Thread-97 is running... 162 | thread Thread-98 is running... 163 | Thread-96 10000 164 | thread Thread-96 ended. 165 | Thread-97 10000 166 | thread Thread-97 ended. 167 | Thread-95 10000 168 | thread Thread-95 ended. 169 | Thread-98 10000 170 | thread Thread-98 ended. 171 | Thread-94 10000 172 | thread Thread-94 ended. 173 | thread MainThread ended. 174 | ``` 175 | 176 | # 小结 177 | 178 | - 使用 ThreadLocal 对象来线程绑定自己独有的数据。 179 | 180 | # 参考资料 181 | 182 | - [ThreadLocal - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386832845200f6513494f0c64bd882f25818a0281e80000) 183 | - [深入理解Python中的ThreadLocal变量(上) | Just For Fun](http://selfboot.cn/2016/08/22/threadlocal_overview/) 184 | - [Python线程同步机制 | Python见闻志](https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_thread_sync.html) 185 | 186 | 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cover](https://i.loli.net/2020/03/23/9B1oWLeu6Q3Hbrz.png) 2 | 3 | Python 之旅 4 | === 5 | 6 | ![Version](https://img.shields.io/badge/version-1.0-brightgreen.svg) 7 | [![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-brightgreen.svg)](https://raw.githubusercontent.com/ethan-funny/explore-python/master/LICENSE) 8 | 9 | ## Python 简介 10 | 11 | Python 诞生于 1989 年的圣诞期间,由 [Guido van Rossum](https://gvanrossum.github.io/) 开发而成,目前 Guido 仍然是 Python 的主要开发者,主导着 Python 的发展方向,Python 社区经常称呼他为『仁慈的独裁者』。 12 | 13 | Python 是一门面向对象,解释型的高级程序设计语言,它的语法非常简洁、优雅,而这也是 Python 的一些设计哲学。Python 自带了很完善的库,涵盖了数据库,网络,文件处理,GUI 等方方面面,通过这些库,我们可以比较快速地解决一些棘手问题,也可以将其作为基础库,开发出一些高级库。 14 | 15 | 目前 Python 在大部分领域都占有一席之地,比如 Web 开发,机器学习,科学计算等。不少大型网站都是使用 Python 作为后台开发语言的,比如 [YouTube](https://www.youtube.com/)、[Pinterest](https://www.pinterest.com/)、国内的[豆瓣](https://www.douban.com/)和[知乎](http://www.zhihu.com/)等。 16 | 17 | 另外,有不少知名的机器学习库也是使用 Python 开发的,比如,[scikit-learn](http://scikit-learn.org/stable/) 是一个强大的机器学习库,[PyTorch](https://pytorch.org/) 是一个成熟的深度学习库。 18 | 19 | 当然了,Python 也有一些缺点。Python 经常被人们吐槽的一点就是:运行速度慢,和 C/C++ 相比非常慢。但是,除了像视频高清解码等**计算密集型任务**对运行速度有较高的要求外,在大部分时候,我们可能并不需要非常快的运行速度。比如,一个程序使用 C 来实现,运行时间只需 0.01 秒,而使用 Python 来实现,需要 0.1 秒,虽然 Python 的运行时间是 C 的 10 倍,显然很慢,但对我们而言,这压根不是问题。 20 | 21 | ## 关于本书 22 | 23 | 本书是我学习和使用 Python 的总结。在学习和使用 Python 的过程中,我作了不少笔记,并对一些笔记进行了加工和完善,发表在博客上。随着笔记的增加,我就萌生了写一本书的想法,希望能比较系统地总结相关知识,巩固自己的知识体系,而不是停留在『感觉好像懂了』的状态中。 24 | 25 | 有了想法之后,接下来就要开始写了。当然,从产生想法到付诸实践还是纠结了一段时间,毕竟,作笔记和写书很不一样啊。思想斗争过后,我下定决心要把它写出来。 26 | 27 | 首先,我参考一些相关的书籍,作了一个基础的思维导图,如下: 28 | 29 | ![思维导图](https://i.loli.net/2020/03/23/uZN8aehmwl14XcG.png) 30 | ![Eng journy](PYTHONroadmap.png) 31 | 32 | 接下来,就要开始写作了,这也是最艰难的一关。 33 | 34 | 我没有按照从头到尾的顺序写,而是从最感兴趣的知识点入手,比如函数式编程、类的使用等等。就这样,一点一点地写,实在不想写了,就先搁置一下,过两天继续写。 35 | 36 | 我在写作的过程中,给自己提了一个要求:**尽量深入浅出,条理清晰**。至于是否达到了,希望读者们多多批评指正,并给我提意见和建议。 37 | 38 | 本书的每章基本上都是独立的,读者可以挑选感兴趣的章节进行阅读。目前本书有 15 个章节: 39 | 40 | - 第 1 章:介绍一些基础知识,包括 Python 中的输入和输出,字符编码。 41 | - 第 2 章:介绍常用数据类型,比如字符串、列表和字典等。 42 | - 第 3 章:介绍函数的定义和函数参数魔法。 43 | - 第 4 章:介绍 Python 中的函数式编程,包括匿名函数、闭包和装饰器等。 44 | - 第 5 章:介绍 Python 中类的使用,包括类方法、静态方法、super 和元类的使用等。 45 | - 第 6 章:介绍 Python 中的高级特性,比如生成器,上下文管理器。 46 | - 第 7 章:介绍文件和目录操作,os 的使用。 47 | - 第 8 章:介绍使用 Python 处理进程、线程和协程。 48 | - 第 9 章:异常处理。 49 | - 第 10 章:单元测试。 50 | - 第 11 章:正则表达式,re 模块的使用。 51 | - 第 12 章:HTTP 服务,requests 模块的使用。 52 | - 第 13 章:一些标准模块的使用,比如 argparse、collections 和 datetime 等。 53 | - 第 14 章:一些第三方模块的使用。 54 | - 第 15 章:结束语。 55 | 56 | 本书的编码环境: 57 | 58 | - Python 版本以 2.7 为主,同时也会指出在 Python3 中的相应变化 59 | - 操作系统使用 macOS,代码结果,尤其是内存地址等由于运行环境的不同会存在差异 60 | 61 | 本书将会持续进行修订和更新,读者如果遇到问题,请及时向我反馈,我会在第一时间加以解决。 62 | 63 | 64 | ## 声明 65 | 66 | Creative Commons License 67 | 68 | 本书由 [Ethan](https://github.com/ethan-funny) 编写,采用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.zh) 协议发布。 69 | 70 | 这意味着你可以在非商业性使用的前提下自由转载,但必须: 71 | 72 | 1. 保持署名 73 | 2. 不对本书进行修改 74 | 75 | ## 更新记录 76 | 77 | | 时间 | 说明 | 78 | | :---: | :---: | 79 | | 2017-01-03 | 发布版本 v1.0 | 80 | | 2019-02-09 | fix typo | 81 | 82 | 83 | ## 支持我 84 | 85 | 如果你觉得本书对你有所帮助,不妨请我喝杯咖啡,感谢支持! 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /Regular-Expressions/README.md: -------------------------------------------------------------------------------- 1 | # 正则表达式 2 | 3 | 正则表达式(regular expression)是可以匹配文本片段的模式。最简单的正则表达式就是普通字符串,可以匹配其自身。比如,正则表达式 'hello' 可以匹配字符串 'hello'。 4 | 5 | 要注意的是,正则表达式并不是一个程序,而是用于处理字符串的一种模式,如果你想用它来处理字符串,就必须使用支持正则表达式的工具,比如 Linux 中的 awk, sed, grep,或者编程语言 Perl, Python, Java 等。 6 | 7 | 正则表达式有多种不同的风格,下表(改编自 [huxi](http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html))列出了适用于 Python 或 Perl 等编程语言的部分**元字符**以及说明: 8 | 9 | ![](https://ofaatpail.qnssl.com/re.png) 10 | 11 | # 实例 12 | 13 | - 匹配 python.org 的正则表达式: 14 | 15 | ``` 16 | python\.org 17 | ``` 18 | 19 | 注:如果使用 `python.org` 来匹配,由于 `.` 可以匹配任意一个字符(换行符除外),因此,它也会匹配到类似 pythonmorg 的字符串,为了匹配点号,我们需要加 `\` 来转义。 20 | 21 | - 匹配 010-85692930 的正则表达式: 22 | 23 | ``` 24 | \d{3}\-\d{8} 25 | ``` 26 | 27 | 注:`\d` 表示匹配数字,`\d{3}` 表示匹配 3 个数字,`\-` 表示匹配 `-`。 28 | 29 | - 匹配由数字、26个英文字母或下划线组成的字符串的正则表达式: 30 | 31 | ``` 32 | ^\w+$ 33 | ``` 34 | 35 | 或 36 | 37 | ``` 38 | ^[0-9a-zA-Z_]+$ 39 | ``` 40 | 41 | - 匹配 13、15、18 开头的手机号的正则表达式: 42 | 43 | ``` 44 | ^(13[0-9]|15[0|1|2|3|5|6|7|8|9]|18[0-9])\d{8}$ 45 | ``` 46 | 47 | - 匹配金额,精确到 2 位小数 48 | 49 | ``` 50 | ^[0-9]+(.[0-9]{2})?$ 51 | ``` 52 | 53 | - 匹配中文的正则表达式: 54 | 55 | ```python 56 | ^[\u4e00-\u9fa5]{0,}$ 57 | ``` 58 | 59 | 注:[中文的 unicode 编码范围](http://blog.oasisfeng.com/2006/10/19/full-cjk-unicode-range/)主要在 `\u4e00-\u9fa5`。 60 | 61 | # 参考资料 62 | 63 | - [正则表达式 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F) 64 | - [Python正则表达式指南](http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html) 65 | - [知道这20个正则表达式,能让你少写1,000行代码 - 简书](http://www.jianshu.com/p/e7bb97218946) 66 | - [regexr](http://www.regexr.com/) 67 | 68 | 69 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [前言](README.md) 4 | * [基础](Basic/README.md) 5 | * [字符编码](Basic/character_encoding.md) 6 | * [输入和输出](Basic/input_output.md) 7 | * [常用数据类型](Datatypes/README.md) 8 | * [列表](Datatypes/list.md) 9 | * [元组](Datatypes/tuple.md) 10 | * [字符串](Datatypes/string.md) 11 | * [字典](Datatypes/dict.md) 12 | * [集合](Datatypes/set.md) 13 | * [函数](Function/README.md) 14 | * [定义函数](Function/func_definition.md) 15 | * [函数参数魔法](Function/func_parameter.md) 16 | * [函数式编程](Functional/README.md) 17 | * [高阶函数](Functional/high_order_func.md) 18 | * [map/reduce/filter](Functional/map_reduce_filter.md) 19 | * [匿名函数](Functional/anonymous_func.md) 20 | * [携带状态的闭包](Functional/closure.md) 21 | * [会打扮的装饰器](Functional/decorator.md) 22 | * [partial 函数](Functional/partial.md) 23 | * [类](Class/README.md) 24 | * [类和实例](Class/class_and_object.md) 25 | * [继承和多态](Class/inheritance_and_polymorphism.md) 26 | * [类方法和静态方法](Class/method.md) 27 | * [定制类和魔法方法](Class/magic_method.md) 28 | * [slots 魔法](Class/slots.md) 29 | * [使用 @property](Class/property.md) 30 | * [你不知道的 super](Class/super.md) 31 | * [陌生的 metaclass](Class/metaclass.md) 32 | * [高级特性](Advanced-Features/README.md) 33 | * [迭代器](Advanced-Features/iterator.md) 34 | * [生成器](Advanced-Features/generator.md) 35 | * [上下文管理器](Advanced-Features/context.md) 36 | * [文件和目录](File-Directory/README.md) 37 | * [读写文本文件](File-Directory/text_file_io.md) 38 | * [读写二进制文件](File-Directory/binary_file_io.md) 39 | * [os 模块](File-Directory/os.md) 40 | * [进程、线程和协程](Process-Thread-Coroutine/README.md) 41 | * [进程](Process-Thread-Coroutine/process.md) 42 | * [线程](Process-Thread-Coroutine/thread.md) 43 | * [ThreadLocal](Process-Thread-Coroutine/threadlocal.md) 44 | * [协程](Process-Thread-Coroutine/coroutine.md) 45 | * [异常处理](Exception/README.md) 46 | * [单元测试](Testing/README.md) 47 | * [正则表达式](Regular-Expressions/README.md) 48 | * [re 模块](Regular-Expressions/re.md) 49 | * [HTTP 服务](HTTP/README.md) 50 | * [HTTP 协议简介](HTTP/HTTP.md) 51 | * [Requests 库的使用](HTTP/Requests.md) 52 | * [标准模块](Standard-Modules/README.md) 53 | * [argparse](Standard-Modules/argparse.md) 54 | * [base64](Standard-Modules/base64.md) 55 | * [collections](Standard-Modules/collections.md) 56 | * [itertools](Standard-Modules/itertools.md) 57 | * [datetime](Standard-Modules/datetime.md) 58 | * [hashlib](Standard-Modules/hashlib.md) 59 | * [hmac](Standard-Modules/hmac.md) 60 | * [第三方模块](Third-Party-Modules/README.md) 61 | * [celery](Third-Party-Modules/celery.md) 62 | * [click](Third-Party-Modules/click.md) 63 | * [结束语](Conclusion/README.md) 64 | * [资源推荐](Conclusion/resource_recommendation.md) 65 | * [参考资料](Conclusion/reference_material.md) 66 | 67 | 68 | -------------------------------------------------------------------------------- /Standard-Modules/README.md: -------------------------------------------------------------------------------- 1 | # 标准模块 2 | 3 | 前面我们介绍了 os 模块和 re 模块,本章再介绍 Python 常用的一些标准模块: 4 | 5 | - [argparse](./argparse.md) 6 | - [base64](./base64.md) 7 | - [collections](./collections.md) 8 | - [datetime](./datetime.md) 9 | - [hashlib](./hashlib.md) 10 | - [hmac](./hmac.md) 11 | 12 | 其中: 13 | 14 | - argparse 是用于创建命令行的库; 15 | - base64 是用于 base64 编码和解码的库; 16 | - collections 模块提供了 5 个高性能的数据类型,如 `Counter`,`OrderedDict` 等; 17 | - datetime 是用于处理日期时间的模块; 18 | - hashlib 模块提供了常见的摘要算法,比如 MD5,SHA1 等; 19 | - hmac 模块提供了 HMAC 哈希算法; 20 | 21 | -------------------------------------------------------------------------------- /Standard-Modules/argparse.md: -------------------------------------------------------------------------------- 1 | # argparse 2 | 3 | argparse 是 Python 内置的一个用于命令项选项与参数解析的模块,通过在程序中定义好我们需要的参数,argparse 将会从 sys.argv 中解析出这些参数,并自动生成帮助和使用信息。当然,Python 也有第三方的库可用于命令行解析,而且功能也更加强大,比如 [docopt](http://docopt.org/),[Click](http://click.pocoo.org/5/)。 4 | 5 | # argparse 使用 6 | 7 | ## 简单示例 8 | 9 | 我们先来看一个简单示例。主要有三个步骤: 10 | 11 | - 创建 ArgumentParser() 对象 12 | - 调用 add_argument() 方法添加参数 13 | - 使用 parse_args() 解析添加的参数 14 | 15 | ```python 16 | # -*- coding: utf-8 -*- 17 | 18 | import argparse 19 | 20 | parser = argparse.ArgumentParser() 21 | parser.add_argument('integer', type=int, help='display an integer') 22 | args = parser.parse_args() 23 | 24 | print args.integer 25 | ``` 26 | 27 | 将上面的代码保存为文件 `argparse_usage.py`,在终端运行,结果如下: 28 | 29 | ``` 30 | $ python argparse_usage.py 31 | usage: argparse_usage.py [-h] integer 32 | argparse_usage.py: error: too few arguments 33 | 34 | $ python argparse_usage.py abcd 35 | usage: argparse_usage.py [-h] integer 36 | argparse_usage.py: error: argument integer: invalid int value: 'abcd' 37 | 38 | $ python argparse_usage.py -h 39 | usage: argparse_usage.py [-h] integer 40 | 41 | positional arguments: 42 | integer display an integer 43 | 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | 47 | $ python argparse_usage.py 10 48 | 10 49 | ``` 50 | 51 | ## 定位参数 52 | 53 | 上面的示例,其实就展示了定位参数的使用,我们再来看一个例子 - 计算一个数的平方: 54 | 55 | ```py 56 | # -*- coding: utf-8 -*- 57 | 58 | import argparse 59 | 60 | parser = argparse.ArgumentParser() 61 | parser.add_argument("square", help="display a square of a given number", type=int) 62 | args = parser.parse_args() 63 | print args.square**2 64 | ``` 65 | 66 | 将上面的代码保存为文件 `argparse_usage.py`,在终端运行,结果如下: 67 | 68 | ``` 69 | $ python argparse_usage.py 9 70 | 81 71 | ``` 72 | 73 | ## 可选参数 74 | 75 | 现在看下可选参数的用法,所谓可选参数,也就是命令行参数是可选的,废话少说,看下面例子: 76 | 77 | ``` 78 | # -*- coding: utf-8 -*- 79 | 80 | import argparse 81 | 82 | parser = argparse.ArgumentParser() 83 | 84 | parser.add_argument("--square", help="display a square of a given number", type=int) 85 | parser.add_argument("--cubic", help="display a cubic of a given number", type=int) 86 | 87 | args = parser.parse_args() 88 | 89 | if args.square: 90 | print args.square**2 91 | 92 | if args.cubic: 93 | print args.cubic**3 94 | ``` 95 | 96 | 将上面的代码保存为文件 `argparse_usage.py`,在终端运行,结果如下: 97 | 98 | ``` 99 | $ python argparse_usage.py --h 100 | usage: argparse_usage.py [-h] [--square SQUARE] [--cubic CUBIC] 101 | 102 | optional arguments: 103 | -h, --help show this help message and exit 104 | --square SQUARE display a square of a given number 105 | --cubic CUBIC display a cubic of a given number 106 | 107 | $ python argparse_usage.py --square 8 108 | 64 109 | 110 | $ python argparse_usage.py --cubic 8 111 | 512 112 | 113 | $ python argparse_usage.py 8 114 | usage: argparse_usage.py [-h] [--square SQUARE] [--cubic CUBIC] 115 | argparse_usage.py: error: unrecognized arguments: 8 116 | 117 | $ python argparse_usage.py # 没有输出 118 | ``` 119 | 120 | ## 混合使用 121 | 122 | 定位参数和选项参数可以混合使用,看下面一个例子,给一个整数序列,输出它们的和或最大值(默认): 123 | 124 | ```py 125 | import argparse 126 | 127 | parser = argparse.ArgumentParser(description='Process some integers.') 128 | parser.add_argument('integers', metavar='N', type=int, nargs='+', 129 | help='an integer for the accumulator') 130 | parser.add_argument('--sum', dest='accumulate', action='store_const', 131 | const=sum, default=max, 132 | help='sum the integers (default: find the max)') 133 | 134 | args = parser.parse_args() 135 | print args.accumulate(args.integers) 136 | ``` 137 | 138 | 结果: 139 | 140 | ```py 141 | $ python argparse_usage.py 142 | usage: argparse_usage.py [-h] [--sum] N [N ...] 143 | argparse_usage.py: error: too few arguments 144 | $ python argparse_usage.py 1 2 3 4 145 | 4 146 | $ python argparse_usage.py 1 2 3 4 --sum 147 | 10 148 | ``` 149 | 150 | ## add_argument() 方法 151 | 152 | add_argument() 方法定义如何解析命令行参数: 153 | 154 | ``` 155 | ArgumentParser.add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest]) 156 | ``` 157 | 158 | 每个参数解释如下: 159 | 160 | - name or flags - 选项字符串的名字或者列表,例如 foo 或者 -f, --foo。 161 | - action - 命令行遇到参数时的动作,默认值是 store。 162 | - store_const,表示赋值为const; 163 | - append,将遇到的值存储成列表,也就是如果参数重复则会保存多个值; 164 | - append_const,将参数规范中定义的一个值保存到一个列表; 165 | - count,存储遇到的次数;此外,也可以继承 argparse.Action 自定义参数解析; 166 | - nargs - 应该读取的命令行参数个数,可以是具体的数字,或者是?号,当不指定值时对于 Positional argument 使用 default,对于 Optional argument 使用 const;或者是 * 号,表示 0 或多个参数;或者是 + 号表示 1 或多个参数。 167 | - const - action 和 nargs 所需要的常量值。 168 | - default - 不指定参数时的默认值。 169 | - type - 命令行参数应该被转换成的类型。 170 | - choices - 参数可允许的值的一个容器。 171 | - required - 可选参数是否可以省略 (仅针对可选参数)。 172 | - help - 参数的帮助信息,当指定为 `argparse.SUPPRESS` 时表示不显示该参数的帮助信息. 173 | - metavar - 在 usage 说明中的参数名称,对于必选参数默认就是参数名称,对于可选参数默认是全大写的参数名称. 174 | - dest - 解析后的参数名称,默认情况下,对于可选参数选取最长的名称,中划线转换为下划线. 175 | 176 | # 参考资料 177 | 178 | - [Argparse Tutorial — Python 2.7.12 documentation](https://docs.python.org/2/howto/argparse.html) 179 | - [Argparse – Command line option and argument parsing](https://pymotw.com/2/argparse/) 180 | - [Argparse — Parser for command-line options, arguments and sub-commands](http://python.usyiyi.cn/python_278/library/argparse.html) 181 | - [Python 中的命令行解析工具介绍](http://lingxiankong.github.io/blog/2014/01/14/command-line-parser/) 182 | 183 | 184 | -------------------------------------------------------------------------------- /Standard-Modules/base64.md: -------------------------------------------------------------------------------- 1 | # Base64 2 | 3 | **Base64,简单地讲,就是用 64 个字符来表示二进制数据的方法**。这 64 个字符包含小写字母 a-z、大写字母 A-Z、数字 0-9 以及符号"+"、"/",其实还有一个 "=" 作为后缀用途,所以实际上有 65 个字符。 4 | 5 | 本文主要介绍如何使用 Python 进行 Base64 编码和解码,关于 Base64 编码转换的规则可以参考 [Base64 笔记](http://www.ruanyifeng.com/blog/2008/06/base64.html)。 6 | 7 | Python 内置了一个用于 Base64 编解码的库:`base64`: 8 | 9 | - 编码使用 `base64.b64encode()` 10 | - 解码使用 `base64.b64decode()` 11 | 12 | 下面,我们介绍文本和图片的 Base64 编解码。 13 | 14 | # 对文本进行 Base64 编码和解码 15 | 16 | ```python 17 | >>> import base64 18 | >>> str = 'hello world' 19 | >>> 20 | >>> base64_str = base64.b64encode(str) # 编码 21 | >>> print base64_str 22 | aGVsbG8gd29ybGQ= 23 | >>> 24 | >>> ori_str = base64.b64decode(base64_str) # 解码 25 | >>> print ori_str 26 | hello world 27 | ``` 28 | 29 | # 对图片进行 Base64 编码和解码 30 | 31 | ```python 32 | def convert_image(): 33 | # 原始图片 ==> base64 编码 34 | with open('/path/to/alpha.png', 'r') as fin: 35 | image_data = fin.read() 36 | base64_data = base64.b64encode(image_data) 37 | 38 | fout = open('/path/to/base64_content.txt', 'w') 39 | fout.write(base64_data) 40 | fout.close() 41 | 42 | # base64 编码 ==> 原始图片 43 | with open('/path/to/base64_content.txt', 'r') as fin: 44 | base64_data = fin.read() 45 | ori_image_data = base64.b64decode(base64_data) 46 | 47 | fout = open('/path/to/beta.png', 'wb'): 48 | fout.write(ori_image_data) 49 | fout.close() 50 | ``` 51 | 52 | # 小结 53 | 54 | - Base64 可以将任意二进制数据编码到文本字符串,常用于在 URL、Cookie 和网页中传输少量二进制数据。 55 | 56 | # 参考资料 57 | 58 | - [Base64 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/Base64) 59 | - [Base64笔记 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2008/06/base64.html) 60 | 61 | 62 | -------------------------------------------------------------------------------- /Standard-Modules/collections.md: -------------------------------------------------------------------------------- 1 | # collections 2 | 3 | 我们知道,Python 的数据类型有 list, tuple, dict, str 等,**collections 模块**提供了额外 5 个高性能的数据类型: 4 | 5 | - `Counter`: 计数器 6 | - `OrderedDict`: 有序字典 7 | - `defaultdict`: 带有默认值的字典 8 | - `namedtuple`: 生成可以通过属性访问元素内容的 tuple 子类 9 | - `deque`: 双端队列,能够在队列两端添加或删除元素 10 | 11 | # Counter 12 | 13 | `Counter` 是一个简单的计数器,可用于统计字符串、列表等的元素个数。 14 | 15 | 看看例子: 16 | 17 | ```python 18 | >>> from collections import Counter 19 | >>> 20 | >>> s = 'aaaabbbccd' 21 | >>> c = Counter(s) # 创建了一个 Counter 对象 22 | >>> c 23 | Counter({'a': 4, 'b': 3, 'c': 2, 'd': 1}) 24 | >>> isinstance(c, dict) # c 其实也是一个字典对象 25 | True 26 | >>> c.get('a') 27 | 4 28 | >>> c.most_common(2) # 获取出现次数最多的前两个元素 29 | [('a', 4), ('b', 3)] 30 | ``` 31 | 32 | 在上面,我们使用 `Counter()` 创建了一个 `Counter` 对象 `c`,`Counter` 其实是 dict 的一个子类,我们可以使用 `get` 方法来获取某个元素的个数。`Counter` 对象有一个 `most_common` 方法,允许我们获取出现次数最多的前几个元素。 33 | 34 | 另外,两个 `Counter` 对象还可以做运算: 35 | 36 | ```python 37 | >>> from collections import Counter 38 | >>> 39 | >>> s1 = 'aaaabbbccd' 40 | >>> c1 = Counter(s1) 41 | >>> c1 42 | Counter({'a': 4, 'b': 3, 'c': 2, 'd': 1}) 43 | >>> 44 | >>> s2 = 'aaabbef' 45 | >>> c2 = Counter(s2) 46 | >>> c2 47 | Counter({'a': 3, 'b': 2, 'e': 1, 'f': 1}) 48 | >>> 49 | >>> c1 + c2 # 两个计数结果相加 50 | Counter({'a': 7, 'b': 5, 'c': 2, 'e': 1, 'd': 1, 'f': 1}) 51 | >>> c1 - c2 # c2 相对于 c1 的差集 52 | Counter({'c': 2, 'a': 1, 'b': 1, 'd': 1}) 53 | >>> c1 & c2 # c1 和 c2 的交集 54 | Counter({'a': 3, 'b': 2}) 55 | >>> c1 | c2 # c1 和 c2 的并集 56 | Counter({'a': 4, 'b': 3, 'c': 2, 'e': 1, 'd': 1, 'f': 1}) 57 | ``` 58 | 59 | # OrderedDict 60 | 61 | Python 中的 dict 是无序的: 62 | 63 | ```python 64 | >>> dict([('a', 10), ('b', 20), ('c', 15)]) 65 | {'a': 10, 'c': 15, 'b': 20} 66 | ``` 67 | 68 | 有时,我们希望保持 key 的顺序,这时可以用 OrderedDict: 69 | 70 | ```python 71 | >>> from collections import OrderedDict 72 | >>> OrderedDict([('a', 10), ('b', 20), ('c', 15)]) 73 | OrderedDict([('a', 10), ('b', 20), ('c', 15)]) 74 | ``` 75 | 76 | # defaultdict 77 | 78 | 在 Python 中使用 dict 时,如果访问了不存在的 key,会抛出 KeyError 异常,因此,在访问之前,我们经常需要对 key 作判断,比如: 79 | 80 | ```python 81 | >>> d = dict() 82 | >>> s = 'aaabbc' 83 | >>> for char in s: 84 | ... if char in d: 85 | ... d[char] += 1 86 | ... else: 87 | ... d[char] = 1 88 | ... 89 | >>> d 90 | {'a': 3, 'c': 1, 'b': 2} 91 | ``` 92 | 93 | 使用 defaultdict,我们可以给字典中的 key 提供一个默认值。访问 defaultdict 中的 key,如果 key 存在,就返回 key 对应的 value,如果 key 不存在,就返回默认值。 94 | 95 | ```python 96 | >>> from collections import defaultdict 97 | >>> d = defaultdict(int) # 默认的 value 值是 0 98 | >>> s = 'aaabbc' 99 | >>> for char in s: 100 | ... d[char] += 1 101 | ... 102 | >>> d 103 | defaultdict(, {'a': 3, 'c': 1, 'b': 2}) 104 | >>> d.get('a') 105 | 3 106 | >>> d['z'] 107 | 0 108 | ``` 109 | 110 | 使用 defaultdict 时,我们可以传入一个工厂方法来指定默认值,比如传入 int,表示默认值是 0,传入 list,表示默认是 `[]`: 111 | 112 | ```python 113 | >>> from collections import defaultdict 114 | >>> 115 | >>> d1 = defaultdict(int) 116 | >>> d1['a'] 117 | 0 118 | >>> d2 = defaultdict(list) 119 | >>> d2['b'] 120 | [] 121 | >>> d3 = defaultdict(str) 122 | >>> d3['a'] 123 | '' 124 | ``` 125 | 126 | 我们还可以自定义默认值,通过 `lambda` 函数来实现: 127 | 128 | ```python 129 | >>> from collections import defaultdict 130 | >>> 131 | >>> d = defaultdict(lambda: 10) 132 | >>> d['a'] 133 | 10 134 | ``` 135 | 136 | # namedtuple 137 | 138 | 我们经常用 tuple (元组) 来表示一个不可变对象,比如用一个 `(姓名, 学号, 年龄)` 的元组来表示一个学生: 139 | 140 | ```python 141 | >>> stu = ('ethan', '001', 20) 142 | >>> stu[0] 143 | 'ethan' 144 | ``` 145 | 146 | 这里使用 tuple 没什么问题,但可读性比较差,我们必须清楚索引代表的含义,比如索引 0 表示姓名,索引 1 表示学号。如果用类来定义,就可以通过设置属性 name, id, age 来表示,但就有些小题大作了。 147 | 148 | 我们可以通过 namedtuple 为元组的每个索引设置名称,然后通过「属性名」来访问: 149 | 150 | ```python 151 | >>> from collections import namedtuple 152 | >>> Student = namedtuple('Student', ['name', 'id', 'age']) # 定义了一个 Student 元组 153 | >>> 154 | >>> stu = Student('ethan', '001', 20) 155 | >>> stu.name 156 | 'ethan' 157 | >>> stu.id 158 | '001' 159 | ``` 160 | 161 | # deque 162 | 163 | deque 是双端队列,允许我们在队列两端添加或删除元素。 164 | 165 | ```python 166 | >>> from collections import deque 167 | 168 | >>> q = deque(['a', 'b', 'c', 'd']) 169 | >>> q.append('e') # 添加到尾部 170 | >>> q 171 | deque(['a', 'b', 'c', 'd', 'e']) 172 | >>> q.appendleft('o') # 添加到头部 173 | >>> q 174 | deque(['o', 'a', 'b', 'c', 'd', 'e']) 175 | >>> q.pop() # 从尾部弹出元素 176 | 'e' 177 | >>> q 178 | deque(['o', 'a', 'b', 'c', 'd']) 179 | >>> q.popleft() # 从头部弹出元素 180 | 'o' 181 | >>> q 182 | deque(['a', 'b', 'c', 'd']) 183 | >>> q.extend('ef') # 在尾部 extend 元素 184 | >>> q 185 | deque(['a', 'b', 'c', 'd', 'e', 'f']) 186 | >>> q.extendleft('uv') # 在头部 extend 元素,注意顺序 187 | >>> q 188 | deque(['v', 'u', 'a', 'b', 'c', 'd', 'e', 'f']) 189 | >>> 190 | >>> q.rotate(2) # 将尾部的两个元素移动到头部 191 | >>> q 192 | deque(['e', 'f', 'v', 'u', 'a', 'b', 'c', 'd']) 193 | >>> q.rotate(-2) # 将头部的两个元素移动到尾部 194 | >>> q 195 | deque(['v', 'u', 'a', 'b', 'c', 'd', 'e', 'f']) 196 | ``` 197 | 198 | 其中,`rotate` 方法用于旋转,如果旋转参数 n 大于 0,表示将队列右端的 n 个元素移动到左端,否则相反。 199 | 200 | 201 | # 参考资料 202 | 203 | - [collections — High-performance container datatypes — Python 2.7.13 documentation](https://docs.python.org/2/library/collections.html#module-collections) 204 | 205 | 206 | -------------------------------------------------------------------------------- /Standard-Modules/datetime.md: -------------------------------------------------------------------------------- 1 | # datetime 2 | 3 | Python 提供了两个标准库用于处理跟时间相关的问题,一个是 `time`,另一个是 `datetime`,`datetime` 对 `time` 进行了封装,提供了更多实用的函数。本文介绍 `datetime` 库的简单使用。 4 | 5 | # 当前时间 6 | 7 | 获取当前时间可以使用 `now()` 或 `utcnow()` 方法,其中,`now()` 用于获取当地时间,而 `utcnow()` 用于获取 UTC 时间。 8 | 9 | ```python 10 | >>> from datetime import datetime 11 | 12 | >>> datetime.now() # 返回一个 datetime 对象,这里是当地时间 13 | datetime.datetime(2016, 12, 10, 11, 32, 43, 806970) 14 | 15 | >>> datetime.utcnow() # 返回一个 datetime 对象,这里是 UTC 时间 16 | datetime.datetime(2016, 12, 10, 3, 32, 49, 999423) 17 | 18 | >>> datetime.now().year, datetime.now().month, datetime.now().day # 年月日 19 | (2016, 12, 10) 20 | 21 | >>> datetime.now().hour, datetime.now().minute, datetime.now().second # 时分秒 22 | (11, 35, 37) 23 | ``` 24 | 25 | # 时间格式化 26 | 27 | 有时,我们需要对时间做格式化处理,可以使用 `strftime()` 或 `strptime()` 方法,其中,`strftime` 用于对 `datetime` 对象进行格式化,`strptime` 用于对字符串对象进行格式化。 28 | 29 | ```python 30 | >>> from datetime import datetime 31 | 32 | # 获取当前当地时间 33 | >>> now = datetime.now() 34 | >>> now 35 | datetime.datetime(2016, 12, 10, 11, 46, 24, 432168) 36 | 37 | # 对 datetime 对象进行格式化,转为字符串格式 38 | >>> now_str = now.strftime('%Y-%m-%d %H:%M:%S.%f') 39 | >>> now_str 40 | '2016-12-10 11:46:24.432168' 41 | 42 | # 对字符串对象进行格式化,转为 datetime 对象 43 | >>> datetime.strptime(now_str, '%Y-%m-%d %H:%M:%S.%f') 44 | datetime.datetime(2016, 12, 10, 11, 46, 24, 432168) 45 | ``` 46 | 47 | # 时间戳 48 | 49 | [Unix 时间戳](http://funhacks.net/2015/04/29/Unix-timestamp/)根据精度的不同,有 10 位(秒级),13 位(毫秒级),16 位(微妙级)和 19 位(纳秒级)。 50 | 51 | 要注意的是,由于每个时区都有自己的本地时间(北京在东八区),因此也产生了世界标准时间(UTC, Universal Time Coordinated)。所以,在将一个时间戳转换为普通时间(比如 2016-01-01 12:00:00)时,要注意是要本地时区的时间还是世界时间等。 52 | 53 | - 获取当前当地时间戳 54 | 55 | ```python 56 | >>> import time 57 | >>> from datetime import datetime 58 | 59 | # 获取当前当地时间,返回一个 datetime 对象 60 | >>> now = datetime.now() 61 | >>> now 62 | datetime.datetime(2016, 12, 9, 11, 56, 47, 632778) 63 | 64 | # 13 位的毫秒时间戳 65 | >>> long(time.mktime(now.timetuple()) * 1000.0 + now.microsecond / 1000.0) 66 | 1481255807632L 67 | 68 | # 10 位的时间戳 69 | >>> int(time.mktime(now.timetuple())) 70 | 1481255807 71 | ``` 72 | 73 | - 获取当前 UTC 时间戳 74 | 75 | ```python 76 | >>> import calendar 77 | >>> from datetime import datetime 78 | 79 | # 获取当前的 UTC 时间,返回 datetime 对象 80 | >>> utc_now = datetime.utcnow() 81 | >>> utc_now 82 | datetime.datetime(2016, 12, 9, 4, 0, 53, 356641) 83 | 84 | # 13 位的时间戳 85 | >>> long(calendar.timegm(utc_now.timetuple()) * 1000.0 + utc_now.microsecond / 1000.0) 86 | 1481256053356L 87 | 88 | # 10 位的时间戳 89 | >>> calendar.timegm(utc_now.timetuple()) 90 | 1481256053 91 | ``` 92 | 93 | - 将时间戳转为字符串形式 94 | 95 | ```python 96 | >>> from datetime import datetime 97 | 98 | # 13 位的毫秒时间戳 99 | >>> timestamp = 1456402864242 100 | 101 | # 根据时间戳构建当地时间 102 | >>> datetime.fromtimestamp(timestamp / 1000.0).strftime('%Y-%m-%d %H:%M:%S.%f') 103 | '2016-02-25 20:21:04.242000' 104 | 105 | # 根据时间戳构建 UTC 时间 106 | >>> datetime.utcfromtimestamp(timestamp / 1000.0).strftime('%Y-%m-%d %H:%M:%S.%f') 107 | '2016-02-25 12:21:04.242000' 108 | 109 | # 10 位的时间戳 110 | >>> timestamp = 1456402864 111 | 112 | # 根据时间戳构建当地时间 113 | >>> datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') 114 | '2016-02-25 20:21:04' 115 | 116 | # 根据时间戳构建 UTC 时间 117 | >>> datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') 118 | '2016-02-25 12:21:04' 119 | ``` 120 | 121 | - 将时间戳转为 datetime 形式 122 | 123 | ```python 124 | >>> from datetime import datetime 125 | 126 | # 13 位的毫秒时间戳 127 | >>> timestamp = 1456402864242 128 | 129 | # 根据时间戳构建当地时间 130 | >>> datetime.fromtimestamp(timestamp / 1000.0) 131 | datetime.datetime(2016, 2, 25, 20, 21, 4, 242000) 132 | 133 | # 根据时间戳构建 UTC 时间 134 | >>> datetime.utcfromtimestamp(timestamp / 1000.0) 135 | datetime.datetime(2016, 2, 25, 12, 21, 4, 242000) 136 | 137 | # 10 位的时间戳 138 | >>> timestamp = 1456402864 139 | 140 | # 根据时间戳构建当地时间 141 | >>> datetime.fromtimestamp(timestamp) 142 | datetime.datetime(2016, 2, 25, 20, 21, 4) 143 | 144 | # 根据时间戳构建 UTC 时间 145 | >>> datetime.utcfromtimestamp(timestamp) 146 | datetime.datetime(2016, 2, 25, 12, 21, 4) 147 | ``` 148 | 149 | # 参考资料 150 | 151 | - [python-datetime-time-conversions](http://www.saltycrane.com/blog/2008/11/python-datetime-time-conversions/) 152 | 153 | 154 | -------------------------------------------------------------------------------- /Standard-Modules/hashlib.md: -------------------------------------------------------------------------------- 1 | # hashlib 2 | 3 | Python 内置的 hashlib 模块提供了常见的**摘要算法**(或称哈希算法,散列算法),如 MD5,SHA1, SHA256 等。**摘要算法的基本原理是:将数据(如一段文字)运算变为另一固定长度值**。 4 | 5 | MD5 (Message-Digest Algorithm 5, 消息摘要算法),是一种被广泛使用的密码散列函数,可以产生出一个 128 位(16 字节)的散列值(hash value),用于确保信息传输完整一致。 6 | 7 | SHA1 (Secure Hash Algorithm, 安全哈希算法) 是 SHA 家族的其中一个算法,它经常被用作数字签名。 8 | 9 | # MD5 10 | 11 | hashlib 模块提供了 `md5` 函数,我们可以很方便地使用它: 12 | 13 | ```python 14 | >>> import hashlib 15 | >>> 16 | >>> m = hashlib.md5('md5 test in Python!') 17 | >>> m.digest() 18 | '\xad\xc0\x99\x01\x12\xc7&\xb5~\xb0\xaf \x974\x11\xab' 19 | >>> m.hexdigest() # 使用一个 32 位的 16 进制字符串表示 20 | 'adc0990112c726b57eb0af20973411ab' 21 | ``` 22 | 23 | 上面,我们是直接把数据传入 `md5()` 函数,我们也可以通过一次或多次使用 `update` 来实现: 24 | 25 | ```python 26 | >>> import hashlib 27 | >>> m = hashlib.md5() 28 | >>> m.update('md5 test ') 29 | >>> m.update('in Python!') 30 | >>> m.hexdigest() 31 | 'adc0990112c726b57eb0af20973411ab' 32 | ``` 33 | 34 | # SHA1 35 | 36 | SHA1 的使用和 MD5 的使用类似: 37 | 38 | ```python 39 | >>> import hashlib 40 | >>> 41 | >>> sha1 = hashlib.sha1() 42 | >>> sha1.update('md5 test ') 43 | >>> sha1.update('in Python!') 44 | >>> sha1.hexdigest() 45 | '698a8b18d5f99a140520475c342af455183c58a3' 46 | ``` 47 | 48 | # 小结 49 | 50 | - MD5 以前经常用来做用户密码的存储。2004年,它被证明无法防止碰撞,因此无法适用于安全性认证,但仍广泛应用于普通数据的错误检查领域。如果你需要储存用户密码,你应该去了解一下诸如 [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) 或 [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) 之类的算法。而 SHA1 则经常用作数字签名。 51 | 52 | # 参考资料 53 | 54 | - [MD5 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/MD5) 55 | - [SHA家族 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/SHA%E5%AE%B6%E6%97%8F) 56 | 57 | 58 | -------------------------------------------------------------------------------- /Standard-Modules/hmac.md: -------------------------------------------------------------------------------- 1 | # hmac 2 | 3 | HMAC 是用于消息认证的加密哈希算法,全称是 keyed-Hash Message Authentication Code。**HMAC 利用哈希算法,以一个密钥和一个消息作为输入,生成一个加密串作为输出**。HMAC 可以有效防止类似 MD5 的彩虹表等攻击,比如将常见密码的 MD5 值存入数据库,可能被反向破解。 4 | 5 | Python 的 `hmac` 模块提供了 HMAC 算法,它的使用形式是: 6 | 7 | ```python 8 | hmac.new(key[, msg[, digestmod]]) 9 | ``` 10 | 11 | 其中,key 是一个密钥;msg 是消息,可选,如果给出 msg,则调用方法 `update(msg)`;digestmod 是 HMAC 对象使用的摘要构造函数或模块,默认为 `hashlib.md5` 构造函数。 12 | 13 | HMAC 对象常用的方法有: 14 | 15 | - HMAC.update(msg) 16 | 17 | 用字符串 msg 更新 HMAC 对象,重复的调用等同于一次调用所有参数的组合,即: 18 | 19 | ``` 20 | m.update(a); 21 | m.update(b); 22 | ``` 23 | 24 | 相当于 25 | 26 | ``` 27 | m.update(a+b) 28 | ``` 29 | 30 | - HMAC.digest() 31 | 32 | 返回目前传递给 `update()` 方法的字符串的摘要。此字符串长度将与给构造函数的摘要的 digest_size 相同。它可能包含非 ASCII 字符,包括 NULL 字节。 33 | 34 | - HMAC.hexdigest() 35 | 36 | 类似 digest(),但是返回的摘要的字符串的长度翻倍,且只包含十六进制数字。 37 | 38 | 现在,让我们看一个简单的例子: 39 | 40 | ```python 41 | >>> from datetime import datetime 42 | >>> import hashlib 43 | >>> import hmac 44 | 45 | >>> key = 'you-never-know' 46 | >>> msg = datetime.utcnow().strftime('%Y-%m-%d') 47 | 48 | >>> m = hmac.new(key, msg, hashlib.sha1) 49 | >>> signature = m.hexdigest() 50 | >>> signature 51 | 'fdb2087a66a2f00afbc1884738467ba089782779' 52 | ``` 53 | 54 | # 参考资料 55 | 56 | - [hmac — 用于消息认证的加密哈希算法](http://python.usyiyi.cn/python_278/library/hmac.html) 57 | 58 | 59 | -------------------------------------------------------------------------------- /Testing/README.md: -------------------------------------------------------------------------------- 1 | # 单元测试 2 | 3 | 软件系统的开发是一个很复杂的过程,随着系统复杂性的提高,代码中隐藏的 bug 也可能变得越来越多。为了保证软件的质量,测试是一个必不可少的部分,甚至还有**测试驱动开发(Test-driven development, TDD)**的理念,也就是先测试再编码。 4 | 5 | 在计算机编程中,**单元测试(Unit Testing)**又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作,所谓的单元是指一个函数,一个模块,一个类等。 6 | 7 | 在 Python 中,我们可以使用 `unittest` 模块编写单元测试。 8 | 9 | 下面以[官方文档](https://docs.python.org/2/library/unittest.html)的例子进行介绍,该例子对字符串的一些方法进行测试: 10 | 11 | ```python 12 | # -*- coding: utf-8 -*- 13 | 14 | import unittest 15 | 16 | class TestStringMethods(unittest.TestCase): 17 | 18 | def test_upper(self): 19 | self.assertEqual('foo'.upper(), 'FOO') # 判断两个值是否相等 20 | 21 | def test_isupper(self): 22 | self.assertTrue('FOO'.isupper()) # 判断值是否为 True 23 | self.assertFalse('Foo'.isupper()) # 判断值是否为 False 24 | 25 | def test_split(self): 26 | s = 'hello world' 27 | self.assertEqual(s.split(), ['hello', 'world']) 28 | # check that s.split fails when the separator is not a string 29 | with self.assertRaises(TypeError): # 检测异常 30 | s.split(2) 31 | ``` 32 | 33 | 在上面,我们定义了一个 TestStringMethods 类,它从 `unittest.TestCase` 继承。注意到,我们的方法名都是以 `test` 开头,表明该方法是测试方法,不以 `test` 开头的方法测试的时候不会被执行。 34 | 35 | 在方法里面,我们使用了`断言(assert)`判断程序运行的结果是否和预期相符。其中: 36 | 37 | - `assertEqual` 用于判断两个值是否相等; 38 | - `assertTrue/assertFalse` 用于判断表达式的值是 True 还是 False; 39 | - `assertRaises` 用于检测异常; 40 | 41 | 断言方法主要有三种类型: 42 | 43 | - 检测两个值的大小关系:相等,大于,小于等 44 | - 检查逻辑表达式的值:True/Flase 45 | - 检查异常 46 | 47 | 下面列举了部分常用的断言方法: 48 | 49 | | Method | Checks that | 50 | | :--- | :--- | 51 | | assertEqual(a, b) | a == b | 52 | | assertNotEqual(a, b) | a != b | 53 | | assertGreater(a, b) | a > b | 54 | | assertGreaterEqual(a, b) | a >= b | 55 | | assertLess(a, b) | a < b | 56 | | assertLessEqual(a, b) | a <= b | 57 | | assertTrue(x) | bool(x) is True | 58 | | assertFalse(x) | bool(x) is False | 59 | | assertIs(a, b) | a is b | 60 | | assertIsNot(a, b) | a is not b | 61 | | assertIsNone(x) | x is None | 62 | | assertIsNotNone(x) | x is not None | 63 | | assertIn(a, b) | a in b | 64 | | assertNotIn(a, b) | a not in b | 65 | | assertIsInstance(a, b) | isinstance(a, b) | 66 | | assertNotIsInstance(a, b) | not isinstance(a, b) | 67 | 68 | 现在,让我们来运行上面的单元测试,将上面的代码保存为文件 `mytest.py`,通过 `-m unittest` 参数运行单元测试: 69 | 70 | ``` 71 | $ python -m unittest mytest 72 | test_isupper (mytest.TestStringMethods) ... ok 73 | test_split (mytest.TestStringMethods) ... ok 74 | test_upper (mytest.TestStringMethods) ... ok 75 | ``` 76 | 77 | 执行结果: 78 | 79 | ```python 80 | ... 81 | ---------------------------------------------------------------------- 82 | Ran 3 tests in 0.000s 83 | 84 | OK 85 | ``` 86 | 87 | 上面的结果表明测试通过,我们也可以加 `-v` 参数得到更加详细的测试结果: 88 | 89 | ```python 90 | $ python -m unittest -v mytest 91 | test_isupper (mytest.TestStringMethods) ... ok 92 | test_split (mytest.TestStringMethods) ... ok 93 | test_upper (mytest.TestStringMethods) ... ok 94 | 95 | ---------------------------------------------------------------------- 96 | Ran 3 tests in 0.000s 97 | 98 | OK 99 | ``` 100 | 101 | 上面这种运行单元测试的方法是我们推荐的做法,当然,你也可以在代码的最后添加两行: 102 | 103 | ```python 104 | if __name__ == '__main__': 105 | unittest.main() 106 | ``` 107 | 108 | 然后再直接运行: 109 | 110 | ```python 111 | $ python mytest.py 112 | ``` 113 | 114 | # setUp 和 tearDown 115 | 116 | 在某些情况下,我们需要在每个测试方法执行前和执行后做一些相同的操作,比如我们想在每个测试方法执行前连接数据库,执行后断开数据库连接,为了避免在每个测试方法中编写同样的代码,我们可以使用 setUp 和 tearDown 方法,比如: 117 | 118 | ```python 119 | # -*- coding: utf-8 -*- 120 | 121 | import unittest 122 | 123 | class TestStringMethods(unittest.TestCase): 124 | 125 | def setUp(self): # 在每个测试方法执行前被调用 126 | print 'setUp, Hello' 127 | 128 | def tearDown(self): # 在每个测试方法执行后被调用 129 | print 'tearDown, Bye!' 130 | 131 | def test_upper(self): 132 | self.assertEqual('foo'.upper(), 'FOO') # 判断两个值是否相等 133 | 134 | def test_isupper(self): 135 | self.assertTrue('FOO'.isupper()) # 判断值是否为 True 136 | self.assertFalse('Foo'.isupper()) # 判断值是否为 False 137 | 138 | def test_split(self): 139 | s = 'hello world' 140 | self.assertEqual(s.split(), ['hello', 'world']) 141 | # check that s.split fails when the separator is not a string 142 | with self.assertRaises(TypeError): # 检测异常 143 | s.split(2) 144 | ``` 145 | 146 | 看看执行结果: 147 | 148 | ``` 149 | $ python -m unittest -v mytest 150 | test_isupper (mytest.TestStringMethods) ... setUp, Hello 151 | tearDown, Bye! 152 | ok 153 | test_split (mytest.TestStringMethods) ... setUp, Hello 154 | tearDown, Bye! 155 | ok 156 | test_upper (mytest.TestStringMethods) ... setUp, Hello 157 | tearDown, Bye! 158 | ok 159 | 160 | ---------------------------------------------------------------------- 161 | Ran 3 tests in 0.000s 162 | 163 | OK 164 | ``` 165 | 166 | # 小结 167 | 168 | - 通过从 `unittest.TestCase` 继承来编写测试类。 169 | - 使用断言方法判断程序运行的结果是否和预期相符。 170 | - setUp 在每个测试方法执行前被调用,tearDown 在每个测试方法执行后被调用。 171 | 172 | # 参考资料 173 | 174 | - [unittest — Unit testing framework — Python 2.7.12 documentation](https://docs.python.org/2/library/unittest.html) 175 | - [python单元测试unittest | Lucia Garden](http://luciastar.com/2016/05/16/python%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95unittest/) 176 | - [单元测试 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00140137128705556022982cfd844b38d050add8565dcb9000) 177 | - [单元测试 - 维基百科,自由的百科全书](https://zh.wikipedia.org/wiki/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95) 178 | - [“单元测试要做多细?” | 酷 壳 - CoolShell.cn](http://coolshell.cn/articles/8209.html) 179 | 180 | 181 | -------------------------------------------------------------------------------- /Third-Party-Modules/README.md: -------------------------------------------------------------------------------- 1 | # 第三方模块 2 | 3 | 在前面,我们介绍了一个优秀的第三方库 -- requests,本章再介绍两个第三方库: 4 | 5 | - [celery](./celery.md) 6 | - [click](./click.md) 7 | 8 | 其中: 9 | 10 | - celery 是一个强大的分布式任务队列,通常用于实现异步任务; 11 | - click 是快速创建命令行的神器; 12 | 13 | 14 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Ethan", 3 | "description": "Notes for Learning Python", 4 | "extension": null, 5 | "generator": "site", 6 | "links": { 7 | "sharing": { 8 | "all": null, 9 | "facebook": null, 10 | "google": null, 11 | "twitter": null, 12 | "weibo": null 13 | }, 14 | "sidebar": { 15 | "书籍源码": "https://github.com/ethan-funny/explore-python/" 16 | } 17 | }, 18 | "output": null, 19 | "pdf": { 20 | "toc": true, 21 | "fontSize": 12, 22 | "footerTemplate": null, 23 | "headerTemplate": null, 24 | "margin": { 25 | "bottom": 36, 26 | "left": 62, 27 | "right": 62, 28 | "top": 36 29 | }, 30 | "pageNumbers": true, 31 | "paperSize": "a4" 32 | }, 33 | "plugins": ["splitter"], 34 | "pluginsConfig": { 35 | "theme-default": { 36 | "showLevel": false 37 | } 38 | }, 39 | "title": "Python 之旅", 40 | "variables": {} 41 | } 42 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Python 之旅", 3 | "introduction":"Explore Python,Python 学习之旅。", 4 | "path": { 5 | "toc": "SUMMARY.md" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethan-funny/explore-python/dbb36fb53334d3d5b1c0f28878be1b6202011331/cover.png -------------------------------------------------------------------------------- /cover/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethan-funny/explore-python/dbb36fb53334d3d5b1c0f28878be1b6202011331/cover/background.jpg -------------------------------------------------------------------------------- /cover/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethan-funny/explore-python/dbb36fb53334d3d5b1c0f28878be1b6202011331/cover/cover.png -------------------------------------------------------------------------------- /cover/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ethan-funny/explore-python/dbb36fb53334d3d5b1c0f28878be1b6202011331/cover/logo.png --------------------------------------------------------------------------------