在Python中处理警告
off999 2024-11-20 20:03 13 浏览 0 评论
我们每个人都会遇到这种情况: 你写了一些Python代码,但是你遇到了一个错误:
这不仅仅是一个错误,而是一个异常。这是Python以明确的方式表述存在问题的方式,这样,我们就可以用“try”和“except”关键字来捕获它。
就像Python中的其他东西一样,异常也是一个对象。这意味着一个异常有一个类——我们就是用这个类来捕获异常的:
我们甚至可以有几个“except”子句,每个子句会寻找一种不同类型的错误。但是,每个Python类(除了“object”)都继承自其他类,对于异常类也是如此。因此,如果我们想同时捕获“KeyError”和“IndexError”,那么我们可以显式地命名它们。或者我们可以只捕获“LookupError”,它是“KeyError”和“IndexError”的父类。
在Python标准库文档中,你可以在https://docs.python.org/3/library/exceptions.html 中查看Python的异常类层次结构。它有助于你了解Python中存在哪些异常,层次结构如何,以及一般地理解异常如何工作。
但是,如果你查看该层次结构的底部,你将看到一个名为“Warning”的异常类,以及一些子类,比如“DeprecationWarning”和“BytesWarning”。这些是什么?虽然它们与异常层次结构一起被包含在其中,警告也是异常,但它们既不会像正常的异常那样被抛出,也不会像正常的异常那样被使用。它们是什么,我们要如何使用它们?
首先,回顾一下历史: Warnings在Python中已经存在很长时间了,从Python 2.1开始(追溯到2000年)。PEP 230是由Guido van Rossum (Python的创造者和长期的“仁慈的生活的独裁者”)编写的,它的添加不仅是为了创建一种机制来提醒用户可能出现的问题,而且是为了从程序内部发送此类警告并决定如何处理它们。
为什么要用警告?
在我向你展示如何在你自己的代码中使用警告之前,让我们首先考虑一下为什么以及何时需要使用警告。毕竟,你总是可以使用“print”来显示警告。或者,如果确实有问题,那么你也可以抛出一个异常。
但这正是问题的关键:在某些时候,你想要吸引用户的注意,但又不需要停止程序或强制执行try-except子句。虽然“print”总是很有用,但它通常会写入标准输出(也就是Python中的“sys.stdout”),这意味着你的警告可能会与系统的警告本身混合在一起。
(虽然我刚写过,你可能会想要引起用户的注意,但我认为,大多数情况下,警告是针对开发人员而不是用户的。Python中的警告有点像汽车上的“所需的服务”灯;用户可能知道有些地方出了问题,但是只有一个经过认证的维修人员才会知道该怎么做。开发人员应该避免向最终用户显示警告。)
你还可以想象这样一种情况,在这种情况下,某些警告比其他警告更重要。你当然可以设计一种方案,在该方案中,程序会“print”警告,并且该警告的第一个字符将指示警告的严重程度……但是,在Python有一个完整的对象系统,以及可以自由使用的复杂数据类型的情况下,我们为什么要这样做呢?
此外,在某些情况下,用户可能不希望忽略警告。也许我在生产环境中运行的是一种非常敏感的程序,我宁愿让程序提前退出,也不愿在一个潜在的不确定情况下继续运行。
Python的警告系统考虑到了这一切:
它将警告看作一个单独的输出类型 , 这样我们就不会将它与异常或者程序的打印文本相混淆。
它允许我们指明我们正在发送给用户哪种警告,
它可以让用户指示如何处理不同类型的警告,让一些引发严重错误,其他的在屏幕上显示它们的信息,还有一些始终被忽略,
它可以让程序员开发它们自己的、新的警告类型。
并不是每个程序都需要有或使用警告。但是你的程序可能会因为加载模块或调用函数的方式而责骂用户,因此,Python的警告系统就为你提供了你想要的功能。
警告用户
假设你想要警告用户某些事情。你可以通过导入“warnings”模块来实现这一点,然后使用“warnings.warn”来告诉他们出现了什么问题:
当我运行上面的代码(在一个名为“warnings1.py”的文件中)时,会发生什么呢?输出如下:
换句话说,上面的内容都被写到了我的终端屏幕上。这三行代码都是按顺序打印的,所以警告并不是在程序的单独阶段(例如,编译)被打印的。但是在“print”语句的文本和我从警告中得到的输出之间有一个明显的区别。
首先,我们被告知警告发生在哪个文件中,在哪一行。在这样一个小而琐碎的例子中,这似乎有些过分。但是,如果你有一个包含许多不同文件的大型应用程序,那么知道是什么代码生成了警告无疑是很好的。
我们还被告知这是一个“UserWarning”—我们可以生成的警告类型之一。正如不同类型的异常允许我们选择性地捕获它们一样,不同类型的警告也允许我们以不同的方式处理它们。
但是在这个输出中还有一些隐藏的东西:“print”语句和我的“warnings.warn”语句实际上将它们的输出发送到了两个不同的地方。正如我上面写的,“print”通常会写到“标准输出”,也就是“sys.stdout”,它通常会连接到用户的终端窗口。但“warnings.warn”通常会写到“标准错误”,也就是“sys.stderr”。问题是,在默认情况下,“sys.stdout” 和 “sys.stderr”都会写到同一个地方,即用户的终端。
但是如果我把程序输出重定向到一个文件中,我们来看看会发生什么:
我告诉我的Unix shell我想运行“warnings1.py”,所有的输出都应该被放在“output.txt”中,而不是显示在屏幕上。但我并没有说“全部输出”。相反,通过使用“>”,我只重定向了发送到“sys.stdout”的输出。被发送到“sys.stderr”的警告仍然被显示出来了。这通常被认为是一件好事,它可以确保即使你将输出重定向到一个文件,你仍然能够看到警告和其他错误。因此,尽管sys.stdout 和 sys.stderr 在默认情况下都会去同一个目的地,但我们也可以看到将它们分开的好处。
不同类型的警告
假设我正在维护一个已经存在了一段时间的库。这个库有一个有用的函数,但是这个函数有点过时了,并且不支持现代的用例。作为该库的维护者,支持该函数的两个版本(旧版本和新版本)对我来说是一件痛苦的事情。
我可以在文档和社交媒体中声明,我的库的新版本(3.0)将于明年释出,而且这个新版本将不再支持该函数的旧版本。但是我们都知道程序员并不倾向于阅读文档。因此,我更愿意让用户感到震惊,告诉他们虽然旧的函数版本仍然可以运行,但他们应该开始转向更新的版本。
我该怎么做呢?当然是使用警告!下面是一个例子:
现在,只要用户运行了“hello”函数,他们就会得到一个警告。此外,因为这个警告会写到标准错误(而不是标准输出),所以它不会与正常输出混在一起。下面是来自上面代码的输出:
但还有比这更好的方法:或许我们想把普通的、乏味的警告与其他类型的警告区分开来。例如,我们可能有许多被废弃的函数。为了处理这中情况,“warnings.warn”函数支持可选的第二个参数——警告的类别。例如,我们可以使用DeprecationWarning:
我们不必“import”DeprecationWarning或任何其他的标准警告类型,因为它们已经被自动导入到了Python程序始终可用的“内置”命名空间中。这样一来,就有许多这样的警告类可以供我们使用,包括UserWarning(默认)、DeprecationWarning(我们在这里使用了)、SyntaxWarning和UnicodeWarning。你可以使用其中你认为最合适的一个。
你可能已经注意到,这些警告类别与我们在前面查看Python的内置异常层次结构时所看到的类完全相同。实际上,这些类就是这样被使用的,作为第二个参数传递给“warnings.warn”。
简单的过滤
假设你正在使用一堆旧函数,每一个函数都会提醒你,你应该切换到它们的新的替代版本。如果每次运行程序时都收到一堆警告,你可能会有点恼火。警告是用来通知你你应该进行升级……但是有时候,这些警告与其说有用,不如说是烦人。
在这种情况下,你可能希望过滤掉一些警告。现在,“filtering”是警告系统使用的一个非常普遍的术语。它基本上是让你说,“当一个匹配特定条件的警告触发时,使用它做X”——X可以是各种各样的事情。
最简单的过滤器是“warnings.simplefilter”,而调用它的最简单方法是使用单个字符串参数。这个参数告诉警告系统,如果它遇到一个警告,该怎么做:
“默认”——在警告第一次出现时显示它
“错误”——将警告转换成一个异常
“忽略”——忽略警告
“总是”——总是显示警告,即使它以前被显示过
“模块”——每个模块显示一次警告
“一次”——在整个程序中只显示一次警告
例如,如果我想忽略所有警告,我可以这样写:
并且如果我有这样的代码:
我们会看到这样的输出:
如你所见,由于使用了“ignore”,警告完全消失了。
如果我们采取另一种极端,即将警告转换为异常,会发生什么?
果然,我们得到了一个异常:
正如你所看到的,我们得到了一个UserWarning异常。我们可以在这些警告上面使用“try”和“except”,如果我们想的话,我们就可以捕获它们……尽管我必须承认,在我看来,将警告转换成异常只是为了捕获它们是很奇怪的。(不过,我相信有这样的用例。)
更具体的过滤
我提到过“simplefilter”采用了一个强制性的参数,并且我们已经看到了这些参数可以是什么。但事实证明,“simplefilter”使用了几个额外的、可选的参数,这些参数可用于指定一个警告被发出时将发生什么。
例如,假设我想忽略UserWarning,并将DeprecationWarning转换为异常。我可以这样写:
这段代码会生成以下输出:
换句话说,我们成功地忽略了一种类型的警告,同时将另一种类型的警告转换为异常——与所有异常一样,如果忽略这种异常,它将是致命的。
“simplefilter”函数接受四个参数,除了第一个参数外,其他参数都是可选的
你还能做什么?
警告系统可以处理非常广泛的各种情况,并且可以通过多种方式进行配置。除此之外,你还可以:
使用-W标志从命令行定义警告过滤器
设置多个过滤器,每个过滤器处理一种不同的情况
指定应该过滤的消息和模块,可以以一个字符串,也可以以一个正则表达式
创建你自己的警告,作为现有警告类的子类
使用Python的日志模块捕获警告,而不是将输出打印到sys.stderr。
将输出传递到一个你选择的可调用对象(即函数或类),而不是sys.stderr,用于更高级的处理。
关于警告的原始文档(PEP 230)是一个很好的起点,它还描述了引入警告的动机。
标准库中的“warnings”模块文档描述了我在这里所写的所有内容,以及更多的内容。
“本周Python模块”网站有一个很好的介绍:https://pymotw.com/3/warnings/
英文原文:https://lerner.co.il/2020/04/27/working-with-warnings-in-python/
译者:天天向上
相关推荐
- Python中的两个内置函数id()和type()
-
id()>>>id(3)2531362761072>>>id(3.222222)2531397393680>>>id(3.0)25313...
- python 函数中,如何将另一个函数作为参数传递
-
python函数中,如何将另一个函数作为参数传递,类似C#委托defadd(a,b):"""这是一个简单的加法函数,接受两个参数并返回它们的和。""...
- Python性能暴涨10倍的终极指南:7个核心技巧+代码压缩秘籍
-
提升Python程序运行性能,使代码运行更流畅更快,以及压缩代码,减小代码大小,下面的方法仅供大家参考,有什么更好的方法在评论区说说。1.使用NumPy/SciPy替代纯Python循环...
- Python 匿名函数(Lambda 函数)详解
-
匿名函数(AnonymousFunction),在Python中称为lambda函数,是一种不需要使用def关键字定义的小型函数。它主要用于简化代码,特别适合需要函数对象的地方。1.基...
- Python学习笔记 | 匿名函数lambda、映射函数map和过滤函数filter
-
什么是匿名函数?定义:没有函数名的自定义函数场景:函数体非常简单,使用次数很少,没有必要声明函数,通常搭配高阶函数使用。高阶函数是能够把函数当成参数进行传递的函数,如:映射函数map和过滤函数fil...
- python练习:自定义函数调用:商品购物实例
-
1、商品录入dict_myshanpin_iof={101:{"商品名称":"毛毛熊","单价":25},102:{"商品名称":...
- Python中如何使用Lambda函数(lambda在python中的用法)
-
Python和其他编程语言一样,都在其语法中添加了lambda函数,Pythonlambda是匿名函数,比常规Python自定义函数有更简洁的语法。虽然Lambda语法在开始时可能会觉得有点混乱,...
- 8-Python内置函数(python内置函数代码)
-
Python提供了丰富的内置函数,这些函数可以直接使用而无需导入任何模块。以下是一些常用的内置函数及其示例:1-print()1-1-说明输出指定的信息到控制台。1-2-例子2-len()2-1-说...
- 用Python进行函数式编程(python函数程序)
-
什么是函数式编程?函数式程序设计是一种编程范例,它把计算当作数学函数的评价,避免状态和可变数据。换句话说,函数编程(FunctionalProgramming,FP)促进没有副作用和不变变量的代码。它...
- python 函数进阶(python如何进阶)
-
1.有名函数和匿名函数#该函数有名称,名称是adddefadd(x,y):returnx+y#改函数没有名称属于匿名函数,也叫lambda表达式lambda_add...
- python自学者的分享:自定义函数、参数作用域、匿名函数、装饰器
-
#自定义新函数函数名newhsdefnewhs(a,b=1):#b的默认值为1,在没有传入b值时,采用默认值,,默认值参数不能放前边returna-bprint(newh...
- Python 函数式编程的 8 大核心技巧,不允许你还不会
-
函数式编程是一种强调使用纯函数、避免共享状态和可变数据的编程范式。Python虽然不是纯函数式语言,但提供了丰富的函数式编程特性。以下是Python函数式编程的8个核心技巧:1.纯函数(...
- 零基础到发布:手把手教你创建并分发 Python 自定义库
-
作为程序员,我们经常依赖各种外部库来解决不同的问题。这些库由技术娴熟的开发者创建,为我们提供了节省时间和精力的解决方案。但你是否曾想过:“我也能创建属于自己的自定义库吗?”答案是肯定的!本文将为你详细...
- 打工人学Python:(七)自定义函数,打造自己的武器库
-
从一个简单的函数开始#!/usr/bin/envpython#-*-encoding:utf-8-*-'''@Purpose:Wordcount@...
- 肖sir_python自定义函数format、zip函数
-
python自定义函数一、常见的自定义函数已经学过的函数:list、print、set、str、type、tuple、dict、range、input等今天学的函数:format二、实战讲解(一)f...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python自定义函数 (53)
- python进度条 (54)
- python的for循环 (56)
- python串口编程 (60)
- python写入txt (51)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python字典增加键值对 (53)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python qt (52)
- python人脸识别 (54)
- python多态 (60)
- python命令行参数 (53)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- centos7安装python (53)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)