2015-04-11 3 views
4

Например, у меня есть функция, которая генерирует процессуальное NOISEПроверьте параметры функции в Python

def procedural_noise(width, height, seed): 
    ... 

Все параметры этой функции должны быть положительными. Я полагаю, что мне нужно проверить это и выбросить исключение, если по параметрам неположительно. Это хороший (питонический подход)?

Предположим, что я прав. Каков наилучший способ проверить параметры?


Я могу написать шашки для каждого из параметров:

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) 

Является ли это правильное решение?


UPD: Я upvoted всех ответов, потому что каждый из них полезной информации для меня, но я не могу выбрать лучшие ответы из них (я полагаю, что это справедливо, чтобы выбрать наиболее голосовал вопрос завтра)

+1

Здесь что-то не так - ноль не положительный, но ошибки не возникло :) – Messa

+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), но обеспечивает очень хороший и гибкий способ проверить функциональные аргументы - без if с, for с или другой шумо кода в проверенных типах функциях.

+0

Большое спасибо, это очень интересное решение. В частности, потому, что раньше я не встречал такого замечательного применения аннотаций функций. –

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 

+2

Однако для вызова требуются именованные параметры при вызове функции - то, что OP может не хотеть. – MattDMo

+1

Вы правы. Я думаю о лучшем решении. –

+1

@MattDmo: См. Мой отредактированный пост;) –

0

Вы можете использовать all встроенную команду - all принимает итератор и возвращает True, если все элементы в iterable являются правдивыми.Так для примера, так как все положительные числа truthy, вы могли бы написать:

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 ключевым словом, следующим образом:

import foo 
import unittest 

class Test(unittest.TestCase): 
    def test_positive(self): 
    with self.assertRaises(ValueError): 
     foo.procedural_noise(-10, -10, 187) 

Подробнее об этом читайте на странице unittest docs.

+4

Это просто неверно. Ваш код вызовет ошибку, только если все три входа равны 0. Даже в противном случае это явно не то, что ищет OP, он явно заявляет, что хочет предоставить конкретные сообщения об исключениях. –

+0

Вы правы, я изначально написал пример как 'if all ((args)): do stuff'. Я считаю, что исправил его - если вы поместите код в условное выражение, оно должно иметь ожидаемое поведение. – Dan

+0

Это не совсем подходит для меня, но в любом случае спасибо за ответ. Что касается тестового вопроса, я написал, что моя функция находится в тестовом классе, поэтому у меня есть код, похожий на ваш код. Я спросил, правильно ли использовать 'assertRaises' и' ValueError' в моей ситуации? –

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 хранится в чистоте любой обработки аргументов, за исключением самой основной информации. Это более семантический :)

+1

Возможно 'check_positive (** kwargs)' для 'check_positive (width = width, height = height, seed = seed)'? – JuniorCompressor

+1

@JuniorCompressor интересная идея, ответ обновлен. – Messa

+1

Я уже одобрил;) Я думаю, что он имеет другие полезные применения, такие как check_positive (area = width * height) '. Кроме того, для полноты вы можете сказать что-то для тестирования. – JuniorCompressor

1

Попробуйте что-то вроде

def procedural_noise(width, height, seed): 
    for key,val in locals().items(): 
     if val < 0: 
      raise ValueError(key + " should be positive") 
+0

Спасибо! В нашей стране есть глагол: «краткость - это сестра таланта» :) –

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 

вещий способ, как правило, не проверять ваши аргументы слишком много, но есть контрпримеры. Примеры встроенных команд, которые делают совершенно разные вещи:

  • range(-1) - Выглядит смешно, но это хорошо, возвращает []
  • time.sleep(-1) - Аварии с IOError: [Errno 22] Invalid argument, который я думаю, что это просто способ в Python сказать, что системный вызов возвращается ошибка. Может быть, мне повезло, что системные вызовы делают проверку аргументов ...
  • chr(-1) - ValueError: chr() arg not in range(256)
+0

Спасибо. Не могли бы вы посоветовать кое-что прочитать о проверке аргументов в Python? Это очень интересная тема, потому что я знаю, что проверка типа не является одобрением, но существует множество ситуаций, когда переменная using с неправильным типом приведет к сбою в работе функции. –

Смежные вопросы