是编写接受参数的函数的常见模式,如果参数是标量(如数字或字符串),则对其应用某些操作,如果参数是可迭代的,则对参数应用相同的操作此可迭代的每个元素
问题在于字符串是可迭代的,所以我不能依靠 Ask寻求宽恕而不是获得许可来实现这一目标,因为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
最后,我的问题是,是否有任何 AFFNFP 方法在Python中像标量类型一样处理字符串,而不是可迭代的类型?
答案 0 :(得分:2)
我认为一个好的解决方案是这样的:
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
。