Python编程基础之十三Python执行环境

一、简介

       在python中有多种运行外部程序的方法,比如运行操作系统命令,或另外的python脚本,或执行一个磁盘上的文件,或通过网络来运行文件,这完全取决于用户调用。有些特定的执行场景:当前脚本继续运行、创建和管理子进程、执行外部命令或程序、执行需要输入的命令、通过网络来调用命令、执行命令来创建需要处理的输出、执行其他的 Python 脚本、执行一系列动态生成Python语句导入 Python 模块(和执行它顶层的代码)。
        python中,内建和外部模块都可以提供上述各种功能。执行其他程序的能力不仅大大增强了 python 脚本的威力,也节约了资源,因为重复实现这些代码肯定是不合逻辑的,更是浪费时间和人力。python 给当前脚本环境提供了许多执行程序或者外部命令的机制。

二、详解

1、可调用对象

       许多的python对象都是可调用的,即能通过函数操作符()来调用的对象。要调用可调用对象,函数操作符得紧跟在可调用对象之后。可调用对象可以通过函数式编程接口来进行调用,如apply()、filter()、map()以及reduce(),Python有4种可调用对象:函数、方法、类以及一些类的实例。
(1)函数
        Python中有3种不同类型函数对象。
        第一种是内建函数(BIFs),BIF是用c/c++写的,编译过后放入python解释器,然后把它们作为第一(内建)名字空间的一部分加载进系统。这些函数在_bulitin_模块里,并作为__builtins__模块导入到解释器中。内建函数属性有bif.__doc__、bif.__name__、bif.__self__、bif.__module__等,用dir()列出函数的所有属性。
        第二种是用户自定义的函数(User-Defined Function),通常是用python写的,定义在模块的最高级,因此会作为全局名字空间的一部分(一旦创建好内建名字空间)装载到系统中。可以用func_closure属性来钩住在其他地方定义的属性。从内部机制来看,用户自定义的函数是“函数“类型的,用type()表明是<type 'function'>。
        第三种是lambda表达式,lambda表达式和用户自定义对函数相比,略有不同。虽然它们也是返回一个函数对象,但是lambda表达式不是用def语句创建的,而是用lambda关键字。因为lambda表达式没有给命名绑定的代码提供基础结构,所以要通过函数式编程接口来调用,或把它们的引用赋值给一个变量,然后就可以直接调用或者再通过函数来调用。变量仅是个别名,并不是函数对象的名字。通过lambda来创建函数的对象除了没有命名之外,享有和用户自定义函数相同的属性。
(2)方法
         用户自定义方法是被定义为类的一部分的函数,许多python数据类型,比如列表和字典,也有方法,这些被称为内建方法。为了进一步说明“所有权“的类型,方法通过对象的名字和句点属性标识进行命名。
         第一种是内建方法,如bim.__doc__、bim.__name__、bim.__self__等,只有内建类型(BIT)有内建方法(BIM),对于内建方法,type()工厂函数给出了和BIF相同的输出:<type 'builtin_function_or_method'>。BIM和BIF两者也都享有相同属性,不同之处在于BIM的__self__属性指向一个Python对象,而BIF指向None。对于类和实例,都能以该对象为参数,通过内建函数dir()来获得他们的数据和方法属性。
         第二种是用户自定义的方法,它包含在类定义之中,只是拥有标准函数的包装,仅有定义它们的类可以使用。如果没有在子类定义中被覆盖掉,也可以通过子类实例来调用它们。 用户自定义方法与类对象是关联的(非绑定方法),但是只能通过类的实例来调用(绑定方法)。无论方法是否绑定,所有的方法都是相同的类型:<type 'instancemethod'>。
(3)类
      利用类的可调用性来创建实例,“调用”类的结果便是创建了实例。类有默认构造函数。该函数什么都不做,基本上只有一个pass语句。程序员可以通过实现__int__()方法,来自定义实例化过程,实例化调用的任何参数都会传入到构造函数里。
(4)类的实例
        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 am callable!Whit args:", args
... 
>>> obj = C()
>>> obj
<__main__.C object at 0x7f2116676610>
>>> callable(obj)
True
>>> obj()
I am callable!Whit args: ()
>>> obj(3)
I am callable!Whit args: (3,)
>>> obj(3, "data")
I am callable!Whit args: (3, 'data')
       记住只有定义类的时候实现了__call__方法,类的实例才能成为可调用的。

2、代码对象

       可调用的对象是python执行环境里最重要的部分,然而他们只是冰山一角。python语句、赋值、表达式甚至还有模块构成了更宏大的场面。这些可执行对象无法像可调用物那样被调用。更确切地说,这些对象只是构成可执行代码块的拼图的很小一部分,而这些代码块被称为代码对象。
        每个可调用物的核心都是代码对象,由语句、赋值、表达式以及其他可调用物组成。察看一个模块意味着观察一个较大的、包含了模块中所有代码的对象。一般说来,代码对象可以作为函数或者方法调用的一部分来执行,也可用exec语句或内建函数eval()来执行。从整体上看,一个python模块的代码对象是构成该模块的全部代码。
         如果要执行python代码,那么该代码必须先要转换成字节编译的代码(又称字节码),这才是真正的代码对象。然而,它们不包含任何关于它们执行环境的信息,这便是可调用物存在的原因,它被用来包装一个代码对象并提供额外的信息。函数对象仅是代码对象的包装,方法则是给函数对象的包装。

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

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

(1)callable()
        callable()是一个布尔函数,确定一个对象是否可以通过函数操作符(())来调用。如果函数可调用便返回 True,否则便是False。
>>> callable(dir)
True
>>> callable(1)
False
>>> def foo():pass
... 
>>> callable(foo)
True
>>> class C(object):pass
... 
>>> callable(C)
True
(2)compile()
        compile 的通常用法是动态生成字符串形式的Python代码,然后生成一个代码对象。compile()函数允许程序员在运行时刻迅速生成代码对象,然后就可以用exec语句或者内建函数eval()来执行这些对象或者对它们进行求值。exec和eval()都可以执行字符串格式的Python代码。当执行字符串形式的代码时,每次都必须对这些代码进行字节编译处理。compile()函数提供了一次性字节代码预编译,以后每次调用的时候,都不用编译了。compile的三个参数都是必需的,第一参数代表了要编译的python代码;第二个是字符串,虽然是必需的,但通常被置为空串,该参数代表了存放代码对象的文件的名字(字符串类型);第三个参数是个字符串,它用来表明代码对象的类型,有三个可能值:'eval', 可求值的表达式[和eval()一起使用];'single',单一可执行语句[和exec一起使用];'exec',可执行语句组[和exec一起使用]。
>>> eval_code = compile('100 + 200', '', 'eval')
>>> eval(eval_code)
300
>>> single_code = compile('print "Hello world!"', '', 'single')
>>> single_code
<code object <module> at 0x7f947accabe8, file "", line 1>
>>> exec single_code
Hello world!
>>> exec_code = compile("""
... req = input('Count how many numbers? ')
... for eachNum in range(req):
...     print eachNum
... """, '', 'exec')
>>> exec exec_code
Count how many numbers? 4
0
1
2
3
(3)eval()
        eval()对表达式求值,后者可以为字符串或内建函数 complie()创建的预编译代码对象。这是eval()第一个也是最重要的参数。第二个和第三个参数,都为可选的,分别代表了全局和局部名字空间中的对象。如果给出这两个参数,globals必须是个字典,locals可以是任意的映射对象。
>>> eval("932")
932
>>> int("932")
932
       eval()和int()都返回相同的结果,然而它们采用的方式却不尽相同。内建函数eval()接收引号内的字符串并把它作为 python 表达式进行求值。内建函数 int()接收代表整数的字符串并把它转换为整数。这只有在该字符串只由数字字符串组成的时候才会成功,当用纯字符串表达式的时候,两者便不再相同了:
>>> eval('100 + 200')
300
>>> int('100 + 200')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '100 + 200'
        eval()接收一个字符串并把"100+200"作为表达式求值,当进行整数加法后,给出返回值300。而对int()的调用失败了,因为字符串参数不是能代表整数的字符串,在字符串中有非法的文字,即空格以及“+”字符。
(4)exec
        和eval()相似,exec语句执行代码对象或字符串形式的python代码。类似地,用compile()预编译重复代码有助于改善性能,因为在调用时不必经过字节编译处理。exec语句只接受一个参数: exec obj,被执行的对象(obj)可以只是原始的字符串,比如单一语句或是语句组,它们也可以预编译成一个代码对象(分别用'single'和'exec"参数)。
>>> exec """
... x = 0
... print 'x is currently:', x
... while x < 5:
...     x += 1
...     print "x is:", x
... """
x is currently: 0
x is: 1
x is: 2
x is: 3
x is: 4
x is: 5
        exec还可以接受有效的python文件对象,f = open('xcount.py');exec f。
(5)input()
       内建函数input()是eval()和raw_input()的组合,等价于eval(raw_input()),input()有一个可选的参数,该参数代表了给用户的字符串提示,如果不给定参数,该字符串默认为空串。
        input()不同于raw_input(),因为 raw_input()总是以字符串的形式返回用户的输入,而input()不仅输出还把输入作为Python表达式求值,这意味input()返回的数据是对输入表达式求值的结果:一个Python对象。
>>> aString = raw_input("Enter a list:")
Enter a list:[123, "xyz", 45.67]
>>> type(aString)
<type 'str'>
>>> aString
'[123, "xyz", 45.67]'
>>> 
>>> aList = input("Enter a list:")
Enter a list:[123, "xyz", 45.67]
>>> type(aList)
<type 'list'>
>>> aList
[123, 'xyz', 45.670000000000002]
>>> aResult = input("Enter string:")
Enter string:3+5
>>> aResult
8

4、执行其他(Python)程序

        其他程序分类为python程序和其他所有的非python程序,后者包括了二进制可执行文件或其他脚本语言的源代码。先讨论如何运行其他的python程序,然后是如何用os模块调用外部程序。
(1)导入
       在运行时刻,有很多执行另外python脚本的方法。第一次导入模块,不管是否需要,都会执行模块最高级的代码。只有属于模块最高级的代码才是全局变量、全局类和全局函数声明。如何处理那些不想每次导入都执行的代码:缩进它,并放入if __name__ == '__main__' 的内部,它通过检测__name__来确定是否要调用脚本,如果相等的话,脚本会执行main内代码;否则只是打算导入这个脚本,那么可以在这个模块内对代码进行测试。
(2)execfile()
        导入模块不是从另外的python脚本中执行python脚本最可取的方法,导入模块的副作用是导致最高级代码运行。可以通过文件对象,使用exec语句来读取 python 脚本的内容并执行。
f = open(filename, 'r')
exec f
f.close()
等价于:execfile(filename)
       execfile()函数的语法非常类似于eval()函数的:execfile(filename, globals=globals(), locals=locals()),类似eval()、globals和locals都是可选的,如果不提供参数值的话,默认是执行环境的名字空间。如果只给定 globals,那么locals默认和globals相同。如果提供locals值的话,它可以是任何映射对象[一个定义/覆盖了__getitem__()的对象]。
(3)将模块作为脚本执行
       python2.4里加入了一个新的命令行选项(或开关),允许从shell或DOS提示符,直接把模块作为脚本来执行。当以脚本的方式来书写模块的时候,执行它们是很容易的。可以使用命令行从工作目录调用脚本:$ myScript.py或者$python myScript.py。

5、执行其他(非 Python)程序

       在python程序里,也可以执行非python程序。这些程序包括了二进制可执行文件、其他的shell脚本等等。所有的要求只是一个有效的执行环境,比如允许文件访问和执行,脚本文件必须能访问它们的解释器(perl、bash等等),二进制必须是可访问的(和本地机器的构架兼容)。
       python 提供了各种执行非python程序的方法,大部分函数都可以在os模块中找到。当调用import os的时候,python会装载正确的模块,不需要直接导入特定的操作系统模块。最新的有个subprocess模块,可以作为 os模块中部分函数很好的替代品。

(1)os.system()
        system()接收字符串形式的系统命令并执行它,当执行命令的时候python的运行是挂起的。当执行完成之后,将会以system()的返回值形式给出退出状态,python的执行也会继续。
        system()保留了现有的标准文件,包括标准的输出,意味着执行任何的命令和程序显示输出都会传到标准输出上。通常的约定是利用退出状态,0表示成功,非零表示其他类型的错误。
>>> import os
>>> result = os.system('cat /etc/modet &> /dev/null')
>>> result
256
>>> result = os.system('cat /etc/modet')
cat: /etc/modet: 没有那个文件或目录
>>> result = os.system('uname -a')
Linux localhost.localdomain 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
>>> result
0
(2)os.popen()
       open()函数是文件对象和system()函数的结合,它工作方式和 system()相同。但它可以建立一个指向那个程序的单向连接,然后如访问文件一样访问这个程序。如果程序要求输入,那么用'w'模式写入那个命令来调用 popen(),发送给程序的数据会通过标准输入接收到。同样地,'r'模式允许spawn命令,那么当它写入标准输出的时候,就可以通过类文件句柄使用熟悉的file对象的read*()方法来读取输入。就像对于文件,当使用完毕以后,用close()关闭。
>>> import os
>>> f = os.popen('uname -a')
>>> data = f.readline()
>>> f.close()
>>> print data,
Linux localhost.localdomain 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
        popen()返回一个类文件对象,其中readline()往往保留输入文本行尾的newline字符。
(3)os.fork(), os.exec*(),os.wait*()
         fork()采用称为进程的单一执行流程控制,用户系统同时接管了两个fork,也就是说让用户拥有了两个连续且并行的程序( 它们运行的是同一个程序两个进程都是紧跟在 fork()调用后的下一行代码开始并行执行的)。调用fork()的原始进程称为父进程,而作为该调用结果新创建的进程则称为子进程。当子进程返回的时候,其返回值永远是0;当父进程返回时,其返回值永远是子进程的进程标识符(又称进程 ID或PID)。这样父进程就可以监控所有的子进程了,PID也是唯一可以区分它们的方式。创建另外一个进程的主要目的是为了运行其他程序,所以必须在父进程和子进程返回时采取分流措施,通过PID区分它们。
ret = os.fork()  #产生两个进程,都返回
if ret == 0:        #子进程返回的PID是0
    child_suite  #子进程的代码
else:                #父进程返回是子进程的 PID
    parent_suite  #父进程的代码
       调用了 fork(),子进程和父进程同时运行,子进程本身有虚拟内存地址空间的拷贝以及一份父进程地址空间的原样拷贝。子进程返回的时候,其返回值PID为0,父进程非0。在子进程的代码中,可以调用任何exec*()函数来运行完全不同的程序或者同一个程序
中的其他的函数(只要子进程和父进程用不同的路径执行)。普遍做法是让子进程做一件事,而父进程耐心等来子进程完成任务,或继续执行,稍后再来检查子进程是否正常结束。
ret = os.fork()
if ret == 0:
    execvp('xbill', ['xbill'])      # 子进程代码
else:
    os.wait()                          #父进程代码
        当子进程完成执行,父进程还没有收获它的时候,它进入了闲置状态,变成了著名的僵尸进程。父进程调用 wait()会挂起执行,直到子进程(其他的子进程)正常执行完毕或通过信号终止。wait()将会收获子进程,释放所有的资源。如果子进程已经完成,那么 wait()只是进行收获的过程。waitpid()具有和wait()相同的的功能,但是多了一个参数PID(指定要等待子进程的进程标识符)以及选项(通常是零或用‘OR’组成的可选标志集合)。
(4)os.spawn*()
       函数spawn*()家族和fork、exec*()相似,因为它们在新进程中执行命令。然而,不需要分别调用两个函数来创建进程,并让这个进程执行命令,只需调用一次spawn*()家族。该模型类似于在线程中启动函数,但必须知道传入spawn*()的模式参数。在其他的操作系统中(尤其是嵌入式实时操作系统)spawn*()比fork()快很多。不是这种情况的操作系统通常使用写实拷贝(copy-on-write)技术。
(5)subprocess模块
       subprocess模块中的call()便捷函数,可以轻易地取代了os.system()。   
>>> from subprocess import call
>>> ret = call(("cat", "/etc/resolv.conf"))
# Generated by NetworkManager
search 8.8.8.8
nameserver 192.168.10.11
>>> ret
0
        取代os.popen():
>>> f = Popen(('uname', '-a'), stdout=PIPE).stdout
>>> data = f.readline()
>>> f.close()
>>> print data,
Linux localhost.localdomain 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
>>> f = Popen('who', stdout=PIPE).stdout
>>> data = [ eachLine.strip() for eachLine in f ]
>>> f.close()
>>> for eachLine in data:
...     print eachLine
... 
aoyang   tty1         2015-03-26 09:24 (:0)
aoyang   pts/0        2015-03-26 09:41 (:0.0)
aoyang   pts/1        2015-03-26 11:07 (:0.0)
(6)相关函数

6、结束执行

        当程序运行完成,所有模块最高级的语句执行完毕后退出,便称这是干净的执行。可能有很多情况,需要从python提前退出,比如某种致命错误或是不满足继续执行的条件的时候。在python中,有各种应对错误的方法,其中之一便是通过异常和异常处理,另外一个方法便是建造一个“清扫器”方法,这样便可以把代码的主要部分放在 if 语句里,在没有错误的情况下执行,因而可以让错误的情况“正常地“终结。然而,有时也需要在退出调用程序的时候,返回错误代码以表明发生何种事件。
(1)sys.exit() and SystemExit
      sys模块中的exit()函数是立即退出程序并返回调用程序,sys.exit()的语法为sys.exit(status=0)。当调用sys.exit()时,就会引发systemExit()异常。除非对异常进行监控(在一个 try 语句和合适的 except 子句中),异常通常是不会被捕捉到或处理的,解释器会用给定的状态参数退出,如果没有给出的话,该参数默认为 0。System Exit是唯一不看作错误的异常。它仅仅表示要退出python的 望。
        sys.exit()经常用在命令调用的中途发现错误之后,比如如果参数不正确、无效或者参数数目不正确。调用sys.exit()使python解释器退出。exit()的任何整数参数都会以退出状态返回给调用者,该值默认为 0。
(2)sys.exitfunc()
        sys.exitfunc()默认是不可用的,但可以改写它以提供额外的功能。当调用了sys.exit()并在解释器退出之前,就会用到这个函数了。这个函数不带任何参数的,所以创建的函数也应该是无参的。如果sys.exitfunc已经被先前定义的exit函数覆盖了,最好的方法是把这段代码作为exit()函数的一部分来执行。一般说来,exit函数用于执行某些类型的关闭活动,比如关闭文件和网络连接,最好用于完成维护任务,比如释放先前保留的系统资源。
import sys
prev_exit_func = getattr(sys, 'exitfunc', None)
def my_exit_func(old_exit = prev_exit_func):
    # perform cleanup 进行清理
    pass
if old_exit is not None and callable(old_exit):
    old_exit()
sys.exitfunc = my_exit_func
(3)os._exit()
       os模块的_exit()函数不应该在一般应用中使用(平台相关,只适用特定的平台,比如基于 Unix的平台以及 Win32 平台),其语法为:os._exit(status),这个函数提供的功能与sys.exit()和 sys.exitfunc()相反,根本不执行任何清理便立即退出python。与sys.exit()不同,状态参数是必需的。通过sys.exit()退出是退出解释器的首选方法。
(4)os.kill()
       os模块的kill()函数模拟传统的unix函数来发送信号给进程。kill()参数是进程标识数(PID)和想要发送到进程的信号。发送的典型信号为SIGINT、SIGQUIT或更彻底的SIGKILL,来使进程终结。

7、各种操作系统接口及模块

         各种os模块属性:


      除了os和sys模块,还有与执行环境相关的模块:

三、总结

(1)Python与shell、操作系统的交互须简单的了解,在实际开发中比较有用,比如退出程序释放资源、调用系统接口、输入输出不定长参数等。
(2)Python的系统环境模块也在不断的更新,可以参考最新的文档。
(3)若有不足,请留言,在此先感谢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乌托邦2号

博文不易,支持的请给予小小打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值