Python论坛  - 讨论区

标题:[python-chinese] Python 之优雅与瑕疵

2007年09月02日 星期日 22:02

路人张三 jsonic1106在gmail.com
星期日 九月 2 22:02:26 HKT 2007

    自从 Python 1.5.2(一个长期以来一直稳定且可靠的版本)迈入 "黄金时代" 以来,Python
增加了许多语法特性以及内置函数和类型。这些改进单独地看都是合理的调整,但是作为一个整体,它们使 Python
变得更加复杂,不再是有经验的程序员 "花上一个下午" 就能够掌握的语言了;另外,一些修改在带来好处的同时也有缺陷。

在本文中,我要讨论在最近几个 Python
版本中增加的不那么引人注目的特性,我将分析哪些改进具有真正的价值,哪些特性只是不必要地增加了复杂性。我希望向所有并非一直使用 Python
的程序员指出真正具有价值的东西。这包括使用其他语言的程序员以及只将编程当做副业的科学家。当遇到一些难题时,我会提供解决方案。

不可比较的麻烦

在 Python 2.0 和 Python 2.1
之间,发生了一些奇怪的变化。以前可以比较的对象在进行比较时却引发了异常。具体地说,复数无法与其他数字进行比较了,包括其他复数以及整数、浮点数和长整数。实际上,在此之前,比较
Unicode 字符串和文本字符串时就可能会遇到这个问题,但那只发生在一些极端情况下。

我认为,这些修改很怪异,没有必要。在 1.5.2 的黄金时代,无论比较什么对象,不等操作符总会返回一个结果。当然,结果不一定是有意义的 ――
比如字符串和浮点数的比较就没有意义。但是,至少我们总会得到一个一致的结果。

出现这些修改之后,一些 Python
支持者认为不允许对不同类型的对象进行不等比较是件好事,只有定义了定制的比较函数之后,才能进行这种比较。我觉得,在处理定制类和多重继承时,编写定制的比较函数实际上很需要技巧。另外,不能在浮点数、整数和长整数(比如
decimal)之间进行比较是非常不方便的。但是,或许可以定义一个合理的规则。

但是,无论定义什么样的规则,它都与 Python
过去的做法有非常大的差异。现在的情况是比较行为无规律可循,即使知道比较的对象的类型,也无法确定它们是否是可比较的(而且不等性既非可传递也非封闭式):

清单 1. 比较是否成功取决于类型和值

>>> map(type, (u1, s1, s2))
[, , ]

>>> u1 < s1
True

>>> s1 < s2
True

>>> u1 < s2
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf0 in position 0:
    ordinal not in range(128)

>>> map(type, (n, j, u1))
[, , ]

>>> n < u1
True

>>> j < u1
True

>>> n < j
TypeError: no ordering relation is defined for complex numbers


更糟糕的是,复数现在不能与大多数 数字值进行比较,但是可以通过大多数非数字值判断出绝对的不等性。例如,考虑到理论纯洁性,我知道 1+1j 与
2-3j 的比较是没有意义的,但是为什么有下面这样的结果:

清单 2. 令人吃惊的比较结果

>>> 2-3j < 'spam'
True

>>> 4+0j < decimal.Decimal('3.14')
True

>>> 4+0j < 5+0j
TypeError: no ordering relation is defined for complex numbers



从理论上来讲,这全无 "纯" 可言。

一个真正的瑕疵:对异构集合进行排序

自变量有时候会造成编程错误,试图对不可比较的类型进行比较。但是 Python 可以顺利地执行许多这种类型的比较;并且依照 "duck
typing" 哲学来完成这样的任务(duck typing 是指
"如果看起来像鸭子,听起来像鸭子,就可以把它当作鸭子",也就是说,不管对象是 什么,只在乎它做 什么。)Python
集合常常将不同类型的对象组织在一起,希望能够做
与其中的各对象相似的事情。一种常见的用例是对一组不同类型的值进行编码,以便通过某种协议进行传输。

对于这其中的大多数值,不等比较是不必要的。但是,在一种常见的情况下,不等性是非常有用的;那就是对集合进行排序
时,通常是对列表或与列表类似的定制集合进行排序。有时候,需要以一种有意义的升序来处理集合(例如,按照数据值从小到大的次序)。在其他时候,需要为多个集合创建一种固定的次序,尤其是为了对两个集合执行某种类似于
"list diff" 的处理时。也就是说,如果一个对象在这两个集合中都存在,那么就执行一种操作;如果它只在一个集合中存在,就执行另一种操作。不断地检查
if x in otherlist 会导致效率成 big-O 式几何级数递减;在两个固定排序的列表之间进行平行匹配的效率要高得多。例如:

清单 3. 根据两个列表的成员关系执行不同的操作

list1.sort()
list2.sort()
list2_xtra = []
list2_ndx = 0
for it1 in list1:
    it2 = list2[list2_ndx]
    while it1 < it2:
        list2_ndx += 1
        it2 = list2[list2_ndx]
        if it1 == it2:
            item_in_both(it1)
        elif it1 > it2:
            item_in_list1(it1)
        else:
            list2_xtra.appen(it2)
 for it2 in list2_xtra:
     item_in_list2(it2)


有时候,有意义比较的 "局部序列" 是有用的,甚至在出现不同类型对象的情况下也是如此(例如,"依次"
处理所有浮点值,即使它们与其他地方处理的字符串没有可比性)。

排序失败

当然,上面执行 "list diff" 的代码几乎可以任意扩展。例如,list1 和 list2
可以是下面这样的小列表的集合。请试着猜一下哪些部分是可以排序的:

清单 4. 可排序和不可排序列表的大杂烩

['x','y','z', 1],
['x','y','z', 1j],
['x','y','z', 1j, 1],       # Adding an element makes it unsortable
[0j, 1j, 2j],               # An obvious "natural" order
[0j, 1, 2],
[0, 1, 2],                  # Notice that 0==0j --> True
[chr(120), chr(240)],
[chr(120), chr(240), 'x'],
[chr(120), chr(240), u'x'], # Notice u'x'=='x' --> True
[u'a', 'b', chr(240)],
[chr(240), u'a', 'b']       # Same items, different initial order


我编写了一个小程序来尝试排序各列表:

清单 5. 对各列表进行排序的结果

% python compare.py
(0)  ['x', 'y', 'z', 1] --> [1, 'x', 'y', 'z']
(1)  ['x', 'y', 'z', 1j] --> [1j, 'x', 'y', 'z']
(2)  ['x', 'y', 'z', 1j, 1] --> exceptions.TypeError
(3)  [0j, 1j, 2j] --> exceptions.TypeError
(4)  [0j, 1, 2] --> exceptions.TypeError
(5)  [0, 1, 2] --> [0, 1, 2]
(6)  ['x', 'xf0'] --> ['x', 'xf0']
(7)  ['x', 'xf0', 'x'] --> ['x', 'x', 'xf0']
(8)  ['x', 'xf0', u'x'] --> exceptions.UnicodeDecodeError
(9)  [u'a', 'b', 'xf0'] --> [u'a', 'b', 'xf0']
(10) ['xf0', u'a', 'b'] --> exceptions.UnicodeDecodeError


通过前面的解释,或多或少能够猜出一部分结果。但是,看一下 (9) 和
(10),这两个列表以不同次序包含完全相同的对象:由此可见,排序是否失败不但取决于列表中 对象的类型和值,还取决于 list.sort()
方法的特定实现!

修订比较

自 1.5.2 以来,Python
发展出了一种非常有用的数据类型:集(set),它最初是一个标准模块,后来成了一种内置类型(模块还包含一些额外的特性)。对于上面描述的许多问题,只需使用集来取代列表即可轻松地判断对象是在一个集合中、在另一个集合中还是同时存在于两个集合中,而不需要编写自己的
"list diff" 代码。例如:

清单 6. 集与集操作

>>> set1 = set([1j, u'2', 3, 4.0])

>>> set2 = set([4, 3, 2, 1])

>>> set1 | set2
set([3, 1, 2, 1j, 4.0, u'2'])

>>> set1 & set2
set([3, 4])


在编写上面这个示例时,我发现了一些相当奇怪的现象。集操作似乎采用相等性(equality)而不是同一性(identity)。或许这有某种意义,但奇怪的是,这两个集的合集包含浮点数
4.0,而其交集包含整数 4。更奇怪的是,尽管求合集和交集的操作在理论上是对称的,但是实际结果却与次序相关:

清单 7. 集中得到的奇怪类型

>>> set2 & set1
set([3, 4.0])

>>> set([3, 4.0, 4, 4+0j])
set([3, 4.0])


当然,初看起来,集是一种很棒的数据类型。但是,定制的比较总是应该考虑的解决方案。在 Python 2.4 之前,完全有可能实现定制的
cmp() 函数并将它传递给 list.sort()。这样就可以实现原本不可比较的对象之间的比较;cmp
自变量的问题是,每次比较时都要调用这个函数:Python 的调用开销非常高,而且更糟糕的是,要经过多次计算才能得出所计算的值。

对于 cmp 效率低下的问题,有效的解决方案是使用 Schwartzian
排序:修饰(decorate)各对象,进行排序,然后去掉修饰。遗憾的是,这需要使用一些定制的代码,不是简单地调用 list.sort()
就能够实现的。Python 2.4 提供了一种出色的组合解决方案,使用 key
自变量。这个自变量接受一个函数,这个函数返回一个经装饰的对象并 "在幕后" 进行 Schwartzian
排序。请记住,即便是复数与复数之间也不能进行比较,而 Unicode 对象只在与某些 字符串比较时会出问题。我们可以使用以下代码:

清单 8. 一个稳定、通用的排序键

def stablesort(o):
    # Use as: mylist.sort(key=stablesort)
    if type(o) is complex:
        return (type(o), o.real, o.imag)
    else:
        return (type(o), o)


请记住,元素的次序可能与您期望的不完全一致:即使在未修饰排序成功的地方,次序与未修饰排序也不一样。尤其是,具有不同数字类型的元素不再混在一起,而是被分隔为排序结果的不同部分。但是,它至少是固定的,而且对于几乎任何列表都是有效的(仍然可以用定制的对象扩展这种排序)。

以生成器作为准序列

经过数个版本的发展,Python 的 "惰性" 大大增强。有几个版本中已出现了在函数体中用 yield
语句定义的生成器。但是,在这个过程中,还出现了 itertools 模块,它可以组合和创建各种类型的迭代器。还出现了 iter()
内置函数,它可以将许多类似于序列的对象转换为迭代器。在 Python 2.4 中,出现了生成器表达式(generator
expression);在 2.5 中,出现了改进的生成器,这使编写协同例程更为轻松。另外,越来越多的 Python
对象成为迭代器或类迭代器,例如,过去读取文件需要使用 .xreadlines() 方法或 xreadlines 模块,而现在 open()
的默认行为就能是读取文件。

同样,过去要实现 dict 的惰性循环遍历需要使用 .iterkeys() 方法;现在,它是默认的 for key in dct
行为。xrange() 这样的函数与生成器类似的方面有些 "特殊",它们既不是真正的 迭代器(没有 .next()
方法),也不是实际的列表(比如 range() 返回的列表)。但是,enumerate() 返回一个真正的生成器,通常会实现以往希望
xrange() 实现的功能。itertools.count() 是另一个惰性调用,它的作用与 xrange() 几乎
完全相同,但它是一个功能完备的迭代器。

Python 的发展大方向是以惰性方式构造类似于序列的对象;总体来说,这个方向是正确的。惰性的伪序列既可以节省内存空间,还可以提高操作的速度(尤其是在处理非常大的与序列类似的
"东西" 时)。

问题在于,Python 在判断 "硬" 序列和迭代器之间的差异和相似性方面仍然不完善。这个问题最棘手的部分是,它实际上违背了 Python
的 "duck typing"
思想:只要给定的对象具有正确的行为,就能够将它用于特定的用途,而不存在任何继承或类型限制。迭代器或类迭代器有时表现得像序列,但有时候不是;反过来说,序列常常表现得像迭代器,但并非总是如此。表现不像迭代器那些序列已涉及
Python 的神秘领地,其作用尚不明朗。

分歧

所有类序列或类迭代器的主要相似之处是,它们都允许进行循环遍历,无论是使用 for 循环、列表理解(list
comprehension)还是生成器理解(generator
comprehension)。除此之外,就出现了分歧。其中最重要的差异是,序列既可编制索引,也可直接切片(slice),而迭代器不能。实际上,为序列编制索引可能是最常用的序列操作
―― 究竟为什么在迭代器上无法使用索引呢?例如:

清单 9. 与序列和迭代器相似的东西

>>> r = range(10)

>>> i = iter(r)

>>> x = xrange(10)

>>> g = itertools.takewhile(lambda n: n<10, itertools.count())

#...etc...


对于所有这一切,都可以使用 for n in thing。实际上,如果用 list(thing)
显示它们,会得到完全相同的结果。但是,如果希望获得其中的一个特定条目(或一些条目的切片),就需要考虑 thing 的具体类型。例如:

清单 10. 索引操作成功和失败的示例

>>> r[4]
4

>>> i[4]
TypeError: unindexable object


对于每种序列/迭代器类型,只要费一番功夫,总能够获得其中的特定条目。一种方法是进行循环,直至到达所需的对象。另一种古怪的解决方案如下:

清单 11. 获得索引的怪方法

>>> thing, temp = itertools.tee(thing)

>>> zip(temp, '.'*5)[-1][0]
4


对 itertools.tee() 的预调用保留了原始迭代器。对于切片,可以按照特殊方式使用 itertools.islice() 函数。

清单 12. 获得一个切片的特殊方法

>>> r[4:9:2]
[4, 6, 8]

>>> list(itertools.islice(r,4,9,2))  # works for iterators
[4, 6, 8]


类包装器

为了方便起见,可以将这些技术组合成一个类包装器:

清单 13. 使迭代器可编制索引

所以,通过某些措施,可以让一个对象同时表现得像序列和迭代器。但是,费这么大力气实际上
应该是不必要的;无论涉及的是序列还是迭代器,编制索引和切片都应该是 "可行的"。

注意,Indexable 类包装器仍然不够灵活。主要问题是,每次都要创建迭代器的一个新副本。更好的方法应该是在对序列切片时缓存序列的头部,然后在以后访问已经检查的元素时使用所缓存的头部。当然,在使用迭代器时,要在占用的内存和速度之间有所取舍。但是,如果
Python 本身能够 "在幕后" 完成这些,就再好不过了 ―― "高级用户" 可以对行为进行调优,而一般程序员应该不需要考虑这些。

来源: http://aolinux.wannish.com

[导入自Mailman归档:http://www.zeuux.org/pipermail/zeuux-python]

2007年09月02日 星期日 22:19

Zoom.Quiet zoom.quiet在gmail.com
星期日 九月 2 22:19:11 HKT 2007

David 老哥又恢复 "可爱"系列了哪!!
好文,这也是为什么有各种Python 分支出现的原因,Gudio 老爹可能耳根软,以前坚持的原则在成名后,乐于追加各种时尚元素进来,结果有些乱了,
Py3k 是个真正的分水岭了,是否成功向未来进化就看这次了...

On 9/2/07, 路人张三 <jsonic1106在gmail.com> wrote:
>     自从 Python 1.5.2(一个长期以来一直稳定且可靠的版本)迈入 "黄金时代" 以来,Python
> 增加了许多语法特性以及内置函数和类型。这些改进单独地看都是合理的调整,但是作为一个整体,它们使 Python
> 变得更加复杂,不再是有经验的程序员 "花上一个下午" 就能够掌握的语言了;另外,一些修改在带来好处的同时也有缺陷。
>
> 在本文中,我要讨论在最近几个 Python
> 版本中增加的不那么引人注目的特性,我将分析哪些改进具有真正的价值,哪些特性只是不必要地增加了复杂性。我希望向所有并非一直使用 Python
> 的程序员指出真正具有价值的东西。这包括使用其他语言的程序员以及只将编程当做副业的科学家。当遇到一些难题时,我会提供解决方案。
>
> 不可比较的麻烦
>
> 在 Python 2.0 和 Python 2.1
> 之间,发生了一些奇怪的变化。以前可以比较的对象在进行比较时却引发了异常。具体地说,复数无法与其他数字进行比较了,包括其他复数以及整数、浮点数和长整数。实际上,在此之前,比较
> Unicode 字符串和文本字符串时就可能会遇到这个问题,但那只发生在一些极端情况下。
>
> 我认为,这些修改很怪异,没有必要。在 1.5.2 的黄金时代,无论比较什么对象,不等操作符总会返回一个结果。当然,结果不一定是有意义的 ――
> 比如字符串和浮点数的比较就没有意义。但是,至少我们总会得到一个一致的结果。
>
> 出现这些修改之后,一些 Python
> 支持者认为不允许对不同类型的对象进行不等比较是件好事,只有定义了定制的比较函数之后,才能进行这种比较。我觉得,在处理定制类和多重继承时,编写定制的比较函数实际上很需要技巧。另外,不能在浮点数、整数和长整数(比如
> decimal)之间进行比较是非常不方便的。但是,或许可以定义一个合理的规则。
>
> 但是,无论定义什么样的规则,它都与 Python
> 过去的做法有非常大的差异。现在的情况是比较行为无规律可循,即使知道比较的对象的类型,也无法确定它们是否是可比较的(而且不等性既非可传递也非封闭式):
>
> 清单 1. 比较是否成功取决于类型和值
>
> >>> map(type, (u1, s1, s2))
> [, , ]
>
> >>> u1 < s1
> True
>
> >>> s1 < s2
> True
>
> >>> u1 < s2
> UnicodeDecodeError: 'ascii' codec can't decode byte 0xf0 in position 0:
>     ordinal not in range(128)
>
> >>> map(type, (n, j, u1))
> [, , ]
>
> >>> n < u1
> True
>
> >>> j < u1
> True
>
> >>> n < j
> TypeError: no ordering relation is defined for complex numbers
>
>
> 更糟糕的是,复数现在不能与大多数 数字值进行比较,但是可以通过大多数非数字值判断出绝对的不等性。例如,考虑到理论纯洁性,我知道 1+1j 与
> 2-3j 的比较是没有意义的,但是为什么有下面这样的结果:
>
> 清单 2. 令人吃惊的比较结果
>
> >>> 2-3j < 'spam'
> True
>
> >>> 4+0j < decimal.Decimal('3.14')
> True
>
> >>> 4+0j < 5+0j
> TypeError: no ordering relation is defined for complex numbers
>
>
>
> 从理论上来讲,这全无 "纯" 可言。
>
> 一个真正的瑕疵:对异构集合进行排序
>
> 自变量有时候会造成编程错误,试图对不可比较的类型进行比较。但是 Python 可以顺利地执行许多这种类型的比较;并且依照 "duck
> typing" 哲学来完成这样的任务(duck typing 是指
> "如果看起来像鸭子,听起来像鸭子,就可以把它当作鸭子",也就是说,不管对象是 什么,只在乎它做 什么。)Python
> 集合常常将不同类型的对象组织在一起,希望能够做
> 与其中的各对象相似的事情。一种常见的用例是对一组不同类型的值进行编码,以便通过某种协议进行传输。
>
> 对于这其中的大多数值,不等比较是不必要的。但是,在一种常见的情况下,不等性是非常有用的;那就是对集合进行排序
> 时,通常是对列表或与列表类似的定制集合进行排序。有时候,需要以一种有意义的升序来处理集合(例如,按照数据值从小到大的次序)。在其他时候,需要为多个集合创建一种固定的次序,尤其是为了对两个集合执行某种类似于
> "list diff" 的处理时。也就是说,如果一个对象在这两个集合中都存在,那么就执行一种操作;如果它只在一个集合中存在,就执行另一种操作。不断地检查
> if x in otherlist 会导致效率成 big-O 式几何级数递减;在两个固定排序的列表之间进行平行匹配的效率要高得多。例如:
>
> 清单 3. 根据两个列表的成员关系执行不同的操作
>
> list1.sort()
> list2.sort()
> list2_xtra = []
> list2_ndx = 0
> for it1 in list1:
>     it2 = list2[list2_ndx]
>     while it1 < it2:
>         list2_ndx += 1
>         it2 = list2[list2_ndx]
>         if it1 == it2:
>             item_in_both(it1)
>         elif it1 > it2:
>             item_in_list1(it1)
>         else:
>             list2_xtra.appen(it2)
>  for it2 in list2_xtra:
>      item_in_list2(it2)
>
>
> 有时候,有意义比较的 "局部序列" 是有用的,甚至在出现不同类型对象的情况下也是如此(例如,"依次"
> 处理所有浮点值,即使它们与其他地方处理的字符串没有可比性)。
>
> 排序失败
>
> 当然,上面执行 "list diff" 的代码几乎可以任意扩展。例如,list1 和 list2
> 可以是下面这样的小列表的集合。请试着猜一下哪些部分是可以排序的:
>
> 清单 4. 可排序和不可排序列表的大杂烩
>
> ['x','y','z', 1],
> ['x','y','z', 1j],
> ['x','y','z', 1j, 1],       # Adding an element makes it unsortable
> [0j, 1j, 2j],               # An obvious "natural" order
> [0j, 1, 2],
> [0, 1, 2],                  # Notice that 0==0j --> True
> [chr(120), chr(240)],
> [chr(120), chr(240), 'x'],
> [chr(120), chr(240), u'x'], # Notice u'x'=='x' --> True
> [u'a', 'b', chr(240)],
> [chr(240), u'a', 'b']       # Same items, different initial order
>
>
> 我编写了一个小程序来尝试排序各列表:
>
> 清单 5. 对各列表进行排序的结果
>
> % python compare.py
> (0)  ['x', 'y', 'z', 1] --> [1, 'x', 'y', 'z']
> (1)  ['x', 'y', 'z', 1j] --> [1j, 'x', 'y', 'z']
> (2)  ['x', 'y', 'z', 1j, 1] --> exceptions.TypeError
> (3)  [0j, 1j, 2j] --> exceptions.TypeError
> (4)  [0j, 1, 2] --> exceptions.TypeError
> (5)  [0, 1, 2] --> [0, 1, 2]
> (6)  ['x', 'xf0'] --> ['x', 'xf0']
> (7)  ['x', 'xf0', 'x'] --> ['x', 'x', 'xf0']
> (8)  ['x', 'xf0', u'x'] --> exceptions.UnicodeDecodeError
> (9)  [u'a', 'b', 'xf0'] --> [u'a', 'b', 'xf0']
> (10) ['xf0', u'a', 'b'] --> exceptions.UnicodeDecodeError
>
>
> 通过前面的解释,或多或少能够猜出一部分结果。但是,看一下 (9) 和
> (10),这两个列表以不同次序包含完全相同的对象:由此可见,排序是否失败不但取决于列表中 对象的类型和值,还取决于 list.sort()
> 方法的特定实现!
>
> 修订比较
>
> 自 1.5.2 以来,Python
> 发展出了一种非常有用的数据类型:集(set),它最初是一个标准模块,后来成了一种内置类型(模块还包含一些额外的特性)。对于上面描述的许多问题,只需使用集来取代列表即可轻松地判断对象是在一个集合中、在另一个集合中还是同时存在于两个集合中,而不需要编写自己的
> "list diff" 代码。例如:
>
> 清单 6. 集与集操作
>
> >>> set1 = set([1j, u'2', 3, 4.0])
>
> >>> set2 = set([4, 3, 2, 1])
>
> >>> set1 | set2
> set([3, 1, 2, 1j, 4.0, u'2'])
>
> >>> set1 & set2
> set([3, 4])
>
>
> 在编写上面这个示例时,我发现了一些相当奇怪的现象。集操作似乎采用相等性(equality)而不是同一性(identity)。或许这有某种意义,但奇怪的是,这两个集的合集包含浮点数
> 4.0,而其交集包含整数 4。更奇怪的是,尽管求合集和交集的操作在理论上是对称的,但是实际结果却与次序相关:
>
> 清单 7. 集中得到的奇怪类型
>
> >>> set2 & set1
> set([3, 4.0])
>
> >>> set([3, 4.0, 4, 4+0j])
> set([3, 4.0])
>
>
> 当然,初看起来,集是一种很棒的数据类型。但是,定制的比较总是应该考虑的解决方案。在 Python 2.4 之前,完全有可能实现定制的
> cmp() 函数并将它传递给 list.sort()。这样就可以实现原本不可比较的对象之间的比较;cmp
> 自变量的问题是,每次比较时都要调用这个函数:Python 的调用开销非常高,而且更糟糕的是,要经过多次计算才能得出所计算的值。
>
> 对于 cmp 效率低下的问题,有效的解决方案是使用 Schwartzian
> 排序:修饰(decorate)各对象,进行排序,然后去掉修饰。遗憾的是,这需要使用一些定制的代码,不是简单地调用 list.sort()
> 就能够实现的。Python 2.4 提供了一种出色的组合解决方案,使用 key
> 自变量。这个自变量接受一个函数,这个函数返回一个经装饰的对象并 "在幕后" 进行 Schwartzian
> 排序。请记住,即便是复数与复数之间也不能进行比较,而 Unicode 对象只在与某些 字符串比较时会出问题。我们可以使用以下代码:
>
> 清单 8. 一个稳定、通用的排序键
>
> def stablesort(o):
>     # Use as: mylist.sort(key=stablesort)
>     if type(o) is complex:
>         return (type(o), o.real, o.imag)
>     else:
>         return (type(o), o)
>
>
> 请记住,元素的次序可能与您期望的不完全一致:即使在未修饰排序成功的地方,次序与未修饰排序也不一样。尤其是,具有不同数字类型的元素不再混在一起,而是被分隔为排序结果的不同部分。但是,它至少是固定的,而且对于几乎任何列表都是有效的(仍然可以用定制的对象扩展这种排序)。
>
> 以生成器作为准序列
>
> 经过数个版本的发展,Python 的 "惰性" 大大增强。有几个版本中已出现了在函数体中用 yield
> 语句定义的生成器。但是,在这个过程中,还出现了 itertools 模块,它可以组合和创建各种类型的迭代器。还出现了 iter()
> 内置函数,它可以将许多类似于序列的对象转换为迭代器。在 Python 2.4 中,出现了生成器表达式(generator
> expression);在 2.5 中,出现了改进的生成器,这使编写协同例程更为轻松。另外,越来越多的 Python
> 对象成为迭代器或类迭代器,例如,过去读取文件需要使用 .xreadlines() 方法或 xreadlines 模块,而现在 open()
> 的默认行为就能是读取文件。
>
> 同样,过去要实现 dict 的惰性循环遍历需要使用 .iterkeys() 方法;现在,它是默认的 for key in dct
> 行为。xrange() 这样的函数与生成器类似的方面有些 "特殊",它们既不是真正的 迭代器(没有 .next()
> 方法),也不是实际的列表(比如 range() 返回的列表)。但是,enumerate() 返回一个真正的生成器,通常会实现以往希望
> xrange() 实现的功能。itertools.count() 是另一个惰性调用,它的作用与 xrange() 几乎
> 完全相同,但它是一个功能完备的迭代器。
>
> Python 的发展大方向是以惰性方式构造类似于序列的对象;总体来说,这个方向是正确的。惰性的伪序列既可以节省内存空间,还可以提高操作的速度(尤其是在处理非常大的与序列类似的
> "东西" 时)。
>
> 问题在于,Python 在判断 "硬" 序列和迭代器之间的差异和相似性方面仍然不完善。这个问题最棘手的部分是,它实际上违背了 Python
> 的 "duck typing"
> 思想:只要给定的对象具有正确的行为,就能够将它用于特定的用途,而不存在任何继承或类型限制。迭代器或类迭代器有时表现得像序列,但有时候不是;反过来说,序列常常表现得像迭代器,但并非总是如此。表现不像迭代器那些序列已涉及
> Python 的神秘领地,其作用尚不明朗。
>
> 分歧
>
> 所有类序列或类迭代器的主要相似之处是,它们都允许进行循环遍历,无论是使用 for 循环、列表理解(list
> comprehension)还是生成器理解(generator
> comprehension)。除此之外,就出现了分歧。其中最重要的差异是,序列既可编制索引,也可直接切片(slice),而迭代器不能。实际上,为序列编制索引可能是最常用的序列操作
> ―― 究竟为什么在迭代器上无法使用索引呢?例如:
>
> 清单 9. 与序列和迭代器相似的东西
>
> >>> r = range(10)
>
> >>> i = iter(r)
>
> >>> x = xrange(10)
>
> >>> g = itertools.takewhile(lambda n: n<10, itertools.count())
>
> #...etc...
>
>
> 对于所有这一切,都可以使用 for n in thing。实际上,如果用 list(thing)
> 显示它们,会得到完全相同的结果。但是,如果希望获得其中的一个特定条目(或一些条目的切片),就需要考虑 thing 的具体类型。例如:
>
> 清单 10. 索引操作成功和失败的示例
>
> >>> r[4]
> 4
>
> >>> i[4]
> TypeError: unindexable object
>
>
> 对于每种序列/迭代器类型,只要费一番功夫,总能够获得其中的特定条目。一种方法是进行循环,直至到达所需的对象。另一种古怪的解决方案如下:
>
> 清单 11. 获得索引的怪方法
>
> >>> thing, temp = itertools.tee(thing)
>
> >>> zip(temp, '.'*5)[-1][0]
> 4
>
>
> 对 itertools.tee() 的预调用保留了原始迭代器。对于切片,可以按照特殊方式使用 itertools.islice() 函数。
>
> 清单 12. 获得一个切片的特殊方法
>
> >>> r[4:9:2]
> [4, 6, 8]
>
> >>> list(itertools.islice(r,4,9,2))  # works for iterators
> [4, 6, 8]
>
>
> 类包装器
>
> 为了方便起见,可以将这些技术组合成一个类包装器:
>
> 清单 13. 使迭代器可编制索引
>
> 所以,通过某些措施,可以让一个对象同时表现得像序列和迭代器。但是,费这么大力气实际上
> 应该是不必要的;无论涉及的是序列还是迭代器,编制索引和切片都应该是 "可行的"。
>
> 注意,Indexable 类包装器仍然不够灵活。主要问题是,每次都要创建迭代器的一个新副本。更好的方法应该是在对序列切片时缓存序列的头部,然后在以后访问已经检查的元素时使用所缓存的头部。当然,在使用迭代器时,要在占用的内存和速度之间有所取舍。但是,如果
> Python 本身能够 "在幕后" 完成这些,就再好不过了 ―― "高级用户" 可以对行为进行调优,而一般程序员应该不需要考虑这些。
>
> 来源: http://aolinux.wannish.com
> _______________________________________________
> python-chinese
> Post: send python-chinese在lists.python.cn
> Subscribe: send subscribe to python-chinese-request在lists.python.cn
> Unsubscribe: send unsubscribe to  python-chinese-request在lists.python.cn
> Detail Info: http://python.cn/mailman/listinfo/python-chinese


-- 
'''Time is unimportant, only life important!
过程改进的目标不是高品质产品,而是促生靠谱的人的组织!
'''http://zoomquiet.org
blog  @ http://blog.zoomquiet.org/pyblosxom/
wiki  @ http://wiki.woodpecker.org.cn/moin/ZoomQuiet
scrap @ http://floss.zoomquiet.org
share @ http://share.zoomquiet.org
douban@ http://www.douban.com/people/zoomq/
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pls. usage OOo to replace M$ Office. http://zh.openoffice.org
Pls. usage 7-zip to replace WinRAR/WinZip.  http://7-zip.org
You can get the truely Freedom 4 software.

[导入自Mailman归档:http://www.zeuux.org/pipermail/zeuux-python]

如下红色区域有误,请重新填写。

    你的回复:

    请 登录 后回复。还没有在Zeuux哲思注册吗?现在 注册 !

    Zeuux © 2025

    京ICP备05028076号