Пифонический способ проверки параметра - это последовательность, а не строка

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

pg_dump( file='/tmp/dump.sql',
         tables=('stack', 'overflow'),
         port=5434
         name=europe)

Должен вернуть что-то вроде:

pg_dump -t stack -t overflow -f /tmp/dump.sql -p 5434 europe

Это делается с помощью tables_string='-t '+' -t '.join(tables).

Самое интересное начинается, когда функция вызывается с: tables=('stackoverflow') (строка) вместо tables=('stackoverflow',) (кортеж), что дает:

pg_dump -t s -t t -t a -t c -t k -t o -t v -t e -t r -t f -t l -t o -t w
        -f /tmp/dump.sql -p 5434 europe

Потому что итерируется сама строка.

Этот вопрос SO предлагает использовать утверждения для типа, но я не уверен, что он достаточно Pythonic, потому что он нарушает соглашение о типе утки.

Есть идеи?

Адам


person Adam Matan    schedule 21.11.2010    source источник
comment
Вы имеете в виду пользователя, который по ошибке использует ('foo') вместо ('foo',), верно?   -  person orip    schedule 21.11.2010
comment
@orip В том-то и дело: пользователи могут совершить эту ошибку, и я хочу предотвратить ее.   -  person Adam Matan    schedule 21.11.2010


Ответы (4)


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

Другой способ справиться с этим распространенным случаем - проверить строку и правильно обработать ее как особый случай.

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

def pg_dump(*tables, **kwargs):
  file = kwargs['file']
  port = kwargs['port']
  name = kwargs['name']
  ...

pg_dump('stack', 'overflow', file='/tmp/dump.sql', port=5434, name='europe')
person orip    schedule 21.11.2010
comment
+1 Красиво. Думаю, я протестирую параметр и просто конвертирую строку в кортеж из 1. Это сделало бы пользователей более счастливыми. - person Adam Matan; 21.11.2010

Вы можете использовать ABC, чтобы утверждать, что объект является итерируемым, но не строкой:

from types import StringType
from collections import Iterable
assert isinstance(x, Iterable) and not isinstance(x, StringType)
person Katriel    schedule 21.11.2010

Распространенная идиома Python для определения того, является ли аргумент последовательностью (списком или кортежем) или строкой, заключается в проверке наличия у него атрибута __iter__:

def func(arg):
    if hasattr(arg, '__iter__'):
        print repr(arg), 'has __iter__ attribute'
    else:
        print repr(arg), 'has no __iter__ attribute'

func('abc')
# 'abc' has no __iter__

func(('abc'))
# 'abc' has no __iter__

func(('abc',))
# ('abc',) has __iter__

Когда это не последовательность, ее также обычно заменяют на одну, чтобы упростить остальную часть кода (который имеет дело только с одним типом вещей). В примере это можно было сделать с помощью простого arg = [arg].

person martineau    schedule 21.11.2010
comment
Замечание: строки, не имеющие атрибута __iter__, были несогласованны и были изменены в Python 3. как и должно быть. - person Jim Brissom; 21.11.2010
comment
@ Джим Бриссом: Мне всегда казалось, что это непоследовательно, поскольку строки - это после всех последовательностей символов. Знаете хорошую идиому PY3K? Как насчет проверки, есть ли у type(arg) строковый метод, например 'lower'? - person martineau; 21.11.2010
comment
вы можете проверить not isinstance(foo, str), как предложено в вопросе, на который ссылается спрашивающий (basestring вместо str для Python ‹3) - person orip; 21.11.2010
comment
@orip: Конечно, всегда можно проверить тип, но, как указано в OP, это не похоже на Pythonic. Было бы неплохо, если бы существовал общий способ проверить, является ли что-то последовательностью или нет, что было запутано типами строк - то, что делает тест __iter__ в Python 2, но похоже, что они исправили это в v3. - person martineau; 21.11.2010

Разве нельзя использовать список вместо кортежа?

pg_dump( file='/tmp/dump.sql',
         tables=['stack', 'overflow'],
         port=5434,
         name='europe')
person grifaton    schedule 21.11.2010
comment
Не решает проблему - если пользователь передает строку, все равно не удается. - person Adam Matan; 21.11.2010