抑制字符串处理为可迭代

max*_*max 14 python string iterable python-3.x

更新:

2006年在python.org上提出了使内置字符串不可迭代的想法.我的问题不同之处在于我试图偶尔禁止这个功能; 仍然这整个线程非常相关.

以下是Guido的批评意见,他们str在试验基础上实施了不可迭代的评论:

[...]我实现了这个(这很简单)然后发现我必须修复大量遍布字符串的地方.例如:

  • sre解析器和编译器使用set("0123456789")之类的东西,并迭代输入regexp的字符来解析它.

  • difflib有两个字符串列表定义的API(文件的典型逐行差异),或两个字符串(典型的行内差异),甚至两个任何列表(对于广义序列差异) .

  • optparse.py,textwrap.py,string.py中的小变化.

而且我甚至还没有在regrtest.py框架工作的地方(由于difflib问题).

我放弃了这个项目; 补丁是SF补丁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
Run Code Online (Sandbox Code Playgroud)

除了isinstance(fields, str)在无数地方进行测试之外,没有任何例外,也没有任何例外.在某些情况下,这个bug需要很长时间才能找到.

我想在我的项目中完全禁止字符串被视为可迭代的.这是个好主意吗?它可以轻松安全地完成吗?

也许我可以子类化内置,str这样我就需要显式调用,get_iter()如果我希望它的对象被视为可迭代的.然后每当我需要一个字符串文字时,我会创建一个这个类的对象.

以下是一些与切线相关的问题:

如何判断python变量是字符串还是列表?

如何判断变量是可迭代的但不是字符串

kin*_*all 8

遗憾的是,没有任何方法可以自动执行此操作.你提出的解决方案(一个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...
Run Code Online (Sandbox Code Playgroud)


Gar*_*tty 6

扩展,并从中得出答案:

不,你不应该这样做.

  1. 它改变了人们对字符串的期望.
  2. 这意味着整个程序的额外开销.
  3. 这在很大程度上是不必要的
  4. 检查类型非常简单.

你可以做到,你给出的方法可能是最好的方法(对于记录,我认为子分类是更好的选择如果你必须这样做,请参阅@ 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')
Run Code Online (Sandbox Code Playgroud)

例如:命名参数.

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.
Run Code Online (Sandbox Code Playgroud)

如果你真的希望订单相同,但不要认为命名参数的想法足够清楚,那么如何使每个记录成为一个类似dict的项目而不是dict(如果它还没有)并且具有:

class Record:
    ...
    def set_fields(self, *fields, value):
        for f in fileds:
            self[f] = value

weapon1.set_fields("Name", "ShortName", value="Dagger")
Run Code Online (Sandbox Code Playgroud)

这里唯一的问题是引入的类以及值参数必须使用关键字完成的事实,尽管它保持清晰.

或者,如果您使用的是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')
Run Code Online (Sandbox Code Playgroud)

或者,对于我的最后一个例子:

class Record:
    ...
    def set_fields(self, *args):
        *fields, value = args
        for f in fileds:
            self[f] = value

weapon1.set_fields("Name", "ShortName", "Dagger")
Run Code Online (Sandbox Code Playgroud)

但是,这些在读取函数调用时确实会留下一些奇怪的东西,因为人们通常认为参数不会以这种方式处理.

  • 我知道这是unpythonic,这就是为什么我这样做感觉不好......但是我怎么能避免这些错误呢?我们谈论的是字面上遗漏了一对括号..偶尔几乎不可能避免,不是吗? (2认同)