抑制字符串处理为可迭代

时间:2012-02-06 23:27:57

标签: string python-3.x python iterable

更新

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

以下是试用期实施不可互换str的关键comments by Guido

  

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

     
      
  • 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

除非在无数地方测试isinstance(fields, str),否则不会引发任何异常。在某些情况下,这个bug需要很长时间才能找到。

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

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

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

How can I tell if a python variable is a string or a list?

how to tell a variable is iterable but not a string

5 个答案:

答案 0 :(得分: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...

答案 1 :(得分:6)

展开,并从中做出回答:

不,你不应该这样做。

  1. 它改变了人们对字符串的期望。
  2. 这意味着整个程序的额外开销。
  3. 这在很大程度上是不必要的。
  4. 检查类型非常简单。
  5. 你可以这样做,你给出的方法可能是最好的方法(用于记录,我认为子分类是更好的选择如果你必须这样做,请参阅@ kindall's方法),但它根本不值得做,而且它不是非常pythonic。首先避免错误。在您的示例中,您可能想要问问自己,这是否是一个问题清晰的问题,以及命名参数或splat是否可能是更好的解决方案。

    E.g:更改顺序。

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

    E.g:命名参数。

    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的项目而不是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")
    

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

答案 2 :(得分:4)

在这种情况下,类型检查不是单一的或不好的。做一个:

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

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

if isinstance(var, (str, bytes)):
    raise TypeError("Var should be an iterable, not str or bytes")

答案 3 :(得分: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

答案 4 :(得分:0)

不要试图使你的字符串不可迭代,而是改变你正在看问题的方式:你的一个参数是可迭代的,或者是......

  • 字符串
  • INT
  • 自定义类

编写函数时,首先要验证参数,对吧?

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

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