Chapter 2: Python语言

Python语言

Python

关于Python

Python是一种通用的高级编程语言。它的设计思想是强调程序员的工作效率及代码的可读性。它有一个最低限度的核心语法,包括很少的基本命令以及简单语义,但与此同时它又有一个大规模的全面的标准库,包括许多面向底层操作系统(OS)函数的应用编程接口(API)。Python代码,在简约的同时,定义了内置对象,例如列表 (list)、元组(tuple)、哈希表(字典dict)以及任意长度整型数(long)。

Python支持多重编程架构,包括面向对象(class)、指令 (def)、以及函数 (lambda)编程,Python还有一套动态类型系统以及通过引用计数实现的(类似于Perl、Ruby和Scheme)自动内存管理。

Python最初由Guido van Rossum于1991年发布。该语言是开源的、基于社区开发模型的,由非盈利的Python软件基金会管理。可以实现Python语言的解释器及编译器有许多,包括Java版的Jython,不过在此简要回顾中,我们特指由Guido开发的C语言实现。

在Python官方网站上[python] ,您可以找到该语言的许多教程、官方文档以及库指南。

若要获取更多参考文献,我们推荐参考文献[guido][lutz] 中的书。

如果您已经熟悉Python语言,可以跳过本章节。

启动

shell

Web2py的二进制发行版支持微软Windows或苹果OS X操作系统,该发行文件中包含内置的Python解释器。

在Windows操作系统下,可以使用如下命令来启动web2py(在DOS提示符下输入):

web2py.exe -S welcome

在苹果OS X操作系统下,在终端窗口中输入如下命令(假如您与web2py.app在同一文件夹中):

./web2py.app/Contents/MacOS/web2py -S welcome

在Linux或其他Unix操作系统下,很可能Python已经安装过了,如果是这样的话,在shell提示符下输入:

python web2py.py -S welcome

如果您没有预先安装Python2.5(或者更新的版本2.x),在运行web2py之前,您需要下载并安装新的Python。

  • S welcome 命令行选项指示web2py运行交互式壳(shell),就像是welcome应用中命令在控制器内被执行,即web2py基本构建应用。这向用户暴露了几乎所有的web2py的类、对象及函数。这是web2py交互式命令行与普通Python命令行的唯一不同之处。

对于每个应用,管理接口提供了一个基于web的shell。对于"welcome"应用,您可以访问shell。

http://127.0.0.1:8000/admin/shell/index/welcome

您可以使用普通shell或基于web的shell来尝试本章中的所有例子。

help, dir

help
dir

Python语言提供了两条命令来获取定义在当前作用域内对象的有关文档,包括内置的和用户定义的。

我们可以寻求有关对象的help,例如"1":

>>> help(1)
Help on int object:

class int(object)
 |  int(x[, base]) -> integer
 |
 |  Convert a string or number to an integer, if possible.  A floating point
 |  argument will be truncated towards zero (this does not include a string
 |  representation of a floating point number!)  When converting a string, use
 |  the optional base.  It is an error to supply a base when converting a
 |  non-string. If the argument is outside the integer range a long object
 |  will be returned instead.
 |
 |  Methods defined here:
 |
 |  __abs__(...)
 |      x.__abs__() <==> abs(x)
...

并且由于"1"是一个整数,我们得到int类及其所有方法的描述。这里输出被截断了,因为它很长非常详细。

类似的,我们用命令dir获取对象"1"的方法列表:

>>> dir(1)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__',
'__delattr__', '__div__', '__divmod__', '__doc__', '__float__',
'__floordiv__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__',
'__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__',
'__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__',
'__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__',
'__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__',
'__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__',
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__',
'__str__', '__sub__', '__truediv__', '__xor__']

类型

type

Python是一种动态类型语言,这意味着变量没有类型,因此也不需要被声明。另一方面,值是有类型的。您可以查询变量包含的值的类型:

>>> a = 3
>>> print type(a)
<type 'int'>
>>> a = 3.14
>>> print type(a)
<type 'float'>
>>> a = 'hello python'
>>> print type(a)
<type 'str'>

Python也包括了原生的数据结构,如列表和字典。

字符串

str
ASCII
UTF8
Unicode
encode

Python支持两种不同类型字符串的使用:ASCII字符串和Unicode字符串。ASCII字符串使用'...'、"..." 、'..' 或 """..."""分隔。三重引号分隔多行字符串。Unicode字符串以一个u起始,包含Unicode字符的字符串跟在后面。通过选择编码,可以将Unicode字符串转换成ASCII字符串,例如:

>>> a = 'this is an ASCII string'
>>> b = u'This is a Unicode string'
>>> a = b.encode('utf8')

执行完这三条命令之后,得到的结果a是一个存储UTF8编码字符的ASCII字符串。按照设计,web2py内部使用UTF8编码的字符串。

可以用多种方法将变量写入字符串:

>>> print 'number is ' + str(3)
number is 3
>>> print 'number is %s' % (3)
number is 3
>>> print 'number is %(number)s' % dict(number=3)
number is 3

最后一种标记是首选,它更明确、不容易出错。

使用str或repr命令,许多Python对象可以被序列化成字符串,例如数字。这两条命令非常相似,但会产生略有不同的输出。例如:

>>> for i in [3, 'hello']:
        print str(i), repr(i)
3 3
hello 'hello'

对于用户定义的类,使用特殊操作符__str__ 和__repr__,str和repr可以被定义/重定义。后面会对此作简要介绍;更多相关内容,请参阅Python官方文档。repr总有一个默认值。

Python字符串的另一重要特征是,像列表一样,它是一个遍历对象。

>>> for i in 'hello':
        print i
h
e
l
l
o

列表

list

Python列表的主要方法包括添加(append)、插入(insert)、删除(delete):

>>> a = [1, 2, 3]
>>> print type(a)
<type 'list'>
>>> a.append(8)
>>> a.insert(2, 7)
>>> del a[0]
>>> print a
[2, 7, 3, 8]
>>> print len(a)
4

列表可以被分片:

>>> print a[:3]
[2, 7, 3]
>>> print a[1:]
[7, 3, 8]
>>> print a[-2:]
[3, 8]

还可以被连接:

>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]

列表可以被遍历;可以通过循环访问:

>>> a = [1, 2, 3]
>>> for i in a:
        print i
1
2
3

列表中的元素不必是相同类型;它们可以是Python对象的任意类型。

有一种很常见的情况,可以使用列表解析(list comprehension)。请看如下代码:

>>> a = [1,2,3,4,5]
>>> b = []
>>> for x in a:
        if x % 2 == 0:
            b.append(x * 3)
>>> b
[6, 12]

这段代码清楚地处理了一个项目列表,选择和修改了输入列表的一个子表,并创建了一个新的结果列表。用下列列表解析,可以完全代替上述代码:

>>> a = [1,2,3,4,5]
>>> b = [x * 3 for x in a if x % 2 == 0]
>>> b
[6, 12]

元组

tuple

元组类似于列表,但是它的大小和元素是不可变的,而列表的大小和元素是可变的。如果元组元素是一个对象,则对象的属性是可变的。元组由圆括号分隔。

>>> a = (1, 2, 3)

因此,如下代码适用于列表:

>>> a = [1, 2, 3]
>>> a[1] = 5
>>> print a
[1, 5, 3]

元素赋值不能用于元组:

>>> a = (1, 2, 3)
>>> print a[1]
2
>>> a[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

类似于列表,元组是一个遍历对象。请注意,一个元素构成的元组必须包含一个结尾逗号,如下所示:

>>> a = (1)
>>> print type(a)
<type 'int'>
>>> a = (1,)
>>> print type(a)
<type 'tuple'>

由于元组的不变性,对高效封装对象,元组是非常有用的,而且括号往往是可选的。

>>> a = 2, 3, 'hello'
>>> x, y, z = a
>>> print x
2
>>> print z
hello

字典

dict

Python字典是一个哈希表,将键对象映射成值对象。例如:

>>> a = {'k':'v', 'k2':3}
>>> a['k']
v
>>> a['k2']
3
>>> a.has_key('k')
True
>>> a.has_key('v')
False

键可以是任何哈希类型(整型、字符串,或类能实现_hash_方法的任何对象)。值可以是任何类型。在同一字典中,不同键和值不一定是相同的类型。如果键是字母数字字符,也能用替代句法声明字典:

>>> a = dict(k='v', h2=3)
>>> a['k']
v
>>> print a
{'k':'v', 'h2':3}

实用的方法是has_key、 keys、values和items:

>>> a = dict(k='v', k2=3)
>>> print a.keys()
['k', 'k2']
>>> print a.values()
['v', 3]
>>> print a.items()
[('k', 'v'), ('k2', 3)]

Items方法产生一元组列表,每个元组包含一个键和它的对应值。

字典元素和列表元素可以用del命令删除。

>>> a = [1, 2, 3]
>>> del a[1]
>>> print a
[1, 3]
>>> a = dict(k='v', h2=3)
>>> del a['h2']
>>> print a
{'k':'v'}

在内部,Python用hash运算符将对象转换成整数,并用得到的整数确定值存储在哪里。

>>> hash("hello world")
-1500746465

关于缩进

Python使用缩进分隔代码块。一个代码块起始于一行以冒号结尾的代码,下面所有缩进相同或更多的行都被视为同一个代码块。例如:

>>> i = 0
>>> while i < 3:
>>>    print i
>>>    i = i + 1
>>>
0
1
2

通常每级缩进使用四个空格。不混用tab和空格是一个好的策略,那样可能会导致(不可见的)混乱。

for...in

for

在Python中,你可以循环遍历对象:

>>> a = [0, 1, 'hello', 'python']
>>> for i in a:
        print i
0
1
hello
python

一种常用快捷方式是xrange,它可以产生一个遍历范围而无需存储整个列表元素。

>>> for i in xrange(0, 4):
        print i
0
1
2
3

这与C/C++/C#/Java句法是等价的:

for(int i=0; i<4; i=i+1) { print(i); }

另一种实用的命令是enumerate,在循环的同时能计数:

>>> a = [0, 1, 'hello', 'python']
>>> for i, j in enumerate(a):
        print i, j
0 0
1 1
2 hello
3 python

还有一个关键词range(a,b,c)可以返回一个整数列表,从值a开始,递增c,结束值小于b,其中a的默认值是0,c的默认值是1。xrange与此类似,但不会真正生成列表,只生成一个列表的遍历,因此对于循环来说它更好。 你可以使用break跳出循环。

你可以使用break跳出循环。

>>> for i in [1, 2, 3]:
         print i
         break
1

使用continue,你可以不执行完整个代码块就跳转到下一个循环迭代。

>>> for i in [1, 2, 3]:
         print i
         continue
         print 'test'
1
2
3

while

while

在Python中,while循环的工作方式与其它语言类似,在执行循环体前先判断循环次数和测试条件。如果条件为假,循环就终止。

>>> i = 0
>>> while i < 10:
        i = i + 1
>>> print i
10

Python中没有loop…until结构。

if...elif...else

if
elif
else

在Python中使用条件是直观的:

>>> for i in range(3):
>>>     if i == 0:
>>>         print 'zero'
>>>     elif i == 1:
>>>         print 'one'
>>>     else:
>>>         print 'other'
zero
one
other

“elif”意味着 “else if”。elif和else从句都是可选的。可以有多个elif语句,但只能有一个else声明。使用not,and和or运算符可以构造复杂条件。

>>> for i in range(3):
>>>     if i == 0 or (i == 1 and i + 1 == 2):
>>>         print '0 or 1'

try...except...else...finally

try
except
finally
Exception

Python可以抛出中断,引发异常:

>>> try:
>>>     a = 1 / 0
>>> except Exception, e:
>>>     print 'oops: %s' % e
>>> else:
>>>     print 'no problem here'
>>> finally:
>>>     print 'done'
oops: integer division or modulo by zero
done

如果引起异常,它会被except语句捕获,except语句会被执行,而else语句不被执行。如果未引起异常,except语句不被执行,而else语句会被执行。finally语句始终会被执行。

对于可能出现的不同异常,可以有多种except语句。

>>> try:
>>>     raise SyntaxError
>>> except ValueError:
>>>     print 'value error'
>>> except SyntaxError:
>>>     print 'syntax error'
syntax error

else和finally语句都是可选的。

以下是Python内置异常+HTTP(由web2py定义)列表。

BaseException
 +-- HTTP (defined by web2py)
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- Exception
      +-- GeneratorExit
      +-- StopIteration
      +-- StandardError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |    |    +-- UnicodeError
      |    |         +-- UnicodeDecodeError
      |    |         +-- UnicodeEncodeError
      |    |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
	   +-- ImportWarning
	   +-- UnicodeWarning

关于以上列表中每一项的详细介绍,请参阅Python官方文档。

web2py仅暴露一个新异常,该异常被称作HTTP。当该异常出现时,它会使程序返回一个HTTP错误页面(更多相关内容请参阅第4章)

任何对象都可以引起异常,但推荐的做法是引起异常的对象是内置异常类的扩展。

def...return

def
return

函数声明使用def。下面是一个典型的Python函数:

>>> def f(a, b):
        return a + b
>>> print f(4, 2)
6

没有必要(或者方法)指定参数类型或返回类型。这个例子中,定义了一个有两个参数的函数f。

函数是本章描述的第一个代码句法特征,这是为了引入域和命名空间的概念。在上面的例子中,标识符a和b在函数f的域之外是没有定义的:

>>> def f(a):
        return a + 1
>>> print f(1)
2
>>> print a
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    print a
NameError: name 'a' is not defined

定义在函数域之外的标识符是可以在函数内访问的;请观察在下面代码中标识符a是如何被处理的:

>>> a = 1
>>> def f(b):
        return a + b
>>> print f(1)
2
>>> a = 2
>>> print f(1) # new value of a is used
3
>>> a = 1 # reset a
>>> def g(b):
        a = 2 # creates a new local a
        return a + b
>>> print g(2)
4
>>> print a # global a is unchanged
1

如果a被修改了,随后的函数调用将使用全局变量a的新值,因为函数定义绑定了标识符a的存储位置,而不是在函数声明时标识符a本身的值;但是,如果a被分配到函数g内,全局变量a将不会受影响因为新的局部变量a覆盖了全局变量的值。在创建闭包(closure)时,可以使用外部域参考。

>>> def f(x):
        def g(y):
            return x * y
        return g
>>> doubler = f(2) # doubler is a new function
>>> tripler = f(3) # tripler is a new function
>>> quadrupler = f(4) # quadrupler is a new function
>>> print doubler(5)
10
>>> print tripler(5)
15
>>> print quadrupler(5)
20

函数f会创建新函数;并且注意名称g的域完全在f内部。闭包是非常强大的。

函数参数可以有默认值,并且能够返回多个结果。

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(5)
>>> print x
7
>>> print y
3

利用函数名,函数参数可以被显式传递,这意味着在函数调用中指定的参数顺序可以不同于函数定义时的参数顺序:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(b=5, a=2)
>>> print x
7
>>> print y
-3

函数也能取一个运行时可变数目的参数:

>>> def f(*a, **b):
        return a, b
>>> x, y = f(3, 'hello', c=4, test='world')
>>> print x
(3, 'hello')
>>> print y
{'c':4, 'test':'world'}

这里在元组a中存储的参数没有被(3, ’hello’)传递,而字典b中存储的参数被(c 和 test)传递。

在相反的情况下,将一个列表或元组传递给函数,它需要通过拆分得到单个位置参数。

>>> def f(a, b):
        return a + b
>>> c = (1, 2)
>>> print f(*c)
3

并且可以将字典拆分传递关键词参数。

>>> def f(a, b):
        return a + b
>>> c = {'a':1, 'b':2}
>>> print f(**c)
3

lambda

lambda

lambda提供了一种创建非常短的未命名函数的简易方法:

>>> a = lambda b: b + 2
>>> print a(3)
5

表达式"lambda [a]:[b]" 直译为“一个带参数[a] 返回[b]的函数”。lambda表达式本身是匿名的,但是该函数通过绑定标识符a获得了名称。def的域规则同样适用于lambda,实际上就a而言,上面的代码等同于使用 def的函数声明:

>>> def a(b):
        return b + 2
>>> print a(3)
5

lambda唯一的优点是简洁;然而,在特定情况下简洁是十分方便的。考虑一个名为map的函数,它对列表中的所有项应用一个函数来创建一个新列表:

>>> a = [1, 7, 2, 5, 4, 8]
>>> map(lambda x: x + 2, a)
[3, 9, 4, 7, 6, 10]

在上面的代码中,如果使用def取代lambda,代码将扩大一倍。lambda的主要缺点是(在Python实现下)句法只允许一个单一表达式;然而对于更长的函数可以使用def,因为提供函数名的额外成本随着函数长度增加而减少。同def一样,lambda也可用来curry函数:可以通过封装已有函数创建新函数,这样新函数就会携带一组不同的参数。

>>> def f(a, b): return a + b
>>> g = lambda a: f(a, 3)
>>> g(2)
5

很多情况下curry功能是有用的,其中一种在web2py中是直接有用的:缓存(caching)。 假设你有一个高负荷函数,检查参数是否为素数:

def isprime(number):
    for p in range(2, number):
        if (number % p) == 0:
            return False
    return True

该函数明显是耗时的。

假设你有一个缓存函数cache.ram,它带有3个参数:键、函数和秒数。

value = cache.ram('key', f, 60)

该函数初次被调用时,它将调用函数f(),在内存中的字典里保存输出(比方说“d”),并且返回它,因此值(value)是:

value = d['key']=f()

该函数第二次被调用时,如果键在字典中,而且存在的时间不多于指定的秒数(60),它将返回相应的值而不执行函数调用。

value = d['key']

对任意输入,如何缓存函数isprime的输出呢?这里是一种方法:

>>> number = 7
>>> seconds = 60
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True
>>> print cache.ram(str(number), lambda: isprime(number), seconds)
True

输出总是一样的,但是cache.ram第一次被调用时,isprime会被调用;第二次时,就不调用了。

使用def或lambda创建的Python函数,允许根据不同参数组重构已有函数。 cache.ram和cache.disk是web2py缓存函数。

class

因为Python是动态类型,Python类和对象似乎有点异样。事实上,当声明类时不需要定义成员变量(属性),而且同一个类的不同实例可以有不同属性。属性通常与实例相关,而不是类(除了被声明为类属性,这与C++/Java中的静态成员变量是一样的)。

下面是一个例子:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.myvariable = 3
>>> print myinstance.myvariable
3

注意pass是一条不作任何操作的命令。在本例中它被用来定义MyClass()类,该类不包含任何内容。MyClass()调用类构造函数(在本例中是默认构造),并返回一个对象,即类的一个实例。在类定义中,(object)表明我们的类扩展内置的object类。这不是必需的,但这是好的做法。

下面是一个更复杂的类:

>>> class MyClass(object):
>>>    z = 2
>>>    def __init__(self, a, b):
>>>        self.x = a, self.y = b
>>>    def add(self):
>>>        return self.x + self.y + self.z
>>> myinstance = MyClass(3, 4)
>>> print myinstance.add()
9

在类中被声明的函数是方法。有些方法有特殊的保留名称,例如, __init__ 是构造。除了方法外声明的变量,其余所有变量都是方法的局部变量。例如, z 是一个类变量,等同于一个C++中的静态成员变量,该变量对类的所有实例保持相同的值。

注意 __init__ 带有3个参数, add 带有1个参数,但我们分别用2个和0个参数调用它们。按照惯例,第一个参数表示方法内部使用的指示当前对象的局部名称。这里我们用 self 指示当前对象,但也可以使用其它名称。 self 起到相同的作用,正像 *this 在C++中或者 this 在Java中一样,但self不是保留的关键词。

当声明嵌套类的时候,为了避免歧义采取这种句法是有必要的,例如一个类在另一个类中是局部方法。

特殊属性、方法、运算符

以双下划线开头的类属性、方法和运算符通常被视作私有的(即在内部使用但不暴露在类的外部),虽然这是一种惯例,但解释器并不强制。

其中有些是保留关键词,并具有特别含义。

其中3个例子是:

  • __len__
  • __getitem__
  • __setitem__

例如,可以用它们创建一个功能类似于列表的容器对象:

>>> class MyList(object):
>>>     def __init__(self, *a): self.a = list(a)
>>>     def __len__(self): return len(self.a)
>>>     def __getitem__(self, i): return self.a[i]
>>>     def __setitem__(self, i, j): self.a[i] = j
>>> b = MyList(3, 4, 5)
>>> print b[1]
4
>>> b.a[1] = 7
>>> print b.a
[3, 7, 5]

其它特殊运算符包括 __getattr____setattr____sum____sub__ ,前两个定义类的get和set属性,后两个重载了算数运算符。关于这些运算符的使用,我们向读者推荐这一主题的更深的书籍。前面我们已经提到了特殊运算符 __str____repr__

文件输入/输出

file.read
file.write

在Python中,你可以用如下代码打开和写入文件:

>>> file = open('myfile.txt', 'w')
>>> file.write('hello world')
>>> file.close()

类似的,你可以用如下代码读文件:

>>> file = open('myfile.txt', 'r')
>>> print file.read()
hello world

另外,你可以使用标准C标记,用 "rb" 在二进制模式下读取,用"wb"在二进制模式下写入,用"a"在附加模式下打开文件。

file.seek

read命令有一个可选参数,它是字节数。你还可以在文件中用seek跳转到任意位置。

file.seek

你可以用read读取文件,

>>> print file.seek(6)
>>> print file.read()
world

而且你可以用如下方法关闭文件:

>>> file.close()

在Python标准发行版被称为CPython,变量是引用计数的,包括那些持有的文件句柄,所以当一个打开文件句柄的引用计数减少到0时,CPython是知道的,这时就可以关闭文件和处理变量。然而,在Python的其它实现如PyPy中,使用垃圾回收取代引用计数,这意味着同一时间内可能积聚过多的打开文件句柄,在gc有机会关闭和处理它们之前导致错误。因此,当不再需要的时候,最好明确关闭文件句柄。web2py在gluon.fileutils 命名空间内提供两个帮助函数read_file() 和 write_file(),它们封装文件访问,并确保正在实用的文件句柄被正确关闭。

在使用web2py时,你不能确定当前目录的位置,因为这依赖于web2py的配置。变量request.folder包含了当前应用的路径。使用os.path.join命令可以级联路径,将在后面讨论。

exec, eval

exec
eval

与Java不同,Python是真正的解释型语言。这意味着它能执行存储在字符串中的Python语句。例如:

>>> a = "print 'hello world'"
>>> exec(a)
'hello world'

发生了什么呢?exec函数告诉解释器调用它,执行参数传递的字符串的内容。它也可以执行词典中的符号定义的范围内的一个字符串的内容。

>>> a = "print b"
>>> c = dict(b=3)
>>> exec(a, {}, c)
3

当执行字符串 a 时,这里解释器看到定义在 c 中的符号(本例中是 b ),但不能看到 ca 本身。这与受限环境不同,因为 exec 不限制内部代码可以做什么;它只是定义了一组代码可见的变量。

与此相关的函数是eval,它的工作方式非常像exec,除了它期待参数能计算得到一个值,并且它将返回这个值。

>>> a = "3*4"
>>> b = eval(a)
>>> print b
12

import

import
random

Python真正的强大之处是它的库模块。这些模块对许多系统库提供了一组大量的、一致性的应用编程接口(以一种独立于操作系统的方式)。

例如,如果你需要使用一个随机数生成器,可以采用如下方法:

>>> import random
>>> print random.randint(0, 9)
5

它会打印一个0到9之间(包括9)的随机数,本例中是5。randint函数是定义在模块random中的。它也可以将来自模块的一个对象导入到当前命名空间:

>>> from random import randint
>>> print randint(0, 9)

或者将来自模块的全部对象导入到当前命名空间:

>>> from random import *
>>> print randint(0, 9)

或者将全部内容导入新定义的命名空间:

>>> import random as myrand
>>> print myrand.randint(0, 9)

在本书的其余部分,我们将主要使用定义在 ossys , datetimecPickle 模块中的对象。

通过胶子(gluon)模块可以访问全部web2py对象,这是后面章节中的主题。web2py在内部使用许多Python模块(例如thread),但你很少需要直接访问它们。

在以下小节中,我们介绍几个最实用的模块。

os

os
os.path.join
os.unlink

这个模块提供了一个操作系统API接口。例如:

>>> import os
>>> os.chdir('..')
>>> os.unlink('filename_to_be_deleted')

一些os函数千万不能用在web2py中,因为它们不是线程安全的,例如chdir。

os.path.join是非常实用的;它允许以独立于操作系统的方式级联路径:

>>> import os
>>> a = os.path.join('path', 'sub_path')
>>> print a
path/sub_path

通过如下方法,可以访问系统环境变量:

>>> print os.environ

这是一个只读字典。

sys

sys
sys.path

sys 模块包含许多变量和函数,我们使用最多的一个是sys.path 。它包含了一个路径列表,Python在那里搜索模块。当我们试图导入一个模块时,Python会在 sys.path 列出的所有文件夹中查找。如果你在一些位置安装额外模块,并且希望Python能找到它们,你需要将到那个地址的路径附加到 sys.path 中。

>>> import sys
>>> sys.path.append('path/to/my/modules')

在运行web2py时,Python常驻在内存中,并且只有一个 sys.path ,然而有多个线程为HTTP请求服务。为了避免内存泄露,在添加路径前,最好先检查一下该路径是否已经存在。

>>> path = 'path/to/my/modules'
>>> if not path in sys.path:
        sys.path.append(path)

datetime

date
datetime
time

datetime模块的使用最好通过例子来说明:

>>> import datetime
>>> print datetime.datetime.today()
2008-07-04 14:03:90
>>> print datetime.date.today()
2008-07-04

有时你需要的时间戳数据可能是基于UTC时间,而不是本地时间。在这种情况下,你可以使用如下函数:

>>> import datetime
>>> print datetime.datetime.utcnow()
2008-07-04 14:03:90

datetime模块包含各种类:date,datetime,time和timedelta。两个date或两个datetime或两个time对象之间的区别就是timedelta。

>>> a = datetime.datetime(2008, 1, 1, 20, 30)
>>> b = datetime.datetime(2008, 1, 2, 20, 30)
>>> c = b - a
>>> print c.days
1

在web2py中,当传递到数据库或从数据库中返回时,date和datetime可用于存储相应的SQL类型。

time

time

time模块不同于 datedatetime ,因为它表示的时间是从纪元(从1970年开始)开始的秒数。

>>> import time
>>> t = time.time()
1215138737.571

关于用秒表示的时间和用 datetime 表示的时间之间的转换函数,请参阅Python文档。

cPickle

cPickle

这是一个非常强大的模块。它提供的函数可以序列化几乎所有Python对象,包括自引对象。例如,让我们构建一个特殊的对象:

>>> class MyClass(object): pass
>>> myinstance = MyClass()
>>> myinstance.x = 'something'
>>> a = [1 ,2, {'hello':'world'}, [3, 4, [myinstance]]]

现在:

>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)

本例中,b是一个a的字符串表示,c是一个通过反序列化b产生的a的副本。

cPickle也可以序列化到文件和从文件反序列化;

>>> cPickle.dump(a, open('myfile.pickle', 'wb'))
>>> c = cPickle.load(open('myfile.pickle', 'rb'))
 top