15

更新:

2006 年在 python.org 上提出了使内置字符串不可迭代的想法。我的问题有所不同,因为我只是偶尔尝试抑制此功能;整个线程仍然非常相关。

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

[...] 我实现了这个(做起来真的很简单),但后来发现我必须修复大量迭代字符串的地方。例如:

  • sre 解析器和编译器使用 set("0123456789") 之类的东西,并遍历输入正则表达式的字符来解析它。

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

  • 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 变量是字符串还是列表?

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

4

5 回答 5

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...
于 2012-02-06T23:39:07.910 回答
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')

例如:命名参数。

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

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

或者,如果您使用的是 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")

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

于 2012-02-06T23:37:04.417 回答
4

在这种情况下,类型检查并不是不合常规的或不好的。只需做一个:

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

在通话开始时。或者,如果您想教育调用者:

if isinstance(var, (str, bytes)):
    raise TypeError("Var should be an iterable, not str or bytes")
于 2012-02-07T12:26:43.827 回答
2

您如何看待创建不可迭代的字符串?

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
于 2012-02-07T02:03:13.327 回答
0

与其尝试使您的字符串不可迭代,不如切换您查看问题的方式:您的参数之一是可迭代的,或者...

  • 细绳
  • 整数
  • 自定义类
  • 等等

当你写你的函数时,你做的第一件事就是验证你的参数,对吧?

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

当您处理其他可以是单数或复数的函数和参数时,这将为您提供很好的服务。

于 2012-02-07T04:20:35.090 回答