Подавление обработки строки как iterable
UPDATE:
Идея создания встроенных строк non-iterable была предложенная на python.org в 2006 году. Мой вопрос отличается тем, что я пытаюсь только подавлять эти функции время от времени; все же весь этот поток весьма уместен.
Вот критические комментарии Guido, которые внедрили non-iterable str
на пробной основе:
[...] Я реализовал это (было действительно просто do), но затем я обнаружил, что мне пришлось исправить тонны мест, которые повторяются строки. Например:
-
sre parser и compiler используют такие вещи, как set ( "0123456789" ), а также перебирают символы входного regexp для его синтаксического анализа.
-
difflib имеет API, определенный для двух списков строк (типичный поэтапный разброс файла) или двух строк (типичный внутрилинейный diff) или даже два списка (для обобщенного последовательность diff).
-
небольшие изменения в optparse.py, textwrap.py, string.py.
И я даже не в том месте, где структура regrtest.py даже работает (из-за проблемы с difflib).
Я отказываюсь от этого проекта; патч SF patch 1471291. Я не дольше в пользу этой идеи; это просто не практично, а помещение что есть несколько веских причин для итерации по строке, было опровергнуты случаями использования, которые я нашел как в sre, так и в difflib.
ОРИГИНАЛЬНЫЙ ВОПРОС:
В то время как он является опрятной особенностью языка, что строка является итерируемой, в сочетании с утиным набором текста, это может привести к катастрофе:
# record has to support [] operation to set/retrieve values
# fields has to be an iterable that contains the fields to be set
def set_fields(record, fields, value):
for f in fields:
record[f] = value
set_fields(weapon1, ('Name', 'ShortName'), 'Dagger')
set_fields(weapon2, ('Name',), 'Katana')
set_fields(weapon3, 'Name', 'Wand') # I was tired and forgot to put parentheses
Никакое исключение не будет поднято, и нет простого способа поймать его, кроме как путем тестирования для isinstance(fields, str)
в бесчисленных местах. В некоторых случаях эта ошибка займет очень много времени.
Я хочу отключить строки, которые будут обрабатываться как итерируемые целиком в моем проекте. Это хорошая идея? Можно ли это сделать легко и безопасно?
Возможно, я мог бы подкласса встроенного str
, так что мне нужно было бы явно вызвать get_iter()
, если бы я хотел, чтобы его объект рассматривался как итерабельность. Тогда, когда мне нужен строковый литерал, я бы вместо этого создал объект этого класса.
Вот некоторые касательные вопросы:
Как определить, является ли переменная python строкой или списком?
как указать переменную, является итерируемой, но не строкой
Ответы
Ответ 1
Нет никаких способов сделать это автоматически, к сожалению. Решение, которое вы предлагаете (подкласс str
, который не является итерируемым), страдает от той же проблемы, что и isinstance()
... а именно, вы должны помнить, что используете его везде, где используете строку, потому что нет возможности сделать Python используйте его вместо родного класса. И, конечно же, вы не можете обезопасить встроенные объекты.
Я мог бы предположить, что если вы обнаружите, что пишете функцию, которая принимает либо итерируемый контейнер, либо строку, возможно, что-то не так с вашим дизайном. Иногда вы не можете избежать этого.
На мой взгляд, наименее навязчивое дело - поставить чек в функцию и называть это, когда вы попадаете в цикл. Это, по крайней мере, приводит к изменению поведения, где вы, скорее всего, его увидите: в инструкции for
, не скрытой где-то в классе.
def iterate_no_strings(item):
if issubclass(item, str): # issubclass(item, basestring) for Py 2.x
return iter([item])
else:
return iter(item)
for thing in iterate_no_strings(things):
# do something...
Ответ 2
Чтобы расширить и сделать ответ из этого:
Нет, вы не должны этого делать.
- Он изменяет функциональность, которую люди ожидают от строк.
- Это означает дополнительные накладные расходы во всей вашей программе.
- В основном это не нужно.
- Типы проверок очень несовместимы.
Вы можете это сделать, и методы, которые вы указали, вероятно, являются лучшими способами ( для записи, я думаю, что более подходящий вариант. Если вам нужно это сделать, см. @kindall метод), но это просто не стоит делать, и это не очень pythonic. Избегайте ошибок в первую очередь. В вашем примере вы можете спросить себя, является ли эта проблема более ясной в ваших аргументах, и могут ли быть более эффективными решения или аргументы splat.
Например: измените порядок.
def set_fields(record, value, *fields):
for f in fields:
record[f] = value
set_fields(weapon1, 'Dagger', *('Name', 'ShortName')) #If you had a tuple you wanted to use.
set_fields(weapon2, 'Katana', 'Name')
set_fields(weapon3, 'Wand', 'Name')
Например: Именованные аргументы.
def set_fields(record, fields, value):
for f in fields:
record[f] = value
set_fields(record=weapon1, fields=('Name', 'ShortName'), value='Dagger')
set_fields(record=weapon2, fields=('Name'), value='Katana')
set_fields(record=weapon3, fields='Name', value='Wand') #I find this easier to spot.
Если вы действительно хотите, чтобы порядок был таким же, но не думайте, что идея с именованными аргументами достаточно ясна, то как сделать каждую запись диктоподобным элементом вместо dict (если это еще не так) и иметь
class Record:
...
def set_fields(self, *fields, value):
for f in fileds:
self[f] = value
weapon1.set_fields("Name", "ShortName", value="Dagger")
Единственная проблема здесь - это введенный класс и тот факт, что параметр значения должен выполняться с ключевым словом, хотя он сохраняет четкость.
В качестве альтернативы, если вы используете Python 3, у вас всегда есть возможность использовать расширенную распаковку:
def set_fields(*args):
record, *fields, value = args
for f in fields:
record[f] = value
set_fields(weapon1, 'Name', 'ShortName', 'Dagger')
set_fields(weapon2, 'Name', 'Katana')
set_fields(weapon3, 'Name', 'Wand')
Или, для моего последнего примера:
class Record:
...
def set_fields(self, *args):
*fields, value = args
for f in fileds:
self[f] = value
weapon1.set_fields("Name", "ShortName", "Dagger")
Однако при чтении вызовов функций они оставляют некоторые странности из-за того, что обычно предполагается, что аргументы не будут обрабатываться таким образом.
Ответ 3
Проверка типов в этом случае не является беспризорной или плохой. Просто выполните:
if isinstance(var, (str, bytes)):
var = [var]
В начале вызова. Или, если вы хотите обучить вызывающего:
if isinstance(var, (str, bytes)):
raise TypeError("Var should be an iterable, not str or bytes")
Ответ 4
Что вы думаете о создании строки без итераций?
class non_iter_str(str):
def __iter__(self):
yield self
>>> my_str = non_iter_str('stackoverflow')
>>> my_str
'stackoverflow'
>>> my_str[5:]
'overflow'
>>> for s in my_str:
... print s
...
stackoverflow
Ответ 5
Вместо того чтобы пытаться сделать ваши строки неистребимыми, переключите способ поиска проблемы: один из ваших параметров либо итерабелен, либо...
- строка
- ИНТ
- пользовательский класс
- и др.
Когда вы пишете свою функцию, первое, что вы делаете, это проверка ваших параметров, правильно?
def set_fields(record, fields, value):
if isinstance(fields, str):
fields = (fields, ) # tuple-ize it!
for f in fields:
record[f] = value
Это послужит вам хорошо, поскольку вы будете иметь дело с другими функциями и параметрами, которые могут быть как сингулярными, так и множественными.