首页 > 解决方案 > 迭代期间与字符串的列表/元组不同

问题描述

是编写接受参数的函数的常见模式,如果参数是数字或字符串等标量,则对其应用一些操作,如果参数是可迭代的,则对每个元素应用相同的操作这个可迭代的

问题是字符串是可迭代的,所以我不能依靠请求宽恕而不是许可来实现这一点,因为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 中处理字符串?

标签: 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大多数情况下都必须使用。


推荐阅读