在网站上签失业保险怎样做,代码给wordpress添加图片不显示,北京市朝阳区网站制作,林州建筑网文章目录 九、异常处理9.1 异常堆栈跟踪9.2 异常处理的基本语法9.3 异常类及异常处理机制9.4 自定义异常类9.5 raise触发异常#xff0c;及其与except的区别9.6 异常链9.7 处理多个不相关的异常9.8 用注释细化异常情况9.9 异常处理的最佳实践 Python 3.11.5 中文文档、cpython… 文章目录 九、异常处理9.1 异常堆栈跟踪9.2 异常处理的基本语法9.3 异常类及异常处理机制9.4 自定义异常类9.5 raise触发异常及其与except的区别9.6 异常链9.7 处理多个不相关的异常9.8 用注释细化异常情况9.9 异常处理的最佳实践 Python 3.11.5 中文文档、cpython源代码错误和异常、内置异常 九、异常处理
9.1 异常堆栈跟踪 异常是指在程序执行过程中发生的不寻常或错误的事件。当程序在运行中发生异常时Python会生成一个异常对象其中包含了关于异常的信息比如异常类型、发生异常的代码位置等。 异常堆栈跟踪是一份详细的报告它记录了异常是如何传播的从异常发生的地方开始一直到最初的异常引发点。这份报告以栈的形式呈现因此被称为“堆栈跟踪”。下面是一个常见的除0错误示例
result 10 / 0 # 这会引发一个除零异常
result---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[16], line 1
---- 1 result 10 / 0 # 这会引发一个除零异常2 resultZeroDivisionError: division by zero在这个示例中会发生以下操作 解析器Python解释器会尝试解析您的代码以执行它但在遇到除0错误时解析器会停止执行并生成一个错误消息。 这个错误消息是一个典型的Python异常堆栈跟踪 ZeroDivisionError: 这是异常的类型表示发生了一个除零错误。Traceback (most recent call last): 这一行告诉我们以下内容是异常堆栈跟踪的开始。Cell In[16], line 1: 这一行显示了发生异常的代码位置。具体来说在 “Cell In[16]” 中的第1行发生了异常。result 10 / 0: 这是尝试执行的代码它试图将10除以0但由于除零错误引发了异常。result: 这是在异常发生后的下一行的代码但由于异常在前一行引发所以这一行实际上不会执行。 总结起来这个错误消息告诉我们在尝试将10除以0时发生了除零错误ZeroDivisionError。异常堆栈跟踪还显示了异常发生的确切代码位置这对于调试问题非常有帮助。
下面是一个稍微复杂一点的堆栈跟踪示例
Traceback (most recent call last):File main.py, line 10, in moduledivision_result divide(10, 0)File main.py, line 6, in divideresult numerator / denominator
ZeroDivisionError: division by zeroDuring handling of the above exception, another exception occurred:Traceback (most recent call last):File main.py, line 12, in modulelog_exception()File main.py, line 8, in log_exceptionraise Exception(An error occurred while logging the exception.)
Exception: An error occurred while logging the exception.在这个示例中发生了两个异常并且堆栈跟踪显示了异常传播的路径 最初的异常发生在 “main.py” 文件的第10行其中我们尝试调用 divide(10, 0) 函数但由于除以零引发了 ZeroDivisionError 异常。这是堆栈跟踪的起始点。 堆栈跟踪接着显示了异常处理程序的执行情况。在 log_exception() 函数中我们尝试记录异常但由于代码中引发了另一个异常所以出现了第二个异常。第二个异常是一个通用的 Exception并且在堆栈跟踪中显示了详细信息。
从上述示例可以看出异常堆栈跟踪有以下几个重要作用 定位异常引发点: 它告诉我们异常是在哪个位置引发的。这对于找到问题的根本原因至关重要。 追踪异常传播路径: 堆栈跟踪显示了异常是如何传播的从引发异常的地方到最终导致程序中止的地方这有助于我们理解异常如何影响程序的执行流程。 有时候堆栈跟踪可能会非常复杂特别是在大型项目中但基本的原则是相同的从底部向上查看以了解异常的源头和传播路径。 调试信息: 堆栈跟踪通常包含有关引发异常的代码的行号和文件名这有助于我们快速定位到异常发生的代码行以便进行调试。
9.2 异常处理的基本语法
为了避免异常导致程序终止我们引入了异常处理操作它可以
防止程序崩溃当程序遇到异常时异常处理机制可以防止程序因错误而突然停止运行从而保持程序的稳定性。提供信息异常处理允许程序员捕获和记录异常的详细信息以便调试和分析问题。执行恢复操作在某些情况下程序可以通过异常处理来执行恢复操作以使程序继续正常运行或采取适当的措施来处理异常情况
在Python等编程语言中异常处理通常通过以下关键字和结构来实现 trytry语句通常包含可能引发异常的代码当尝试执行时若无异常则跳过 except 子句执行后续语句 except捕获异常并定义相应的异常处理逻辑而不会导致程序的终止。 在try代码块中发生异常时except中的语句会被执行即判断异常类型是否与 except 关键字后指定的异常相匹配 如果匹配上则会执行 对应的except 子句然后跳过 try/except 代码块继续执行后续代码。如果不匹配则它会被传递到外部的 try 语句中如果没有找到处理程序则抛出一个 未处理异常 执行将终止并输出错误信息。try 语句可以有多个 except 子句 来为不同的异常指定处理程序。 但最多只有一个处理程序会被执行 else处理没有异常的情况所以else 子句必须放在所有 except 子句 之后。若try块中的代码成功执行且没有异常被引发那么else中的代码块会被执行。 finally无论是否发生异常都会执行 finally语句块即使在try或except块中有return语句也不例外。finally语句通常用于确保资源的正确清理例如关闭文件或释放网络连接。 通过合理使用这些结构程序员可以在异常发生时采取适当的措施以确保程序的稳定性和可维护性编写出更健壮的应用程序。 下面是一个简单的文件操作异常处理示例我们尝试打开文件并在except块中自定义了相应的异常处理逻辑
try:# 将文件操作代码包装在try语句中以捕获可能发生的异常with open(file.txt, r) as file:content file.read()
except FileNotFoundError:# 处理文件不存在的情况print(文件不存在。)
except PermissionError:# 处理权限问题print(没有权限执行此操作。)
except Exception as e:# 处理其他异常print(发生错误:, str(e))
else:# 如果没有异常发生执行这里的代码print(文件操作成功。)
finally:# 无论是否发生异常都会执行这里的代码print(文件操作完成。)在except块中你也可以根据需要捕获特定类型的异常。这使您可以更精确地处理不同类型的问题。例如如果只关心文件不存在的异常可以捕获FileNotFoundError。
try:with open(file.txt, r) as file:content file.read()
except FileNotFoundError:print(文件不存在。)except 子句 可以用带圆括号的元组来指定多个异常例如:
except (RuntimeError, TypeError, NameError):passfinally 是try语句的可选子句用于定义在所有情况下都必须要执行的清理操作例如释放文件等外部资源且不论 try 语句是否触发异常都会执行 finally 子句。以下内容介绍了几种比较复杂的触发异常情景 如果 finally 子句中包含 break、continue 或 return 等语句异常将不会被重新引发。 def example():try:x 1 / 0 # 会引发异常except ZeroDivisionError:print(Caught an exception)finally:print(Finally block executed)return 42 # 返回值来自 finally 子句不是来自 try 子句result example()
print(Result:, result) Caught an exception
Finally block executed
Result: 42如果执行 try 语句时遇到 break,、continue 或 return 语句则 finally 子句在执行 break、continue 或 return 语句之前执行。 def example():try:print(In try block)return 10finally:print(In finally block)result example()
print(Result:, result) In try block
In finally block
Result: 10如果 finally 子句中包含 return 语句则返回值来自 finally 子句的某个 return 语句的返回值而不是来自 try 子句的 return 语句的返回值。 def example():try:return 10finally:return 20 # 返回值来自 finally 子句不是来自 try 子句result example()
print(Result:, result) Result: 20下面再举一个复杂的例子说明
def divide(x, y):try:result x / yexcept ZeroDivisionError:print(division by zero!)else:print(result is, result)finally:print(executing finally clause)divide(2, 1)
result is 2.0
executing finally clausedivide(2, 0)
division by zero!
executing finally clausedivide(2, 1)
executing finally clause
Traceback (most recent call last):File stdin, line 1, in moduleFile stdin, line 3, in divide
TypeError: unsupported operand type(s) for /: str and str9.3 异常类及异常处理机制 在Python中异常是通过异常类的实例来表示的。异常类是一种用于组织和表示不同类型异常情况的方式。每个异常类都代表一种特定类型的错误或异常情况例如ValueError表示数值错误。当程序中发生异常情况时Python会创建一个与该异常情况相关的异常类的实例并将其引发raise。 Python为常见的错误情况定义了许多内置异常以便在程序执行过程中处理错误和异常情况这些类都继承自基类BaseException。整个异常类的层级结构如下
BaseException├── BaseExceptionGroup├── GeneratorExit├── KeyboardInterrupt├── SystemExit└── Exception├── ArithmeticError│ ├── FloatingPointError│ ├── OverflowError│ └── ZeroDivisionError├── AssertionError├── AttributeError├── BufferError├── EOFError├── ExceptionGroup [BaseExceptionGroup]├── ImportError│ └── ModuleNotFoundError├── LookupError│ ├── IndexError│ └── KeyError├── MemoryError├── NameError│ └── UnboundLocalError
...
...下面对部分异常类进行解释
主要异常类描述BaseException所有内置异常类的基类通常不直接使用而是派生其他异常类GeneratorExit用于当生成器或协程在执行中遇到该异常时自动关闭。通常用于清理生成器或协程中的资源KeyboardInterrupt当用户在命令行中按下CtrlC时会引发此异常中断正在执行的程序。SystemExit当调用sys.exit()函数时引发的异常用于退出Python解释器。Exception所有内置的非系统退出类的基类所有用户自定义异常也应当派生自此类
常见异常类描述SyntaxError语法错误IndentationError缩进错误ImportError导入模块不存在NameError名称错误当尝试访问未定义的变量或函数时引发。AttributeError属性错误当尝试访问对象没有的属性或方法时引发。TypeError类型错误当操作不兼容的数据类型时引发。KeyError使用字典中不存在的键时引发。ValueError当函数收到的参数类型正确但值不合法时引发。ZeroDivisionError零除错误当试图除以零时引发。FileNotFoundError文件未找到错误PermissionError文件权限不足错误IndexError索引错误当尝试访问不存在的列表元素时引发。MemoryError内存耗尽异常。通常在处理大型数据集时发生。 类有继承这一特性所以若发生的异常与 except 子句中的类是同一个类或是它的基类时则该类与该异常相兼容反之则不成立。简单来说就是子类兼容父类的异常父类不兼容子类的异常。下面举例说明
class B(Exception):passclass C(B):passclass D(C):passfor cls in [B, C, D]:try:raise cls()except D:print(D)except C:print(C)except B:print(B)上面的代码将依次打印 B, C, D。而如果将except子句顺序改为 try:raise cls()except B:print(B)except C:print(C)except D:print(D)则会打印出B,B,B。这是因为C和D都是B的子类能捕获B类的错误肯定也会捕获C类和D类所以三次遍历都会被except B捕获。
9.4 自定义异常类 内置异常类可以被子类化以定义新的异常但是在创建自定义异常类时推荐从Exception类或其子类来继承而不是直接继承自BaseException类。这是因为Exception 是所有非致命异常的基类而BaseException类和它的其它子类表示的都是非常严重的异常通常会导致程序终止也就不需要进行处理。 Exception 类具有一个构造函数 __init__(self, *args)该构造函数允许传递一个或多个参数通常是异常消息。你可以在派生的自定义异常类中扩展此构造函数以添加额外的参数如错误代码、时间戳等。 异常类命名一般都以 “Error” 结尾与标准异常的命名保持一致。异常类应当保持简单只提供一些属性允许相应的异常处理程序提取有关错误的信息。许多标准模块都定义了自己的异常以报告他们定义的函数中可能出现的错误。
自定义属性以提供有关错误的更多上下文信息
import datetime# 自定义一个简单的异常类继承自 Exception
class CustomError(Exception):# message用于提供异常的描述性消息error_code用于指定自定义错误代码。def __init__(self, message, error_code):# super调用父类 Exception 的构造函数init并传递 message 参数以初始化异常的消息。super().__init__(message)self.error_code error_code# 获取当前时间戳并将其赋值给异常对象的 timestamp 属性self.timestamp datetime.datetime.now()# 将异常对象的 file_name 属性初始化为 None目前还没有指定文件名。self.file_name Nonetry:# 使用 raise 语句抛出了一个 CustomError 异常的实例。# 异常的消息是 This is a custom exception错误代码是 1001。raise CustomError(This is a custom exception, 1001)
except CustomError as ce:# 打印了异常的消息、错误代码和时间戳print(fCustom error occurred: {ce}, Error code: {ce.error_code}, Timestamp: {ce.timestamp})# 自定义FileNotFoundError类用于捕获异常时获取文件名信息
class FileNotFoundError(Exception):def __init__(self, file_name):super().__init__(fFile not found: {file_name})file_name example.txt
try:# 尝试打开文件with open(file_name, r) as file:content file.read()
except FileNotFoundError as fnfe:print(fnfe)自定义方法执行与异常处理相关的操作例如记录异常、发送通知、自动修复等。
class CustomError(Exception):def __init__(self, message, error_code):super().__init__(message)self.error_code error_codedef log_error(self):# 将异常信息记录到日志文件# 打开error.log 日志文件模式为 a追加with open(error.log, a) as log_file:log_file.write(fError Code: {self.error_code}, Message: {str(self)}\n)def notify_admin(self):# 发送电子邮件或其他通知给管理员pass
try:raise CustomError(This is a custom exception, 1001)
except CustomError as ce:ce.log_error()ce.notify_admin()print(fCustom error occurred: {ce}, Error code: {ce.error_code})9.5 raise触发异常及其与except的区别 除了使用try...except语句你也可以使用 raise 语句来手动引发指定的异常其唯一的参数就是要触发的异常但必须是异常实例或异常类。 引发异常实例您可以使用 raise 语句引发已经创建的某个异常类的实例这样做的目的是在特定的代码位置引发异常并且可以提供关于异常的详细信息。 # 创建一个自定义异常类
class CustomError(Exception):def __init__(self, message):super().__init__(message)try:# 创建异常实例并引发error_instance CustomError(This is a custom exception)raise error_instance
except CustomError as ce:print(fCaught custom exception: {ce})引发异常类当您想引发某种标准异常时但不需要提供额外的异常信息时可以简单地引发异常类。 try:# 引发内置的 ValueError 异常类raise ValueError(This is a ValueError)
except ValueError as ve:print(fCaught ValueError: {ve})某些时候我们想要在捕获异常后继续将同一异常传播到更高级别的异常处理程序或者让其他部分的代码处理它。这时我们可以使用 raise 语句来重新引发已捕获的异常
try:result 10 / 0 # 这会引发一个除零异常
except ZeroDivisionError:print(Divided by zero)# 当异常在 except 块中被捕获后可以使用 raise 语句重新引发相同的异常以便允许其他代码或更高级别的异常处理程序来处理异常。raise ---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
Cell In[14], line 21 try:
---- 2 result 10 / 0 # 这会引发一个除零异常3 except ZeroDivisionError:4 print(Divided by zero)ZeroDivisionError: division by zeroraise和except都用于处理异常但它们的角色和使用场景是不同的 except用于捕获和处理在 try 块中引发的异常。这是一种被动的操作它用于响应异常的发生。你可以在except 语句中定义异常处理逻辑例如指定不同的 except 子句处理不同类型的异常。如果不处理异常程序将终止并显示异常信息。 raise用于在代码中明确指出异常情况的发生这是一种主动的操作你可以在任何地方使用它来引发异常。raise 通常用于异常的起始点它告诉程序出现了异常情况然后程序的控制权会被传递给异常处理程序except 子句。 下面是一个具体的示例。假设一个函数接受一个数字作为参数它在参数为负数时应该引发一个自定义异常。这里就是 raise 的使用场景它用于主动引发异常
def divide_positive_numbers(a, b):if a 0 or b 0:raise ValueError(Both numbers should be positive)return a / b然后在调用这个函数时可以使用 try 和 except 来捕获和处理这个异常
try:result divide_positive_numbers(-5, 2)
except ValueError as e:print(An error occurred:, e)在这个示例中raise 用于在函数内部检测到不符合要求的输入时引发异常而 except 用于捕获和处理这个异常。这就是 raise 和 except 在异常处理中的不同用途和角色
9.6 异常链 异常链是指在 Python 中一个异常引发另一个异常的情况从而形成一个异常的嵌套链条。这个链条可以用来跟踪异常的源头以便更好地理解程序中发生的错误特别是当异常发生在复杂的程序堆栈中时。通过正确使用 raise 语句的 from 子句可以更好地组织和管理异常链。下面是常见的异常链操作 使用 from子句触发异常链 你可以在 raise 语句中使用 from 子句将一个异常视为另一个异常的原因。这样新引发的异常将成为前一个异常的 “直接原因”。 # 示例1异常的直接后果和异常链
try:# 引发一个异常raise ValueError(This is the first exception)
except ValueError as ve:try:# 引发另一个异常将前一个异常作为直接的原因raise TypeError(This is the second exception) from veexcept TypeError as te:print(Caught TypeError:, te)print(Direct cause:, te.__cause__)在这个示例中我们首先引发一个 ValueError 异常然后在 except 块中引发一个 TypeError 异常并将前一个异常 ve 作为直接的原因。这样我们就可以在 TypeError 异常中通过 te.__cause__ 访问到直接的原因即 ValueError 异常。 使用 from None 禁用自动异常链如果在 raise 语句的 from 子句中使用 None则会禁止Python自动创建异常链以减少异常的复杂性。此时异常会被视为相互独立的。 try:# 引发一个异常并禁用自动异常链raise ValueError(This is an exception) from None
except ValueError as ve:print(Caught ValueError:, ve)print(Direct cause:, ve.__cause__)在这个示例中异常链被禁用ve.__cause__ 的值为 None不再与其他异常相关联。
9.7 处理多个不相关的异常 在某些情况下可能需要报告多个已经发生的异常而不仅仅只是报告第一个异常。例如在并发框架等情境中多个任务并行执行时可能会发生多个错误。 为了处理这种情况Python 提供了一种 ExceptionGroup它允许将多个异常实例打包成一个列表从而一起引发这使得多个异常可以同时被捕获和处理。
def f():# 创建了一个包含两个异常实例的列表这两个异常实例都包含了错误消息excs [OSError(error 1), SystemError(error 2)]raise ExceptionGroup(there were problems, excs)f()---------------------------------------------------------------------------
ExceptionGroup Traceback (most recent call last)
Cell In[17], line 52 excs [OSError(error 1), SystemError(error 2)]3 raise ExceptionGroup(there were problems, excs)
---- 5 f()Cell In[17], line 3, in f()1 def f():2 excs [OSError(error 1), SystemError(error 2)]
---- 3 raise ExceptionGroup(there were problems, excs)ExceptionGroup: there were problems (2 sub-exceptions)上述代码使用 raise ExceptionGroup 引发了一个异常该异常包含一个错误消息 ‘there were problems’ 和前面创建的异常实例列表 excs。这样就创建了一个异常链其中 ExceptionGroup 是顶级异常而 OSError 和 SystemError 是其下的子异常。
使用以下代码可以打印具体的异常信息
def f():excs [OSError(error 1), SystemError(error 2)]raise ExceptionGroup(there were problems, excs)try:f()
except Exception as e:print(fcaught {type(e).__name__}: {e}) # 打印异常信息# 如果异常是 ExceptionGroup打印其中包含的子异常信息if isinstance(e, ExceptionGroup):for i, exc in enumerate(e.exceptions, 1):print(fException {i}: {type(exc).__name__}: {exc})caught ExceptionGroup: there were problems (2 sub-exceptions)
Exception 1: OSError: error 1
Exception 2: SystemError: error 2对于一个嵌套的异常组可以使用except* 子句从组中提取了某种类型的异常而让所有其他的异常传播到其他子句并最终被重新引发。另外嵌套在一个异常组中的异常必须是实例而不是类型。这是因为在实践中这些异常通常是那些已经被程序提出并捕获的异常。此部分详细内容请参考《错误和异常》。
9.8 用注释细化异常情况 在异常被捕获后有时需要向异常对象添加额外的信息以便更好地描述错误和提供上下文信息。Python 的异常对象具有 add_note(note) 方法允许您将字符串注释添加到异常对象的注释列表中。当异常被引发后异常对象中的注释会按照它们被添加的顺序显示在标准异常回溯信息中例如
try:raise TypeError(bad type)
except Exception as e:e.add_note(Add some information)e.add_note(Add some more information)raise---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[21], line 21 try:
---- 2 raise TypeError(bad type)3 except Exception as e:4 e.add_note(Add some information)TypeError: bad type
Add some information
Add some more information你也可以在自定义异常中添加额外的异常注释
class CustomError(Exception):def __init__(self, message):super().__init__(message)self.notes [] # 初始化注释列表def add_note(self, note):self.notes.append(note) # 添加注释到异常对象的注释列表中def divide(a, b):try:result a / breturn resultexcept ZeroDivisionError as zd_err:# 创建自定义异常并添加注释custom_err CustomError(Division error)custom_err.add_note(Attempted to divide by zero)raise custom_errtry:result divide(10, 0)
except CustomError as ce:print(Caught CustomError:, ce)print(Notes:)for note in ce.notes:print(-, note)Caught CustomError: Division error
Notes:
- Attempted to divide by zero在这个示例中divide 函数尝试执行除法操作但如果除法操作失败除以零它会引发一个自定义异常 CustomError。在这个自定义异常中我们使用 add_note 方法添加了两个注释分别描述了错误的性质和错误的上下文。最后在异常处理代码中我们遍历异常对象的注释列表以查看所有添加的注释。
9.9 异常处理的最佳实践 在编写Python代码时异常处理是一个关键的方面它有助于增加代码的稳定性和可靠性能够处理各种不可预测的情况增加代码的健壮性。以下是一些建议 仅捕获您知道如何处理的异常以便精确处理问题 在异常处理中应该只捕获那些您知道如何处理的异常。不要仅仅因为可能发生异常就捕获所有异常。这可能会导致隐藏真正的问题使调试变得更加困难。只捕获你能够处理的异常对于其他异常应该让它们引发并停止程序的执行。 引发具有明确描述的自定义异常 当密需要在代码中指示特定的错误条件时应该引发自定义异常。这些异常应该具有清晰的描述以便其他开发人员能够理解问题的本质。例如 if condition:raise CustomException(This is a custom exception message.)不要捕获 Exception 的通用异常 避免捕获通用的 Exception 异常因为这样会捕获所有异常包括系统退出等。这可能导致程序的行为不可预测。应该捕获具体的异常类型例如 ValueError、FileNotFoundError 等。 记录异常信息 当捕获异常时应该记录异常信息以便后续调试。可以使用标准库中的 logging 模块来记录异常信息以便更容易诊断问题。例如 import logging
# 配置日志记录将日志保存到名为myapp.log的文件中只记录ERROR级别及以上的日志并使用特定的格式
logging.basicConfig(filenamemyapp.log, levellogging.ERROR, format%(asctime)s [%(levelname)s] %(message)s)try:result 10 / 0 # 这会引发一个除零异常
except ZeroDivisionError as e: # 捕获ZeroDivisionError异常并将其赋给变量e logging.error(An error occurred: %s, e) # 使用logging.error()记录错误级别的日志包括异常信息 上述代码中format 参数用于定义日志消息的格式它是一个包含占位符的字符串这些占位符将在实际记录日志消息时被替换为相应的值。常用参数包括
占位符含义%s用于插入字符串。%d用于插入整数。%f用于插入浮点数。%r用于插入表示对象的字符串通常是 repr(object) 的结果。%(name)s用于插入命名的变量值其中 name 是一个变量名。%(asctime)s时间戳通常带有日期和时间的字符串。asctime 表示 “人类可读的时间”通常包括日期和时间。%(levelname)s日志消息级别例如 “ERROR”、“INFO” 等。%(message)s日志消息的主体部分。 最后logging.error()这是调用日志记录模块中的 error 方法表示记录一个错误级别的日志消息s%表示字符串格式。通过设置以上格式最终输出的日志为以下内容并被记录在myapp.log文件中
# 519表示毫秒
2023-09-07 13:36:06,519 [ERROR] An error occurred: division by zero使用 finally 块进行清理操作 如果你有需要在无论是否发生异常都要执行的清理代码可以使用 finally 块这确保了清理代码始终会执行即使发生异常也不例外。