Имена параметров в функциях Python, которые принимают один объект или итерацию - PullRequest
8 голосов
/ 10 сентября 2010

В моем коде есть некоторые функции, которые принимают либо объект, либо итерацию объектов в качестве входных данных. Меня учили использовать значимые имена для всего, но я не уверен, как соблюдать здесь. Что я должен назвать параметр, который может sinlge объект или итерируемые объекты? У меня есть две идеи, но мне не нравится ни одна из них:

  1. FooOrManyFoos - Это отражает то, что происходит, но я мог бы предположить, что кто-то, кто не привык к этому, может иметь проблемы с пониманием того, что это означает сразу
  2. param - какое-то родовое имя. Это ясно показывает, что это может быть несколько вещей, но ничего не объясняет, для чего используется параметр.

Обычно я называю итерируемые объекты просто множественным числом того, что я бы назвал одним объектом. Я знаю, что это может показаться немного навязчивым, но предполагается, что Python (помимо прочего) должен быть удобочитаем.

Ответы [ 9 ]

7 голосов
/ 10 сентября 2010

В моем коде есть некоторые функции, которые принимают в качестве входных данных либо объект, либо итерацию объектов.

Это очень необычная и часто очень плохая вещь. Этого легко избежать.

То есть, перед вызовом этой функции передайте [foo] вместо foo.

Единственный раз, когда вы можете оправдать это, - это когда (1) у вас есть установленная база программного обеспечения, которая ожидает одну форму (итерируемая или одноэлементная), и (2) вы должны расширить ее для поддержки другого варианта использования. Так. Вы только делаете это при расширении существующей функции, которая имеет существующую кодовую базу.

Если это новая разработка, не делайте этого.

У меня есть две идеи, но мне не нравится ни одна из них:

[Только два?]

FooOrManyFoos - это отражает то, что происходит, но я могу представить, что у кого-то, кто не привык к этому, могут возникнуть проблемы с пониманием того, что это значит сразу

Что? Вы говорите, что не предоставляете никакой другой документации и никакого другого обучения? Никакой поддержки? Нет совета? Кто такой "кто-то не привык к этому"? Поговори с ними. Не предполагайте и не представляйте вещи о них.

Кроме того, не используйте ведущие имена в верхнем регистре.

param - Некое общее имя. Это ясно показывает, что это может быть несколько вещей, но ничего не объясняет, для чего используется параметр.

Грозный. Никогда. Делать. Это.

Я искал в библиотеке Python примеры. Большинство функций, которые делают это, имеют простые описания.

http://docs.python.org/library/functions.html#isinstance

isinstance (объект, classinfo)

Они называют его "classinfo", и это может быть класс или кортеж классов.

Вы тоже можете это сделать.

Вы должны рассмотреть общий вариант использования и исключения. Следуйте правилу 80/20.

  1. 80% времени, вы можете заменить это на итерацию и не иметь этой проблемы.

  2. В оставшихся 20% случаев у вас есть установленная база программного обеспечения, построенная на предположении (итеративном или отдельном элементе), и вам необходимо добавить другой случай. Не меняйте название, просто меняйте документацию. Если раньше он говорил «foo», он по-прежнему говорил «foo», но вы заставляете его принимать итерируемое «foo» без каких-либо изменений параметров. Если раньше он произносил «foo_list» или «foo_iter», то он по-прежнему произносил «foo_list» или «foo_iter», но он спокойно переносит синглтон без разрывов.

    • 80% кода является устаревшим ("foo" или "foo_list")

    • 20% кода - это новая функция («foo» может быть итеративным или «foo_list» может быть отдельным объектом).

4 голосов
/ 24 сентября 2010

Думаю, я немного опоздал на вечеринку, но я удивлен, что никто не предложил декоратора.

def withmany(f):
    def many(many_foos):
        for foo in many_foos:
            yield f(foo)
    f.many = many
    return f

@withmany
def process_foo(foo):
    return foo + 1


processed_foo = process_foo(foo)

for processed_foo in process_foo.many(foos):
    print processed_foo

Я видел похожий шаблон в одном из постов Алекса Мартелли, но не помню ссылку с руки.

3 голосов
/ 10 сентября 2010

Звучит так, будто вы мучаетесь из-за уродства кода:

def ProcessWidget(widget_thing):
  # Infer if we have a singleton instance and make it a
  # length 1 list for consistency
  if isinstance(widget_thing, WidgetType):
    widget_thing = [widget_thing]

  for widget in widget_thing:
    #...

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

def ProcessOneWidget(widget):
  #...

def ProcessManyWidgets(widgets):
  for widget in widgets:
    ProcessOneWidget(widget)

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

2 голосов
/ 10 сентября 2010

Вы можете использовать * args magic (varargs), чтобы ваши параметры всегда были повторяемыми.

Передайте один или несколько известных элементов как обычные аргументы функций, например func (arg1, arg2, ...) и передайте итерируемые аргументы со звездочкой, например func (* args)

Пример:

# magic *args function
def foo(*args):
    print args

# many ways to call it
foo(1)
foo(1, 2, 3)

args1 = (1, 2, 3)
args2 = [1, 2, 3]
args3 = iter((1, 2, 3))

foo(*args1)
foo(*args2)
foo(*args3)
1 голос
/ 10 сентября 2010

Можете ли вы назвать свой параметр на очень высоком уровне?люди, которые читают код, больше заинтересованы в том, чтобы знать, что представляет параметр («клиенты»), чем их тип («list_of_tuples»);тип может быть определен в строке документации функции, что хорошо, так как он может измениться в будущем (тип иногда является деталью реализации).

0 голосов
/ 10 сентября 2010

Поскольку вам абсолютно все равно, какой тип итерации вы получаете, вы можете попытаться получить итератор для параметра, используя iter ().Если iter () вызывает исключение TypeError, параметр не может быть повторен, поэтому вы создаете список или кортеж из одного элемента, который является повторяемым, и Боб - ваш дядя.

def doIt(foos):
    try:
        iter(foos)
    except TypeError:
        foos = [foos]
    for foo in foos:
        pass    # do something here

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

def iterfy(iterable):
    if isinstance(iterable, basestring):
        iterable = [iterable]
    try:
        iter(iterable)
    except TypeError:
        iterable = [iterable]
    return iterable

def doIt(foos):
    for foo in iterfy(foos):
        pass    # do something

В отличие от некоторых из тех, кто отвечает, мне нравится это делать, поскольку он исключает одну вещь, которую может сделать вызывающая сторона.ошибаться при использовании вашего API.«Будьте консервативны в том, что вы генерируете, но либеральны в том, что вы принимаете».

Чтобы ответить на ваш первоначальный вопрос, то есть то, что вы должны назвать параметром, я бы все равно использовал «foos», даже если вы примете одинпункт, так как ваше намерение состоит в том, чтобы принять список.Если это не повторяется, то технически это 1012 * ошибка, хотя вы и исправите ее для вызывающего, поскольку, вероятно, они хотят обработать только один элемент.Кроме того, если вызывающий считает, , что он должен передать итеративный хотя бы один элемент, это, конечно, будет работать нормально и требует очень небольшого синтаксиса, так что зачем беспокоиться об исправлении их неправильного понимания?

0 голосов
/ 10 сентября 2010

Я бы сделал 1 вещь,

def myFunc(manyFoos):
    if not type(manyFoos) in (list,tuple):
        manyFoos = [manyFoos]
    #do stuff here

, поэтому вам больше не нужно беспокоиться о его названии.

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

Вместо заполнения функций ifs у вас может быть 2 функции.

0 голосов
/ 10 сентября 2010

Я бы пошел с именем, объясняющим, что параметр может быть экземпляром или списком экземпляров.Скажи one_or_more_Foo_objects.Я нахожу это лучше, чем мягкий param.

0 голосов
/ 10 сентября 2010

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

Я бы сказал, вместо того, чтобы называть его тем, что есть, вы должны назватьдля чего он используется.Также, будьте осторожны, что вы не можете вызвать use in для не повторяемого.

...