Fork me on GitHub
Google

ezcf,一个不算失败的作品

4.18 更新
目前 star 数已经达到 55 了,可喜可贺。
主要是因为去 reddit 发了一个帖子。发之前是25个star。之前没有去 reddit 宣传主要是感觉 reddit 门槛太高,geek 比较多,怕被喷。发觉 newsletter 这条路行不通之后,也没别的办法了,reddit 算是不得已的选择。从回复来看,reddit 比我想象的还是要友善不少,是个不错的地方。

这几个月断断续续地完成了一个名叫 ezcf 的库。可以直接 import .py 之外的文件格式,在我看来,还算有趣。所以原本的期望,是在 GitHub 超过 30 个 star,但现在看来,还是预期过高了。国内能宣传的地方差不多宣传了一遍,在 V2EX 发了帖之后,多少吸引了一些人,但是其他社区基本无效。然而真正让我没想到的是去 HackerNews 发帖居然连一个 star 都没增加。说实话,一个开源项目的好坏不取决于代码怎么样,知道的人多,用得人多就是好,这代表有人认同这个东西是有用的,而代码质量啊功能完善度啊都是可以逐渐改善的。没有人用的开源项目没有存在的价值。好在真正去看了项目的人,有几个对我说,“项目非常有趣”,这多少令人欣慰。

上周在推上@了 importpython,他说“pretty cool,下次放进 newsletter”。然而紧接着的 newsletter 里并没有。如果下次还没有,大概就真的没有了。

我感觉 ezcf 没有达到预期认可度的最大问题是,大多人第一眼都不知道它到底在干嘛。如果是列出几个函数的用法,写上输入输出,大家都很明白,问题是 ezcf 并不是这样起作用的,或者说,没法让人第一眼就知道 import ezcf 这句话起了什么作用,以及和之后的 import 语句有什么关系。说实话,一两句话根本说不清楚这个问题。其次是有直接 import 配置文件需求的人并不多。总而言之,没有办法快速展现出项目的有趣之处,这就是问题。

总之先放上地址
https://github.com/laike9m/ezcf
如果不写这篇博客,感觉无法释怀啊。顺便把 README 也放上来好了。

ezcf

Build Status Supported Python versions PyPI version Coverage Status

ezcf stands for easy configuration, it allows you to import JSON/YAML/INI/XML like .py files. It is useful whenever you need to read from these formats, especially for reading configuration files.

OK, stop talking, show us some code!

On the left is what you'll normally do, on the right is the ezcf way. Much more elegant isn't it?

Install

pip install ezcf

If you run into error: yaml.h: No such file or directory, don't worry, you can still use ezcf without any problem.

Supported File Types

ezcf supports JSON, YAML, INI and XML with extension json, yaml, yml, ini, xml.

Sample Usage

ezcf supports all kinds of valid import statements, here's an example:

├── subdir
│   ├── __init__.py
│   └── sample_yaml.yaml
├── test_normal.py
└── sample_json.json

Various ways to use configurations in sample_yaml.yaml and sample_json.json:

import ezcf

from subdir.sample_yaml import *
# or
from subdir.sample_yaml import something
# or
import subdir.sample_yaml as sy
print(sy.something)

from sample_json import *
# or
from sample_json import something
# or
import sample_json as sj
print(sj.something)

You can assume they're just regular python files.(Currently ezcf only supports files with utf-8 encoding)

What about relative import? Yes, ezcf supports relative import, as long as you use it correctly.

Something to note before using ezcf:

  1. ezcf is still in developement. If you find any bug, please report it in issues;
  2. Be careful importing YAML which contains multiple documents: if there exists keys with the same name, only one of them will be loaded. So it's better not to use multiple documents;
  3. All values in .ini files are kept as it is and loaded as a string;
  4. Since XML only allows single root, the whole xml will be loaded as one dict with root's name as variable name;
  5. Namespace package is not supported yet, pull requests are welcome.
Google

还在疑惑并发和并行?

OK,如果你还在为并发(concurrency)和并行(parallesim)这两个词的区别而感到困扰,那么这篇文章就是写给你看的。搞这种词语辨析到底有什么意义?其实没什么意义,但是有太多人在混用错用这两个词(比如遇到的某门课的老师)。不论中文圈还是英文圈,即使已经有数不清的文章在讨论并行vs并发,却极少有能讲清楚的。让一个讲不清楚的人来解释,比不解释更可怕。比如我随便找了个网上的解释:

前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生.

并发性(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要同一时刻发生。

并行(parallelism)是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行。

来个比喻:并发和并行的区别就是一个人同时吃三个馒头和三个人同时吃三个馒头。

看了之后,你懂了么?不懂,更晕了。写出这类解释的人,自己也是一知半解,却又把自己脑子里模糊的影像拿出来写成文章,让读者阅毕反而更加疑惑。当然也有可能他确实懂了,但是写出这种文字也不能算负责。至于本文,请相信,一定是准确的,我也尽量做到讲解清晰。

OK,下面进入正题,concurrency vs parallesim

让我们大声朗读下面这句话:

“并发”指的是程序的结构,“并行”指的是程序运行时的状态

即使不看详细解释,也请记住这句话。下面来具体说说:

并行(parallesim)

这个概念很好理解。所谓并行,就是同时执行的意思,无需过度解读。判断程序是否处于并行的状态,就看同一时刻是否有超过一个“工作单位”在运行就好了。所以,单线程永远无法达到并行状态

要达到并行状态,最简单的就是利用多线程和多进程。但是Python的多线程由于存在著名的GIL,无法让两个线程真正“同时运行”,所以实际上是无法到达并行状态的。

并发(concurrency)

要理解“并发”这个概念,必须得清楚,并发指的是程序的“结构”。当我们说这个程序是并发的,实际上,这句话应当表述成“这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计?

正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行(two tasks can start, run, and complete in overlapping time periods)

这句话的重点有两个。我们先看“(操作)在重叠的时间段内进行”这个概念。它是否就是我们前面说到的并行呢?是,也不是。并行,当然是在重叠的时间段内执行,但是另外一种执行模式,也属于在重叠时间段内进行。这就是协程

使用协程时,程序的执行看起来往往是这个样子:

task1、task2是两段不同的代码,比如两个函数,其中黑色块代表某段代码正在执行。注意,这里从始至终,在任何一个时间点上都只有一段代码在执行,但是,由于task1和task2在重叠的时间段内执行,所以这是一个支持并发的设计。与并行不同,单核单线程能支持并发。

经常看到这样一个说法,叫做并发执行。现在我们可以正确理解它。有两种可能:

  1. 原本想说的是“并行执行”,但是用错了词
  2. 指多个操作可以在重叠的时间段内进行,即,真的并行,或是类似上图那样的执行模式。

我的建议是尽可能不使用这个词,容易造成误会,尤其是对那些并发并行不分的人。但是读到这里的各位显然能正确区分,所以下面为了简便,将使用并发执行这个词。

第二个重点是“可以在重叠的时间段内进行”中的“可以”两个字。“可以”的意思是,正确的并发设计使并发执行成为可能,但是程序在实际运行时却不一定会出现多个任务执行时间段overlap的情形。比如:我们的程序会为每个任务开一个线程或者协程,只有一个任务时,显然不会出现多个任务执行时间段重叠的情况,有多个任务时,就会出现了。这里我们看到,并发并不描述程序执行的状态,它描述的是一种设计,是程序的结构,比如上面例子里“为每个任务开一个线程”的设计。并发设计和程序实际执行情况没有直接关联,但是正确的并发设计让并发执行成为可能。反之,如果程序被设计为执行完一个任务再接着执行下一个,那就不是并发设计了,因为做不到并发执行。

那么,如何实现支持并发的设计?两个字:拆分

之所以并发设计往往需要把流程拆开,是因为如果不拆分也就不可能在同一时间段进行多个任务了。这种拆分可以是平行的拆分,比如抽象成同类的任务,也可以是不平行的,比如分为多个步骤。

并发和并行的关系

Different concurrent designs enable different ways to parallelize.

这句话来自著名的talk: Concurrency is not parallelism。它足够concise,以至于不需要过多解释。但是仅仅引用别人的话总是不太好,所以我再用之前文字的总结来说明:并发设计让并发执行成为可能,而并行是并发执行的一种模式

最后,关于Concurrency is not parallelism这个talk再多说点。自从这个talk出来,直接引爆了一堆讨论并发vs并行的文章,并且无一例外提到这个talk,甚至有的文章直接用它的slide里的图片来说明。比如这张:

以为我要解释这张图吗?NO。放这张图的唯一原因就是萌萌的gopher。

再来张特写:

之前看到知乎上有个关于go为什么流行的问题,有个答案是“logo萌”当时我就笑喷了。

好像跑题了,继续说这个talk。和很多人一样,我也是看了这个talk才开始思考concurrency vs parallesim的问题。为了研究那一堆推小车的gopher到底是怎么回事,我花费了相当多的时间。实际上后来我更多地是通过网上的只言片语(比如SO的回答)和自己的思考弄清了这个问题,talk并没有很大帮助。彻底明白之后再回过头来看这个talk,确实相当不错,Andrew Gerrand对这个问题的理解绝对够深刻,但是太不新手向了。最大问题在于,那一堆gopher的例子不够好,太复杂。Andrew Gerrand花了大把时间来讲述不同的并发设计,但是作为第一次接触这个话题的人,在没有搞清楚并发并行区别的情况下就去研究推小车的gopher,太难了。“Different concurrent designs enable different ways to parallelize”这句总结很精辟,但也只有那些已经透彻理解的人才能领会,比如我和看到这里的读者,对新手来说就和经文一样难懂。总结下来一句话,不要一开始就去看这个视频,也不要花时间研究推小车的gopher。Gopher is moe, but confusing.

Google

Python相对导入机制详解

本文是对http://stackoverflow.com/questions/14132789/python-relative-imports-for-the-billionth-time#answer-14132912这个SO答案的翻译。本人的翻译一向只追求含义准确而不追求字字对应,有些不好翻的术语或者固定说法就直接保留。

这个答案能解释大多关于relative import,即相对导入的疑惑,讲解十分详尽清晰,算是SO上被低估的一个答案。

问题不翻译了,直接摘录下来:
The forever-recurring question is this: With Windows 7, 32-bit Python 2.7.3, how do I solve this "Attempted relative import in non-package" message? I built an exact replica of the package on pep-0328:

package/

    __init__.py

    subpackage1/

        __init__.py

        moduleX.py

        moduleY.py

    subpackage2/

        __init__.py

        moduleZ.py

    moduleA.py

I did make functions named spam and eggs in their appropriate modules. Naturally, it didn't work. The answer is apparently in the 4th URL I listed, but it's all alumni to me. There was this response on one of the URLs I visited:

Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

The above response looks promising, but it's all hieroglyphs to me. So my question, how do I make Python not return to me "Attempted relative import in non-package"? has an answer that involves -m, supposedly.

Can somebody please tell me why Python gives that error message, what it Means by non-package!, why and how do you define a 'package', and the precise answer put in terms easy enough for a kindergartener to understand. Thanks in advance!

Edit: The imports were done from the console.


BrenBarn的精彩答案(这个哥们可以算是import专家了,答了好多这方面的题)

简单地说,直接运行.Py文件和import这个文件有很大区别。Python解释器判断一个py文件属于哪个package时并不完全由该文件所在的文件夹决定。它还取决于这个文件是如何load进来的(直接运行or import)。

有两种方式加载一个py文件:作为top-level脚本或者作为module。前者指的是直接运行脚本,比如python myfile.py。如果执行python -m myfile,或者在其它py文件中用import语句来加载,那么它就会被当作一个module。有且只能有一个top-level脚本,就是最开始执行的那个(比如python myfile.py中的myfile.py,译者注)。

当一个py文件被加载之后,它会被赋予一个名字,保存在__name__属性中。如果是top-level脚本,那么名字就是__main__。如果是作为module,名字就是把它所在的packages/subpackages和文件名用.连接起来。

例如,moduleX被import进来,它的名字就是package.subpackage1.moduleX。如果import了moduleA,它的名字是package.moduleA。如果直接运行moduleXmoduleA,那么名字就都是__main__了。

另一个令人担忧的问题是,一个module的名字取决于它是直接从它所在的文件夹import还是通过某个package import的。不过只有当你在某个路径中运行Python并试图从当前文件夹import一个py文件时,才需要关注它们的不同。例如,在路径pacakge/subpackage1中运行python解释器,然后脚本中有import moduleX这个语句,此时moduleX的名字正是moduleX,而不是package.subpackage1.moduleX。这是因为Python解释器在启动时把当前路径加入了它的搜索路径(search path);如果发现要import的module就在当前路径,那么Python解释器就不知道当前路径是属于哪个package的,所以pacakge的信息就不会成为module的名字的一部分。

一个特例是直接运行python REPL,这个REPL的session的名字是__main__

关于你遇到的错误信息,关键点来了:如果一个module的名字中没有点(即package.subpackage1中的那个点,译者注),那么它就被认为不属于任何一个package。文件在磁盘上的位置在哪里都不影响,唯一起决定作用的就是module的名字,而这又取决于它是如何被加载的。

先在我们看看你在问题中引用的这段话

Relative imports use a module's name attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to 'main') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

relative import使用module的名字来决定它是否属于一个package,属于哪个package。当你使用这种relative import from .. import foo,其中的点的数量代表了package结构中的某个层次。例如,如果当前module的名字是package.subpackage1.moduleX,那么..moduleA代表package.moduleA。为了让形如from .. import的这种导入能够正常工作,module的名字里的点数量应当至少和import语句中一样多。

前面说了,如果module的名字是__main__,那么Python就不认为它属于某个package。由于名字里不包含点,所以在这个py文件中from .. import语句无法正常工作。试图执行这条语句就会报"relative-import in non-package"错误。

你犯的错误可能是从命令行运行moduleX或类似的操作。当你执行这个操作,moduleX的名字被设置成__main__,所以relative imports失败了,因为不包含package信息。正如前面说的,如果在同一个路径里import一个文件,这时module的名字就是文件名,不包含package信息,所以相对导入也会失败。

记住,因为REPL session的名字总是__main__,所以试图在REPL里执行relative import是不行的。relative import应当只在module文件中被使用。

(无法相对导入的问题)有两个解决方法。如果你真的想直接运行moduleX,同时又希望它被当作所在package的一部分,可以这么做:python -m package.subpackage.moduleX-m参数告诉Python解释器,把这个文件当作一个module载入,而不是top-level脚本。

如果你并不想直接运行moduleX,而是想在另一个文件比如myfile.py中使用moduleX中定义的函数,那么解决方法是把myfile.py文件挪到另一个地方,只要不在moduleX所属的package的文件夹里就行。然后在myfile.py中执行from package.moduleA import spam,就能正常工作了。

注意,不论哪种解决方案,都需要package的路径(上文中的package)在python的搜索路径也就是sys.path里。如果不在,那么就无法使用这个package中的任何文件。

(更严谨的说明:从Python2.6开始,在做package-resolution时,module的“名字”并不完全等于__name__属性,还和__package__属性有关。这也是为什么上文中我一直尽量避免用__name__来指代module的名字。从python2.6开始,一个module的“名字”实际上是__package__ + '.' + __name__, 或者直接就是__name__,如果__package__None的话)


top