关于python:使membermethod成为默认参数

Make a membermethod a default parameter

我想做的是:

1
2
3
4
5
6
class demo(object):
    def a(self):
        pass

    def b(self, param=self.a):  #I tried demo.a as well after making a static
        param()

很明显,问题是不能访问函数声明行中的类。有没有办法像C(++)那样添加原型?

现在我用一个难看的工具:

1
2
3
4
5
6
7
8
def b(self, param=True): #my real function shall be able to use None, to skip the function call
    if param == True:
        param = self.a

    if param != None: #This explainds why I can't take None as default,
                      #for param, I jsut needed something as default which was
                      #neither none or a callable function (don't want to force the user to create dummy lambdas)
        param()

那么,如果没有这个难看的工作安排,是否有可能达到上面描述的效果呢?注:我绑定到Jython,它大约是python 2.5(我知道有2.7,但我不能升级)


别告诉任何人我给你看了这个。

1
2
3
4
5
6
class demo:
    def a(self): print(self,"called 'a'")
    def b(self, param): param(self)
demo.b.__defaults__ = (demo.a,)

demo().b()

(在2.x中,__defaults__的拼写为func_defaults


简短回答:不。

我认为最好的方法是创建一个自定义的占位符对象,例如,如果你想传递像NoneTrue等对象:

1
2
3
4
5
6
7
8
9
10
11
default_value = object()

class demo(object):
    def a(self):
        pass

    def b(self, param=default_value):
        if param is default_value:
            self.a()
        else:
            param()

您可以使用funciton a作为b的默认值,如下所示:

1
    def b(self, param=a):

只要在b之前定义了a就可以了。但是函数a与绑定方法self.a不同,所以在调用它之前需要对它进行绑定,并且需要某种方法来区分传递的可调用方法和默认方法a之间的区别,以便绑定后者而不是前者。这显然比我建议的相对较短和可读的代码要麻烦得多。


我将再次回答这个问题,与我先前的回答相矛盾:

简短回答:是的!(有点)

在方法修饰器的帮助下,这是可能的。代码很长,有点难看,但是用法又短又简单。

问题是我们只能使用未绑定的方法作为默认参数。那么,如果我们创建一个包装函数——一个装饰器——它在调用真正的函数之前绑定参数呢?

首先,我们创建一个可以执行此任务的助手类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from inspect import getcallargs
from types import MethodType
from functools import wraps

class MethodBinder(object):
    def __init__(self, function):
        self.function = function

    def set_defaults(self, args, kwargs):
        kwargs = getcallargs(self.function, *args, **kwargs)
        # This is the self of the method we wish to call
        method_self = kwargs["self"]

        # First we build a list of the functions that are bound to self
        targets = set()
        for attr_name in dir(method_self):
            attr = getattr(method_self, attr_name)
            # For older python versions, replace __func__ with im_func
            if hasattr(attr,"__func__"):
                targets.add(attr.__func__)

        # Now we check whether any of the arguments are identical to the
        # functions we found above. If so, we bind them to self.
        ret = {}
        for kw, val in kwargs.items():
            if val in targets:
                ret[kw] = MethodType(val, method_self)
            else:
                ret[kw] = val

        return ret

因此,MethodBinder的实例与一个方法(或者更确切地说是一个将成为方法的函数)相关联。可以给MethodBinder的方法set_defaults提供用于调用关联方法的参数,它将绑定关联方法的self的任何未绑定方法,并返回可用于调用关联方法的kwargs dict。

现在我们可以使用这个类创建一个装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def bind_args(f):
    # f will be b in the below example
    binder = MethodBinder(f)

    @wraps(f)
    def wrapper(*args, **kwargs):
        # The wrapper function will get called instead of b, so args and kwargs
        # contains b's arguments. Let's bind any unbound function arguments:
        kwargs = binder.set_defaults(args, kwargs)

        # All arguments have been turned into keyword arguments. Now we
        # may call the real method with the modified arguments and return
        # the result.
        return f(**kwargs)
    return wrapper

既然我们已经把丑陋抛在脑后,让我们来展示一下简单而漂亮的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class demo(object):
    def a(self):
        print("{0}.a called!".format(self))

    @bind_args
    def b(self, param=a):
        param()

def other():
    print("other called")

demo().b()
demo().b(other)

这个配方使用了一个相当新的python,来自inspectgetcallargs。它只在python2.7和3.1的更新版本中可用。


我也更喜欢Lazyr的答案(我通常使用None作为默认参数),但您也可以使用关键字参数来更明确地说明这一点:

1
2
3
def b(self, **kwargs):
    param = kwargs.get('param', self.a)
    if param: param()

您仍然可以使用None作为参数,导致param未被执行。但是,如果不包含关键字参数param=,它将默认为a()

1
2
3
4
5
demo.b() #demo.a() executed

demo.b(param=some_func) #some_func() executed

demo.b(param=None) #nothing executed.


我喜欢Lazyr的回答,但也许你会更喜欢这个解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Demo(object):
    def a(self):
        pass

    def b(self, *args):
        if not args:
            param=self.a
        elif len(args)>1:
            raise TypeError("b() takes at most 1 positional argument")
        else:
            param=args[0]
        if param is not None:
            param()


您可以将方法名放入函数定义中:

1
2
3
4
5
6
7
8
class Demo(object):

    def a(self):
        print 'a'

    def b(self, param='a'):
        if param:
            getattr(self, param)()

但是,您仍然需要检查param的值是否为None。注意,这种方法不应该用于不受信任的输入,因为它允许执行该类的任何函数。