I have a Python class that issues a warning inside __init__()
. It also provides a factory class method for opening and reading a file:
from warnings import warn
class MyWarning(Warning):
"""Warning issued when an invalid name is found."""
pass
class MyClass:
def __init__(self, names):
# Simplified; actual code is longer
if is_invalid(names):
names = fix_names(names)
warn(f'{names!r} contains invalid element(s)',
MyWarning, stacklevel=2)
self._names = names
@classmethod
def from_file(cls, filename):
with open(filename) as file:
names = extract_names(file)
return cls(names)
stacklevel=2
makes the warning refer to the call to MyClass()
rather than the warn()
statement itself. This works when user code directly instantiates MyClass. However, when MyClass.from_file()
issues the warning, MyWarning
refers to return cls(names)
, not the user code calling from_file()
.
How do I ensure that the factory method also issues a warning that points to the caller? Some options I've considered:
_stacklevel
parameter to __init__()
, and instantiate MyClass with _stacklevel=2
inside from_file()
.
_stacklevel
class attribute, and access it inside __init__()
. Then temporarily modify this attribute in from_file()
_set_names()
method that checks/fixes the names and issues a warning when needed. Then call this method inside the constructor. For from_file()
, first instantiate MyClass with empty args, then directly call _set_names()
to ensure that MyWarning points to the caller.
_set_names()
twice when from_file()
is called.I read the warning
module docs but it offers little help on safely catching and re-throwing warnings. Converting the warning to an exception using warnings.simplefilter()
would interrupt MyClass()
and force me to call it again.
A RuntimeWarning is used to signal to the runtime framework that a non-fatal error has been encountered. Server startup will proceed as usual.
The warn() function defined in the ' warning ' module is used to show warning messages. The warning module is actually a subclass of Exception which is a built-in class in Python. print ( 'Geeks !' )
You can catch warnings similar to the way you catch exceptions using warnings.catch_warnings()
:
import warnings
class MyWarning(Warning):
"""Warning issued when an invalid name is found."""
pass
class MyClass:
def __init__(self, names):
# Simplified; actual code is longer
if is_invalid(names):
names = fix_names(names)
warn(f'{names!r} contains invalid element(s)',
MyWarning, stacklevel=2)
self._names = names
@classmethod
def from_file(cls, filename):
with open(filename) as file:
names = extract_names(file)
with warnings.catch_warnings(record=True) as cx_manager:
inst = cls(names)
#re-report warnings with the stack-level we want
for warning in cx_manager:
warnings.warn(warning.message, warning.category, stacklevel=2)
return inst
Just keep in mind the following note from the documentation of warnings.catch_warnings()
:
Note The catch_warnings manager works by replacing and then later restoring the module’s
showwarning()
function and internal list of filter specifications. This means the context manager is modifying global state and therefore is not thread-safe.
David is right, warnings.catch_warnings(record=True)
is probably what you want. Though I would write it as a function decorator instead:
def reissue_warnings(func):
def inner(*args, **kwargs):
with warnings.catch_warnings(record = True) as warning_list:
result = func(*args, **kwargs)
for warning in warning_list:
warnings.warn(warning.message, warning.category, stacklevel = 2)
return result
return inner
And then in your example:
class MyClass:
def __init__(self, names):
# ...
@classmethod
@reissue_warnings
def from_file(cls, filename):
with open(filename) as file:
names = extract_names(file)
return cls(names)
inst = MyClass(['some', 'names']) # 58: MyWarning: ['some', 'names'] contains invalid element(s)
inst = MyClass.from_file('example') # 59: MyWarning: ['example'] contains invalid element(s)
This way also allows you to cleanly collect and reissue warnings across multiple functions as well:
class Test:
def a(self):
warnings.warn("This is a warning issued from a()")
@reissue_warnings
def b(self):
self.a()
@reissue_warnings
def c(self):
warnings.warn("This is a warning issued from c()")
self.b()
@reissue_warnings
def d(self):
self.c()
test = Test()
test.d() # Line 59
# 59: UserWarning: This is a warning issued from c()
# 59: UserWarning: This is a warning issued from a()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With