Как я могу написать общую функцию Python 2.2, которая возвращает список неустановленных параметров? - PullRequest
1 голос
/ 02 декабря 2011

У меня есть функция со многими входными параметрами, и мне нужна функция, которая будет возвращать список имен параметров (не значений) для каждого параметра, значение которого равно '' или Нет

Обычно я выбрасываю исключение в таком методе. Если кто-то хочет решить проблему с помощью исключения, это нормально. У меня все еще есть требование, чтобы функция возвращала список имен параметров.

Подводя итог

  1. Возвращает список имен параметров для параметров, которые не установлены
  2. «unset» означает, что значение параметра не является пустой строкой или отсутствует
  3. Примите один параметр: один список измерений или dict
  4. Список должен содержать полный набор пустых имен параметров
  5. Мне нужно, чтобы он был обратно совместим с Python 2.2 и Jython
  6. 2.2 не подлежит обсуждению. Код должен работать на устаревших системах, которые мы не имеем права обновлять. Отстой, чтобы быть нами.
  7. Параметры - это не аргументы командной строки, а параметры функция.
  8. Параметры хранятся в отдельных переменных, но при необходимости я могу вручную поместить их в dict.
  9. Вместо того, чтобы возвращать список имен переменных Python, возвращайте список удобных для пользователя описаний для каждой пустой переменной. Пример: «Имя базы данных» против «db_name».

Ответы на поставленные вопросы:

  1. Что если встретится неизвестный параметр? Нам все равно. Мы создаем список параметров для проверки и выбираем только те, которые являются обязательными в силу логики системы. Таким образом, мы никогда не добавили бы неизвестный параметр в список проверяющих
  2. А как насчет параметров пользовательского интерфейса, которые не являются обязательными или которые должны проверяться другими способами (int или string и т. Д.)? Мы не будем помещать необязательные параметры в список, который мы передаем в функцию проверки. Для других более сложных проверок мы обрабатываем их индивидуально, adhoc. Причина, по которой эта функция показалась удобной, заключается в том, что пустые параметры являются наиболее распространенной проверкой, которую мы делаем, и написание if not foo: для каждой становится утомительным для всех функций, которых у нас много.
  3. Пожалуйста, объясните "" "По характеру нашей платформы" "". Также "" "он прибывает в отдельные переменные" "" ... отдельные переменные в каком пространстве имен? А что значит "" "(предварительная обработка)" ""? - Джон Мачин 2 дня назад. Ответ: Переменные находятся в глобальном пространстве имен. Мы используем внедрение кода (аналогично тому, как препроцессор C заменяет код на имена макросов, за исключением того, что мы подставляем переменные значения для тегов, аналогично следующему:

    DATABASE_NAME = ^ - ^ Укажите здесь переменную, введенную пользователем для имени базы данных ^ - ^

, который заканчивается после запуска препроцессора:

DATABASE_NAME = "DB1"

Вот конкретный пример, показывающий, почему простой метод, генерирующий исключение, не будет работать. Я переписал, чтобы использовать исключение, а не возвращать значение, по запросу:

def validate_parameters(params_map):
    """
    map is like {foo: "this is foo"}
    """
    missing_params_info = []
    for k,v in params_map.items():
        if not k:
            missing_params_info.append(v)
    if missing_params_info:
        raise TypeError('These parameters were unset: %s' % missing_params_info)

params = {}
params['foo'] = '1'
params['bar'] = '2'
params['empty'] = ''
params['empty2'] = ''
params['None'] = None
params_map = {
    params['foo']: 'this is foo',
    params['bar']: 'this is bar',
    params['empty']: 'this is empty',
    params['empty2']: 'this is empty2',
    params['None']: 'this is None',
}

print validate_parameters(params_map)


bash-3.00# python /var/tmp/ck.py
Traceback (most recent call last):
  File "/var/tmp/ck.py", line 26, in ?
    print validate_parameters(params_map)
  File "/var/tmp/ck.py", line 10, in validate_parameters
    raise TypeError('These parameters were unset: %s' % missing_params_info)
TypeError: These parameters were unset: ['this is empty2', 'this is None']

Две причины, по которым он не работает: он печатает только empty2, даже если есть другой пустой параметр, "empty". «empty» перезаписывается «empty2», потому что они используют один и тот же ключ на карте.

Вторая причина: мне нужно получить список описаний в переменную в какой-то момент после запуска этой функции. Может быть, это возможно за исключением, но я не знаю, как сейчас.

Я опубликовал ответ, который, кажется, решает все эти проблемы, но не идеален. Я пометил ответ на вопрос, но изменит его, если кто-то ответит лучше.

Спасибо!

Ответы [ 7 ]

2 голосов
/ 06 декабря 2011

Я почти уверен, что не понимаю вопроса или того, что то, что вы разместили как «лучшее решение», отвечает требованиям, но работает только с:

У меня есть функция со многими входными параметрами, и мне нужна функция который будет возвращать список имен параметров (не значений) для каждого параметр, значение которого равно '' или отсутствует

Вот простой способ сделать то, о чем просит эта строка:

def validate_parameters(args):
    unset = []
    for k in args:
        if args[k] is None or args[k]=="":
            unset.append(k)
    return unset

, а затем просто вызовите validate_parameters из first строки функции:

def foo(a, b, c):
    print "Unset:", validate_parameters(locals())

>>> foo(1, None, 3)
Unset: ['b']
>>> foo(1, None, "")
Unset: ['c', 'b']

Если бы не было требований Python 2.2, вы могли бы сделать все это в едином понимании списка строк. Важно то, что вы должны вызывать его из самой первой строки функции, чтобы locals() собирал только параметры, а не любые другие локальные переменные.

1 голос
/ 02 декабря 2011

Почему бы не Зойдберг декоратор?

def argsnotempty(**requiredargs):

    def decorator(func):

        def wrapper(*args, **kwargs):
            code     = func.func_code
            argsreq  = code.co_argcount - 1
            argsrec  = len(args)
            posargs  = code.co_varnames[1:argsreq + 1]
            errs     = []

            # validate positional args
            for i, arg in enumerate(args):
                if i == len(posargs):
                    break
                # falsy but not False: 0, '', None, [], etc.
                if not (arg or arg is False):
                    argname = posargs[i]
                    if argname in requiredargs:
                        errs.append(argname + " (" + requiredargs[argname] + ")")

            # validate keyword args
            for argname, arg in kwargs.iteritems():
                if argname in requiredargs:
                    if not (arg or arg is False):
                        errs.append(argname + " (" + requiredargs[argname] + ")")

            # make sure all required args are present
            for argname in requiredargs:
                if argname not in kwargs and argname not in posargs:
                    errs.append(argname + " (" + requiredargs[argname] + ")")

            return func(errs, *args, **kwargs)

        wrapper.__name__, wrapper.__doc__ = func.__name__, func.__doc__

        return wrapper

    return decorator

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

Использование:

@argsnotempty(a="alpha", b="beta", g="gamma")
def foo(errs, a, b, g):
    print errs

foo(3.14, "blarney", None)    # prints "['g (gamma)']"

Вот пример вызова исключения, если вы не получаете нужные значения:

@argsnotempty(a="alpha", b="beta", g="gamma")
def bar(errs, a, b, g):
    if errs:
       raise ValueError("arguments " + ", ".join(errs) + " cannot be empty")

bar(0, None, "")

Конечно, вы можете настроить декоратор, чтобы сделать это для вас, вместо включения стандартного кода в каждую функцию.

Редактировать: Исправлено некоторое баггейдж

0 голосов
/ 06 декабря 2011

Вопрос остается очень запутанным. Очень.

Возможно, вы спрашиваете о самоанализе объекта кода функции:

def noisy_typerror( func ):
    def fix_exception( **kw ):
        try:
            # This is generally needless; mostly a waste of CPU cycles.
            if not all(kw[arg] for arg in kw  ):
                raise TypeError
            # Simply apply the function and see if a TypeError occurs 
            return func( **kw )
        except TypeError:
            required= ", ".join( func.func_code.co_varnames[:func.func_code.co_argcount] )
            provided= ", ".join( "{0}={1!r}".format(k,v) for k,v in kw.items() )
            raise TypeError( "{2}( {0} ) got {1}".format(required, provided,func.func_name) )
    return fix_exception

@noisy_typerror
def some_func( this, that, the_other ):
    a= this
    b= that
    print( this, that, the_other )

Чтобы применить декораторы в более старой версии Python

def the_real_func( this, that, the_other ):
   etc.

some_func= noisy_typerror( the_real_func )

Вот несколько вариантов использования этого декоратора

try:
    some_func( this=2, that=3 )
except TypeError, e:
    print e 
try:
    some_func( this=4 )
except TypeError, e:
    print e 
try:
    some_func( this=2, that=3, the_other='' )
except TypeError, e:
    print e 

Я получаю такие результаты при печати строк TypeError.

some_func( this, that, the_other ) got this=2, that=3
some_func( this, that, the_other ) got this=4
some_func( this, that, the_other ) got this=2, the_other='', that=3
0 голосов
/ 05 декабря 2011

Это лучший ответ, который я мог придумать. Это требует большой подготовительной работы перед вызовом функции, поэтому мне это не нравится. Тем не менее, он отвечает всем требованиям.

Спасибо всем, кто участвовал, и я прошу прощения за то, что вопрос требует так много поправок!

def validate_parameters(params_map):
    """
    map is like {foo: "this is foo"}
    """
    missing_params_info = []
    for k,v in params_map.items():
        if not v:
            missing_params_info.append(k)
    return missing_params_info
# or do this if you want to use exceptions:
#    if missing_params_info:
#        raise TypeError('These parameters were unset: %s' % missing_params_info)

params = {}
params['foo'] = '1'
params['bar'] = '2'
params['empty'] = ''
params['empty2'] = ''
params['None'] = None

reverse_params_map = {
    'this is foo' : params['foo'],
    'this is bar' : params['bar'],
    'this is empty' : params['empty'],
    'this is empty2' : params['empty2'],
    'this is None' : params['None'],
}

print validate_parameters(reverse_params_map)

bash-3.00# python /var/tmp/ck.py
['this is empty2', 'this is empty', 'this is None']
0 голосов
/ 02 декабря 2011

Ваша функция звучит довольно широко.Задумывались ли вы, уместно ли это разбить или превратить в отдельный класс?

0 голосов
/ 02 декабря 2011

Это обычный шаблон "много переменных".

def function( variable1, variable2, variable3, ..., variablen ):
   """user-friendly description of the function.
   :param variable1: meaning, units, range, whatever.
   :param variable2: meaning, units, range, whatever.
   ...
   :param variablen: meaning, units, range, whatever.
   :returns: range, type, whatever.
   """
   # do the processing

НЕ проверять отсутствие или недопустимость параметров. Python уже выполняет всю проверку типов, когда-либо необходимую. Просто напишите свой код. Не делайте ничего особенного или дополнительного для «проверки» входных данных. Когда возникают исключения, это означает, что входные данные были плохими.

Это так просто.

Не усложняйте, переписав всю проверку типов Python в посторонних операторах if.

Также.

НИКОГДА не смешивайте «возврат ошибок» с действительными возвращениями. Любой неправильный ввод должен привести к исключению. Хорошие входы возвращают хорошие значения. Неверные данные вызывают исключения.

Это так просто. Не усложняй.

При вызове этой функции вы можете сделать это:

the_variables = { 
    'variable1': some value,
    'variable2': some value,
    ...
    'variablen': some value,
}
try:
    function( **the_variables )
except Exception:
    print( function.__doc__ )

Что-нибудь пропало? Вы получаете TypeError. Что-то неправильно None или пусто? Вы получаете ValueError (или TypeError, это зависит).

Когда что-то идет не так, вы печатаете удобное описание функции.

Это работает довольно хорошо и совсем не требует большого количества программирования.

0 голосов
/ 02 декабря 2011

Чтобы проверить, что все необходимые параметры передаются в вашу функцию, вы можете создать словарь, который отображает все необходимые параметры в None, а затем скопировать и обновить этот словарь в начале каждого метода.

needed_params = {'one': None, 'two': None, 'three': None}

def my_func(**kwargs):
    params = needed_params.copy()
    params.update(kwargs)
    for key, value in params.iteritems():
        if not value:
            raise TypeError("You need to provide the argument %s" % key)
    result = do_stuff_here
    return result

Как отмечается в комментариях, вероятно, не очень хорошая идея возвращать "удобное для пользователя" описание. Вместо этого вы, вероятно, захотите вызвать ошибку, если параметр отсутствует. Тогда вы сможете обработать эту ошибку в другом месте вашего пользовательского интерфейса.

Киндалл предлагает декоратора. В зависимости от того, насколько сложным вы хотите, чтобы проверка была, я думаю, вы могли бы справиться с чем-то более простым, чем его предложение:

def check_needed_params(target):
    needed_params = {'one': None, 'two': None, 'three': ''}
    def wrapper(*args, **kwargs):
        params = needed_params.copy()
        params.update(kwargs)
        for key, value in params.iteritems():
            if not value:
                raise TypeError("You need to provide the argument '%s'" % key)
        return target(**params)
    return wrapper

Вы можете использовать это, чтобы идентифицировать функции, для которых необходимо проверить свои параметры, следующим образом:

@check_needed_params
def adder(**kwargs):
    return kwargs["one"] + kwargs["two"] + kwargs["three"]

Затем, когда эта функция вызывается, она будет работать без проблем, если вы предоставите все результаты, но вызовет ошибку, если вы этого не сделаете.

>>> adder(one=1, two=2, three=3)
6
>>> adder(one=1, two=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:/Users/.../python-6940fCr.py", line 8, in wrapper
TypeError: You need to provide the argument three
...