在同类方法中使用类/静态方法作为默认参数值

5 投票
3 回答
1556 浏览
提问于 2025-04-16 00:14

我想做类似这样的事情:

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() 函数创建的,装饰器把它放在了 SillyWalkis_silly_enough 属性和最初定义的那个函数之间。

这意味着,为了在 SillyWalk.walk 或者它的调用者中使用 appraisal_method 的默认值,我们必须:

  • 调用 appraisal_method.__get__(instance, owner)(...),而不是直接调用 appraisal_method(...)
  • 或者把它作为某个对象的属性,然后像调用 appraisal_method 一样调用这个对象的属性。

考虑到这两种解决方案似乎都不是特别符合 Python 的风格,我在想是否有更好的方法来实现这种功能。我基本上想要一种方法,默认情况下可以指定一个方法使用同一类中定义的特定类或静态方法来执行它日常工作的一部分。

我不太想用 None,因为我希望 None 传达一个信息:这个特定的函数不应该被调用。我想我可以用其他值,比如 FalseNotImplemented,但这似乎 a) 有点 hackety b) 让人烦恼,因为还得多写几行代码,以及一些冗余的文档,而这些似乎可以通过一个默认参数简洁地表达。

那么,最好的方法是什么呢?

3 个回答

1

我不太确定我是否完全理解你的意思,但用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
2

我最终写了一个(解)封装函数,可以在函数定义的开头使用,比如:

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

也许你一开始只需要使用函数,而不是方法?

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>

你可以用一个walk参数来调用is_silly_enough,或者用.walk实例来调用.is_silly_enough()。

如果你真的想让is_silly_enough成为一个静态方法,你可以在walk定义之后的任何地方添加

    is_silly_enough = staticmethod(is_silly_enough)

撰写回答