Подавление обработки строки как повторяемой - PullRequest
14 голосов
/ 07 февраля 2012

ОБНОВЛЕНИЕ:

Идея сделать встроенные строки неповторяемыми была , предложенной на python.org в 2006 году .Мой вопрос отличается тем, что я пытаюсь подавлять эти функции только время от времени;Тем не менее, весь этот поток весьма актуален.

Вот критические комментарии Гвидо , который реализовал не повторяемый str на экспериментальной основе:

[...] Я реализовал это (это было действительно просто сделать), но потом обнаружил, что мне нужно исправить множество мест, которые перебирают строки.Например:

  • Анализатор и компилятор sre используют такие вещи, как set ("0123456789"), а также перебирают символы входного регулярного выражения для его анализа.

  • У difflib есть API, определенный для двух списков строк (типичный построчный diff файла), или для двух строк (типичный внутристрочный 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 переменнойстрока или список?

как сказать, что переменная итеративна, но не строка

Ответы [ 5 ]

8 голосов
/ 07 февраля 2012

К сожалению, нет никаких способов сделать это автоматически.Решение, которое вы предлагаете (подкласс 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...
6 голосов
/ 07 февраля 2012

Чтобы расширить и сделать из этого ответ:

Нет, вы не должны этого делать.

  1. Изменяет функциональность, которую люди ожидают от строк.
  2. Это означает дополнительные накладные расходы по всей вашей программе.
  3. Это в основном не нужно.
  4. Проверка типов очень не пифонична.

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

Например: изменить порядок.

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

Eg: Именованные аргументы.

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.

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

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

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

4 голосов
/ 07 февраля 2012

Проверка типов в этом случае не является пифонической или плохой. Просто сделайте:

if isinstance(var, (str, bytes)):
    var = [var]

В начале разговора. Или, если вы хотите обучить звонящего:

if isinstance(var, (str, bytes)):
    raise TypeError("Var should be an iterable, not str or bytes")
2 голосов
/ 07 февраля 2012

Что вы думаете о создании не повторяемой строки?

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
0 голосов
/ 07 февраля 2012

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

  • string
  • int
  • пользовательский класс
  • и т. Д.

Когда вы пишете свою функцию, первое, что вы делаете, это проверка параметров, верно?

def set_fields(record, fields, value):
    if isinstance(fields, str):
        fields = (fields, )  # tuple-ize it!
    for f in fields:
        record[f] = value

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...