Pythonic способ проверки параметра - это последовательность, но не строка

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

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, потому что он нарушает соглашение типа утки.

Любые идеи?

Адам

Ответ 1

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

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

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

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')

Ответ 2

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

from types import StringType
from collections import Iterable
assert isinstance(x, Iterable) and not isinstance(x, StringType)

Ответ 3

Общая идиома 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].

Ответ 4

Не можете ли вы использовать список, а не кортеж?

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