Как я могу использовать functools.partial для нескольких методов объекта и фиксировать параметры не по порядку? - PullRequest
2 голосов
/ 15 марта 2010

Я считаю functools.partial чрезвычайно полезным, но я хотел бы иметь возможность замораживать аргументы не по порядку (аргумент, который вы хотите заблокировать, не всегда первый), и я хотел бы иметь возможность применить это позволяет нескольким методам класса одновременно создавать прокси-объект, который имеет те же методы, что и базовый объект, за исключением того, что некоторые параметры его методов заморожены (представьте, что это обобщающее частичное для применения к классам). И я бы предпочел сделать это без редактирования исходного объекта, так же как частичное не меняет свою первоначальную функцию.

Мне удалось собрать версию functools.partial под названием 'bind', которая позволяет мне указывать параметры не по порядку, передавая их по аргументу ключевого слова. Эта часть работает:

>>> def foo(x, y):
...     print x, y
...
>>> bar = bind(foo, y=3)
>>> bar(2)
2 3

Но мой прокси-класс не работает, и я не уверен, почему:

>>> class Foo(object):
...     def bar(self, x, y):
...             print x, y
...
>>> a = Foo()
>>> b = PureProxy(a, bar=bind(Foo.bar, y=3))
>>> b.bar(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bar() takes exactly 3 arguments (2 given)

Я, вероятно, делаю все это неправильно, потому что я просто использую то, что собрал воедино из случайной документации, блогов и запуска dir () для всех частей. Буду признателен за предложения как о том, как сделать это, так и о лучших способах его реализации;) Я не уверен в одной детали, как все это должно взаимодействовать с дескрипторами. Код следует.

from types import MethodType

class PureProxy(object):
    def __init__(self, underlying, **substitutions):
        self.underlying = underlying

        for name in substitutions:
            subst_attr = substitutions[name]
            if hasattr(subst_attr, "underlying"):
                setattr(self, name, MethodType(subst_attr, self, PureProxy))

    def __getattribute__(self, name):
        return getattr(object.__getattribute__(self, "underlying"), name)

def bind(f, *args, **kwargs):
    """ Lets you freeze arguments of a function be certain values. Unlike
    functools.partial, you can freeze arguments by name, which has the bonus
    of letting you freeze them out of order. args will be treated just like
    partial, but kwargs will properly take into account if you are specifying
    a regular argument by name. """
    argspec = inspect.getargspec(f)
    argdict = copy(kwargs)

    if hasattr(f, "im_func"):
        f = f.im_func

    args_idx = 0
    for arg in argspec.args:
        if args_idx >= len(args):
            break

        argdict[arg] = args[args_idx]
        args_idx += 1

    num_plugged = args_idx

    def new_func(*inner_args, **inner_kwargs):
        args_idx = 0
        for arg in argspec.args[num_plugged:]:
            if arg in argdict:
                continue
            if args_idx >= len(inner_args):
                # We can't raise an error here because some remaining arguments
                # may have been passed in by keyword.
                break
            argdict[arg] = inner_args[args_idx]
            args_idx += 1

        f(**dict(argdict, **inner_kwargs))

    new_func.underlying = f

    return new_func

Обновление: в случае, если кто-то может извлечь выгоду, вот заключительная реализация, с которой я пошел:

from types import MethodType

class PureProxy(object):
    """ Intended usage:
    >>> class Foo(object):
    ...     def bar(self, x, y):
    ...             print x, y
    ...
    >>> a = Foo()
    >>> b = PureProxy(a, bar=FreezeArgs(y=3))
    >>> b.bar(1)
    1 3
    """

    def __init__(self, underlying, **substitutions):
        self.underlying = underlying

        for name in substitutions:
            subst_attr = substitutions[name]
            if isinstance(subst_attr, FreezeArgs):
                underlying_func = getattr(underlying, name)
                new_method_func = bind(underlying_func, *subst_attr.args, **subst_attr.kwargs)
                setattr(self, name, MethodType(new_method_func, self, PureProxy))

    def __getattr__(self, name):
        return getattr(self.underlying, name)

class FreezeArgs(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs

def bind(f, *args, **kwargs):
    """ Lets you freeze arguments of a function be certain values. Unlike
    functools.partial, you can freeze arguments by name, which has the bonus
    of letting you freeze them out of order. args will be treated just like
    partial, but kwargs will properly take into account if you are specifying
    a regular argument by name. """
    argspec = inspect.getargspec(f)
    argdict = copy(kwargs)

    if hasattr(f, "im_func"):
        f = f.im_func

    args_idx = 0
    for arg in argspec.args:
        if args_idx >= len(args):
            break

        argdict[arg] = args[args_idx]
        args_idx += 1

    num_plugged = args_idx

    def new_func(*inner_args, **inner_kwargs):
        args_idx = 0
        for arg in argspec.args[num_plugged:]:
            if arg in argdict:
                continue
            if args_idx >= len(inner_args):
                # We can't raise an error here because some remaining arguments
                # may have been passed in by keyword.
                break
            argdict[arg] = inner_args[args_idx]
            args_idx += 1

        f(**dict(argdict, **inner_kwargs))

    return new_func

1 Ответ

3 голосов
/ 15 марта 2010

Вы слишком сильно привязываетесь: измените def __getattribute__(self, name): на def __getattr__(self, name): в классе PureProxy. __getattribute__ перехватывает каждый доступ к атрибуту и, таким образом, обходит все, что вы установили с помощью setattr(self, name, ..., что делает эти setattr лишенными любого эффекта, что, очевидно, не то, что вы хотите; __getattr__ вызывается только для доступа к атрибутам , не определенным иным образом , поэтому эти setattr вызовы становятся "оперативными" и полезными.

В теле этого переопределения вы можете и должны также изменить object.__getattribute__(self, "underlying") на self.underlying (поскольку вы больше не переопределяете __getattribute__). Я бы предложил другие изменения (enumerate вместо низкоуровневой логики, которую вы используете для счетчиков и т. Д.), Но они не изменили бы семантику.

С предложенным изменением ваш пример кода работает (вам, конечно, придется продолжать тестирование с более тонкими случаями). Кстати, способ, которым я это отлаживал, заключался в том, чтобы просто вставлять операторы print в соответствующих местах (подход юры = эпохи, но все же мой любимый; -).

...