普通视图

发现新文章,点击刷新页面。
昨天以前小明明s à domicile

2017年度Python榜单

作者 xiaoming
2018年1月31日 08:00

一起见证全世界 Python 的这一年

非常抱歉这个榜单到现在才发出来,主要有 2 个原因:1. 本来准备起用 http://ipython.io 这个域名;2. 域名备案时间长,造成后续事情都等;3. 对榜单运营的数据需要的精力和时间预估不够。好在赶在 1 月的尾巴上线了。

话不多说,之后我们详聊。地址是 https://annual2017.pycourses.com/

榜单内容如下:

  1. 开始页
  2. 2017 年 Star 最多的 Python 项目
  3. 2017 年最受关注的 Python 项目
  4. 2017 年最受关注的中国开发者 Python 项目
  5. 项目 / 文章介绍 系统设计入门
  6. 2017 年最受欢迎的 Web 框架
  7. 2017 年最受欢迎的爬虫框架
  8. 项目 / 文章介绍 不建议使用的 Python Web 框架
  9. 2017 年最受欢迎的 pycon 视频 10.2017 年必备的 Python 工具
  10. 2017 年要熟悉的 Python 项目
  11. 项目 / 文章介绍 知乎响应这么慢是因为使用了 Python 语言吗?
  12. 2017 年最受欢迎的 Python 开发者
  13. 2017 年最知名的中国 Python 开发者
  14. 项目 / 文章介绍 Code Like a Pythonista: Idiomatic Python
  15. 2017 年最受欢迎的 Python 英文文章
  16. 2017 年最受欢迎的 Python 中文文章
  17. 项目 / 文章介绍 Reddit Python 节点
  18. 2017 年最受欢迎的 Django 英文文章
  19. 2017 年最受欢迎的 Django 中文文章
  20. 项目 / 文章介绍 [英] 给 Django 贡献代码比你想得简单
  21. 2017 年最受关注的 podcast
  22. 2017 年最受关注的英文技术博客
  23. 2017 年最受关注的中文技术博客
  24. 项目 / 文章介绍 10 books Python developers should be reading (podcast)
  25. 2017 年最受关注的 Python 英文书籍
  26. 2017 年最受关注的 Python 中文书籍
  27. 2017 年最受关注的 Python 开源书籍
  28. 项目 / 文章介绍 10 分钟速成 Python 3
  29. 2017 年最值得关注的知乎 Python 回答
  30. 项目 / 文章介绍 关于 Python 的面试题
  31. StackOverflow 上最受关注的 10 个 Python 问题
  32. StackOverflow 上最受关注的 10 个 Python 回答
  33. 2017 年 StackOverflow 上最受关注的 10 个 Python 回答
  34. 项目 / 文章介绍 Python 2 和 Python 3 有哪些主要区别?
  35. 2017 年最受关注的 Python 新闻渠道
  36. 2017 年 Reddit 热议话题
  37. 项目 / 文章介绍 [视频] Python 高级编程
  38. 2017 年 1 月最受关注的内容(文章,项目等)
  39. 2017 年 2 月最受关注的内容(文章,项目等)
  40. 项目 / 文章介绍 python-koans
  41. 2017 年 3 月最受关注的内容(文章,项目等)
  42. 2017 年 4 月最受关注的内容(文章,项目等)
  43. 项目 / 文章介绍 Python 最佳实践 (BOBP)
  44. 2017 年 5 月最受关注的内容(文章,项目等)
  45. 2017 年 6 月最受关注的内容(文章,项目等)
  46. 项目 / 文章介绍 使用 Python 来挑战算法和数据结构
  47. 2017 年 7 月最受关注的内容(文章,项目等)
  48. 2017 年 8 月最受关注的内容(文章,项目等)
  49. 项目 / 文章介绍 Sanic(asyncio)暂时不适合生产环境
  50. 2017 年 9 月最受关注的内容(文章,项目等)
  51. 2017 年 10 月最受关注的内容(文章,项目等)
  52. 项目 / 文章介绍 FreeCodeCamp
  53. 2017 年 11 月最受关注的内容(文章,项目等)
  54. 2017 年 12 月最受关注的内容(文章,项目等)
  55. 项目 / 文章介绍 选择 python 2/3 ?
  56. 2017 年发生的重大事件时间线
  57. 感谢那些 Python 幻灯片
  58. 感谢那些老书
  59. 感谢那些不再更新的博客和它的作者们
  60. 缅怀去世 web.py 作者
  61. 2018 年最值得期待的 Python 书籍
  62. 结束页

话不多说,之后我们详聊。地址是 https://annual2017.pycourses.com/

需要注意几点:

  1. Web 端默认自动开启背景音乐(嗯,曲子都是抖音里面的),主要不要被吓到
  2. 移动设备由于有适配问题可能效果不佳,非常推荐在 PC 端浏览
  3. 这个榜单非常长(一共 62 页),有很多图片,预计需要花费 20M,非 wifi 下请谨慎打开,另外由于内容太多,第一次打开可能会慢
  4. Web 端可以玩弹幕
  5. 同样是榜单太长的缘故,可以点击右上角的「目录」了解目前的进度,以及直接跳到对应的榜单
  6. 同学如果看榜单过程中发现样式问题,欢迎截图戳我

明天我会介绍为什么要做这个榜单,以及榜单数据是怎么定的

原文: 2017年度Python榜单

Python 3新特性汇总

作者 xiaoming
2018年1月17日 08:00

这篇文章灵感来源于一个新项目 A short guide on features of Python 3 for data scientists ,这个项目列出来了作者使用 Python 3 用到的一些特性。正巧我最近也想写一篇介绍 Python 3 (特指 Python 3.6+) 特色用法的文章。开始吧!

pathlib 模块

pathlib 模块是 Python 3 新增的模块,让你更方便的处理路径相关的工作。

In : from pathlib import Path
In : Path.home()
Out: PosixPath('/Users/dongweiming')  # 用户目录
In : path = Path('/user')
In : path / 'local'  # 非常直观
Out: PosixPath('/user/local')
In : str(path / 'local' / 'bin')
Out: '/user/local/bin'

In : f = Path('example.txt')
In : f.write_bytes('This is the content'.encode('utf-8'))
Out[16]: 19

In : with f.open('r', encoding='utf-8') as handle:  # open现在是方法了
....:         print('read from open(): {!r}'.format(handle.read()))
....:
read from open(): 'This is the content'

In : p = Path('touched')
In : p.exists()  # 集成了多个常用方法
Out: False
In : p.touch()
In : p.exists()
Out: True

In : p.with_suffix('.jpg')
Out: PosixPath('touched.jpg')
In : p.is_dir()
Out: False
In : p.joinpath('a', 'b')
Out: PosixPath('touched/a/b')

可迭代对象的解包

In : a, b, *rest = range(10)  # 学过lisp就很好懂了,相当于一个「everything else」
In : a
Out: 0
In : b
Out: 1
In : rest
Out: [2, 3, 4, 5, 6, 7, 8, 9]

In : *prev, next_to_last, last = range(10)
In : prev, next_to_last, last
Out: ([0, 1, 2, 3, 4, 5, 6, 7], 8, 9)

强制关键字参数

使用强制关键字参数会比使用位置参数表意更加清晰,程序也更加具有可读性,那么可以让这些参数强制使用关键字参数传递,可以将强制关键字参数放到某个参数或者单个后面就能达到这种效果:

In : def recv(maxsize, *, block):
....:
....:     pass
....:

In : recv(1024, True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-49-8e61db2ef94b> in <module>()
----> 1 recv(1024, True)

TypeError: recv() takes 1 positional argument but 2 were given

In : recv(1024, block=True)

通配符**

我们都知道在 Python 2 时不能直接通配递归的目录,需要这样:

found_images = \
    glob.glob('/path/*.jpg') \
  + glob.glob('/path/*/*.jpg') \
  + glob.glob('/path/*/*/*.jpg') \
  + glob.glob('/path/*/*/*/*.jpg') \
  + glob.glob('/path/*/*/*/*/*.jpg')

Python3 的写法要清爽的多:

found_images = glob.glob('/path/**/*.jpg', recursive=True)

事实上更好的用法是使用 pathlib:

found_images = pathlib.Path('/path/').glob('**/*.jpg')

print

Python 3 之后 print 成为了函数,有了更多的扩展能力:

In : print(*[1, 2, 3], sep='\t')
1   2   3
In : [x if x % 3 else print('', x) for x in range(10)]
 0
 3
 6
 9
Out: [None, 1, 2, None, 4, 5, None, 7, 8, None]

格式化字符串变量

In : name = 'Fred'
In : f'My name is {name}'
Out: 'My name is Fred'

In : from datetime import *
In : date = datetime.now().date()
In : f'{date} was on a {date:%A}'
Out: '2018-01-17 was on a Wednesday'

In : def foo():
....:     return 20
....:
In : f'result={foo()}'
Out: 'result=20'

更严格的对比规范

下面这几种类型的用法在 Python 3 都是非法的:

3 < '3'
2 < None
(3, 4) < (3, None)
(4, 5) < [4, 5]
sorted([2, '1', 3])

统一 unicode 的使用

这是很多人黑 Python 2 的一点,举个例子。在 Python 2 里面下面的结果很奇怪:

In : s = '您好'

In : print(len(s))
6

In : print(s[:2])
?

Python 3 就方便了:

In : s = '您好'

In : print(len(s))
2

In : print(s[:2])
您好

合并字典

In : x = dict(a=1, b=2)
In : y = dict(b=3, d=4)
In : z = {**x, **y}
In : z
Out: {'a': 1, 'b': 3, 'd': 4}

字典可排序

Python 3 不再需要直接使用 OrderedDict:

In : {str(i):i for i in range(5)}
Out: {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}

原文: Python 3新特性汇总

使用pipenv管理你的项目

作者 xiaoming
2018年1月9日 08:00

前言

刚才使用 pipenv 发现了一个 bug, 顺手提了个的 PR。无聊之下翻了下贡献者列表,貌似没有一个我国的开发者!我的普及工作任重而道远啊,我写篇文章给大家介绍下这个终极大杀器。

Python 开发者应该听过 pip、easy_install 和 virtualenv,如果看过我的书应该还知道 virtualenvwrapper、virtualenv-burrito 和 autoenv,再加上 pyvenv、venv(Python 3 标准库)、pyenv...

额,是不是有种发懵的感觉?

那么现在有个好消息,你可以只使用终极方案: pipenv + autoenv(可选)。

「终极方案」,听起来好噱头呀。给出我的理由之前我们先了解一下 Python 虚拟环境和包管理的历史吧。

历史

在 Python 发展史上出现了很多创建和发布包的工具。 当你想要把你的项目分享出去,放到 PYPI 或者其他托管服务上的时候,就需要借助这样的工具来构建和分发项目。早在 1998 年 Python 标准库内置了模块 distutils,但是只提供了有限的支持,之后社区选择通过 setuptools 这个包实现构建和发布,它自带 easy_install,能帮助你找到、下载、安装以及更新需要使用的包。不过依然功能很有限,比如不能删除包。

当你做一个专职的 Python 开发,独立的虚拟环境也是一个开发中迫切需要的功能,在这里请大家记住一个 Ian Bicking(下称 ianb) 的开发者,08 年,他开发了 virtualenv。

社区一些 Python 核心开发者和知名项目(如 Django)核心开发者也在支持和推动这件事,后来成立了 pypa (Python Packaging Authority),pypa 早期做的就是 pip - 现在最主流的安装包的工具。再提一下,ianb 也是 pip 的早期核心开发者。

不过非常遗憾,由于和社区产生了一些矛盾,ianb 很早之前就不再写 Python 项目(可见这矛盾多大呀 😮),virtualenv 也转给了其他开发者,这是 Python 社区一个极大的损失。

现在 pip 和 virtualenv 已经被大家所熟知,甚至可以说是 Python 官方的包管理和虚拟环境选择。不过其实还是有问题,我举几个例子:

  1. 必须手动安装或删除某些特定版本的包,并记得定期更新 requirements.txt 文件,以保持项目环境的一致
  2. 有时项目中需要有多个 requirements.txt 文件,比如开发时应该用 dev-requirements.txt,现有的模式不能满足这些复杂的需要
  3. 卸载包的时候只是卸载包自己,不能处理相关依赖,时间久了项目环境就混乱了

pipenv 是什么?

Pipenv 是 Python 项目的依赖管理器。其实它不是什么先进的理念和技术,如果你熟悉 Node.js 的 npm/yarn 或 Ruby 的 bundler,那么就非常好理解了,它在思路上与这些工具类似。尽管 pip 可以安装 Python 包,但仍推荐使用 Pipenv,因为它是一种更高级的工具,可简化依赖关系管理的常见使用情况。

主要特性包含:

  1. 根据 Pipfile 自动寻找项目根目录。
  2. 如果不存在,可以自动生成 Pipfile 和 Pipfile.lock。
  3. 自动在项目目录的 .venv 目录创建虚拟环境。(当然这个目录地址通过设置 WORKON_HOME 改变)
  4. 自动管理 Pipfile 新安装和删除的包。
  5. 自动更新 pip。

对于新手和老手来说都是更好的选择。

pipenv 都包含什么?

pipenv 是 Pipfile 主要倡导者、requests 作者 Kenneth Reitz 写的一个命令行工具,主要包含了 Pipfile、pip、click、requests 和 virtualenv。Pipfile 和 pipenv 本来都是 Kenneth Reitz 的个人项目,后来贡献给了 pypa 组织。Pipfile 是社区拟定的依赖管理文件,用于替代过于简陋的 requirements.txt 文件。

Pipfile 的基本理念是:

  1. Pipfile 文件是 TOML 格式而不是 requirements.txt 这样的纯文本。
  2. 一个项目对应一个 Pipfile,支持开发环境与正式环境区分。默认提供 default 和 development 区分。
  3. 提供版本锁支持,存为 Pipfile.lock。

click 是 Flask 作者 Armin Ronacher 写的命令行库,现在 Flask 已经集成了它。

接下来,我们看看怎么使用它吧

入门

pipenv 兼容 Python 2/3,我们这里以 Mac 下 Python 3 为例:

安装 pipenv

❯ brew install python3  # 如果已经安装了可以忽略
❯ python3 -m pip install --upgrade --force-reinstall pip
❯ pip3 install pipenv --user  # 推荐安装在个人目录下export PATH="/Users/dongweiming/Library/Python/3.6/bin:$PATH"  # 把用户目录下bin放在最前面,这样可以直接使用pipenv了

使用 pipenv

用一个空目录体验一下:

❯ mkdir test_pipenv
❯ cd test_pipenv
❯ pipenv install  # 创建一个虚拟环境
Creating a virtualenv for this project…
...
Installing setuptools, pip, wheel...done.

Virtualenv location: /Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5
Creating a Pipfile for this project…
Pipfile.lock not found, creating…
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (c23e27)!
Installing dependencies from Pipfile.lock (c23e27)…
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 0/0 — 00:00:00
To activate this project's virtualenv, run the following:
 $ pipenv shell

❯ which python3
/usr/local/Cellar/python3/3.6.1/bin/python3  # 还是mac自带的Python

❯ pipenv shell  # 激活虚拟环境
Spawning environment shell (/bin/zsh). Use 'exit' to leave.
source /Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/activate

❯ which python3  # 已经在虚拟环境里了
/Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/python3

❯ exit  # 退出虚拟环境

❯ which python3
/usr/local/Cellar/python3/3.6.1/bin/python3

以上就是原来 virtualenv 的基本用法了。我们看一下当前目录现在是什么样子的:

❯ ls
Pipfile  Pipfile.lock

这个环境下目前什么都没有。我们安装 2 个包:

❯ pipenv install elasticsearch-dsl requests
Installing elasticsearch-dsl…
...
Adding elasticsearch-dsl to Pipfile's [packages]…
Installing requests…
...
Adding requests to Pipfile's [packages]…
  PS: You have excellent taste! ✨ 🍰 ✨
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (8d307d)!

现在 Pipfile.lock 已经更新了,包含了 elasticsearch-dsl、requests 和相关依赖的包信息。

另外如果你添加 --two 或 --three 标志到上面的最后一个命令,它分别使用 Python 2 或 3 来初始化你的项目。 否则将使用默认版本的 Python。

可以看一下依赖关系:

❯ pipenv graph
elasticsearch-dsl==6.1.0
  - elasticsearch [required: <7.0.0,>=6.0.0, installed: 6.1.1]
    - urllib3 [required: <1.23,>=1.21.1, installed: 1.22]
  - ipaddress [required: Any, installed: 1.0.19]
  - python-dateutil [required: Any, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.10.0]
  - six [required: Any, installed: 1.10.0]
requests==2.18.4
  - certifi [required: >=2017.4.17, installed: 2017.11.5]
  - chardet [required: <3.1.0,>=3.0.2, installed: 3.0.4]
  - idna [required: <2.7,>=2.5, installed: 2.6]
  - urllib3 [required: <1.23,>=1.21.1, installed: 1.22]

可以看到,他俩都依赖了 urllib3。虽然现在 pipenv 不能直接卸载包及其依赖,但是由于它提供了良好的接口,我们还是可以实现:

❯ pipenv uninstall `pipenv graph --json |python3 depends.py requests`
Un-installing certifi…
Uninstalling certifi-2017.11.5:
  Successfully uninstalled certifi-2017.11.5

No package certifi to remove from Pipfile.
Un-installing requests…
Uninstalling requests-2.18.4:
  Successfully uninstalled requests-2.18.4

Removing requests from Pipfile…
Un-installing idna…
Uninstalling idna-2.6:
  Successfully uninstalled idna-2.6

No package idna to remove from Pipfile.
Un-installing chardet…
Uninstalling chardet-3.0.4:
  Successfully uninstalled chardet-3.0.4

No package chardet to remove from Pipfile.
Locking [dev-packages] dependencies…
Locking [packages] dependencies…
Updated Pipfile.lock (c05ac4)!

其中 depends.py 脚本会解析依赖关系,排除其他包依赖的项目然后删除:

 cat depends.py
import sys
import json

package = sys.argv[1]
other_dependencies = set()
removing_dependencies = set([package])

for i in json.load(sys.stdin):
    for p in i['dependencies']:
        key = p['key']
        if i['package']['key'] == package:
            removing_dependencies.add(key)
        else:
            other_dependencies.add(key)

print(' '.join(removing_dependencies - other_dependencies))

再看一下现在环境中的包依赖关系:

❯ pipenv graph
elasticsearch-dsl==6.1.0
  - elasticsearch [required: >=6.0.0,<7.0.0, installed: 6.1.1]
    - urllib3 [required: >=1.21.1,<1.23, installed: 1.22]
  - ipaddress [required: Any, installed: 1.0.19]
  - python-dateutil [required: Any, installed: 2.6.1]
    - six [required: >=1.5, installed: 1.10.0]
  - six [required: Any, installed: 1.10.0]

是不是很干净呢?

其他功能

除了上述基本功能以外,pipenv 还有很多附加的功能,我举几个日常比较常用的例子:

❯ pipenv run which python # 「pipenv run」可以激活虚拟环境,并使用shell命令
/Users/dongweiming/.virtualenvs/test_pipenv-GP_s2TW5/bin/python
❯ pipenv check  # 检查装的包的安全性
Checking PEP 508 requirements…
Passed!
Checking installed package safety…
All good!
❯ pipenv --man  # 传统的看文档的方法
❯ pipenv check --style depends.py  # 代码Flake8检查,在这里我修改了depends.py让它故意有问题
/Users/dongweiming/test_pipenv/depends.py:1:1: F401 'os' imported but unused

另外由于 autoenv 也是 Kenneth Reitz 写的,所以 pipenv 默认也包含了对.env 文件的支持。

是不是方便很多呢?

原文: 使用pipenv管理你的项目

详解Python元类

作者 xiaoming
2017年11月8日 08:00

什么是元类?

理解元类(metaclass)之前,我们先了解下 Python 中的 OOP 和类(Class)。

面向对象全称 Object Oriented Programming 简称 OOP,这种编程思想被大家所熟知。它是把对象作为一个程序的基本单元,把数据和功能封装在里面,能够实现很好的复用性,灵活性和扩展性。OOP 中有 2 个基本概念:类和对象:

  1. 类是描述如何创建一个对象的代码段,用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法
  2. 对象是类的实例(Instance)。

我们举个例子:

In : class ObjectCreator(object):
...:     pass
...:

In : my_object = ObjectCreator()

In : my_object
Out: <__main__.ObjectCreator at 0x1082bbef0>

而 Python 中的类并不是仅限于此:

In : print(ObjectCreator)
<class '__main__.ObjectCreator'>

ObjectCreator 竟然可以被 print,所以它的类也是对象!既然类是对象,你就能动态地创建它们,就像创建任何对象那样。我在日常工作里面就会有这种动态创建类的需求,比如在 mock 数据的时候,现在有个函数 func 接收一个参数:

In : def func(instance):
...:     print(instance.a, instance.b)
...:     print(instance.method_a(10))
...:

正常使用起来传入的 instance 是符合需求的(有 a、b 属性和 method_a 方法),但是当我想单独调试 func 的时候,需要「造」一个,假如不用元类,应该是这样写:

In : def generate_cls(a, b):
...:     class Fake(object):
...:         def method_a(self, n):
...:             return n
...:     Fake.a = a
...:     Fake.b = b
...:     return Fake
...:

In : ins = generate_cls(1, 2)()

In : ins.a, ins.b, ins.method_a(10)
Out: (1, 2, 10)

你会发现这不算算是「动态创建」的:

  1. 类名(Fake)不方便改变
  2. 要创建的类需要的属性和方法越多,就要对应的加码,不灵活。

我平时怎么做呢:

In : def method_a(self, n):
...:     return n
...: 
In : ins = type('Fake', (), {'a': 1, 'b': 2, 'method_a': method_a})()

In : ins.a, ins.b, ins.method_a(10)
Out: (1, 2, 10)

到了这里,引出了 type 函数。本来它用来能让你了解一个对象的类型:

In : type(1)
Out: int

In : type('1')
Out: str

In : type(ObjectCreator)
Out: type

In : type(ObjectCreator())
Out: __main__.ObjectCreator

另外,type 如上所说还可以动态地创建类:type 可以把对于类的描述作为参数,并返回一个类。

用来创建类的东东就是「元类」,放张图吧:

MyClass = type('MyClass', (), {})

这种用法就是由于 type 实际上是一个元类,作为元类的 type 在 Python 中被用于在后台创建所有的类。在 Python 语言上有个说法「Everything is an object」。包整数、字符串、函数和类... 所有这些都是对象。所有这些都是由一个类创建的:

In : age = 35
In : age.__class__
Out: int

In : name = 'bob'
In : name.__class__
Out: str
...

现在,任何class中的特定class是什么?

In : age.__class__.__class__
Out: type

In : name.__class__.__class__
Out: type
...

如果你愿意,你可以把 type 称为「类工厂」。type 是 Python 中内建元类,当然,你也可以创建你自己的元类。

创建自己的元类

Python2 创建类的时候,可以添加一个metaclass属性:

class Foo(object):
    __metaclass__ = something...
    [...]

如果你这样做,Python 会使用元类来创建 Foo 这个类。Python 会在类定义中寻找metaclass。如果找到它,Python 会用它来创建对象类 Foo。如果没有找到它,Python 将使用 type 来创建这个类。

在 Python3 中语法改变了一下:

class Simple1(object, metaclass=something...):
    [...]

本质上是一样的。拿一个 4 年前写分享的元类例子(就是为了推荐你来阅读 😉 我的 PPT:《Python 高级编程》 )吧:

class HelloMeta(type):
    def __new__(cls, name, bases, attrs):
        def __init__(self, func):
            self.func = func
        def hello(self):
            print 'hello world'
        t = type.__new__(cls, name, bases, attrs)
        t.__init__ = __init__
        t.hello = hello
        return t

class New_Hello(object):
    __metaclass__ = HelloMeta

New_Hello 初始化需要添加一个参数,并包含一个叫做 hello 的方法:

In : h = New_Hello(lambda x: x)

In : h.func(10), h.hello()
hello world
Out: (10, None)

PS: 这个例子只能运行于 Python2。

在 Python 里new方法创建实例,init负责初始化一个实例。对于 type 也是一样的效果,只不过针对的是「类」,在上面的 HelloMeta 中只使用了new创建类,我们再感受一个使用init的元类:

In : class HelloMeta2(type):
...:     def __init__(cls, name, bases, attrs):
...:         super(HelloMeta2, cls).__init__(name, bases, attrs)
...:         attrs_ = {}
...:         for k, v in attrs.items():
...:             if not k.startswith('__'):
...:                 attrs_[k] = v
...:         setattr(cls, '_new_dict', attrs_)
...:
...:

别往下看。思考下这样创建出来的类有什么特殊的地方?

我揭晓一下(这次使用 Python 3 语法):

In : class New_Hello2(metaclass=HelloMeta2):
...:     a = 1
...:     b = True

In : New_Hello2._new_dict
Out: {'a': 1, 'b': True}

In : h2 = New_Hello2()

In : h2._new_dict
Out: {'a': 1, 'b': True}

有点明白么?其实就是在创建类的时候把类的属性循环了一遍把不是__开头的属性最后存在了_new_dict 上。

什么时候需要用元类?

日常的业务逻辑开发是不太需要使用到元类的,因为元类是用来拦截和修改类的创建的,用到的场景很少。我能想到最典型的场景就是 ORM。ORM 就是「对象 关系 映射」的意思,简单的理解就是把关系数据库的一张表映射成一个类,一行记录映射为一个对象。ORM 框架中的 Model 只能动态定义,因为这个模式下这些关系只能是由使用者来定义,元类再配合描述符就可以实现 ORM 了,现在做个预告,未来我会分享「如何写一个 ORM」这个主题。

参考资料

  1. What is a metaclass in Python?
  2. Understanding Python metaclasses

原文: 详解Python元类

Python北京开发者活动第一期PPT出炉啦

作者 xiaoming
2017年10月25日 08:00

Python 北京开发者活动第一期结束了,虽然我没有参加,不过仍然第一时间拿到了主题的幻灯片分享给大家。和高大上的 Pycon 相比,这种技术技术活动更是 Python 工程师需要也是想要了解到的内容,本文我站着说话不腰疼地也对这些主题闲扯几句吧。

三个主题的 Slide 地址是: https://github.com/Python-Meetup-Peking/PMP_slide ,你也可以通过文末的「阅读原文」到达。我下面的内容会提到主讲人幻灯片中的一点内容,建议大家先完整看过 PPT 再来看本文效果会更好。

asyncio 和它的朋友们

asyncio 是 Python 3 官方的解决方案,也是 Guido van Rossum(Python 创建者,简称 GvR)主推的,我在之前的博文 我为什么不喜欢 Gevent 提到过我是比较认同 asyncio,不认可 gevent 的,我也说过 asyncio 的生态还远远没有建立,现在基本都只能算是玩一玩。asyncio 是非常有革命性意义的,直到它大家当做基本常识一样的被认可需要一个过程,这个过程看起来还比较长,我认为的原因有下面这么几点:

  1. 迁移成本太高。首先是 Python 2 到 Python 3 的迁移,并不是安装一个库就完事了,而且现在大部分公司在 Python 2 下的产品和服务运行良好,迁移得有值得的收益,所以现阶段不能说服 boss 干这件事,也让大家没了动力。
  2. 使用后的效果不突出。前年使用 asyncio 发现确实比之前的方案都要快,可以翻之前的内容,不过这个快并不是一个质的飞跃,而只是一个效率的提升,当效率没有成倍的提高的时候,你很难综合的决定为了那百分之 XX 的提升来做这么大的改变,毕竟现有的效率也没有出现瓶颈。
  3. asyncio 改变了编程习惯。asyncio 的作者们已经很努力的让开发者感受不到这种变化,但是 asyncio 用起来需要一整套的开发习惯的支持,简单地说:并不是你用了 aiohttp 你的 web 请求就都是异步非阻塞的了。请求到回应过程中某个地方没有注意阻塞了直接让效果大打折扣,但是如果你对业务以及 Python 和相关的库不熟很多时候这种问题是很难发现和杜绝的。
  4. 没有大公司出来背书,也鲜有重要项目宣称支持 asyncio 版本的驱动并及时维护。举个例子,aioredis 接口和 py-redis 参数不一致,一些常用的开源项目的更新迭代需要对应驱动也跟上,要不然开发者就会很尴尬。主题中主讲人提到了 https://github.com/aio-libs 这个组织,组织下面有些 aio 的驱动,不过这是一个民间组织,大部分开发者都是俄罗斯人,其实没有 Python 核心开发者参与的,自娱自乐成份要更多一些,质量上我是不放心的。如果哪天豆瓣上了 asyncio,我们肯定会选择自己维护一份相关的驱动,而不用社区性质的。其实在 Github 看到的大部分 asyncio 项目都不是官方的。大部分 Python 核心开发者在自己的公司也没有推这件事,只有少量的核心开发做了 asyncio 支持,比如 MongoDB 就有官方的 motor。

说了这么多消极的,那么我们能给 asyncio 这个生态做点什么?

  1. 多多用 asyncio 做项目,多写一些 asyncio 相关的文章,让更多人了解它接受它。
  2. 能力范围内造造常用工具的驱动,或者参与进来给现有项目提交 PR,你们可能不知道 Flask 刚流行那会插件系统的繁荣场景,其实做这个事情并没有那么难,整的理解 asyncio 之后,驱动就是一些套路罢了。

Python 性能调优

性能调优是开发者一直很关注的话题,在我的《Python Web 开发实战》中也介绍了很多的我常用的工具和使用方法,这个主题的内容基本覆盖了,唯一在书中没有提到的是 pyflame,不过之前在转载 Glow 的一篇《 Python web 应用性能调优 》时也提到了。

现在大家也能知道大家平时用的工具都差不多,倒没有秘籍。我其实更想说的是性能调优要求工程师平时要很关注到服务器资源的指标,及时发现问题,并且愿意花时间去分析和定位问题。

Python 在一家创业公司中的应用

清风老师早早的使用上了 Python3,羡慕。当然这也是由于没有历史包袱的原因。在老师介绍「Python3 带来了哪些好处」的时候提到:

  1. 带来了类型检查
  2. 不再被字符串编码问题折磨

第二点是很多人的痛就不提了,我来讲下对类型检查的看法。无独有偶,今年 Pycon 上海唯一能看的洪教授所在的爱因互动的主题演讲「Building Chatbot Service」中也提到了 Type Hint(当然也提到了 asyncio)。我之前提到过我不喜欢 TypeScript(当然原因复杂得多),所以答案是我对类型检查持保留意见(甚至反对)。当然这部分在一开始也在 Python 社区产生过分歧,我不喜欢的原因有几点:

  1. 我觉得 Python 就应该走简洁的路线,加上类型检查(哪怕是非强制的)让代码看起来臃肿不舒服,如果哪天 Python 强制要这么干,我就不再❤️它了。
  2. 类型错误造成的错误主要是由于工程师不了解业务需要和对自己写的代码没有充分理解才会出现,当然这还有异常处理的考虑的经验问题。靠类型检查来查出来,本质上是对工程质量的不信任。

当然类型检查这种方式可以极大的减少了代码出错的几率,让问题更早的暴露出来,这在实际生产环境中确实是有意义的,比如核心的如支付系统,为了确保万无一失,用上类型检查我还可以认同。

第二个点是老师创业选择的框架是 Django,理由是:

  1. 对于长期项目更加利于维护
  2. 第三方库众多
  3. BUG 比较少
  4. instagram 也在使用 - 之前我也发过一个翻译分享 Instagram 的工程师 Hui Ding 说到:『一直到用户 ID 已经超过了 32bit int 的限额(约为 20 亿),Django 本身仍然没有成为我们的瓶颈所在。』

这些我都是认可的,Django 确实是 Web 开发首选。大家知道我一直是推荐 Flask 的,它对于初学者友好,但是并不是让大家止步于此。我不用 Django 最重要的是我日常工作中没什么机会用 Django,而更常用 Flask 的首要的原因是我对它很熟悉,我可以随便自由地玩出花儿来,大家在实际开发的时候应该都知道都要基于业务场景对相关第三方库要进行一些定制,有些可能都不合适提交给上游而只能自己维护一个版本,所以对 Web 框架的熟悉程度是选择的重要标准。我之前记得什么地方有人说「企业开发都是用 Django,Flask 就是玩具」这样的论调,一看就是知识太狭隘,框架学的也不好,一个框架学好了,学其他的真的很轻松的。

想想为什么我没有什么机会用 Django 呢?

  1. 我个人不喜欢 Django 的大包大揽的不灵活,不用它但是我有能力「造」
  2. 没有历史遗留项目的包袱牵扯我的精力
  3. 我身边的工程师也不喜欢它,不会主动选择它
  4. 很早前阅读它的代码,它耦合度很高,和 Flask 写代码的方式差别是很明显的,这种方式倒不是不好,优点是代码利用率很高,缺点是刚了解它的人来说阅读和理解代码耗时耗力,Flask 则反之,代码调用层级明确且关系简单。

清风老师说工作中也使用了 Flask,在适当的场景下选择最适合的框架,这才是优秀工程师应该做的。

原文: Python北京开发者活动第一期PPT出炉啦

IPython3时代到来

作者 xiaoming
2015年2月28日 08:00

前言

我以前写过一些 IPython 高级用法 , 还有在组内分享了一期 IPython notebook 的分享 . 今天 IPython3 被 release 了。它带来什么可以看一下 release notes . 好吧,我也没有意识到 ipython3 来的会这样快。这多半年来。我作为一个 150 个贡献者之一,见证了 IPython 的发展。这是个里程碑的版本。他带来了非常多的变化和新的特性。今天我来帮大家迁移和解读一些吧.

IPython 是什么?本质上它是一个增强版的 python 交互模式解释器,所见即所得的执行代码,查看结果,也拥有历史记录。我认为这是一个 python 开发者必备的工具。我个人依赖 ipython 常用的功能有:

  • ipython notebook - 一个可以跑的在线可编辑可运行的笔记。可以测试程序,执行代码,当做说明文档,能帮助不擅长 web 开发的同学做出很多页面的效果,支持 markdown 语法等
  • 自动补全 - 当我 import xx 的时候 我可以像用 zsh 一样使用 Tab 自动补全对应的模块 / 方法的名字
  • magic - 它提供很很多 magic 的函数命令,比如你可以直接执行 ls, pwd 等。还能使用其他 shell 命令,调用编辑器等
  • 它能通过?或者?帮我查看代码的注释,接口参数等等.
  • 它提供很多的配置选择,可以使用内置 / 外部插件达到一些其他的功能,比如 autoreload - 你不需要退出 ipython 就能获得你已经 import 之后的代码修改后的效果.
  • 它在分布计算,数据分析上又很好的支持,ipython 非常大的使用群体是科学家和算法工程师

它在 python 界有什么地位?我肯定会带有个人色彩。来一些 github 的数据说一说 (截止到 2015-03-01 之前):

项目 Issue 数 Star 数

django 4221 13088

flask 1359 12810

tornado 1352 8626

ipython 7898 5822

这是 python 最有名的几个项目。可以看到 ipython 的 star 远落后于其他。但是他的 issue 数却大大的高于其他,一方面 IPython 覆盖的功能和逻辑更多更复杂. 一方面用户对 IPython 的依赖和兴趣要高很多,还有一方面 IPython 也由于内容太多更容易有 bug, 且主要维护者都是科学家没有太多精力和兴趣做一些基础保障. 可见 IPython 的知名度不高,但是对用户粘性却很高.

如何升级

假如你需要使用 ipython notebook, 需要使用

pip install --upgrade "ipython[all]"

否则直接

pip install --upgrade ipython

使用不同的内核 (kernel)

IPython 的组件大多是核心开发者开发的,中提到了 kernel 是这样几个:

Bash
Echo
Python2
Python3
R

Bash 是这个项目 https://github.com/takluyver/bash_kernel/ , 你可以直接

sudo pip install bash_kernel

那么开始说 kernel 是什么,kernel 是一个能执行各种语言的程序封装,比如可以用 notebook 跑 bash, 跑 ruby, 能使用其他语言的语法. 上面的 bash 就是借用 pexpect 的 replwrap 实现的 bash 的封装。对比一下就知道了:

$/usr/local/bin/ipython
In [1]: echo
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-53f31a089339> in <module>()
----> 1 echo

NameError: name 'echo' is not defined
In [2]: bc
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-b79898bb7907> in <module>()
----> 1 bc

NameError: name 'bc' is not defined
$/usr/local/bin/ipython console --kernel bash # 使用bash内核就可以使用这些bash下命令了
IPython Console 3.0.0

In [1]: echo '2-1'
2-1

In [2]: echo '2-1'|bc
1

看一下我本地都能用什么 kernel:

$ipython kernelspec list
Available kernels:
python2
python3
bash
echo

python2 就是系统默认的,原来用的那个。看到 echo 和 python3 的原因在这里:

$pwd
/Users/dongweiming/.ipython/kernels
$tree
.
├── echo # 在~/.ipython/kernels有这个echo的目录里面包含了正确地kernel.json就会出现对应的kernel
   └── kernel.json
├── echokernel.py
└── python3
    └── kernel.json

2 directories, 3 files

看一下代码:

$cat echo/kernel.json
{"argv":["python","-m","echokernel", "-f", "{connection_file}"],
 "display_name":"Echo"
 }

$cat python3/kernel.json
{
  "display_name": "IPython (Python 3)",
  "language": "python",
  "argv": [
    "python3",
    "-c", "from IPython.kernel.zmq.kernelapp import main; main()",
    "-f", "{connection_file}"
    ],
  "codemirror_mode": {
    "version": 2,
    "name": "ipython"
    }
}
$ipython console --kernel python3 # 可以在python2下跑python3的代码了
In [1]: print
Out[1]: <function print>

In [2]: print 'sd'
  File "<ipython-input-2-f747b7d9e029>", line 1
      print 'sd'
      ^
SyntaxError: invalid syntax

更多的自定义请看 Wrapper kernels

当然这里默认都可以在 notebook 里使用

Widget

widget 系统经过了很大的重构和更新,全部信息在 widget migration guide .

Widget 是什么?这是 ipython notebook 的插件系统,大部分的插件都可以看这里: containers_widgets.ipynb , 看完就知道它是什么和它能做什么了. 在我分享 notebook 的项目 divingintoipynb 里你能看到我自定义的 widget: selectize_widget.ipynb , 和对应的 widget_selectize.py .

custom.js 在使用的时候也有了很大的变动,可以看我分享项目的 custom.js

Notebook format

原来的 Notebook 的版本是 3, 现在已经升级为 4. 他们是不兼容的版本。在启动新版 IPython 访问你的 ipynb 的时候会出现这样的弹出框:

This notebook has been converted from an older notebook format (v3) to the current notebook format (v4). The next time you save this notebook, the current notebook format will be used. Older versions of IPython may not be able to read the new format. To preserve the original version, close the notebook without saving it.

你记得保存一下会帮你自动转化为新版本,下次再启动就可以。假如由于 bug 或者其他原因想降级可以这样:

ipython nbconvert --to notebook --nbformat 3 <notebook>

使用 jinja2 自定义模板

通过 NotebookApp.extra_template_paths 可以指定外部模板目录,可以代替默认模板。或者:

ipython notebook '--extra_template_paths=["/Users/dongweiming/.ipython/templates/"]'

比如你不喜欢 ipython notebook 提供的现有的目录页 ( http://localhost:8000/tree 这样的路径 ). 你可以自己写一个叫做 tree.html. 的模板放在 /Users/dongweiming/.ipython/templates/ 目录下。但是建议还是继承原来的 tree.html, 再自由发挥.

使用 ipython notebook 的 terminal 功能.

在 notebook 页面上其实是可以直接使用 websocket 连接到服务器上的。但是你需要安装 terminado. 这样在 /tree 下新建的时候就能选择 terminal 了.

其他

剩下就是一些 bug 修改,方法重命名,功能增强,去掉一些不再被维护的内容等等。对 95% 的用户影响几乎没有.

原文: IPython3时代到来

ptpython- a better Python REPL

作者 xiaoming
2015年1月26日 08:00

前言

今天发现一个项目: ptpython . 新一代的 REPL 神器。玩了一下。毅然的 ipython 换成了 ptipython - ptpython 自带的 iython 的接口。和大家介绍下

ptpython 的优点

先说几个在用 ipython 过程中遇到的问题吧.

  1. ipython 在 Mac 下缩进问题。每次在交互模式里面输入一个缩进的内容,比如下面
In [1]: def t():
   ...:     return 1
      ...:

In [2]: def t():
    return 1

看到了吧,缩进不正确了. ptpython 没有这个问题,它的底层库 prompt_toolkit 实现了一个替代物

  1. jedi. 我在 emacs 里面,jedi 是标配。其一是因为有 epc, 可以和 python 通信,它的自动补全和跳到函数 / 类定义真的很方便。但是 python 交互解释器 还没有一个用它的。这里真的感觉效果很好.
  2. emacs/vim 键位。好吧我经常在用 ipython 的时候习惯 Ctrl+x Ctrl+c 退出。能用 emacs 的键位做编辑真的很爽
  3. 提供一个终端的菜单,有多个选项可以选择
  4. 模式粘贴。大家知道 python 有个问题:你粘贴过来的代码不一定能运行 - 粘贴后的缩进会很奇怪的. ipython 虽然有 % paste 和 % cpaste. 但是有时候还是会有 IndentationError 问题. ptpython 在这里独创了 Paste mode. 使用 F7 切换。还可以多行编辑.
  5. 可以开启多个 Tab, 甚至多个 Tab 的内容一齐显示出来。类似 vim 的:split
  6. 能对你写的每行程序判断是否出现语法错误。如果你的代码有问题,下面左侧会有错误提示 - 这其实能延伸做很多检查嘛
  7. ipython 对查看对象的方法有一些问题,比如这个:
$ipython
In [1]: '/tmp'.<tab> # 不会理你的

ptpython 对这样的处理都很好

  1. 一个很贴心的特性:
$ptpython
In [1]: 'tmp<Tab> # 他会告诉你这是个目录, 还会自动完成列出目录下的文件. autocompletion
  1. 最后一点吧。它非常容易的被嵌入你的程序,你的解释器
python
Python 2.7.3 (default, Apr 10 2013, 06:20:15)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Welcome to Lazy Python.  Type "help LazyPython" for help.
>>> from ptpython.repl import embed
>>> embed(globals(), locals(), vi_mode=False, history_filename=None)
In [1]: # 看就这么2句

ptipython

ptipython = ptpython + ipython

在安装了 ptipython 之后,就可以使用了。完全可以替代 ipython. 类似 bipython = bpython + ipython

原文: ptpython- a better Python REPL

推荐pre-commit/pre-push

作者 xiaoming
2015年1月12日 08:00

前言

使用 git 的同学想必都有这样的工作场景 - 保证生产环境的 ci 不挂。也就是检查 python 是否符合 pep8/csslint/jslint/pylint/pyflake8 等. 我在我的 emacs 配置中加入了这一项 py-autopep8 , 就是在保存缓存区的时候把当前缓存区的文本放到一个临时文件,然后执行 autopep8, 再检查 pep8/flake8

但是不能对 css/js/html 做规范检查。而且也不通用。周末看到了 Yelp 的 pre-commit . 感觉是个很有意思的东西,虽然之前也写过类似的 hook. 但是没有它灵活。看完他的源码后,我今天给大家介绍下这个东西

pre-commit

玩过 svn/git 的同学应该都知道他们有各种的 hook. 也就是准备 / 完成什么事件的时候做些额外的工作。一般是 shell 脚本,版本控制工具会判断脚本的退出码,如果不是 0, 就不会继续完成. pre-commit 顾名思义就是在 commit 之前做的准备,也就是每次执行

git commit -m 'xxx'

的时候去做一些检查。启用的插件都放到这个版本库目录的根目录下,名字叫做.pre-commit-config.yaml -> 详细文档请看: http://pre-commit.com/

这里有我的一个配置例子:

-   repo: https://github.com/pre-commit/pre-commit-hooks
    sha: b03733bc86d9e8b2564a5798ade40d64baae3055
    hooks:
    -   id: trailing-whitespace
    -   id: end-of-file-fixer
    -   id: autopep8-wrapper
    args: ['-i', '--ignore=E265,E309,E501']
    -   id: check-docstring-first
    -   id: check-json
    -   id: check-yaml
    -   id: debug-statements
    -   id: name-tests-test
    -   id: requirements-txt-fixer
    -   id: flake8
-   repo: https://github.com/pre-commit/pre-commit
    sha: 86c99c6b870a261d2aff0b4cdb36995764edce1b
    hooks:
    -   id: validate_config
    -   id: validate_manifest
-   repo: https://github.com/asottile/reorder_python_imports
    sha: ea9fa14a757bb210d849de5af8f8ba2c9744027a
    hooks:
    -   id: reorder-python-imports

安装使用

pip install pre-commit
pre-commit install
# PS: 第一次执行commit会比较慢,因为他会clone对应的源, 之后就会用这个缓存的源
# 其他的可选源和用法直接参照[https://github.com/pre-commit](https://github.com/pre-commit)里面的项目或者[http://pre-commit.com/hooks.html](http://pre-commit.com/hooks.html)

看一个失败的例子 (有颜色效果,不能展示出来)

$git commit -m 'test'

Trim Trailing Whitespace.................................................................................................................Passed
Fix End of Files.........................................................................................................................Passed
autopep8 wrapper.........................................................................................................................Passed
Check docstring is first.................................................................................................................Passed
Check JSON..........................................................................................................(no files to check) Skipped
Check Yaml..........................................................................................................(no files to check) Skipped
Debug Statements (Python)................................................................................................................Passed
Tests should end in _test.py........................................................................................(no files to check) Skipped
Fix requirements.txt................................................................................................(no files to check) Skipped
Flake8...................................................................................................................................Failed
hookid: flake8

pre_commit/__init__.py:2:1: F401 'os' imported but unused
pre_commit/__init__.py:3:1: F401 'sys' imported but unused

Validate Pre-Commit Config..........................................................................................(no files to check) Skipped
Validate Pre-Commit Manifest........................................................................................(no files to check) Skipped
Reorder python imports...................................................................................................................Passed
# 因为我的flake8有问题 所以commit失败了

pre-commit 的问题

我觉得对每次 commit 做一次审查,第一是需要时间,第二是没有必要,因为经常一个 pr 有多个 commit, 我只保证整体结果是正确的就好了 - 也就是说应该是在 push 的时候。整个过程我可能对 commit 做多次 rebase/--amend 等等。某一次的检查失败其实完全不 影响我做后的结果 - 我是手快党

so. 我基于它修改了一个版本 pre-push , 只是我对 push 做了拦截。并且我会经常和它保持同步

pre-commit install -t pre-commit # 默认安装pre-commit钩子, 每次commit触发
pre-commit install -t pre-push # 默认安装pre-push钩子, 每次push触发

其他用法完全一样.

假如 push 的时候想要不检查而强制 push, 可以加上 --no-verify 参数

Update from 2015-01-15

我的这个分支已经合并到 pre-commit . pull189

大家可以不要用我的分支了. PS: 这是我见到测试覆盖最高的项目.

原文: 推荐pre-commit/pre-push

ipython的一些高级用法(二)

作者 xiaoming
2014年12月11日 08:00

今天我们学习下写 ipython 的 magic 命令。好,magic 是什么?它是 ipython 自带的一些扩展命令,类似 % history, % prun, % logstart..

想查看全部的 magic 可以使用 ismagic, 列出可用的全部 magics

%lsmagic

magic 分为 2 类:

  • line magic: 一些功能命令
  • cell magic: 主要是渲染 ipython notebook 页面效果以及执行某语言的代码
idb-pythondb.pyshellextension"> idb - python db.py shell extension

idb 是我最近写的一个 magic. 主要是给 ipython 提供 db.py 的接口,我们直接分析代码 (我只截取有代表性的一段):

import os.path
from functools import wraps
from operator import attrgetter
from urlparse import urlparse

from db import DB # db.py提供的接口
from IPython.core.magic import Magics, magics_class, line_magic # 这三个就是我们需要做magic插件的组件


def get_or_none(attr):
    return attr if attr else None


def check_db(func):
    @wraps(func)
    def deco(*args):
        if args[0]._db is None: # 每个magic都需要首页实例化过db,so 直接加装饰器来判断
            print '[ERROR]Please make connection: `con = %db_connect xx` or `%use_credentials xx` first!'  # noqa
            return
        return func(*args)
    return deco


@magics_class  # 每个magic都需要加这个magics_class装饰器
class SQLDB(Magics): # 要继承至Magics
    _db = None # 每次打开ipython都是一次实例化

    @line_magic('db_connect') # 这里用了line_magic 表示它是一个line magic.(其他2种一会再说) magic的名字是db_connect. 注意 函数名不重要
                              # 最后我们用 %db_connect而不是%conn
    def conn(self, parameter_s): # 每个这样的方法都接收一个参数 就是你在ipython里输入的内容
        """Conenct to database in ipython shell.
        Examples::
            %db_connect
            %db_connect postgresql://user:pass@localhost:port/database
        """
        uri = urlparse(parameter_s) # 剩下的都是解析parameter_s的逻辑

        if not uri.scheme:
            params = {
                'dbtype': 'sqlite',
                'filename': os.path.join(os.path.expanduser('~'), 'db.sqlite')
            }
        elif uri.scheme == 'sqlite':
            params = {
                'dbtype': 'sqlite',
                'filename': uri.path
            }
        else:
            params = {
                'username': get_or_none(uri.username),
                'password': get_or_none(uri.password),
                'hostname': get_or_none(uri.hostname),
                'port': get_or_none(uri.port),
                'dbname': get_or_none(uri.path[1:])
            }

        self._db = DB(**params) # 这里给_db赋值

        return self._db # return的结果就会被ipython接收,显示出来

    @line_magic('db') # 一个新的magic 叫做%db -- 谨防取名冲突
    def db(self, parameter_s):
        return self._db

    @line_magic('table')
    @check_db
    def table(self, parameter_s):
        p = parameter_s.split() # 可能传进来的是多个参数,但是对ipython来说,传进来的就是一堆字符串,所以需要按空格分隔下
        l = len(p)
        if l == 1:
            if not p[0]:
                return self._db.tables
            else:
                return attrgetter(p[0])(self._db.tables)
        else:
            data = self._db.tables
            for c in p:
                if c in ['head', 'sample', 'unique', 'count', 'all', 'query']:
                    data = attrgetter(c)(data)()
                else:
                    data = attrgetter(c)(data)
            return data

def load_ipython_extension(ipython): # 注册一下. 假如你直接去ipython里面加 就不需要这个了
    ipython.register_magics(SQLDB)

PS:

  1. 调试中可以使用 % reloa_ext idb 的方式重启 magic
  2. % install_ext 之后默认放在你的 ipython 自定义目录 /extensions 里。我这里是~/.ipython/extensions

好了,大家是不是觉得 ipython 的 magic 也不是很难嘛

来了解 ipython 都提供了什么?
  1. magic 装饰器的类型:
  • line_magic # 刚才我们见识了,就是 % xx, xx 就是 magic 的名字
  • cell_magic # 就是 %% xx
  • line_cell_magic # 可以是 % xx, 也可以是 %% xx

先说 cell_magic 来个例子,假如我想执行个 ruby, 本来应该是:

In [1]: !ruby -e 'p "hello"'
"hello"

In [2]: %%ruby # 也可以这样
   ...: p "hello"
      ...:
      "hello"

再说个notebook的:

In [3]: %%javascript
   ...: require.config({
   ...:     paths: {
   ...:         chartjs: '//code.highcharts.com/highcharts'
   ...:     }
   ...: });
   ...:
   <IPython.core.display.Javascript object>
});

然后再说 line_cell_magic:

In [4]: %time 2**128
CPU times: user 2 µs, sys: 1 µs, total: 3 µs
Wall time: 5.01 µs
Out[4]: 340282366920938463463374607431768211456L

In [5]: %%time
   ...: 2**128
   ...:
   CPU times: user 4 µs, sys: 0 ns, total: 4 µs
   Wall time: 9.06 µs
   Out[5]: 340282366920938463463374607431768211456L

Ps: line_cell_magic 方法的参数是 2 个:

@line_cell_magic
def xx(self, line='', cell=None):
带参数的 magic(我直接拿 ipython 源码提供的 magic 来说明):

一共 2 种风格:

  • 使用 getopt: self.parse_options
  • 使用 argparse: magic_arguments
self.parse_options
@line_cell_magic
def prun(self, parameter_s='', cell=None):
    opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
                                       list_all=True, posix=False)
    ...

getopt 用法可以看这里 http://pymotw.com/2/getopt/index.html#module-getopt

我简单介绍下 'D:l:rs:T:q' 就是可以使用 -D, -l, -r, -s, -T, -q 这些选项.: 号是告诉你是否需要参数,split 下就是: D:,l:,r,s:,T:,q 也就是 - r 和 - q 不需要参数其他的都是参数 类似 % prun -D

magic_arguments
@magic_arguments.magic_arguments() # 最上面
@magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
    help="""
    Set break point at LINE in FILE.
    """
) # 这种argument可以有多个
@magic_arguments.argument('statement', nargs='*',
    help="""
    Code to run in debugger.
    You can omit this in cell magic mode.
    """
)
@line_cell_magic
def debug(self, line='', cell=None):
    args = magic_arguments.parse_argstring(self.debug, line) # 要保持第一个参数等于这个方法名字,这里就是self.debug
    ...

还有个 magic 方法集:用于并行计算的 magics: IPython/parallel/client/magics.py

原文: ipython的一些高级用法(二)

python几个特别的__开头的方法

作者 xiaoming
2014年12月6日 08:00

前言

A Guide to Python's Magic Methods python 的绝大多数这样的特殊方法都 在这里面被提到了。今天我来说 3 个他没有提到的[dir, slots, weakref], 再强调下他提到的 2 个[missing, contains]

dir-> 看个小例子就知道了
In [1]: class T(object):
   ...:     pass
   ...:
In [2]: t = T()
In [3]: t.<Tab>
啥也没有...
In [4]: class T2(object):
   ...:     def __dir__(self):
   ...:         return ['a', 'b']
   ...:
In [5]: t = T2()
In [6]: t.
t.a  t.b
In [7]: dir(t)
Out[7]: ['a', 'b']

看出来了把,不解释,但是这个dir是相对于类的实例有效果的.

slots

这个在我初学 python 的时候就被模糊了,原来的理解是它的出现替代了dict,也就是说你只能给slots这个变量列表项的属性赋值。对外的接口减少了,也安全了。后来看了这篇 Saving 9 GB of RAM with Python’s slots . 好久不做运维了,在生产环境究竟怎么样我无法定论,也提到了,在对象实例很多的时候他能帮助减少内存,详见 https://www.safaribooksonline.com/library/view/python-cookbook-3rd/9781449357337/ch08s04.html . 这里来个小实验 (在 Hacker News 也被讨论过 https://news.ycombinator.com/item?id=6750187 )

代码例子 (我对细节做注释):

# coding=utf-8
import sys
from itertools import starmap, product


class SlotTest(object):
    # __slots__ = ['x', 'y', 'z'] 主要对比去掉这句和包含这句程序内存占用

    def __init__(self, x, y, z):
            self.x = x
                    self.y = y
                            self.z = z

    def __str__(self):
            return "{} {} {}".format(self.x, self.y, self.z)

p = product(range(10000), range(20), [4]) # 创建0-1000 & 0-20 & 4 的笛卡尔积
a = list(starmap(SlotTest, p)) # 相当于对每个SlotTest实例化,实例化的格式是p的长度

print a[0]
sys.stdin.read(1)

结果对比:

$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 未使用__slots__
  total kB          103496   76480   73728
$pmap -x `ps -ef|grep test_slot.py|grep -v grep|awk '{print $2}'`|grep total # 使用了__slots__
  total kB           49960   22888   20136

结果很明显,内存占用减少了很多...

weakref弱引用

首先先说下 weakref : 弱引用,与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收. 在 Python 中,当一个对象的引用数目为 0 的时候,才会被从内存中回收。但是被循环引用呢?

In [1]: import weakref

In [2]: import gc

In [3]: class Obj(object):
   ...:     def a(self):
   ...:         return 1
   ...:
In [4]: obj = Obj()

In [5]: s = obj

In [6]: gc.collect() # 不可达引用对象的数量
Out[6]: 3

In [7]: print s is obj
True

In [8]: obj = 1 # 最初的被引用的对象改变了.

In [9]: gc.collect()
Out[9]: 0

In [10]: s is None # s还是指向了Obj 引用计数为1
Out[10]: False

In [11]: s
Out[11]: <__main__.Obj at 0x2b36510>

----华丽的分割一下

In [12]: obj = Obj()

In [13]: r = weakref.ref(obj) # 让obj变成那个弱引用

In [14]: gc.collect()
Out[14]: 211

In [15]: r() is obj
True

In [16]: obj = 1

In [17]: gc.collect()
Out[17]: 0

In [18]: r() is None # 弱引用计数器没有增加,所以当obj不在引用Obj的时候,Obj对象就被释放了
Out[18]: True

好吧,我的总结是弱引用是个好东西,但是加了slots就不支持弱引用了。所以需要weakref

In [9]: class T3(object):
   ...:     __slots__ = []
      ...:

In [10]: class T4(object):
   ....:     __slots__ = '__weakref__'  # 这样就支持了weakref
      ....:

In [11]:  import weakref

In [12]: t3 = T3()

In [13]: t4 = T4()

In [14]: weakref.ref(t3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-bdb7ab7ac3bc> in <module>()
----> 1 weakref.ref(t3)

TypeError: cannot create weak reference to 'T3' object

In [15]: weakref.ref(t4)
Out[15]: <weakref at 0x2766f70; to 'T4' at 0x2586fd8>
contains判断某值 in/not in 实例
In [1]: class NewList(object):
   ...:     def __init(self, values):
   ...:         self.values = values
   ...:     def __contains__(self, value):
   ...:         return value in self.values
   ...:
In [2]: l = NewList([1, 2, 3, 4])

In [3]: 4 in l
Out[3]: True

In [4]: 10 in l
Out[4]: False
missing

最初看这个特殊方法是看 python 标准库的源码的时候 (collections#L421):

class Counter(dict):
    ...

    def __missing__(self, key):
        'The count of elements not in the Counter is zero.'
        # Needed so that self[missing_item] does not raise KeyError
        return 0

什么意思呢?

In [6]: c = collections.Counter({'a':1})

In [7]: c['b'] # 没有键的count设置默认值0
Out[7]: 0

很多人可能看过这个 (关于 defaultdict 的 ppt)[ http://discorporate.us/jek/talks/defaultdict/ ]. 内容就不说了,讲的非常好.

原文: python几个特别的__开头的方法

一个使用python的web程序员的emacs.d

作者 xiaoming
2014年8月12日 08:00

前言

越来越多的人使用 emacs 作为开发工具。甚至 skype,gmail, 豆瓣 FM 都能通过 emacs. 作为一个产品开发,肯定使用很多插件,设置一些快捷键来提高开发效率。以前一直使用 prelude , 很久之后发现有以下问题:

  1. 比如开启 python 语言支持需要在 prelude-modules.el 里面把 python 这样的注释去掉
  2. 我不需要支持这么多的语言,也不需要那么多快捷键
  3. aotupair 实在太难用了
  4. scss/css 模式不好自定义缩进空格数,tab 和空格混用。不好定制
  5. 看过源码后发现,其实很来很简单粗暴的事情弄得有点复杂了

我造了个轮子 .emacs.d , 主要针对 python 和 web 开发

Update

2014-09-28, 经过这一个月的继续研究,已经有了很大的改变

项目目录结构

├── Cask ; 我使用[cask](https://github.com/cask/cask)做包管理工具
├── auto-insert ; 使用auto-insert设置新增elisp/python文件自动添加基于yasnippet的模板
│   ├── elisp-auto-insert
│   └── python-auto-insert
├── custom ; 自定义插件目录,你也可以把你写的程序放进来然后在init.el里面require
│   ├── flycheck.el ; 定制flycheck,让它在保存python程序时自动执行pep8和flake8,有问题的条目会打开新的buffer打印出来
│   └── py-autopep8.el ; 我自己实现了autopep8插件,保存时自动根据pep8标准处理文件
├── functions.el ; 用到的相关函数
├── helper.el ; 我自己写了个类似`C-h b`的介绍绑定的快捷键的预览表
├── hs-minor-mode-conf.el ; python函数/类折叠
├── init.el ; emacs启动的主程序
├── keys.el ; Key-chord配置,默认被注释了,因为它和我经常大片粘贴代码中代码重复造成很多麻烦
├── local-settings.el ; 本机的本地配置,比如用户名,单独的快捷键等
├── misc.el ; 对emacs本身的一些配置
├── mode-mappings.el ; 模式映射,比如Cask会自动用emacs-lisp-mode
├── modeline.el ; 我重新定制了modeline,使用了nyan-mode和powerline,一些加颜色的hack
├── osx.el ; Mac下的一些独立配置,为我的hhkb定制
├── smartparens-config.el ; 定制了smartparens配置
├── tmp
│   └── README.md
└── xiaoming-theme.el ; 我自己写了一个主题,好吧 我就是`小明`

使用的插件列表

  1. f - 处理文件相关的库
  2. s - 处理字符串相关的库
  3. ag - 据说比 ack 更快的文本搜索工具 the_silver_searcher 的 emacs 插件
  4. ht - 处理哈希相关的库
  5. anzu - 显示当前匹配文本,预览替换效果和总匹配数的插件
  6. dash - 常用函数集合
  7. helm - 方便查找各种文件内容,buffer 切换,emacs 命令执行等
  8. jedi - python 代码补全,快速需要函数 / 模块定义的插件
  9. smex - M-x 的命令行补全的功能
  10. direx - 展示目录树
  11. magit - git 插件
  12. slime - commonlisp 交互模式
  13. ac-js2 - js2-mode 支持 js 函数定义查找
  14. rinari - 依赖,需要安装
  15. diff-hl - 在行首用颜色表示 git 状态 - 只支持图形界面的 emacs
  16. dired-k - 用带不同颜色的高亮显示文件 / 目录,大小等信息
  17. bind-key - 本项目绑定快捷键的用法都根据这个包,没有用 global-set-key
  18. css-mode - css-mode
  19. js2-mode - js-mode 的升级版
  20. web-mode - 前端开发必备,html 缩进,支持根据 tag / 元素 / 属性 /block/dom 跳转,语法高亮,支持 mako,jinja2 等模板
  21. git-blame - git-blame, 单独版
  22. key-chord - 可以快速按键达到快捷键的作用
  23. nyan-mode - 一直可爱的小猫
  24. plim-mode - 我写的编辑 plim 的 major-mode
  25. powerline - 提供一个漂亮的状态栏
  26. sass-mode - 编辑 sass
  27. scss-mode - 编辑 scss
  28. sublimity - 在图形界面的 emacs 能缩小预览代码 - sublime-text 有类似的插件
  29. undo-tree - 让 undo 可视化
  30. yaml-mode - 编辑 yaml
  31. yasnippet - 一个神奇的模板系统,定义缩写并通过 tab 键自动帮你展开 (一些自动的 "填空题" 机制)
  32. drag-stuff - 可以将代码块整体拖动
  33. helm-swoop - 项目内关键词查找,并能自动跳到对应文件和对应行
  34. ibuffer-vc - 支持版本空的 ibuffer 模式
  35. projectile - 管理项目,可快速访问项目里任何文件,支持全项目关键词搜索
  36. coffee-mode - 编辑 coffee
  37. python-mode - 编辑 python
  38. smartparens - 自动括号匹配,可以按块删除,tag 跳转
  39. use-package - 本项目引用包的方式
  40. crontab-mode - 高亮编辑 crontab
  41. golden-ratio - 黄金分割展示当前 window
  42. helm-ipython - helm 的 ipython 插件
  43. rainbow-mode - 在代码中通过背景色标示颜色值
  44. ace-jump-mode - 快速让光标位置到你想去的地方
  45. expand-region - 按层次块区域选择
  46. helm-css-scss - helm 的 css/scss 插件
  47. markdown-mode - 编辑 markdown
  48. switch-window - 可视化切换窗口
  49. visual-regexp - 可视化正则匹配
  50. gitconfig-mode - 单独的 gitconfig-mode
  51. gitignore-mode - 单独的 gitignore-mode
  52. helm-descbinds - 让默认的 C-h b 高亮并且按组分开
  53. imenu-anywhere - 类似于 etag, 可直接跳到对应的标签
  54. multiple-cursors - 一次编辑多处 / 行文字
  55. discover-my-major - 告诉你当前 mode 的一些说明 / 快捷键设置
  56. virtualenvwrapper - virtualenvwrapper
  57. gitattributes-mode - 独立的 gitattributes-mode
  58. rainbow-delimiters - 对内嵌的括号等 pair 符号加不同颜色
  59. idle-highlight-mode - 在设置的一段设置时间未操作电脑会自动高亮当前关键词,并且全文高亮相同关键词
  60. exec-path-from-shell - 可以使用 $PATH 环境变量
  61. find-file-in-repository - 根据 git 属性在项目里查找文件
  62. emmet-mode - 类似于 zencoding,但是能编辑 css, 使用很少的代码就能构造一个复杂的 div/css
  63. browse-kill-ring - 查看最近操作的删除文本,以及恢复后的效果

安装使用

curl -fsSkL https://raw.github.com/cask/cask/master/go | python
git clone https://github.com/dongweiming/emacs.d .emacs.d
cd .emacs.d
cask
sudo pip install jedi pep8 autopep8 flake8

快捷键分布

请参看项目的 README.md

原文: 一个使用python的web程序员的emacs.d

python的魔法二

作者 xiaoming
2014年5月8日 08:00

python 的魔法 (-) 之基础知识,我们再来说说 python 开发中的坑 "> 有了第一篇 python 的魔法 (-) 之基础知识,我们再来说说 python 开发中的坑

不要使用可变对象作为函数默认值

In [1]: def append_to_list(value, def_list=[]):
   ...:         def_list.append(value)
   ...:         return def_list
   ...: 

In [2]: my_list = append_to_list(1)

In [3]: my_list
Out[3]: [1]

In [4]: my_other_list = append_to_list(2)

In [5]: my_other_list
Out[5]: [1, 2] # 看到了吧,其实我们本来只想生成[2] 但是却把第一次运行的效果页带了进来

In [6]: import time

In [7]: def report_arg(my_default=time.time()):
   ...:         print(my_default)
   ...:     

In [8]: report_arg() # 第一次执行
1399562371.32

In [9]: time.sleep(2) # 隔了2秒

In [10]: report_arg()
1399562371.32 # 时间竟然没有变

这 2 个例子说明了什么?字典,集合,列表等等对象是不适合作为函数默认值的。因为这个默认值实在函数建立的时候就生成了,每次调用都是用了这个对象的 "缓存". 我在上段时间的分享 python 高级编程 也说到了这个问题,这个是实际开发遇到的问题,好好检查你学过的代码,也许只是问题没有暴露

可以这样改:

def append_to_list(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

生成器不保留迭代过后的结果

In [12]: gen = (i for i in range(5))

In [13]: 2 in gen
Out[13]: True

In [14]: 3 in gen
Out[14]: True

In [15]: 1 in gen
Out[15]: False # 1为什么不在gen里面了? 因为调用1->2,这个时候1已经不在迭代器里面了,被按需生成过了

In [20]: gen = (i for i in range(5))

In [21]: a_list = list(gen) # 可以转化成列表,当然a_tuple = tuple(gen) 也可以

In [22]: 2 in a_list
Out[22]: True

In [23]: 3 in a_list
Out[23]: True

In [24]: 1 in a_list # 就算循环过,值还在
Out[24]: True

lambda 在闭包中会保存局部变量

In [29]: my_list = [lambda: i for i in range(5)]

In [30]: for l in my_list:
   ....:         print(l())
   ....:     
4
4
4
4
4

这个问题还是上面说的 python 高级编程 中说过具体原因。其实就是当我赋值给 my_list 的时候,lambda 表达式就执行了 i 会循环,直到 i =4,i 会保留

但是可以用生成器

In [31]: my_gen = (lambda: n for n in range(5))

In [32]: for l in my_gen:
   ....:         print(l())
   ....:     
0
1
2
3
4

也可以坚持用 list:

In [33]: my_list = [lambda x=i: x for i in range(5)] # 看我给每个lambda表达式赋了默认值

In [34]: for l in my_list:
   ....:         print(l())
   ....:     
0
1
2
3
4

有点不好懂是吧,在看看 python 的另外一个魔法:

In [35]: def groupby(items, size):
   ....:     return zip(*[iter(items)]*size)
   ....: 

In [36]: groupby(range(9), 3)
Out[36]: [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

一个分组的函数,看起来很不好懂,对吧?我们来解析下这里

In [39]: [iter(items)]*3
Out[39]: 
[<listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>] # 看到了吧, 其实就是把items变成可迭代的, 重复三回(同一个对象哦), 但是别忘了,每次都.next(), 所以起到了分组的作用
 In [40]: [lambda x=i: x for i in range(5)]
Out[40]: 
[<function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>,
 <function __main__.<lambda>>] # 看懂了吗?

在循环中修改列表项

In [44]: a = [1, 2, 3, 4, 5]

In [45]: for i in a:
   ....:     if not i % 2:
   ....:         a.remove(i)
   ....:         

In [46]: a
Out[46]: [1, 3, 5] # 没有问题

In [50]: b = [2, 4, 5, 6]

In [51]: for i in b:
   ....:      if not i % 2:
   ....:          b.remove(i)
   ....:         

In [52]: b
Out[52]: [4, 5] # 本来我想要的结果应该是去除偶数的列表

思考一下,为什么 -- 是因为你对列表的 remove, 影响了它的 index

In [53]: b = [2, 4, 5, 6]

In [54]: for index, item in enumerate(b):
   ....:     print(index, item)
   ....:     if not item % 2:
   ....:         b.remove(item)
   ....:         
(0, 2) # 这里没有问题 2被删除了
(1, 5) # 因为2被删除目前的列表是[4, 5, 6], 所以索引list[1]直接去找5, 忽略了4
(2, 6)

IndexError - 列表取值超出了他的索引数

In [55]: my_list = [1, 2, 3, 4, 5]

In [56]: my_list[5] # 根本没有这个元素
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-56-037d00de8360> in <module>()
----> 1 my_list[5]

IndexError: list index out of range # 抛异常了

In [57]: my_list[5:] # 但是可以这样, 一定要注意, 用好了是trick,用错了就是坑啊
Out[57]: []

重用全局变量

In [58]: def my_func():
   ....:         print(var) # 我可以先调用一个未定义的变量
   ....:     

In [59]: var = 'global' # 后赋值

In [60]: my_func() # 反正只要调用函数时候变量被定义了就可以了
global

In [61]: def my_func():
   ....:     var = 'locally changed'
   ....:     

In [62]: var = 'global'

In [63]: my_func()

In [64]: print(var)
global # 局部变量没有影响到全局变量

In [65]: def my_func():
   ....:         print(var) # 虽然你全局设置这个变量, 但是局部变量有同名的, python以为你忘了定义本地变量了
   ....:         var = 'locally changed'
   ....:         

In [66]: var = 'global'

In [67]: my_func()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-67-d82eda95de40> in <module>()
----> 1 my_func()

<ipython-input-65-0ad11d690936> in my_func()
      1 def my_func():
----> 2         print(var)
      3         var = 'locally changed'
      4 

UnboundLocalError: local variable 'var' referenced before assignment

In [68]: def my_func():
   ....:         global var # 这个时候得加全局了
   ....:         print(var) # 这样就能正常使用
   ....:         var = 'locally changed' 
   ....:     

In [69]: var = 'global'

In [70]: 

In [70]: my_func()
global

In [71]: print(var)
locally changed # 但是使用了global就改变了全局变量

拷贝可变对象

In [72]: my_list1 = [[1, 2, 3]] * 2

In [73]: my_list1
Out[73]: [[1, 2, 3], [1, 2, 3]]

In [74]: my_list1[1][0] = 'a' # 我只修改子列表中的一项

In [75]: my_list1
Out[75]: [['a', 2, 3], ['a', 2, 3]] # 但是都影响到了

In [76]: my_list2 = [[1, 2, 3] for i in range(2)] # 用这种循环生成不同对象的方法就不影响了

In [77]: my_list2[1][0] = 'a'

In [78]: my_list2
Out[78]: [[1, 2, 3], ['a', 2, 3]]

原文: python的魔法二

python的魔法(一)

作者 xiaoming
2014年5月8日 08:00

前言

最近读了一篇 A collection of not-so-obvious Python stuff you should know! , 感觉受益颇多。翻译过来 (非直接翻译), 再加上一些我的理解和注释。让大家注意 python 鲜为人知的 "魔法". 我会分 2 篇

python 多继承 (C3)
In [1]: class A(object):
   ...:         def foo(self):
   ...:                 print("class A")
   ...:         

In [2]: class B(object):
   ...:         def foo(self):
   ...:                 print("class B")
   ...:         

In [3]: class C(A, B):
   ...:         pass
   ...: 

In [4]: C().foo()
class A # 例子很好懂, C继承了A和B,从左到右,发现A有foo方法,返回了

看起来都是很简单,有次序的从底向上,从前向后找,找到就返回。再看例子:

In [5]: class A(object):
   ...:        def foo(self):
   ...:               print("class A")
   ...:         

In [6]: class B(A):
   ...:        pass
   ...: 

In [7]: class C(A):
   ...:        def foo(self):
   ...:               print("class C")
   ...:         

In [8]: class D(B,C):
   ...:        pass
   ...: 

In [9]: D().foo()
class C # ? 按道理, 顺序是 D->B->A,为什么找到了C哪去了

这也就涉及了 MRO ( Method Resolution Order ):

In [10]: D.__mro__
Out[10]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

简单的理解其实就是新式类是广度优先了, D->B, 但是发现 C 也是继承 A,就先找 C,最后再去找 A

列表的 + 和 +=, append 和 extend

In [17]: print('ID:', id(a_list))
('ID:', 4481323592)

In [18]: a_list += [1]

In [19]: print('ID (+=):', id(a_list))
('ID (+=):', 4481323592) # 使用+= 还是在原来的列表上操作

In [20]: a_list = a_list + [2]

In [21]: print('ID (list = list + ...):', id(a_list))
('ID (list = list + ...):', 4481293056) # 简单的+其实已经改变了原有列表
In [28]: a_list = []

In [29]: id(a_list)
Out[29]: 4481326976

In [30]: a_list.append(1)

In [31]: id(a_list)
Out[31]: 4481326976 # append 是在原有列表添加

In [32]: a_list.extend([2])

In [33]: id(a_list)
Out[33]: 4481326976 # extend 也是在原有列表上添加

datetime 也有布尔值

这是一个

In [34]: import datetime

In [35]: print('"datetime.time(0,0,0)" (Midnight) ->', bool(datetime.time(0,0,0)))
('"datetime.time(0,0,0)" (Midnight) ->', False)

In [36]: print('"datetime.time(1,0,0)" (1 am) ->', bool(datetime.time(1,0,0)))
('"datetime.time(1,0,0)" (1 am) ->', True)

'==' 和 is 的区别

我的理解是 "is" 是判断 2 个对象的身份,== 是判断 2 个对象的值

In [37]: a = 1

In [38]: b = 1

In [39]: print('a is b', bool(a is b))
('a is b', True)

In [40]: c = 999

In [41]: d = 999

In [42]: print('c is d', bool(c is d))
('c is d', False) # 原因是python的内存管理,缓存了-5 - 256的对象

In [43]: print('256 is 257-1', 256 is 257-1)
('256 is 257-1', True)

In [44]: print('257 is 258-1', 257 is 258 - 1)
('257 is 258-1', False)

In [45]: print('-5 is -6+1', -5 is -6+1)
('-5 is -6+1', True)

In [46]: print('-7 is -6-1', -7 is -6-1)
('-7 is -6-1', False)
In [47]: a = 'hello world!'

In [48]: b = 'hello world!'

In [49]: print('a is b,', a is b)
('a is b,', False) # 很明显 他们没有被缓存,这是2个字段串的对象

In [50]: print('a == b,', a == b)
('a == b,', True) # 但他们的值相同
# But, 有个特例
In [51]: a = float('nan')

In [52]: print('a is a,', a is a)
('a is a,', True)

In [53]: print('a == a,', a == a)
('a == a,', False) # 亮瞎我眼睛了~

浅拷贝和深拷贝

我们在实际开发中都可以向对某列表的对象做修改,但是可能不希望改动原来的列表.浅拷贝只拷贝父对象,深拷贝还会拷贝对象的内部的子对象

In [65]: list1 = [1, 2]

In [66]: list2 = list1 # 就是个引用, 你操作list2,其实list1的结果也会变

In [67]: list3 = list1[:]

In [69]: import copy

In [70]: list4 = copy.copy(list1) # 他和list3一样 都是浅拷贝

In [71]: id(list1), id(list2), id(list3), id(list4)
Out[71]: (4480620232, 4480620232, 4479667880, 4494894720)

In [72]: list2[0] = 3

In [73]: print('list1:', list1)
('list1:', [3, 2])

In [74]: list3[0] = 4

In [75]: list4[1] = 4

In [76]: print('list1:', list1)
('list1:', [3, 2]) # 对list3和list4操作都没有对list1有影响

# 再看看深拷贝和浅拷贝的区别

In [88]: from copy import copy, deepcopy

In [89]: list1 = [[1], [2]]

In [90]: list2 = copy(list1) # 还是浅拷贝

In [91]: list3 = deepcopy(list1) # 深拷贝

In [92]: id(list1), id(list2), id(list3)
Out[92]: (4494896592, 4495349160, 4494896088)

In [93]: list2[0][0] = 3

In [94]: print('list1:', list1)
('list1:', [[3], [2]]) # 看到了吧 假如你操作其子对象 还是和引用一样 影响了源

In [95]: list3[0][0] = 5

In [96]: print('list1:', list1)
('list1:', [[3], [2]]) # 深拷贝就不会影响

bool 其实是 int 的子类

这篇 bool-is-int 很有趣:

In [97]: isinstance(True, int)
Out[97]: True

In [98]: True + True
Out[98]: 2

In [99]: 3 * True + True
Out[99]: 4

In [100]: 3 * True - False
Out[100]: 3

In [104]: True << 10
Out[104]: 1024

元组是不是真的不可变?

In [111]: tup = ([],)

In [112]: tup[0] += [1]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-112-d4f292cf35de> in <module>()
----> 1 tup[0] += [1]

TypeError: 'tuple' object does not support item assignment

In [113]: tup
Out[113]: ([1],) # 我靠 又是亮瞎我眼睛,明明抛了异常 还能修改?

In [114]: tup = ([],)

In [115]: tup[0].extend([1])

In [116]: tup[0]
Out[116]: [1] # 好吧,我有点看明白了, 虽然我不能直接操作元组,但是不能阻止我操作元组中可变的子对象(list)

这里有个不错的解释 Python's += Is Weird, Part II :

In [117]: my_tup = (1,)

In [118]: my_tup += (4,)

In [119]: my_tup = my_tup + (5,)

In [120]: my_tup
Out[120]: (1, 4, 5) # ? 嗯 不是不能操作元组嘛?

In [121]: my_tup = (1,)

In [122]: print(id(my_tup))
4481317904

In [123]: my_tup += (4,)

In [124]: print(id(my_tup))
4480606864 # 操作的不是原来的元组 所以可以

In [125]: my_tup = my_tup + (5,)

In [126]: print(id(my_tup))
4474234912

python 没有私有方法 / 变量?但是可以有 "伪" 的

In [127]: class my_class(object^E):
   .....:     def public_method(self):
   .....:         print('Hello public world!')
   .....:     def __private_method(self): # 私有以双下划线开头
   .....:         print('Hello private world!')
   .....:     def call_private_method_in_class(self):
   .....:         self.__private_method()

In [132]: my_instance = my_class()

In [133]: my_instance.public_method()
Hello public world! # 普通方法

In [134]: my_instance._my_class__private_method()
Hello private world! # 私有的可以加"_ + 类名字 + 私有方法名字”

In [135]: my_instance.call_private_method_in_class()
Hello private world! # 还可以通过类提供的公有接口内部访问

In [136]: my_instance._my_class__private_variable
Out[136]: 1

异常处理加 else

In [150]: try:
   .....:     print('third element:', a_list[2])
   .....: except IndexError:
   .....:     print('raised IndexError')
   .....: else:
   .....:     print('no error in try-block') # 只有在try里面没有异常的时候才会执行else里面的表达式
   .....:     
raised IndexError # 抛异常了 没完全完成
In [153]: i = 0

In [154]: while i < 2:
   .....:     print(i)
   .....:     i += 1
   .....: else:
   .....:     print('in else')
   .....:     
0
1
in else # while也支持哦~
In [155]: i = 0

In [156]: while i < 2:
   .....:         print(i)
   .....:         i += 1
   .....:         break
   .....: else:
   .....:         print('completed while-loop')
   .....:     
0 # 被break了 没有完全执行完 就不执行else里面的了
In [158]: for i in range(2):
   .....:         print(i)
   .....: else:
   .....:         print('completed for-loop')
   .....:     
0
1
completed for-loop

In [159]: for i in range(2):
   .....:         print(i)
   .....:         break
   .....: else:
   .....:         print('completed for-loop')
   .....:     
0 # 也是因为break了

原文: python的魔法(一)

❌
❌