Использование классов / статических методов в качестве значений параметров по умолчанию в методах того же класса - PullRequest
5 голосов
/ 21 июня 2010

Я хотел бы сделать что-то вроде этого:

class SillyWalk(object):
    @staticmethod
    def is_silly_enough(walk):
        return (False, "It's never silly enough")
    def walk(self, appraisal_method=is_silly_enough):
        self.do_stuff()
        (was_good_enough, reason) = appraisal_method(self)
        if not was_good_enough:
            self.execute_self_modifying_code(reason)
        return appraisal_method
    def do_stuff(self):
        pass
    def execute_self_modifying_code(self, problem):
        from __future__ import deepjuju
        deepjuju.kiss_booboo_better(self, problem)

с идеей, что кто-то может сделать

>>> silly_walk = SillyWalk()
>>> appraise = walk()
>>> is_good_walk = appraise(silly_walk)

, а также получить некоторое волшебное машинное обучение;этот последний бит не представляет особого интереса для меня, это было только первое, что пришло мне на ум в качестве примера использования статического метода как в функциональном контексте, так и с точки зрения вызывающего.

В любом случае, это не работает, потому что is_silly_enough на самом деле не является функцией: это объект, чей метод __get__ вернет исходную функцию is_silly_enough.Это означает, что он работает только «нормальным» образом, когда на него ссылаются как на атрибут объекта.Рассматриваемый объект создается функцией staticmethod(), которую декоратор помещает между атрибутом SillyWalk is_silly_enough и функцией, которая изначально определена с этим именем.

Это означает, что для использованиязначение по умолчанию appraisal_method из либо SillyWalk.walk или вызывающего абонента, мы должны либо

  • вызвать appraisal_method.__get__(instance, owner)(...), чем просто вызватьappraisal_method(...)
  • или назначьте его в качестве атрибута какого-либо объекта, затем обратитесь к этому свойству объекта как к методу, который мы вызываем, как мы бы назвали appraisal_method.

Учитывая, что ниЭти решения кажутся особенно Pythonic ™, мне интересно, возможно, есть лучший способ получить такую ​​функциональность.По сути, я хочу, чтобы был способ указать, что метод должен по умолчанию использовать определенный класс или статический метод, определенный в области действия того же класса, для выполнения некоторой части его повседневной рутины.

Я бы предпочел не использовать None, потому что я бы хотел, чтобы None передавал сообщение о том, что эта конкретная функция не должна вызываться.Я думаю, что я мог бы использовать какое-то другое значение, например False или NotImplemented, но это выглядит так: а) хакерство б) раздражение от необходимости писать лишнюю пару строк кода, а также избыточную документацию для чего-то, чтоПохоже, это можно выразить довольно кратко в качестве параметра по умолчанию.

Какой лучший способ сделать это?

Ответы [ 3 ]

2 голосов
/ 21 июня 2010

В итоге я написал (не) функцию-оболочку, которая будет использоваться в заголовках определения функции, например,

def walk(self, appraisal_method=unstaticmethod(is_silly_enough)):

На самом деле, похоже, это работает, по крайней мере, из-за этого мои тесты проходят без него.

Вот оно:

def unstaticmethod(static):
    """Retrieve the original function from a `staticmethod` object.

    This is intended for use in binding class method default values
      to static methods of the same class.

    For example:
        >>> class C(object):
        ...     @staticmethod
        ...     def s(*args, **kwargs):
        ...         return (args, kwargs)
        ...     def m(self, args=[], kwargs={}, f=unstaticmethod(s)):
        ...         return f(*args, **kwargs)
        >>> o = C()
        >>> o.s(1, 2, 3)
        ((1, 2, 3), {})
        >>> o.m((1, 2, 3))
        ((1, 2, 3), {})
    """
    # TODO: Technically we should be passing the actual class of the owner
    #         instead of `object`, but
    #         I don't know if there's a way to get that info dynamically,
    #         since the class is not actually declared
    #         when this function is called during class method definition.
    #       I need to figure out if passing `object` instead
    #         is going to be an issue.
    return static.__get__(None, object)

Обновление:

Я написал документы для самой функции unstaticmethod; они тоже проходят. Я все еще не совсем уверен, что это действительно умная вещь, но, похоже, она работает.

2 голосов
/ 21 июня 2010

Может быть, все, что вам нужно, это использовать функцию (а не метод), во-первых?

class SillyWalk(object):
    def is_silly_enough(walk):
        return (False, "It's never silly enough")

    def walk(self, appraisal_function=is_silly_enough):
        self.do_stuff()
        (was_good_enough, reason) = appraisal_function(self)
        if not was_good_enough:
            self.execute_self_modifying_code(reason)
        return appraisal_function
    def do_stuff(self):
        pass
    def execute_self_modifying_code(self, problem):
        deepjuju.kiss_booboo_better(self, problem)

Обратите внимание, что по умолчанию для appraisal_function теперь будет функция, а не метод, хотяis_silly_enough будет привязан как метод класса после создания класса (в конце кода).

Это означает, что

>>> SillyWalk.is_silly_enough
<unbound method SillyWalk.is_silly_enough>

, но

>>> SillyWalk.walk.im_func.func_defaults[0] # the default argument to .walk
<function is_silly_enough at 0x0000000002212048>

И вы можете вызвать is_silly_enough с аргументом ходьбы или вызвать экземпляр прогулки с помощью .is_silly_enough ().

Если вы действительно хотите, чтобы is_silly_enough был статическим методом, вы всегда можете добавить

    is_silly_enough = staticmethod(is_silly_enough)

где-нибудь после определения ходьбы.

1 голос
/ 21 июня 2010

Не уверен, получу ли я именно то, что вам нужно, но будет ли чище использовать getattr?

>>> class SillyWalk(object):
    @staticmethod
    def ise(walk):
        return (False, "boo")
    def walk(self, am="ise"):
        wge, r = getattr(self, am)(self)
        print wge, r


>>> sw = SillyWalk()
>>> sw.walk("ise")
False boo
...