SECTION 28 执行环境

执行环境

    • 28.1 可调用对象
        • 28.1.1 函数
          • 28.1.1.1 内建函数(BIF)
          • 28.1.1.2 用户定义的函数(UDF)
          • 28.1.1.3 用户定义的方法(UDM)
        • 28.1.2 类
        • 28.1.3 类的实例
    • 28.2 代码对象(了解)
    • 28.3 可执行的对象声明和内建函数
        • 28.3.1 callable()
        • 28.3.2 compile()
        • 28.3.3 eval()
        • 28.3.4 exec
        • 28.3.5 可执行对象语句和内建函数
        • 28.3.6 input()
        • 28.3.7 使用 Python 在运行时生成和执行 Python 代码
    • 28.4 执行其他(Python)程序
        • 28.4.1 导入
        • 28.4.2 execfile()
        • 28.4.3 将模块作为脚本执行
    • 28.5 执行其他(非 Python)程序
        • 28.5.1 执行外部程序的 os 模块函数
        • 28.5.2 os.system()
        • 28.5.3 os.popen()
        • 28.5.4 os.fork(), os.exec*(),os.wait*()(了解)
        • 28.5.5 os.spawn*()(了解)
        • 28.5.6 subprocess 模块
    • 28.6 结束执行
    • 28.7 各种操作系统接口

28.1 可调用对象

许多的 python 对象都是我们所说的可调用的,即是任何能通过函数操作符“()”来调用的对象。要调用可调用对象,函数操作符得紧跟在可调用对象之后。比方说,用“foo()”来调用函数"foo"。可调用对象可以通过函数式编程接口来进行调用,如 apply(),filter(),map(),以及 reduce()。

Python 有 4 种可调用对象:函数,方法,类,以及一些类的实例。记住这些对象的任何引用或者别名都是可调用的。

28.1.1 函数

我们介绍的第一种可调用的对象是函数。python 有 3 种不同类型函数对象。第一种是内建函数(BIFs)。BIF 是用 c/c++写的,编译过后放入 python 解释器,然后把它们作为第一(内建)名字空间的一部分加载进系统。如前面章节所提到的,这些函数在_bulitin_模块里,并作为__builtins__模块导入到解释器中。

28.1.1.1 内建函数(BIF)

BIF 属性|描述

bif.__doc__|文档字符串(或 None)
bif.__name__|字符串类型的文档名字
bif.__self__|设置为 None(保留给 built-in 方法)
bif.__module__|存放 bif 定义的模块名字(或 None)

>>> dir.__doc__
"dir([object]) -> list of strings\n\nIf called without an argument, return the names in the current scope.\nElse, return an alphabetized list of names comprising (some of) the attributes\nof the given object, and of attributes reachable from it.\nIf the object supplies a method named __dir__, it will be used; otherwise\nthe default dir() logic is used and returns:\n  for a module object: the module's attributes.\n  for a class object:  its attributes, and recursively the attributes\n    of its bases.\n  for any other object: its attributes, its class's attributes, and\n    recursively the attributes of its class's base classes."
>>> dir.__name__
'dir'
>>> dir.__self__
>>> dir.__module__
'__builtin__'
28.1.1.2 用户定义的函数(UDF)

UDF(User-Defined Function,用户定义的函数)通常是用 python 写的,定义在模块的最高级,因此会作为全局名字空间的一部分(一旦创建好内建名字空间)装载到系统中。函数也可在其他的函数体内定义,并且由于在 2.2 中嵌套作用域的改进,我们现在可以对多重嵌套作用域中的属性进行访问。可以用 func_closure 属性来钩住在其他地方定义的属性。

UDF 属性 描述
udf.__doc__ 文档字符串(也可以用 udf.func_doc)
udf.__name__ 字符串类型的函数名字(也可以用 udf.func_name)
udf.func_code 字节编译的代码对象
udf.func_defaults 默认的参数元组
udf.func_globals 全局名字空间字典; 和从函数内部调用 globals(x)一样
udf.func_dict 函数属性的名字空间
udf.func_doc (见上面的 udf.__doc__)
udf.func_name (见上面的 udf.__name__)
udf.func_closure 包含了自由变量的引用的单元对象元组(自用变量在 UDF 中使用,但在别处定义;参见 python[语言]参考手册)
28.1.1.3 用户定义的方法(UDM)

UDM(User-defined method,用户定义的方法)包含在类定义之中,只是拥有标准函数的包装,仅有定义它们的类可以使用。如果没有在子类定义中被覆盖掉,也可以通过子类实例来调用它们。UDM 与类对象是关联的(非绑定方法),但是只能通过类的实例来调用(绑定方法)。无论 UDMs 是否绑定,所有的 UMD 都是相同的类型——“实例方法“,如在下面例子看到的 type()调用:

>>> class C(object): # define class # 定义类
... 	def foo(self): pass # define UDM # 定义 UDM
...
>>> c = C() # instantiation # 实例化
>>> type(C) # type of class # 类的类别
<type 'type'>
>>> type(c) # type of instance # 实例的类别
<class '__main__.C'>
>>> type(C.foo) # type of unbound method # 非绑定方法的类别
<type 'instancemethod'>
>>> type(c.foo) # type of bound method # 绑定方法的类别
<type 'instancemethod'>

访问对象本身将会揭示你正在引用一个绑定方法还是非绑定方法。正如你从下面看到的,绑定的方法揭示了方法绑定到哪一个实例。

>>> C.foo # unbound method object # 非绑定方法对象
<unbound method C.foo>
>>>
>>> c.foo # bound method object # 绑定方法对象
<bound method C.foo of <__main__.C object at 0x00B42DD0>
>>> c
# instance foo()'s bound to # foo()实例被绑定到......
<__main__.C object at 0x00B42DD0>

用户自定义属性

UDM 属性 描述
udm.__doc__ 文档字符串(与 udm.im_fuc.__doc__相同)
udm.__name__ 字符串类型的方法名字(与 umd.im_func.__name__相同)
udm.__module__ 定义 udm 的模块的名字(或 none)
udm.im_class 方法相关联的类(对于绑定的方法;如果是非绑定,那么为要求 udm 的类)
udm.im_func 方法的函数对象(见 UDFs)
udm.im_self 如果绑定的话为相关联的实例,如果非绑定位为 none

28.1.2 类

我们可以利用类的可调用性来创建实例。“调用”类的结果便是创建了实例,即大家所知道的实例化。类有默认构造函数,该函数什么都不做,基本上只有一个 pass 语句。程序员可以通过实现__int__()方法,来自定义实例化过程。实例化调用的任何参数都会传入到构造函数里。

>>> class C(object):
		def __init__(self, *args):
			print 'Instantiated with these arguments:\n', args
>>> c1 = C() # invoking class to instantiate c1
Instantiated with these arguments:
()
>>> c2 = C('The number of the counting shall be', 3) 
Instantiated with these arguments:('The number of the counting shall be', 3)

28.1.3 类的实例

python 给类提供了名为__call__的特别方法,该方法允许程序员创建可调用的对象(实例)。默认情况下,__call__()方法是没有实现的,这意味着大多数实例都是不可调用的。然而,如果在类定义中覆盖了这个方法,那么这个类的实例就成为可调用的了。调用这样的实例对象等同于调用
__call__()方法。自然地,任何在实例调用中给出的参数都会被传入到__call()__中。…那么 foo()就和 foo.__call__(foo)的效果相同, 这里 foo 也作为参数出现,因为是对自己的引用,实例将自动成为每次方法调用的第一个参数。如果 __call__()有参数,比如,(self, arg),那么 foo(arg)就和调用 foo.__call__(foo, arg)一样。这里我们给出一个可调用实例的例子,和前面小节的例子相似:

>>> class C(object):
... 	def __call__(self, *args):
... 		print "I'm callable! Called with args:\n", args
>>> c =C()
# instantiation
>>> c	# 实例化# our instance# 我们的实例
<__main__.C instance at 0x00B42DD0>
>>> callable(c) # instance is callable #实例是可调用的
True
>>> c() # instance invoked # 调用实例
I'm callable! Called with arguments:()
>>> c(3) # invoked with 1 arg # 呼叫的时候给出一个参数
I'm callable! Called with arguments:(3,)
>>> c(3, 'no more, no less') # invoked with 2 args # 呼叫的时候给出两个参数
I'm callable! Called with arguments:(3, 'no more, no less')

记住只有定义类的时候实现了__call__方法,类的实例才能成为可调用的。

28.2 代码对象(了解)

可调用的对象是 python 执行环境里最重要的部分,然而他们只是冰山一角。python 语句,赋值,表达式,甚至还有模块构成了更宏大的场面。这些可执行对象无法像可调用物那样被调用。更确切地说,这些对象只是构成可执行代码块的拼图的很小一部分,而这些代码块被称为代码对象。

每个可调用物的核心都是代码对象,由语句,赋值,表达式,以及其他可调用物组成。查看一个模块意味着观察一个较大的、包含了模块中所有代码的对象。然后代码可以分成语句,赋值,表达式,以及可调用物。可调用物又可以递归分解到下一层,那儿有自己的代码对象。

一般说来,代码对象可以作为函数或者方法调用的一部分来执行,也可用 exec 语句或内建函数eval()来执行。从整体上看,一个 python 模块的代码对象是构成该模块的全部代码。如果要执行 python 代码,那么该代码必须先要转换成字节编译的代码(又称字节码)。这才是真正的代码对象。然而,它们不包含任何关于它们执行环境的信息,这便是可调用物存在的原因,它被用来包装一个代码对象并提供额外的信息。

还记得前面的小节中 UDF 的 udf.func_code 属性吗?呃,想不到吧?那就是代码对象。UDM 的udm.im_func 函数对象又是怎么一回事呢?因为那也是一个函数对象,所以他同样有它自己的udm.im_func.func_code 代码对象。这样的话,你会发现,函数对象仅是代码对象的包装,方法则是
给函数对象的包装。

28.3 可执行的对象声明和内建函数

Python 提供了大量的 BIF 来支持可调用/可执行对象,其中包括 exec 语句。这些函数帮助程序员执行代码对象,也可以用内建函数 complie()来生成代码对象。

可执行对象和内建函数

内建函数和语句 描述
callable(obj) 如果 obj 可调用,返回 True,否则返回 FALSE
compile(string,file, type) 从 type 类型中创建代码对象;file 是代码存放的地方(通常设为"")
eval(obj, globals=globals(),locals=locals()) 对 obj 进行求值,obj 是已编译为代码对象的表达式,或是一个字符串表达式;可以给出全局或者/和局部的名字空间
exec obj 执行 obj、单一的 python 语句或者语句的集合,也就是说格式是代码对象或者字符串;obj 也可以是一个文件对象(已经打开的有效 python 脚本中)
input(prompt=‘’) 等同于 eval(raw_input(prompt=”))

28.3.1 callable()

callable()是一个布尔函数,确定一个对象是否可以通过函数操作符(())来调用。如果函数可调用便返回 True,否则便是 False。这里有些对象及其对应的 callable 返回值

>>> callable(dir) # built-in function # 内建函数
True
>>> callable(1) # integer #整数
False
>>> def foo(): pass
...
>>> callable(foo) # user-defined function # 用户自定义函数
True

28.3.2 compile()

compile()函数允许程序员在运行时刻迅速生成代码对象,然后就可以用 exec 语句或者内建函数 eval()来执行这些对象或者对它们进行求值。一个很重要的观点是:exec 和 eval()都可以执行字符串格式的 Python 代码。当执行字符串形式的代码时,每次都必须对这些代码进行字节编译处理。compile()函数提供了一次性字节代码预编译,以后每次调用的时候,都不用编译了。

compile 的三个参数都是必需的,第一参数代表了要编译的 python 代码。第二个字符串,虽然是必需的,但通常被置为空串。该参数代表了存放代码对象的文件的名字(字符串类型)。compile 的通常用法是动态生成字符串形式的 Python 代码, 然后生成一个代码对象——代码显然没有存放在任何文件。最后的参数是个字符串,它用来表明代码对象的类型。有三个可能值:

‘eval’ 可求值的表达式[和 eval()一起使用]
‘single’ 单一可执行语句[和 exec 一起使用]
‘exec’ 可执行语句组[和 exec 一起使用]

1.可求值表达式

>>> eval_code = compile('100 + 200', '', 'eval')
>>> eval(eval_code)
300

2.单一可执行语句

>>> single_code = compile('print "Hello world!"', '', 'single')
>>> single_code
<code object ? at 120998, file "", line 0>
>>> exec single_code
Hello world!

3.可执行语句组

>>> exec_code = compile("""
... req = input('Count how many numbers? ')
... for eachNum in range(req):
... 	print eachNum
... """, '', 'exec')
>>> exec exec_code
Count how many numbers? 3
0
1
2

28.3.3 eval()

eval()对表达式求值,后者可以为字符串或内建函数 complie()创建的预编译代码对象。这是eval()第一个也是最重要的参数…这便是你想要执行的对象。第二个和第三个参数,都为可选的,分别代表了全局和局部名字空间中的对象。如果给出这两个参数,globals 必须是个字典,locals
可以是任意的映射对象,比如,一个实现了__getitem__()方法的对象。如果都没给出这两个参数,分别默认为 globals()和 locals()返回的对象,如果只传入了一个全局字典,那么该字典也作为 locals 传入。好了,我们一起来看看 eval():

>>> eval('932')
932
>>> int('932')
932

在这种情况下,eval()和 int()都返回相同的结果:整数 932。然而,它们采用的方式却不尽相同。内建函数 eval()接收引号内的字符串并把它作为 python 表达式进行求值。内建函数 int()接收代表整数的字符串并把它转换为整数。这只有在该字符串只由字符串 932 组成的时候才会成功,而该字符串作为表达式返回值 932,932 也是字符串”932”所代表的整数。

当我们用纯字符串表达式的时候,两者便不再相同了:

>>> eval('100 + 200')
300
>>> int('100 + 200') 
Traceback (innermost last):
File "", line 1, in ?
ValueError: invalid literal for int(): 100 + 200

在这种情况下,eval()接收一个字符串并把"100+200"作为表达式求值,当进行整数加法后,给出返回值 300。而对 int()的调用失败了,因为字符串参数不是能代表整数的字符串, 因为在字符串中有非法的文字,即,空格以及“+”字符。可以这样理解 eval()函数的工作方式:对表达式两端的引号视而不见,接着假设“如果我是 python 解释器,我会怎样去观察表达式呢?”,换句话说,如果以交互方式输入相同的表达式,解释器会做出怎么样的反应呢?按下回车后的结果应该和 eval()返回的结果相同。

28.3.4 exec

和 eval()相似,exec 语句执行代码对象或字符串形式的 python 代码。类似地,用 compile()预编译重复代码有助于改善性能,因为在调用时不必经过字节编译处理。exec 语句只接受一个参数,下面便是它的通用语法:
exec obj
被执行的对象(obj)可以只是原始的字符串,比如单一语句或是语句组,它们也可以预编译成一个代码对象(分别用’single’和’exec"参数)。下面的例子中,多个语句作为一个字符串发送给 exec:

>>> exec """
... x = 0
... print 'x is currently:', x
... while x < 5:
... 	x += 1
... 	print 'incrementing x to:', x
... """
x is currently: 0
incrementing x to: 1
incrementing x to: 2
incrementing x to: 3
incrementing x to: 4
incrementing x to: 5

最后, exec 还可以接受有效的 python 文件对象。如果我们用上面的多行代码创建一个叫xcount.py 的文件,那么也可以用下面的方法执行相同的代码

>>> f = open('xcount.py') # open the file
>>> exec f # execute the file
x is currently: 0
incrementing x to: 1
incrementing x to: 2
incrementing x to: 3
incrementing x to: 4
incrementing x to: 5
>>> exec f
#尝试再一次执行
>>>
#哦,失败了....为什么?

28.3.5 可执行对象语句和内建函数

注意一旦执行完毕,继续对 exec 的调用就会失败。呃,并不是真正的失败。。。只是不再做任何事,这或许让你感到吃惊。事实上,exec 已从文件中读取了全部的数据且停留在文件末尾(EOF)。当用相同文件对象对 exec 进行调用的时候,便没有可以执行的代码了,所以 exec 什么都不做,如同上面看见的行为。我们如何知道它在 EOF 呢?

我们用文件对象的 tell()方法来告诉我们处于文件的何处,然后用 os.path.getsize()来告诉我们 xcount.py 脚本有多大。这样你就会发现,两个数字完全一样:

>>>f.tell()# where are we in the file? # 我们在文件的什么地方?
116
>>>f.close() # close the file # 关闭文件
>>>from os.path import getsize
>>>getsize('xcount.py') # what is the file size? # 文件有多大?
116

如果想在不关闭和重新打开文件的情况下再次运行它,可以用 seek()到文件最开头并再次调用exec 了。比如,假定我们还没有调用 f.close(),那么我们可以这样做:

>>> f.seek(0) # rewind to beginning 倒会文件开头
>>> exec f
x is currently: 0
incrementing x to: 1
incrementing x to: 2
incrementing x to: 3
incrementing x to: 4
incrementing x to: 5
>>> f.close()

28.3.6 input()

内建函数 input()是 eval()和 raw_input()的组合,等价于 eval(raw_input())。类似于raw_input(),input()有一个可选的参数,该参数代表了给用户的字符串提示。如果不给定参数的话,该字符串默认为空串。从功能上看,input 不同于 raw_input(),因为 raw_input()总是以字符串的形式,逐字地返回用户的输入。input()履行相同的的任务;而且,它还把输入作为 python 表达式进行求值这意味着input()返回的数据是对输入表达式求值的结果:一个 python 对象。下面的例子会让人更加清楚:当用户输入一个列表时,raw_input()返回一个列表的字符串描绘,而 input()返回实际的列表:

>>> aString = raw_input('Enter a list: ') Enter a list: [ 123, 'xyz', 45.67 ]
>>> aString
"[ 123, 'xyz', 45.67 ]"
>>> type(aString)
<type 'str'>

上面用 raw_input()运行。正如你看见的,每样东西都是字符串。现在来看看当用 input()的时候会发生什么:

>>> aList = input('Enter a list: ') Enter a list: [ 123, 'xyz', 45.67 ]
>>> aList
[123, 'xyz', 45.67]
>>> type(aList)
<type 'list'>

虽然用户输入字符串,但是 input()把输入作为 python 对象来求值并返回表达式的结果。

28.3.7 使用 Python 在运行时生成和执行 Python 代码

示例一,动态生成和执行 Python 代码(loopmake.py)

#!/usr/bin/env python

dashes = '\n' + '-' * 50 # dashed line
exec_dict = {
'f': """ # for loop
for %s in %s:
	print %s
""",

's': """ # sequence while loop
%s = 0
%s = %s
while %s < len(%s):
	print %s[%s]
	%s = %s + 1
""",

'n': """ # counting while loop
%s = %d
while %s < %d:
	print %s
	%s = %s + %d
"""
}

def main():

	ltype = raw_input('Loop type? (For/While) ')
	dtype = raw_input('Data type? (Number/Seq) ')
	if dtype == 'n':
		start = input('Starting value? ')
		stop = input('Ending value (non-inclusive)? ')
		step = input('Stepping value? ')
		seq = str(range(start, stop, step))

	else:
		seq = raw_input('Enter sequence: ')

	var = raw_input('Iterative variable name? ')

	if ltype == 'f':
		exec_str = exec_dict['f'] % (var, seq, var)

	elif ltype == 'w':
		if dtype == 's':
			svar = raw_input('Enter sequence name? ')
			exec_str = exec_dict['s'] % \
				(var, svar, seq, var, svar, svar, var, var, var)
		elif dtype == 'n':
			exec_str = exec_dict['n'] % \
				(var, start, var, stop, var, var, var, step)

	print dashes
	print 'Your custom-generated code:' + dashes
	print exec_str + dashes
	print 'Test execution of the code:' + dashes
	exec exec_str
	print dashes

if __name__ == '__main__':
	main()

以下脚本执行的例子

Loop type? (For/While) f
Data type? (Number/Seq) n
Starting value? 0
Ending value (non-inclusive)? 10
Stepping value? 2
Iterative variable name? mylist

--------------------------------------------------
Your custom-generated code:
--------------------------------------------------
 # for loop
for mylist in [0, 2, 4, 6, 8]:
	print mylist

--------------------------------------------------
Test execution of the code:
--------------------------------------------------
0
2
4
6
8
--------------------------------------------------

示例二,Function Attributes (funcAttrs.py)

#!/usr/bin/env python

def foo():
	return True

def bar():
	'bar() does not do much'
	return True

foo.__doc__ = 'foo() does not do much'
foo.tester = '''
if foo():
	print 'PASSED'
else:
	print 'FAILED'
'''

for eachAttr in dir():
	obj = eval(eachAttr)
	if isinstance(obj, type(foo)):
		if hasattr(obj, '__doc__'):
			print '\nFunction "%s" has a doc string:\n\t%s' % (eachAttr, obj.__doc__)

		if hasattr(obj, 'tester'):
			print 'Function "%s" has a tester... executing'% eachAttr
			exec obj.tester
		else:
			print 'Function "%s" has no tester... skipping'% eachAttr
	else:
		print '"%s" is not a function' % eachAttr
$ python funcAttr.py
"__builtins__" is not a function
"__doc__" is not a function
"__file__" is not a function
"__name__" is not a function
Function "bar" has a doc string:
bar() does not do much
Function "bar" has no tester... skipping
Function "foo" has a doc string:
foo() does not do much
Function "foo" has a tester... executing
PASSED

28.4 执行其他(Python)程序

28.4.1 导入

在运行时刻,有很多执行另外 python 脚本的方法。正如我们先前讨论的,第一次导入模块会执行模块最高级的代码。不管你是否需要,这就是 python 导入的行为。提醒,只有属于模块最高级的代码才是全局变量,全局类,和全局函数声明。

核心笔记:当模块导入后,就执行所有的模块
当导入 python模块后,就执行所有的模块!当导入 python 模块后,会执行该模块!当你导入 foo 模块时候,它运行所有最高级别的(即没有缩进的)python 代码,比如,'main()’。如果 foo 含有 bar 函数的声明,那么便执行 def foo(…)。再问一次为什么会这样做呢?.. 由于某些原因,bar 必须被识别为 foo模块中一个有效的名字,也就是说 bar 在 foo 的名字空间中,其次,解释器要知道它是一个已声明的函数,就像本地模块中的任何一个函数。现在我们知道要做什么了,那么如何处理那些不想每次导入都执行的代码呢?缩进它,并放入 if __name__ == '__main__' 的内部。跟着应该是一个 if 语句,它通过检测__name__来确定是否要调用脚本,比如,“if__name__ =='__main__'”。如果相等的话,你的脚本会执行 main 内代码;否则只是打算导入这个脚本,那么可以在这个模块内对代码进行测试。

当导入 python 模块后,会执行该模块!当你导入 foo 模块时候,它运行所有最高级别的(即没有缩进的)python 代码,… 再问一次为什么会这样做呢?.. 由于某些原因,bar 必须被识别为 foo 模块中一个有效的名字,也就是说 bar 在 foo 的名字空间中,其次,解释器要知道它是一个已声明的函数,就像本地模块中的任何一个函数。现在我们知道要做什么了,那么如何处理那些不想每次导入都执行的代码呢?缩进它,并放入 if __name__ == '__main__' 的内部。

# import1.py
print 'loaded import1'
import import2

这里是 import2.py 的内容:

# import2.py
print 'loaded import2'

这是当我们导入 import1 时的输出

>>> import import1  
loaded import1
loaded import2

根据建议检测__name__值的迂回工作法,我们改变了 import1.py 和 import2.py 里的代码,这样的情况就不会发生了。这里是修改后的 import.py 版本:

# import1.py
import import2
if __name__ == '__main__':
	print 'loaded import1'

接着是 import2.py 的代码,以相同的方式修改:

# import2.py
if __name__ == '__main__'
	print 'loaded import2'

当从 python 中导入 import1 的时候,我们不再会得到任何输出

>>> import import1
>>>

这不意味着在任何的情况下,都该这样编写代码。在某些情况中,你可能想要显示输出来确定输入模块。这取决于你自身的情况。我们的目标是提供实效的编程例子来屏蔽副作用。

28.4.2 execfile()

显然,导入模块不是从另外的 python 脚本中执行 python 脚本最可取的方法。那也就不是导入过程。导入模块的副作用是导致最高级代码运行。这章一开始,我们描述了如何通过文件对象,使用 exec 语句来读取 python 脚本的内容并执行。
下面的代码给出了例子:

f = open(filename, 'r')
exec f 
f.close()

这 3 行可以调用 execfile()来换掉:

execfile(filename)

虽然上述代码执行了一个模块,但是仅可以在现有的执行环境下运行(比如,它自己的全局和局部的名字空间)。在某些情况下,可能需要用不同全局和局部的名字空间集合,而不是默认的集合来执行模块。execfile() 函数的语法非常类似于 eval()函数的。
execfile(filename, globals=globals(), locals=locals())

类似 eval(),globals 和 locals 都是可选的,如果不提供参数值的话,默认为执行环境的名字空间。如果只给定 globals,那么 locals 默认和globals 相同。如果提供 locals 值的话,它可以是任何映射对象[一个定义/覆盖了__getitem__()的对象]。

28.4.3 将模块作为脚本执行

python2.4 里加入了一个新的命令行选项(或开关),允许从 shell 或 DOS 提示符,直接把模块作为脚本来执行。当以脚本的方式来书写你的模块的时候,执行它们是很容易的。你可以使用命令行从你的工作目录调用你的脚本。

$ myScript.py # or $ python myScript.py

如果模块是标准库的一部分,安装在 site-packages 里,或者仅仅是包里面的模块,处理这样的模块就不是那么容易了,尤其是它们共享了已存在的同名 python 模块。举例来说,你想运行免费的 python web 服务器,以便创建和测试你自己的 web 页面和 CGI 脚本。你将必须在命令行敲入如下的东西:

$ python /usr/local/lib/python2x/CGIHTTPServer.py
Serving HTTP on 0.0.0.0 port 8000 ...

这是段很长的命令,如果它是第三方的,你不得不深入到 site-packages 去找到它真正定位的地方。如果没给出完全的路径名,可以从命令行运行一个模块,并让 python 的导入机制为我们做这种跑腿工作吗?答案是肯定的。我们可以用 python -c 命令行开关:

$ python -c "import CGIHTTPServer; CGIHTTPServer.test()"

该选项允许你指定你想要运行的 python 语句。虽然它可以这样工作,但问题是__name__模块不是‘__main__’…而是你正在使用的模块。在最后一行,解释器通过 import 装载了你的模块,并不是它当作脚本。因为如此,所有在if __name__ == '__main__'之下的代码是不会执行的,所以你不得不手动地调用模块的 test()函数,就如同前面我们所做的一样。所以我们想同时要两者的优点——能够在类库中执行作为脚本的模块而不是作为导入的模块。这就是-m 参数的动机。现在可以像这样运行脚本:

$ python -m CGIHTTPServer

这是不小的改进。尽管如此,还没有完全如预想那样实现特性。

28.5 执行其他(非 Python)程序

在 python 程序里我们也可以执行非 python 程序。这些程序包括了二进制可执行文件,其他的shell 脚本等等。所有的要求只是一个有效的执行环境,比如,允许文件访问和执行,脚本文件必须能访问它们的解释器(perl, bash,等等),二进制必须是可访问的(和本地机器的构架兼容)最终,程序员必须考虑 python 脚本是否必须和其他将要执行的程序通信。有些程序需要输入,而有的程序返回输出以及执行完成时的错误代码,也许有的两者都做。针对不同的环境,python 提供了各种执行非 python 程序的方法。在本节讨论的所有函数都可以在 os 模块中找到。

28.5.1 执行外部程序的 os 模块函数

(u 只对 unix 有效, w 只对 windows 有效)

os 模块函数 描述
system(cmd) 执行程序 cmd(字符串),等待程序结束,返回退出代码(windows 下,始终为 0)
fork() 创建一个和父进程并行的子进程[通常来说和 exec*()一起使用];返回两次…一次给父进程一次给子进程
execl(file, arg0,arg1,…) 用参数列表 arg0, arg1 等等执行文件
execv(file, arglist) 除了使用参数向量列表,其他的和 execl()相同
execle(file, arg0,arg1,… env) 和 execl 相同,但提供了环境变量字典 env
execve(file,arglist, env) 除了带有参数向量列表,其他的和 execle()相同
execlp(cmd, arg0,arg1,…) 于 execl()相同,但是在用户的搜索路径下搜索完全的文件路径名
execvp(cmd, arglist) 除了带有参数向量列表,与 execlp()相同
execlpe(cmd, arg0, arg1,… env) 和 execlp 相同,但提供了环境变量字典 env
execvpe(cmd,arglist, env) 和 execvp 相同,但提供了环境变量字典 env
spawn* a (mode, file, args[, env]) spawn*()家族在一个新的进程中执行路径,args 作为参数,也许还有环境变量的字典 env;模式(mode)是个显示不同操作模式的魔术。
wait() 等待子进程完成[通常和 fock 和 exec*()一起使用] ○U
waitpid(pid,options) 等待指定的子进程完成[通常和 fock 和 exec*()一起使用] ○U
popen(cmd, mode='r',buffering=-1) 执行字符串 cmd,返回一个类文件对象作为运行程序通信句柄,默认为读取模式和默认系统缓冲
startfile (path) 用关联的应用程序执行路径 W

28.5.2 os.system()

我们列表中的第一个函数是 system(),一个非常简单的函数,接收字符串形式的系统命令并执行它。当执行命令的时候,python 的运行是挂起的。当我们的执行完成之后,将会以 system()的返回值形式给出退出状态,python 的执行也会继续。

system()保留了现有的标准文件,包括标准的输出,意味着执行任何的命令和程序显示输出都会传到标准输出上。这里要当心,因为特定应用程序比如公共网关接口(CGI),如果将除了有效的超文本标示语言(HTML)字符串之外的输出,经过标准输出发送回客户端,会引起 web 浏览器错误。

system()通常和不会产生输出的命令一起使用,其中的一些命令包括了压缩或转换文件的程序,挂载磁盘到系统的程序,或其他执行特定任务的命令—通过退出状态显示成功或失败而不是通过输入和/或输出通信。通常的约定是利用退出状态,0 表示成功,非零表示其他类型的错误。为了给出一个例子,我们执行了两个从交互解释器中获取程序输入的命令,这样你便可以观察system()是如何工作的

>>> import os
>>> result = os.system('cat /etc/motd') 
Have a lot of fun...
>>> result
0
>>> result = os.system('uname -a')
Linux solo 2.2.13 #1 Mon Nov 8 15:08:22 CET 1999 i586 unknown
>>> result
0

28.5.3 os.popen()

popen()函数是文件对象和 system()函数的结合。它工作方式和 system()相同,但它可以建立一个指向那个程序的单向连接,然后如访问文件一样访问这个程序。如果程序要求输入,那么你要用’w’模式写入那个命令来调用 popen()。你发送给程序的数据会通过标准输入接收到。同样地,'r’模式允许 spawn 命令,那么当它写入标准输出的时候,你就可以通过类文件句柄使用熟悉的 file 对象的 read*()方法来读取输入。就像对于文件,当使用完毕以后,你应当 close()连接。在上面其中一个使用 system()的例子中,我们调用了 unix 程序 uname 来来给我们提供机器和使用的操作系统的相关信息。该命令产生了一行输出,并直接写到屏幕上。如果想要把该字符串读入变量中并执行内部操作或者把它存储到日志文件中,我们可以使用 popen()。实际上,代码如下所示:

>>> import os
>>> f = os.popen('uname -a')
>>> data = f.readline()
>>> f.close()
>>> print data,
Linux solo 2.2.13 #1 Mon Nov 8 15:08:22 CET 1999 i586 unknown

如你所见,popen()返回一个类文件对象;注意 readline(),往往,保留输入文本行尾的 newline字符。

28.5.4 os.fork(), os.exec*(),os.wait*()(了解)

fork()采用称为进程的单一执行流程控制,如果你喜欢的话,可称之为创建“岔路口”。有趣的事情发生了:用户系统同时接管了两个 fork——也就是说让用户拥有了两个连续且并行的程序。(不用说,它们运行的是同一个程序,因为两个进程都是紧跟在 fork()调用后的下一行代码开始执行的)。调用fork()的原始进程称为父进程,而作为该调用结果新创建的进程则称为子进程。当子进程返回的时候,其返回值永远是 0;当父进程返回时,其返回值永远是子进程的进程标识符(又称进程 ID,或 PID)(这样父进程就可以监控所有的子进程了)PID 也是唯一可以区分他们的方式!

我们提到了两个进程会在调用 fork()后立刻运行。因为代码是相同的,如果没有其他的动作,我们将会看到同样的执行结果。而这通常不是我们想要的结果。创建另外一个进程的主要目的是为了运行其他程序,所以我们必须在父进程和子进程返回时采取分流措施。正如上面我们所说,它们的 PID 是不同的,而这正是我们区分它们的方法。对于那些有进程管理经验的人来说,接下来的这段代码是再熟悉不过了。但是,如果你是新手的话,一开始就弄懂它是如何工作的可能就有点困难了,但是一旦你懂了,就会体会到其中的奥妙。

ret = os.fork() 	# spawn 2 processes, both return #产生两个进程,都返回
if ret == 0:		# child returns with PID of 0 #子进程返回的 PID 是 0
	child_suite 	# child code #子进程的代码
else:				# parent returns with child's PID #父进程返回是子进程的 PID
	parent_suite	# parent code #父进程的代码

在代码第一行便调用了 fork()。现在子进程和父进程同时在运行。子进程本身有虚拟内存地址空间的拷贝,以及一份父进程地址空间的原样拷贝。-----是的,两者几乎都是相同的。fork()返回两次,意味着父进程和子进程都返回了。你或许会问,如果它们两个同时返回,如何区分两者呢?
当父亲返回的时候,会带有进程的 PID。而当子进程返回的时候,其返回值为 0。这就是区分两个进程的方法。

利用 if-else 语句,我们能给子进程(比如,if 子句)和父进程(else 子句)指定各自的执行代码。在子进程的代码中,我们可以调用任何 exec*()函数来运行完全不同的程序,或者同一个程序中的其他的函数(只要子进程和父进程用不同的路径执行)。普遍做法是让子进程做所有的脏活,而父进程耐心等来子进程完成任务,或继续执行,稍后再来检查子进程是否正常结束。所有的 exec*()函数装载文件或者命令,并用参数列表(分别给出或作为参数列表的一部分)来执行它。如果适用的话,也可以给命令提供环境变量字典。这些变量普遍用于给程序提供对当前执行环境的精确描述。其中一些著名的变量包括用户的名字,搜索路径,现在的 shell,终端类型,本地化语言,机器类型,操作系统名字等等。

所有版本的 exec*()都会用给定文件作为现在要执行的程序取代当前(子)进程的 Python 解释器。和 system()不一样,对于 Python 来说没有返回值(因为 Python 已经被替代了)。如果因为某种原因,程序不能执行,那么 exec*()就会失败,进而导致引发异常。接下来的代码在子进程中开始了一个称为“xbill"的可爱小巧的游戏,而父进程继续运行 Python解释器。因为子进程从不返回,所以无需去顾虑调用 exec*()后的子进程码。注意该命令也是参数列表中的必须的第一个参数。

ret = os.fork()
if ret == 0:	# child code #子进程代码
	execvp('xbill', ['xbill'])
else:# parent code #父进程代码 
	os.wait()

在这段代码中,还可以看到对 wait()的调用。当子进程执行完毕,需要它们的父进程进行扫尾工作。这个任务,称为”收获孩子”(reaping a child),可以用 wati*()函数完成。紧跟在 fork()之后,父进程可以等待子进程完成并在那进行扫尾。父进程也可以继续运行,稍后再扫尾,同样也是用 wait*()函数中的一个。不管父进程选择了那个方法,该工作都必须进行。当子进程完成执行,还没有被收获的时候,它进入了闲置状态,变成了著名的僵尸进程。在系统中,应该尽量把僵尸进程的数目降到最少,因为在这种状态下的子进程仍保留着在存活时期分配给它们的系统资源,而这些资源只能在父进程收获它们之后才能释放掉。调用 wait()会挂起执行(比如,waits),直到子进程(其他的子进程)正常执行完毕或通过信号终止。wait()将会收获子进程,释放所有的资源。如果子进程已经完成,那么 wait()只是进行些收获的过程。waitpid()具有和 wait()相同的的功能,但是多了一个参数 PID(指定要等待子进程的进程标识符),以及选项(通常是零或用‘OR’组成的可选标志集合)

28.5.5 os.spawn*()(了解)

函数 spawn*()家族和 fork,exec*()相似,因为它们在新进程中执行命令;然而,你不需要分别调用两个函数来创建进程,并让这个进程执行命令。你只需调用一次 spawn*()家族。由于其简单性,你放弃了“跟踪”父进程和子进程执行的能力;该模型类似于在线程中启动函数。还有点不同
的是你必须知道传入 spawn*()的魔法模式参数。在其他的操作系统中(尤其是嵌入式实时操作系统[RTOS]),spawn*()比 fork()快很多。不是这种情况的操作系统通常使用写实拷贝(copy-on-write)技术。

28.5.6 subprocess 模块

在 python2.3 出来之后,一些关于 popen5 模块的工作开始展开。一开始该命名继承了先前popen*()函数的传统,但是并没有延续下来,该模块最终被命名为 subproess,其中一个类叫 Popen,集中了我们在这章讨论的大部分面向进程的函数。同样也有名为 **call()**的便捷函数,可以轻易地取代了 os.system()。在 python2.4 中,subprocess 初次登场。下面就是演示该模块的例子:

替换 os.system()

>>> from subprocess import call
>>> import os
>>> res = call(('cat', '/etc/motd'))
Linux starship 2.4.18-1-686 #4 Sat Nov 29 10:18:26 EST 2003 i686
GNU/Linux
>>> res
0

取代 os.popen()
创建 **Popen()**实例的语法只比调用 os.popen()函数复杂了一点

>>> from subprocess import Popen, PIPE
>>> f = Popen(('uname', '-a'), stdout=PIPE).stdout
>>> data = f.readline()
>>> f.close()
>>> print data,
Linux starship 2.4.18-1-686 #4 Sat Nov 29 10:18:26 EST 2003 i686
GNU/Linux
>>> f = Popen('who', stdout=PIPE).stdout
>>> data = [ eachLine.strip() for eachLine in f ]
>>> f.close()
>>> for eachLine in data:
... 	print eachLine

28.6 结束执行

当程序运行完成,所有模块最高级的语句执行完毕后退出,我们便称这是干净的执行。可能有很多情况,需要从 python 提前退出,比如某种致命错误,或是不满足继续执行的条件的时候。在 python 中,有各种应对错误的方法。其中之一便是通过异常和异常处理。另外一个方法便是建造一个“清扫器”方法,这样便可以把代码的主要部分放在 if 语句里,在没有错误的情况下执行,因而可以让错误的情况“正常地“终结。然而,有时也需要在退出调用程序的时候,返回错误代码以表明发生何种事件。

sys.exit() and SystemExit
立即退出程序并返回调用程序的主要方式是 sys 模块中的 exit()函数。sys.exit()的语法为:

sys.exit(status=0)

当调用 sys.exit()时,就会引发 systemExit()异常。除非对异常进行监控(在一个 try 语句和合适的 except 子句中),异常通常是不会被捕捉到或处理的,解释器会用给定的状态参数退出,如果没有给出的话,该参数默认为 0。System Exit 是唯一不看作错误的异常。它仅仅表示要退出python的愿望。sys.exit()经常用在命令调用的中途发现错误之后,比如,如果参数不正确,无效,或者参数数目不正确。下面的例子 14.4(args.py)仅仅是一个测试脚本,在正确执行之前需要给出确定数目的参数。

28.7 各种操作系统接口

各种 os 模块属性

os 模块属性 描述
uname() 获得系统信息(主机名,操作系统版本,补丁级别, 系统构架等等)
getuid()/setuid(uid) 获取/设置现在进程的真正的用户 ID
getpid()/getppid() 获取真正的现在/父进程 ID(PID)
getgid()/setgid(gid) 获取/设置现在进程的群组 ID
getsid()/setsid() 获取会话 ID(SID)或创建和返回新的 SID。
umask(mask) 设置现在的数字 unmask,同时返回先前的那个(mask 用于文件许可) ○
getenv(ev)/putenv(ev, value),environ 获取和设置 环境变量 ev 的值;os.envion 属性是描述当前所有环境变量的字典 ○
geteuid()/setegid() 获取/设置当前进程的有效用户 ID(GID)
getegid()/setegid() 获取/设置当前进程的有效组 ID(GID)
getpgid(pid)/setpgid(pid, pgrp) 获取和设置进程 GID 进程 PID;对于 get,如果 pid 为 0,便返回现在进程的进程 GID
getlogin() 返回运行现在进程的用户登录
times() 返回各种进程时期的元组 ○
strerror(code) 返回和错误代码对应的错误信息
getloadavg() 返回代表在过去 1,5,15 分钟内的系统平均负载值的元组。

执行环境相关模块

模块 描述
atexit 注册当 python 解释器退出时候的执行句柄
popen2 提供额外的在 os.popen 之上的功能:(提供通过标准文件和其他的进程交 互的能力;对于 python2.4 和更新的版本,使用 subpross)
commands 提供额外的在 os.system 之上的功能:把所有的程序输出保存在返回的字符串中(与输出到屏幕的相反);对于 python2.4 和更新的版本,使用 subpross ○
getopt 在这样的应用程序中的处理选项和命令行参数
site 处理 site-specific 模块或包
platform 底层平台和架构的属性
subprocess 管理(计划替代旧的函数和模块,比如 os.system(), os.spawn*(),os.popen*(), popen2., command.)

你可能感兴趣的:(Python基础,python,开发语言)