异常
Python有66个内置的异常(exception)类,每个类都旨在供用户,标准库和其他所有人使用,作为解释和捕获代码中错误的有意义的方法。
为了确切解释为什么Python中有单独的异常类,这里有一个快速示例:
代码语言:javascript复制def fetch_from_cache(key):
"""Returns a key's value from cached items."""
if key is None:
raise ValueError('key must not be None')
return cached_items[key]
def get_value(key):
try:
value = fetch_from_cache(key)
except KeyError:
value = fetch_from_api(key)
return values
专注于函数get_value
。如果key存在,它应该返回一个缓存值,否则从API获取数据。
该函数中可能发生 3 件事:
- • 如果
key
不在缓存中,则尝试访问cached_items[key]
将引发一个KeyError
.这会在try
块中捕获,并进行 API 调用以获取数据。 - • 如果
key
存在于缓存中,则按原样返回。 - • 还有第三种情况,其中
key
是None
。
如果键是None
,则fetch_from_cache
引发 一个ValueError
,指示提供给此函数的值不合适。由于该try
块只捕获 KeyError
,因此此错误直接显示给用户。
如果没有预定义ValueError
和KeyError
,就不能这样区分错误类型。
关于异常的更多内容,如异常的子类化,Exception几乎是任何异常的父类、BaseException是所有异常的父类。这里不在赘述。
现在我应该指出,上面输出中的所有大写值都不是异常类型,实际上,Python中有另一种类型的内置对象是大写的:常量。让我们来谈谈这些。
常量
有5种内置的常量(constants):True, False, None, Ellipsis,和NotImplemented。
Ture, False 和 None 出现的很多。Ellipsis(省略号)很有意思,它有两种形式:Ellipsis
和...
。最常出现在类型注解(annotations)和一些切片操作中。 NotImplemented
用于类内的运算符(operator)定义,当你想要告诉Python类的运算符还没有具体定义。
Python中的对象可以通过实现__add__
实现对
运算符的支持。__iadd__
对 =
提供支持。等等。例如:
class MyNumber:
def __add__(self, other):
return other 42
代码语言:javascript复制>>> num = MyNumber()
>>> num 3
45
>>> num 100
142
补充 右运算符。上面实现的__add__ 只有对象在运算符左侧才有效。
__radd__
是右运算符,添加后就可以计算3 num
。
如果你想要只在整数进行加法,而不包括浮点数,这是就用到了NotImplemented
:
class MyNumber:
def __add__(self, other):
if isinstance(other, float):
return NotImplemented
return other 42
关于常量的一个奇怪的事实是,它们甚至不是在Python中实现的,而是直接在C代码中实现的。
globals
内置的输出有一些奇怪的东西,例如__spec__
,__loader__
,__debug__
等。
这些实际上不是builtins
模块所独有的。这些属性都存在于Python中每个模块的全局范围内,因为它们是模块属性。它们保存有关导入所需的模块的信息。让我们来看看它们: __name__
包含模块的名称。例如builtins.__name__
的值是字符串'builtins'
。当你运行一个Python文件,也是在运行一个模块,此时该模块的名称为__main__
。这就结束了为什么 if __name__ == '__main__'
内的语句会被执行。
__doc__
包含模块的文档字符串。这是执行help(module_name)
时显示为模块说明的内容。
__package__
此模块所属的包。对于顶级模块,它与__name__
相同。对于子模块,它是包的__name__
。例如:
>>> import urllib.request
>>> urllib.__package__
'urllib'
>>> urllib.request.__name__
'urllib.request'
>>> urllib.request.__package__
'urllib'
__spec__
这是指模块的空间。它包含元数据,例如模块名称,它是哪种模块,以及它的创建和加载方式。
__loader__
__loader__
设置为导入在加载模块时使用的加载程序对象。这个特定的模块在_frozen_importlib
模块中定义,并且是用于导入内置模块的内容。
__import__
__import__
是定义import
语句在 Python 中的工作方式的内置函数。
np = __import__('numpy') # Same as doing 'import numpy as np'
__debug__
这是 Python 中的一个全局常量值,几乎总是设置为 True。它指的是Python在调试模式下运行。默认情况下,Python始终在调试模式下运行。
此外,__debug__
, True
, False
和None
是 Python 中唯一的真常量,即这 4 个是 Python 中唯一不能用新值覆盖的全局变量。
__build_class__
此全局变量是在 Python 3.1 中添加的,以允许类定义接受任意位置和关键字参数。为什么这是一个功能有很长的技术原因,它涉及元类等高级主题,所以不幸的是,我不会解释为什么它存在。
__cached__
导入__cached__
模块时,该属性存储该模块的已编译 Python 字节码的缓存文件的路径。你可能会惊讶,Python也要编译吗?是的。Python被编译。事实上,所有的Python代码都是被编译的,但不是机器代码 ,而是字节码(bytecode)。让我通过解释Python如何运行你的代码来解释这一点。
- 1. 获取源文件,并解析为语法树。保证语法正确。
- 2. 将语法树编译为字节码。字节码是Python虚拟机(virtual machine,VM)的一组微指令。这个“虚拟机”是Python的解释器逻辑所在的位置。它本质上是在您的机器上模拟一个非常简单的基于堆栈的计算机,以便执行您编写的Python代码。
- 3. 然后,在 Python VM 上运行此代码形式的代码。字节码指令很简单,例如从当前堆栈中推送和弹出数据。当这些指令一个接一个地运行时,这些指令中的每一个都会执行整个程序。
现在,由于上面的“编译为字节码”步骤在导入模块时需要花费大量时间,因此Python将字节码存储(编组)到.pyc
文件中,并将其存储在名为__pycache__
的文件夹中。然后,导入模块的__cached__
参数指向此.pyc
文件。你可以直接在Python代码中运行或导入一个.pyc
文件,就像运行一个.py
文件。