python - 迭代期间与字符串的列表/元组不同
问题描述
是编写接受参数的函数的常见模式,如果参数是数字或字符串等标量,则对其应用一些操作,如果参数是可迭代的,则对每个元素应用相同的操作这个可迭代的
问题是字符串是可迭代的,所以我不能依靠请求宽恕而不是许可来实现这一点,因为iter('hello world')
不会引发 TypeError。
例如
def apply_(func, val):
try:
for v in iter(val):
print(func(v), end=' ')
print()
except TypeError:
print(func(val))
apply_(lambda x: x+1, 1) # 2 ... Ok
apply_(lambda x: x*2, range(3)) # 0 2 4 ... Ok
apply_(str.upper, ['hello', 'world']) # HELLO WORLD ... Okay
apply_(str.upper, 'hello world') # H E L L O W O R L D, ... oops
我可以请求许可。但这仅适用于字符串的子类型。
def apply_safe(func, val):
if issubclass(type(val), str):
print(func(val))
return
try:
for v in iter(val):
print(func(v), end=' ')
print()
except TypeError:
print(func(val))
另一种选择是将此逻辑添加到类型中,这似乎是正确的做法,因为可迭代是字符串的不希望的(对于这种情况)行为。但这对调用者来说很容易出错,迟早它会忘记用NonIterableString
或任何其他类调用它。
class NonIterableString(str):
def __iter__(self):
raise TypeError()
apply(str.upper, NonIterableString('hello world')) # HELLO WORLD
我找到的最后一个解决方案是解决我的问题但可能不适用于现有代码的解决方案
def apply_multi(func, *vals):
for v in vals:
print(func(v), end=' ')
print()
这似乎是更惯用的一种。它总是有效,小巧优雅,但因为它不面对问题,它巧妙地规避了它。这里的问题是我需要为每种情况编写一个这样的函数,这似乎不是一个坏主意,但仍然很冗长......
最后这里是完整的例子
def apply_(func, val):
try:
for v in iter(val):
print(func(v), end=' ')
print()
except TypeError:
print(func(val))
def apply_safe(func, val):
if (issubclass(type(val), str)):
print(func(val))
return
try:
for v in iter(val):
print(func(v), end=' ')
print()
except TypeError:
print(func(val))
def apply_multi(func, *vals):
for v in vals:
print(func(v), end=' ')
print()
class NonIterableString(str):
def __iter__(self):
raise TypeError()
apply_(lambda x: x+1, 1) # 2 => ok
apply_(lambda x: x*2, range(3)) # 0 2 4 => ok
apply_(str.upper, ['hello', 'world']) # HELLO WORLD => ok
apply_(str.upper, 'hello world') # H E L L O W O R L D => oops
apply_(str.upper, NonIterableString('hello world')) # HELLO WORLD => ok
apply_safe(str.upper, 'Hello world') # HELLO WORLD =>j
apply_multi(str.upper, 'hello world') # HELLO WORLD => ok
最后我的问题是,是否有任何AFNFFP方法可以像标量类型而不是可迭代的那样在 python 中处理字符串?
解决方案
我认为一个好的解决方案是这样的:
defaults = {
str : False
}
def apply_(func, val, isiter: bool=None):
if isiter is None:
isiter = defaults[type(val)] if type(val) in defaults else True
if isiter:
try:
for i in iter(val):
print(func(i), end=' ')
print()
except TypeError:
print(func(val))
else:
print(func(val))
这种方法使您可以决定(仅当值是可迭代的)是否要将值视为可迭代的。如果您希望字符串类型的这种行为可能有一天您会将函数应用于列表,但仅应用于它自己的列表,而不是将函数应用于每个值。
这里defaults
表示一个可迭代类型是否应该默认被视为可迭代,在这种情况下,默认情况下str
不应被视为可迭代。
如果需要,您还有一个参数可以覆盖此“默认”行为(但前提是它有意义)。例如:
apply_(lambda x: x+1, 1) # 2 ... Ok
apply_(lambda x: x*2, range(3)) # 0 2 4 ... Ok
apply_(str.upper, ['hello', 'world']) # HELLO WORLD ... Okay
直到这里一切都一样,都按预期运行。然后让我们看看:
apply_(str.upper, 'hello world') # HELLO WORLD ... Okay
apply_(str.upper, 'hello world', isiter=True) # H E L L O W O R L D ... okay
正如您所看到hello world
的,首先被视为一个值,就像我们在中定义的那样defaults
,如果我们设置,isiter=True
那么将被视为您设置的。
让我们看另一个例子:
apply_(lambda x: 2*x, ['hello', 'world']) # hellohello worldworld ... Okay
apply_(lambda x: 2*x, ['hello', 'world'], isiter=False) # ['hello', 'world', 'hello', 'world'] ... Okay
正如您在第一种情况下看到的,列表被视为可迭代的,如果我们将isiter=False
函数设置为应用于列表本身。
最后让我们看看我们是否尝试将不可迭代的类型视为可迭代的:
apply_(lambda x: x+1, 1, isiter=True) # 2 ... Ok
在这种情况下,try: except:
处理错误。
该字典非常方便,因为设置默认行为将确保您在isiter
大多数情况下都必须使用。
推荐阅读
- python - 如何检查两列是否匹配基于它们具有相同的字符串?
- registry - 尝试远程获取/更改当前登录用户的默认打印机注册表值
- flutter - Flutter - Pub 无法重命名目录,因为访问被拒绝
- vbscript - 使用 VBS 返回两个字符之间不同长度的值
- python - 使用 az webapp deployment source 从 git 部署代码,但当应用位于 git 存储库子文件夹中时
- angular - 如何与 Angular Http 和 IP 地址通信
- python - 为 bar3d matplotlib 图选择颜色
- powerbi - DAX 计数行,但不适用于未来日期
- javascript - php - 防止后退按钮
- curl - curl命令504网关超时错误的gitlab管道失败