Привязка функции как метода, который получает `self` в позиции, отличной от первой - PullRequest
0 голосов
/ 10 апреля 2020

У меня следующая ситуация:

# Module A.py

def AddThreeNumbers(a, b, c, parent=None):
    """
    Add `a + b + c` and return the result.

    If `parent` is supplied, additionally store
    the result in `parent.result`.

    `parent` is optional, so we don't want it
    to be the first argument when this is
    called as a global function.
    """

    result = a + b + c
    if parent:
        parent.result = result
    return result

и:

# Module B.py

class SomeClass:
    # ... various methods
    pass

try:
    import A
except ImportError:
    # The `A` module may or may not be available.
    pass # If it isn't, no harm no foul, but...
else:
    # if module `A` *is* available, then we would
    # like to monkey-patch the `SomeClass` class
    # so that it has an `AddThreeNumbers` method...

    SomeClass.AddThreeNumbers = XXX( A.AddThreeNumbers )

XXX в последней строке - недостающий фрагмент. Я хочу каким-то образом преобразовать функцию AddThreeNumbers, чтобы self передавался как последний аргумент (parent), а не как первый. Я мог бы, конечно, обернуть это:

SomeClass.AddThreeNumbers = lambda self, a, b, c: A.AddThreeNumbers(a, b, c, parent=self)

... но это имеет два недостатка. Во-первых, я теряю __doc__ (я полагаю, что это можно обойти с помощью functools.wraps), а во-вторых, я должен поддерживать подпись (имена аргументов a, b и c) в двух местах, если это когда-либо меняется / расширяется.

Есть ли лучший способ?

Ответы [ 2 ]

2 голосов
/ 10 апреля 2020

Обертка может просто взять (self, *args, **kwargs), поэтому вам не нужно дублировать вещи. И, как вы упомянули, functools.wraps всегда хорошая идея для упаковщиков.

Однако мне это кажется немного вонючим. AddThreeNumbers следует заботиться только о добавлении трех чисел; установка этого результата на другом объекте должна быть обязанностью другого человека. Может быть, обертка должна сделать это:

@functools.wraps(A.AddThreeNumbers)
def add_three_numbers_wrapper(self, *args, **kwargs):
    self.result = A.AddThreeNumbers(*args, **kwargs)
    return self.result

SomeClass.AddThreeNumbers = add_three_numbers_wrapper
0 голосов
/ 11 апреля 2020

Спасибо @ jadkik94 за указатель на пост в блоге, который ссылается на merge_args и makefun , оба сторонних пакета, которые, похоже, способны это сделать.

Вот пример, который работает (только Python 3+):

from merge_args import merge_args
@merge_args(A.AddThreeNumbers)
def AddThreeNumbers( self, *pargs, **kwargs ):
    kwargs[ 'parent' ] = self
    return A.AddThreeNumbers( *pargs, **kwargs )
SomeClass.AddThreeNumbers = AddThreeNumbers

Вывод help() выглядит идеально. Так что вопрос лишь в том, стоит ли добавлять зависимость от merge_args (или скопировать 100+ строк «Advanced Hackery», которые она содержит).

Обновление: позже я обнаружил, что в Python 3 (или не менее 3,7), functools.wraps достигает почти то же самое:

import functools
@functools.wraps(A.AddThreeNumbers)
def AddThreeNumbers( self, *pargs, **kwargs ):
    kwargs[ 'parent' ] = self
    return A.AddThreeNumbers( *pargs, **kwargs )
SomeClass.AddThreeNumbers = AddThreeNumbers

... за исключением того, что self отсутствует в сигнатуре при выводе help(B.SomeClass.AddThreeNumbers) и первый аргумент проглатывается (поскольку предполагается, что он играет роль self) в выводе help(B.SomeClass().AddThreeNumbers).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...