迭代期间与字符串列表/元组不同

时间:2019-04-07 18:02:03

标签: python

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

问题在于字符串是可迭代的,所以我不能依靠 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中像标量类型一样处理字符串,而不是可迭代的类型?

1 个答案:

答案 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