├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── Readme.rst ├── docs ├── .gitignore ├── Descriptor-HOW-TO-Guide.rst ├── GitHub-Repos-For-Pythoners.rst ├── Makefile ├── awesome-python-libraries.rst ├── conf.py ├── improving-your-python-productivity.rst ├── index.rst ├── make.bat ├── python-hidden-features.rst ├── python-idioms.rst ├── python-magic-methods-guide.rst ├── python-pandas.rst ├── python-questions-on-stackoverflow.rst ├── python-realtime.rst ├── python-setup-dot-py-vs-requirements-dot-txt.rst ├── python-the-dictionary-playbook.rst ├── single-line-command-with-python.rst └── the-python-yield-keyword-explained.rst ├── requirements.pip └── todo.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.pyc 4 | docs/_build/ 5 | venv/ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/_themes/rux"] 2 | path = docs/_themes/rux 3 | url = git://github.com/hit9/sphinx-theme-rux.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, Kiven 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | doc: 2 | make -C docs html 3 | 4 | -------------------------------------------------------------------------------- /Readme.rst: -------------------------------------------------------------------------------- 1 | 关于 2 | ---- 3 | 4 | 利用readthedocs的Python技术文章的翻译和收集。 5 | 6 | 订阅 7 | ---- 8 | 9 | RSS: https://pyzh.readthedocs.org/en/latest/rss.xml 10 | 11 | Online: https://pyzh.readthedocs.org/en/latest/ 12 | 13 | 约定 14 | ---- 15 | 16 | - 文件名必须是英文。一个文章的多个文件如下命名:: 17 | 18 | xxxx-Part1.rst, xxxx-Part2.rst .. 19 | 20 | - 文第一行注明日期:: 21 | 22 | :Date: 2013-04-15 22:00:00 23 | 24 | - 翻译的文章,需要注明原文链接 25 | 26 | 请Fork一起编写! 27 | ---------------- 28 | 29 | 1. 初始化环境:: 30 | 31 | git clone https://github.com/hit9/PyZh 32 | cd PyZh 33 | git submodule init & git submodule update 34 | virtualenv venv 35 | source /bin/activate 36 | pip install -r requirements.pip 37 | 38 | 2. 编写文章:: 39 | 40 | vim docs/xxxxx.rst 41 | 42 | 3. 编译预览:: 43 | 44 | make doc 45 | cd _build/html 46 | python -m SimpleHTTPServer 47 | 48 | 打开 ``http://localhost:8000`` 预览 49 | 50 | 4. 更新Readthedocs文档: 51 | 52 | push上去到Github, 然后到https://readthedocs.org/projects/pyzh build下即可 53 | 54 | RST 55 | --- 56 | 57 | RST文档的语法: https://github.com/ralsina/rst-cheatsheet/blob/master/rst-cheatsheet.rst 58 | 59 | Example可以看项目中其它文章的源码 60 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | -------------------------------------------------------------------------------- /docs/Descriptor-HOW-TO-Guide.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-02-01 14:01:01 2 | 3 | ====================== 4 | Python描述器引导(翻译) 5 | ====================== 6 | 7 | :作者: Raymond Hettinger 8 | 9 | :联系: 10 | 11 | :翻译: hit9, iceout 12 | 13 | :译者注: 原文链接- http://docs.python.org/2/howto/descriptor.html 14 | 15 | .. Contents:: 16 | 17 | 摘要 18 | ---- 19 | 20 | 定义描述器, 总结描述器协议,并展示描述器是怎么被调用的。展示一个自定义的描述器和包括函数,属性(property), 静态方法(static method), 类方法在内的几个Python内置描述器。通过给出一个纯Python的实现和示例应用来展示每个描述器是怎么工作的。 21 | 22 | 学习描述器不仅让你接触到更多的工具,还可以让你更深入地了解Python,让你体会到Python设计的优雅之处。 23 | 24 | 定义和介绍 25 | ---------- 26 | 27 | 一般来说,一个描述器是一个有“绑定行为”的对象属性(object attribute),它的访问控制被描述器协议方法重写。这些方法是 :meth:`__get__`, :meth:`__set__`, 和 :meth:`__delete__` 。有这些方法的对象叫做描述器。 28 | 29 | 默认对属性的访问控制是从对象的字典里面(__dict__)中获取(get), 设置(set)和删除(delete)它。举例来说, ``a.x`` 的查找顺序是, ``a.__dict__['x']`` , 然后 ``type(a).__dict__['x']`` , 然后找 ``type(a)`` 的父类(不包括元类(metaclass)).如果查找到的值是一个描述器, Python就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪个描述器方法。注意, 只有在新式类中时描述器才会起作用。(新式类是继承自 ``type`` 或者 ``object`` 的类) 30 | 31 | 描述器是强大的,应用广泛的。描述器正是属性, 实例方法, 静态方法, 类方法和 ``super`` 的背后的实现机制。描述器在Python自身中广泛使用,以实现Python 2.2中引入的新式类。描述器简化了底层的C代码,并为Python的日常编程提供了一套灵活的新工具。 32 | 33 | 描述器协议 34 | ---------- 35 | 36 | ``descr.__get__(self, obj, type=None) --> value`` 37 | 38 | ``descr.__set__(self, obj, value) --> None`` 39 | 40 | ``descr.__delete__(self, obj) --> None`` 41 | 42 | 这是所有描述器方法。一个对象具有其中任一个方法就会成为描述器,从而在被当作对象属性时重写默认的查找行为。 43 | 44 | 如果一个对象同时定义了 :meth:`__get__` 和 :meth:`__set__`,它叫做资料描述器(data descriptor)。仅定义了 :meth:`__get__` 的描述器叫非资料描述器(常用于方法,当然其他用途也是可以的) 45 | 46 | 资料描述器和非资料描述器的区别在于:相对于实例的字典的优先级。如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。(译者注:这就是为何实例 ``a`` 的方法和属性重名时,比如都叫 ``foo`` Python会在访问 ``a.foo`` 的时候优先访问实例字典中的属性,因为实例函数的实现是个非资料描述器) 47 | 48 | 要想制作一个只读的资料描述器,需要同时定义 ``__set__`` 和 ``__get__``,并在 ``__set__`` 中引发一个 ``AttributeError`` 异常。定义一个引发异常的 ``__set__`` 方法就足够让一个描述器成为资料描述器。 49 | 50 | 描述器的调用 51 | ------------ 52 | 53 | 描述器可以直接这么调用: ``d.__get__(obj)`` 54 | 55 | 然而更常见的情况是描述器在属性访问时被自动调用。举例来说, ``obj.d`` 会在 ``obj`` 的字典中找 ``d`` ,如果 ``d`` 定义了 ``__get__`` 方法,那么 ``d.__get__(obj)`` 会依据下面的优先规则被调用。 56 | 57 | 调用的细节取决于 ``obj`` 是一个类还是一个实例。另外,描述器只对于新式对象和新式类才起作用。继承于 ``object`` 的类叫做新式类。 58 | 59 | 对于对象来讲,方法 :meth:`object.__getattribute__` 把 ``b.x`` 变成 ``type(b).__dict__['x'].__get__(b, type(b))`` 。具体实现是依据这样的优先顺序:资料描述器优先于实例变量,实例变量优先于非资料描述器,__getattr__()方法(如果对象中包含的话)具有最低的优先级。完整的C语言实现可以在 `Objects/object.c `_ 中 `PyObject_GenericGetAttr() `_ 查看。 60 | 61 | 对于类来讲,方法 :meth:`type.__getattribute__` 把 ``B.x`` 变成 ``B.__dict__['x'].__get__(None, B)`` 。用Python来描述就是:: 62 | 63 | def __getattribute__(self, key): 64 | "Emulate type_getattro() in Objects/typeobject.c" 65 | v = object.__getattribute__(self, key) 66 | if hasattr(v, '__get__'): 67 | return v.__get__(None, self) 68 | return v 69 | 70 | 其中重要的几点: 71 | 72 | * 描述器的调用是因为 :meth:`__getattribute__` 73 | * 重写 :meth:`__getattribute__` 方法会阻止正常的描述器调用 74 | * :meth:`__getattribute__` 只对新式类的实例可用 75 | * :meth:`object.__getattribute__` 和 :meth:`type.__getattribute__` 对 :meth:`__get__` 的调用不一样 76 | * 资料描述器总是比实例字典优先。 77 | * 非资料描述器可能被实例字典重写。(非资料描述器不如实例字典优先) 78 | 79 | ``super()`` 返回的对象同样有一个定制的 :meth:`__getattribute__` 方法用来调用描述器。调用 ``super(B, obj).m()`` 时会先在 ``obj.__class__.__mro__`` 中查找与B紧邻的基类A,然后返回 ``A.__dict__['m'].__get__(obj, A)`` 。如果不是描述器,原样返回 ``m`` 。如果实例字典中找不到 ``m`` ,会回溯继续调用 :meth:`object.__getattribute__` 查找。(译者注:即在 ``__mro__`` 中的下一个基类中查找) 80 | 81 | 注意:在Python 2.2中,如果 ``m`` 是一个描述器, ``super(B, obj).m()`` 只会调用方法 :meth:`__get__` 。在Python 2.3中,非资料描述器(除非是个旧式类)也会被调用。 :c:func:`super_getattro()` 的实现细节在: 82 | `Objects/typeobject.c `_ 83 | ,[del] 一个等价的Python实现在 `Guido's Tutorial`_ [/del] (译者注:原文此句已删除,保留供大家参考)。 84 | 85 | .. _`Guido's Tutorial`: http://www.python.org/2.2.3/descrintro.html#cooperation 86 | 87 | 88 | 以上展示了描述器的机理是在 :class:`object`, :class:`type`, 和 :class:`super` 的 :meth:`__getattribute__()` 方法中实现的。由 :class:`object` 派生出的类自动的继承这个机理,或者它们有个有类似机理的元类。同样,可以重写类的 :meth:`__getattribute__()` 方法来关闭这个类的描述器行为。 89 | 90 | 描述器例子 91 | ---------- 92 | 93 | 下面的代码中定义了一个资料描述器,每次 ``get`` 和 ``set`` 都会打印一条消息。重写 :meth:`__getattribute__` 是另一个可以使所有属性拥有这个行为的方法。但是,描述器在监视特定属性的时候是很有用的。 94 | 95 | :: 96 | 97 | class RevealAccess(object): 98 | """A data descriptor that sets and returns values 99 | normally and prints a message logging their access. 100 | """ 101 | 102 | def __init__(self, initval=None, name='var'): 103 | self.val = initval 104 | self.name = name 105 | 106 | def __get__(self, obj, objtype): 107 | print 'Retrieving', self.name 108 | return self.val 109 | 110 | def __set__(self, obj, val): 111 | print 'Updating' , self.name 112 | self.val = val 113 | 114 | >>> class MyClass(object): 115 | x = RevealAccess(10, 'var "x"') 116 | y = 5 117 | 118 | >>> m = MyClass() 119 | >>> m.x 120 | Retrieving var "x" 121 | 10 122 | >>> m.x = 20 123 | Updating var "x" 124 | >>> m.x 125 | Retrieving var "x" 126 | 20 127 | >>> m.y 128 | 5 129 | 130 | 这个协议非常简单,并且提供了令人激动的可能。一些用途实在是太普遍以致于它们被打包成独立的函数。像属性(property), 方法(bound和unbound method), 静态方法和类方法都是基于描述器协议的。 131 | 132 | 属性(properties) 133 | ---------------- 134 | 135 | 调用 :func:`property` 是建立资料描述器的一种简洁方式,从而可以在访问属性时触发相应的方法调用。这个函数的原型:: 136 | 137 | property(fget=None, fset=None, fdel=None, doc=None) -> property attribute 138 | 139 | 下面展示了一个典型应用:定义一个托管属性(Managed Attribute) ``x`` 。 :: 140 | 141 | class C(object): 142 | def getx(self): return self.__x 143 | def setx(self, value): self.__x = value 144 | def delx(self): del self.__x 145 | x = property(getx, setx, delx, "I'm the 'x' property.") 146 | 147 | 想要看看 :func:`property` 是怎么用描述器实现的? 这里有一个纯Python的等价实现:: 148 | 149 | class Property(object): 150 | "Emulate PyProperty_Type() in Objects/descrobject.c" 151 | 152 | def __init__(self, fget=None, fset=None, fdel=None, doc=None): 153 | self.fget = fget 154 | self.fset = fset 155 | self.fdel = fdel 156 | self.__doc__ = doc 157 | 158 | def __get__(self, obj, objtype=None): 159 | if obj is None: 160 | return self 161 | if self.fget is None: 162 | raise AttributeError, "unreadable attribute" 163 | return self.fget(obj) 164 | 165 | def __set__(self, obj, value): 166 | if self.fset is None: 167 | raise AttributeError, "can't set attribute" 168 | self.fset(obj, value) 169 | 170 | def __delete__(self, obj): 171 | if self.fdel is None: 172 | raise AttributeError, "can't delete attribute" 173 | self.fdel(obj) 174 | 175 | def getter(self, fget): 176 | return type(self)(fget, self.fset, self.fdel, self.__doc__) 177 | 178 | def setter(self, fset): 179 | return type(self)(self.fget, fset, self.fdel, self.__doc__) 180 | 181 | def deleter(self, fdel): 182 | return type(self)(self.fget, self.fset, fdel, self.__doc__) 183 | 184 | 当用户接口已经被授权访问属性之后,需求发生一些变化,属性需要进一步处理才能返回给用户。这时 :func:`property` 能够提供很大帮助。 185 | 186 | 例如,一个电子表格类提供了访问单元格的方式: ``Cell('b10').value`` 。 之后,对这个程序的改善要求在每次访问单元格时重新计算单元格的值。然而,程序员并不想影响那些客户端中直接访问属性的代码。那么解决方案是将属性访问包装在一个属性资料描述器中:: 187 | 188 | class Cell(object): 189 | . . . 190 | def getvalue(self, obj): 191 | "Recalculate cell before returning value" 192 | self.recalc() 193 | return obj._value 194 | value = property(getvalue) 195 | 196 | 函数和方法 197 | ---------- 198 | 199 | Python的面向对象特征是建立在基于函数的环境之上的。非资料描述器把两者无缝地连接起来。 200 | 201 | 类的字典把方法当做函数存储。在定义类的时候,方法通常用关键字 :keyword:`def` 和 :keyword:`lambda` 来声明。这和创建函数是一样的。唯一的不同之处是类方法的第一个参数用来表示对象实例。Python约定,这个参数通常是 *self*, 但也可以叫 *this* 或者其它任何名字。 202 | 203 | 为了支持方法调用,函数包含一个 :meth:`__get__` 方法以便在属性访问时绑定方法。这就是说所有的函数都是非资料描述器,它们返回绑定(bound)还是非绑定(unbound)的方法取决于他们是被实例调用还是被类调用。用Python代码来描述就是:: 204 | 205 | class Function(object): 206 | . . . 207 | def __get__(self, obj, objtype=None): 208 | "Simulate func_descr_get() in Objects/funcobject.c" 209 | return types.MethodType(self, obj, objtype) 210 | 211 | 下面运行解释器来展示实际情况下函数描述器是如何工作的:: 212 | 213 | >>> class D(object): 214 | def f(self, x): 215 | return x 216 | 217 | >>> d = D() 218 | >>> D.__dict__['f'] # 存储成一个function 219 | 220 | >>> D.f # 从类来方法,返回unbound method 221 | 222 | >>> d.f # 从实例来访问,返回bound method 223 | > 224 | 225 | 从输出来看,绑定方法和非绑定方法是两个不同的类型。它们是在文件 226 | Objects/classobject.c(http://svn.python.org/view/python/trunk/Objects/classobject.c?view=markup) 227 | 中用C实现的, :c:type:`PyMethod_Type` 是一个对象,但是根据 :attr:`im_self` 是否是 *NULL* (在C中等价于 *None* ) 而表现不同。 228 | 229 | 同样,一个方法的表现依赖于 :attr:`im_self` 。如果设置了(意味着bound), 原来的函数(保存在 :attr:`im_func` 中)被调用,并且第一个参数设置成实例。如果unbound, 所有参数原封不动地传给原来的函数。函数 :func:`instancemethod_call()` 的实际C语言实现只是比这个稍微复杂些(有一些类型检查)。 230 | 231 | 静态方法和类方法 232 | ---------------- 233 | 234 | 非资料描述器为将函数绑定成方法这种常见模式提供了一个简单的实现机制。 235 | 236 | 简而言之,函数有个方法 :meth:`__get__` ,当函数被当作属性访问时,它就会把函数变成一个实例方法。非资料描述器把 ``obj.f(*args)`` 的调用转换成 ``f(obj, *args)`` 。 调用 ``klass.f(*args)`` 就变成调用 ``f(*args)`` 。 237 | 238 | 下面的表格总结了绑定和它最有用的两个变种: 239 | 240 | +-----------------+----------------------+------------------+ 241 | | Transformation | Called from an | Called from a | 242 | | | Object | Class | 243 | +=================+======================+==================+ 244 | | function | f(obj, \*args) | f(\*args) | 245 | +-----------------+----------------------+------------------+ 246 | | staticmethod | f(\*args) | f(\*args) | 247 | +-----------------+----------------------+------------------+ 248 | | classmethod | f(type(obj), \*args) | f(klass, \*args) | 249 | +-----------------+----------------------+------------------+ 250 | 251 | 静态方法原样返回函数,调用 ``c.f`` 或者 ``C.f`` 分别等价于 ``object.__getattribute__(c, "f")`` 或者 ``object.__getattribute__(C, "f")`` 。也就是说,无论是从一个对象还是一个类中,这个函数都会同样地访问到。 252 | 253 | 那些不需要 ``self`` 变量的方法适合用做静态方法。 254 | 255 | 例如, 一个统计包可能包含一个用来做实验数据容器的类。这个类提供了一般的方法,来计算平均数,中位数,以及其他基于数据的描述性统计指标。然而,这个类可能包含一些概念上与统计相关但不依赖具体数据的函数。比如 ``erf(x)`` 就是一个统计工作中经常用到的,但却不依赖于特定数据的函数。它可以从类或者实例调用: ``s.erf(1.5) --> .9332`` 或者 ``Sample.erf(1.5) --> .9332``. 256 | 257 | 258 | 既然staticmethod将函数原封不动的返回,那下面的代码看上去就很正常了:: 259 | 260 | >>> class E(object): 261 | def f(x): 262 | print x 263 | f = staticmethod(f) 264 | 265 | >>> print E.f(3) 266 | 3 267 | >>> print E().f(3) 268 | 3 269 | 270 | 利用非资料描述器, :func:`staticmethod` 的纯Python版本看起来像这样:: 271 | 272 | class StaticMethod(object): 273 | "Emulate PyStaticMethod_Type() in Objects/funcobject.c" 274 | 275 | def __init__(self, f): 276 | self.f = f 277 | 278 | def __get__(self, obj, objtype=None): 279 | return self.f 280 | 281 | 不像静态方法,类方法需要在调用函数之前会在参数列表前添上class的引用作为第一个参数。不管调用者是对象还是类,这个格式是一样的:: 282 | 283 | >>> class E(object): 284 | def f(klass, x): 285 | return klass.__name__, x 286 | f = classmethod(f) 287 | 288 | >>> print E.f(3) 289 | ('E', 3) 290 | >>> print E().f(3) 291 | ('E', 3) 292 | 293 | 当一个函数不需要相关的数据做参数而只需要一个类的引用的时候,这个特征就显得很有用了。类方法的一个用途是用来创建不同的类构造器。在Python 2.3中, :func:`dict.fromkeys` 可以依据一个key列表来创建一个新的字典。等价的Python实现就是:: 294 | 295 | class Dict: 296 | . . . 297 | def fromkeys(klass, iterable, value=None): 298 | "Emulate dict_fromkeys() in Objects/dictobject.c" 299 | d = klass() 300 | for key in iterable: 301 | d[key] = value 302 | return d 303 | fromkeys = classmethod(fromkeys) 304 | 305 | 现在,一个新的字典就可以这么创建:: 306 | 307 | >>> Dict.fromkeys('abracadabra') 308 | {'a': None, 'r': None, 'b': None, 'c': None, 'd': None} 309 | 310 | 用非资料描述器协议, :func:`classmethod` 的纯Python版本实现看起来像这样:: 311 | 312 | class ClassMethod(object): 313 | "Emulate PyClassMethod_Type() in Objects/funcobject.c" 314 | 315 | def __init__(self, f): 316 | self.f = f 317 | 318 | def __get__(self, obj, klass=None): 319 | if klass is None: 320 | klass = type(obj) 321 | def newfunc(*args): 322 | return self.f(klass, *args) 323 | return newfunc 324 | -------------------------------------------------------------------------------- /docs/GitHub-Repos-For-Pythoners.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-03-01 14:01:01 2 | 3 | .. _github_awesome_python_repos: 4 | 5 | ================================== 6 | Github上Python开发者应该关心的Repo 7 | ================================== 8 | 9 | :作者: hit9 10 | :日期: 2013-01-30 11 | :注: 欢迎fork后pull request来丰富这个文章. 12 | 13 | .. Contents:: 14 | 15 | carbaugh/lice 16 | ------------- 17 | 18 | lice_ : Generate license files for your projects 19 | 20 | .. _lice: https://github.com/jcarbaugh/lice 21 | 22 | 一个用来为你的项目生成许可证的工具。这下可方便了,不用手工的去修改了! 23 | 24 | coleifer/peewee 25 | --------------- 26 | 27 | peewee_: a small, expressive orm -- supports postgresql, mysql and sqlite 28 | 29 | 你在用SQLAlchemy ? 我强烈推荐你看下peewee 30 | 31 | 来看一个sample:: 32 | 33 | User.select().where(User.active == True).order_by(User.username) 34 | 35 | .. _peewee: https://github.com/coleifer/peewee 36 | 37 | 一个单文件的Python ORM.相当轻巧,支持三个数据库。而且,它最讨人喜欢的是它的轻量级的语法。 38 | 39 | docopt/docopt 40 | ------------- 41 | 42 | docopt_ : Pythonic command line arguments parser, that will make you smile 43 | 44 | .. _docopt: https://github.com/docopt/docopt 45 | 46 | 用过doctest? 那来看看docopt。有时候你用py写一个命令行程序,需要接收命令行参数,看看这个例子:: 47 | 48 | """ 49 | Usage: test.py [--verbose] 50 | """ 51 | 52 | from docopt import docopt 53 | 54 | print docopt(__doc__) 55 | 56 | 如果你这么执行程序:: 57 | 58 | python test.py somefile --verbose 59 | 60 | 你会得到这样的输出:: 61 | 62 | {'--verbose': True, '': 'somefile'} 63 | 64 | hhatto/autopep8 65 | --------------- 66 | 67 | autopep8_ : A tool that automatically formats Python code to conform to the PEP 8 style guide. 68 | 69 | .. _autopep8: https://github.com/hhatto/autopep8 70 | 71 | 每个Python程序员都应该checkout的repo.自动的把你的Python代码转成符合PEP8风格的代码. 72 | 73 | 使用 ``-i`` 参数来直接修改你的 Python文件:: 74 | 75 | autopep8 -i mycode.py 76 | 77 | kachayev/fn.py 78 | -------------- 79 | 80 | fn.py_ : Functional programming in Python: implementation of missing features to enjoy FP 81 | 82 | .. _fn.py: https://github.com/kachayev/fn.py 83 | 84 | 这是个很有趣的项目,来弥补Python在函数式编程方面没有的一些特性。来看个sample:: 85 | 86 | from fn import _ 87 | assert list(map(_ * 2, range(5))) == [0,2,4,6,8] 88 | 89 | nose-devs/nose 90 | -------------- 91 | 92 | nose_ : nose is nicer testing for python 93 | 94 | .. _nose: https://github.com/nose-devs/nose 95 | 96 | 或许nose已经不是新鲜的测试框架了,现在还有很多新的测试框架诞生,不过大家都在用它,而且似乎没要离开nose的意思。 97 | 98 | amoffat/sh 99 | ---------- 100 | 101 | sh_ : Python subprocess interface 102 | 103 | .. _sh: https://github.com/amoffat/sh 104 | 105 | 这个库已经被津津乐道很久了。看代码:: 106 | 107 | from sh import git 108 | 109 | git.clone("https://github.com/amoffat/sh") 110 | 111 | 是不是比 ``os.system`` 更简洁明了。 112 | 113 | Lokaltog/powerline 114 | ------------------ 115 | 116 | 如果你是个linux(or mac)下的开发者,又喜欢在终端下工作的话,你一定喜欢用powerline来美化自己的工作空间。 117 | 118 | 之前github上兴起了vim-powerline,tmux-powerline,还有powerline-bash,现在Lokaltog提供了一个统一的解决方案,只要安装这个python包,再追加些东西到配置文件就可以使用漂亮的powerline了 119 | 120 | 具体的效果请见repo : https://github.com/Lokaltog/powerline 121 | 122 | benoitc/gunicorn 123 | ---------------- 124 | 125 | gunicorn_ : gunicorn 'Green Unicorn' is a WSGI HTTP Server for UNIX, fast clients and sleepy applications 126 | 127 | .. _gunicorn: https://github.com/benoitc/gunicorn 128 | 129 | 一个Python WSGI UNIX的HTTP服务器,从Ruby的独角兽(Unicorn)项目移植。Gunicorn大致与各种Web框架兼容. 130 | 131 | 一个例子,运行你的flask app:: 132 | 133 | gunicorn myproject:app 134 | 135 | 使用起来超级简单! 136 | 137 | faif/python-patterns 138 | -------------------- 139 | 140 | python-patterns_ : A collection of design patterns implemented (by other people) in python 141 | 142 | .. _python-patterns : https://github.com/faif/python-patterns 143 | 144 | 这个repo收集了很多设计模式的python写法 145 | 146 | gutworth/six/ 147 | ------------- 148 | 149 | six_ : Six is a Python 2 and 3 compatibility library 150 | 151 | .. _six: https://bitbucket.org/gutworth/six 152 | 153 | Six没有托管在Github上,而是托管在了Bitbucket上,不过这些都不是重点,重点是它的作用。 154 | 155 | 众所周知 Python 2 和 Python 3 版本的分裂给 Python 开发者们带来了很大的烦恼,为了使代码同时兼容两个版本,往往要增加大量的代码。 于是 Six 出现了。正如它的介绍所说,它是一个专门用来兼容 Python 2 和 Python 3 的库。它解决了诸如 urllib 的部分方法不兼容, str 和 bytes 类型不兼容等“知名”问题。 156 | 157 | 它的效果怎么样?pypi上单日十万以上,单月几百万的下载量足以说明了。要知道诸如 Flask 和 Django 这类知名的库,月下载量也只有几十万。 158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext watch 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " text to make text files" 38 | @echo " man to make manual pages" 39 | @echo " texinfo to make Texinfo files" 40 | @echo " info to make Texinfo files and run them through makeinfo" 41 | @echo " gettext to make PO message catalogs" 42 | @echo " changes to make an overview of all changed/added/deprecated items" 43 | @echo " linkcheck to check all external links for integrity" 44 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 45 | 46 | clean: 47 | -rm -rf $(BUILDDIR)/* 48 | 49 | html: 50 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 53 | 54 | dirhtml: 55 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 58 | 59 | singlehtml: 60 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 61 | @echo 62 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 63 | 64 | pickle: 65 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 66 | @echo 67 | @echo "Build finished; now you can process the pickle files." 68 | 69 | json: 70 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 71 | @echo 72 | @echo "Build finished; now you can process the JSON files." 73 | 74 | htmlhelp: 75 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 76 | @echo 77 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 78 | ".hhp project file in $(BUILDDIR)/htmlhelp." 79 | 80 | qthelp: 81 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 82 | @echo 83 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 84 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 85 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyZh.qhcp" 86 | @echo "To view the help file:" 87 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyZh.qhc" 88 | 89 | devhelp: 90 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 91 | @echo 92 | @echo "Build finished." 93 | @echo "To view the help file:" 94 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyZh" 95 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyZh" 96 | @echo "# devhelp" 97 | 98 | epub: 99 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 100 | @echo 101 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 102 | 103 | latex: 104 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 105 | @echo 106 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 107 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 108 | "(use \`make latexpdf' here to do that automatically)." 109 | 110 | latexpdf: 111 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 112 | @echo "Running LaTeX files through pdflatex..." 113 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 114 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 115 | 116 | text: 117 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 118 | @echo 119 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 120 | 121 | man: 122 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 123 | @echo 124 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 125 | 126 | texinfo: 127 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 128 | @echo 129 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 130 | @echo "Run \`make' in that directory to run these through makeinfo" \ 131 | "(use \`make info' here to do that automatically)." 132 | 133 | info: 134 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 135 | @echo "Running Texinfo files through makeinfo..." 136 | make -C $(BUILDDIR)/texinfo info 137 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 138 | 139 | gettext: 140 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 141 | @echo 142 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 143 | 144 | changes: 145 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 146 | @echo 147 | @echo "The overview file is in $(BUILDDIR)/changes." 148 | 149 | linkcheck: 150 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 151 | @echo 152 | @echo "Link check complete; look for any errors in the above output " \ 153 | "or in $(BUILDDIR)/linkcheck/output.txt." 154 | 155 | doctest: 156 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 157 | @echo "Testing of doctests in the sources finished, look at the " \ 158 | "results in $(BUILDDIR)/doctest/output.txt." 159 | 160 | watch: 161 | onChanges 'make html' -p '*.rst' 162 | -------------------------------------------------------------------------------- /docs/awesome-python-libraries.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-04-16 21:20:00 2 | 3 | ====================== 4 | 让人耳目一新的Python库 5 | ====================== 6 | 7 | :作者: hit9 8 | 9 | :联系: 10 | 11 | .. Contents:: 12 | 13 | 摘要 14 | ---- 15 | 16 | 工欲善其事,必先利其器。Python社区高产富饶,有大量的美丽,实用的库, 这里挑选出来一些接口设计简洁的库。 17 | 18 | docopt 19 | ------ 20 | 21 | Github: https://github.com/docopt/docopt 22 | 23 | Pythonic的命令行参数解析库:: 24 | 25 | """Usage: 26 | quick_example.py tcp [--timeout=] 27 | quick_example.py serial [--baud=9600] [--timeout=] 28 | quick_example.py -h | --help | --version 29 | 30 | """ 31 | from docopt import docopt 32 | 33 | 34 | if __name__ == '__main__': 35 | arguments = docopt(__doc__, version='0.1.1rc') 36 | print(arguments) 37 | 38 | requests 39 | -------- 40 | 41 | Github: https://github.com/kennethreitz/requests 42 | 43 | 大神kennethreitz的作品,简易明了的HTTP请求操作库, 是urllib2的理想替代品 44 | 45 | API简洁明了,这才是Python开发者喜欢的:: 46 | 47 | >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) 48 | >>> r.status_code 49 | 200 50 | >>> r.headers['content-type'] 51 | 'application/json; charset=utf8' 52 | >>> r.encoding 53 | 'utf-8' 54 | >>> r.text 55 | u'{"type":"User"...' 56 | >>> r.json() 57 | {u'private_gists': 419, u'total_private_repos': 77, ...} 58 | 59 | sh 60 | --- 61 | 62 | http://amoffat.github.io/sh/ 63 | 64 | 如其名,子进程接口。 65 | 66 | :: 67 | 68 | from sh import ifconfig 69 | print(ifconfig("wlan0")) 70 | 71 | purl 72 | ---- 73 | 74 | github: https://github.com/codeinthehole/purl 75 | 76 | 拥有简洁接口的URL处理器:: 77 | 78 | >>> from purl import URL 79 | >>> from_str = URL('https://www.google.com/search?q=testing') 80 | >>> u.query_param('q') 81 | u'testing' 82 | >>> u.host() 83 | u'www.google.com' 84 | 85 | path.py 86 | ------- 87 | 88 | github: https://github.com/jaraco/path.py 89 | 90 | 一个文件系统处理库,不过目前还在开发阶段 91 | 92 | :: 93 | 94 | from path import path 95 | d = path('/home/guido/bin') 96 | for f in d.files('*.py'): 97 | f.chmod(0755) 98 | 99 | Peewee 100 | ------ 101 | 102 | https://github.com/coleifer/peewee 103 | 104 | 小型ORM, 接口很漂亮:: 105 | 106 | # get tweets by editors ("<<" maps to IN) 107 | Tweet.select().where(Tweet.user << editors) 108 | 109 | # how many active users are there? 110 | User.select().where(User.active == True).count() 111 | 112 | 类似的我的 CURD.py (https://github.com/hit9/CURD.py) :) :: 113 | 114 | User.create(name="John", email="John@gmail.com") # create 115 | 116 | User.at(2).update(email="John@github.com") # update 117 | 118 | John = User.where(name="John").select().fetchone() # read 119 | 120 | # who wrote posts? 121 | for post, user in (Post & User).select().fetchall(): 122 | print "Author: %s, PostName: %s" % (user.name, post.name) 123 | 124 | 125 | Pony ORM 126 | -------- 127 | 128 | https://github.com/ponyorm/pony 129 | 130 | 一个十分独特的ORM,接口简单干净,最大的特点是支持使用generator的语法来进行查询,可以使查询语句变得简洁,灵活,而且漂亮。 131 | 132 | 例如可以使用如下的语句来进行一个查询:: 133 | 134 | select(p for p in Product if p.name.startswith('A') and p.cost <= 1000) 135 | 136 | 同时,Pony ORM还提供了一个ER图编辑工具来进行数据库原型设计。 137 | 138 | Spyne 139 | ----- 140 | 141 | https://github.com/arskom/spyne 142 | 143 | 一个用于构建RPC服务的工具集,支持SOAP,JSON,XML等多种流行的协议。 144 | 145 | 现在有诸如 flask-restful 以及 django-rest-framework 等框架用于 REST 服务的开发,人们对于 REST 之外的框架似乎兴趣不大。Spyne 很好地填补了这一空白,它支持多种协议,而且本身也封装地相当好:: 146 | 147 | class HelloWorldService(ServiceBase): 148 | @srpc(Unicode, Integer, _returns=Iterable(Unicode)) 149 | def say_hello(name, times): 150 | for i in range(times): 151 | yield 'Hello, %s' % name 152 | 153 | application = Application([HelloWorldService], 154 | tns='spyne.examples.hello', 155 | in_protocol=Soap11(validator='lxml'), 156 | out_protocol=Soap11() 157 | ) 158 | 159 | 160 | 短短几行代码便实现了一个支持SOAP 1.1 协议的服务器端application,接入任何一个WSGI兼容的服务器后端就可以运行了。 161 | 162 | schema 163 | ------ 164 | 165 | https://github.com/halst/schema 166 | 167 | 同样是docopt的作者编写的,一个数据格式检查库,非常新颖:: 168 | 169 | >>> from schema import Schema 170 | >>> Schema(int).validate(123) 171 | 123 172 | >>> Schema(int).validate('123') 173 | Traceback (most recent call last): 174 | ... 175 | SchemaError: '123' should be instance of 176 | 177 | fn.py 178 | ----- 179 | 180 | https://github.com/kachayev/fn.py 181 | 182 | 增强Python的函数式编程:: 183 | 184 | from fn import _ 185 | 186 | print (_ + 2) # "(x1) => (x1 + 2)" 187 | print (_ + _ * _) # "(x1, x2, x3) => (x1 + (x2 * x3))" 188 | 189 | when.py 190 | -------- 191 | 192 | https://github.com/dirn/When.py 193 | 194 | 友好的时间日期库:: 195 | 196 | >>> import when 197 | >>> when.timezone() 198 | 'Asia/Shanghai' 199 | >>> when.today() 200 | datetime.date(2013, 5, 14) 201 | >>> when.tomorrow() 202 | datetime.date(2013, 5, 15) 203 | >>> when.now() 204 | datetime.datetime(2013, 5, 14, 21, 2, 23, 78838) 205 | 206 | clize 207 | ------ 208 | 209 | https://github.com/epsy/clize 210 | 211 | 用 docopt 写程序的使用doc是不是很爽, 212 | clize是一个类似的库。可以用程序的函数名字来作为使用方法 213 | 214 | :: 215 | 216 | #!/usr/bin/env python 217 | 218 | from clize import clize 219 | 220 | @clize 221 | def echo(text, reverse=false): 222 | if reverse: 223 | text = text[::-1] 224 | print(text) 225 | if __name__ == '__main__': 226 | import sys 227 | echo(*sys.argv) 228 | 229 | 230 | 而这个小程序就可以这么使用:: 231 | 232 | $ ./echo.py --help 233 | Usage: ./echo.py [OPTIONS] text 234 | 235 | Positional arguments: 236 | text 237 | 238 | Options: 239 | --reverse 240 | -h, --help Show this help 241 | 242 | 243 | Pocoo小组 244 | --------- 245 | 246 | pocoo出的库,必属精品。 http://www.pocoo.org/ 247 | 248 | 它的库很出名: flask, jinja2, pygments,sphinx 249 | 250 | 后言和链接 251 | ---------- 252 | 253 | - 你可能对 :ref:`github_awesome_python_repos` 感兴趣 254 | - 看下这个gist https://gist.github.com/medecau/797129 255 | - HN: https://news.ycombinator.com/item?id=4772261 256 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PyZh documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Jan 12 22:09:35 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | #extensions = ['sphinxcontrib.feed'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'PyZh' 44 | copyright = u'2013, hit9' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | language = 'zh_CN' 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'PyZhdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'PyZh.tex', u'PyZh Documentation', 187 | u'hit9', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'pyzh', u'PyZh Documentation', 217 | [u'hit9'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'PyZh', u'PyZh Documentation', 231 | u'hit9', 'PyZh', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | 244 | html_title = u"一起写Python文章,一起看Python文章" 245 | 246 | # the feed based url 247 | #feed_base_url = "https://pyzh.readthedocs.org/en/latest" 248 | 249 | #feed_filename = 'rss.xml' 250 | 251 | sys.path.append(os.path.abspath('_themes')) 252 | html_theme_path = ['_themes'] 253 | html_theme = 'rux' 254 | -------------------------------------------------------------------------------- /docs/improving-your-python-productivity.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-04-15 22:00:00 2 | 3 | ========================== 4 | (译)提高你的Python编码效率 5 | ========================== 6 | 7 | :作者: Oz Katz 8 | 9 | :联系: https://twitter.com/ozkatz100 10 | 11 | :翻译: hit9 12 | 13 | :译者注: 原文链接 - http://ozkatz.github.io/improving-your-python-productivity.html 14 | 15 | .. Contents:: 16 | 17 | 我用Python编程有几年了, 并且我仍然经常惊讶于Python代码可以如何的简洁,如何的 DRY_ 。 我学到了很多小贴士和技巧,大多数来自于阅读开源项目的源代码,像 Django_, Flask_, Requests_ 等。 18 | 19 | .. _DRY: http://en.wikipedia.org/wiki/Don't_repeat_yourself 20 | .. _Django: https://www.djangoproject.com/ 21 | .. _Flask: http://flask.pocoo.org/ 22 | .. _Requests: http://docs.python-requests.org/en/latest/ 23 | 24 | 这里我挑出了几个有时被大家忽略的几条,但是它们在日常工作中会有很大帮助。 25 | 26 | 1. 字典和集合推导式 27 | ------------------- 28 | 29 | 大多数Python开发者知道使用列表推导式。你不熟悉这一点? 一个列表推导式是一个创造列表的简短方式:: 30 | 31 | >>> some_list = [1, 2, 3, 4, 5] 32 | >>> another_list = [ x + 1 for x in some_list ] 33 | >>> another_list 34 | [2, 3, 4, 5, 6] 35 | 36 | 从Python 3.1开始(也反向地移植到了Python 2.7),我们可以用同样的方式创建集合和字典:: 37 | 38 | >>> # Set Comprehensions 39 | >>> some_list = [1, 2, 3, 4, 5, 2, 5, 1, 4, 8] 40 | >>> even_set = { x for x in some_list if x % 2 == 0 } 41 | >>> even_set 42 | set([8, 2, 4]) 43 | 44 | >>> # Dict Comprehensions 45 | >>> d = { x: x % 2 == 0 for x in range(1, 11) } 46 | >>> d 47 | {1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False, 10: True} 48 | 49 | 50 | 第一个例子中,我们用 ``some_list`` 建立了一个元素不重复的集合,但只有偶数。第二个字典的例子中展示了一个字典的创建,这个字典的键是1到10(包括10),值是布尔值,指明该键是不是一个偶数。 51 | 52 | 另一个值得注意的地方是集合的文法,我们可以这么简单的创建一个集合:: 53 | 54 | >>> my_set = {1, 2, 1, 2, 3, 4} 55 | >>> my_set 56 | set([1, 2, 3, 4]) 57 | 58 | 而没有使用到内建的 ``set`` 方法 59 | 60 | 2.使用计数器对象计数 61 | -------------------- 62 | 63 | 很明显,但很容易遗忘。计数是一个寻常不过的编程任务,而且大多数情形下这不是个难事。不过计数可以更简单。 64 | 65 | Python的 collections_ 库包含一个 ``dict`` 的子类,专门解决计数任务:: 66 | 67 | >>> from collections import Counter 68 | >>> c = Counter('hello world') 69 | >>> c 70 | Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1}) 71 | >>> c.most_common(2) 72 | [('l', 3), ('o', 2)] 73 | 74 | .. _collections: http://docs.python.org/2/library/collections.html 75 | 76 | 3. 漂亮地打印JSON 77 | ------------------ 78 | 79 | JSON是一个很棒的序列格式,如今广泛应用在API和web服务中,但是很难用裸眼来看大数据量的JSON,它们很长,还在一行里。 80 | 81 | 可以用参数 ``indent`` 来更好地打印JSON数据,这在跟 REPL或是日志打交道的时候很有用:: 82 | 83 | >>> import json 84 | >>> print(json.dumps(data)) # No indention 85 | {"status": "OK", "count": 2, "results": [{"age": 27, "name": "Oz", "lactose_intolerant": true}, {"age": 29, "name": "Joe", "lactose_intolerant": false}]} 86 | >>> print(json.dumps(data, indent=2)) # With indention 87 | { 88 | "status": "OK", 89 | "count": 2, 90 | "results": [ 91 | { 92 | "age": 27, 93 | "name": "Oz", 94 | "lactose_intolerant": true 95 | }, 96 | { 97 | "age": 29, 98 | "name": "Joe", 99 | "lactose_intolerant": false 100 | } 101 | ] 102 | } 103 | 104 | 105 | 另外,去看看内建模块 ``pprint`` , 它可以帮助你漂亮地输出其它的东西。 106 | 107 | 4. 快速建立一个web服务 108 | ----------------------- 109 | 110 | 有时我们需要一个建立RPC服务简单而快速的方法。我们需要的只是让程序B去调用程序A(可能在另一个机器上)的方法。 111 | 112 | 我们不用了解关于这个的任何技术,但是我们只是需要这么个简单的东西,我们可以使用一个叫做 XML-RPC_ 的协议(对应的Python库实现 SimpleXMLRPCServer_ )来处理这种事。 113 | 114 | .. _XML-RPC: http://en.wikipedia.org/wiki/XML-RPC 115 | .. _SimpleXMLRPCServer: http://docs.python.org/2/library/simplexmlrpcserver.html 116 | 117 | 这里是一个简单粗糙的文件阅读服务器:: 118 | 119 | from SimpleXMLRPCServer import SimpleXMLRPCServer 120 | 121 | def file_reader(file_name): 122 | with open(file_name, 'r') as f: 123 | return f.read() 124 | 125 | server = SimpleXMLRPCServer(('localhost', 8000)) 126 | server.register_introspection_functions() 127 | 128 | server.register_function(file_reader) 129 | 130 | server.serve_forever() 131 | 132 | 响应它的客户端:: 133 | 134 | import xmlrpclib 135 | proxy = xmlrpclib.ServerProxy('http://localhost:8000/') 136 | proxy.file_reader('/tmp/secret.txt') 137 | 138 | 现在我们就有了一个远程的文件阅读器,除了一点代码,没有外部依赖。(当然,不安全,所以只在"家"用这个吧) 139 | 140 | 5. Python的开源社区 141 | -------------------- 142 | 143 | 刚我一直在说Python的标准库了,这些库只要你安装Python就会包含在你的Python中。对于大多数的其他任务,这里有大量的社区维护的第三方库来满足我们的需求。 144 | 145 | 这是一个我挑选Python库的办法: 146 | 147 | - 包含一个明确的协议,以便我们使用 148 | 149 | - 积极活跃的开发和维护 150 | 151 | - 可以用 ``pip`` 来安装,可以轻易地重复部署 152 | 153 | - 拥有一个合适覆盖率的测试集 154 | 155 | 如果你发现了一个适合你需求的Python库,不要害羞,大多数开源项目欢迎我们贡献代码和协助,即使你不是一个Python老将。帮助之手随时受欢迎! 156 | 157 | 译者追加的技巧 158 | -------------- 159 | 160 | 原文评论里的一些技巧, 值得一看! 161 | 162 | - 快速在一个目录建立HTTP服务器 163 | :: 164 | 165 | python -m SimpleHTTPServer 166 | 167 | 在 Python 3 中:: 168 | 169 | python -m http.server 170 | 171 | - 命令行上漂亮地打印JSON:: 172 | 173 | echo '{"json":"obj"}' | python -mjson.tool 174 | 175 | 而且,如果你安装了 ``Pygments`` 模块,可以高亮地打印JSON:: 176 | 177 | echo '{"json":"obj"}' | python -mjson.tool | pygmentize -l json 178 | 179 | - 注意 ``{}`` 是一个空的字典,而不是空的集合 180 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | PyZh 4 | ==== 5 | 6 | 一起写文章, 一起看文章 7 | ---------------------- 8 | 9 | 这是个Python技术文章的收集,翻译的Git源的doc. 10 | 11 | :Github: https://github.com/hit9/PyZh 12 | :Issue: https://github.com/hit9/PyZh/issues 13 | :Authors: https://github.com/hit9/PyZh/contributors 14 | :Feed: https://pyzh.readthedocs.org/en/latest/rss.xml 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | :numbered: 19 | 20 | Descriptor-HOW-TO-Guide 21 | python-the-dictionary-playbook 22 | the-python-yield-keyword-explained 23 | python-idioms 24 | GitHub-Repos-For-Pythoners 25 | improving-your-python-productivity 26 | awesome-python-libraries 27 | python-questions-on-stackoverflow 28 | python-hidden-features 29 | single-line-command-with-python 30 | python-magic-methods-guide 31 | python-setup-dot-py-vs-requirements-dot-txt 32 | python-realtime 33 | python-pandas 34 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyZh.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyZh.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/python-hidden-features.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-04-20 16:14:10 2 | 3 | =================================== 4 | (译)Python的隐藏特性(StackOverflow) 5 | =================================== 6 | 7 | :译: hit9 8 | :原文: http://stackoverflow.com/questions/101268/hidden-features-of-python 9 | :注: 这里忽略了生成器,装饰器,交换变量等熟知技巧 10 | 11 | .. Contents:: 12 | 13 | 函数参数unpack 14 | -------------- 15 | 16 | 老生常谈的了:: 17 | 18 | 19 | def foo(x, y): 20 | print x, y 21 | 22 | alist = [1, 2] 23 | adict = {'x': 1, 'y': 2} 24 | 25 | foo(*alist) # 1, 2 26 | foo(**adict) # 1, 2 27 | 28 | 链式比较操作符 29 | --------------- 30 | 31 | :: 32 | 33 | >>> x = 3 34 | >>> 1 < x < 5 35 | True 36 | >>> 4 > x >=3 37 | True 38 | 39 | 注意函数的默认参数 40 | ------------------ 41 | 42 | :: 43 | 44 | >>> def foo(x=[]): 45 | ... x.append(1) 46 | ... print x 47 | ... 48 | >>> foo() 49 | [1] 50 | >>> foo() 51 | [1, 1] 52 | 53 | 更安全的做法:: 54 | 55 | >>> def foo(x=None): 56 | ... if x is None: 57 | ... x = [] 58 | ... x.append(1) 59 | ... print x 60 | ... 61 | >>> foo() 62 | [1] 63 | >>> foo() 64 | [1] 65 | >>> 66 | 67 | 字典有个get()方法 68 | ----------------- 69 | 70 | ``dct.get(key[, default_value])`` , 当字典 ``dct`` 中找不到 ``key`` 时, ``get`` 就会返回 ``default_value`` :: 71 | 72 | sum[value] = sum.get(value, 0) + 1 73 | 74 | 带关键字的格式化 75 | ----------------- 76 | 77 | :: 78 | 79 | >>> print "Hello %(name)s !" % {'name': 'James'} 80 | Hello James ! 81 | >>> print "I am years %(age)i years old" % {'age': 18} 82 | I am years 18 years old 83 | 84 | 更新些的格式化:: 85 | 86 | >>> print "Hello {name} !".format(name="James") 87 | Hello James ! 88 | 89 | 快有些模板引擎的味道了:) 90 | 91 | for...else 语法 92 | ----------------- 93 | 94 | :: 95 | 96 | >>> for i in (1, 3, 5): 97 | ... if i % 2 == 0: 98 | ... break 99 | ... else: 100 | ... print "var i is always an odd" 101 | ... 102 | var i is always an odd 103 | >>> 104 | 105 | ``else`` 语句块会在循环结束后执行,除非在循环块中执行 ``break`` 106 | 107 | dict 的特殊方法__missing__ 108 | --------------------------- 109 | 110 | Python 2.5之后引入的。当查找不到 ``key`` 的时候,会执行这个方法。 111 | 112 | :: 113 | 114 | >>> class Dict(dict): 115 | ... def __missing__(self, key): 116 | ... self[key] = [] 117 | ... return self[key] 118 | ... 119 | >>> dct = Dict() 120 | >>> dct["foo"].append(1) 121 | >>> dct["foo"].append(2) 122 | >>> dct["foo"] 123 | [1, 2] 124 | 125 | 这很像 ``collections.defaultdict`` 不是吗? :: 126 | 127 | >>> from collections import defaultdict 128 | >>> dct = defaultdict(list) 129 | >>> dct["foo"] 130 | [] 131 | >>> dct["bar"].append("Hello") 132 | >>> dct 133 | defaultdict(, {'foo': [], 'bar': ['Hello']}) 134 | 135 | 136 | 切片操作的步长参数 137 | ------------------ 138 | 139 | 还能用步长 ``-1`` 来反转链表:: 140 | 141 | >>> a = [1, 2, 3, 4, 5] 142 | >>> a[::2] 143 | [1, 3, 5] 144 | >>> a[::-1] 145 | [5, 4, 3, 2, 1] 146 | >>> 147 | 148 | 另一种字符串连接 149 | ---------------- 150 | 151 | :: 152 | 153 | >>> Name = "Wang" "Hong" 154 | >>> Name 155 | 'WangHong' 156 | 157 | 连接多行:: 158 | 159 | >>> Name = "Wang" \ 160 | ... "Hong" 161 | >>> Name 162 | 'WangHong' 163 | 164 | Python解释器中的"_" 165 | ------------------- 166 | 167 | :: 168 | 169 | >>> range(4) 170 | [0, 1, 2, 3] 171 | >>> _ 172 | [0, 1, 2, 3] 173 | 174 | ``_`` 即Python解释器上一次返回的值 175 | 176 | Python 描述器 177 | -------------- 178 | 179 | Python描述器是Python 中很魔幻的东西,方法等都是描述器。不再举例 180 | 181 | Zen 182 | ---- 183 | 184 | :: 185 | 186 | import this 187 | 188 | 嵌套列表推导式 189 | --------------- 190 | 191 | :: 192 | 193 | >>> [(i, j) for i in range(3) for j in range(i)] 194 | [(1, 0), (2, 0), (2, 1)] 195 | 196 | 197 | try/except/else 198 | ---------------- 199 | 200 | :: 201 | 202 | try: 203 | put_4000000000_volts_through_it(parrot) 204 | except Voom: 205 | print "'E's pining!" 206 | else: 207 | print "This parrot is no more!" 208 | finally: 209 | end_sketch() 210 | 211 | 212 | print 重定向输出到文件 213 | ---------------------- 214 | 215 | :: 216 | 217 | >>> print >> open("somefile", "w+"), "Hello World" 218 | 219 | 注意打开的模式: ``"w+"`` 而不能 ``"w"`` , 当然 ``"a"`` 是可以的 220 | 221 | 省略号 222 | ------ 223 | 224 | 在 ``Python 3`` 中你可以直接使用省略号这个文法:: 225 | 226 | Python 3.2 (r32:88445, Oct 20 2012, 14:09:50) 227 | [GCC 4.5.2] on linux2 228 | Type "help", "copyright", "credits" or "license" for more information. 229 | >>> ... 230 | Ellipsis 231 | 232 | Python2 中呢? :: 233 | 234 | >>> class C(object): 235 | ... def __getitem__(self, item): 236 | ... return item 237 | ... 238 | >>> C()[1:2, ..., 3] 239 | (slice(1, 2, None), Ellipsis, 3) 240 | >>> 241 | 242 | Python3中的元组unpack 243 | ---------------------- 244 | 245 | 真的但愿Python2也这样:: 246 | 247 | >>> a, b, *rest = range(10) 248 | >>> a 249 | 0 250 | >>> b 251 | 1 252 | >>> rest 253 | [2, 3, 4, 5, 6, 7, 8, 9] 254 | >>> 255 | 256 | 当然也可以取出最后一个:: 257 | 258 | >>> first, second, *rest, last = range(10) 259 | >>> first 260 | 0 261 | >>> second 262 | 1 263 | >>> last 264 | 9 265 | >>> rest 266 | [2, 3, 4, 5, 6, 7, 8] 267 | 268 | pow()还有第三个参数 269 | ------------------- 270 | 271 | 我们都知道内置函数 ``pow``, ``pow(x, y)`` 即 ``x ** y`` 272 | 273 | 但是它还可以有第三个参数:: 274 | 275 | >>> pow(4, 2, 2) 276 | 0 277 | >>> pow(4, 2, 3) 278 | 1 279 | 280 | 其实第三个参数是来求模的: ``pow(x, y, z) == (x ** y) % z`` 281 | 282 | 注意,内置的 ``pow`` 和 ``math.pow`` 并不是一个函数,后者只接受2个参数 283 | 284 | enumerate还有第二个参数 285 | ------------------------ 286 | 287 | ``enumerate`` 很赞,可以给我们索引和序列值的对, 但是它还有第二个参数:: 288 | 289 | >>> lst = ["a", "b", "c"] 290 | >>> list(enumerate(lst, 1)) 291 | [(1, 'a'), (2, 'b'), (3, 'c')] 292 | 293 | 这个参数用来: 指明索引的起始值 294 | 295 | 显式的声明一个集合 296 | ------------------- 297 | 298 | 新建一个集合,我们会:: 299 | 300 | >>> set([1,2,3]) 301 | 302 | 在Python 2.7 之后可以这么写了:: 303 | 304 | >>> {1,2,3} 305 | set([1, 2, 3]) 306 | 307 | 用切片来删除序列的某一段 308 | ------------------------ 309 | 310 | :: 311 | 312 | >>> a = [1, 2, 3, 4, 5, 6, 7] 313 | >>> a[1:4] = [] 314 | >>> a 315 | [1, 5, 6, 7] 316 | 317 | 当然用 ``del a[1:4]`` 也是可以的 318 | 319 | 去除偶数项(偶数索引的):: 320 | 321 | >>> a = [0, 1, 2, 3, 4, 5, 6, 7] 322 | >>> del a[::2] 323 | >>> a 324 | [1, 3, 5, 7] 325 | 326 | isinstance可以接收一个元组 327 | -------------------------- 328 | 329 | 这个真的鲜为人知, 我们可以用 ``isinstance(x, (float, int))`` 来判断 ``x`` 是不是数:: 330 | 331 | >>> isinstance(1, (float, int)) 332 | True 333 | >>> isinstance(1.3, (float, int)) 334 | True 335 | >>> isinstance("1.3", (float, int)) 336 | False 337 | 338 | 那么对于第三个测试,你把 ``str`` 加入元组就可以看到这是怎么回事了:: 339 | 340 | >>> isinstance("1.3", (float, int, str)) 341 | True 342 | 343 | 也就是那个元组里面是 **或** 的关系,只要是其中一个的实例就返回 ``True`` 344 | 345 | 字典里的无限递归 346 | ----------------- 347 | 348 | :: 349 | 350 | >>> a, b = {}, {} 351 | >>> a['b'] = b 352 | >>> b['a'] = a 353 | >>> a 354 | {'b': {'a': {...}}} 355 | 356 | 当然你可以制作一个链表中的无限循环:: 357 | 358 | >>> a, b = [], [] 359 | >>> a.append(b) 360 | >>> b.append(a) 361 | >>> a 362 | [[[...]]] 363 | 364 | 真心不知道有什么用,不过蛮好玩的不是吗 365 | 366 | Python可以认识Unicode中的数字 367 | ----------------------------- 368 | 369 | 所以说,Python很赞:: 370 | 371 | >>> int(u'1234') 372 | 1234 373 | 374 | 不只是ASCII字符串的可以认出来,连Unicode的也可以。 375 | 376 | 不能访问到的属性 377 | ----------------- 378 | 379 | 回答这个答案的人太坏了:) :: 380 | 381 | >>> class O(object):pass 382 | ... 383 | >>> o = O() 384 | >>> setattr(o, "can't touch this", 123) 385 | >>> o.can't touch this 386 | File "", line 1 387 | o.can't touch this 388 | ^ 389 | SyntaxError: EOL while scanning string literal 390 | >>> 391 | 392 | 不过,能用 ``setattr`` 设置属性,就可以用 ``getattr`` 取出 393 | 394 | 尾语 395 | ---- 396 | 397 | 欢迎fork, 追加内容。 398 | -------------------------------------------------------------------------------- /docs/python-idioms.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-02-23 14:01:01 2 | 3 | ================ 4 | (译)Python的惯例 5 | ================ 6 | 7 | :原文: http://courses.cms.caltech.edu/cs11/material/python/misc/python_idioms.html 8 | 9 | 每一种计算机语言都有其使用惯例,就是那些完成给定任务所采用的典型方法。Python也不例外。一些使用惯例并不为人所知,所以我们在这里收集一下。我们同样添加了一些可能你在阅读教程的时候没有注意到的一些特性。这些惯例在本文中将按照它们的使用难度和使用的频率的顺序展开。 10 | 11 | **提醒!** 本文一些东西可能过时了,请参照最新的Python官方文档. 12 | 13 | .. Contents:: 14 | 15 | 请看这些文档... 16 | --------------- 17 | 18 | 虽然它们并不是真正的使用惯例,但是你应该知道它们 19 | 20 | 1. 长整数 21 | 22 | 2. 函数的可选参数 23 | 24 | 3. 函数的关键字参数 25 | 26 | 4. 类的 :meth:`getattr` 方法和 :meth:`__getattr__` 方法 27 | 28 | 5. 操作符重载 29 | 30 | 6. 多继承 31 | 32 | 7. 文档字符串(docstring) 33 | 34 | 8. 正则表达式 35 | 36 | 迭代一个数组 37 | ------------ 38 | 39 | Python 的for语句并不和C语言的一样,更像其他语言的foreach. 如果你需要在迭代中使用索引,标准的做法是:: 40 | 41 | array = [1, 2, 3, 4, 5] # or whatever 42 | 43 | for i in range(len(array)): 44 | # Do something with 'i'. 45 | 46 | 这是相当笨拙的,更干净简洁的做法是:: 47 | 48 | array = [1, 2, 3, 4, 5] # or whatever 49 | 50 | for i, e in enumerate(array): 51 | # Do something with index 'i' and its corresponding element 'e'. 52 | 53 | 打断无限循环 54 | ------------ 55 | 56 | Python并不像C语言有"do/while"语句,它只有一个while循环语句和for循环语句,有时你并不会提前知道什么时候循环会结束,或者你需要打破循环。一个不能再普通的例子就是正在按行迭代一个文件内容:: 57 | 58 | file = open("some_filename", "r") 59 | 60 | while 1: # infinite loop 61 | line = file.readline() 62 | if not line: # 'readline()' returns None at end of file. 63 | break 64 | 65 | # Process the line. 66 | 67 | 这样做并不聪明,但是足够规范。对于文件来说还有一个更漂亮的做法:: 68 | 69 | file = open("some_filename", "r") 70 | 71 | for line in file: 72 | # Process the line. 73 | 74 | 注意Python中也有一个continue语句(就像C中的)来跳过本次循环进入下一次循环。 75 | 76 | 注意内置函数 :meth:`file` 跟 :meth:`open` 一样,而且现在更流行了(因为对象的构造器应该和这个对象一个名字) 77 | 78 | 序列乘法 79 | -------- 80 | 81 | 在Python中,链表和字符串都是序列,它们有很多一样的操作(比如 :meth:`len`).一个不太明显的惯用手法是序列乘法.想要得到一个包含100个0的链表,你可以这么做:: 82 | 83 | zeroes = [0] * 100 84 | 85 | 类似地,可以这样做来获取一个包含100个空格的字符串:: 86 | 87 | spaces = 100 * " " 88 | 89 | 这很方便。 90 | 91 | xrange 92 | ------ 93 | 94 | 有的时候你想要生成一个长链表但是并不想把它立刻存在内存中。比如,你想要迭代1到1,000,000,000,但是你并不想把这些数都存在内存中。这样你就不会想用 :meth:`range` .取而代之你应该用 :meth:`xrange` ,它是 :meth:`range` 的一个延迟加载的版本(lazy version), 也就是说它只会在需要的时候生成那个数。所以你可以这么写:: 95 | 96 | for i in xrange(1000000000): 97 | # do something with i... 98 | 99 | 而且,内存使用会很平稳 100 | 101 | "Print to"语法 102 | -------------- 103 | 104 | 最近,">>" 操作符被重载了, 这样你就可以像下面那样在print语句中使用它了:: 105 | 106 | print >> sys.stderr, "this is an error message" 107 | 108 | >>右边应该是一个文件对象。 109 | 110 | 译者注例子(Python2.7) :: 111 | 112 | print >> file("myfile", "w"), "hello world" 113 | 114 | 异常类 115 | ------ 116 | 117 | 以前Python中的异常仅仅是简单的字符串。现在不同了,因为类有了很多新的进步。特别是,你可以为异常定义子类,可以选择性的捕捉一些异常或者捕捉它们的超类。异常类一般不复杂.一个典型的异常类看起来是这样的:: 118 | 119 | class MyException: 120 | def __init__(self, value): 121 | self.value = value 122 | def __str__(self): 123 | return `self.value` 124 | 125 | 这样使用:: 126 | 127 | try: 128 | do_stuff() 129 | if something_bad_has_happened(): 130 | raise MyException, "something bad happened" 131 | except MyException, e: 132 | print "My exception occurred, value: ", e.value 133 | 134 | 列表生成式 135 | ---------- 136 | 137 | 这是Python中全新的一个特征,来源于函数式编程语言Haskell(很酷的编程语言,顺便告诉你,你应该看看haskell) 138 | 139 | 其思想是:有时你想要为具有某些特征的对象做一个链表,比如你想要为0到20的偶数做一个链表:: 140 | 141 | results = [] 142 | for i in range(20): 143 | if i % 2 == 0: 144 | results.append(i) 145 | 146 | ``results`` 里面就是结果:``[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]`` (没有20,因为range(20)是从0到19).但是同样的事情你可以用列表生成式来做地更简洁些:: 147 | 148 | results = [x for x in range(20) if x % 2 == 0] 149 | 150 | 列表生成式是循环的语法糖.你可以做些更复杂的:: 151 | 152 | results = [(x, y) 153 | for x in range(10) 154 | for y in range(10) 155 | if x + y == 5 156 | if x > y] 157 | 158 | 结果 ``results`` 是 ``[(3, 2), (4, 1), (5, 0)]`` . 所以你可以在方括号中写任意多个for和if语句(可能更多,详细参见文档), 你可以用列表生成式来实现快速排序算法:: 159 | 160 | def quicksort(lst): 161 | if len(lst) == 0: 162 | return [] 163 | else: 164 | return quicksort([x for x in lst[1:] if x < lst[0]]) + [lst[0]] + \ 165 | quicksort([x for x in lst[1:] if x >= lst[0]]) 166 | 167 | 优美吗? :-) 168 | 169 | 函数式编程 170 | ---------- 171 | 172 | Python实现了很多平常只出现在函数式编程语言(像lisp和ML)中的函数和特性。 173 | 174 | 1. :meth:`map` :meth:`reduce` :meth:`filter` 函数 175 | 176 | :meth:`map` 需要一个函数和几个序列做参数(通常一个),然后对于序列的每个元素作为函数的参数,所有的返回值产生一个新的序列作为map的返回值。比如你想要把一个字符串链表转换成数字链表:: 177 | 178 | lst = ["1", "2", "3", "4", "5"] 179 | nums = map(string.atoi, lst) # [1, 2, 3, 4, 5] 180 | 181 | (译者注:Py2.7中使用 ``map(int, lst)`` ) 182 | 183 | 你可以对两个参数的函数使用map:: 184 | 185 | def add(x, y): 186 | return x + y 187 | 188 | lst1 = [1, 2, 3, 4, 5] 189 | lst2 = [6, 7, 8, 9, 10] 190 | lst_sum = map(add, lst1, lst2) 191 | 192 | # lst_sum == [7, 9, 11, 13, 15] 193 | 194 | (译者注:这个函数可以有任意多参数,map的后面的参数要跟相应多的序列即可) 195 | 196 | 你可以使用 :meth:`reduce` 来把一个序列减少成一个值。第一个参数是函数,这个函数首先作用于序列的第一个和第二个元素,然后用返回的值继续和序列的第三个元素执行这个函数。。。直到剩下一个值,作为reduce的返回值.比如,你可以这么来求0到9的和:: 197 | 198 | reduce(lambda x, y: x+y, range(10)) 199 | 200 | (译者注:这里为了讲解,一般推荐直接用函数 :meth:`sum` ) 201 | 202 | 你可以使用 :meth:`filter` 来生成一个序列的子集。比如,获取0到100的所有奇数:: 203 | 204 | nums = range(0,101) # [0, 1, ... 100] 205 | 206 | def is_odd(x): 207 | return x % 2 == 1 208 | 209 | odd_nums = filter(is_odd, nums) # [1, 3, 5, ... 99] 210 | 211 | 2. ``lambda`` 关键字 212 | 213 | lambda 语句声明了一个匿名的函数,很多时候我们在reduce,map等函数中使用的函数只使用了一次。这些函数可以被简洁地声明为匿名函数:: 214 | 215 | lst1 = [1, 2, 3, 4, 5] 216 | lst2 = [6, 7, 8, 9, 10] 217 | lst_elementwise_sum = map(lambda x, y: x + y, lst1, lst2) 218 | lst1_sum = reduce(lambda x, y: x + y, lst1) 219 | nums = range(101) 220 | odd_nums = filter(lambda x: x % 2 == 1, nums) 221 | 222 | 注意,你仍然可以在lambda函数中使用这个匿名函数外定义的变量。这叫做“词法范围”(lexical scoping),在Python 2.2时官方性地给出了介绍.就像这样:: 223 | 224 | a = 1 225 | add_a = lambda x: x + a 226 | b = add_a(10) # b == 11 227 | 228 | lambda中的 ``a`` 是在上一行定义的。如果你认为这是显然的,很好!事实证明很多Python开发者花了很多时间来搞明白它。 229 | 230 | 关于lambda你可以参见下 lisp 或 scheme的文档。 231 | 232 | 3. ``apply`` 函数 233 | 234 | 在python中函数是对象,你可以像操作数和字符串一样操作它们(把它们存在变量中等等),有时你有一个函数对象,你想把程序中生成的一个序列作为参数传给这个函数:: 235 | 236 | # Sorry about the long variable names ;-) 237 | 238 | args = function_returning_list_of_numbers() 239 | f = function_returning_a_function_which_operates_on_a_list_of_numbers() 240 | 241 | # You want to do f(arg[0], arg[1], ...) but you don't know how many 242 | # arguments are in 'args'. For this you have to use 'apply': 243 | 244 | result = apply(f, args) 245 | 246 | # A trivial example: 247 | args = [1, 1] 248 | two = apply(lambda x, y: x + y, args) # == 2 249 | 250 | 译者注:之所以我们不经常用 :meth:`apply` ,一般可以这么做:: 251 | 252 | args = [1, 1] 253 | foo = lambda x, y: x+y 254 | foo(*args) 255 | 256 | 生成器和迭代器 257 | -------------- 258 | 259 | 这是一个高级的(并且很cool)的话题,我们这里没有地方展开说了。如果你关心这个,去看Python文档吧.-_- 260 | 261 | PEPS 262 | ---- 263 | 264 | Python社区非常活跃,"comp.lang.python"小组有很多关于Python应该添加什么新特征的讨论。 265 | 266 | 所有PEP,见这里:http://www.python.org/dev/peps/ 267 | -------------------------------------------------------------------------------- /docs/python-magic-methods-guide.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-07-05 22:34:00 2 | 3 | ====================== 4 | (译)Python魔法方法指南 5 | ====================== 6 | 7 | :原文: http://www.rafekettler.com/magicmethods.html 8 | :原作者: Rafe Kettler 9 | :翻译: hit9 10 | :原版(英文版) Repo: https://github.com/RafeKettler/magicmethods 11 | 12 | .. Contents:: 13 | 14 | 简介 15 | ---- 16 | 17 | 本指南归纳于我的几个月的博客,主题是 **魔法方法** 。 18 | 19 | 什么是魔法方法呢?它们在面向对象的Python的处处皆是。它们是一些可以让你对类添加“魔法”的特殊方法。 20 | 它们经常是两个下划线包围来命名的(比如 `__init__` , `__lt__` )。但是现在没有很好的文档来解释它们。 21 | 所有的魔法方法都会在Python的官方文档中找到,但是它们组织松散。而且很少会有示例(有的是无聊的语法描述, 22 | 语言参考)。 23 | 24 | 所以,为了修复我感知的Python文档的缺陷,我开始提供更为通俗的,有示例支持的Python魔法方法指南。我一开始 25 | 写了一些博文,现在我把这些博文总起来成为一篇指南。 26 | 27 | 希望你喜欢这篇指南,一篇友好,通俗易懂的Python魔法方法指南! 28 | 29 | 构造方法 30 | -------- 31 | 32 | 我们最为熟知的基本的魔法方法就是 `__init__` ,我们可以用它来指明一个对象初始化的行为。然而,当我们调用 33 | `x = SomeClass()` 的时候, `__init__` 并不是第一个被调用的方法。事实上,第一个被调用的是 `__new__` ,这个 34 | 方法才真正地创建了实例。当这个对象的生命周期结束的时候, `__del__` 会被调用。让我们近一步理解这三个方法: 35 | 36 | - `__new__(cls,[...)` 37 | 38 | `__new__` 是对象实例化时第一个调用的方法,它只取下 `cls` 参数,并把其他参数传给 `__init__` 。 `__new__` 39 | 很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。我不打算深入讨论 40 | `__new__` ,因为它并不是很有用, `Python文档 `_ 中 41 | 有详细的说明。 42 | 43 | - `__init__(self,[...])` 44 | 45 | 类的初始化方法。它获取任何传给构造器的参数(比如我们调用 `x = SomeClass(10, 'foo')` , `__init__` 就会接到参数 46 | `10` 和 `'foo'` 。 `__init__` 在Python的类定义中用的最多。 47 | 48 | - `__del__(self)` 49 | 50 | `__new__` 和 `__init__` 是对象的构造器, `__del__` 是对象的销毁器。它并非实现了语句 `del x` (因此该语句不等同于 `x.__del__()`)。而是定义了当对象被垃圾回收时的行为。 51 | 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 `socket` 对象、文件对象。但是需要注意的是,当Python解释器退出但对象仍然存活的时候, `__del__` 并不会 52 | 执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。 53 | 54 | 这里有个 `__init__` 和 `__del__` 的例子:: 55 | 56 | from os.path import join 57 | 58 | class FileObject: 59 | '''文件对象的装饰类,用来保证文件被删除时能够正确关闭。''' 60 | 61 | def __init__(self, filepath='~', filename='sample.txt'): 62 | # 使用读写模式打开filepath中的filename文件 63 | self.file = open(join(filepath, filename), 'r+') 64 | 65 | def __del__(self): 66 | self.file.close() 67 | del self.file 68 | 69 | 70 | 操作符 71 | ------ 72 | 73 | 使用Python魔法方法的一个巨大优势就是可以构建一个拥有Python内置类型行为的对象。这意味着你可以避免使用非标准的、丑陋的方式来表达简单的操作。 74 | 在一些语言中,这样做很常见:: 75 | 76 | if instance.equals(other_instance): 77 | # do something 78 | 79 | 你当然可以在Python也这么做,但是这样做让代码变得冗长而混乱。不同的类库可能对同一种比较操作采用不同的方法名称,这让使用者需要做很多没有必要的工作。运用魔法方法的魔力,我们可以定义方法 `__eq__` :: 80 | 81 | if instance == other_instance: 82 | # do something 83 | 84 | 这是魔法力量的一部分,这样我们就可以创建一个像内建类型那样的对象了! 85 | 86 | 比较操作符 87 | '''''''''' 88 | 89 | Python包含了一系列的魔法方法,用于实现对象之间直接比较,而不需要采用方法调用。同样也可以重载Python默认的比较方法,改变它们的行为。下面是这些方法的列表: 90 | 91 | - `__cmp__(self, other)` 92 | 93 | `__cmp__` 是所有比较魔法方法中最基础的一个,它实际上定义了所有比较操作符的行为(<,==,!=,等等),但是它可能不能按照你需要的方式工作(例如,判断一个实例和另一个实例是否相等采用一套标准,而与判断一个实例是否大于另一实例采用另一套)。 `__cmp__` 应该在 `self < other` 时返回一个负整数,在 `self == other` 时返回0,在 `self > other` 时返回正整数。最好只定义你所需要的比较形式,而不是一次定义全部。 如果你需要实现所有的比较形式,而且它们的判断标准类似,那么 `__cmp__` 是一个很好的方法,可以减少代码重复,让代码更简洁。 94 | 95 | 96 | - `__eq__`(self, other)` 97 | 98 | 定义等于操作符(==)的行为。 99 | 100 | - `__ne__(self, other)` 101 | 102 | 定义不等于操作符(!=)的行为。 103 | 104 | - `__lt__(self, other)` 105 | 106 | 定义小于操作符(<)的行为。 107 | 108 | - `__gt__(self, other)` 109 | 110 | 定义大于操作符(>)的行为。 111 | 112 | - `__le__(self, other)` 113 | 114 | 定义小于等于操作符(<)的行为。 115 | 116 | - `__ge__(self, other)` 117 | 118 | 定义大于等于操作符(>)的行为。 119 | 120 | 举个例子,假如我们想用一个类来存储单词。我们可能想按照字典序(字母顺序)来比较单词,字符串的默认比较行为就是这样。我们可能也想按照其他规则来比较字符串,像是长度,或者音节的数量。在这个例子中,我们使用长度作为比较标准,下面是一种实现:: 121 | 122 | class Word(str): 123 | '''单词类,按照单词长度来定义比较行为''' 124 | 125 | def __new__(cls, word): 126 | # 注意,我们只能使用 __new__ ,因为str是不可变类型 127 | # 所以我们必须提前初始化它(在实例创建时) 128 | if ' ' in word: 129 | print "Value contains spaces. Truncating to first space." 130 | word = word[:word.index(' ')] 131 | # Word现在包含第一个空格前的所有字母 132 | return str.__new__(cls, word) 133 | 134 | def __gt__(self, other): 135 | return len(self) > len(other) 136 | def __lt__(self, other): 137 | return len(self) < len(other) 138 | def __ge__(self, other): 139 | return len(self) >= len(other) 140 | def __le__(self, other): 141 | return len(self) <= len(other) 142 | 143 | 144 | 现在我们可以创建两个 `Word` 对象( `Word('foo')` 和 `Word('bar')`)然后根据长度来比较它们。注意我们没有定义 `__eq__` 和 `__ne__` ,这是因为有时候它们会导致奇怪的结果(很明显, `Word('foo') == Word('bar')` 得到的结果会是true)。根据长度测试是否相等毫无意义,所以我们使用 `str` 的实现来比较相等。 145 | 146 | 从上面可以看到,不需要实现所有的比较魔法方法,就可以使用丰富的比较操作。标准库还在 `functools` 模块中提供了一个类装饰器,只要我们定义 `__eq__` 和另外一个操作符( `__gt__`, `__lt__` 等),它就可以帮我们实现比较方法。这个特性只在 Python 2.7 中可用。当它可用时,它能帮助我们节省大量的时间和精力。要使用它,只需要它 `@total_ordering` 放在类的定义之上就可以了 147 | 148 | 数值操作符 149 | '''''''''' 150 | 151 | 就像你可以使用比较操作符来比较类的实例,你也可以定义数值操作符的行为。固定好你的安全带,这样的操作符真的有很多。看在组织的份上,我把它们分成了五类:一元操作符,常见算数操作符,反射算数操作符(后面会涉及更多),增强赋值操作符,和类型转换操作符。 152 | 153 | 154 | 一元操作符 155 | ========== 156 | 157 | 一元操作符只有一个操作符。 158 | 159 | - `__pos__(self)` 160 | 161 | 实现取正操作,例如 `+some_object`。 162 | 163 | - `__neg__(self)` 164 | 165 | 实现取负操作,例如 `-some_object`。 166 | 167 | - `__abs__(self)` 168 | 169 | 实现内建绝对值函数 `abs()` 操作。 170 | 171 | - `__invert__(self)` 172 | 173 | 实现取反操作符 `~`。 174 | 175 | - `__round__(self, n)` 176 | 177 | 实现内建函数 `round()` ,n 是近似小数点的位数。 178 | 179 | - `__floor__(self)` 180 | 181 | 实现 `math.floor()` 函数,即向下取整。 182 | 183 | - `__ceil__(self)` 184 | 185 | 实现 `math.ceil()` 函数,即向上取整。 186 | 187 | - `__trunc__(self)` 188 | 189 | 实现 `math.trunc()` 函数,即距离零最近的整数。 190 | 191 | 192 | 常见算数操作符 193 | =============== 194 | 195 | 现在,我们来看看常见的二元操作符(和一些函数),像+,-,\*之类的,它们很容易从字面意思理解。 196 | 197 | 198 | - `__add__(self, other)` 199 | 200 | 实现加法操作。 201 | 202 | - `__sub__(self, other)` 203 | 204 | 实现减法操作。 205 | 206 | - `__mul__(self, other)` 207 | 208 | 实现乘法操作。 209 | 210 | - `__floordiv__(self, other)` 211 | 212 | 实现使用 `//` 操作符的整数除法。 213 | 214 | - `__div__(self, other)` 215 | 216 | 实现使用 `/` 操作符的除法。 217 | 218 | - `__truediv__(self, other)` 219 | 220 | 实现 `_true_` 除法,这个函数只有使用 `from __future__ import division` 时才有作用。 221 | 222 | - `__mod__(self, other)` 223 | 224 | 实现 `%` 取余操作。 225 | 226 | - `__divmod__(self, other)` 227 | 228 | 实现 `divmod` 内建函数。 229 | 230 | - `__pow__` 231 | 232 | 实现 `**` 操作符。 233 | 234 | - `__lshift__(self, other)` 235 | 236 | 实现左移位运算符 `<<` 。 237 | 238 | - `__rshift__(self, other)` 239 | 240 | 实现右移位运算符 `>>` 。 241 | 242 | 243 | - `__and__(self, other)` 244 | 245 | 实现按位与运算符 `&` 。 246 | 247 | - `__or__(self, other)` 248 | 249 | 实现按位或运算符 `|` 。 250 | 251 | - `__xor__(self, other)` 252 | 253 | 实现按位异或运算符 `^` 。 254 | 255 | 256 | 反射算数运算符 257 | =============== 258 | 259 | 还记得刚才我说会谈到反射运算符吗?可能你会觉得它是什么高端霸气上档次的概念,其实这东西挺简单的,下面举个例子:: 260 | 261 | some_object + other 262 | 263 | 这是“常见”的加法,反射是一样的意思,只不过是运算符交换了一下位置:: 264 | 265 | other + some_object 266 | 267 | 所有反射运算符魔法方法和它们的常见版本做的工作相同,只不过是处理交换连个操作数之后的情况。绝大多数情况下,反射运算和正常顺序产生的结果是相同的,所以很可能你定义 `__radd__` 时只是调用一下 `__add__`。注意一点,操作符左侧的对象(也就是上面的 `other` )一定不要定义(或者产生 `NotImplemented` 异常) 操作符的非反射版本。例如,在上面的例子中,只有当 `other` 没有定义 `__add__` 时 `some_object.__radd__` 才会被调用。 268 | 269 | 270 | - `__radd__(self, other)` 271 | 272 | 实现反射加法操作。 273 | 274 | - `__rsub__(self, other)` 275 | 276 | 实现反射减法操作。 277 | 278 | - `__rmul__(self, other)` 279 | 280 | 实现反射乘法操作。 281 | 282 | - `__rfloordiv__(self, other)` 283 | 284 | 实现使用 `//` 操作符的整数反射除法。 285 | 286 | - `__rdiv__(self, other)` 287 | 288 | 实现使用 `/` 操作符的反射除法。 289 | 290 | - `__rtruediv__(self, other)` 291 | 292 | 实现 `_true_` 反射除法,这个函数只有使用 `from __future__ import division` 时才有作用。 293 | 294 | - `__rmod__(self, other)` 295 | 296 | 实现 `%` 反射取余操作符。 297 | 298 | - `__rdivmod__(self, other)` 299 | 300 | 实现调用 `divmod(other, self)` 时 `divmod` 内建函数的操作。 301 | 302 | - `__rpow__` 303 | 304 | 实现 `**` 反射操作符。 305 | 306 | - `__rlshift__(self, other)` 307 | 308 | 实现反射左移位运算符 `<<` 的作用。 309 | 310 | - `__rshift__(self, other)` 311 | 312 | 实现反射右移位运算符 `>>` 的作用。 313 | 314 | - `__rand__(self, other)` 315 | 316 | 实现反射按位与运算符 `&` 。 317 | 318 | - `__ror__(self, other)` 319 | 320 | 实现反射按位或运算符 `|` 。 321 | 322 | - `__rxor__(self, other)` 323 | 324 | 实现反射按位异或运算符 `^` 。 325 | 326 | 327 | 增强赋值运算符 328 | =============== 329 | 330 | Python同样提供了大量的魔法方法,可以用来自定义增强赋值操作的行为。或许你已经了解增强赋值,它融合了“常见”的操作符和赋值操作,如果你还是没听明白,看下面的例子:: 331 | 332 | x = 5 333 | x += 1 # 也就是 x = x + 1 334 | 335 | 这些方法都应该返回左侧操作数应该被赋予的值(例如, `a += b` `__iadd__` 也许会返回 `a + b` ,这个结果会被赋给 a ),下面是方法列表: 336 | 337 | - `__iadd__(self, other)` 338 | 339 | 实现加法赋值操作。 340 | 341 | - `__isub__(self, other)` 342 | 343 | 实现减法赋值操作。 344 | 345 | - `__imul__(self, other)` 346 | 347 | 实现乘法赋值操作。 348 | 349 | - `__ifloordiv__(self, other)` 350 | 351 | 实现使用 `//=` 操作符的整数除法赋值操作。 352 | 353 | - `__idiv__(self, other)` 354 | 355 | 实现使用 `/=` 操作符的除法赋值操作。 356 | 357 | - `__itruediv__(self, other)` 358 | 359 | 实现 `_true_` 除法赋值操作,这个函数只有使用 `from __future__ import division` 时才有作用。 360 | 361 | - `__imod__(self, other)` 362 | 363 | 实现 `%=` 取余赋值操作。 364 | 365 | - `__ipow__` 366 | 367 | 实现 `**=` 操作。 368 | 369 | - `__ilshift__(self, other)` 370 | 371 | 实现左移位赋值运算符 `<<=` 。 372 | 373 | - `__irshift__(self, other)` 374 | 375 | 实现右移位赋值运算符 `>>=` 。 376 | 377 | - `__iand__(self, other)` 378 | 379 | 实现按位与运算符 `&=` 。 380 | 381 | - `__ior__(self, other)` 382 | 383 | 实现按位或赋值运算符 `|` 。 384 | 385 | - `__ixor__(self, other)` 386 | 387 | 实现按位异或赋值运算符 `^=` 。 388 | 389 | 390 | 类型转换操作符 391 | =============== 392 | 393 | Python也有一系列的魔法方法用于实现类似 `float()` 的内建类型转换函数的操作。它们是这些: 394 | 395 | - `__int__(self)` 396 | 397 | 实现到int的类型转换。 398 | 399 | - `__long__(self)` 400 | 401 | 实现到long的类型转换。 402 | 403 | - `__float__(self)` 404 | 405 | 实现到float的类型转换。 406 | 407 | - `__complex__(self)` 408 | 409 | 实现到complex的类型转换。 410 | 411 | - `__oct__(self)` 412 | 413 | 实现到八进制数的类型转换。 414 | 415 | - `__hex__(self)` 416 | 417 | 实现到十六进制数的类型转换。 418 | 419 | - `__index__(self)` 420 | 421 | 实现当对象用于切片表达式时到一个整数的类型转换。如果你定义了一个可能会用于切片操作的数值类型,你应该定义 `__index__`。 422 | 423 | - `__trunc__(self)` 424 | 425 | 当调用 `math.trunc(self)` 时调用该方法, `__trunc__` 应该返回 `self` 截取到一个整数类型(通常是long类型)的值。 426 | 427 | - `__coerce__(self)` 428 | 429 | 该方法用于实现混合模式算数运算,如果不能进行类型转换, `__coerce__` 应该返回 `None` 。反之,它应该返回一个二元组 `self` 和 `other` ,这两者均已被转换成相同的类型。 430 | 431 | 432 | 类的表示 433 | --------- 434 | 435 | 使用字符串来表示类是一个相当有用的特性。在Python中有一些内建方法可以返回类的表示,相对应的,也有一系列魔法方法可以用来自定义在使用这些内建函数时类的行为。 436 | 437 | - `__str__(self)` 438 | 439 | 定义对类的实例调用 `str()` 时的行为。 440 | 441 | - `__repr__(self)` 442 | 443 | 定义对类的实例调用 `repr()` 时的行为。 `str()` 和 `repr()` 最主要的差别在于“目标用户”。 `repr()` 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 `str()` 则产生人类可读的输出。 444 | 445 | - `__unicode__(self)` 446 | 447 | 定义对类的实例调用 `unicode()` 时的行为。 `unicode()` 和 `str()` 很像,只是它返回unicode字符串。注意,如果调用者试图调用 `str()` 而你的类只实现了 `__unicode__()` ,那么类将不能正常工作。所有你应该总是定义 `__str__()` ,以防有些人没有闲情雅致来使用unicode。 448 | 449 | - `__format__(self)` 450 | 451 | 定义当类的实例用于新式字符串格式化时的行为,例如, `"Hello, 0:abc!".format(a)` 会导致调用 `a.__format__("abc")` 。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。 452 | 453 | - `__hash__(self)` 454 | 455 | 定义对类的实例调用 `hash()` 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 `__eq__` ,并且遵守如下的规则: `a == b` 意味着 `hash(a) == hash(b)`。 456 | 457 | - `__nonzero__(self)` 458 | 459 | 定义对类的实例调用 `bool()` 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。 460 | 461 | - `__dir__(self)` 462 | 463 | 定义对类的实例调用 `dir()` 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现 `__dir__` 。但是如果你重定义了 `__getattr__` 或者 `__getattribute__` (下个部分会介绍),乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。 464 | 465 | 466 | 467 | 到这里,我们基本上已经结束了魔法方法指南中无聊并且例子匮乏的部分。既然我们已经介绍了较为基础的魔法方法,是时候涉及更高级的内容了。 468 | 469 | 470 | 471 | 访问控制 472 | --------- 473 | 474 | 很多从其他语言转向Python的人都抱怨Python的类缺少真正意义上的封装(即没办法定义私有属性然后使用公有的getter和setter)。然而事实并非如此。实际上Python不是通过显式定义的字段和方法修改器,而是通过魔法方法实现了一系列的封装。 475 | 476 | - `__getattr__(self, name)` 477 | 478 | 当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。 479 | 480 | - `__setattr__(self, name, value)` 481 | 482 | 和 `__getattr__` 不同, `__setattr__` 可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 `__setattr__` ,这个列表最后的例子中会有所展示。 483 | 484 | - `__delattr__(self, name)` 485 | 486 | 这个魔法方法和 `__setattr__` 几乎相同,只不过它是用于处理删除属性时的行为。和 `_setattr__` 一样,使用它时也需要多加小心,防止产生无限递归(在 `__delattr__` 的实现中调用 `del self.name` 会导致无限递归)。 487 | 488 | - `__getattribute__(self, name)` 489 | 490 | ` __getattribute__` 看起来和上面那些方法很合得来,但是最好不要使用它。 `__getattribute__` 只能用于新式类。在最新版的Python中所有的类都是新式类,在老版Python中你可以通过继承 `object` 来创建新式类。 `__getattribute__` 允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题(通过调用基类的 `__getattribute__` 来避免)。 `__getattribute__` 基本上可以替代 `__getattr__` 。只有当它被实现,并且显式地被调用,或者产生 `AttributeError` 时它才被使用。 这个魔法方法可以被使用(毕竟,选择权在你自己),我不推荐你使用它,因为它的使用范围相对有限(通常我们想要在赋值时进行特殊操作,而不是取值时),而且实现这个方法很容易出现Bug。 491 | 492 | 493 | 自定义这些控制属性访问的魔法方法很容易导致问题,考虑下面这个例子:: 494 | 495 | def __setattr__(self, name. value): 496 | self.name = value 497 | # 因为每次属性幅值都要调用 __setattr__(),所以这里的实现会导致递归 498 | # 这里的调用实际上是 self.__setattr('name', value)。因为这个方法一直 499 | # 在调用自己,因此递归将持续进行,直到程序崩溃 500 | 501 | def __setattr__(self, name, value): 502 | self.__dict__[name] = value # 使用 __dict__ 进行赋值 503 | # 定义自定义行为 504 | 505 | 再次重申,Python的魔法方法十分强大,能力越强责任越大,了解如何正确的使用魔法方法更加重要。 506 | 507 | 到这里,我们对Python中自定义属性存取控制有了什么样的印象?它并不适合轻度的使用。实际上,它有些过分强大,而且违反直觉。然而它之所以存在,是因为一个更大的原则:Python不指望让杜绝坏事发生,而是想办法让做坏事变得困难。自由是至高无上的权利,你真的可以随心所欲。下面的例子展示了实际应用中某些特殊的属性访问方法(注意我们之所以使用 `super` 是因为不是所有的类都有 `__dict__` 属性):: 508 | 509 | class AccessCounter(object): 510 | ''' 一个包含了一个值并且实现了访问计数器的类 511 | 每次值的变化都会导致计数器自增''' 512 | 513 | def __init__(self, val): 514 | super(AccessCounter, self).__setattr__('counter', 0) 515 | super(AccessCounter, self).__setattr__('value', val) 516 | 517 | def __setattr__(self, name, value): 518 | if name == 'value': 519 | super(AccessCounter, self).__setattr_('counter', self.counter + 1) 520 | # 使计数器自增变成不可避免 521 | # 如果你想阻止其他属性的赋值行为 522 | # 产生 AttributeError(name) 就可以了 523 | super(AccessCounter, self).__setattr__(name, value) 524 | 525 | def __delattr__(self, name): 526 | if name == 'value': 527 | super(AccessCounter, self).__setattr('counter', self.counter + 1) 528 | super(AccessCounter, self).__delattr(name) 529 | 530 | 531 | 自定义序列 532 | ----------- 533 | 534 | 有许多办法可以让你的Python类表现得像是内建序列类型(字典,元组,列表,字符串等)。这些魔法方式是目前为止我最喜欢的。它们给了你难以置信的控制能力,可以让你的类与一系列的全局函数完美结合。在了解激动人心的内容之前,首先你需要掌握一些预备知识。 535 | 536 | 预备知识 537 | ''''''''' 538 | 539 | 既然讲到创建自己的序列类型,就不得不说一说协议了。协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。 540 | 541 | 为什么我们要讲协议?因为在Python中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义 `__len__` 和 `__getitem__` (后面会具体说明)。可变容器的协议除了上面提到的两个方法之外,还需要定义 `__setitem__` 和 `__delitem__` 。最后,如果你想让你的对象可以迭代,你需要定义 `__iter__` ,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 `__iter__` (返回它自己)和 `next` 方法。 542 | 543 | 容器背后的魔法方法 544 | ''''''''''''''''''' 545 | - `__len__(self)` 546 | 547 | 返回容器的长度,可变和不可变类型都需要实现。 548 | 549 | - `__getitem__(self, key)` 550 | 551 | 定义对容器中某一项使用 `self[key]` 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 `TypeError` 异常,同时在没有与键值相匹配的内容时产生 `KeyError` 异常。 552 | 553 | - `__setitem__(self, key)` 554 | 555 | 定义对容器中某一项使用 `self[key]` 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 `KeyError` 和 `TypeError` 异常。 556 | 557 | - `__iter__(self, key)` 558 | 559 | 它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 `iter()` 函数调用,以及在类似 `for x in container:` 的循环中被调用。迭代器是他们自己的对象,需要定义 `__iter__` 方法并在其中返回自己。 560 | 561 | - `__reversed__(self)` 562 | 563 | 定义了对容器使用 `reversed()` 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法, 564 | 565 | - `__contains__(self, item)` 566 | 567 | `__contains__` 定义了使用 `in` 和 `not in` 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 `__contains__` 没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 `True` 。 568 | 569 | - `__missing__(self ,key)` 570 | 571 | `__missing__` 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 `d` , `"george"` 不是字典中的一个键,当试图访问 `d["george']` 时就会调用 `d.__missing__("george")` )。 572 | 573 | 一个例子 574 | ''''''''' 575 | 576 | 让我们来看一个实现了一些函数式结构的列表,可能在其他语言中这种结构更常见(例如Haskell):: 577 | 578 | class FunctionalList: 579 | '''一个列表的封装类,实现了一些额外的函数式 580 | 方法,例如head, tail, init, last, drop和take。''' 581 | 582 | def __init__(self, values=None): 583 | if values is None: 584 | self.values = [] 585 | else: 586 | self.values = values 587 | 588 | def __len__(self): 589 | return len(self.values) 590 | 591 | def __getitem__(self, key): 592 | # 如果键的类型或值不合法,列表会返回异常 593 | return self.values[key] 594 | 595 | def __setitem__(self, key, value): 596 | self.values[key] = value 597 | 598 | def __delitem__(self, key): 599 | del self.values[key] 600 | 601 | def __iter__(self): 602 | return iter(self.values) 603 | 604 | def __reversed__(self): 605 | return reversed(self.values) 606 | 607 | def append(self, value): 608 | self.values.append(value) 609 | 610 | def head(self): 611 | # 取得第一个元素 612 | return self.values[0] 613 | 614 | def tail(self): 615 | # 取得除第一个元素外的所有元素 616 | return self.valuse[1:] 617 | 618 | def init(self): 619 | # 取得除最后一个元素外的所有元素 620 | return self.values[:-1] 621 | 622 | def last(self): 623 | # 取得最后一个元素 624 | return self.values[-1] 625 | 626 | def drop(self, n): 627 | # 取得除前n个元素外的所有元素 628 | return self.values[n:] 629 | 630 | def take(self, n): 631 | # 取得前n个元素 632 | return self.values[:n] 633 | 634 | 635 | 就是这些,一个(微不足道的)有用的例子,向你展示了如何实现自己的序列。当然啦,自定义序列有更大的用处,而且绝大部分都在标准库中实现了(Python是自带电池的,记得吗?),像 `Counter` , `OrderedDict` 和 `NamedTuple` 。 636 | 637 | 反射 638 | ------ 639 | 640 | 你可以通过定义魔法方法来控制用于反射的内建函数 `isinstance` 和 `issubclass` 的行为。下面是对应的魔法方法: 641 | 642 | - `__instancecheck__(self, instance)` 643 | 644 | 检查一个实例是否是你定义的类的一个实例(例如 `isinstance(instance, class)` )。 645 | 646 | - `__subclasscheck__(self, subclass)` 647 | 648 | 检查一个类是否是你定义的类的子类(例如 `issubclass(subclass, class)` )。 649 | 650 | 651 | 这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿(也因为你阅读了这本指南!) 652 | 653 | 654 | 抽象基类 655 | --------- 656 | 657 | 请参考 http://docs.python.org/2/library/abc.html. 658 | 659 | 660 | 可调用的对象 661 | -------------- 662 | 663 | 你可能已经知道了,在Python中,函数是一等的对象。这意味着它们可以像其他任何对象一样被传递到函数和方法中,这是一个十分强大的特性。 664 | 665 | Python中一个特殊的魔法方法允许你自己类的对象表现得像是函数,然后你就可以“调用”它们,把它们传递到使用函数做参数的函数中,等等等等。这是另一个强大而且方便的特性,让使用Python编程变得更加幸福。 666 | 667 | - `__call__(self, [args...])` 668 | 669 | 允许类的一个实例像函数那样被调用。本质上这代表了 `x()` 和 `x.__call__()` 是相同的。注意 `__call__` 可以有多个参数,这代表你可以像定义其他任何函数一样,定义 `__call__` ,喜欢用多少参数就用多少。 670 | 671 | `__call__` 在某些需要经常改变状态的类的实例中显得特别有用。“调用”这个实例来改变它的状态,是一种更加符合直觉,也更加优雅的方法。一个表示平面上实体的类是一个不错的例子:: 672 | 673 | class Entity: 674 | '''表示一个实体的类,调用它的实例 675 | 可以更新实体的位置''' 676 | 677 | def __init__(self, size, x, y): 678 | self.x, self.y = x, y 679 | self.size = size 680 | 681 | def __call__(self, x, y): 682 | '''改变实体的位置''' 683 | self.x, self.y = x, y 684 | 685 | 686 | 上下文管理器 687 | ------------- 688 | 689 | 在Python 2.5中引入了一个全新的关键词,随之而来的是一种新的代码复用方法—— `with` 声明。上下文管理的概念在Python中并不是全新引入的(之前它作为标准库的一部分实现),直到PEP 343被接受,它才成为一种一级的语言结构。可能你已经见过这种写法了:: 690 | 691 | with open('foo.txt') as bar: 692 | # 使用bar进行某些操作 693 | 694 | 695 | 当对象使用 `with` 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义: 696 | 697 | - `__enter__(self)` 698 | 699 | 定义使用 `with` 声明创建的语句块最开始上下文管理器应该做些什么。注意 `__enter__` 的返回值会赋给 `with` 声明的目标,也就是 `as` 之后的东西。 700 | 701 | - `__exit__(self, exception_type, exception_value, traceback)` 702 | 703 | 定义当 `with` 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, `exception_type` , `exception_value` 和 `traceback` 会是 `None` 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 `__exit__` 在完成工作之后返回 `True` 。如果你不想处理异常,那就让它发生吧。 704 | 705 | 706 | 707 | 对一些具有良好定义的且通用的设置和清理行为的类,`__enter__` 和 `__exit__` 会显得特别有用。你也可以使用这几个方法来创建通用的上下文管理器,用来包装其他对象。下面是一个例子:: 708 | 709 | class Closer: 710 | '''一个上下文管理器,可以在with语句中 711 | 使用close()自动关闭对象''' 712 | 713 | def __init__(self, obj): 714 | self.obj = obj 715 | 716 | def __enter__(self, obj): 717 | return self.obj # 绑定到目标 718 | 719 | def __exit__(self, exception_type, exception_value, traceback): 720 | try: 721 | self.obj.close() 722 | except AttributeError: # obj不是可关闭的 723 | print 'Not closable.' 724 | return True # 成功地处理了异常 725 | 726 | 727 | 这是一个 `Closer` 在实际使用中的例子,使用一个FTP连接来演示(一个可关闭的socket):: 728 | 729 | >>> from magicmethods import Closer 730 | >>> from ftplib import FTP 731 | >>> with Closer(FTP('ftp.somesite.com')) as conn: 732 | ... conn.dir() 733 | ... 734 | # 为了简单,省略了某些输出 735 | >>> conn.dir() 736 | # 很长的 AttributeError 信息,不能使用一个已关闭的连接 737 | >>> with Closer(int(5)) as i: 738 | ... i += 1 739 | ... 740 | Not closable. 741 | >>> i 742 | 6 743 | 744 | 看到我们的包装器是如何同时优雅地处理正确和不正确的调用了吗?这就是上下文管理器和魔法方法的力量。Python标准库包含一个 `contextlib` 模块,里面有一个上下文管理器 `contextlib.closing()` 基本上和我们的包装器完成的是同样的事情(但是没有包含任何当对象没有close()方法时的处理)。 745 | 746 | 创建描述符对象 747 | --------------- 748 | 749 | 描述符是一个类,当使用取值,赋值和删除 时它可以改变其他对象。描述符不是用来单独使用的,它们需要被一个拥有者类所包含。描述符可以用来创建面向对象数据库,以及创建某些属性之间互相依赖的类。描述符在表现具有不同单位的属性,或者需要计算的属性时显得特别有用(例如表现一个坐标系中的点的类,其中的距离原点的距离这种属性)。 750 | 751 | 要想成为一个描述符,一个类必须具有实现 `__get__` , `__set__` 和 `__delete__` 三个方法中至少一个。 752 | 753 | 让我们一起来看一看这些魔法方法: 754 | 755 | - `__get__(self, instance, owner)` 756 | 757 | 定义当试图取出描述符的值时的行为。 `instance` 是拥有者类的实例, `owner` 是拥有者类本身。 758 | 759 | - `__set__(self, instance, owner)` 760 | 761 | 定义当描述符的值改变时的行为。 `instance` 是拥有者类的实例, `value` 是要赋给描述符的值。 762 | 763 | - `__delete__(self, instance, owner)` 764 | 765 | 定义当描述符的值被删除时的行为。 `instance` 是拥有者类的实例 766 | 767 | 768 | 现在,来看一个描述符的有效应用:单位转换:: 769 | 770 | class Meter(object): 771 | '''米的描述符。''' 772 | 773 | def __init__(self, value=0.0): 774 | self.value = float(value) 775 | def __get__(self, instance, owner): 776 | return self.value 777 | def __set__(self, instance, owner): 778 | self.value = float(value) 779 | 780 | class Foot(object): 781 | '''英尺的描述符。''' 782 | 783 | def __get(self, instance, owner): 784 | return instance.meter * 3.2808 785 | def __set(self, instance, value): 786 | instance.meter = float(value) / 3.2808 787 | 788 | class Distance(object): 789 | '''用于描述距离的类,包含英尺和米两个描述符。''' 790 | meter = Meter() 791 | foot = Foot() 792 | 793 | 794 | 拷贝 795 | ----- 796 | 797 | 有些时候,特别是处理可变对象时,你可能想拷贝一个对象,改变这个对象而不影响原有的对象。这时就需要用到Python的 `copy` 模块了。然而(幸运的是),Python模块并不具有感知能力, 798 | 因此我们不用担心某天基于Linux的机器人崛起。但是我们的确需要告诉Python如何有效率地拷贝对象。 799 | 800 | - `__copy__(self)` 801 | 802 | 定义对类的实例使用 `copy.copy()` 时的行为。 `copy.copy()` 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。 803 | 804 | - `__deepcopy__(self, memodict=)` 805 | 806 | 定义对类的实例使用 `copy.deepcopy()` 时的行为。 `copy.deepcopy()` 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 `memodict` 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 `copy.deepcopy()` ,使用 `memodict` 作为第一个参数。 807 | 808 | 这些魔法方法有什么用武之地呢?像往常一样,当你需要比默认行为更加精确的控制时。例如,如果你想拷贝一个对象,其中存储了一个字典作为缓存(可能会很大),拷贝缓存可能是没有意义的。如果这个缓存可以在内存中被不同实例共享,那么它就应该被共享。 809 | 810 | Pickling 811 | --------- 812 | 813 | 如果你和其他的Python爱好者共事过,很可能你已经听说过Pickling了。Pickling是Python数据结构的序列化过程,当你想存储一个对象稍后再取出读取时,Pickling会显得十分有用。然而它同样也是担忧和混淆的主要来源。 814 | 815 | Pickling是如此的重要,以至于它不仅仅有自己的模块( `pickle` ),还有自己的协议和魔法方法。首先,我们先来简要的介绍一下如何pickle已存在的对象类型(如果你已经知道了,大可跳过这部分内容)。 816 | 817 | 818 | Pickling : 小试牛刀 819 | ''''''''''''''''''''' 820 | 821 | 我们一起来pickle吧。假设你有一个字典,你想存储它,稍后再取出来。你可以把它的内容写入一个文件,小心翼翼地确保使用了正确地格式,要把它读取出来,你可以使用 `exec()` 或处理文件输入。但是这种方法并不可靠:如果你使用纯文本来存储重要数据,数据很容易以多种方式被破坏或者修改,导致你的程序崩溃,更糟糕的情况下,还可能在你的计算机上运行恶意代码。因此,我们要pickle它:: 822 | 823 | import pickle 824 | 825 | data = {'foo': [1,2,3], 826 | 'bar': ('Hello', 'world!'), 827 | 'baz': True} 828 | jar = open('data.pkl', 'wb') 829 | pickle.dump(data, jar) # 将pickle后的数据写入jar文件 830 | jar.close() 831 | 832 | 833 | 过了几个小时,我们想把它取出来,我们只需要反pickle它:: 834 | 835 | import pickle 836 | 837 | pkl_file = open('data.pkl', 'rb') # 与pickle后的数据连接 838 | data = pickle.load(pkl_file) # 把它加载进一个变量 839 | print data 840 | pkl_file.close() 841 | 842 | 843 | 将会发生什么?正如你期待的,它就是我们之前的 `data` 。 844 | 845 | 现在,还需要谨慎地说一句: pickle并不完美。Pickle文件很容易因为事故或被故意的破坏掉。Pickling或许比纯文本文件安全一些,但是依然有可能被用来运行恶意代码。而且它还不支持跨Python版本,所以不要指望分发pickle对象之后所有人都能正确地读取。然而不管怎么样,它依然是一个强有力的工具,可以用于缓存和其他类型的持久化工作。 846 | 847 | 848 | Pickle你的对象 849 | ''''''''''''''' 850 | 851 | Pickle不仅仅可以用于内建类型,任何遵守pickle协议的类都可以被pickle。Pickle协议有四个可选方法,可以让类自定义它们的行为(这和C语言扩展略有不同,那不在我们的讨论范围之内)。 852 | 853 | - `__getinitargs__(self)` 854 | 855 | 如果你想让你的类在反pickle时调用 `__init__` ,你可以定义 `__getinitargs__(self)` ,它会返回一个参数元组,这个元组会传递给 `__init__` 。注意,这个方法只能用于旧式类。 856 | 857 | - `__getnewargs__(self)` 858 | 859 | 对新式类来说,你可以通过这个方法改变类在反pickle时传递给 `__new__` 的参数。这个方法应该返回一个参数元组。 860 | 861 | - `__getstate__(self)` 862 | 863 | 你可以自定义对象被pickle时被存储的状态,而不使用对象的 `__dict__` 属性。 这个状态在对象被反pickle时会被 `__setstate__` 使用。 864 | 865 | - `__setstate__(self)` 866 | 867 | 当一个对象被反pickle时,如果定义了 `__setstate__` ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 `__dict__` 属性。这个魔法方法和 `__getstate__` 相互依存:当这两个方法都被定义时,你可以在Pickle时使用任何方法保存对象的任何状态。 868 | 869 | - `__reduce__(self)` 870 | 871 | 当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 `__reduce__` 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 `__setstate__` 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选); 872 | 873 | - `__reduce_ex__(self)` 874 | 875 | `__reduce_ex__` 的存在是为了兼容性。如果它被定义,在pickle时 `__reduce_ex__` 会代替 `__reduce__` 被调用。 `__reduce__` 也可以被定义,用于不支持 `__reduce_ex__` 的旧版pickle的API调用。 876 | 877 | 878 | 一个例子 879 | ''''''''' 880 | 881 | 我们的例子是 `Slate` ,它会记住它的值曾经是什么,以及那些值是什么时候赋给它的。然而 882 | 每次被pickle时它都会变成空白,因为当前的值不会被存储:: 883 | 884 | import time 885 | 886 | class Slate: 887 | '''存储一个字符串和一个变更日志的类 888 | 每次被pickle都会忘记它当前的值''' 889 | 890 | def __init__(self, value): 891 | self.value = value 892 | self.last_change = time.asctime() 893 | self.history = {} 894 | 895 | def change(self, new_value): 896 | # 改变当前值,将上一个值记录到历史 897 | self.history[self.last_change] = self.value 898 | self.value = new_value) 899 | self.last_change = time.asctime() 900 | 901 | def print_change(self): 902 | print 'Changelog for Slate object:' 903 | for k,v in self.history.items(): 904 | print '%s\t %s' % (k,v) 905 | 906 | def __getstate__(self): 907 | # 故意不返回self.value或self.last_change 908 | # 我们想在反pickle时得到一个空白的slate 909 | return self.history 910 | 911 | def __setstate__(self): 912 | # 使self.history = slate,last_change 913 | # 和value为未定义 914 | self.history = state 915 | self.value, self.last_change = None, None 916 | 917 | 总结 918 | ------ 919 | 920 | 这本指南的目标是使所有阅读它的人都能有所收获,无论他们有没有使用Python或者进行面向对象编程的经验。如果你刚刚开始学习Python,你会得到宝贵的基础知识,了解如何写出具有丰富特性的,优雅而且易用的类。如果你是中级的Python程序员,你或许能掌握一些新的概念和技巧,以及一些可以减少代码行数的好办法。如果你是专家级别的Python爱好者,你又重新复习了一遍某些可能已经忘掉的知识,也可能顺便了解了一些新技巧。无论你的水平怎样,我希望这趟遨游Python特殊方法的旅行,真的对你产生了魔法般的效果(实在忍不住不说最后这个双关)。 921 | 922 | 附录1:如何调用魔法方法 923 | ------------------------- 924 | 925 | 一些魔法方法直接和内建函数对应,这种情况下,如何调用它们是显而易见的。然而,另外的情况下,调用魔法方法的途径并不是那么明显。这个附录旨在展示那些不那么明显的调用魔法方法的语法。 926 | 927 | 928 | +--------------------------------+----------------------------------+-------------------------+ 929 | | 魔法方法 | 什么时候被调用 |解释 | 930 | +================================+==================================+=========================+ 931 | |__new__(cls [,...]) |instance = MyClass(arg1, arg2) |__new__在实例创建时调用 | 932 | +--------------------------------+----------------------------------+-------------------------+ 933 | |__init__(self [,...]) |instance = MyClass(arg1,arg2) |__init__在实例创建时调用 | 934 | +--------------------------------+----------------------------------+-------------------------+ 935 | |__cmp__(self) |self == other, self > other 等 |进行比较时调用 | 936 | +--------------------------------+----------------------------------+-------------------------+ 937 | |__pos__(self) |+self |一元加法符号 | 938 | +--------------------------------+----------------------------------+-------------------------+ 939 | |__neg__(self) |-self |一元减法符号 | 940 | +--------------------------------+----------------------------------+-------------------------+ 941 | |__invert__(self) |~self |按位取反 | 942 | +--------------------------------+----------------------------------+-------------------------+ 943 | |__index__(self) |x[self] |当对象用于索引时 | 944 | +--------------------------------+----------------------------------+-------------------------+ 945 | |__nonzero__(self) |bool(self) |对象的布尔值 | 946 | +--------------------------------+----------------------------------+-------------------------+ 947 | |__getattr__(self, name) |self.name #name不存在 |访问不存在的属性 | 948 | +--------------------------------+----------------------------------+-------------------------+ 949 | |__setattr__(self, name) |self.name = val |给属性赋值 | 950 | +--------------------------------+----------------------------------+-------------------------+ 951 | |__delattr_(self, name) |del self.name |删除属性 | 952 | +--------------------------------+----------------------------------+-------------------------+ 953 | |__getattribute__(self,name) |self.name |访问任意属性 | 954 | +--------------------------------+----------------------------------+-------------------------+ 955 | |__getitem__(self, key) |self[key] |使用索引访问某个元素 | 956 | +--------------------------------+----------------------------------+-------------------------+ 957 | |__setitem__(self, key) |self[key] = val |使用索引给某个元素赋值 | 958 | +--------------------------------+----------------------------------+-------------------------+ 959 | |__delitem__(self, key) |del self[key] |使用索引删除某个对象 | 960 | +--------------------------------+----------------------------------+-------------------------+ 961 | |__iter__(self) |for x in self |迭代 | 962 | +--------------------------------+----------------------------------+-------------------------+ 963 | |__contains__(self, value) |value in self, value not in self |使用in进行成员测试 | 964 | +--------------------------------+----------------------------------+-------------------------+ 965 | |__call__(self [,...]) |self(args) |“调用”一个实例 | 966 | +--------------------------------+----------------------------------+-------------------------+ 967 | |__enter__(self) |with self as x: |with声明的上下文管理器 | 968 | +--------------------------------+----------------------------------+-------------------------+ 969 | |__exit__(self, exc, val, trace) |with self as x: |with声明的上下文管理器 | 970 | +--------------------------------+----------------------------------+-------------------------+ 971 | |__getstate__(self) |pickle.dump(pkl_file, self) |Pickling | 972 | +--------------------------------+----------------------------------+-------------------------+ 973 | |__setstate__(self) |data = pickle.load(pkl_file) |Pickling | 974 | +--------------------------------+----------------------------------+-------------------------+ 975 | 976 | 附录2:Python 3中的变化 977 | ------------------------ 978 | 979 | 在这里,我们记录了几个在对象模型方面 Python 3 和 Python 2.x 之间的主要区别。 980 | 981 | - Python 3中string和unicode的区别不复存在,因此 `__unicode__` 被取消了, `__bytes__` 加入进来(与Python 2.7 中的 `__str__` 和 `__unicode__` 行为类似),用于新的创建字节数组的内建方法。 982 | 983 | - Python 3中默认除法变成了 true 除法,因此 `__div__` 被取消了。 984 | 985 | - `__coerce__` 被取消了,因为和其他魔法方法有功能上的重复,以及本身行为令人迷惑。 986 | 987 | - `__cmp__` 被取消了,因为和其他魔法方法有功能上的重复。 988 | 989 | - `__nonzero__` 被重命名成 `__bool__` 。 990 | -------------------------------------------------------------------------------- /docs/python-pandas.rst: -------------------------------------------------------------------------------- 1 | :Date: 2017-04-07 10:39 2 | 3 | .. _python_pandas: 4 | 5 | =========================== 6 | (译)十分钟搞定pandas + 实例 7 | =========================== 8 | 9 | :作者: wzl 10 | :日期: 2017-04-06 11 | :注: 欢迎fork后pull request来丰富这个文章. 12 | 13 | .. Contents:: 14 | 15 | 什么是pandas? 16 | ------------- 17 | 18 | pandas_ : Python数据分析模块 19 | 20 | .. _pandas: https://github.com/pandas-dev/pandas 21 | 22 | pandas是为了解决数据分析任务而创建的,纳入了大量的库和标准数据模型,提供了高效地操作大型数据集所需的工具。 23 | 24 | ``pandas中的数据结构`` : 25 | 26 | 1. Series: 一维数组,类似于python中的基本数据结构list,区别是series只允许存储相同的数据类型,这样可以更有效的使用内存,提高运算效率。就像数据库中的列数据。 27 | 28 | 2. DataFrame: 二维的表格型数据结构。很多功能与R中的data.frame类似。可以将DataFrame理解为Series的容器。 29 | 30 | 3. Panel:三维的数组,可以理解为DataFrame的容器。 31 | 32 | 十分钟搞定pandas(译文+注释) 33 | ---------------------------- 34 | 35 | ``说明`` : 本文是pandas官网 `10 Minutes to pandas `_ 的翻译。 36 | 37 | ``引入需要的包``:: 38 | 39 | import pandas as pd 40 | import numpy as np 41 | import matplotlib.pyplot as plt 42 | 43 | ``注`` 44 | 45 | - numpy 是一个python实现的科学计算包 46 | 47 | - matplotlib 是一个python的2D绘图库 48 | 49 | - 更多章节请查看 `Cookbook `_ 50 | 51 | 创建对象 52 | -------- 53 | 详情请查看 `数据结构介绍 `_ 54 | 55 | 1.通过传入一个列表来创建 `Series `_ ,pandas会创建默认的整形指标:: 56 | 57 | >>> s = pd.Series([1,3,5,np.nan,6,8]) 58 | >>> s 59 | 0 1 60 | 1 3 61 | 2 5 62 | 3 NaN 63 | 4 6 64 | 5 8 65 | dtype: float64 66 | 67 | 2.通过传递数字数组、时间索引、列标签来创建 `DataFrame `_ :: 68 | 69 | >>> dates = pd.date_range('20130101',periods=6) 70 | >>> dates 71 | DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', 72 | '2013-01-05', '2013-01-06'], 73 | dtype='datetime64[ns]', freq='D') 74 | 75 | >>> df = pd.DataFrame(np.random.randn(6,4),index=dates,columns=list('ABCD')) 76 | >>> df 77 | A B C D 78 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 79 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 80 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 81 | 2013-01-04 -0.921692 0.876693 -0.670553 1.468060 82 | 2013-01-05 -0.300317 -0.011320 -1.376442 1.694740 83 | 2013-01-06 -1.903683 0.786785 -0.194179 0.177973 84 | 85 | ``注`` 86 | 87 | - np.random.randn(6,4) 即创建6行4列的随机数字数组 88 | 89 | 3.通过传递能被转换成类似结构的字典来创建DataFrame:: 90 | 91 | >>>df2 = pd.DataFrame({'A' : 1., 92 | 'B' : pd.Timestamp('20130102'), 93 | 'C' : pd.Series(1,index=list(range(4)),dtype='float32'), 94 | 'D' : np.array([3] * 4,dtype='int32'), 95 | 'E' : pd.Categorical(["test","train","test","train"]), 96 | 'F' : 'foo' }) 97 | 98 | >>> df2 99 | A B C D E F 100 | 0 1 2013-01-02 1 3 test foo 101 | 1 1 2013-01-02 1 3 train foo 102 | 2 1 2013-01-02 1 3 test foo 103 | 3 1 2013-01-02 1 3 train foo 104 | 105 | 4.查看各列的 `dtypes `_ :: 106 | 107 | >>> df2.dtypes 108 | A float64 109 | B datetime64[ns] 110 | C float32 111 | D int32 112 | E category 113 | F object 114 | dtype: object 115 | 116 | 5.如果使用IPython,Tab会自动补全所有的属性和自定义的列,如下所示:: 117 | 118 | >>> df2. 119 | df2.A df2.boxplot 120 | df2.abs df2.C 121 | df2.add df2.clip 122 | df2.add_prefix df2.clip_lower 123 | df2.add_suffix df2.clip_upper 124 | df2.align df2.columns 125 | df2.all df2.combine 126 | df2.any df2.combineAdd 127 | df2.append df2.combine_first 128 | df2.apply df2.combineMult 129 | df2.applymap df2.compound 130 | df2.as_blocks df2.consolidate 131 | df2.asfreq df2.convert_objects 132 | df2.as_matrix df2.copy 133 | df2.astype df2.corr 134 | df2.at df2.corrwith 135 | df2.at_time df2.count 136 | df2.axes df2.cov 137 | df2.B df2.cummax 138 | df2.between_time df2.cummin 139 | df2.bfill df2.cumprod 140 | df2.blocks df2.cumsum 141 | df2.bool df2.D 142 | 143 | 可以看到,A、B、C、D列均通过Tab自动生成 144 | 145 | 查看数据 146 | -------- 147 | 详情请查看 `基本功能 `_ 148 | 149 | 1.查看DataFrame头部&尾部数据:: 150 | 151 | >>> df.head() 152 | A B C D 153 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 154 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 155 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 156 | 2013-01-04 -0.921692 0.876693 -0.670553 1.468060 157 | 013-01-05 -0.300317 -0.011320 -1.376442 1.694740 158 | >>> df.tail(3) 159 | A B C D 160 | 2013-01-04 -0.921692 0.876693 -0.670553 1.468060 161 | 2013-01-05 -0.300317 -0.011320 -1.376442 1.694740 162 | 2013-01-06 -1.903683 0.786785 -0.194179 0.177973 163 | 164 | 2.查看索引、列、和数组数据:: 165 | 166 | >>> df.index 167 | DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', 168 | '2013-01-05', '2013-01-06'], 169 | dtype='datetime64[ns]', freq='D') 170 | >>> df.columns 171 | Index([u'A', u'B', u'C', u'D'], dtype='object') 172 | >>> df.values 173 | array([[ 0.85961861, -0.54590304, 0.01244705, 1.25768432], 174 | [ 0.11962178, -0.4840508 , 0.40472795, 0.36088029], 175 | [-0.7192337 , -0.39617432, 0.63523701, 0.21669124], 176 | [-0.92169244, 0.87669275, -0.67055318, 1.46806034], 177 | [-0.30031679, -0.01132035, -1.37644224, 1.69474031], 178 | [-1.90368258, 0.78678454, -0.19417942, 0.17797326]]) 179 | 180 | 3.查看数据的快速统计结果:: 181 | 182 | >>> df.describe() 183 | A B C D 184 | count 6.000000 6.000000 6.000000 6.000000 185 | mean -0.477614 0.037671 -0.198127 0.862672 186 | std 0.945047 0.643196 0.736736 0.685969 187 | min -1.903683 -0.545903 -1.376442 0.177973 188 | 25% -0.871078 -0.462082 -0.551460 0.252739 189 | 50% -0.509775 -0.203747 -0.090866 0.809282 190 | 75% 0.014637 0.587258 0.306658 1.415466 191 | max 0.859619 0.876693 0.635237 1.694740 192 | 193 | 4.对数据进行行列转换:: 194 | 195 | >>> df.T 196 | 2013-01-01 2013-01-02 2013-01-03 2013-01-04 2013-01-05 2013-01-06 197 | A 0.859619 0.119622 -0.719234 -0.921692 -0.300317 -1.903683 198 | B -0.545903 -0.484051 -0.396174 0.876693 -0.011320 0.786785 199 | C 0.012447 0.404728 0.635237 -0.670553 -1.376442 -0.194179 200 | D 1.257684 0.360880 0.216691 1.468060 1.694740 0.177973 201 | 202 | 5.按 `axis `_ 排序:: 203 | 204 | >>> df.sort_index(axis=1, ascending=False) 205 | D C B A 206 | 2013-01-01 1.257684 0.012447 -0.545903 0.859619 207 | 2013-01-02 0.360880 0.404728 -0.484051 0.119622 208 | 2013-01-03 0.216691 0.635237 -0.396174 -0.719234 209 | 2013-01-04 1.468060 -0.670553 0.876693 -0.921692 210 | 2013-01-05 1.694740 -1.376442 -0.011320 -0.300317 211 | 2013-01-06 0.177973 -0.194179 0.786785 -1.903683 212 | 213 | 6.按值排序:: 214 | 215 | >>> df.sort_values(by='B') 216 | A B C D 217 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 218 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 219 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 220 | 2013-01-05 -0.300317 -0.011320 -1.376442 1.694740 221 | 2013-01-06 -1.903683 0.786785 -0.194179 0.177973 222 | 2013-01-04 -0.921692 0.876693 -0.670553 1.468060 223 | 224 | 选择数据 225 | -------- 226 | 227 | ``注意``:虽然标准的Python/Numpy表达式是直观且可用的,但是我们推荐使用优化后的pandas方法,例如:.at,.iat,.loc,.iloc以及.ix 228 | 详情请查看: `Indexing and Selecting Data `_ 和 `MultiIndex / Advanced Indexing `_ 229 | 230 | - 获取 231 | 232 | 1.选择一列,返回Series,相当于df.A:: 233 | 234 | >>> df['A'] 235 | 2013-01-01 0.859619 236 | 2013-01-02 0.119622 237 | 2013-01-03 -0.719234 238 | 2013-01-04 -0.921692 239 | 2013-01-05 -0.300317 240 | 2013-01-06 -1.903683 241 | Freq: D, Name: A, dtype: float64 242 | 243 | 2.通过[]选择,即对行进行切片:: 244 | 245 | >>> df[0:3] 246 | A B C D 247 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 248 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 249 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 250 | 251 | - 标签式选择 252 | 253 | 1.通过标签获取交叉区域:: 254 | 255 | >>> df.loc[dates[0]] 256 | A 0.859619 257 | B -0.545903 258 | C 0.012447 259 | D 1.257684 260 | Name: 2013-01-01 00:00:00, dtype: float64 261 | ``注``:即获取时间为2013-01-01的数据 262 | 263 | 2.通过标签获取多轴数据:: 264 | 265 | >>> df.loc[:,['A','B']] 266 | A B 267 | 2013-01-01 0.859619 -0.545903 268 | 2013-01-02 0.119622 -0.484051 269 | 2013-01-03 -0.719234 -0.396174 270 | 2013-01-04 -0.921692 0.876693 271 | 2013-01-05 -0.300317 -0.011320 272 | 2013-01-06 -1.903683 0.786785 273 | 274 | 3.标签切片:: 275 | 276 | >>> df.loc['20130102':'20130104',['A','B']] 277 | A B 278 | 2013-01-02 0.119622 -0.484051 279 | 2013-01-03 -0.719234 -0.396174 280 | 2013-01-04 -0.921692 0.876693 281 | 282 | 4.对返回的对象缩减维度:: 283 | 284 | >>> df.loc['20130102',['A','B']] 285 | A 0.119622 286 | B -0.484051 287 | Name: 2013-01-02 00:00:00, dtype: float64 288 | 289 | 5.获取单个值:: 290 | 291 | >>> df.loc[dates[0],'A'] 292 | 0.85961861159875042 293 | 294 | 6.快速访问单个标量(同5):: 295 | 296 | >>> df.at[dates[0],'A'] 297 | 0.85961861159875042 298 | 299 | ``注``:loc通过行标签获取行数据,iloc通过行号获取行数据 300 | 301 | - 位置式选择 302 | 303 | 详情请查看 `通过位置选择 `_ 304 | 305 | 1.通过数值选择:: 306 | 307 | >>> df.iloc[3] 308 | A -0.921692 309 | B 0.876693 310 | C -0.670553 311 | D 1.468060 312 | Name: 2013-01-04 00:00:00, dtype: float64 313 | 314 | 2.通过数值切片:: 315 | 316 | >>> df.iloc[3:5,0:2] 317 | A B 318 | 2013-01-04 -0.921692 0.876693 319 | 2013-01-05 -0.300317 -0.011320 320 | ``注``:左开右闭 321 | 322 | 3.通过指定列表位置:: 323 | 324 | >>> df.iloc[[1,2,4],[0,2]] 325 | A C 326 | 2013-01-02 0.119622 0.404728 327 | 2013-01-03 -0.719234 0.635237 328 | 2013-01-05 -0.300317 -1.376442 329 | 330 | 4.对行切片:: 331 | 332 | >>> df.iloc[1:3,:] 333 | A B C D 334 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 335 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 336 | 337 | 5.对列切片:: 338 | 339 | >>> df.iloc[:,1:3] 340 | B C 341 | 2013-01-01 -0.545903 0.012447 342 | 2013-01-02 -0.484051 0.404728 343 | 2013-01-03 -0.396174 0.635237 344 | 2013-01-04 0.876693 -0.670553 345 | 2013-01-05 -0.011320 -1.376442 346 | 2013-01-06 0.786785 -0.194179 347 | 348 | 6.获取特定值:: 349 | 350 | >>> df.iloc[1,1] 351 | -0.48405080229207309 352 | 353 | 7.快速访问某个标量(同6):: 354 | 355 | >>> df.iat[1,1] 356 | -0.48405080229207309 357 | 358 | - Boolean索引 359 | 1.通过某列选择数据:: 360 | 361 | >>> df[df.A > 0] 362 | A B C D 363 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 364 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 365 | 366 | 2.通过where选择数据:: 367 | 368 | >>> df[df > 0] 369 | A B C D 370 | 2013-01-01 0.859619 NaN 0.012447 1.257684 371 | 2013-01-02 0.119622 NaN 0.404728 0.360880 372 | 2013-01-03 NaN NaN 0.635237 0.216691 373 | 2013-01-04 NaN 0.876693 NaN 1.468060 374 | 2013-01-05 NaN NaN NaN 1.694740 375 | 2013-01-06 NaN 0.786785 NaN 0.177973 376 | 377 | 3.通过 `isin() `_ 过滤数据:: 378 | 379 | >>> df2 = df.copy() 380 | >>> df2['E'] = ['one', 'one','two','three','four','three'] 381 | >>> df2 382 | A B C D E 383 | 2013-01-01 0.859619 -0.545903 0.012447 1.257684 one 384 | 2013-01-02 0.119622 -0.484051 0.404728 0.360880 one 385 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 two 386 | 2013-01-04 -0.921692 0.876693 -0.670553 1.468060 three 387 | 2013-01-05 -0.300317 -0.011320 -1.376442 1.694740 four 388 | 2013-01-06 -1.903683 0.786785 -0.194179 0.177973 three 389 | >>> df2[df2['E'].isin(['two','four'])] 390 | A B C D E 391 | 2013-01-03 -0.719234 -0.396174 0.635237 0.216691 two 392 | 2013-01-05 -0.300317 -0.011320 -1.376442 1.694740 four 393 | 394 | - 设置 395 | 1.新增一列数据:: 396 | 397 | >>> s1 = pd.Series([1,2,3,4,5,6], index=pd.date_range('20130102', periods=6)) 398 | >>> s1 399 | 2013-01-02 1 400 | 2013-01-03 2 401 | 2013-01-04 3 402 | 2013-01-05 4 403 | 2013-01-06 5 404 | 2013-01-07 6 405 | Freq: D, dtype: int64 406 | >>> df['F'] = s1 407 | 408 | 2.通过标签更新值:: 409 | 410 | >>> df.at[dates[0],'A'] = 0 411 | 412 | 3.通过位置更新值:: 413 | 414 | >>> df.iat[0,1] = 0 415 | 416 | 4.通过数组更新一列值:: 417 | 418 | >>> df.loc[:,'D'] = np.array([5] * len(df)) 419 | 420 | 上面几步操作的结果:: 421 | 422 | >>> df 423 | A B C D F 424 | 2013-01-01 0.000000 0.000000 0.012447 5 NaN 425 | 2013-01-02 0.119622 -0.484051 0.404728 5 1 426 | 2013-01-03 -0.719234 -0.396174 0.635237 5 2 427 | 2013-01-04 -0.921692 0.876693 -0.670553 5 3 428 | 2013-01-05 -0.300317 -0.011320 -1.376442 5 4 429 | 2013-01-06 -1.903683 0.786785 -0.194179 5 5 430 | 431 | 5.通过where更新值:: 432 | 433 | >>> df2 = df.copy() 434 | >>> df2[df2 > 0] = -df2 435 | >>> df2 436 | A B C D F 437 | 2013-01-01 0.000000 0.000000 -0.012447 -5 NaN 438 | 2013-01-02 -0.119622 -0.484051 -0.404728 -5 -1 439 | 2013-01-03 -0.719234 -0.396174 -0.635237 -5 -2 440 | 2013-01-04 -0.921692 -0.876693 -0.670553 -5 -3 441 | 2013-01-05 -0.300317 -0.011320 -1.376442 -5 -4 442 | 2013-01-06 -1.903683 -0.786785 -0.194179 -5 -5 443 | 444 | 缺失数据处理 445 | ------------ 446 | pandas用np.nan代表缺失数据,详情请查看 `Missing Data section `_ 447 | 448 | 1.reindex()可以修改/增加/删除索引,会返回一个数据的副本:: 449 | 450 | >>> df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E']) 451 | >>> df1.loc[dates[0]:dates[1],'E'] = 1 452 | >>> df1 453 | A B C D F E 454 | 2013-01-01 0.000000 0.000000 0.012447 5 NaN 1 455 | 2013-01-02 0.119622 -0.484051 0.404728 5 1 1 456 | 2013-01-03 -0.719234 -0.396174 0.635237 5 2 NaN 457 | 2013-01-04 -0.921692 0.876693 -0.670553 5 3 NaN 458 | 459 | 2.丢掉含有缺失项的行:: 460 | 461 | >>> df1.dropna(how='any') 462 | A B C D F E 463 | 2013-01-02 0.119622 -0.484051 0.404728 5 1 1 464 | 465 | 3.对缺失项赋值:: 466 | 467 | >>> df1.fillna(value=5) 468 | A B C D F E 469 | 2013-01-01 0.000000 0.000000 0.012447 5 5 1 470 | 2013-01-02 0.119622 -0.484051 0.404728 5 1 1 471 | 2013-01-03 -0.719234 -0.396174 0.635237 5 2 5 472 | 2013-01-04 -0.921692 0.876693 -0.670553 5 3 5 473 | 474 | 4.对缺失项布尔赋值:: 475 | 476 | >>> pd.isnull(df1) 477 | A B C D F E 478 | 2013-01-01 False False False False True False 479 | 2013-01-02 False False False False False False 480 | 2013-01-03 False False False False False True 481 | 2013-01-04 False False False False False True 482 | 483 | 相关操作 484 | -------- 485 | 详情请查看 `Basic section on Binary Ops `_ 486 | 487 | - 统计(操作通常情况下不包含缺失项) 488 | 489 | 1.按列求平均值:: 490 | 491 | >>> df.mean() 492 | A -0.620884 493 | B 0.128655 494 | C -0.198127 495 | D 5.000000 496 | F 3.000000 497 | dtype: float64 498 | 499 | 2.按行求平均值:: 500 | 501 | >>> df.mean(1) 502 | 2013-01-01 1.253112 503 | 2013-01-02 1.208060 504 | 2013-01-03 1.303966 505 | 2013-01-04 1.456889 506 | 2013-01-05 1.462384 507 | 2013-01-06 1.737785 508 | Freq: D, dtype: float64 509 | 510 | 3.操作不同的维度需要先对齐,pandas会沿着指定维度执行:: 511 | 512 | >>> s = pd.Series([1,3,5,np.nan,6,8], index=dates).shift(2) 513 | >>> s 514 | 2013-01-01 NaN 515 | 2013-01-02 NaN 516 | 2013-01-03 1 517 | 2013-01-04 3 518 | 2013-01-05 5 519 | 2013-01-06 NaN 520 | Freq: D, dtype: float64 521 | >>> df.sub(s, axis='index') 522 | A B C D F 523 | 2013-01-01 NaN NaN NaN NaN NaN 524 | 2013-01-02 NaN NaN NaN NaN NaN 525 | 2013-01-03 -1.719234 -1.396174 -0.364763 4 1 526 | 2013-01-04 -3.921692 -2.123307 -3.670553 2 0 527 | 2013-01-05 -5.300317 -5.011320 -6.376442 0 -1 528 | 2013-01-06 NaN NaN NaN NaN NaN 529 | 530 | ``注``: 531 | 532 | - 这里对齐维度指的对齐时间index 533 | 534 | - shift(2)指沿着时间轴将数据顺移两位 535 | 536 | - sub指减法,与NaN进行操作,结果也是NaN 537 | 538 | --------------------------------------------- 539 | 540 | - 应用 541 | 542 | 1.对数据应用function:: 543 | 544 | >>> df.apply(np.cumsum) 545 | A B C D F 546 | 2013-01-01 0.000000 0.000000 0.012447 5 NaN 547 | 2013-01-02 0.119622 -0.484051 0.417175 10 1 548 | 2013-01-03 -0.599612 -0.880225 1.052412 15 3 549 | 2013-01-04 -1.521304 -0.003532 0.381859 20 6 550 | 2013-01-05 -1.821621 -0.014853 -0.994583 25 10 551 | 2013-01-06 -3.725304 0.771932 -1.188763 30 15 552 | >>> df.apply(lambda x: x.max() - x.min()) 553 | A 2.023304 554 | B 1.360744 555 | C 2.011679 556 | D 0.000000 557 | F 4.000000 558 | dtype: float64 559 | ``注``: 560 | - cumsum 累加 561 | 562 | 详情请查看 `直方图和离散化 `_ 563 | 564 | - 直方图:: 565 | 566 | >>> s = pd.Series(np.random.randint(0, 7, size=10)) 567 | >>> s 568 | 0 1 569 | 1 3 570 | 2 5 571 | 3 1 572 | 4 6 573 | 5 1 574 | 6 3 575 | 7 4 576 | 8 0 577 | 9 3 578 | dtype: int64 579 | >>> s.value_counts() 580 | 3 3 581 | 1 3 582 | 6 1 583 | 5 1 584 | 4 1 585 | 0 1 586 | dtype: int64 587 | 588 | 589 | pandas默认配置了一些字符串处理方法,可以方便的操作元素,如下所示:(详情请查看 `Vectorized String Methods `_) 590 | 591 | - 字符串方法:: 592 | 593 | >>> s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat']) 594 | >>> s.str.lower() 595 | 0 a 596 | 1 b 597 | 2 c 598 | 3 aaba 599 | 4 baca 600 | 5 NaN 601 | 6 caba 602 | 7 dog 603 | 8 cat 604 | dtype: object 605 | 606 | 合并 607 | ---- 608 | - 连接 609 | pandas提供了大量的方法,能轻松的对Series,DataFrame和Panel执行合并操作。详情请查看 `Merging section `_ 610 | 611 | 使用concat()连接pandas对象:: 612 | 613 | >>> df = pd.DataFrame(np.random.randn(10, 4)) 614 | >>> df 615 | 0 1 2 3 616 | 0 -0.199614 1.914485 0.396383 -0.295306 617 | 1 -0.061961 -1.352883 0.266751 -0.874132 618 | 2 0.346504 -2.328099 -1.492250 0.095392 619 | 3 0.187115 0.562740 -1.677737 -0.224807 620 | 4 -1.422599 -1.028044 0.789487 0.806940 621 | 5 0.439478 -0.592229 0.736081 1.008404 622 | 6 -0.205641 -0.649465 -0.706395 0.578698 623 | 7 -2.168725 -2.487189 0.060258 1.965318 624 | 8 0.207634 0.512572 0.595373 0.816516 625 | 9 0.764893 0.612208 -1.022504 -2.032126 626 | >>> pieces = [df[:3], df[3:7], df[7:]] 627 | >>> pd.concat(pieces) 628 | 0 1 2 3 629 | 0 -0.199614 1.914485 0.396383 -0.295306 630 | 1 -0.061961 -1.352883 0.266751 -0.874132 631 | 2 0.346504 -2.328099 -1.492250 0.095392 632 | 3 0.187115 0.562740 -1.677737 -0.224807 633 | 4 -1.422599 -1.028044 0.789487 0.806940 634 | 5 0.439478 -0.592229 0.736081 1.008404 635 | 6 -0.205641 -0.649465 -0.706395 0.578698 636 | 7 -2.168725 -2.487189 0.060258 1.965318 637 | 8 0.207634 0.512572 0.595373 0.816516 638 | 9 0.764893 0.612208 -1.022504 -2.032126 639 | 640 | - Join 641 | 类似SQL的合并操作,详情请查看 `Database style joining `_ 642 | 643 | 栗子:: 644 | 645 | >>> left = pd.DataFrame({'key': ['foo', 'foo'], 'lval': [1, 2]}) 646 | >>> right = pd.DataFrame({'key': ['foo', 'foo'], 'rval': [4, 5]}) 647 | >>> left 648 | key lval 649 | 0 foo 1 650 | 1 foo 2 651 | >>> right 652 | key rval 653 | 0 foo 4 654 | 1 foo 5 655 | >>> pd.merge(left, right, on='key') 656 | key lval rval 657 | 0 foo 1 4 658 | 1 foo 1 5 659 | 2 foo 2 4 660 | 3 foo 2 5 661 | 662 | 栗子:: 663 | 664 | >>> left = pd.DataFrame({'key': ['foo', 'bar'], 'lval': [1, 2]}) 665 | >>> right = pd.DataFrame({'key': ['foo', 'bar'], 'rval': [4, 5]}) 666 | >>> left 667 | key lval 668 | 0 foo 1 669 | 1 bar 2 670 | >>> right 671 | key rval 672 | 0 foo 4 673 | 1 bar 5 674 | >>> pd.merge(left, right, on='key') 675 | key lval rval 676 | 0 foo 1 4 677 | 1 bar 2 5 678 | 679 | 680 | - 追加,详情请查看 `Appending `_:: 681 | 682 | >>> df = pd.DataFrame(np.random.randn(8, 4), columns=['A','B','C','D']) 683 | >>> df 684 | A B C D 685 | 0 -1.710447 2.541720 -0.654403 0.132077 686 | 1 0.667796 -1.124769 -0.430752 -0.244731 687 | 2 1.555865 -0.483805 0.066114 -0.409518 688 | 3 1.171798 0.036219 -0.515065 0.860625 689 | 4 -0.834051 -2.178128 -0.345627 0.819392 690 | 5 -0.354886 0.161204 1.465532 1.879841 691 | 6 0.560888 1.208905 1.301983 0.799084 692 | 7 -0.770196 0.307691 1.212200 0.909137 693 | >>> s = df.iloc[3] 694 | >>> df.append(s, ignore_index=True) 695 | A B C D 696 | 0 -1.710447 2.541720 -0.654403 0.132077 697 | 1 0.667796 -1.124769 -0.430752 -0.244731 698 | 2 1.555865 -0.483805 0.066114 -0.409518 699 | 3 1.171798 0.036219 -0.515065 0.860625 700 | 4 -0.834051 -2.178128 -0.345627 0.819392 701 | 5 -0.354886 0.161204 1.465532 1.879841 702 | 6 0.560888 1.208905 1.301983 0.799084 703 | 7 -0.770196 0.307691 1.212200 0.909137 704 | 8 1.171798 0.036219 -0.515065 0.860625 705 | 706 | 分组 707 | ---- 708 | ``group by``: 709 | - Splitting 将数据分组 710 | - Applying 对每个分组应用不同的function 711 | - Combining 使用某种数据结果展示结果 712 | 详情请查看 `Grouping section `_ 713 | 714 | 举个栗子:: 715 | 716 | >>> df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar','foo', 'bar', 'foo', 'foo'], 717 | 'B' : ['one', 'one', 'two', 'three','two', 'two', 'one', 'three'], 718 | 'C' : np.random.randn(8), 719 | 'D' : np.random.randn(8)}) 720 | >>> df 721 | A B C D 722 | 0 foo one -0.655020 -0.671592 723 | 1 bar one 0.846428 1.884603 724 | 2 foo two -2.280466 0.725070 725 | 3 bar three 1.166448 -0.208171 726 | 4 foo two -0.257124 -0.850319 727 | 5 bar two -0.654609 1.258091 728 | 6 foo one -1.624213 -0.383978 729 | 7 foo three -0.523944 0.114338 730 | 731 | 分组后sum求和:: 732 | 733 | >>> df.groupby('A').sum() 734 | C D 735 | A 736 | bar 1.358267 2.934523 737 | foo -5.340766 -1.066481 738 | 739 | 对多列分组后sum:: 740 | 741 | >>> df.groupby(['A','B']).sum() 742 | C D 743 | A B 744 | bar one 0.846428 1.884603 745 | three 1.166448 -0.208171 746 | two -0.654609 1.258091 747 | foo one -2.279233 -1.055570 748 | three -0.523944 0.114338 749 | two -2.537589 -0.125249 750 | 751 | 重塑 752 | ---- 753 | 详情请查看 `Hierarchical Indexing `_ 和 `Reshaping `_ 754 | 755 | ``stack``:: 756 | 757 | >>> tuples = list(zip(*[['bar', 'bar', 'baz', 'baz', 758 | 'foo', 'foo', 'qux', 'qux'], 759 | ['one', 'two', 'one', 'two', 760 | 'one', 'two', 'one', 'two']])) 761 | >>> tuples 762 | [('bar', 'one'), ('bar', 'two'), 763 | ('baz', 'one'), ('baz', 'two'), 764 | ('foo', 'one'), ('foo', 'two'), 765 | ('qux', 'one'), ('qux', 'two')] 766 | >>> index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second']) 767 | >>> index 768 | MultiIndex(levels=[[u'bar', u'baz', u'foo', u'qux'], [u'one', u'two']], 769 | labels=[[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 0, 1, 0, 1, 0, 1]], 770 | names=[u'first', u'second']) 771 | >>> df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B']) 772 | >>> df 773 | A B 774 | first second 775 | bar one -0.922059 -0.918091 776 | two -0.825565 -0.880527 777 | baz one 0.241927 1.130320 778 | two -0.261823 2.463877 779 | foo one -0.220328 -0.519477 780 | two -1.028038 -0.543191 781 | qux one 0.315674 0.558686 782 | two 0.422296 0.241212 783 | >>> df2 = df[:4] 784 | >>> df2 785 | A B 786 | first second 787 | bar one -0.922059 -0.918091 788 | two -0.825565 -0.880527 789 | baz one 0.241927 1.130320 790 | two -0.261823 2.463877 791 | ``注``:pd.MultiIndex.from_tuples 将包含多个list的元组转换为复杂索引 792 | 793 | 使用stack()方法为DataFrame增加column:: 794 | 795 | >>> stacked = df2.stack() 796 | >>> stacked 797 | first second 798 | bar one A -0.922059 799 | B -0.918091 800 | two A -0.825565 801 | B -0.880527 802 | baz one A 0.241927 803 | B 1.130320 804 | two A -0.261823 805 | B 2.463877 806 | dtype: float64 807 | 808 | 使用unstack()方法还原stack的DataFrame,默认还原最后一级,也可以自由指定:: 809 | 810 | >>> stacked.unstack() 811 | A B 812 | first second 813 | bar one -0.922059 -0.918091 814 | two -0.825565 -0.880527 815 | baz one 0.241927 1.130320 816 | two -0.261823 2.463877 817 | >>> stacked.unstack(1) 818 | second one two 819 | first 820 | bar A -0.922059 -0.825565 821 | B -0.918091 -0.880527 822 | baz A 0.241927 -0.261823 823 | B 1.130320 2.463877 824 | >>> stacked.unstack(0) 825 | first bar baz 826 | second 827 | one A -0.922059 0.241927 828 | B -0.918091 1.130320 829 | two A -0.825565 -0.261823 830 | B -0.880527 2.463877 831 | 832 | ``透视表`` 833 | 详情请查看 `Pivot Tables `_ 834 | 835 | 栗子:: 836 | 837 | >>> df = pd.DataFrame({'A' : ['one', 'one', 'two', 'three'] * 3, 838 | 'B' : ['A', 'B', 'C'] * 4, 839 | 'C' : ['foo', 'foo', 'foo', 'bar', 'bar', 'bar'] * 2, 840 | 'D' : np.random.randn(12), 841 | 'E' : np.random.randn(12)}) 842 | 843 | ``注``:可以理解为自由组合表的行与列,类似于交叉报表 844 | 845 | 我们能非常简单的构造透视表:: 846 | 847 | >>> pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C']) 848 | C bar foo 849 | A B 850 | one A -1.250611 -1.047274 851 | B 1.532134 -0.455948 852 | C 0.125989 -0.500260 853 | three A 0.623716 NaN 854 | B NaN 0.095117 855 | C -0.348707 NaN 856 | two A NaN 0.390363 857 | B -0.743466 NaN 858 | C NaN 0.792279 859 | 860 | 时间序列 861 | -------- 862 | pandas可以简单高效的进行重新采样通过频率转换(例如:将秒级数据转换成五分钟为单位的数据)。这常见与金融应用中,但是不限于此。详情请查看 `Time Series section `_ 863 | 864 | 栗子:: 865 | 866 | >>> rng = pd.date_range('1/1/2012', periods=100, freq='S') 867 | >>> ts = pd.Series(np.random.randint(0, 500, len(rng)), index=rng) 868 | >>> ts.resample('5Min').sum() 869 | 2012-01-01 24390 870 | Freq: 5T, dtype: int64 871 | ``注``:将随机产生的秒级数据整合成5min的数据 872 | 873 | 时区表现:: 874 | 875 | >>> rng = pd.date_range('3/6/2012 00:00', periods=5, freq='D') 876 | >>> ts = pd.Series(np.random.randn(len(rng)), rng) 877 | >>> ts 878 | 2012-03-06 0.972202 879 | 2012-03-07 -0.839969 880 | 2012-03-08 -0.979993 881 | 2012-03-09 -0.052460 882 | 2012-03-10 -0.487963 883 | Freq: D, dtype: float64 884 | >>> ts_utc = ts.tz_localize('UTC') 885 | >>> ts_utc 886 | 2012-03-06 00:00:00+00:00 0.972202 887 | 2012-03-07 00:00:00+00:00 -0.839969 888 | 2012-03-08 00:00:00+00:00 -0.979993 889 | 2012-03-09 00:00:00+00:00 -0.052460 890 | 2012-03-10 00:00:00+00:00 -0.487963 891 | Freq: D, dtype: float64 892 | 893 | 时区变换:: 894 | 895 | >>> ts_utc.tz_convert('US/Eastern') 896 | 2012-03-05 19:00:00-05:00 0.972202 897 | 2012-03-06 19:00:00-05:00 -0.839969 898 | 2012-03-07 19:00:00-05:00 -0.979993 899 | 2012-03-08 19:00:00-05:00 -0.052460 900 | 2012-03-09 19:00:00-05:00 -0.487963 901 | Freq: D, dtype: float64 902 | 903 | 在不同的时间跨度表现间变换:: 904 | 905 | >>> rng = pd.date_range('1/1/2012', periods=5, freq='M') 906 | >>> ts = pd.Series(np.random.randn(len(rng)), index=rng) 907 | >>> ts 908 | 2012-01-31 -0.681068 909 | 2012-02-29 -0.263571 910 | 2012-03-31 1.268001 911 | 2012-04-30 0.331786 912 | 2012-05-31 0.663572 913 | Freq: M, dtype: float64 914 | >>> ps = ts.to_period() 915 | >>> ps 916 | 2012-01 -0.681068 917 | 2012-02 -0.263571 918 | 2012-03 1.268001 919 | 2012-04 0.331786 920 | 2012-05 0.663572 921 | Freq: M, dtype: float64 922 | >>> ps.to_timestamp() 923 | 2012-01-01 -0.681068 924 | 2012-02-01 -0.263571 925 | 2012-03-01 1.268001 926 | 2012-04-01 0.331786 927 | 2012-05-01 0.663572 928 | Freq: MS, dtype: float64 929 | 930 | ``注``:to_period()默认频率为M,to_period和to_timestamp可以相互转换 931 | 932 | 在周期和时间戳间转换,下面的栗子将季度时间转换为各季度最后一个月的09am:: 933 | 934 | >>> prng = pd.period_range('1990Q1', '2000Q4', freq='Q-NOV') 935 | >>> prng 936 | PeriodIndex(['1990Q1', '1990Q2', '1990Q3', '1990Q4', '1991Q1', '1991Q2', 937 | '1991Q3', '1991Q4', '1992Q1', '1992Q2', '1992Q3', '1992Q4', 938 | '1993Q1', '1993Q2', '1993Q3', '1993Q4', '1994Q1', '1994Q2', 939 | '1994Q3', '1994Q4', '1995Q1', '1995Q2', '1995Q3', '1995Q4', 940 | '1996Q1', '1996Q2', '1996Q3', '1996Q4', '1997Q1', '1997Q2', 941 | '1997Q3', '1997Q4', '1998Q1', '1998Q2', '1998Q3', '1998Q4', 942 | '1999Q1', '1999Q2', '1999Q3', '1999Q4', '2000Q1', '2000Q2', 943 | '2000Q3', '2000Q4'], 944 | dtype='int64', freq='Q-NOV') 945 | >>> ts = pd.Series(np.random.randn(len(prng)), prng) 946 | >>> ts.index = (prng.asfreq('M', 'e') + 1).asfreq('H', 's') + 9 947 | >>> ts.head() 948 | 1990-03-01 09:00 -0.927090 949 | 1990-06-01 09:00 -1.045881 950 | 1990-09-01 09:00 -0.837705 951 | 1990-12-01 09:00 -0.529390 952 | 1991-03-01 09:00 -0.423405 953 | Freq: H, dtype: float64 954 | 955 | 分类 956 | ---- 957 | 从0.15版以后,pandas可以造DataFrame中包含分类数据,详情请查看 `分类介绍 `_ 和 `API 文档 `_:: 958 | 959 | >>> df = pd.DataFrame({"id":[1,2,3,4,5,6], "raw_grade":['a', 'b', 'b', 'a', 'a', 'e']}) 960 | 961 | 1.将原始成绩转换为分类数据:: 962 | 963 | >>> df["grade"] = df["raw_grade"].astype("category") 964 | >>> df["grade"] 965 | 0 a 966 | 1 b 967 | 2 b 968 | 3 a 969 | 4 a 970 | 5 e 971 | Name: grade, dtype: category 972 | Categories (3, object): [a, b, e] 973 | 974 | 2.重命名分类使其更有意义:: 975 | 976 | >>> df["grade"].cat.categories = ["very good", "good", "very bad"] 977 | 978 | 3.重新整理类别,并添加缺少的类别:: 979 | 980 | >>> df["grade"] = df["grade"].cat.set_categories(["very bad", "bad", "medium", "good", "very good"]) 981 | >>> df["grade"] 982 | 0 very good 983 | 1 good 984 | 2 good 985 | 3 very good 986 | 4 very good 987 | 5 very bad 988 | Name: grade, dtype: category 989 | Categories (5, object): [very bad, bad, medium, good, very good] 990 | 991 | 4.按整理后的类别排序(并非词汇的顺序):: 992 | 993 | >>> df.sort_values(by="grade") 994 | id raw_grade grade 995 | 5 6 e very bad 996 | 1 2 b good 997 | 2 3 b good 998 | 0 1 a very good 999 | 3 4 a very good 1000 | 4 5 a very good 1001 | 1002 | 5.按类别分组也包括空类别:: 1003 | 1004 | >>> df.groupby("grade").size() 1005 | grade 1006 | very bad 1 1007 | bad 0 1008 | medium 0 1009 | good 2 1010 | very good 3 1011 | dtype: int64 1012 | 1013 | 绘图 1014 | ---- 1015 | 1016 | 详情请查看 `Plotting `_:: 1017 | 1018 | >>> ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000)) 1019 | >>> ts = ts.cumsum() 1020 | >>> ts.plot() 1021 | 1022 | `图一 `_ 1023 | 1024 | 在DataFrame中,plot()可以绘制所有带有标签的列:: 1025 | 1026 | >>> df = pd.DataFrame(np.random.randn(1000, 4), index=ts.index, 1027 | columns=['A', 'B', 'C', 'D']) 1028 | >>> df = df.cumsum() 1029 | >>> plt.figure(); df.plot(); plt.legend(loc='best') 1030 | 1031 | `图二 `_ 1032 | 1033 | 获取数据 写入\导出 1034 | ------------------ 1035 | - CSV 1036 | 1. `写入csv文件 `_:: 1037 | 1038 | >>> df.to_csv('foo.csv') 1039 | 1040 | 2. `读取csv文件 `_:: 1041 | 1042 | >>> pd.read_csv('foo.csv') 1043 | Unnamed: 0 A B C D 1044 | 0 2000-01-01 0.266457 -0.399641 -0.219582 1.186860 1045 | 1 2000-01-02 -1.170732 -0.345873 1.653061 -0.282953 1046 | 2 2000-01-03 -1.734933 0.530468 2.060811 -0.515536 1047 | 3 2000-01-04 -1.555121 1.452620 0.239859 -1.156896 1048 | 4 2000-01-05 0.578117 0.511371 0.103552 -2.428202 1049 | 5 2000-01-06 0.478344 0.449933 -0.741620 -1.962409 1050 | 6 2000-01-07 1.235339 -0.091757 -1.543861 -1.084753 1051 | .. ... ... ... ... ... 1052 | 993 2002-09-20 -10.628548 -9.153563 -7.883146 28.313940 1053 | 994 2002-09-21 -10.390377 -8.727491 -6.399645 30.914107 1054 | 995 2002-09-22 -8.985362 -8.485624 -4.669462 31.367740 1055 | 996 2002-09-23 -9.558560 -8.781216 -4.499815 30.518439 1056 | 997 2002-09-24 -9.902058 -9.340490 -4.386639 30.105593 1057 | 998 2002-09-25 -10.216020 -9.480682 -3.933802 29.758560 1058 | 999 2002-09-26 -11.856774 -10.671012 -3.216025 29.369368 1059 | 1060 | [1000 rows x 5 columns] 1061 | 1062 | - HDF5 1063 | `HDFStores `_ 1064 | 1065 | 1.写入HDF5 Store:: 1066 | 1067 | >>> df.to_hdf('foo.h5','df') 1068 | 1069 | 2.从HDF5 Store读取:: 1070 | 1071 | >>> pd.read_hdf('foo.h5','df') 1072 | A B C D 1073 | 2000-01-01 0.266457 -0.399641 -0.219582 1.186860 1074 | 2000-01-02 -1.170732 -0.345873 1.653061 -0.282953 1075 | 2000-01-03 -1.734933 0.530468 2.060811 -0.515536 1076 | 2000-01-04 -1.555121 1.452620 0.239859 -1.156896 1077 | 2000-01-05 0.578117 0.511371 0.103552 -2.428202 1078 | 2000-01-06 0.478344 0.449933 -0.741620 -1.962409 1079 | 2000-01-07 1.235339 -0.091757 -1.543861 -1.084753 1080 | ... ... ... ... ... 1081 | 2002-09-20 -10.628548 -9.153563 -7.883146 28.313940 1082 | 2002-09-21 -10.390377 -8.727491 -6.399645 30.914107 1083 | 2002-09-22 -8.985362 -8.485624 -4.669462 31.367740 1084 | 2002-09-23 -9.558560 -8.781216 -4.499815 30.518439 1085 | 2002-09-24 -9.902058 -9.340490 -4.386639 30.105593 1086 | 2002-09-25 -10.216020 -9.480682 -3.933802 29.758560 1087 | 2002-09-26 -11.856774 -10.671012 -3.216025 29.369368 1088 | 1089 | [1000 rows x 4 columns] 1090 | 1091 | - Excel 1092 | `MS Excel `_ 1093 | 1094 | 1.写入excel文件:: 1095 | 1096 | >>> df.to_excel('foo.xlsx', sheet_name='Sheet1') 1097 | 1098 | 2.从excel文件读取:: 1099 | 1100 | >>> pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA']) 1101 | A B C D 1102 | 2000-01-01 0.266457 -0.399641 -0.219582 1.186860 1103 | 2000-01-02 -1.170732 -0.345873 1.653061 -0.282953 1104 | 2000-01-03 -1.734933 0.530468 2.060811 -0.515536 1105 | 2000-01-04 -1.555121 1.452620 0.239859 -1.156896 1106 | 2000-01-05 0.578117 0.511371 0.103552 -2.428202 1107 | 2000-01-06 0.478344 0.449933 -0.741620 -1.962409 1108 | 2000-01-07 1.235339 -0.091757 -1.543861 -1.084753 1109 | ... ... ... ... ... 1110 | 2002-09-20 -10.628548 -9.153563 -7.883146 28.313940 1111 | 2002-09-21 -10.390377 -8.727491 -6.399645 30.914107 1112 | 2002-09-22 -8.985362 -8.485624 -4.669462 31.367740 1113 | 2002-09-23 -9.558560 -8.781216 -4.499815 30.518439 1114 | 2002-09-24 -9.902058 -9.340490 -4.386639 30.105593 1115 | 2002-09-25 -10.216020 -9.480682 -3.933802 29.758560 1116 | 2002-09-26 -11.856774 -10.671012 -3.216025 29.369368 1117 | 1118 | [1000 rows x 4 columns] 1119 | 1120 | 小陷阱 1121 | ------ 1122 | 如果你操作时遇到了如下异常:: 1123 | 1124 | >>> if pd.Series([False, True, False]): 1125 | ... print("I was true") 1126 | ... 1127 | Traceback (most recent call last): 1128 | File "", line 1, in 1129 | File "/usr/lib64/python2.7/site-packages/pandas/core/generic.py", line 730, in __nonzero__ 1130 | .format(self.__class__.__name__)) 1131 | ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all(). 1132 | 1133 | 请查看 `Comparisons `_ 来处理异常 1134 | 查看 `Gotchas `_ 也可以 1135 | 1136 | pandas实战 1137 | ---------- 1138 | -------------------------------------------------------------------------------- /docs/python-questions-on-stackoverflow.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-04-17 20:52:01 2 | 3 | =============================== 4 | Stackoverflow上的Python问题精选 5 | =============================== 6 | 7 | :Author: hit9 8 | :注1: 以下问题来自Stackoverflow, 但不完全一致 9 | :注2: 欢迎fork向本文添加内容, 文章在Github上,地址见首页。 10 | 11 | .. Contents:: 12 | 13 | 不能直接给对象设置属性? 14 | ----------------------- 15 | 16 | :: 17 | 18 | >>> obj = object() 19 | >>> obj.name = "whatever" 20 | Traceback (most recent call last): 21 | File "", line 1, in 22 | AttributeError: 'object' object has no attribute 'name' 23 | 24 | 但是为什么这样就可以呢:: 25 | 26 | >>> class Object(object):pass 27 | ... 28 | >>> Obj = Object() 29 | >>> Obj.name = "whatever" 30 | >>> Obj.name 31 | 'whatever' 32 | >>> 33 | 34 | 答: 现在你给第二个代码块中的Object加上属性 ``__slots__`` 试试:: 35 | 36 | >>> class Object(object): 37 | ... __slots__ = {} 38 | ... 39 | >>> Obj = Object() 40 | >>> Obj.name = "whatever" 41 | Traceback (most recent call last): 42 | File "", line 1, in 43 | AttributeError: 'Object' object has no attribute 'name' 44 | 45 | 会发现抛出了同样的异常。 ``object`` 、 ``list`` 、 ``dict`` 等内置函数都如此。 46 | 47 | 拥有 ``__slots__`` 属性的类在实例化对象时不会自动分配 ``__dict__`` ,而 ``obj.attr`` 即 ``obj.__dict__['attr']``, 48 | 所以会引起 ``AttributeError`` 49 | 50 | 对于拥有 ``__slots__`` 属性的类的实例 ``Obj`` 来说,只能对 ``Obj`` 设置 ``__slots__`` 中有的属性:: 51 | 52 | >>> class Object(object): 53 | ... __slots__ = {"a","b"} 54 | ... 55 | >>> Obj = Object() 56 | >>> Obj.a = 1 57 | >>> Obj.a 58 | 1 59 | >>> Obj.c = 1 60 | Traceback (most recent call last): 61 | File "", line 1, in 62 | AttributeError: 'Object' object has no attribute 'c' 63 | 64 | 详细见 Python-slots-doc_ 65 | 66 | .. _Python-slots-doc: http://docs.python.org/2/reference/datamodel.html#__slots__ 67 | 68 | 如何打印一个对象的所有属性和值的对 69 | ---------------------------------- 70 | 71 | 原问题: http://stackoverflow.com/questions/1251692/how-to-enumerate-an-objects-properties-in-python 72 | 73 | 答: 74 | 75 | :: 76 | 77 | for property, value in vars(theObject).iteritems(): 78 | print property, ": ", value 79 | 80 | 这个做法其实就是 ``theObject.__dict__`` , 也就是 ``vars(obj)`` 其实就是返回了 ``o.__dict__`` 81 | 82 | 另一个做法: ``inspect.getmembers(object[, predicate])`` :: 83 | 84 | >>> import inspect 85 | >>> for attr, value in inspect.getmembers(obj): 86 | ... print attr, value 87 | 88 | 两者不同的是, ``inspect.getmembers`` 返回的是元组 ``(attrname, value)`` 的列表。而且是所有的属性, 包括 ``__class__`` , ``__doc__`` , 89 | ``__dict__`` , ``__init__`` 等特殊命名的属性和方法。而 ``vars()`` 只返回 ``__dict__``. 对于一个空的对象来说, ``__dict__`` 会是 ``{}`` 90 | , 而 ``inspect.getmembers`` 返回的不是空的。 91 | 92 | 93 | 类的__dict__无法更新? 94 | --------------------- 95 | 96 | :: 97 | 98 | >>> class O(object):pass 99 | ... 100 | >>> O.__dict__["a"] = 1 101 | Traceback (most recent call last): 102 | File "", line 1, in 103 | TypeError: 'dictproxy' object does not support item assignment 104 | 105 | 答: 是的, class的 ``__dict__`` 是只读的:: 106 | 107 | >>> class O(object):pass 108 | ... 109 | >>> O.__dict__ 110 | 111 | >>> O.__dict__.items() 112 | [('__dict__', ), ('__module__', '__main__'), ('__weakref__', ), ('__doc__', None)] 113 | >>> O.func = lambda: 0 114 | >>> O.__dict__.items() 115 | [('__dict__', ), ('__module__', '__main__'), ('__weakref__', ), ('__doc__', None), ('func', at 0xb76de224>)] 116 | >>> O.func 117 | > 118 | 119 | 可以看到 ``O.__dict__`` 是一个 ``dictproxy`` 对象,而不是一个 ``dict`` . (你可以 ``dir(O.__dict__)`` ,但不会发现有它有属性 ``__setitem__`` ) 120 | 121 | 那我们怎么给类设置属性呢? 用 ``setattr`` :: 122 | 123 | >>> setattr(O, "a", 1) 124 | >>> O.a 125 | 1 126 | 127 | unbound method和bound method 128 | ----------------------------- 129 | 130 | :: 131 | 132 | >>> class C(object): 133 | ... def foo(self): 134 | ... pass 135 | ... 136 | >>> C.foo 137 | 138 | >>> C().foo 139 | > 140 | >>> 141 | 142 | 为什么 ``C.foo`` 是一个 ``unbound method`` , ``C().foo`` 是一个 ``bound method`` ? Python 为什么这样设计? 143 | 144 | 答:这是问题 http://stackoverflow.com/questions/114214/class-method-differences-in-python-bound-unbound-and-static 145 | 146 | 来自Armin Ronacher(Flask 作者)的回答: 147 | 148 | 如果你明白python中描述器(descriptor)是怎么实现的, 方法(method) 是很容易理解的。 149 | 150 | 上例代码中可以看到,如果你用类 ``C`` 去访问 ``foo`` 方法,会得到 ``unbound`` 方法,然而在class的内部存储中它是个 ``function``, 为什么? 151 | 原因就是 ``C`` 的类 (注意是类的类) 实现了一个 ``__getattribute__`` 来解析描述器。听起来复杂,但并非如此。上例子中的 ``C.foo`` 等价于:: 152 | 153 | >>> C.__dict__['foo'].__get__(None, C) 154 | 155 | 156 | 这是因为方法 ``foo`` 有个 ``__get__`` 方法,也就是说, 方法是个描述器。如果你用实例来访问的话也是一模一样的:: 157 | 158 | >>> c = C() 159 | >>> C.__dict__['foo'].__get__(c, C) 160 | > 161 | 162 | 只是那个 ``None`` 换成了这个实例。 163 | 164 | 现在我们来讨论,为什么Python要这么设计? 165 | 166 | 其实,所谓 ``bound method`` ,就是方法对象的第一个函数参数绑定为了这个类的实例(所谓 ``bind`` )。这也是那个 ``self`` 的由来。 167 | 168 | 当你不想让类把一个函数作为一个方法,可以使用装饰器 ``staticmethod`` :: 169 | 170 | >>> class C(object): 171 | ... @staticmethod 172 | ... def foo(): 173 | ... pass 174 | ... 175 | >>> C.foo 176 | 177 | >>> C.__dict__['foo'].__get__(None, C) 178 | 179 | 180 | ``staticmethod`` 装饰器会让 ``foo`` 的 ``__get__`` 返回一个函数,而不是一个方法。 181 | 182 | 那么,function,bound method 和 unbound method 的区别是什么? 183 | --------------------------------------------------------------- 184 | 185 | 一个函数(function)是由 ``def`` 语句或者 ``lambda`` 创建的。 186 | 187 | 当一个函数(function)定义在了class语句的块中(或者由 ``type`` 来创建的), 它会转成一个 ``unbound method`` , 当我们通过一个类的实例来 188 | 访问这个函数的时候,它就转成了 ``bound method`` , ``bound method`` 会自动把这个实例作为函数的地一个参数。 189 | 190 | 所以, ``bound method`` 就是绑定了一个实例的方法, 否则叫做 ``unbound method`` .它们都是方法(method), 是出现在 ``class`` 中的函数。 191 | 192 | 193 | 194 | 什么是metaclass 195 | --------------- 196 | 197 | 这是stackoverflow投票很高的问题: http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python 198 | 199 | 回答: (最高得分的答案) 200 | 201 | 什么叫做元类 202 | ============ 203 | 204 | Metaclass是创建class的东西。 205 | 206 | 一个class是用来创建对象的是不是? 207 | 208 | 但是我们知道,Python中的类也是对象。 209 | 210 | Metaclass就是用来创建类这些对象的,它们是类的类,你可以形象化地理解为:: 211 | 212 | MyClass = MetaClass() 213 | MyObject = MyClass() 214 | 215 | 你知道, ``type`` 函数可以这样使用:: 216 | 217 | MyClass = type('MyClass', (), {}) 218 | 219 | 这是因为 ``type`` 实际上是个 ``metaclass`` , Python使用 ``type`` 这个元类来创建所有的类。 220 | 221 | 现在你是不是有疑问了,为什么 ``type`` 是小写开头的,而不是 ``Type`` 呢?既然它是个元类! 222 | 223 | 我猜,大概是因为和 ``str`` , ``int`` 来保持一致吧, ``str`` 也是一个类,用来创建字符串。 224 | 225 | 你可以检查下对象的 ``__class__`` 属性来看看它们的类是谁. Python中万物都是对象:: 226 | 227 | >>> age = 35 228 | >>> age.__class__ 229 | 230 | >>> name = 'bob' 231 | >>> name.__class__ 232 | 233 | >>> def foo():pass 234 | ... 235 | >>> foo.__class__ 236 | 237 | >>> class Bar(object): pass 238 | ... 239 | >>> b = Bar() 240 | >>> b.__class__ 241 | 242 | >>> 243 | 244 | 那么, ``__class__`` 的 ``__class__`` 属性又是谁? :: 245 | 246 | >>> a = 1 247 | >>> a.__class__.__class__ 248 | 249 | >>> name = 'bob' 250 | >>> name.__class__.__class__ 251 | 252 | 253 | 所以,元类是用来创建类的。 254 | 255 | 你可以叫元类为 *类工厂* 256 | 257 | ``type`` 是Python使用的内建元类,当然,Python允许大家建立自己的元类. 258 | 259 | __metaclass__ 属性 260 | ================== 261 | 262 | 你可以在写一个类的时候加上这个属性 ``__metaclass__`` :: 263 | 264 | class Foo(object): 265 | __metaclass__ = something... 266 | [...] 267 | 268 | 这样的话,Python就会用这个元类(上例中为 ``something`` ) 来创建类 ``Foo`` 269 | 270 | 我们首先写的是 ``class Foo(object)`` ,但是Python跑到这里看到这一行时,并没有在内存中建立类 ``Foo`` 271 | 272 | **因为Python这么做的:查找它有没有 ``__metaclass__`` 属性,有的话,用指定的类来创建 ``Foo`` ,否则(也就是一般情形下),使用 ``type`` 来创建** 273 | 274 | 最好还是记住上面那句话 :) 275 | 276 | 当你这么写的时候:: 277 | 278 | class Foo(Bar): 279 | pass 280 | 281 | Python会这么做: 282 | 283 | 1. 有 ``__metaclass__`` 定义吗? 如果有,在内存中建立一个类的对象。用 ``__metaclass__`` 指定的类来创建。 284 | 285 | 2. 如果没有找到这个属性,它会继续在父类 ``Bar`` 中找 286 | 287 | 3. 这样一直向父类找,父类的父类。。。直到 module 级别的才停止。 288 | 289 | 4. 如果在任何的父类中都找不到,那就用 ``type`` 创建 ``Foo`` 290 | 291 | 现在一个问题,我们可以给 ``__metaclass__`` 赋值什么呢? 292 | 293 | 答案当然是,一个可以创建类的东西。 294 | 295 | 那么,什么才能创建一个类呢? 296 | 297 | 普通的元类 298 | ========== 299 | 300 | 设计元类的主要目的就是允许我们在类创建的时候动态的修改它,这经常用在API的设计上。 301 | 302 | 让我们举一个很纯的例子,比如你想要让一个模块中的所有类都共享一些属性,有很多办法可以做到,其中一个就是 303 | 在模块中定义一个 ``__metaclass__`` 属性。 304 | 305 | 这样,模块中所有的类都会被 ``__metaclass__`` 创建。 306 | 307 | 幸运的是 , ``__metaclass__`` 可以是任何可以被调用的对象。不非要是个class,还可以是个函数。 308 | 309 | 所以,我们这么做,用一个函数来作为metaclass:: 310 | 311 | # the metaclass will automatically get passed the same argument 312 | # that you usually pass to `type` 313 | def upper_attr(future_class_name, future_class_parents, future_class_attr): 314 | """ 315 | Return a class object, with the list of its attribute turned 316 | into uppercase. 317 | """ 318 | 319 | # pick up any attribute that doesn't start with '__' 320 | attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) 321 | # turn them into uppercase 322 | uppercase_attr = dict((name.upper(), value) for name, value in attrs) 323 | 324 | # let `type` do the class creation 325 | return type(future_class_name, future_class_parents, uppercase_attr) 326 | 327 | __metaclass__ = upper_attr # this will affect all classes in the module 328 | 329 | class Foo(): # global __metaclass__ won't work with "object" though 330 | # but we can define __metaclass__ here instead to affect only this class 331 | # and this will work with "object" children 332 | bar = 'bip' 333 | 334 | print hasattr(Foo, 'bar') 335 | # Out: False 336 | print hasattr(Foo, 'BAR') 337 | # Out: True 338 | 339 | f = Foo() 340 | print f.BAR 341 | # Out: 'bip' 342 | 343 | 344 | 现在我们用一个类来作为一个metaclass:: 345 | 346 | # remember that `type` is actually a class like `str` and `int` 347 | # so you can inherit from it 348 | class UpperAttrMetaclass(type): 349 | # __new__ is the method called before __init__ 350 | # it's the method that creates the object and returns it 351 | # while __init__ just initializes the object passed as parameter 352 | # you rarely use __new__, except when you want to control how the object 353 | # is created. 354 | # here the created object is the class, and we want to customize it 355 | # so we override __new__ 356 | # you can do some stuff in __init__ too if you wish 357 | # some advanced use involves overriding __call__ as well, but we won't 358 | # see this 359 | def __new__(upperattr_metaclass, future_class_name, 360 | future_class_parents, future_class_attr): 361 | 362 | attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) 363 | uppercase_attr = dict((name.upper(), value) for name, value in attrs) 364 | 365 | return type(future_class_name, future_class_parents, uppercase_attr) 366 | 367 | 368 | 但这样并不是很 OOP , 我们可以直接调用 ``type`` 函数,并且不覆盖父亲的 ``__new__`` 方法:: 369 | 370 | class UpperAttrMetaclass(type): 371 | 372 | def __new__(upperattr_metaclass, future_class_name, 373 | future_class_parents, future_class_attr): 374 | 375 | attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) 376 | uppercase_attr = dict((name.upper(), value) for name, value in attrs) 377 | 378 | # reuse the type.__new__ method 379 | # this is basic OOP, nothing magic in there 380 | return type.__new__(upperattr_metaclass, future_class_name, 381 | future_class_parents, uppercase_attr) 382 | 383 | 你可能注意到了参数 ``upperattr_metaclass`` ,没什么特殊的,一个方法总是拿那个实例来作为第一个参数。就像寻常的 ``self`` 参数。 384 | 385 | 当然,可以这么写,我上面的例子命名不那么好:) :: 386 | 387 | class UpperAttrMetaclass(type): 388 | 389 | def __new__(cls, name, bases, dct): 390 | 391 | attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) 392 | uppercase_attr = dict((name.upper(), value) for name, value in attrs) 393 | 394 | return type.__new__(cls, name, bases, uppercase_attr) 395 | 396 | 397 | 我们可以使用 ``super`` 函数来让这个例子变得更简洁:: 398 | 399 | class UpperAttrMetaclass(type): 400 | 401 | def __new__(cls, name, bases, dct): 402 | 403 | attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) 404 | uppercase_attr = dict((name.upper(), value) for name, value in attrs) 405 | 406 | return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr) 407 | 408 | 元类是个简单的魔术,只要: 409 | 410 | - 注入类的创建 411 | 412 | - 修改类 413 | 414 | - 返回修改后的类 415 | 416 | 那么你为什么用类来作为metaclass而不是函数 417 | ========================================= 418 | 419 | 既然 ``__metaclass__`` 可以是任何可以被调用的对象,那么你为什么用类作为metaclass而不是函数呢? 420 | 421 | 几个原因: 422 | 423 | - 更能清楚的表达意图 424 | 425 | - 可以使用OOP, metaclass可以继承,重写父类,甚至使用metaclass,可以使用面向对象的特性。 426 | 427 | - 更好的组织代码. 428 | 429 | - .. 等等,译者不再多写了~ :) 430 | 431 | 应用场景 432 | ======== 433 | 434 | 一个典型例子,Django ORM (译者注,peewee也用metaclass):: 435 | 436 | class Person(models.Model): 437 | name = models.CharField(max_length=30) 438 | age = models.IntegerField() 439 | 440 | 但是你这么做:: 441 | 442 | guy = Person(name='bob', age='35') 443 | print guy.age 444 | 445 | 并不返回一个 ``IntegerField`` 对象,而是一个 ``int`` 446 | 447 | 结束语 448 | ====== 449 | 450 | Python的世界里,万物都是对象 451 | 452 | 但是 ``type`` 是它自己的元类。 453 | 454 | 99%的情形下你不需要用这个东西。 455 | 456 | 457 | 为什么Python没有unzip函数 458 | ------------------------- 459 | 460 | 众所周知, ``zip`` 函数可以把多个序列打包到元组中:: 461 | 462 | >>> a, b = [1, 2, 3], [4, 5, 6] 463 | >>> c = zip(a, b) 464 | >>> c 465 | [(1, 4), (2, 5), (3, 6)] 466 | 467 | 468 | 那么为什么没有这样的 ``unzip`` 函数来把 ``[(1, 4), (2, 5), (3, 6)]`` 还原呢? 469 | 470 | 答: Python中有个很神奇的操作符 ``*`` 来 ``unpack`` 参数列表:: 471 | 472 | >>> zip(*c) 473 | [(1, 2, 3), (4, 5, 6)] 474 | 475 | Python中的新式类和老式类 476 | ------------------------ 477 | 478 | 无论你怎么叫吧, 英文来说是 ``new style class`` 和 ``old style class`` 479 | 480 | 问题链接: http://stackoverflow.com/questions/54867/old-style-and-new-style-classes-in-python 481 | 482 | 新式类是继承自 ``object`` 或其他新式类的类:: 483 | 484 | class NewStyleClass(object): 485 | pass 486 | 487 | class AnotherNewStyleClass(NewStyleClass): 488 | pass 489 | 490 | 否则是老式类:: 491 | 492 | class OldStyleClass(): 493 | pass 494 | 495 | **为什么引入新式类?** 496 | 497 | > The major motivation for introducing new-style classes is to provide a unified object model with a full meta-model. 498 | 499 | Python为了提供一个更完整的元模型。(好吧,译者也不大明白,不过我知道Python中很神奇的描述器只能在新式类里用) 500 | 501 | 502 | 译者注 503 | ------ 504 | 505 | 本文Github地址: https://github.com/HIT-ON-Github/PyZh/blob/master/docs/python-questions-on-stackoverflow.rst 506 | 507 | 欢迎Fork追加问题。 508 | -------------------------------------------------------------------------------- /docs/python-realtime.rst: -------------------------------------------------------------------------------- 1 | Python Realtime 2 | =============== 3 | 4 | :英文原文: http://mrjoes.github.io/2013/06/21/python-realtime.html 5 | :译文原文: http://taokeqin.com/python-realtime.html 6 | :翻译: `陶克勤 `_ 7 | 8 | .. Contents:: 9 | 10 | 简介 11 | ---- 12 | 13 | 最近我参与了 `Flask book `_ 14 | 的一次聚会,涉及到些realtime相关的问题-它是如何工作的, 15 | 如何整合实时部分与传统的WSGI应用,如何组织应用程序代码等。 16 | 17 | 我们使用Google Hangouts,原本打算将我们的Hangouts视频录下来,但失败了。 所以,我决定还是写一篇详尽的博客文章, 18 | 其中包括一些基础知识,以及简短的介绍用Python异步编程等。 19 | 20 | 一点理论知识 21 | ------------ 22 | 23 | 让我们尝试解决服务器"推送"的问题。web都是拉取数据 - 浏览器发出请求到服务器,服务器会产生并发送回响应。 24 | 但是,如果有需要将数据主动从服务器推送到浏览器该怎么办呢? 25 | 26 | 解决方法很简单:浏览器发出AJAX请求到服务器,并要求更新。 虽然看起来这个是常用浏览器和服务器之间推送办法,但有一个问题: 27 | 28 | 如果服务器没有什么要发送,它会保持连接打开,直到为客户提供一些数据。 客户端收到响应后,它会发出另一个请求,以获得更多的数据。 29 | 30 | 这种技术被称为 **长轮询** (long-polling) 31 | 32 | 显然,这方法不不太高效。在大多数情况下,噪信比是非常高的 (无用数据比有用数据-因为这样一来更多的时间是花在处理HTTP请求(比如解析和验证报头) 33 | 而不是实际数据发送到客户端。 34 | 35 | 但是,不幸的是,它是目前将数据推送到客户端最适合的方式。 36 | 37 | 基于HTTP/1.1情况好转了一点。TCP连接可以使用 `Keep-Alive `_ 头,默认情况下, 38 | 连接在请求发起后将保持打开状态。 此功能使长轮询延迟得到了降低,这样就没有必要为每个轮询请求重新打开TCP连接。 39 | 40 | HTTP/1.1还引入了 `块传输协议 `_ 。 它允许讲响应分解为成更小的数据块, 41 | 并将它们立即发送到客户端,而不是一直等到完成HTTP请求。 42 | 43 | HTTP/1.1还引入了 块传输协议 。 它允许讲响应分解为成更小的数据块,并将它们立即发送到客户端,而不是一直等到完成HTTP请求。 44 | 45 | 不幸的是,有些不兼容这个功能的代理服务器还是试图在转发之前缓存整个响应, 所以客户端将不会收到任何数据,直到代理认为HTTP请求已经完成。 46 | 虽然看起来web还是能"正常"工作 - 因为客户端最终还是会得到来自服务器的响应, 但它打破了为实时而设计的块传输协议的整体思路。 47 | 48 | Opera在2006年9月,为它的浏览器实现了实验性的 `服务事件发送 `_ 功能。 虽然SSE和块传输协议很相似, 49 | 但还是是不同的协议,而且有更好的客户端API。 50 | 51 | 2009年4月23日,SSE得到WHATWG批准,得到几乎所有的现代的桌面浏览器(Internet Explorer的除外)的支持。 52 | 在这个链接你可以 `看到兼容性图表 `_ 。 53 | 54 | 还有其他的技术,比如 `forever-iframe `_ , 55 | 这是两种可以为Internet Explorer版本低于8做跨域推送的技术之一(另一个是 `jsonp `_ -polling), 56 | 以及 `HTMLFILE `_ 等 57 | 58 | 总之,所有这些基于HTTP的折中方案都可以叫做 `Comet `_ 。 59 | 60 | 让我们来看看这些方法的利弊: 61 | 62 | * 长轮询(Long-polling)是昂贵的,但兼容性很好。 63 | * 块传输协议效率更高,但有可能不是所有客户端都能正常工作,并且如果没有某种形式的探测你都无法发现这个问题的存在。 64 | * SSE也不错,但不是所有的浏览器都支持。比较好的是有办法在建立连接前就知道它是否支持。 65 | 66 | 但是,所有这些方法都有一个问题: 他们都只提供一种方式将数据从服务器推送到客户端,而不是在建立双向通信, 客户端每次想发送一些数据的时候, 67 | 将不得不使用AJAX请求到服务器。 这样会增加延迟,并在服务器也会产生额外的负载。 68 | 69 | 邂逅WebSockets 70 | -------------- 71 | 72 | 虽然WebSockets的不是什么新技术,但经历了几个不兼容的迭代后该规范终于通过了,RFC编号: `RFC-6455 `_ 。 73 | 74 | 简而言之,基于WebSocket的服务器和客户端之间建立的是基于TCP的双向连接。 连接的建立建立使用兼容HTTP的握手协议(加上额外的WebSocket相关的头), 75 | 并具有额外的协议层次划分, 所以它也不仅仅是一个从浏览器中打开原始的TCP连接。 76 | 77 | WebSocket协议最大的问题是浏览器支持,防火墙,代理服务器和防病毒应用的支持。 78 | 79 | 这个链接有浏览器的 `兼容性图表 `_ 。 80 | 81 | 企业防火墙和代理服务器通常因各种原因阻止的WebSocket连接。 82 | 83 | 有些代理服务器不能处理WebSocket在端口80上连接 - 他们认为这是一般的HTTP请求,并尝试缓存它。 有HTTP扫描组件的杀毒软件也不允许WebSocket连接。 84 | 85 | 无论如何,WebSocket的来建立客户端和服务器端之间的双向通信是最好的方式,但不能单一的用来解决推送问题。 86 | 87 | 用例 88 | ---- 89 | 90 | 91 | 鉴于以上情况,如果您的应用程序大多是从服务器推送数据,基于HTTP的传输会工作得很好。 92 | 93 | 但是,如果浏览器支持WebSocket的传输并且WebSocket的连接是可以建立的,它将是更好的选择。 94 | 95 | 总而言之,最好的办法是:尝试打开的WebSocket连接,如果失败 - 尝试回退到基于HTTP传输。 当然也有可以"升级"连接 - 首先使用长轮询(long-polling), 96 | 然后尝试建立WebSocket的连接。 如果成功,就切换到WebSocket的连接。 虽然这种做法可能会降低初始连接的时间,需要注意服务器端实现, 97 | 以避免但在两者连接之间切换时发生任何的跳变情况(race conditions)。 98 | 99 | Polyfill库 100 | ---------- 101 | 幸运的是,你没有必要自己实现这一切。 为所有已知的浏览器提供变通方案,搞定代理和防火墙的奇怪问题,尤其是从头开始搞这些事情,是非常困难的。 102 | 已经有人投入人多年的工作使他们的解决方案尽可能稳定。 103 | 104 | 有一些 `polyfill `_ 库,像 `SockJS `_ 库 , `Socket.IO `_ 库 , `Faye `_ 105 | 和其他一些框架,实现了基于各种不同的传输实现上的类WebSocket的 API。 106 | 107 | 虽然他们所提供的服务器和客户端API不尽相同,但他们有着共同的理念: 在给定的情况下用最好的传输方案,并且提供一致的服务器端API。 108 | 109 | 例如,如果浏览器支持WebSocket协议,polyfill将尝试建立WebSocket连接。 如果失败了,他们将下降回到下一个最好的传输协议。 110 | `Engine.IO `_ 使用稍微不同的方法 - 他先建立长轮询连接(long-polling),并尝试在后台升级到WebSocket。 111 | 112 | 在任何情况下 - 这些库将尝试建立双向连接到服务器上使用最可靠的传输。 113 | 114 | 不幸的是,在使用Socket.IO 0.8.x的时候有较差的体验。 我一般在我自己的项目中使用 `sockjs-tornado `_ , 115 | 即使我自己写了 `TornadIO2 `_ 。 116 | Socket.IO早期的server实现是基于 `Tornado `_ 的。 117 | 118 | 服务器端 119 | -------- 120 | 121 | 让我们回到Python。 122 | 123 | 不幸的是,基于 `WSGI `_ 服务器不能被用于创建实时应用,因为WSGI协议是同步的。 124 | WSGI服务器一次只能处理一个请求。 125 | 126 | 让我们再次回顾长轮询(long-polling)传输: 127 | 128 | * 客户端打开HTTP连接到服务器,以获得更多的数据 129 | 130 | * 无可用数据,服务器保持连接打开并等待数据发送 131 | 132 | * 因为服务器无法处理任何其他请求,一切都被阻塞 133 | 134 | 用伪代码,它会看起来像这样:: 135 | 136 | def handle_request(request): 137 | data = get_more_data(request) 138 | return send_response(data) 139 | 140 | 如果get_more_data阻塞了,那整个服务器就会被阻塞,不能处理请求了。 141 | 142 | 当然,可以每个请求创建线程,但这非常低效。 143 | 144 | 虽然有一些变通办法(如 `Armin Ronacher `_ 所描述的方法,以及一些相关的变种将在稍后讨论, 145 | 异步执行模式更适合这个任务。 146 | 147 | 在异步执行模式中,服务器处理依然在一个线程中顺序处理请求,但当处理程序无事可做的时候, 可以将控制转移到另一个请求处理程序。 148 | 149 | 在这种情况下,长轮询(long-polling)传输将看起来像这样: 150 | 151 | * 客户端打开HTTP连接到服务器,以获得更多的数据 152 | 153 | * 没有可用数据,服务器保持打开TCP连接,并同时做别的东西 154 | 155 | * 当有数据要发送时,服务器发送数据然后关闭连接 156 | 157 | Greenlets 158 | --------- 159 | 160 | 在Python中有两种方法编写异步代码: 161 | 162 | - 使用 `corutines `_ (也称为greenlets) 163 | - 使用 `回调 `_ 164 | 165 | 简而言之,greenlets让你写出能在执行过程中暂停的功能,然后还可以继续执行。 166 | 167 | Greenlet的实现是从 `Stackless Python `_ 向后移植到CPython的。 168 | 虽然有greenlet模块的CPython看起来和Stackless Python是相同的 - 但他确实不是。 Stackless Python有两种上下文切换模式:软切换,硬切换。 169 | 软切换涉及到Python应用程序堆栈的切换(就是指针互换,快速并且容易)和硬切换需要堆栈分片(慢而且容易出错)。 Greenlet 基本上就是移植Stackless的硬切换模式。 170 | 171 | 让我们再次看看长轮询(long-polling)的例子,这次基于greenlets: 172 | 173 | 1. 客户端打开HTTP连接到服务器,以获得更多的数据 174 | 175 | 2. 服务器启动新的greenlet用来处理长轮询逻辑 176 | 177 | 3. 没有数据要发送,greenlet开始休眠,暂停当前正在执行的功能 178 | 179 | 4. 当有数据要发生的时候,greenlet唤醒,发送数据然后关闭连接 180 | 181 | 182 | 用伪代码,它看起来和同步版本完全一样:: 183 | 184 | def handle_request(request): 185 | # 如果这里没有数据, greenlet 就会休眠 186 | # 然后切换到其他greenlet执行 187 | data = get_more_data(request) 188 | return make_response(data) 189 | 190 | 为什么greenlets很重要? 191 | 192 | 因为它们允许以同步的方式编写异步代码。 他们允许异步的使用现有的同步库。greenlet的实现隐藏了上下文切换的问题。 193 | 194 | `GEVENT `_ 是可以用greenlets实现的很好的例子。 这个框架补充了Python标准库,引入了异步IO(输入输出), 195 | 在没有明显的上下文切换下使得所有代码异步。 196 | 197 | 另一方面,greenlet的 CPython实现也是相当可怕的。 198 | 199 | 每个协程都有自己的堆栈。CPython使用非托管堆栈的Python应用, 当Python程序运行时堆栈看起来像烤宽面条 - 解释器数据,本地模块的数据,Python应用程序的数据, 200 | 一切以随机顺序分层混合在一起。 在这种情况下,想要预留堆栈并且想无痛的做协同程序之间的上下文切换是相当困难的,因为很难预测在栈上保存的到底什么。 201 | 202 | Greenlet试图通过把一部分栈数据复制到堆,然后复制回来的方法克服这一限制。 虽然大多数情况下它是可以工作的,但任何未经测试的第三方库与原生扩展, 203 | 都有可能会产生奇怪的错误,如栈或堆破坏。 204 | 205 | 基于greenlets的代码实现方式也不太像线程。 因为他更容易造成死锁,代码实现中调用者其实并不希望调用的函数去暂停greenlet,但是这个函数却把greenlet暂停了,调用者将没有机会释放锁。 206 | 207 | 回调 208 | ---- 209 | 210 | 做上下文切换的另一种方法是使用回调。以长轮询(long-polling)为例: 211 | 212 | 1. 客户端打开HTTP连接到服务器,以获得更多的数据 213 | 214 | 2. 服务器发现有没有数据发送 215 | 216 | 3. 服务器等待数据,并传入当有数据的时候应该被调用的callback函数 217 | 218 | 4. 服务器发送响应的回调函数,并关闭连接 219 | 220 | 伪代码:: 221 | 222 | def handle_request(request): 223 | get_more_data(request, callback=on_data) 224 | 225 | def on_data(request): 226 | send_response(request, make_response(data)) 227 | 228 | 229 | 正如你看到的,工作流是相似的,但代码结构有所不同。 230 | 231 | 不幸的是,回调不是很直观,而且调试基于回调的大型应用程序简直就是个噩梦。 此外,这种方式很难让现有的"阻塞"类库与异步应用一起使用, 232 | 除非做一些重写或使用某种形式的线程池。 例如, `Motor `_ , 233 | 为Tornado用混合方式实现的异步MongoDB的驱动程序-它用greenlets封装了 IO,但提供了与Tornado兼容的异步API。 234 | 235 | Futures 236 | -------- 237 | 238 | 有不同的方法来完善使用回调的情况: 239 | 240 | - 使用futures 241 | 242 | - 使用generators 243 | 244 | 什么是Futures?首先,Futures是一个函数的返回值,它是一个对象,它有以下几个属性: 245 | 246 | 1. 函数执行的状态(idling, running,停止等) 247 | 248 | 2. 返回值(如果函数尚未执行,可能是空的) 249 | 250 | 3. 各种方法: cancel(),以防止执行,add_done_callback方法,当绑定函数执行完毕时注册回调函数等。 251 | 252 | 您可以看看这篇优秀的 `博客文章 `_ , 253 | 其中比较了promises和回调,以及为什么对于写更好的异步代码来说promises优于纯写回调。 254 | 255 | Generators 256 | ---------- 257 | 258 | Python生成器也可让写异步程序的程序员更快乐一点。 我们还是看长轮询的例子,但这次我们基于生成器(请注意,从Python 3.3开始会允许从生成器返回值):: 259 | 260 | @coroutine 261 | def handle_request(request): 262 | data = yield get_more_data(request) 263 | return make_response(data) 264 | 265 | 266 | 正如你可以看到,生成器允许编写的异步代码有点像同步方式。查看 `PEP 342 `_ 获取的更多信息。 267 | 268 | 生成器最大的问题:程序员在还没有调用这个函数之前必须要知道他是否是异步函数。 269 | 270 | 看下面的例子:: 271 | 272 | @coroutine 273 | def get_mode_data(request): 274 | data = yield make_db_query(request.user_id) 275 | return data 276 | 277 | def process_request(request): 278 | data = get_more_data(request) 279 | return data 280 | 281 | 这行代码不会得到预期的效果,在python调用生成器函数返回的生成器器对象不包含执行的内容。 在这种情况下,process_request也应该变为为异步用coroutine装饰器封装并且应该从get_more_data产生。 另一种方法 - 使用框架功能运行异步函数(如通过回调或Future回调)的能力。 282 | 283 | 另一个问题 - 如果有必要使现有的函数异步,它的所有的调用者都应更新。 284 | 285 | 总结 286 | ---- 287 | 288 | Greenlets使一切变得"容易",但其代价是你可能遇到问题,并要允许隐式上下文切换。 289 | 290 | 使用回调的代码非常的乱。Futures使得情况有所改善。生成器使代码更易于阅读。 291 | 292 | 使用Python编写异步应用程序,似乎"官方"推荐的方式是使用回调/Futures/生成器,而不是greenlets。请参阅 `PEP 3156 `_ 。 293 | 294 | 当然,没有什么会阻止您使用greenlet框架。有选择是件好事。 295 | 296 | 我更喜欢明确的上下文切换,因为在花了几个晚上生产环境中使用gdb搞清楚奇怪的解释器崩溃问题后,我对greenlets变得比较谨慎了。 297 | 298 | 异步框架 299 | --------- 300 | 301 | 302 | 在大多数的情况下,完全没有必要写自己的异步网络层,应该更好地利用现有的框架。 在这里我就不一一列举所有的Python异步框架, 303 | 我只说工作中使用的一个,所以不会对其他框架有所冒犯。 304 | 305 | 1. GEVENT不错,使编写异步程序变得容易,但就像上面说的,我不太适应greenlets。 306 | 307 | 2. Twisted是最古老的异步框架,即使是现在也有积极维护,。我个人的感受相当复杂:复杂,非PEP8,不容易学习。 308 | 309 | 3. Tornado是的我最终选择的框架。有几个原因: 310 | 311 | * 快 312 | * 可预测的 313 | * 更符合Python的风格 314 | * 相对较小 315 | * 开发活跃 316 | * 源代码很容易阅读和理解 317 | 318 | Tornado没有Twisted大,并且没有一些同步调用库的异步移植(主要是DB方面), 但附带了Twisted reactor,所以它是可以在Tornado的基础上使用为Twisted编写的模块。 319 | 320 | 我会基于Tornado来解释后面所有的例子,但我敢肯定,类似的抽象同样可以用于其他框架。 321 | 322 | Tornado 323 | ------- 324 | 325 | 326 | Tornado的结构是非常简单的。有一个主循环(称为IOLoop)。IOLoop检查socket, 文件描述符等的IO事件(借助 epoll, kqueue或select ), 327 | 并管理基于时间的回调函数。 当有IO事件发生,Tornado调用注册的回调函数。 328 | 329 | 例如,如果绑定在某个socket上的的连接进来,Tornado将触发相应的回调函数, 这将创建HTTP请求处理程序类,然后从socket读取头部信息。 330 | 331 | Tornado不仅只是epoll的一个封装 - 它包含自己的模板和认证系统,异步Web客户端等。 332 | 333 | 如果你不熟悉Tornado,看看这个相对较短的 `框架概述 `_ 。 334 | 335 | Tornado自带的WebSocket协议的实现,我也在这个基础上写了 `sockjs `_ 和 `socket.io `_ 库。 336 | 337 | 就像这篇文章开始的时候提到的,SockJS是WebSocket的polyfill库,在客户端是WebSocket对象, 在服务器端用socketjs-tornado提供类似的api。 338 | 339 | SockJS负责选择客户端和服务器之间最佳的可用的传输方式,并建立逻辑连接。 340 | 341 | 这里是基于sockjs-tornado的简单聊天例子:: 342 | 343 | class ChatConnection(sockjs.tornado.SockJSConnection): 344 | participants = set() 345 | 346 | def on_open(self, info): 347 | self.broadcast(self.participants, "Someone joined.") 348 | self.participants.add(self) 349 | 350 | def on_message(self, message): 351 | self.broadcast(self.participants, message) 352 | 353 | def on_close(self): 354 | self.participants.remove(self) 355 | self.broadcast(self.participants, "Someone left.") 356 | 357 | 为了举例,聊天不会有任何的内部协议或认证 - 它只是广播消息发送给所有的参与者。 358 | 359 | 没错,就这么就可以了。 如果客户端不支持WebSocket的传输,这不要紧,SockJS会回退去使用长轮询传输 - 开发人员只编写一次代码,sockjs-tornado负责抽象协议的差异。 360 | 361 | 逻辑是非常简单的: 362 | 363 | 对于每个传入SockJS连接,sockjs-tornado将创建新的连接类的实例,并调用on_open 在on_open中,处理程序将所广播有人有聊天者加入,并把聊天者的self加入参与者集合。 如果从客户端接收到一些消息,ON_MESSAGE将被调用并且消息将被广播给所有参与者 如果客户端断开连接,on_close将其从参与者集合中删除,并广播给剩下的所有参与者他离开了。 364 | 365 | 客户端的完整的例子可以在 这里找到:https://github.com/mrjoes/sockjs-tornado/blob/master/examples/chat/chat.py 366 | 367 | 管理状态 368 | -------- 369 | 370 | 服务器端的session是状态的一个例子。如果服务器需要某种先行数据才能处理请求,那服务器是状态相关的。 371 | 372 | 状态增加了复杂性 - 它消耗内存,它使伸缩更加困难。 例如,如果没有共享的session状态,客户只能和集群中的一台服务器"说话"。 共享会话状态 - 在为每一个请求从存储中获取状态的时候,每一次数据交换会有额外的IO开销。 373 | 374 | 不幸的是,无状态的Comet服务器是不可能实现。为了保持逻辑连接,需要一些会话状态来确保数据在客户端之间数据不会丢失。 375 | 376 | 根据任务,可以将有状态的网络层(Comet)和无状态的业务层(实际应用)分离开来。 在这种情况下,业务层完全不需要异步 - 接收到的任务,对其进行处理,并发送回响应。 因为worker是无状态的,就可以并行地启动大量的workers来增加应用程序整体的吞吐量的。 377 | 378 | 下面看他是如何工作的,架构图: 379 | 380 | .. image:: http://mrjoes.github.io/shared/posts/python-realtime/frontend-worker.png 381 | 382 | 在这个例子中,使用Redis做同步传输,但是这会有单点故障,从可靠性角度来看不是太好。 此外,Redis的队列被用来向workers推送请求,并接收他们的响应。 383 | 384 | 由于网络层是有状态的,运行在应用程序前面负载均衡程序为了满足实时连接将使用粘性sessions(客户端应该每次去到相同的服务器)。 385 | 386 | 与WSGI应用集成 387 | -------------- 388 | 389 | 显然,使用新的异步框架重写现有的Web站点是不太可行的。但是可以让他们共存。 390 | 391 | 有两种方法来整合实时部分: 392 | 393 | 1, 在同一进程 2, 不在同一进程 394 | 395 | 如果使用GEVENT,它是可以使WSGI应用与实时部分共存于相同的进程。 如果使用tornado和其他基于回调的框架,尽管实时部分有可能运行在相同的进程中单独的线程,但是不被推荐这样做, 这有性能方面的问题(由于GIL )。 396 | 397 | 另外,我更喜欢独立进程的方法,其中单独设置进程/服务负责实时部分。 他们可能共存在一个项目/代码库,但他们总是同时但是单独进程运行的。 398 | 399 | 让我们假设你有一个社交网络,并希望实时推动状态更新。 400 | 401 | 最直接的方式来完成这个事情将是:建立单独的服务器来处理实时连接和监听从主站应用发来通知。 402 | 403 | 通知的实现可以通过实时服务提供的定制的REST API(适用于小型部署), 通过Redis的发布/订阅功能(很有可能你的项目已经使用Redis的东西了), 以及在ZeroMQ的帮助下,使用AMQP的消息总线(如RabbitMQ )等。 404 | 405 | 在这篇文章中我们将分析简单的推送broker架构。 406 | 407 | 组织你的代码 408 | ------------ 409 | 410 | 我会用Flask作为一个例子,但同样可以应用到任何其他的框架(Django,Pyramid等)。 411 | 412 | 我更喜欢一个代码仓库包含Flask应用和基于Tornado的实时部分。 在这种情况下,可以在这两个项目之间复用一些代码。 413 | 414 | 对于Flask,我使用普通的Python库:SQLAlchemy,redis-PY等,对应Tornado, 我必须使用异步的替代库或者使用线程池来执行长时间运行的同步函数,以防止阻塞ioloop。 415 | 416 | 我manage.py有两个命令:启动Web应用程序和启动基于tornado的实时部分。 417 | 418 | 让我们看看一些用例。 419 | 420 | 推送broker 421 | ---------- 422 | 423 | Broker接受从Flask应用发来的消息,并将其转发到已连接的客户端。 有很多现成可以使用的broker服务( PubNub , Pusher及一些其他的或自托管解决方案,如Hookbox ), 424 | 但由于某种原因你可能要搭建自己的。 425 | 426 | 这最简单的推送broker:: 427 | 428 | class BrokerConnection(sockjs.tornado.SockJSConnection): 429 | participants = set() 430 | 431 | def on_open(self, info): 432 | self.participants.add(self) 433 | 434 | def on_message(self, message): 435 | pass 436 | 437 | def on_close(self): 438 | self.participants.remove(self) 439 | 440 | @classmethod 441 | def pubsub(cls, data): 442 | msg_type, msg_chan, msg = data 443 | if msg_type == 'message': 444 | for c in cls.clients: 445 | c.send(msg) 446 | 447 | if __name__ == '__main__': 448 | # .. initialize tornado 449 | # .. connect to redis 450 | # .. subscribe to key 451 | rclient.subscribe(v.key, BrokerConnection.pubsub) 452 | 453 | 完整的 例子在这里: https://gist.github.com/mrjoes/3284402 454 | 455 | broker是无状态的 - 他们真的不存储任何特定于应用程序的状态, 这样你就可根据不断增加的负载启动你需要数量的broker,只要负载正确配置好了。 456 | 457 | .. image:: http://mrjoes.github.io/shared/posts/python-realtime/push-servers.png 458 | 459 | 游戏 460 | ----- 461 | 462 | 让我们为一个"典型"的纸牌游戏做个架构草案。 463 | 464 | 假设,有一桌子,一组玩家在玩同一个游戏。 桌子可能包含可见牌和桌面的信息。 每个玩家有其内部状态 - 手上牌的列表,以及一些身份验证数据。 465 | 466 | 此外,对于游戏,客户端应该比较智能点,因为有可能需要有基于原始连接的自定义协议的。 为了简单起见,我们将使用定制的基于JSON协议。 467 | 468 | 让我们弄清楚我们需要什么样的消息: 469 | 470 | - 验证 471 | - 错误处理 472 | - 房间列表 473 | - 加入的房间 474 | - 摸牌 475 | - 出牌 476 | - 离开房间 477 | 478 | 认证消息是从客户端发送到服务器的第一条消息。例如,它可以像:: 479 | 480 | {"msg": "auth", "token": "[encrypted-token-in-base64]"} 481 | 482 | 有效载荷是加密过的令牌,由Flask应用所产生。 有一种方法来生成令牌:获得当前用户ID,用时间戳和一些使用共享密钥加密对称算法(如3DES或AES)随机添加一些东西。 Tornado可以解密令牌,提取出用户ID,然后从数据库进行查询得到任何有关用户的必要的信息。 483 | 484 | 房间列表可以类似表示为:: 485 | 486 | {"msg": "room_list", "rooms": [{"name": "room1"}, {"name": "room2"}]} 487 | 488 | 489 | 依此类推。 490 | 491 | 在服务器端,每个SockJS连接被封装在类的实例中,它是可以使用self存储任玩家相关数据。 492 | 493 | Connection类看起来像这个样子(部分):: 494 | 495 | class GameConnection(SockJSConnection): 496 | def on_open(self, info): 497 | self.authenticated = False 498 | 499 | def on_message(self, data): 500 | msg = json.loads(data) 501 | msg_type = msg['msg'] 502 | 503 | if not self.authenticated and msg_type != 'auth': 504 | self.send_error('authentication required') 505 | return 506 | 507 | if msg_type == 'auth': 508 | self.handle_auth(msg) 509 | return 510 | elif msg_type == 'join_room': 511 | # ... other handlers 512 | pass 513 | 514 | def handle_auth(self, msg): 515 | user_id = decrypt_token(msg['token']) 516 | if user_id is None: 517 | self.send_error('invalid token') 518 | return 519 | self.authenticated = True 520 | self.send_room_list() 521 | 522 | def send_error(self, text): 523 | self.send(json.dumps({'msg': 'error', 'text': text})) 524 | 525 | 房间可以存储在一个字典里,其中key是房间ID,value是房间对象。 526 | 527 | 通过在客户端上实现不同的的消息处理程序和适当的业务逻辑,我们就可以让游戏工作,这作为一个练习留给读者。 528 | 529 | 游戏是有状态的 - 服务器必须跟踪在比赛中发生了什么。这也意味着它是有点难以伸缩。 530 | 531 | 在上面的例子中,一台服务器处理所有连接的玩家的所有游戏。 但是,如果我们希望有两台服务器并且让玩家分布于它们之间呢? 由于他们不知道对方的状态,连接到第一台服务器的玩家将不能和第二台服务器上的玩家游戏。 532 | 533 | 根据游戏规则的复杂性,它是可以使用全连接的拓扑结构 - 每一个服务器连接到每一个其他的服务器: 534 | 535 | .. image:: http://mrjoes.github.io/shared/posts/python-realtime/game-interconnect.png 536 | 537 | 在这种情况下,游戏状态应该需要信息以确定玩家身份,管理他的游戏状态, 并且把游戏相关的信息发送到相应的服务器,这样状态就可以转发给实际客户端。 538 | 539 | 虽然这种方法有用,但异步应用程序是单线程的,更好的方式是将游戏逻辑和相关状态分离成单独的服务器应用程序, 把实时部分作为游戏服务器和客户端之间的智能适配器。 540 | 541 | 因此,它可以是这样的: 542 | 543 | .. image:: http://mrjoes.github.io/shared/posts/python-realtime/realtime-game-servers.png 544 | 545 | 546 | 客户端连接到一个实时服务器,通过验证,获取正在运行的游戏列表(通过游戏服务器和实时服务器之间的一些共享状态)。 当客户端选择特定的游戏玩时,它会发送请求到实时服务器,然后在和真正部署该游戏服务器通信。 虽然这看起来和完全互连解决方案非常相似,但是在同一柜的服务器不需要与对方直接交互,这提供了有效的隔离状态。 伸缩也很简单 - 增加更多的的实时服务器或游戏服务器,由他们的状态是孤立的就易于管理。 547 | 548 | 另外,对于这个任务,我会使用ZeroMQ(AMQP总线)而不是Redis的,因为Redis的成为单点故障。 549 | 550 | 游戏服务器不会暴露在Internet中,他们只可以通过实时服务器访问。 551 | 552 | 我已经说过了,分布式应用程序的伸缩性就看 **状态管理是否高效** 。 553 | 554 | 555 | 部署 556 | ---- 557 | 558 | 把Flask和Tornado放到负载均衡的后面(如haproxy ) 或反向代理服务器(即nginx 是个不错的想法,但要使用最新有WebSocket协议的支持的版本)。 559 | 560 | 有三种部署选项: 561 | 562 | 1. 把Web和实时部分都运行在相同的主机和端口,并使用基于URL路由分发 563 | 564 | - 优点 565 | - 所有的事情看起来是一致的 566 | - 无需担心跨域脚本问题 567 | - 一般可以工作在有防火墙限制的环境 568 | - 缺点 569 | - 一些透明HTTP代理服务器不兼容 570 | 2. 在相同主机的不同端口,Web在端口80上,实时部分在其他端口 571 | 572 | - 优点 573 | - 和透明代理更加兼容 574 | - 缺点 575 | - 跨域脚本问题(不是每个浏览器都支持CORS ) 576 | - 很有可能被防火墙阻止 577 | 3. Web运行在主要域(site.com)和实时部分运行在子域(subdomain.site.com) 578 | 579 | - 优点 580 | - 可以将实时部分从主站点分离出来(无需使用相同的负载均衡) 581 | - 缺点 582 | - 跨域脚本问题 583 | - 会碰到行为古怪的透明代理 584 | 585 | 586 | 现实生活中的经验 587 | ---------------- 588 | 589 | 我看到过一些使用sockjs-tornado的成功案例: `PlayBuildy `_ `PythonAnywhere `_ 和其他的。 590 | 591 | 但不幸的是,我自己没有在大型项目中使用过。 592 | 593 | 不过,我有相当有趣的sockjs-node(为nodejs做的SockJS的服务器实现)经验。 我实现了一个比较大的广播电台的实时部分。 在同一时间平均有3500左右连接的客户端。 594 | 595 | 大多数连接是短时的,服务器就仅仅是一个简单的broker: 管理有层次的频道订阅(例如广播站事件推送或广播员的新闻推送)和频道日志。 客户订阅频道,应该可以获得所有为子频道推送的更新。 客户也可以申请频道日志 - 按日期排序的最新N个频道和其子频道的消息。 这就是一部分在服务器上的逻辑。 596 | 597 | 总体而言,nodejs的表现是很不错的 - 在一台物理服务器上的3个服务器进程就能够毫无压力处理所有这些客户端的链接, 而且还有很多的提升空间。 598 | 599 | 但就我看来,nodejs和其库有太多的问题了。 600 | 601 | 部署到生产环境后,服务器开始没有明显原因的内存泄漏。 所有的工具表明,堆大小是恒定的,但服务器进程的RSS不断增长,直到进程被操作系统杀掉。 作为一个快速的解决方案,nodejs服务器必须每天晚上重新启动。 这个问题问题和这个这个比较相似,但这个SSL没有关系,因为没有使用SSL。 602 | 603 | 如果没有明显的原因的崩溃而且没有产生coredump,那么升级到较新的nodejs版本有帮助。 604 | 605 | 如果V8垃圾收集器开始在某些情况下死锁了而且是一天一次的频率发生。 那么升级到较新的nodejs版本会有帮助,它是发生在V8中的死锁,我在Chrominu的bug跟踪服务里面发现了完全相同的堆栈信息。 606 | 607 | 新的nodejs版本解决了垃圾收集的问题,应用又可以工作。 608 | 609 | 此外,基于回调的编程风格使得代码不是我希望中的那么干净和可读。 610 | 611 | 概括起来 - 尽管nodejs能工作,我有一种强烈的感觉是它没有Python那样成熟。 在以后这样的项目中我宁愿使用Python, 因为我可以肯定,如果出现错误,它发生了是因为我出了错,而且问题可以追溯到,这样就相对容易。 612 | 613 | 性能方面,使用WebSocket传输,CPython和nodejs 是差不多的而 PYPY比两者都快得多。 对于长轮询,PYPY环境的Tornado在使用适当的异步库的情况下,约1.5-2倍慢于nodejs,因此, 考虑目前的WebSocket兼容状态,我会说他们是可比的。 614 | 615 | 我没有理由放弃的Python而用nodejs来做实时部分。 616 | 617 | 更新(2013年7月2日): `TechEmpower `_ 发布了他们的 `第6轮 `_ 的框架基准测试,新版本的Tornado更快了,或与node.js有的一比。 618 | 619 | 备注 620 | ---- 621 | 622 | 虽然有人可能会争辩说,要编写出可伸缩的服务器,Python并不是最好的语言。 当然, Erlang的已经有内建的工具来写高效和可扩展的应用程序(而且也有 623 | `sockjs-erlang `_ ), 但是要找到erlang的开发人员是比较困难的。 Clojure和Scala也是不错的选项,但Java是完全不同的世界,有自己的类库,方法论和约定。 找到不错的Clojure开发者仍然比找到好的Python开发者很难很多。 Go 也不错,但他是相当年轻的语言接受程度还不高。 624 | 625 | 如果你已经有了Python的经验,你可以继续使用Python达到不错的结果。 在大多数情况下,软件开发就是开发成本和性能之间的一个权衡。 我认为Python所处的位置还比较有利, 626 | 特别是借助于 `pypy `_ 627 | 628 | 无论如何,如果你有任何意见,问题或更新 - 随时与我联系。 629 | 630 | P.S. 图表是在 `draw.io `_ 上完成的 -我不得不提一下这个优秀且免费的服务。 631 | 632 | --- 633 | 634 | 转载声明: 本文转载自 http://taokeqin.com/python-realtime.html 635 | -------------------------------------------------------------------------------- /docs/python-setup-dot-py-vs-requirements-dot-txt.rst: -------------------------------------------------------------------------------- 1 | setup.py vs requirements.txt 2 | ============================ 3 | 4 | :原文: https://caremad.io/blog/setup-vs-requirement/ 5 | :作者: Donald 6 | :译: hit9 7 | 8 | .. Contents:: 9 | 10 | 对于 ``setup.py`` 和 ``requirements.txt`` 11 | 的角色有很多误解,很多人认为它们是两个重复的事情,甚至创造了 `工具 `_ 来处理 12 | 这种“重复”。 13 | 14 | Python库 15 | -------- 16 | 17 | 这里所说的Python库是指那些被开发并且为了其他人来使用而发布的东西,你可以在 18 | `PyPI `_ 找到很多Python库。为了更好的推广和传播 19 | 自己,Python库会包含很多的信息,比如它的名字,版本号,依赖等等。而 ``setup.py`` 20 | 就是用来提供这些信息的:: 21 | 22 | from setuptools import setup 23 | 24 | setup( 25 | name="MyLibrary", 26 | version="1.0", 27 | install_requires=[ 28 | "requests", 29 | "bcrypt", 30 | ], 31 | # ... 32 | ) 33 | 34 | 这样很简单地声明了这个Python库的一些信息。但是,你并没有看到你可以从哪里获取这些依赖库。 35 | 这里并没有提供一个url或者一个文件系统来获取这些依赖,而只是告诉我们依赖是 36 | ``requests`` , ``bcrypt`` 37 | ,这很重要,我们可以称这种声明方式为“抽象的依赖”,它们只是以名字(或者加上了一个具体的版本号) 38 | 的方式来出现的。这就像我们说的“鸭子类型”,你不在乎它是什么样子的只要它看起来是 ``requests`` 。 39 | 40 | Python应用 41 | ---------- 42 | 43 | 这里所讲的Python应用是指你所要部署的一些东西,这是区别于我们之前所讲的Python库的。Python应用或许可以在 44 | PyPI上找到,但是不像Python库,它们并不是一种可以被开发者使用多次的工具性的东西。PyPI上的Python应用经常会 45 | 在这个应用的旁边放置一个文件用来声明该应用部署的依赖。 46 | 47 | 一个应用经常会有很多依赖,或许会很复杂。这些依赖里很多没有一个名字,或者没有我们说所的那些信息。这便反映了 `pip `_ 48 | 的requirements文件所做的事情了,一个典型的requirements文件看起来是这样的:: 49 | 50 | # This is an implicit value, here for clarity 51 | --index https://pypi.python.org/simple/ 52 | 53 | MyPackage==1.0 54 | requests==1.2.0 55 | bcrypt==1.0.2 56 | 57 | 这里每个依赖都标明了准确的版本号,一般一个Python库对依赖的版本比较宽松,而一个应用則会依赖比较具体的版本号。虽然也许跑其他 58 | 版本的 ``requests`` 59 | 并不会出错,但是我们在本地测试顺利后,我们就会希望在线上也跑相同的版本。 60 | 61 | 文件的头部有一个 ``--index https://pypi.python.org/simple/`` 62 | ,一般如果你不用声明这项,除非你使用的不是PyPI。然而它却是 ``requirements.txt`` 的一个重要部分, 63 | 这一行把一个抽象的依赖声明 ``requests==1.2.0`` 转变为一个具体的依赖声明 ``requests 1.2.0 from pypi.python.org/simple/`` 64 | ,这不像“鸭子类型”,倒像一次 ``isinstance`` 检查。 65 | 66 | 那么,抽象和具体又有什么关系呢? 67 | -------------------------------- 68 | 69 | 读到这里你或许会说,OK, 我已经知道 ``setup.py`` 70 | 是为可发行的Python库那些包准备的,而 ``requirements.txt`` 71 | 是为那些不被经常作为工具利用的Python应用准备的,但是我已经把我的 72 | ``requirements.txt`` 读进来填充了我的 ``install_requires=[...]`` 啊(译者注: 73 | 比如你在 ``setup.py`` 中把 ``requirements.txt`` 74 | 文件读取进来并切割成行列表,赋值给关键字 75 | ``install_requires``),那我为何要在乎这个区别呢? 76 | 77 | 对于抽象依赖和具体依赖的区分是非常重要的,这点使我们的PyPI镜像源正常工作,这点允许我们可以在公司里搭建我们 78 | 私有的包索引服务,甚至这点允许了你去fork一个包并改造它。因为一个抽象的依赖只是一个名字和一个可选的版本号,你可以从 79 | PyPi来安装它,或者从你自己的文件系统,你可以fork它并改造它,只要你指明了正确的名字和版本号你就可以一直这么使用下去。 80 | 81 | 一个极端点的情况是,你在该使用抽象依赖的地方使用了具体的依赖,这在Go语言中可以看到 82 | 83 | .. code-block:: go 84 | 85 | import ( 86 | "github.com/foo/bar" 87 | ) 88 | 89 | 这里我们指明了一个具体的url。现在如果我以这种指明url的方式使用了这个库,而且现在我想要改造这个库(比如它缺失了我想要的某个功能, 90 | 或者有一个讨厌的bug)。我可能不仅仅需要fork ``bar`` 91 | 这个库,还需要fork依赖这个库的其他库。(译者注:也就是说,想要替换一个底层依赖的话,需要改动依赖这个库的其他依赖对该库的依赖声明。) 92 | 93 | Setuptools的dependency_links 94 | ---------------------------- 95 | 96 | Setuptools有一个功能叫做 `dependency_links `_ :: 97 | 98 | from setuptools import setup 99 | 100 | setup( 101 | # ... 102 | dependency_links = [ 103 | "http://packages.example.com/snapshots/", 104 | "http://example2.com/p/bar-1.0.tar.gz", 105 | ], 106 | ) 107 | 108 | 这一功能除去了依赖的抽象特性,直接把依赖的获取url标在了setup.py里。就像在Go语言中修改依赖包一样,我们只需要修改依赖链中每个包的 ``dependency_links`` 。 109 | 110 | 开发可复用的包与不重复自己 111 | -------------------------- 112 | 113 | 那么我们写依赖声明的时候需要在 ``setup.py`` 中写好抽象依赖,在 ``requirements.txt`` 中写好具体的依赖,但是我们并不想维护两份依赖文件,这样会让我们很难 114 | 做好同步。 ``requirements.txt`` 可以更好地处理这种情况,我们可以在有 ``setup.py`` 的目录里写下一个这样的 ``requirements.txt`` :: 115 | 116 | --index https://pypi.python.org/simple/ 117 | 118 | -e . 119 | 120 | 这样 ``pip install -r requirements.txt`` 可以照常工作,它会先安装该文件路径下的包,然后继续开始解析抽象依赖,结合 ``--index`` 选项后转换为具体依赖然后再安装她们。 121 | 122 | 这个办法可以让我们解决一种类似这样的情形:比如你有两个或两个以上的包在一起开发但是是分开发行的,或者说你有一个尚未发布的包并把它分成了几个部分。如果你的顶层的包 123 | 依然仅仅按照“名字”来依赖的话,我们依然可以使用 ``requirements.txt`` 来安装开发版本的依赖包:: 124 | 125 | --index https://pypi.python.org/simple/ 126 | 127 | -e https://github.com/foo/bar.git#egg=bar 128 | -e . 129 | 130 | 这会首先从 https://github.com/foo/bar.git 来安装包 ``bar`` , 然后进行到第二行 ``-e .`` ,开始安装 ``setup`` 中的抽象依赖,但是包 ``bar`` 已经安装过了, 131 | 所以 pip 会跳过安装,而是仍然使用github.com上安装了的开发版本的包 ``bar`` 。 132 | -------------------------------------------------------------------------------- /docs/python-the-dictionary-playbook.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-02-13 14:01:02 2 | 3 | 4 | ======================================================= 5 | Python:字典的剧本(翻译自Python:The Dictionary Playbook) 6 | ======================================================= 7 | 8 | 9 | :原文: http://blog.amir.rachum.com/post/39501813266/python-the-dictionary-playbook 10 | 11 | :翻译: hit9 12 | 13 | 我经常遇到各种五花八门的关于Python中的字典的代码用法,我决定在这个文章中展示一些并分享完成同样操作的更为简洁的做法。 14 | 15 | .. Contents:: 16 | 17 | 键的存在性(The "Are You There?") 18 | ---------------------------------- 19 | 20 | 不推荐的:: 21 | 22 | dct.has_key(key) 23 | 24 | Python的做法:: 25 | 26 | key in dct 27 | 28 | Yoda 测试(The "Yoda Test") 29 | ----------------------------- 30 | 31 | 对于那些精通 "Are Your There" 的程序员,还有一个简单的同样也很恼人的行为,不仅仅对于dict而言,它很普遍 32 | 33 | 如果你非要Not :: 34 | 35 | not key in dct 36 | 37 | English,你会说么?:: 38 | 39 | key not in dct 40 | 41 | 无论如何都要取到值(The "Get The Value Anyway") 42 | ------------------------------------------------ 43 | 44 | 这个是相当流行。你有一个dict和key,并且你想修改key对应的值,比如给它自增1(假如你在数数)。 45 | 46 | 比较逊一点的做法::: 47 | 48 | if key not in dct: 49 | dct[key] = 0 50 | dct[key] = dct[key] + 1 51 | 52 | 漂亮的做法:: 53 | 54 | dct[key] = dct.get(key, 0) + 1 55 | 56 | 关于 ``dct.get(key[, default])`` ,如果 ``key`` 存在,返回 ``dct[key]``,否则返回 ``default`` 57 | 58 | 更棒的做法是: 59 | 60 | 如果你在使用Python 2.7,你就可以用Counter来计数了:: 61 | 62 | >>> from collections import Counter 63 | >>> d = [1, 1, 1, 2, 2, 3, 1, 1] 64 | >>> Counter(d) 65 | Counter({1: 5, 2: 2, 3: 1}) 66 | 67 | 这里还有些更完整的例子:: 68 | 69 | >>> counter = Counter() 70 | ... for _ in range(10): 71 | ... num = int(raw_input("Enter a number: ")) 72 | ... counter.update([num]) 73 | ... 74 | ... for key, value in counter.iteritems(): 75 | ... print "You have entered {}, {} times!".format(key, value) 76 | Enter a number: 1 77 | Enter a number: 1 78 | Enter a number: 2 79 | Enter a number: 3 80 | Enter a number: 51 81 | Enter a number: 1 82 | Enter a number: 1 83 | Enter a number: 1 84 | Enter a number: 2 85 | Enter a number: 3 86 | You have entered 1, 5 times! 87 | You have entered 2, 2 times! 88 | You have entered 3, 2 times! 89 | You have entered 51, 1 times! 90 | 91 | 让它发生!(The "Make it Happen") 92 | ---------------------------------- 93 | 94 | 有时你的字典里都是经常修改的对象,你需要初始化一些数据到这个字典,也需要修改其中的一些值。比如说,你在维护一个这样的dict:它的值都是链表。 95 | 96 | 写一个实现就是:: 97 | 98 | dct = {} 99 | for (key, value) in data: 100 | if key in dct: 101 | dct[key].append(value) 102 | else: 103 | dct[key] = [value] 104 | 105 | 更Pythonic的做法是: 106 | 107 | :: 108 | 109 | dct = {} 110 | for (key, value) in data: 111 | group = dct.setdefault(key, []) # key might exist already 112 | group.append(value) 113 | 114 | ``setdefault(key, default)`` 所做的是:如果存在,返回 ``dct[key]`` , 不存在则把 ``dct[key]`` 设为 ``default`` 并返回它。当一个默认的值是一个你可以修改的对象的时候这是很有用的。 115 | 116 | 使用 ``defaultdict`` :: 117 | 118 | dct = defaultdict(list) 119 | for (key, value) in data: 120 | dct[key].append(value) # all keys have a default already 121 | 122 | ``defaultdict`` 非常棒, 它每生成一对新的 ``key-value`` ,就会给value一个默认值, 这个默认值就是 123 | ``defaultdict`` 的参数。(注:defaultdict在模块 ``collections`` 中) 124 | 125 | 一个很有意思的就是,defaultdict实现的一行的tree: https://gist.github.com/2012250 126 | -------------------------------------------------------------------------------- /docs/single-line-command-with-python.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-05-14 21:21:00 2 | 3 | ============================ 4 | 使用Python解释器的一句话命令 5 | ============================ 6 | 7 | :作者: hit9 8 | 9 | :联系: 10 | 11 | 全部是 ``python -m`` 形式的 12 | 13 | .. code:: bash 14 | 15 | 16 | python -m SimpleHTTPServer [port] # 当前目录开启一个小的文件服务器, 默认端口8000 17 | # 另外,python 3,中是 python -m http.server 18 | python -m this # python's Zen 19 | python -m calendar # 显示一个日历 20 | echo '{"json":"obj"}' | python -mjson.tool # 漂亮地格式化打印json数据 21 | echo '{"json":"obj"}' | python -mjson.tool | pygmentize -l json # 高亮地打印json格式化 22 | python -m antigravity # 这个该自己试试 23 | python -m smtpd -n -c DebuggingServer localhost:20025 # mail server 24 | -------------------------------------------------------------------------------- /docs/the-python-yield-keyword-explained.rst: -------------------------------------------------------------------------------- 1 | :Date: 2013-02-14 14:01:01 2 | 3 | ========================================== 4 | (译)Python关键字yield的解释(stackoverflow) 5 | ========================================== 6 | 7 | :译者: hit9 8 | 9 | :原文: http://stackoverflow.com/questions/231767/the-python-yield-keyword-explained 10 | 11 | :译者注: 这是stackoverflow上一个很热的帖子,这里是投票最高的一个答案 12 | 13 | .. Contents:: 14 | 15 | 16 | 提问者的问题 17 | ------------ 18 | Python关键字yield的作用是什么?用来干什么的? 19 | 20 | 比如,我正在试图理解下面的代码:: 21 | 22 | def node._get_child_candidates(self, distance, min_dist, max_dist): 23 | if self._leftchild and distance - max_dist < self._median: 24 | yield self._leftchild 25 | if self._rightchild and distance + max_dist >= self._median: 26 | yield self._rightchild 27 | 28 | 下面的是调用:: 29 | 30 | result, candidates = list(), [self] 31 | while candidates: 32 | node = candidates.pop() 33 | distance = node._get_dist(obj) 34 | if distance <= max_dist and distance >= min_dist: 35 | result.extend(node._values) 36 | candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 37 | return result 38 | 39 | 当调用 ``_get_child_candidates`` 的时候发生了什么?返回了一个列表?返回了一个元素?被重复调用了么? 40 | 什么时候这个调用结束呢? 41 | 42 | 回答部分 43 | -------- 44 | 45 | 为了理解什么是 :keyword:`yield`,你必须理解什么是生成器。在理解生成器之前,让我们先走近迭代。 46 | 47 | 可迭代对象 48 | ---------- 49 | 50 | 当你建立了一个列表,你可以逐项地读取这个列表,这叫做一个可迭代对象:: 51 | 52 | >>> mylist = [1, 2, 3] 53 | >>> for i in mylist : 54 | ... print(i) 55 | 1 56 | 2 57 | 3 58 | 59 | ``mylist`` 是一个可迭代的对象。当你使用一个列表生成式来建立一个列表的时候,就建立了一个可迭代的对象:: 60 | 61 | >>> mylist = [x*x for x in range(3)] 62 | >>> for i in mylist : 63 | ... print(i) 64 | 0 65 | 1 66 | 4 67 | 68 | 所有你可以使用 ``for .. in ..`` 语法的叫做一个迭代器:列表,字符串,文件……你经常使用它们是因为你可以如你所愿的读取其中的元素,但是你把所有的值都存储到了内存中,如果你有大量数据的话这个方式并不是你想要的。 69 | 70 | 生成器 71 | ------ 72 | 73 | 生成器是可以迭代的,但是你 **只可以读取它一次** ,因为它并不把所有的值放在内存中,它是实时地生成数据:: 74 | 75 | >>> mygenerator = (x*x for x in range(3)) 76 | >>> for i in mygenerator : 77 | ... print(i) 78 | 0 79 | 1 80 | 4 81 | 82 | 看起来除了把 ``[]`` 换成 ``()`` 外没什么不同。但是,你不可以再次使用 ``for i in mygenerator`` , 因为生成器只能被迭代一次:先计算出0,然后继续计算1,然后计算4,一个跟一个的… 83 | 84 | yield关键字 85 | ----------- 86 | 87 | ``yield`` 是一个类似 ``return`` 的关键字,只是这个函数返回的是个生成器。 88 | 89 | :: 90 | 91 | >>> def createGenerator() : 92 | ... mylist = range(3) 93 | ... for i in mylist : 94 | ... yield i*i 95 | ... 96 | >>> mygenerator = createGenerator() # create a generator 97 | >>> print(mygenerator) # mygenerator is an object! 98 | 99 | >>> for i in mygenerator: 100 | ... print(i) 101 | 0 102 | 1 103 | 4 104 | 105 | 这个例子没什么用途,但是它让你知道,这个函数会返回一大批你只需要读一次的值. 106 | 107 | 为了精通 ``yield`` ,你必须要理解:**当你调用这个函数的时候,函数内部的代码并不立马执行** ,这个函数只是返回一个生成器对象,这有点蹊跷不是吗。 108 | 109 | 那么,函数内的代码什么时候执行呢?当你使用for进行迭代的时候. 110 | 111 | 现在到了关键点了! 112 | 113 | 第一次迭代中你的函数会执行,从开始到达 ``yield`` 关键字,然后返回 ``yield`` 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。 114 | 115 | 如果生成器内部没有定义 ``yield`` 关键字,那么这个生成器被认为成空的。这种情况可能因为是循环进行没了,或者是没有满足 ``if/else`` 条件。 116 | 117 | 回到你的代码 118 | ------------ 119 | 120 | (译者注:这是回答者对问题的具体解释) 121 | 122 | 生成器:: 123 | 124 | # Here you create the method of the node object that will return the generator 125 | def node._get_child_candidates(self, distance, min_dist, max_dist): 126 | 127 | # Here is the code that will be called each time you use the generator object : 128 | 129 | # If there is still a child of the node object on its left 130 | # AND if distance is ok, return the next child 131 | if self._leftchild and distance - max_dist < self._median: 132 | yield self._leftchild 133 | 134 | # If there is still a child of the node object on its right 135 | # AND if distance is ok, return the next child 136 | if self._rightchild and distance + max_dist >= self._median: 137 | yield self._rightchild 138 | 139 | # If the function arrives here, the generator will be considered empty 140 | # there is no more than two values : the left and the right children 141 | 142 | 143 | 调用者:: 144 | 145 | # Create an empty list and a list with the current object reference 146 | result, candidates = list(), [self] 147 | 148 | # Loop on candidates (they contain only one element at the beginning) 149 | while candidates: 150 | 151 | # Get the last candidate and remove it from the list 152 | node = candidates.pop() 153 | 154 | # Get the distance between obj and the candidate 155 | distance = node._get_dist(obj) 156 | 157 | # If distance is ok, then you can fill the result 158 | if distance <= max_dist and distance >= min_dist: 159 | result.extend(node._values) 160 | 161 | # Add the children of the candidate in the candidates list 162 | # so the loop will keep running until it will have looked 163 | # at all the children of the children of the children, etc. of the candidate 164 | candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) 165 | 166 | return result 167 | 168 | 这个代码包含了几个小部分: 169 | 170 | - 我们对一个列表进行迭代,但是迭代中列表还在不断的扩展。它是一个迭代这些嵌套的数据的简洁方式,即使这样有点危险,因为可能导致无限迭代。 ``candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))`` 穷尽了生成器的所有值,但 ``while`` 不断地在产生新的生成器,它们会产生和上一次不一样的值,既然没有作用到同一个节点上. 171 | 172 | - :meth:`extend` 是一个迭代器方法,作用于迭代器,并把参数追加到迭代器的后面。 173 | 174 | 通常我们传给它一个列表参数:: 175 | 176 | >>> a = [1, 2] 177 | >>> b = [3, 4] 178 | >>> a.extend(b) 179 | >>> print(a) 180 | [1, 2, 3, 4] 181 | 182 | 但是在你的代码中的是一个生成器,这是不错的,因为: 183 | 184 | - 你不必读两次所有的值 185 | 186 | - 你可以有很多子对象,但不必叫他们都存储在内存里面。 187 | 188 | 并且这很奏效,因为Python不关心一个方法的参数是不是个列表。Python只希望它是个可以迭代的,所以这个参数可以是列表,元组,字符串,生成器... 这叫做 ``duck typing``,这也是为何Python如此棒的原因之一,但这已经是另外一个问题了... 189 | 190 | 你可以在这里停下,来看看生成器的一些高级用法: 191 | 192 | 控制生成器的穷尽 193 | ---------------- 194 | 195 | :: 196 | 197 | >>> class Bank(): # let's create a bank, building ATMs 198 | ... crisis = False 199 | ... def create_atm(self) : 200 | ... while not self.crisis : 201 | ... yield "$100" 202 | >>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want 203 | >>> corner_street_atm = hsbc.create_atm() 204 | >>> print(corner_street_atm.next()) 205 | $100 206 | >>> print(corner_street_atm.next()) 207 | $100 208 | >>> print([corner_street_atm.next() for cash in range(5)]) 209 | ['$100', '$100', '$100', '$100', '$100'] 210 | >>> hsbc.crisis = True # crisis is coming, no more money! 211 | >>> print(corner_street_atm.next()) 212 | 213 | >>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs 214 | >>> print(wall_street_atm.next()) 215 | 216 | >>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty 217 | >>> print(corner_street_atm.next()) 218 | 219 | >>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business 220 | >>> for cash in brand_new_atm : 221 | ... print cash 222 | $100 223 | $100 224 | $100 225 | $100 226 | $100 227 | $100 228 | $100 229 | $100 230 | $100 231 | ... 232 | 233 | 对于控制一些资源的访问来说这很有用。 234 | 235 | 236 | Itertools,你最好的朋友 237 | ---------------------- 238 | 239 | itertools包含了很多特殊的迭代方法。是不是曾想过复制一个迭代器?串联两个迭代器?把嵌套的列表分组?不用创造一个新的列表的 ``zip/map``? 240 | 241 | 只要 ``import itertools`` 242 | 243 | 需要个例子?让我们看看比赛中4匹马可能到达终点的先后顺序的可能情况:: 244 | 245 | >>> horses = [1, 2, 3, 4] 246 | >>> races = itertools.permutations(horses) 247 | >>> print(races) 248 | 249 | >>> print(list(itertools.permutations(horses))) 250 | [(1, 2, 3, 4), 251 | (1, 2, 4, 3), 252 | (1, 3, 2, 4), 253 | (1, 3, 4, 2), 254 | (1, 4, 2, 3), 255 | (1, 4, 3, 2), 256 | (2, 1, 3, 4), 257 | (2, 1, 4, 3), 258 | (2, 3, 1, 4), 259 | (2, 3, 4, 1), 260 | (2, 4, 1, 3), 261 | (2, 4, 3, 1), 262 | (3, 1, 2, 4), 263 | (3, 1, 4, 2), 264 | (3, 2, 1, 4), 265 | (3, 2, 4, 1), 266 | (3, 4, 1, 2), 267 | (3, 4, 2, 1), 268 | (4, 1, 2, 3), 269 | (4, 1, 3, 2), 270 | (4, 2, 1, 3), 271 | (4, 2, 3, 1), 272 | (4, 3, 1, 2), 273 | (4, 3, 2, 1)] 274 | 275 | 276 | 了解迭代器的内部机理 277 | -------------------- 278 | 279 | 迭代是一个实现可迭代对象(实现的是 :meth:`__iter__` 方法)和迭代器(实现的是 :meth:`__next__` 方法)的过程。可迭代对象是你可以从其获取到一个迭代器的任一对象。迭代器是那些允许你迭代可迭代对象的对象。 280 | 281 | 更多见这个文章 http://effbot.org/zone/python-for-statement.htm 282 | -------------------------------------------------------------------------------- /requirements.pip: -------------------------------------------------------------------------------- 1 | Sphinx==1.3.1 2 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | - [ ] http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 2 | - [x] http://www.reddit.com/r/Python/comments/wpl1h/what_are_some_little_known_features_in_python/ 3 | - [x] https://github.com/epsy/clize 4 | - [x] when.py 5 | - [ ] Websocket的Python解决方案(python realtime) 6 | - [ ] Python异步方案总结 7 | --------------------------------------------------------------------------------