在python中为字符串和列表编写一个通用函数

Posted

技术标签:

【中文标题】在python中为字符串和列表编写一个通用函数【英文标题】:Writing a generalized function for both strings and lists in python 【发布时间】:2012-02-14 11:17:15 【问题描述】:

所以我像草一样绿,向How to think like a computer scientist: Learn python 3学习编程。我能够回答这个问题(见下文),但担心我错过了课程。

编写一个函数(称为 insert_at_end),它将通过(返回给定之前的两个参数的粗体)所有三个:

test(insert_at_end(5, [1, 3, 4, 6]), **[1, 3, 4, 6, 5]**)
test(insert_at_end('x', 'abc'),  **'abcx'**)
test(insert_at_end(5, (1, 3, 4, 6)), **(1, 3, 4, 6, 5)**)

这本书给出了这样的提示:“这些练习很好地说明了序列抽象是通用的(因为切片、索引和连接是如此通用),因此可以编写通用函数处理所有序列类型。”。

这个版本没有在线解决方案(我可以找到),但我找到了某人对之前版本文本的答案(适用于 python 2.7),他们这样做了:

def encapsulate(val, seq):
    if type(seq) == type(""):
        return str(val)
    if type(seq) == type([]):
        return [val]
    return (val,)

def insert_at_end(val, seq): 
    return seq + encapsulate(val, seq)

这似乎是通过区分列表和字符串来解决问题......违背了提示。那么怎么样 有没有办法在不区分的情况下回答这个问题(以及大约 10 个类似的问题)?即不使用“type()”

【问题讨论】:

我认为你不会从尝试解决这个问题中学到任何有用的东西。 【参考方案1】:

我的最大努力:

def insert_at_end(val, seq):
    t = type(seq)
    try:
        return seq + t(val)
    except TypeError:
        return seq + t([val])

这将尝试创建type(seq) 的序列,如果val 不可迭代,则生成一个列表并连接。

【讨论】:

test(insert_at_end('xyz', ['abc']), **['abc','xyz']**) 不会通过。【参考方案2】:

我想说这个例子不是对称的,这意味着它要求读者处理两种不同的情况:

int,列表 str,str

在我看来,练习应该要求实现这一点:

列表,列表:insert_at_end([5], [1, 3, 4, 6]) str, str: insert_at_end('x', 'abc')

在这种情况下,读者只需使用两个使用相同序列类型的参数,提示会更有意义。

【讨论】:

我不同意——问题不是序列类型——而是新值是否可迭代。 我认为,整个问题在于 str(或 unicode)与列表、元组或其他任何东西的工作方式不同。否则 str(['a', 'b', 'c']) 会产生 'abc' 而不是 "['a', 'b', 'c']"...【参考方案3】:

不是解决方案,而是解释为什么真正优雅的解决方案看起来不可能。

+ 连接序列,但仅连接相同类型的序列。 作为第一个参数传递给insert_at_end 的值是“标量”,因此您必须将它们转换为第二个参数所具有的序列类型。 为此,您不能简单地调用带有标量参数的序列构造函数并创建该类型的单项序列:tuple(1) 不起作用。 str 与其他序列类型的工作方式不同:tuple(["a"])("a",)list(["a"])["a"],但 str(["a"]))"['a']" 而不是 "a"

这会使+ 在这种情况下无用,即使您可以轻松地构造一个给定类型的序列,而无需使用instanceof,只需使用type()

你也不能使用切片赋值,因为只有列表是可变的。

在这种情况下,@Hamish 的解决方案看起来最干净。

【讨论】:

Hamish 的解决方案相当干净,但我认为如果您尝试在字符串列表的末尾插入一个多字符串,则会出现严重错误。 @Duncan:嗯,使用列表,您可以添加嵌套列表,但没有嵌套字符串之类的东西。要正确防止它,您要么需要显式检查str(这违背了目的),要么有一个足够强大的静态类型系统来禁止嵌套列表(它采用与 Python 不同的语言)。 @9000 太好了,非常感谢,非常重要(不仅仅是一个具体的答案),我希望确认正如你所说的那样“一个优雅的解决方案看起来不可能”。我担心我错过了一个可能会在以后困扰我的潜在概念。【参考方案4】:

该问题是一长串问题之一,提示适用于所有问题。我认为这是合理的,在编写了 encapsulate 函数后可以重复用于 insert_at_front 之类的东西,其余的实现与类型无关。

但是,我认为encapsulate 的更好实现可能是:

def encapsulate(val, seq):
    if isinstance(seq, basestring):
        return val
    return type(seq)([val])

用更少的代码处理更广泛的类型。

【讨论】:

优势:此解决方案有效。缺点:它不是一个很好的类型无关解决方案,它使用序列的通用接口;相反,它特例一些神奇的类。 @9000,字符串通常需要特殊大小写,它们是唯一只包含与自身相同类型的对象的序列,并且会弄乱很多原本干净的鸭子类型代码。否则,它会处理listtuple、它们的子类和任何其他遵循可以从列表构造的通用模型的序列。 是的,字符串需要特殊大小写,这是一个严酷的事实,阻止了这个问题有一个优雅的类型不可知的、完全基于接口的解决方案。【参考方案5】:

这个问题的挑战(在 Python 2.7 中,我现在正在测试 3.2 来验证)是seq 的两种可能的输入类型是不可变的,并且您应该返回与之前相同的类型传入。对于字符串,这不是什么大问题,因为您可以这样做:

return seq + char

因为这将返回一个新字符串,该字符串是输入序列和附加字符的连接,但这不适用于列表或元组。您只能将列表连接到列表或将元组连接到元组。如果您想避免“类型”检查,您可以使用以下方法:

if hasattr(seq, 'append'): # List input.
  seq.append(char)
elif hasattr(seq, 'strip'): # String input.
  seq = seq + char
else: # Tuple
  seq = seq + (char,)

return seq

这与实际检查类型并没有太大区别,但确实避免了直接使用type 函数。

【讨论】:

【参考方案6】:

与列表/元组相比,此解决方案仍然需要一些单独的字符串代码,但它更简洁,不对特定类型进行任何检查。

def insert_at_end(val, seq):
    try:
        return seq + val
    except TypeError:   # unsupported operand type(s) for +
        return seq + type(seq)([val])

【讨论】:

怎么样:assert insert_at_end(['val'], ['seq']) == ['seq', ['val']] ?【参考方案7】:

也许这更接近答案:

def genappend(x, s):
    if isinstance(s, basestring):
        t = s[0:0].join
    else:
        t = type(s)
    lst = list(s)
    lst.append(x)
    return t(lst)

print genappend(5, [1,2,3,4])    
print genappend(5, (1,2,3,4))
print genappend('5', '1234')

还可以有完全用户定义的序列类型。只要可转换为列表或从列表转换,它们也将起作用。这也有效:

print genappend('5', set('1234'))

【讨论】:

【参考方案8】:

我同意重点是item 是否可迭代。

所以我的解决方案是这样的:

def iterate(seq, item):
    for i in seq:
        yield i
    yield item

def insert_at_end(seq, item):
    if hasattr(item, '__iter__'):
        return seq + item
    else:
        return type(seq)(iterate(seq, item))

例子:

>>> insert_at_end('abc', 'x')
'abcx'
>>> insert_at_end([1, 2, 4, 6], 5)
[1, 2, 4, 6, 5]
>>> insert_at_end((1, 2, 4, 6), 5)
(1, 2, 4, 6, 5)

由于insert_at_end 可以处理可迭代和不可迭代,因此即使在以下情况下也能正常工作:

>>> insert_at_end('abc', 'xyz')
'abcxyz'
>>> insert_at_end([1, 2, 4, 6], [5, 7])
[1, 2, 4, 6, 5, 7]
>>> insert_at_end((1, 2, 4, 6), (5, 7))
(1, 2, 4, 6, 5, 7)

【讨论】:

这是完全错误的。 insert_at_end([1, 2, 4, 6], [5]) 按照所有逻辑应该是 [1, 2, 4, 6, [5]]。 @RomanSusi:如果您想以相同的方式处理字符串和列表,我会说这不是我所期望的。另一方面,按照常识,我同意你的看法;所以我是第一个有点困惑的人,我不会过多争论,因为这个练习的作者似乎也有他自己的逻辑......【参考方案9】:

虽然封装依赖于类型,但 insert_at_end 中的直接代码不依赖于所有 3 种类型的 + 含义相关的东西,因此从这个意义上说,符合提示。

【讨论】:

一定有更好的答案。答案可能使用类型,但不是特定类型。 你不能只是把有问题的代码扔到不同的函数中,然后称之为胜利! :D 我也很想看看——但我还没有看到有人推荐过。

以上是关于在python中为字符串和列表编写一个通用函数的主要内容,如果未能解决你的问题,请参考以下文章

在python中为字符串字段创建嵌套列表

序列类型(列表和元祖包括字符串等)通用的的内建函数

字符串,列表,元组,字典,集合,序列通用操作

在 spark scala 中为 withcolumn 编写通用函数

Java 中类似 Python 的列表推导

如何编写用于将分隔字符串转换为列表的通用扩展方法?