更新
使内置字符串不可迭代的想法是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()
。然后每当我需要一个字符串文字时,我会创建一个这个类的对象。
以下是一些与切向相关的问题:
答案 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)
展开,并从中做出回答:
不,你不应该这样做。
你可以这样做,你给出的方法可能是最好的方法(用于记录,我认为子分类是更好的选择如果你必须这样做,请参阅@ 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)
不要试图使你的字符串不可迭代,而是改变你正在看问题的方式:你的一个参数是可迭代的,或者是......
编写函数时,首先要验证参数,对吧?
def set_fields(record, fields, value):
if isinstance(fields, str):
fields = (fields, ) # tuple-ize it!
for f in fields:
record[f] = value
当您处理其他可以是单数或复数的函数和参数时,这将很好地为您服务。