深入理解Python中的__builtin__和__builtins__

0.说明

        这里的说明主要是以Python 2.7为例,因为在Python 3+中,__builtin__模块被命名为builtins,下面主要是探讨Python 2.x中__builtin__模块和__builtins__模块的区别和联系。

1.名称空间(Namespace)

        首先不得不说名称空间,因为名称空间是Python中非常重要的一个概念,所谓名称空间,其实指的是名称(标识符)到对象的映射。

        在一个正常的Python程序的执行过程中,至少存在两个名称空间:

  • 内建名称空间

  • 全局名称空间

        如果定义了函数,则还会有局部名称空间,全局名称空间一般由在程序的全局变量和它们对应的映射对象组成,而局部名称空间则在函数内部由函数局部变量和它们对应的映射对象组成,这里关键的是内建名称空间,它到底是怎么产生的?

2.内建函数

        在启动Python解释器之后,即使没有创建任何的变量或者函数,还是会有许多函数可以使用,比如:

>>> abs(-1)
1
>>> max(1, 3)
3

        我们把这些函数称为内建函数,是因为它们不需要我们程序员作任何定义,在启动Python解释器的时候,就已经导入到内存当中供我们使用:

>>> abs
<built-in function abs>
>>> 
>>> max
<built-in function max>

3.内建名称空间与__builtins__

        那么内建函数也是函数,虽然我们没有人为导入这些,但是正如前面所说,在启动Python解释器的时候,会自动帮我们导入,那么内建函数存在于哪里呢?

        其实准确地来说,是Python解释器在启动的时候会首先加载内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身(注意区分函数名称和函数对象的区别)。这些名称空间由__builtins__模块中的名字构成:

>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

        可以看到有一个__builtins__的模块名称,这个模块本身定义了一个名称空间,即内建名称空间,我们不妨dir一下:

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

        会看到我们熟悉的内建函数的名称,如list、dict等,当然还有一些异常和其它属性。

4.__builtins__与__builtin__的简单区别

        既然内建名称空间由__builtins__模块中的名称空间定义,那么是不是也意味着内建名称空间中所对应的这些函数也是在__builtins__模块中实现的呢?

        显然不是的,我们可以在解释器中直接输入__builtins__:

>>> __builtins__
<module '__builtin__' (built-in)>

        从结果中可以看到,__builtins__其实还是引用了__builtin__模块而已,这说明真正的模块是__builtin__,也就是说,前面提到的内建函数其实是在内建模块__builtin__中定义的,即__builtins__模块包含内建名称空间中内建名字的集合(因为它引用或者说指向了__builtin__模块),而真正的内建函数、异常和属性来自__builtin__模块。也就是说,在Python中,其实真正是只有__builtin__这个模块,并不存在__builtins__这个模块:

>>> import __builtin__
>>> import __builtins__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named __builtins__

        可以看到,导入__builtin__模块并没有问题,但导入__builtins__模块时就会提示不存在,这充分说明了前面的结论,现在再次总结如下:

  • 在Python中并没有__builtins__这个模块,只有__builtin__模块,__builtins__模块只是在启动Python解释器时,解释器为我们自动创建的一个到__builtin__模块的引用

        当然,至于这种引用到底是怎么样,可以看下面的深入区别。

5.__builtins__与__builtin__的深入区别

        上面只是大概说了下__builtins__与__builtin__两个模块的简单区分而已,其实深究下去,要分成下面所提及的两种情况。

(1)在主模块__main__中

        其实我们在使用Python交互器的时候就是在主模块中进行操作,可以做如下验证:

>>> print __name__
__main__

        在这种情况,__builtins__与__builtin__是完全一样的,它们指向的都是__builtin__这个内建模块:

>>> import __builtin__
>>> __builtin__
<module '__builtin__' (built-in)>
>>> __builtins__
<module '__builtin__' (built-in)>
>>> __builtin__.__name__
'__builtin__'
>>> __builtins__.__name__
'__builtin__'
>>> __builtins__ == __builtin__
True
>>> __builtins__ is __builtin__
True
>>> id(__builtins__)
140295127423752
>>> id(__builtin__)
140295127423752

        可以看到,这时候__builtins__和__builtin__是完全一样的,它们都指向了同一个模块对象,其实这也是Python中引用传递的概念。

        其实这种情况跟我们创建一个变量并对它做一次引用传递时的情况是一样的,可以做如下测试:

>>> def func():
...     print 'test'
... 
>>> func
<function func at 0x7f99012bdc08>
>>> funcs
<function func at 0x7f99012bdc08>
>>> func.__name__
'func'
>>> funcs.__name__
'func'
>>> funcs == func
True
>>> funcs is func
True
>>> id(funcs)
140295126375432
>>> id(func)
140295126375432

        显然,这完全验证了我们上面的结论。

(2)不是在主模块中

        如果不是在主模块中使用__builtins__,这时候,__builtins__只是对__builtin__.__dict__的一个简单引用而已,可以通过下面的测试来验证说明。

        先创建一个test.py模块,后面我们需要在Python交互器中导入它,那么这时候对于test模块来说,它就不是主模块了。如下:

#!/usr/bin/env python

import __builtin__


print 'Module name:', __name__


print '*==test __builtin__ and __builtins__==*'
print '__builtin__ == __builtins__', __builtin__ == __builtins__
print '__builtin__ is __builtins__', __builtin__ is __builtins__
print 'id(__builtin__)', id(__builtin__)
print 'id(__builtins__)', id(__builtins__)

print '='*50

print '*==test __builtin__.__dict__ and __builtins__==*'
print '__builtin__.__dict__ == __builtins__', __builtin__.__dict__ == __builtins__
print '__builtin__ is __builtins__', __builtin__.__dict__ is __builtins__
print 'id(__builtin__)', id(__builtin__.__dict__)
print 'id(__builtins__)', id(__builtins__)

        在Python交互器中导入上面这个test模块,如下:

>>> import test
Module name: test
*==test __builtin__ and __builtins__==*
__builtin__ == __builtins__ False
__builtin__ is __builtins__ False
id(__builtin__) 140592847690504
id(__builtins__) 140592847925608
==================================================
*==test __builtin__.__dict__ and __builtins__==*
__builtin__.__dict__ == __builtins__ True
__builtin__ is __builtins__ True
id(__builtin__) 140592847925608
id(__builtins__) 140592847925608

        可以看到输出的结果跟我们想的是完全一样的,即这时候__builtins__其实是对__builtin__.__dict__模块的引用。

6.总结

        不管怎么说,在启动Python解释器或运行一个Python程序时,内建名称空间都是从__builtins__模块中加载的,只是__builtins__本身是对Python内建模块__builtin__的引用,而这种引用又分下面两种情况:

  • 如果是在主模块__main__中,__builtins__直接引用__builtin__模块,此时模块名__builtins__与模块名__builtin__指向的都是同一个模块,即<builtin>内建模块(这里要注意变量名和对象本身的区别)

  • 如果不是在主模块中,那么__builtins__只是引用了__builtin__.__dict__

        如果需要转载本文,请注明来自香飘叶子的51cto博客

        在写本文的时候,参考了下面的文章,只是这些文章并没有给出像上面我这样的测试,链接如下:

https://docs.python.org/2/library/__builtin__.html?highlight=_builtin__#module-__builtin__

http://www.52ij.com/jishu/665.html

    原文作者:xpleaf
    原文地址: https://blog.51cto.com/xpleaf/1764849
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞