簡體   English   中英

如何在python中用partial填充特定的位置參數?

[英]How to fill specific positional arguments with partial in python?

基本上,我想做的是:

>>> from functools import partial
>>> partial(str.startswith, prefix='a')('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: startswith() takes no keyword arguments

但更普遍的問題是,如何用partial填充特定的位置參數

PS 我確實意識到我可以使用lambda代替。

這是不可能的。 你必須做一個包裝函數。

表面上,您會使用關鍵字參數,就像您嘗試做的那樣 - 這就是它們的用途,對嗎? 不幸的是,正如您所發現的,python 的標准庫函數不接受命名參數 因此,鑒於目前的partial實現方式,不可能在不使其他功能運行干擾的情況下。

根據PEP 309的接受,被接受的內容是“一個 partial() 類型構造函數綁定最左邊的位置參數和任何關鍵字”。 此外,它指出:

請注意,當對象像函數一樣被調用時,位置參數會附加到提供給構造函數的參數上,並且關鍵字參數會覆蓋和擴充提供給構造函數的參數。

可以在創建對象和調用對象時提供位置參數、關鍵字參數或兩者。

因為附加的位置參數被附加,就沒有辦法提供一些前述位置參數(由此實現)。

然而,它繼續:

部分應用來自right 的參數,或在任意位置插入參數會產生其自身的問題,但在發現良好的實現和不混淆的語義之前,我認為不應該排除它。

因此,它顯然可以擺在桌面上,但就目前而言,它並沒有以這種方式實施。

為了披露起見,上面引號中的重點是我自己的。

如果你真的需要這個,你可以使用rpartialfuncy第三方庫。

它的代碼在這里:

def rpartial(func, *args):
    return lambda *a: func(*(a + args))

因此,您的情況可以按以下方式處理:

>>> startswith_a = rpartial(str.startswith, 'a')
>>> startswith_a('abc')
True
>>> startswith_a('def')
False

我知道這很舊,但我一直遇到這個問題,我想我已經創建了一個巧妙的解決方案。 如果您在構建對象時不知道它,我使用了一個稍微修改過的partial版本,它使用一個 Ellipses 對象 ( ... ) 作為占位符值。 這對這個非常有用!

這是partial的原始__call__方法

def __call__(self, /, *args, **keywords):
    keywords = {**self.keywords, **keywords}
    return self.func(*self.args, *args, **keywords)

相反,我們可以使用文字...作為一種特殊情況來指示占位符值

>>> type(...)
<class 'ellipsis'>

這是整個實現:

class bind(partial):
    """
    An improved version of partial which accepts Ellipsis (...) as a placeholder
    """
    def __call__(self, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        iargs = iter(args)
        args = (next(iargs) if arg is ... else arg for arg in self.args)
        return self.func(*args, *iargs, **keywords)

用法相當簡單

def foo(a, b, c, /, *, d):
    print(f"A({a}) B({b}) C({c}) D({d})")

f1 = bind(foo, 1, 2, 3, d=4)
f1()

f2 = bind(foo, 1, 2, d=4)
f2(3)

f3 = bind(foo, 1, ..., 3, d=4)
f3(2)

f4 = bind(foo, ..., 2, ..., d=4)
f4(1, 3)

f5 = bind(foo, ..., d=5)
f5(1, 2, 3, d=4)

使用此代碼:

# See: functoolspartial for binding...

class Function(object):
    def __init__(self, fn):
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def __add__(self, other):
        def subfn(*args, **kwargs):
            return self(*args, **kwargs) + other(*args, **kwargs)
        return subfn


class arg(object):
    """Tagging class"""
    def __init__(self, index):
        self.index = index


class bind(Function):
    """Reorder positional arguments.
    Eg: g = f('yp', _1, 17, _0, dp=23)
    Then g('a', 'b', another=55) --> f('yp', 'b', 17, 'a', dp=23, another=55)
    """

    def __init__(self, fn, *pargs, **pkwargs):
        # Maximum index referred to by the user.
        # Inputs to f above this index will be passed through
        self.fn = fn
        self.pargs = pargs
        self.pkwargs = pkwargs
        self.max_gindex = max(
            (x.index if isinstance(x, arg) else -1 for x in pargs),
            default=-1)

    def __call__(self, *gargs, **gkwargs):
        fargs = \
            [gargs[x.index] if isinstance(x, arg) else x for x in self.pargs] + \
            list(gargs[self.max_gindex+1:])

        fkwargs = dict(self.pkwargs)
        fkwargs.update(gkwargs)    # Overwrite keys
        return self.fn(*fargs, *fkwargs)

然后像這樣嘗試:

def myfn(a,b,c):
print(a,b,c)

bind(myfn, arg(1), 17, arg(0))(19,14)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM