例如,我有一个产生程序噪音的函数
def procedural_noise(width, height, seed):
...
此功能的所有参数均应为正。我想,如果on参数是非正的,我需要检查它并抛出异常。它是一种好的(pythonic方式)方法吗?
让我们假设,我是对的。哪个是检查参数的最佳方法?
我可以为每个参数编写检查器:
def procedural_noise(width, height, seed):
if width <= 0:
raise ValueError("Width should be positive")
if height <= 0:
raise ValueError("Height should be positive")
if seed <= 0:
raise ValueError("Seed should be positive")
...
对于程序员来说,当他得到例外时应该清楚,他应该纠正什么,但在我看来它并不好看。
以下代码更容易,但距离理想太远:
def procedural_noise(width, height, seed):
if width <= 0 or height <= 0 or seed <= 0:
raise ValueError("All of the parameters should be positive")
...
最后一个问题:哪个是使用unittest
框架编写测试的最佳方法,它检查参数类型及其值?
我可以在测试类中编写以下函数:
def test_positive(self):
self.assertRaises(ValueError, main.procedural_noise, -10, -10, 187)
这是一个正确的解决方案吗?
答案 0 :(得分:6)
这也可能是函数注释的一个很好的用例(在Python 3中)。例如:
import inspect
from functools import wraps
def positive(name, value):
if value < 0:
raise ValueError(name + " must be positive")
def check_params(f):
signature = inspect.signature(f)
@wraps(f)
def wrapper(*args, **kwargs):
bound_arguments = signature.bind(*args, **kwargs)
for name, value in bound_arguments.arguments.items():
annotation = signature.parameters[name].annotation
if annotation is inspect.Signature.empty:
continue
annotation(name, value)
return f(*args, **kwargs)
return wrapper
@check_params
def procedural_noise(width: positive, height: positive, seed: positive):
pass # ...
在check_params
装饰器(受github.com/ceronman/typeannotations启发)中有一些inspect-fu,但它提供了非常好用且灵活的方法来检查函数参数 - 没有任何if
s, for
或类型检查函数中的其他噪声代码。
答案 1 :(得分:3)
我会这样做:
def procedural_noise(width, height, seed):
check_positive(width, "width")
check_positive(height, "height")
check_positive(seed, "seed")
def check_positive(value, name):
if value < 0:
raise ValueError(name + " must be positive")
另一个想法 - 一点点“黑客”:
def procedural_noise(width, height, seed):
check_positive(width=width)
check_positive(height=height)
check_positive(seed=seed)
def check_positive(**kwargs):
for name, value in kwargs.items():
if value < 0:
raise ValueError(name + " must be positive")
也可以这样称呼:
def procedural_noise(width, height, seed):
check_positive(width=width, height=height, seed=seed)
这与其他答案几乎相同,但这样原始函数procedural_noise
保持相当清晰,除了非常基本的信息之外的任何参数处理。它更像语义:)
答案 2 :(得分:2)
关于第一个问题,使用inspect
模块:
import inspect
def procedural_noise(width, height, seed):
frame = inspect.currentframe()
args, _, _, values = inspect.getargvalues(frame)
for name in args:
if values[name] < 0:
raise ValueError(name + " should be positive")
procedural_noise(3, -66, 2)
输出:
Traceback (most recent call last):
File "C:\Users\Sam\Desktop\hack.py", line 10, in <module>
procedural_noise(3, -6, 2)
File "C:\Users\Sam\Desktop\hack.py", line 8, in procedural_noise
raise ValueError(name + " should be positive")
ValueError: height should be positive
否则,您也可以这样使用dictionary packing:
def procedural_noise(**params):
for name in params.keys():
if params[name] < 0:
raise ValueError(name + " should be positive")
procedural_noise(width=3, height=6, seed=-2)
输出:
Traceback (most recent call last):
File "...\hack.py", line 6, in <module>
procedural_noise(width=3, height=6, seed=-2)
File "...\hack.py", line 4, in procedural_noise
raise ValueError(name + " should be positive")
ValueError: seed should be positive
答案 3 :(得分:1)
尝试类似
的内容def procedural_noise(width, height, seed):
for key,val in locals().items():
if val < 0:
raise ValueError(key + " should be positive")
答案 4 :(得分:1)
如果你发现自己做了很多,也许装饰工具会让你的代码更具可读性:
def assert_positive(f):
def wrapper(*args, **kwargs):
for i, v in enumerate(args):
if v < 0:
raise ValueError('The parameter at position %d should be >= 0' % i)
for k, v in kwargs.items():
if v < 0:
raise ValueError('The parameter %s should be >= 0' % k)
return f(*args, **kwargs)
return wrapper
然后你可以像这样声明你的函数:
@assert_positive
def procedural_noise(width, height, seed=0):
...
它会引发这样的例外:
>>> procedural_noise(0,-1,0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "argument_checking.py", line 5, in wrapper
raise ValueError('The parameter at position %d should be >= 0' % i)
ValueError: The parameter at position 1 should be >= 0
>>> procedural_noise(0,0,seed=-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "argument_checking.py", line 8, in wrapper
raise ValueError('The parameter %s should be >= 0' % k)
ValueError: The parameter seed should be >= 0
pythonic方式通常不会过多检查你的论点,但有反例。内置函数的例子完全不同:
range(-1)
- 看起来很有趣,但很好,返回[]
time.sleep(-1)
- 与IOError: [Errno 22] Invalid argument
崩溃,我认为这只是Python的一种说法,即系统调用返回错误。也许我很幸运,系统调用会进行参数检查...... chr(-1)
- ValueError: chr() arg not in range(256)
答案 5 :(得分:0)
您可以使用all
内置 - all
获取可迭代,如果迭代中的所有项都是真实的,则返回True
。因此,对于您的示例,由于所有正数都是真实的,您可以写:
def procedural_noise(width, height, seed):
if all((width, height, seed)):
# Do stuff
else:
raise ValueError("You biffed it.")
请注意双重 - 这是因为我将元组(width, height, seed)
传递给all
- 我不能只写all(width, height, seed)
,因为all
需要一个可迭代的参数,就像一个元组。
关于测试问题,假设定义了procedural_noise
的模块的名称命名为foo
,那么在测试文件中,您可以将assertRaises
与with
一起使用{1}}关键字,如下所示:
import foo
import unittest
class Test(unittest.TestCase):
def test_positive(self):
with self.assertRaises(ValueError):
foo.procedural_noise(-10, -10, 187)
在unittest docs了解详情。