zoukankan      html  css  js  c++  java
  • Python sys.md

    sys-System-specific Configuration

    Interpreter Settings

    sys包含用于访问解释器的编译时或运行时配置设置的属性和函数。

    Build-time Version Information

    • sys.version:一个字符串,包含Python解释器的版本号以及版本号和使用的编译器的额外信息。交互式解释器启动时将显示此字符串。不用从它提取版本信息,使用version_info和platform模块所提供的函数。
    • sys.version_info:一个包含版本编号五个组件的元组:major、minor、micro、releaselevel 和serial。除releaselevel之外的所有值都是整数;释放级别是'alpha','beta','candidate'或'final'。对应于Python版本2.0的version_info值是(2,0,0,'final',0)。组件也可按名称访问,所以sys.version_info[0]就等于sys.version_info.major等等。
    • sys.api_version:解释器的C API版本。程序员可能在调试Python和扩展模块之间的版本冲突时会发现这有用。
    • sys.hexversion:编码成一个整数的版本号。它保证在每个版本中递增,包括对于非生产版本的适当支持。
    • sys.platform:此字符串包含一个平台标识符,可用于平台相关的组件追加到sys.path。

    举例
    用于构建C解释器的版本有几种形式。sys.version是一种人类可读的字符串,通常包括完整版本号,以及关于构建日期、编译器和平台的信息。sys.hexversion它是一个简单的整数,所以它更容易用于检查解释器版本。当使用hex() 格式化时,很明显,sys.hexversion的部分来自版本信息,也可以在更可读的sys.version_info(仅代表版本号的五部分namedtuple)中显示。当前解释器使用的独立的C API版本保存在 sys.api_version 中。

    import sys
    
    print('Version info:')
    print()
    print('sys.version      =', repr(sys.version))
    print('sys.version_info =', sys.version_info)
    print('sys.hexversion   =', hex(sys.hexversion))
    print('sys.api_version  =', sys.api_version)
    

    所有这些值都取决于用于运行示例程序的实际解释器。

    # python sys_study.py
    Version info:
    
    sys.version      = '3.5.2 (default, Jul  2 2016, 16:59:21) 
    [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]'
    sys.version_info = sys.version_info(major=3, minor=5, micro=2, releaselevel='final', serial=0)
    sys.hexversion   = 0x30502f0
    sys.api_version  = 1013
    

    用于构建解释器的操作系统平台保存为sys.platform。

    import sys
    
    print('This interpreter was built for:', sys.platform)
    

    对于大多数Unix系统,该值来自将uname -s的输出与uname -r中的第一部分版本相结合。 对于其他操作系统,有一个硬编码的值表。

    # python sys_study.py
    This interpreter was built for: linux
    

    Interpreter Implementation

    • sys.implementation:包含有关当前运行的Python解释器的实现的信息的对象。在所有Python实现中都需要以下属性。
      name是实现的标识符,例如'cpython'。实际的字符串是由Python实现定义的,但它保证为小写。
      version是一个命名的元组,格式与sys.version_info相同。它表示Python 实现的版本。这与当前运行的解释器符合的特定版本的Python 语言有不同的含义,sys.version_info表示。
      hexversion是十六进制格式的实现版本,如sys.hexversion。
      cache_tag是导入机制在缓存模块的文件名中使用的标记。按照惯例,它将是实现的名称和版本的复合,如'cpython-33'。但是,如果适当,Python实现可以使用一些其他值。如果cache_tag设置为None,则表示应禁用模块高速缓存。

    举例
    CPython解释器是Python语言的几种实现之一。提供sys.implementation来检测需要解决解释器差异的库的当前实现。

    import sys
    
    print('Name:', sys.implementation.name)
    print('Version:', sys.implementation.version)
    print('Cache tag:', sys.implementation.cache_tag)
    

    sys.implementation.version与CPython的sys.version_info相同,但与其他解释器将不同。

    # python sys_study.py
    Name: cpython
    Version: sys.version_info(major=3, minor=5, micro=2, releaselevel='final', serial=0)
    Cache tag: cpython-35
    

    Command Line Options

    CPython解释器接受几个命令行选项来控制其行为,并在下面的表中列出。

    flagOptionMeaning
    -B dont_write_bytecode do not write .py[co] files on import
    -b bytes_warning 发出警告,在不正确解码的情况下将字节转换为字符串,并将字节与字符串进行比较
    -bb bytes_warning convert bytes warnings to errors
    -d debug debug output from parser
    -E ignore_environment ignore PYTHON* environment variables (such as PYTHONPATH)
    -i inspect inspect interactively after running script
    -O optimize optimize generated bytecode slightly
    -OO optimize remove doc-strings in addition to the -O optimizations
    -s no_user_site do not add user site directory to sys.path
    -S no_site do not run ‘import site’ on initialization
    -t   issue warnings about inconsistent tab usage
    -tt   issue errors for inconsistent tab usage
    -v verbose verbose

    举例
    其中一些可用于通过sys.flags检查的程序。

    import sys
    
    if sys.flags.bytes_warning:
        print('Warning on bytes/str errors')
    if sys.flags.debug:
        print('Debuging')
    if sys.flags.inspect:
        print('Will enter interactive mode after running')
    if sys.flags.optimize:
        print('Optimizing byte-code')
    if sys.flags.dont_write_bytecode:
        print('Not writing byte-code files')
    if sys.flags.no_site:
        print('Not importing "site"')
    if sys.flags.ignore_environment:
        print('Ignoring environment')
    if sys.flags.verbose:
        print('Verbose mode')
    

    通过不同选项输出如下:

    # python -S -E -b sys_study.py
    Warning on bytes/str errors
    Not importing "site"
    Ignoring environment
    

    Unicode Defaults

    • sys.getdefaultencoding():返回当前Unicode 实现所使用的默认字符串编码的名称。
    • sys.getfilesystemencoding():返回用于将Unicode文件名转换为系统文件名的编码的名称。

    举例
    要获取解释器正在使用的默认Unicode编码的名称,请调用getdefaultencoding()。 该值在启动期间设置,不能更改。
    对于某些操作系统,内部编码默认值和文件系统编码可能不同,因此有一种单独的方法来检索文件系统设置。 getfilesystemencoding()返回特定于操作系统的(非特定文件系统)值。

    import sys
    
    print('Default encoding    :', sys.getdefaultencoding())
    print('File system encoding:', sys.getfilesystemencoding())
    

    大多数Unicode专家建议,不要依赖全局缺省编码,而是要显式地使用Unicode编码。这提供了两个好处:不同数据源的不同的Unicode编码可以更清晰地处理,并且减少了应用程序代码中编码的假设数量。

    # python sys_study.py
    Default encoding    : utf-8
    File system encoding: utf-8
    

    Interactive Prompts

    sys.ps1 sys.ps2:解释器的主提示符和次提示符。它们只在解释器的交互模式中定义。它们的初始值是'>>> '和'... '。 如果一个非字符串对象被分配给任一变量,则每次解释器准备读取一个新的交互式命令时,它的str()这可以用于实现动态提示。

    举例
    交互式解释器使用两个单独的提示来表示默认输入级别(ps1)和多行语句(ps2)的“continuation”。这些值仅供交互解释器使用。

    >>> import sys
    >>> sys.ps1
    '>>> '
    >>> sys.ps2
    '... '
    

    或者两个提示符都可以被更改为一个不同的字符串。

    >>> sys.ps1 = ':::'
    :::sys.ps2 = '~~~'
    :::for i in range(3):
    ~~~    print(i)
    ~~~
    0
    1
    2
    

    或者,可以将任何可以转换为字符串的对象(通过__str__)用于提示。

    # vim sys_study.py
    import sys
    
    class LineCounter:
        def __init__(self):
            self.count = 0
    
        def __str__(self):
            self.count += 1
            return '({:3d})>'.format(self.count)
    

    LineCounter记录了它被使用了多少次,因此提示符的数量每次都增加。

    # python
    >>> from sys_study import LineCounter
    >>> import sys
    >>> sys.ps1 = LineCounter()
    (  1)>
    (  2)>
    

    Display Hook

    sys.displayhook(value):如果值不是None,此函数将打印repr(value)到sys.stdout,并保存中的builtins._If repr(value) is not encodable to sys.stdout.encoding with sys.stdout.errors error handler (which is probably 'strict'), encode it to sys.stdout.encoding with 'backslashreplace' error handler.
    sys.displayhook在 Python 的交互式会话中输入的expression的计算结果上被调用。通过将另一个单参数函数赋值给sys.displayhook,可以自定义这些值的显示。

    sys.__displayhook__ sys.__excepthook__:这两个对象包含displayhook和excepthook在程序开始时的原始值。保存它们是为了在displayhook与excepthook被不小心换成有问题的对象时可以恢复它们。

    举例
    每当用户输入表达式时,交互式解释器都会调用sys.displayhook。 表达式的计算结果作为函数的唯一参数传递。

    # vim sys_study.py
    import sys
    
    class ExpressionCounter:
        def __init__(self):
            self.count = 0
            self.previous_value = self
    
        def __call__(self, value):
            print()
            print('   Previous:', self.previous_value)
            print('   New     :', value)
            print()
            if value != self.previous_value:
                self.count += 1
                sys.ps1 = '({:3d})>'.format(self.count)
            self.previous_value = value
            sys.__displayhook__(value)
    
    print('installing')
    sys.displayhook = ExpressionCounter()
    

    默认值(保存在sys .__ displayhook__中)将结果打印到stdout,并将其保存在_中,以便稍后参考。

    # python
    >>> import sys_study
    installing
    >>> 1 + 2
    
       Previous: <sys_study.ExpressionCounter object at 0x7f7c81b099b0>
       New     : 3
    
    3
    (  1)>'abc'
    
       Previous: 3
       New     : abc
    
    'abc'
    (  2)>'abc'
    
       Previous: abc
       New     : abc
    
    'abc'
    (  2)>'abc' * 3
    
       Previous: abc
       New     : abcabcabc
    
    'abcabcabc'
    (  3)>
    

    Install Location

    sys.executable:给出Python解释器的可执行二进制文件的绝对路径。如果Python无法检索到其可执行程序的真实路径,sys.executable将为一个空字符串或None。
    sys.prefix:提供站点特定的目录前缀,其中安装了平台独立的Python文件;默认情况下,这是字符串'/usr/local'。它可以在构建时通过configure脚本的--prefix参数设置。

    举例
    在具有解释器路径的所有系统上,sys.executable中提供了实际解释器程序的路径。 这对于确保使用正确的解释器是有用的,并且还提供了可能基于解释器位置设置的路径的线索。
    sys.prefix是指解释器安装的父目录。 它通常分别包含可执行文件和已安装模块的bin和lib目录。

    import sys
    
    print('Interpreter executable:')
    print(sys.executable)
    print('
    Installation prefix:')
    print(sys.prefix)
    

    下面是在pyenv中安装的官方python3.5.2 执行的结果:

    # python sys_study.py
    Interpreter executable:
    /root/.pyenv/versions/3.5.2/bin/python
    
    Installation prefix:
    /root/.pyenv/versions/3.5.2
    

    Runtime Environment

    sys提供用于与应用程序之外的系统交互的低级API,通过接受命令行参数,访问用户输入以及将消息和状态值传递给用户。

    Command Line Arguments

    sys.argv:传递给Python脚本的命令行参数列表。argv[0]是脚本的名称(是否是完整的路径名这要取决于操作系统)。如果使用解释器的-c命令行选项执行命令,则argv[0]设置为字符串'-c'如果无脚本名称传递给 Python 解释器, argv[0]是空字符串。

    举例
    解释器捕获的参数在那里进行处理,不会传递给正在运行的程序。 任何剩余的选项和参数(包括脚本本身的名称)将保存到sys.argv,以防程序确实需要使用它们。

    # vim sys_study.py 
    import sys
    
    print('Argument:', sys.argv)
    

    在第三个示例中,解释器可以理解-u选项,并没有传递给正在运行的程序。

    # python sys_study.py
    Argument: ['sys_study.py']
    # python sys_study.py -v haha ahah
    Argument: ['sys_study.py', '-v', 'haha', 'ahah']
    # python -u sys_study.py 
    Argument: ['sys_study.py']
    

    Input and Output Streams

    sys.stdin
    sys.stdout
    sys.stderr
    File objects:

    • stdin用于所有交互输入(包括调用input());
    • stdout用于输出print()和expression语句和input()
    • 解释器自己的提示及其错误消息转到stderr。

    举例
    按照Unix范式,Python程序可以默认地访问三个文件描述符。

    import sys
    
    print('STATUS:Reading from stdin', file=sys.stderr)
    data = sys.stdin.read()
    print('STATUS:Writing data to stdout', file=sys.stderr)
    sys.stdout.write(data)
    sys.stdout.flush()
    print('STATUS:Done', file=sys.stderr)
    

    stdin是读取输入的标准方式,通常是通过控制台,也可以通过管道从其他程序中读取输入。stdout是为用户(到控制台)编写输出的标准方法,或者是将其发送到管道中的下一个程序。stderr的目的是使用警告或错误消息。

    # cat sys_study.py |python -u sys_study.py 
    STATUS:Reading from stdin
    STATUS:Writing data to stdout
    import sys
    
    print('STATUS:Reading from stdin', file=sys.stderr)
    data = sys.stdin.read()
    print('STATUS:Writing data to stdout', file=sys.stderr)
    sys.stdout.write(data)
    sys.stdout.flush()
    print('STATUS:Done', file=sys.stderr)
    STATUS:Done
    

    Returning Status

    要从程序返回一个退出代码,将一个整数值传递给sy.exit()。

    import sys
    
    exit_code = int(sys.argv[1])
    sys.exit(exit_code)
    

    非零值意味着程序以错误的形式退出。

    # python sys_study.py 0; echo "Exited $?"
    Exited 0
    # python sys_study.py 1; echo "Exited $?"
    Exited 1
    

    Memory Management and Limits

    sys包括几个用于理解和控制内存使用的函数。

    Reference Counts

    sys.getrefcount(object):返回object的引用计数。返回的计数一般比你期望的值多一个,因为它包含getrefcount()参数的(临时)引用。

    举例
    Python(CPython)的主要实现使用引用计数和垃圾收集来进行自动内存管理。当引用计数降为0时,将自动标记一个对象。要检查现有对象的引用计数,请使用getrefcount()。

    import sys
    
    one = []
    print('At start        :', sys.getrefcount(one))
    
    two = one
    print('Second reference:', sys.getrefcount(one))
    
    del two
    print('After del       :', sys.getrefcount(one))
    

    报告的值实际上比预期的要高,因为对于getrefcount()本身所持有的对象有一个临时引用。

    At start        : 2
    Second reference: 3
    After del       : 2
    

    Object Size

    sys.getsizeof(object[, default]):返回对象的大小,以字节为单位。该对象可以是任何类型。所有内置对象会返回正确的结果,但这并适用于第三方扩展,因为它特定于实现。
    只考虑直接归因于对象的内存消耗,而不是它所引用的对象的内存消耗。
    如果给出default,若对象没有提供获取其大小的方法将返回该值。否则会引发TypeError 。
    getsizeof()调用对象的__sizeof__方法,如果该对象由垃圾回收器管理还会增加额外的垃圾回收器的开销。

    举例
    知道一个对象有多少引用可能有助于发现周期或内存泄漏,但是还不足以确定哪些对象消耗了最多的内存。这需要知道有多大的物体。

    import sys
    
    class MyClass:
        pass
    
    objects = [
        [], (), {}, 'c', 'string', b'bytes', 1, 2.3,
        MyClass, MyClass(),
    ]
    
    for obj in objects:
        print('{:>10}  :  {}'.format(type(obj).__name__, sys.getsizeof(obj)))
    

    getsizeof()以字节的方式报告对象的大小。

          list  :  64
         tuple  :  48
          dict  :  288
           str  :  50
           str  :  55
         bytes  :  38
           int  :  28
         float  :  24
          type  :  1016
       MyClass  :  56
    

    定制类的报告大小不包括属性值的大小。

    import sys
    
    class WithoutAttributes:
        pass
    
    class WithAttributes:
        def __init__(self):
            self.a = 'a'
            self.b = 'b'
            return
    
    without_attrs = WithoutAttributes()
    print('WithoutAttributes:', sys.getsizeof(without_attrs))
    
    with_attrs = WithAttributes()
    print('WithAttributes:', sys.getsizeof(with_attrs))
    

    这可能会给人一个错误的印象,那就是被消耗的memory 。

    WithoutAttributes: 56
    WithAttributes: 56
    

    对于一个类所使用的空间的一个更完整的估计,提供一个 __sizeof__()方法,通过聚合一个对象的属性的大小来计算这个值。

    import sys
    
    class WithAttributes:
        def __init__(self):
            self.a = 'a'
            self.b = 'b'
            return
    
        def __sizeof__(self):
            return object.__sizeof__(self) + sum(sys.getsizeof(v) for v in self.__dict__.values())
    
    my_inst = WithAttributes()
    print(sys.getsizeof(my_inst))
    

    此版本将对象的基本大小添加到存储在内部__dict__中的所有属性的大小。

    # python sys_study.py
    156
    

    Recursion

    sys.getrecursionlimit():返回当前递归的限制也就是Python解释器堆栈最大深度的值。该限制可防止无限递归导致C堆栈溢出和Python崩溃。它可以通过setrecursionlimit()设置。
    sys.setrecursionlimit(limit):设置Python解释器的堆栈最大深度为limit。该限制可防止无限递归导致C堆栈溢出和Python崩溃。
    最高可能的限制取决于平台。当用户有需要深度递归的程序和支持更高限制的平台时,用户可能需要将限制设置得更高。设置时应该小心,因为太高的限制可能导致程序崩溃。
    如果新限制在当前递归深度处过低,则会引发RecursionError异常。

    举例
    在Python应用程序中允许无限递归可能会在解释器本身引入堆栈溢出,导致崩溃。 为了消除这种情况,解释器提供了一种使用setrecursionlimit() 和getrecursionlimit() 来控制最大递归深度的方法。

    import sys
    
    print('Initial limit:', sys.getrecursionlimit())
    sys.setrecursionlimit(10)
    print('Modified limit:', sys.getrecursionlimit())
    
    def generate_recursion_error(i):
        print('generate_recursion_error({})'.format(i))
        generate_recursion_error(i + 1)
    
    try:
        generate_recursion_error(1)
    except RuntimeError as err:
        print('Caught exception:', err)
    

    一旦堆栈大小达到递归限制,解释器就会产生一个运行时错误异常,这样程序就有机会处理这种情况。

    # python sys_study.py
    Initial limit: 1000
    Modified limit: 10
    generate_recursion_error(1)
    generate_recursion_error(2)
    generate_recursion_error(3)
    generate_recursion_error(4)
    generate_recursion_error(5)
    generate_recursion_error(6)
    generate_recursion_error(7)
    Caught exception: maximum recursion depth exceeded while calling a Python object
    

    Maximum Values

    sys.maxsize:给出类型Py_ssize_t的变量的最大值的整数可以取。
    sys.maxunicode:给出最大Unicode代码点的值的整数,即1114111(十六进制的0x10FFFF)。

    举例
    除了运行时可配置值,sys还包括为不同系统的类型定义最大值的变量。

    import sys
    
    print('maxsize   :', sys.maxsize)
    print('maxunicode:', sys.maxunicode)
    

    maxsize是由C解释器的大小类型决定的列表、字典、字符串或其他数据结构的最大大小。maxunicode是当前配置的解释器所支持的最大整数Unicode点。

    # python sys_study.py
    maxsize   : 9223372036854775807
    maxunicode: 1114111
    

    Floating Point Values

    sys.float_info:保存有关浮点类型的信息的struct sequence。它包含精度和内部表示形式有关的低级别信息。这些值对应于“C”编程语言的标准头文件float.h中定义的各种浮点常量;

    举例
    结构float_info包含基于底层系统浮动实现的解释器使用的浮点类型表示的信息。

    import sys
    
    print('Smallest difference (epsilon):', sys.float_info.epsilon)
    print()
    print('Digits (dig)              :', sys.float_info.dig)
    print('Mantissa digits (mant_dig):', sys.float_info.mant_dig)
    print()
    print('Maximum (max):', sys.float_info.max)
    print('Minimum (min):', sys.float_info.min)
    print()
    print('Radix of exponents (radix):', sys.float_info.radix)
    print()
    print('Maximum exponent for radix (max_exp):', sys.float_info.max_exp)
    print('Minimum exponent for radix (min_exp):', sys.float_info.min_exp)
    print()
    print('Max.  exponent power of 10 (max_10_exp):', sys.float_info.max_10_exp)
    print('Min.  exponent power of 10 (min_10_exp):', sys.float_info.min_10_exp)
    print()
    print('Rounding for addition (rounds):', sys.float_info.rounds)
    

    下面是在 Core(TM) i5-6600 CPU系统为CentOS 6中的结果:

    # python sys_study.py
    Smallest difference (epsilon): 2.220446049250313e-16
    
    Digits (dig)              : 15
    Mantissa digits (mant_dig): 53
    
    Maximum (max): 1.7976931348623157e+308
    Minimum (min): 2.2250738585072014e-308
    
    Radix of exponents (radix): 2
    
    Maximum exponent for radix (max_exp): 1024
    Minimum exponent for radix (min_exp): -1021
    
    Max.  exponent power of 10 (max_10_exp): 308
    Min.  exponent power of 10 (min_10_exp): -307
    
    Rounding for addition (rounds): 1
    

    Exception Handling

    sys包括捕获和处理异常的特性。

    Unhandled Exceptions

    sys.excepthook(type, value, traceback):打印给定的回溯和异常到sys.stderr。当异常被引发并没有被捕获时,解释器将以三个参数:异常类、异常实例和回溯对象调用sys.excepthook。在交互会话中,这发生在控制返回到提示之前;在Python程序中,这发生在程序退出之前。通过向sys.excepthook分配另一个三个参数的函数,可以自定义这种顶级的异常处理。

    举例
    许多应用程序都使用一个主循环来构造,该主循环将执行包装在全局异常处理程序中,以捕获未在较低级别处理的错误。 实现同样目的的另一种方法是将sys.excepthook设置为一个接受三个参数(错误类型,错误值和追溯)的函数,并使其处理未处理的错误。

    import sys
    
    def my_excepthook(type, value, traceback):
        print('Unhandled error:', type, value)
    
    sys.excepthook = my_excepthook
    print('Before exception')
    raise RuntimeError('This is the error message')
    print('After exception')
    

    因为没有 try:except 异常引发的行以外的block,对print() 的以下调用不会运行,即使设置了except钩子。

    # python sys_study.py
    Before exception
    Unhandled error: <class 'RuntimeError'> This is the error message
    

    Current Exception

    sys.exc_info():此函数返回一个三元组,它们给出当前正在处理的异常的信息。返回的信息特定于当前的线程和当前的堆栈帧。如果当前的堆栈帧没在处理异常,则从调用者的堆栈帧、调用者的调用者、等等获取信息,直至找到正在处理异常的堆栈帧。这里,“处理异常”被定义为“执行一个except子句”。对于任何堆栈帧,只有当前处理的异常的信息是可访问的。
    如果堆栈上没有正在处理的异常,则返回包含三个None 值的元组。否则,返回(type, value, traceback)。它们的含义是:类型获取正在处理的异常的类型(BaseException的子类); value获取异常实例(异常类型的实例); traceback获取一个跟踪对象(参见参考手册),它将调用堆栈封装在异常最初发生的点。

    举例
    有时候,显式的异常处理程序是首选的,无论是为了代码清晰还是避免与尝试安装自己的excepthook的库冲突。 在这种情况下,可以创建一个常见的处理程序函数,它不需要通过调用exc_info() 来为线程检索当前的异常来显式地传递异常对象。
    exc_info() 的返回值是包含异常类,异常实例和追溯的三个元组。 使用exc_info() 优于旧表单(使用exc_type,exc_value和exc_traceback),因为它是线程安全的。

    import sys
    import threading
    import time
    
    def do_something_with_exception():
        exc_type, exc_value = sys.exc_info()[:2]
        print('Handing {} exception with message "{}" in {}'.format(exc_type.__name__, exc_value, threading.current_thread().name))
    
    def cause_exception(delay):
        time.sleep(delay)
        raise RuntimeError('This is the error message')
    
    def thread_target(delay):
        try:
            cause_exception(delay)
        except:
            do_something_with_exception()
    
    threads = [
        threading.Thread(target=thread_target, args=(0.3,)),
        threading.Thread(target=thread_target, args=(0.1,)),
    ]
    
    for t in threads:
        t.start()
    
    for t in threads:
        t.join()
    

    此示例避免通过忽略来自exc_info() 的返回值的那部分,在回溯对象和当前帧中的局部变量之间引入循环引用。 如果需要回溯(例如,因此可以记录),请明确删除局部变量(使用del)以避免循环。

    Handing RuntimeError exception with message "This is the error message" in Thread-2
    Handing RuntimeError exception with message "This is the error message" in Thread-1
    

    Previous Interactive Exception

    在交互式翻译中,只有一个交互的线程。 该线程中的未处理异常将保存到sys(last_type,last_value和last_traceback)中的三个变量中,以便轻松检索它们进行调试。 在pdb中使用postmortem调试器避免了直接使用这些值的任何需要。

    $ python3
    Python 3.4.2 (v3.4.2:ab2c023a9432, Oct  5 2014, 20:42:22)
    [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def cause_exception():
    ...     raise RuntimeError('This is the error message')
    ...
    >>> cause_exception()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in cause_exception
    RuntimeError: This is the error message
    >>> import pdb
    >>> pdb.pm()
    > <stdin>(2)cause_exception()
    (Pdb) where
      <stdin>(1)<module>()
    > <stdin>(2)cause_exception()
    (Pdb)
    

    Low-level Thread Support

    sys包括用于控制和调试线程行为的低级功能。

    Switch Interval

    sys.getswitchinterval():返回解释器的“线程切换间隔”;
    sys.setswitchinterval(interval):设置解释器的线程切换间隔(以秒为单位)。此浮点值确定分配给并发运行的Python线程的“时间片”的理想持续时间。请注意,实际值可以更高,特别是如果使用长期运行的内部函数或方法。此外,在间隔结束时哪个线程被调度是操作系统的决定。解释器没有自己的调度器。

    举例
    Python 3使用全局锁来防止单独的线程破坏解释器状态。 在可配置的时间间隔后,字节码执行暂停,解释器检查是否需要执行任何信号处理程序。 在同一检查中,全局解释器锁(GIL)也由当前线程释放,然后重新获取,其他线程优先于刚释放锁的线程。
    默认切换间隔为5毫秒,并且始终可以使用sys.getswitchinterval() 检索当前值。 根据正在执行的操作的性质,使用sys.setswitchinterval() 更改间隔可能会对应用程序的性能产生影响。

    import sys
    import threading
    from queue import Queue
    
    def show_thread(q):
        for i in range(5):
            for j in range(1000000):
                pass
            q.put(threading.current_thread().name)
        return
    
    def run_threads():
        interval = sys.getswitchinterval()
        print('interval = {:0.3f}'.format(interval))
        q = Queue()
        threads = [
            threading.Thread(target=show_thread, name='T{}'.format(i), args=(q,)) for i in range(3)
        ]
        for t in threads:
            t.setDaemon(True)
            t.start()
        for t in threads:
            t.join()
        while not q.empty():
            print(q.get(), end=' ')
        print()
        return
    
    for interval in [0.001, 0.1]:
        sys.setswitchinterval(interval)
        run_threads()
        print()
    

    当切换间隔小于线程运行到完成的时间时,解释器会提供另一个线程控制,以便它运行一段时间。这在第一组输出中显示,其中间隔设置为1毫秒。
    在更长的时间间隔内,活动线程将能够在强制释放控制之前完成更多的工作。在第二个示例中,使用间隔为10毫秒的队列中的名称值的顺序说明了这一点。

    # python sys_study.py
    interval = 0.001
    T0 T1 T2 T0 T1 T2 T0 T1 T2 T0 T1 T2 T1 T2 T0 
    
    interval = 0.100
    T0 T0 T0 T0 T0 T1 T1 T1 T1 T1 T2 T2 T2 T2 T2 
    

    除了开关间隔之外,许多其他因素可能会控制Python线程的上下文切换行为。例如,当一个线程执行输入/输出时,它释放了GIL,因此允许另一个线程接管执行。

    Debugging

    sys._current_frames():返回一个字典,将每个线程标识符映射到函数调用时线程中正在活跃的最顶层的堆栈帧。请注意在traceback模块中的函数可以根据这样的帧构建调用栈。
    这对调试死锁是最有用的:此功能不需要死锁线程的合作,且这些线程的调用堆栈都已冻结只要它们仍处于死锁的状态。非死锁线程返回的帧可能与调用代码检查该帧时线程中正在活跃的帧没有关系。

    举例
    识别死锁可能是使用线程最困难的方面之一。sys.currentframe()可以通过显示一个线程被停止的确切位置来帮助。

    import sys
    import threading
    import time
    
    io_lock = threading.Lock()
    blocker = threading.Lock()
    
    def block(i):
        t = threading.current_thread()
        with io_lock:
            print('{} with ident {} going to sleep'.format(t.name, t.ident))
        if i:
            blocker.acquire()
            time.sleep(0.2)
        with io_lock:
            print(t.name, 'finishing')
        return
    
    threads = [
        threading.Thread(target=block, args=(i,)) for i  in range(3)
    ]
    for t in threads:
        t.setDaemon(True)
        t.start()
    
    threads_by_ident = dict((t.ident, t) for t in threads)
    
    time.sleep(0.01)
    with io_lock:
        for ident, frame in sys._current_frames().items():
            t = threads_by_ident.get(ident)
            if not t:
                continue
            print('{} stoppd in {} at line {} of {}'.format(t.name, frame.f_code.co_name, frame.f_lineno, frame.f_code.co_filename))
    

    由sys.currentframe()返回的字典以线程标识符而不是它的名称为键。需要做一些工作来将这些标识符映射回线程对象。
    因为thread-1不睡觉,所以它在检查它的状态之前就已经完成了。因为它不再是活动的,所以它不会出现在输出中。thread-2获得了锁阻器,然后在短时间内休眠。与此同时,thread-3试图获取拦截器,但不能因为thread-2已经拥有它。

    # python sys_study.py
    Thread-1 with ident 139919260309248 going to sleep
    Thread-1 finishing
    Thread-2 with ident 139919249819392 going to sleep
    Thread-3 with ident 139919260309248 going to sleep
    Thread-3 stoppd in block at line 14 of sys_study.py
    Thread-2 stoppd in block at line 15 of sys_study.py
    

    Modules and Imports

    大多数Python程序最终以多个模块的组合结合,主要应用程序导入它们。 无论是使用标准库的功能还是在单独的文件中组织自定义代码,以便更容易地维护,理解和管理程序的依赖关系是开发的一个重要方面。 sys包含有关应用程序可用的模块的信息,无论是内置还是导入。 它还定义了用于覆盖特殊情况的标准导入行为的钩子。

    Imported Modules

    sys.modules:它是一个字典,将模块名称映射到已加载的模块。它可以被修改来强制重新加载模块和其他技巧。然而,替换字典将不一定按预期工作,删除从字典中的必要项目可能会导致Python失败。

    举例
    sys.modules是将导入的模块的名称映射到保存代码的模块对象的字典。

    import sys
    import textwrap
    
    names = sorted(sys.modules.keys())
    name_text = ','.join(names)
    
    print(textwrap.fill(name_text, width=64))
    

    当引入新模块时,sys.modules的内容会更改。

    # python sys_study.py
    __main__,_codecs,_collections,_collections_abc,_frozen_importlib
    ,_frozen_importlib_external,_functools,_heapq,_imp,_io,_locale,_
    operator,_signal,_sitebuiltins,_sre,_stat,_sysconfigdata,_thread
    ,_warnings,_weakref,_weakrefset,abc,builtins,codecs,collections,
    collections.abc,copyreg,encodings,encodings.aliases,encodings.la
    tin_1,encodings.utf_8,errno,functools,genericpath,heapq,io,itert
    ools,keyword,linecache,marshal,operator,os,os.path,posix,posixpa
    th,re,reprlib,site,sre_compile,sre_constants,sre_parse,stat,sys,
    sysconfig,textwrap,threading,time,token,tokenize,traceback,types
    ,weakref,zipimport
    

    Built-in Modules

    sys.builtin_module_names:一个字符串元组,给出编译进该Python 解释器的所有模块的名称。

    举例
    Python解释器可以编译一些内置的C模块,因此它们不需要作为单独的共享库进行分发。 这些模块不会出现在sys.modules中管理的导入模块列表中,因为它们在技术上不被导入。 找到可用的内置模块的唯一方法是通过sys.builtin_module_names。

    import sys
    import textwrap
    
    name_text = ','.join(sorted(sys.builtin_module_names))
    print(textwrap.fill(name_text, width=64))
    

    下面是CentOS 6.7的输出:

    # python sys_study.py
    _ast,_codecs,_collections,_functools,_imp,_io,_locale,_operator,
    _signal,_sre,_stat,_string,_symtable,_thread,_tracemalloc,_warni
    ngs,_weakref,atexit,builtins,errno,faulthandler,gc,itertools,mar
    shal,posix,pwd,sys,time,xxsubtype,zipimport
    

    Import Path

    sys.path:指定用于模块搜索路径的字符串列表。它根据环境变量PYTHONPATH进行初始化,再加上安装时的默认值。
    此列表的第一项path[0],在程序启动时初始化,是包含调用Python解释器的脚本的目录。如果脚本目录不可用(例如,如果解释器被交互地调用,或者如果从标准输入读取脚本),则path[0]是空字符串,其指示Python首先搜索当前目录中的模块。注意脚本的目录在依据PYTHONPATH生成的结果之前 插入。
    一个程序可以根据它自己的目的自由地修改此列表。只有字符串和字节应添加到sys.path;所有其他数据类型在导入期间将被忽略。

    举例
    模块的搜索路径作为保存在sys.path中的Python列表进行管理。 路径的默认内容包括用于启动应用程序的脚本目录和当前工作目录。

    import sys
    
    for d in sys.path:
        print(d)
    

    搜索路径中的第一个目录是示例脚本本身的主目录。 之后是一系列平台特定的路径,其中可能安装了编译的扩展模块(以C编写),然后最后列出了全局site-packages目录。

    # python sys_study.py
    /root/study
    /root/.pyenv/versions/3.5.2/lib/python35.zip
    /root/.pyenv/versions/3.5.2/lib/python3.5
    /root/.pyenv/versions/3.5.2/lib/python3.5/plat-linux
    /root/.pyenv/versions/3.5.2/lib/python3.5/lib-dynload
    /root/.pyenv/versions/3.5.2/lib/python3.5/site-packages
    

    通过将shell变量PYTHONPATH设置为冒号分隔的目录列表,可以在启动解释器之前修改导入搜索路径列表。

    # PYTHONPATH=/root/study/example/ 
    > python sys_study.py
    /root/study
    /root/study/example
    /root/.pyenv/versions/3.5.2/lib/python35.zip
    /root/.pyenv/versions/3.5.2/lib/python3.5
    /root/.pyenv/versions/3.5.2/lib/python3.5/plat-linux
    /root/.pyenv/versions/3.5.2/lib/python3.5/lib-dynload
    /root/.pyenv/versions/3.5.2/lib/python3.5/site-packages
    

    Custom Importers

    sys.path_hooks:一个可调用对象的列表,它们以一个路径为参数并为该路径创建一个finder。如果可以创建查找器,它将由可调用对象返回,否则引发ImportError。

    举例
    修改搜索路径让程序员控制如何找到标准的Python模块。 但是,如果程序需要从文件系统上通常的.py或.pyc文件之外的其他地方导入代码呢? PEP 302通过引入导入钩子的想法来解决这个问题,这可以阻止在搜索路径上找到模块,并采取替代措施从其他地方加载代码或对其进行预处理。
    自定义导入器是在两个独立的阶段实现的。查找器负责定位一个模块,并提供一个装载器来管理实际的导入。 通过将工厂附加到sys.path_hooks列表中添加自定义模块查找器。 在导入时,路径的每个部分都被赋予一个查找器,直到一个声明支持(不引起ImportError)。 然后,该查找器负责搜索由其命名模块的路径条目表示的数据存储。

    import sys
    
    class NoisyImportFinder:
        PATH_TRIGGER = 'NoisyImportFinder_PATH_TRIGGER'
        def __init__(self, path_entry):
            print('Checking {}:'.format(path_entry), end=' ')
            if path_entry != self.PATH_TRIGGER:
                print('wrong finder')
                raise ImportError()
            else:
                print('works')
            return
    
        def find_module(self, fullname, path=None):
            print('Looking for  {!r}'.format(fullname))
            return None
    
    sys.path_hooks.append(NoisyImportFinder)
    for hook in sys.path_hooks:
        print('Path hook: {}'.format(hook))
    
    sys.path.insert(0, NoisyImportFinder.PATH_TRIGGER)
    try:
        print('importing target_module')
        import target_module
    except Exception as e:
        print('Import failed:', e)
    

    这个例子说明了如何实例化和查询查找器。 当使用与特殊触发值不匹配的路径条目实例化时,NoisyImportFinder会引发ImportError,这显然不是文件系统上的真实路径。 此测试可防止NoisyImportFinder打破实际模块的导入。

    # python sys_study.py
    Path hook: <class 'zipimport.zipimporter'>
    Path hook: <function FileFinder.path_hook.<locals>.path_hook_for_FileFinder at 0x7f9c751bf950>
    Path hook: <class '__main__.NoisyImportFinder'>
    importing target_module
    Checking NoisyImportFinder_PATH_TRIGGER: works
    Looking for  'target_module'
    Import failed: No module named 'target_module'
    

    Importing from a Shelve

    当查找器找到一个模块时,它负责返回一个能够导入该模块的装载器。 此示例说明了将其模块内容保存在由shelve创建的数据库中的自定义导入器。
    首先,使用脚本来填充包含子模块和子包的包。

    import shelve
    import os
    
    filename = '/tmp/pymotw_import_example.shelve'
    if os.path.exists(filename + 'db'):
        os.unlink(filename + '.db')
    with shelve.open(filename) as db:
        db['data:README'] = b"""
    ==============
    package README
    ==============
    
    This is the README for ``package``.
    """
        db['package.__init__'] = b"""
    print('package imported')
    message = 'This message is in package.__init__'
    """
        db['package.modulel'] = b"""
    print('package.module1 imported')
    message = 'This message is in package.module1'
    """
        db['package.subpackage.__init__'] = b"""
    print('package.subpackage imported')
    message = 'This message is in package.subpackage.__init__'
    """
        db['package.subpackage.module2'] = b"""
    print('package.subpackage.module2 imported')
    message = 'This message is in package.subpackage.module2'
    """
        db['package.with_error'] = b"""
    print('package.with_error being imported')
    raise ValueError('raising exception to break import')
    """
        print('Created {} with:'.format(filename))
        for key in sorted(db.keys()):
            print('  ', key)
    

    一个真正的包装脚本将从文件系统读取内容,但使用硬编码的值就足以满足这样一个简单的例子。

    # python sys_study.py
    Created /tmp/pymotw_import_example.shelve with:
       data:README
       package.__init__
       package.modulel
       package.subpackage.__init__
       package.subpackage.module2
       package.with_error
    

    自定义导入器需要提供查找器和加载器类,该类可以知道如何查看模块或包的源代码。

    import imp
    import os
    import shelve
    import sys
    
    
    def _mk_init_name(fullname):
        """Return the name of the __init__ module
        for a given package name.
        """
        if fullname.endswith('.__init__'):
            return fullname
        return fullname + '.__init__'
    
    
    def _get_key_name(fullname, db):
        """Look in an open shelf for fullname or
        fullname.__init__, return the name found.
        """
        if fullname in db:
            return fullname
        init_name = _mk_init_name(fullname)
        if init_name in db:
            return init_name
        return None
    
    
    class ShelveFinder:
        """Find modules collected in a shelve archive."""
    
        _maybe_recursing = False
    
        def __init__(self, path_entry):
            # Loading shelve causes an import recursive loop when it
            # imports dbm, and we know we are not going to load the
            # module # being imported, so when we seem to be
            # recursing just ignore the request so another finder
            # will be used.
            if ShelveFinder._maybe_recursing:
                raise ImportError
            try:
                # Test the path_entry to see if it is a valid shelf
                try:
                    ShelveFinder._maybe_recursing = True
                    with shelve.open(path_entry, 'r'):
                        pass
                finally:
                    ShelveFinder._maybe_recursing = False
            except Exception as e:
                print('shelf could not import from {}: {}'.format(
                    path_entry, e))
                raise
            else:
                print('shelf added to import path:', path_entry)
                self.path_entry = path_entry
            return
    
        def __str__(self):
            return '<{} for {!r}>'.format(self.__class__.__name__,
                                          self.path_entry)
    
        def find_module(self, fullname, path=None):
            path = path or self.path_entry
            print('
    looking for {!r}
      in {}'.format(
                fullname, path))
            with shelve.open(self.path_entry, 'r') as db:
                key_name = _get_key_name(fullname, db)
                if key_name:
                    print('  found it as {}'.format(key_name))
                    return ShelveLoader(path)
            print('  not found')
            return None
    
    
    class ShelveLoader:
        """Load source for modules from shelve databases."""
    
        def __init__(self, path_entry):
            self.path_entry = path_entry
            return
    
        def _get_filename(self, fullname):
            # Make up a fake filename that starts with the path entry
            # so pkgutil.get_data() works correctly.
            return os.path.join(self.path_entry, fullname)
    
        def get_source(self, fullname):
            print('loading source for {!r} from shelf'.format(
                fullname))
            try:
                with shelve.open(self.path_entry, 'r') as db:
                    key_name = _get_key_name(fullname, db)
                    if key_name:
                        return db[key_name]
                    raise ImportError(
                        'could not find source for {}'.format(
                            fullname)
                    )
            except Exception as e:
                print('could not load source:', e)
                raise ImportError(str(e))
    
        def get_code(self, fullname):
            source = self.get_source(fullname)
            print('compiling code for {!r}'.format(fullname))
            return compile(source, self._get_filename(fullname),
                           'exec', dont_inherit=True)
    
        def get_data(self, path):
            print('looking for data
      in {}
      for {!r}'.format(
                self.path_entry, path))
            if not path.startswith(self.path_entry):
                raise IOError
            path = path[len(self.path_entry) + 1:]
            key_name = 'data:' + path
            try:
                with shelve.open(self.path_entry, 'r') as db:
                    return db[key_name]
            except Exception:
                # Convert all errors to IOError
                raise IOError()
    
        def is_package(self, fullname):
            init_name = _mk_init_name(fullname)
            with shelve.open(self.path_entry, 'r') as db:
                return init_name in db
    
        def load_module(self, fullname):
            source = self.get_source(fullname)
    
            if fullname in sys.modules:
                print('reusing module from import of {!r}'.format(
                    fullname))
                mod = sys.modules[fullname]
            else:
                print('creating a new module object for {!r}'.format(
                    fullname))
                mod = sys.modules.setdefault(
                    fullname,
                    imp.new_module(fullname)
                )
    
            # Set a few properties required by PEP 302
            mod.__file__ = self._get_filename(fullname)
            mod.__name__ = fullname
            mod.__path__ = self.path_entry
            mod.__loader__ = self
            # PEP-366 specifies that package's set __package__ to
            # their name, and modules have it set to their parent
            # package (if any).
            if self.is_package(fullname):
                mod.__package__ = fullname
            else:
                mod.__package__ = '.'.join(fullname.split('.')[:-1])
    
            if self.is_package(fullname):
                print('adding path for package')
                # Set __path__ for packages
                # so we can find the sub-modules.
                mod.__path__ = [self.path_entry]
            else:
                print('imported as regular module')
    
            print('execing source...')
            exec(source, mod.__dict__)
            print('done')
            return mod
    

    现在,ShelveFinder和ShelveLoader可以用来从一个架子上导入代码。例如,导入刚刚创建的包:

    import sys
    import sys_shelve_importer
    
    
    def show_module_details(module):
        print('  message    :', module.message)
        print('  __name__   :', module.__name__)
        print('  __package__:', module.__package__)
        print('  __file__   :', module.__file__)
        print('  __path__   :', module.__path__)
        print('  __loader__ :', module.__loader__)
    
    
    filename = '/tmp/pymotw_import_example.shelve'
    sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
    sys.path.insert(0, filename)
    
    print('Import of "package":')
    import package
    
    print()
    print('Examine package details:')
    show_module_details(package)
    
    print()
    print('Global settings:')
    print('sys.modules entry:')
    print(sys.modules['package'])
    

    在修改路径之后,第一次导入导入路径,然后将其添加到导入路径中。finder识别货架并返回一个装载器,该装载器用于所有从该货架上的导入。初始包级别导入创建一个新的模块对象,然后使用exec来运行从架子上加载的源。它使用新的模块作为名称空间,以便在源中定义的名称作为模块级属性保存。

    # python sys_study.py 
    Import of "package":
    shelf added to import path: /tmp/pymotw_import_example.shelve
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    creating a new module object for 'package'
    adding path for package
    execing source...
    package imported
    done
    
    Examine package details:
      message    : This message is in package.__init__
      __name__   : package
      __package__: package
      __file__   : /tmp/pymotw_import_example.shelve/package
      __path__   : ['/tmp/pymotw_import_example.shelve']
      __loader__ : <sys_shelve_importer.ShelveLoader object at 0x7f2fbef4c4e0>
    
    Global settings:
    sys.modules entry:
    <module 'package' (<sys_shelve_importer.ShelveLoader object at 0x7f2fbef4c4e0>)>
    

    Custom Package Importing

    加载其他模块和子包以同样的方式进行。

    import sys
    import sys_shelve_importer
    
    
    def show_module_details(module):
        print('  message    :', module.message)
        print('  __name__   :', module.__name__)
        print('  __package__:', module.__package__)
        print('  __file__   :', module.__file__)
        print('  __path__   :', module.__path__)
        print('  __loader__ :', module.__loader__)
    
    
    filename = '/tmp/pymotw_import_example.shelve'
    sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
    sys.path.insert(0, filename)
    
    print('Import of "package.module1":')
    import package.module1
    
    print()
    print('Examine package.module1 details:')
    show_module_details(package.module1)
    
    print()
    print('Import of "package.subpackage.module2":')
    import package.subpackage.module2
    
    print()
    print('Examine package.subpackage.module2 details:')
    show_module_details(package.subpackage.module2)
    

    finder接收加载模块的整个点名,并返回一个配置为从指向架子文件的路径条目加载模块的ShelveLoader。完全限定的模块名被传递给装入器的loadmodule()方法,该方法构造并返回一个模块实例。

    $ python3 sys_shelve_importer_module.py
    
    Import of "package.module1":
    shelf added to import path: /tmp/pymotw_import_example.shelve
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    creating a new module object for 'package'
    adding path for package
    execing source...
    package imported
    done
    
    looking for 'package.module1'
      in /tmp/pymotw_import_example.shelve
      found it as package.module1
    loading source for 'package.module1' from shelf
    creating a new module object for 'package.module1'
    imported as regular module
    execing source...
    package.module1 imported
    done
    
    Examine package.module1 details:
      message    : This message is in package.module1
      __name__   : package.module1
      __package__: package
      __file__   : /tmp/pymotw_import_example.shelve/package.module1
      __path__   : /tmp/pymotw_import_example.shelve
      __loader__ : <sys_shelve_importer.ShelveLoader object at
    0x101376e10>
    
    Import of "package.subpackage.module2":
    
    looking for 'package.subpackage'
      in /tmp/pymotw_import_example.shelve
      found it as package.subpackage.__init__
    loading source for 'package.subpackage' from shelf
    creating a new module object for 'package.subpackage'
    adding path for package
    execing source...
    package.subpackage imported
    done
    
    looking for 'package.subpackage.module2'
      in /tmp/pymotw_import_example.shelve
      found it as package.subpackage.module2
    loading source for 'package.subpackage.module2' from shelf
    creating a new module object for 'package.subpackage.module2'
    imported as regular module
    execing source...
    package.subpackage.module2 imported
    done
    
    Examine package.subpackage.module2 details:
      message    : This message is in package.subpackage.module2
      __name__   : package.subpackage.module2
      __package__: package.subpackage
      __file__   :
    /tmp/pymotw_import_example.shelve/package.subpackage.module2
      __path__   : /tmp/pymotw_import_example.shelve
      __loader__ : <sys_shelve_importer.ShelveLoader object at
    0x1013a6c88>
    

    上面结果不是很懂,直接复制粘贴。

    Reloading Modules in a Custom Importer

    重新加载模块的处理方式略有不同。不再创建新的模块对象,而是重新使用现有的对象。

    import importlib
    import sys
    import sys_shelve_importer
    
    filename = '/tmp/pymotw_import_example.shelve'
    sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
    sys.path.insert(0, filename)
    
    print('First import of "package":')
    import package
    
    print()
    print('Reloading "package":')
    importlib.reload(package)
    

    通过重新使用相同的对象,即使在重载时修改了类或函数定义,也保留了对模块的现有引用。

    $ python3 sys_shelve_importer_reload.py
    
    First import of "package":
    shelf added to import path: /tmp/pymotw_import_example.shelve
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    creating a new module object for 'package'
    adding path for package
    execing source...
    package imported
    done
    
    Reloading "package":
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    reusing module from import of 'package'
    adding path for package
    execing source...
    package imported
    done
    

    复制粘贴。

    Handling Import Errors

    当任何查找器无法找到模块时,ImportError由主导入代码提出。

    import sys
    import sys_shelve_importer
    
    filename = '/tmp/pymotw_import_example.shelve'
    sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
    sys.path.insert(0, filename)
    
    try:
        import package.module3
    except ImportError as e:
        print('Failed to import:', e)
    

    导入过程中的其他错误将被传播。

    $ python3 sys_shelve_importer_missing.py
    
    shelf added to import path: /tmp/pymotw_import_example.shelve
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    creating a new module object for 'package'
    adding path for package
    execing source...
    package imported
    done
    
    looking for 'package.module3'
      in /tmp/pymotw_import_example.shelve
      not found
    Failed to import: No module named 'package.module3'
    

    Package Data

    除了定义用于加载可执行Python代码的API之外,PEP 302定义了用于检索用于分发数据文件,文档和包使用的其他非代码资源的包数据的可选API。 通过实现get_data(),加载程序可以允许调用应用程序来支持检索与程序包关联的数据,而无需考虑如何实际安装程序包(尤其是在不将程序包作为文件存储在文件系统中的情况下)。

    import sys
    import sys_shelve_importer
    import os
    import pkgutil
    
    filename = '/tmp/pymotw_import_example.shelve'
    sys.path_hooks.append(sys_shelve_importer.ShelveFinder)
    sys.path.insert(0, filename)
    
    import package
    
    readme_path = os.path.join(package.__path__[0], 'README')
    
    readme = pkgutil.get_data('package', 'README')
    # Equivalent to:
    #  readme = package.__loader__.get_data(readme_path)
    print(readme.decode('utf-8'))
    
    foo_path = os.path.join(package.__path__[0], 'foo')
    try:
        foo = pkgutil.get_data('package', 'foo')
        # Equivalent to:
        #  foo = package.__loader__.get_data(foo_path)
    except IOError as err:
        print('ERROR: Could not load "foo"', err)
    else:
        print(foo)
    

    get_data() 根据拥有数据的模块或包获取路径,并将资源“file”的内容作为字节字符串返回,如果资源不存在,则会引发IOError。

    $ python3 sys_shelve_importer_get_data.py
    
    shelf added to import path: /tmp/pymotw_import_example.shelve
    
    looking for 'package'
      in /tmp/pymotw_import_example.shelve
      found it as package.__init__
    loading source for 'package' from shelf
    creating a new module object for 'package'
    adding path for package
    execing source...
    package imported
    done
    looking for data
      in /tmp/pymotw_import_example.shelve
      for '/tmp/pymotw_import_example.shelve/README'
    
    ==============
    package README
    ==============
    
    This is the README for ``package``.
    
    looking for data
      in /tmp/pymotw_import_example.shelve
      for '/tmp/pymotw_import_example.shelve/foo'
    ERROR: Could not load "foo"
    

    Importer Cache

    sys.path_importer_cache:一个字典,作为finder对象的缓存。字典的键是传递给sys.path_hooks的路径,对应的值是找到的查找器。如果路径是有效的文件系统路径,但在sys.path_hooks上找不到finder,则会存储None。

    举例
    每次导入模块时搜索所有的钩子都可能变得昂贵。 为了节省时间,sys.path_importer_cache作为路径条目和加载程序之间的映射进行维护,可以使用该值来查找模块。

    import sys
    import os
    
    prefix = os.path.abspath(sys.prefix)
    
    print('PATH:')
    for name in sys.path:
        name = name.replace(prefix, '...')
        print(' ', name)
    
    print()
    print('IMPORTERS:')
    for name, cache_value in sys.path_importer_cache.items():
        if '..' in name:
            name = os.path.abspath(name)
        name = name.replace(prefix, '...')
        print('   {}:  {!r}'.format(name, cache_value))
    

    FileFinder用于文件系统上找到的路径位置。 任何查找器不支持的路径上的位置都与无关联,因为它们不能用于导入模块。

    # python sys_study.py
    PATH:
      /root/study
      .../lib/python35.zip
      .../lib/python3.5
      .../lib/python3.5/plat-linux
      .../lib/python3.5/lib-dynload
      .../lib/python3.5/site-packages
    
    IMPORTERS:
       .../lib/python3.5/encodings:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5/encodings')
       .../lib/python3.5/:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5/')
       .../lib/python3.5/plat-linux:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5/plat-linux')
       .../lib/python35.zip:  None
       .../lib/python3.5/site-packages:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5/site-packages')
       sys_study.py:  None
       .../lib/python3.5/lib-dynload:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5/lib-dynload')
       .../lib/python3.5:  FileFinder('/root/.pyenv/versions/3.5.2/lib/python3.5')
    

    Meta Path

    sys.meta_path通过允许在扫描常规sys.path之前搜索查找器,进一步扩展潜在导入的来源。 元路径上的查找器的API与常规路径相同。 不同的是,metafinder不限于sys.path中的单个条目 - 它可以在任何地方搜索。

    import sys
    import imp
    
    
    class NoisyMetaImportFinder:
    
        def __init__(self, prefix):
            print('Creating NoisyMetaImportFinder for {}'.format(
                prefix))
            self.prefix = prefix
            return
    
        def find_module(self, fullname, path=None):
            print('looking for {!r} with path {!r}'.format(
                fullname, path))
            name_parts = fullname.split('.')
            if name_parts and name_parts[0] == self.prefix:
                print(' ... found prefix, returning loader')
                return NoisyMetaImportLoader(path)
            else:
                print(' ... not the right prefix, cannot load')
            return None
    
    
    class NoisyMetaImportLoader:
    
        def __init__(self, path_entry):
            self.path_entry = path_entry
            return
    
        def load_module(self, fullname):
            print('loading {}'.format(fullname))
            if fullname in sys.modules:
                mod = sys.modules[fullname]
            else:
                mod = sys.modules.setdefault(
                    fullname,
                    imp.new_module(fullname))
    
            # Set a few properties required by PEP 302
            mod.__file__ = fullname
            mod.__name__ = fullname
            # always looks like a package
            mod.__path__ = ['path-entry-goes-here']
            mod.__loader__ = self
            mod.__package__ = '.'.join(fullname.split('.')[:-1])
    
            return mod
    
    
    # Install the meta-path finder
    sys.meta_path.append(NoisyMetaImportFinder('foo'))
    
    # Import some modules that are "found" by the meta-path finder
    print()
    import foo
    
    print()
    import foo.bar
    
    # Import a module that is not found
    print()
    try:
        import bar
    except ImportError as e:
        pass
    

    在搜索sys.path之前,会在元路径上查询每个finder,因此总是有机会拥有一个中央导入器加载模块而不显式修改sys.path。 一旦模块“found”,加载程序API的工作方式与常规加载程序相同。

    # python sys_study.py
    Creating NoisyMetaImportFinder for foo
    
    looking for 'foo' with path None
     ... found prefix, returning loader
    loading foo
    
    looking for 'foo.bar' with path ['path-entry-goes-here']
     ... found prefix, returning loader
    loading foo.bar
    
    looking for 'bar' with path None
     ... not the right prefix, cannot load
    

    Tracing a Program As It Runs

    有两种注入代码来观察程序运行的方式:跟踪和分析。 它们是相似的,但意图用于不同的目的,因此具有不同的约束。 监视程序的最简单但最不有效的方法是通过一个跟踪钩子,可以用于编写调试器,监视代码覆盖或实现许多其他目的。
    通过将回调函数传递给sys.settrace()来修改跟踪钩子。 回调将接收三个参数:正在运行的代码中的堆栈帧,指定通知类型的字符串以及特定于事件的参数值。 下表列出了正在执行程序时发生的不同级别的信息的七种事件类型。

    EventWhen it occursArgument value
    call Before a line is executed None
    line Before a line is executed None
    return Before a function returns The value being returned
    exception After an exception occurs The (exception, value, traceback) tuple
    c_call Before a C function is called The C function object
    c_return After a C function returns None
    c_exception After a C function throws an error None

    Tracing Function Calls

    举例
    每个函数调用之前都会生成一个调用事件。 传递给回调的框架可以用于查找正在调用哪个函数以及哪个函数。

    def trace_calls(frame, event, arg):
        if event != 'call':
            return
        co = frame.f_code
        func_name = co.co_name
        if func_name == 'write':
            # Ignore write() calls from printing
            return
        func_line_no = frame.f_lineno
        func_filename = co.co_filename
        caller = frame.f_back
        caller_line_no = caller.f_lineno
        caller_filename = caller.f_code.co_filename
        print('* Call to', func_name)
        print('*  on line {} of {}'.format(
            func_line_no, func_filename))
        print('*  from line {} of {}'.format(
            caller_line_no, caller_filename))
        return
    
    
    def b():
        print('inside b()
    ')
    
    
    def a():
        print('inside a()
    ')
        b()
    
    
    sys.settrace(trace_calls)
    a()
    

    此示例忽略对write()的调用,由print写入sys.stdout使用。

    # python sys_study.py
    * Call to a
    *  on line 28 of sys_study.py
    *  from line 34 of sys_study.py
    inside a()
    
    * Call to b
    *  on line 24 of sys_study.py
    *  from line 30 of sys_study.py
    inside b()
    

    Tracing Inside Functions

    跟踪钩子可以返回新的作用域(本地跟踪函数)中使用的新钩子。 例如,可以将跟踪控制为仅在某些模块或功能中逐行运行。

    import functools
    import sys
    
    
    def trace_lines(frame, event, arg):
        if event != 'line':
            return
        co = frame.f_code
        func_name = co.co_name
        line_no = frame.f_lineno
        print('*  {} line {}'.format(func_name, line_no))
    
    
    def trace_calls(frame, event, arg, to_be_traced):
        if event != 'call':
            return
        co = frame.f_code
        func_name = co.co_name
        if func_name == 'write':
            # Ignore write() calls from printing
            return
        line_no = frame.f_lineno
        filename = co.co_filename
        print('* Call to {} on line {} of {}'.format(
            func_name, line_no, filename))
        if func_name in to_be_traced:
            # Trace into this function
            return trace_lines
        return
    
    
    def c(input):
        print('input =', input)
        print('Leaving c()')
    
    
    def b(arg):
        val = arg * 5
        c(val)
        print('Leaving b()')
    
    
    def a():
        b(2)
        print('Leaving a()')
    
    
    tracer = functools.partial(trace_calls, to_be_traced=['b'])
    sys.settrace(tracer)
    a()
    

    在这个例子中,函数列表保存在变量:py``to_be_traced``中,所以当trace_calls() 运行时,它可以返回trace_lines() 来启用b() 中的跟踪。

    # python sys_study.py
    * Call to a on line 43 of sys_study.py
    * Call to b on line 37 of sys_study.py
    *  b line 38
    *  b line 39
    * Call to c on line 32 of sys_study.py
    input = 10
    Leaving c()
    *  b line 40
    Leaving b()
    Leaving a()
    

    Watching the Stack

    使用钩子的另一个有用的方法是跟上要调用哪些函数,以及它们的返回值。 要监视返回值,请注意返回事件。

    import sys
    
    
    def trace_calls_and_returns(frame, event, arg):
        co = frame.f_code
        func_name = co.co_name
        if func_name == 'write':
            # Ignore write() calls from printing
            return
        line_no = frame.f_lineno
        filename = co.co_filename
        if event == 'call':
            print('* Call to {} on line {} of {}'.format(
                func_name, line_no, filename))
            return trace_calls_and_returns
        elif event == 'return':
            print('* {} => {}'.format(func_name, arg))
        return
    
    
    def b():
        print('inside b()')
        return 'response_from_b '
    
    
    def a():
        print('inside a()')
        val = b()
        return val * 2
    
    
    sys.settrace(trace_calls_and_returns)
    a()
    

    本地跟踪功能用于查看返回事件,因此当调用函数时,trace_calls_and_returns()需要返回对其自身的引用,因此可以监视返回值。

    # python sys_study.py
    * Call to a on line 26 of sys_study.py
    inside a()
    * Call to b on line 21 of sys_study.py
    inside b()
    * b => response_from_b 
    * a => response_from_b response_from_b
    

    Exception Propagation

    可以通过在本地跟踪函数中查找异常事件来监视异常。 发生异常时,使用包含异常类型,异常对象和追溯对象的元组调用跟踪钩子。

    import sys
    
    
    def trace_exceptions(frame, event, arg):
        if event != 'exception':
            return
        co = frame.f_code
        func_name = co.co_name
        line_no = frame.f_lineno
        exc_type, exc_value, exc_traceback = arg
        print(('* Tracing exception:
    '
               '* {} "{}"
    '
               '* on line {} of {}
    ').format(
                   exc_type.__name__, exc_value, line_no,
                   func_name))
    
    
    def trace_calls(frame, event, arg):
        if event != 'call':
            return
        co = frame.f_code
        func_name = co.co_name
        if func_name in TRACE_INTO:
            return trace_exceptions
    
    
    def c():
        raise RuntimeError('generating exception in c()')
    
    
    def b():
        c()
        print('Leaving b()')
    
    
    def a():
        b()
        print('Leaving a()')
    
    
    TRACE_INTO = ['a', 'b', 'c']
    
    sys.settrace(trace_calls)
    try:
        a()
    except Exception as e:
        print('Exception handler:', e)
    

    注意限制应用本地功能的地方,因为格式化错误消息的一些内部会产生并忽略自己的异常。 跟踪钩子可以看到每个异常,调用者是否捕获并忽略它。

    # python sys_study.py
    * Tracing exception:
    * RuntimeError "generating exception in c()"
    * on line 28 of c
    
    * Tracing exception:
    * RuntimeError "generating exception in c()"
    * on line 32 of b
    
    * Tracing exception:
    * RuntimeError "generating exception in c()"
    * on line 37 of a
    
    Exception handler: generating exception in c()
    
  • 相关阅读:
    新版新概念英语14册(英音+美音)MP3打包下载
    最近学习目标
    本人兼职C#,WinForm ,数据库,MapXtreme, Arcgis Engine 相关的开发
    everything is good on
    人之惰性思考
    css hacks 以及解决办法
    hibernate学习(1)——核心接口
    【原】可关闭,可重播的flash弹出广告代码
    PNG在IE6下透明问题的解决办法
    JSP解压ZIP压缩文件
  • 原文地址:https://www.cnblogs.com/cuchadanfan/p/7430089.html
Copyright © 2011-2022 走看看