【问题标题】:Higher order functions usage with str.startswith使用 str.startswith 的高阶函数
【发布时间】:2023-04-03 09:12:01
【问题描述】:

我有一个文件,我想从中清除注释行。 我想使用functools.partial 进行操作,类似于以下方式:

from functools import partial

f = open(filetoread, "r")
lines = f.readlines()
f.close()

# Filtering the comment lines
f_func = partial(str.startswith, prefix="#")
lines = filter(f_func, lines)

这不起作用,显然是因为str.startswith 是一个类方法。将functools.partialstr.startswith 一起使用的正确方法是什么?

【问题讨论】:

    标签: python higher-order-functions functools


    【解决方案1】:

    你有三个问题:

    首先,str.startswith 不接受关键字参数,它只接受位置参数。它在 C 中实现,并使用 PyArg_ParseTuple 来获取其位置参数。没有什么可以告诉 Python 他们有什么名字,因为名字 prefix 只出现在文档字符串中。所以prefix= 不好。问题与此类似:

    >>> str.startswith('#hi', prefix='#')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: startswith() takes no keyword arguments
    >>> def foo(self,prefix,start=None,end=None): return str.startswith(self,prefix,start,end)
    ...
    >>> foo('#hi', prefix='#')
    True
    

    其次,str.startswith 的第一个参数不是前缀,它是self,我们正在检查它是否具有前缀的对象。它作为一个方法没有固有的问题,如果有一个名为str.isprefixof 的方法,或者如果这个方法是在 Python 中实现的,你会很好。但是没有,也没有。

    最后,functools.partial 只允许您绑定初始位置参数,它不知道如何绑定第二个而不是第一个。由于您要绑定的参数不是第一个并且无法通过名称访问,因此您没有选择。

    不过,对于这种情况,有一个解决方法是编写函数式代码。你只需要operator.methodcaller,而不是functools.partial

    f_func = operator.methodcaller('startswith', '#')
    

    operator.methodcaller 绑定所有参数除了第一个。


    既然你问什么是使用functools 的正确方法,我想你可以用一个额外的高阶函数来反转参数:

    def flip(func):
        return lambda x,y: func(y,x)
    
    f_func = partial(flip(str.startswith), '#')
    

    您可能会认为这是“作弊”,因为它使用了 lambda,如果我们想使用 lambda,我们会像 Daniel 的回答那样使用它。但是我们正在使用它来实现 Python 不提供的基本高阶函数,一旦不妨碍定义f_func 的行感觉很好且实用。

    请注意,我并没有在 Python 术语中实现那个高阶函数,尤其是 well,因为我没有像 partial 那样提供任何元数据。它有一个func 只读属性,用户可能也希望flip 也提供该属性。

    或者,您可以为 str.startswith 编写一个 Python 包装器,例如上面的 foo,然后将 partial 应用于它。

    【讨论】:

    • 关于“三个问题”你错了:a-str.startswith 有一个名为prefix 的参数,这是在需要时使用partial 生成高阶函数的方法不按签名的顺序分配参数(无论它们是否是关键字参数)。 b- 这很明显,这就是我使用prefix 的原因。 c- 正如我提到的,partial 允许您设置函数的任何参数
    • 嗯,你提出了一个有趣的观点。 str.startswith 使用位置参数的名称失败(它尝试将关键字参数传递给它)但适用于定义为 def foo(self,prefix): return self.startswith(prefix)foo
    • 哦,这可能与str.startswith在CPython中的实现方式有关。我敢打赌它使用PyArg_ParseTuple(或类似的),因此Python不“知道”它的参数的名称。如果你用 Python 编写一个具有相同方法签名的类,它就可以工作。
    • 史蒂夫-谢谢你的发言。我猜我以后就不能这样用了……
    【解决方案2】:

    我认为 partial 在这里不是正确的工具。相反,您可以使用 lambda:

    func = lambda s: s.startswith('#')
    lines = filter(func, lines)
    

    虽然请注意 Python 并不是真正的函数式语言,而更 Python 化的方式是使用列表推导:

    lines = [line for line in lines if line.startswith('#')]
    

    【讨论】:

    • 我知道 partial 不是最好的方法,这就是为什么我提到 我想使用 partial 来解决这个问题——也就是说,即使还有其他解决这个问题的方法。
    猜你喜欢
    • 1970-01-01
    • 2016-05-07
    • 1970-01-01
    • 2021-05-25
    • 1970-01-01
    相关资源
    最近更新 更多