Linux 版 (精华区)

发信人: emacs (被淹死的鱼), 信区: Linux
标  题: 第二章 自省的威力 -- 2
发信站: 哈工大紫丁香 (Sat Jun 15 09:50:07 2002) , 转信



2.6. 过滤列表

你已经知道了,Python拥有强大的映射列表到其它列表的能力。它可以同一种过滤机制合
并,即列表中的某此元素被映射,而其它的被完全忽略。

例 2.14. 列表过滤语法

[mapping-expression for element in source-list if filter-expression]前三分之二
的表达式应该看上去熟悉了,因为它与列表映射有着相同的结构。最后一部分,以 if 开
始的,就是过滤表达式。一个过滤表达式可以是任意地计算真假值的表达式(这在Python中
可能是几乎任何东西)。任何对于过滤表达式计算出真值的元素将包括在映射中。其它所有
元素被忽略掉,所以它们不会进入映射表达式,并且也不被包括在输出列表中。

例 2.15. 列表过滤介绍

>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
>>> [elem for elem in li if len(elem) > 1]       
['mpilgrim', 'foo']
>>> [elem for elem in li if elem != "b"]         
['a', 'mpilgrim', 'foo', 'c', 'd', 'd']
>>> [elem for elem in li if li.count(elem) == 1] 
['a', 'mpilgrim', 'foo', 'c']  这里的映射表达式很简单(它只返回每个元素的值),所
以把注意力集中到过滤表达式上。当Python遍历列表时,它对每个元素执行过滤表达式;
如果过滤表达式为真,元素被映射且映射表达式的值被包括在返回列表中。这里你过滤掉
了所有单个字符字符串,所以留下了一个都是长字符串的列表。
 
  这里你过滤掉了一个特殊值 b。注意,它会过滤掉所有出现的 b 元素,因为每次 b 被
取出来,过滤表达式将是假。
 
  count 是列表的方法,它返回在一个值在列表中出现的次数。你也许会认为这个过滤将
消除列表中的重值,返回一个只包含了在原始列表中有着唯一值拷贝的列表。但是不是这
样的,因为在原始列表中出现两次的值(在本例中, b 和 d)被完全排除了。存在从一个列
表排除重复值的方法,但是过滤不是解决办法。
 

例 2.16. 在 apihelper.py 中过滤列表

    typeList = (BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodTy
pe, ClassType)
    methodList = [method for method in dir(object) if type(getattr(object, met
hod)) in typeList]这个看上去挺复杂的,它的确复杂,但是基本结构是一样的。整个过
滤表达式返回一个列表,列表赋给 methodList 变量。表达式的前半部分是列表映射部分
。映射表达式是一个相同的表达式,它返回每个元素的值。dir(object) 返回一个对象的
属性和方法的列表,那就是你正在映射的列表。所以唯一新的部分就是在 if 后面的过滤
表达式。

过滤表达式看上去很恐怖,但其实不是。你已经知道了type,getattr,和in了。正如你在
前面部分中看到的,如果 object 是一个模块,并且 method 是在那个模块中函数的名字
,表达式 getattr(object, method) 返回一个函数对象。

所以这个表达式接收一个名为 object 的对象,得到它的属性,方法,和一些其它东西的
名字列表,然后过滤那个列表来除掉所有我们不关心的东西。我们执行过滤是通过得到每
个属性/方法/函数的名字,然后通过 getattr 函数得到指向实际东西的引用。然后我们用
 type 函数来检查对象的类型,接着查看是否那个类型是我们所关心的东西之一。特别地
,我们关心方法和函数,内置的(如一个列表的 pop 方法)和用户自定义的(如 odbchelpe
r 模块中的 buildConnectionString 函数)。我们不关心其它的属性,如内置在每一个模
块中的 __name__ 属性。

你可能已经注意到过滤表达式也包括了类型为 ClassType 的对象。现在不要对此担心,当
我们进行到第三章中的面向对象概念时,我们将讨论Python的类(译注:原文为“Don't w
orry about this for now; we'll discuss Python classes ad nauseum when we get i
nto object-oriented concepts in chapter 3.”,不知道 ad nauseum 是什么意思。)。


2.7. and-or 技巧

如果你是一名C语言高手,当然对 bool ? a : b 表达式熟悉,这个表达式当 bool 为真时
计算为 a,其它值则为 b。象很多编程技巧一样,它是一种诱人的便利。你可在Python中
完成同样的事情,但是需要完全理解它是如何工作的,以避免不明显的毛病。

例 2.17. and-or 技巧

>>> a = "first"
>>> b = "second"
>>> 1 and a or b 
'first'
>>> 0 and a or b 
'second'
  这个语法看起来与C语言中的 bool ? a : b 相似。第一部分将在布尔环境中进行计算,
它可以是任意Python表达式。如果计算为真,整个表达式的值为 a。
 
  如果第一部分计算为假,整个表达示的值为 b。 

然而,因为这个Python表达式是简单的布尔逻辑,而不是一个特殊的语言结构,所以在这
个Python的 and-or 技巧与C中的 bool ? a : b 语法之间,有一个非常,非常,非常重要
的不同。如果 a 的值为假,表达式将不会按你期望的那样执行。(你能知道我被这个问题
折腾过吗?不只一次?)

例 2.18. 何时 and-or 技巧失败

>>> a = ""
>>> b = "second"
>>> 1 and a or b
'second'
因为 a 是一个空串,空串在一个布尔环境中被Python看成假值,这个表达式将“失败”,
且返回 b 的值。如果你不将它想象成象 bool ? a : b 一样的语法,而把它看成纯粹的布
尔逻辑,这样的话就会得到正确的理解。 1 是真,a 是假,所以 1 and a 是假。假 or 
b 是 b。


 
这个 and-or 技巧,bool and a or b,当 a 为假时,不会象C表达式 bool ? a : b 一样
工作。 

The real trick behind the and-or trick, then, is to make sure that the value o
f a is never false. One common way of doing this is to turn a into [a] and b i
nto [b], then taking the first element of the returned list, which will be eit
her a or b.

Example 2.19. Using the and-or trick safely

>>> a = ""
>>> b = "second"
>>> (1 and [a] or [b])[0]
''因为 [a] 是一个非空列表,它永远不会为假。甚至 a 是 0 或 '' 或其它假值,列表 
[a] 为真,因为它有一个元素。

到现在为止,这个技巧可能看上去问题超过了它的价值。毕竟你可以用一个 if 语句完成
相同的事情,那么为什么要经受这些麻烦呢?哦,在很多情况下,要在两个常量之间进行
选择,所以可以使用更简单的语法而不必担心,因为你知道 a 值将总是真。并且尽管你不
得不使用更复杂的安全形式,也有一些好的理由来使用这个技巧;在Python中有很多时候
, if 语句不允许使用,这一点我们将在下一节看到。


 
一个负责的程序员应该将 and-or 技巧封装成一个函数: 
def choose(bool, a, b):
    return (bool and [a] or [b])[0] 
2.8. 使用 lambda 函数

Python支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做 lambda 的函
数是从Lisp中借用来的,可以被用在任何需要函数的地方。

出于历史的原因,lambda 函数的语法与通常的函数有些细微的不同。

例 2.20. lambda 函数介绍

>>> def f(x):          
...     return x*2
...     
>>> f(3)
6
>>> g = lambda x: x*2  
>>> g(3)
6
>>> (lambda x: x*2)(3) 
6  这是一个通常的函数声明,尽管以前你可能没有看到过定义在交互式窗口中的函数。这
个 ... 说明它是一个多行的交互语句。只要在第一行的后面敲入回车,Python IDE会让你
接着输入命令。
 
  这是一个 lambda 函数,它完成同上面普通函数相同的事情。注意这里的简短的语法;
没有小括号, return 是默认的,并且函数没有名字,只有将它赋值给变量的变量名。
 
  你甚至可以不将 lambda 函数赋值给一个变量而使用它。这不是举世无双的东西,它只
是展示了 lambda 函数只是一个内联函数。
 

总之, lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值
的函数。 lambda 函数不能包含命令,它们所包含的表达式不能超过一个。不要试图向 l
ambda 函数中塞入太多的东西;如果你需要更复杂的东西,应该定义一个普通函数,然后
想让它多长就多长。


 
lambda 函数是风格问题。不一定非要使用它们,任何能够使用它们的地方,都可以定义一
个分离的普通的函数,用它来替换。我将它们用在需要封装特殊的,非重用的代码上,用
许多小的一行函数不会弄乱我的代码。 

例 2.21. 在 in apihelper.py 中的 lambda 函数

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s
)顺便这里有几件事情需要注意。首先,我们使用了 and-or 技巧的简单形式,没问题,因
为一个 lambda 函数在一个布尔环境下总为真。(这并不意味着 lambda 函数不能返回假值
。函数本身总是为真,它的返回值可以为任何值。)

第二,我们使用了 split 函数没带参数。你已经看到过它带1个或2个参数的使用,但是不
带参数它按空白进行分割。

例 2.22. split 不带参数

>>> s = "this   is\na\ttest"  
>>> print s
this   is
a test
>>> print s.split()           
['this', 'is', 'a', 'test']
>>> print " ".join(s.split()) 
'this is a test'  这是一个多行字符串,通过转义字符的定义代替了三重引号。 \n 是
一个回车; \t 是一个制表符。
 
  split 不带参数按空白进行分割。所以三个空格,一个回车,和一个制表符都是一样的

 
  你可以将空白统一化,通过分割一个字符串,然后用单个空格作为分隔符将其重新接起
来。这就是 help 函数所做的,将多行文档字符串合并成单行。
 

那么 help 函数到底用这些 lambda 函数, split 函数,和 and-or 技巧做了什么呢? 


例 2.23. 将函数赋给一个变量

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s
)processFunc 现在是一个函数,但它为哪一个函数要看 collapse 变量的值。如果 coll
apse 为真, processFunc(string) 将压缩空白;否则,processFunc(string) 将返回未
改变的参数。 

在一个不很建壮的语言实现它,象VB,你将可能创建一个函数,它接收一个字符串和一个
 collapse 参数,使用一个 if 语句来判断是否要压缩空白或不压缩,然后返回相应的值
。这样效率低,因为函数将不得不处理每种可能性;每次你调用它,它将不得不在给出你
所想要的东西之前,判断是否要压缩空白。在Python中,你可以将那种判断逻辑拿到函数
外面,而定义一个裁减过的 lambda 函数来给出确切的(并且唯一)你想要的。这样做更有
效率,更漂亮,并且更少导致那些令人讨厌的(哦,想到那些参数就头昏)的错误。


2.9. 全部放在一起

最一行代码,是唯一我们还没有解析过的,它完成全部的工作。过目前为止,很顺利,因
为我们需要的每件事都已经按照需求建立好了。所有的多米诺骨牌已经就位,到了将它们
推倒的时候了。

例 2.24. apihelper.py 的内容

    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__))
                     for method in methodList])注意这是一条命令,分割成多行,但
是没有使用续行符(“\”)。还记得我说过一些表达式可以分割成多行不需要使用反斜线吗
?列表映射就是这些表达式之一,因为整个表达式包括在方括号里。

现在,让我们从后向前分析。

for method in methodList告诉我们这是一个列表映射。如你所知,methodList 是一个在
 object 中所有我们关心的方法的列表。那么我们用 method 来遍历列表。

例 2.25. 动态得到文档字符串

>>> import odbchelper
>>> object = odbchelper                   
>>> method = 'buildConnectionString'      
>>> getattr(object, method)               
<function buildConnectionString at 010D6D74>
>>> print getattr(object, method).__doc__ 
Build a connection string from a dictionary of parameters.

    Returns string.  在 help 函数中,object 是我们正要得到帮助的对象,它被作为
一个参数传进来。 
  当我们遍历 methodList,method 是当前方法的名字。 
  通过使用 getattr 函数,我们得到在 object 模块中对于 method 函数的一个引用。 

  现在,打印出方法的文档字符串很容易。 

接下来的困惑是在文档字符串周围 str 的使用。你可能回忆得起, str 是一个内置的函
数,它可以强制将数据转化为字符串。但是一个文档字符串已经是字符串了,那么为什么
还要费事地使用 str 函数呢?答案就是:不是每个函数都有文档字符串,那么如果没有,
它的 __doc__ 属性为 None。

例 2.26. 为什么对一个文档字符串使用 str ?

>>> {}.keys.__doc__         
>>> {}.keys.__doc__ == None 
1
>>> str({}.keys.__doc__)    
'None'
  字典的 keys 函数没有文档字符串,所以它的 __doc__ 属性为 None。让人迷惑的是,
如果你直接计算 __doc__ 属性,Python IDE什么都不打印,如果你只是考虑它,是有意义
的,但是却没有用。
 
  你可以通过把 __doc__ 属性同 None 值直接进行比较,验证文档字符串确实为 None。
(使用SQL Server脚本的小伙们要注意:在Transact/SQL中,应该使用 IS NULL 而不是 =
 NULL 来比较一个空值。在Python中,没有特别的语法,你可以使用 == 就象任何其它的
比较。)
 
  使用 str 函数接收空值,然后返回它的字符串表示,'None'。 

现在我们保证有了一个字符串,我们可以传递这个字符串给 processFunc,这个函数我们
定义为一个要不压缩空白要不不压缩的函数。现在你明白了为什么用 str 将一个 None 值
转换成一个字符串表达的重要性了吧。processFunc 假定一个字符串参数,并且调用它的
 split 方法,如果我们传给 split 方法 None ,则这个函数会失败,因为 None 没有一
个 split 方法。

往回再深一步,我们看到再一次使用了字符串格式化来连接 processFunc 的返回值和 me
thod 的 ljust 方法的返回值。ljust 方法是一个我们以前没见过的字符串方法。

例 2.27. ljust 方法介绍

>>> s = 'buildConnectionString'
>>> s.ljust(30) 
'buildConnectionString         '
>>> s.ljust(20) 
'buildConnectionString'  ljust 用空格填充字符串到指定长度。help 函数就是用它用
来生成两列输出数据,并且将所有在第二列的文档字符纵向对齐。
 
  如果给定的长度小于字符串的长度,ljust 将简单地返回未变化的字符串。它决不会截
断字符串。
 

我们就快完成了。有了从 ljust 方法得到的填充过的方法名字和从调用 processFunc 得
到的文档字符串(可能压缩过),我们将两者连接起来,然后得到单一字符串。因为我们对
 methodList 进行映射,最后得到一个字符串列表。使用 "\n" 字符串的 join 方法,我
们将这个列表连接成单一字符串,列表中每一个元素独占一行,接着打印出结果。

例 2.28. 打印列表

>>> li = ['a', 'b', 'c']
>>> print "\n".join(li) 
a
b
c  当你处理列表时,这的确是一个有用的调试技巧。并且在Python中,你几乎总是在操纵
列表。
 

这就是最后一个问题。这个代码现在应该相当清楚了。

例 2.29. 重新回顾 apihelper.py 的内容

    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__))
                     for method in methodList])
2.10. 小结

apihelper.py 程序和它的输出现在应该非常清楚了。

例 2.30. apihelper.py

from types import BuiltinFunctionType, BuiltinMethodType, \
    FunctionType, MethodType, ClassType

def help(object, spacing=10, collapse=1):
    """Print methods and doc strings.
    
    Takes module, class, list, dictionary, or string."""
    typeList = (BuiltinFunctionType, BuiltinMethodType, FunctionType, MethodTy
pe, ClassType)
    methodList = [method for method in dir(object) if type(getattr(object, met
hod)) in typeList]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s
)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__))
                     for method in methodList])

if __name__ == "__main__":
    print help.__doc__例 2.31. apihelper.py 的输出

>>> from apihelper import help
>>> li = []
>>> help(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of va
lue
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default 
last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1
, 0, 1在研究下一章之前,确保你可以无困难地完成下面的事情:

或者用import module或者用from module import导入模块 
用可选和定名参数定义和调用函数 
使用 str 来强制将任一值转换为字符串表达 
使用 getattr 动态得到函数和其它什么的引用 
扩展列表映射语法来进行列表过滤 
考虑and-or 技巧和安全使用它 
定义lambda 函数 
将函数赋值给变量然后通过引用变量调用函数。我强调的已经够多了:这种思考方式对于
提高你对Python的理解力至关重要。从这书中你会随处可见这种技术的更复杂的应用。 

--

※ 来源:.哈工大紫丁香 http://bbs.hit.edu.cn [FROM: 211.93.34.115]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:205.563毫秒