考虑以下(破碎)代码:
import functools
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partial(f,1)
x=Foo()
x.g(2)
我想要做的是使用函数f并部分应用它,从而产生函数g(self,b)
。我想使用此函数作为方法,但这当前不起作用,而是我得到错误
Traceback (most recent call last):
File "test.py", line 8, in <module>
x.g(2)
TypeError: f() takes exactly 3 arguments (2 given)
然而,x.g(x,2)
可行,所以似乎问题是g被认为是“正常”函数而不是类的方法。有没有办法让x.g表现得像一个方法(即隐式传递self参数)而不是函数?
答案 0 :(得分:22)
这里有两个问题。首先,对于要转换为方法的函数,它必须存储在类中,而不是实例上。演示:
class Foo(object):
def a(*args):
print 'a', args
def b(*args):
print 'b', args
Foo.b = b
x = Foo()
def c(*args):
print 'c', args
x.c = c
因此a
是类定义中定义的函数,b
是之后分配给类的函数,c
是分配给实例的函数。看看我们打电话给他们时会发生什么:
>>> x.a('a will have "self"')
a (<__main__.Foo object at 0x100425ed0>, 'a will have "self"')
>>> x.b('as will b')
b (<__main__.Foo object at 0x100425ed0>, 'as will b')
>>> x.c('c will only recieve this string')
c ('c will only recieve this string',)
正如您所看到的,与类一起定义的函数与稍后分配给它的函数之间几乎没有区别。我相信只要没有涉及元类,实际上没有区别,但那是另一次。
第二个问题来自于函数实际上是如何实际转变为方法的;函数类型实现描述符协议。 (See the docs for details.)简而言之,函数类型有一个特殊的__get__
方法,当您对类本身执行属性查找时会调用该方法。而不是获取函数对象,而是调用该函数对象的__get__
方法,并返回绑定的方法对象(提供self
参数的对象)。
为什么这是一个问题?因为functools.partial
对象不是描述符!
>>> import functools
>>> def f(*args):
... print 'f', args
...
>>> g = functools.partial(f, 1, 2, 3)
>>> g
<functools.partial object at 0x10042f2b8>
>>> g.__get__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'functools.partial' object has no attribute '__get__'
此时您有很多选择。您可以明确地将self参数提供给partial:
import functools
class Foo(object):
def __init__(self):
def f(self, a, b):
print a + b
self.g = functools.partial(f, self, 1)
x = Foo()
x.g(2)
...或者你会在闭包中嵌入a的自我和价值:
class Foo(object):
def __init__(self):
a = 1
def f(b):
print a + b
self.g = f
x = Foo()
x.g(2)
这些解决方案当然假设在构造函数中有一个尚未指定的方法将类分配给类,因为您可以很容易地直接在类上定义一个方法来执行您在此处执行的操作
编辑:这是一个解决方案的想法,假设可以为类而不是实例创建函数:
class Foo(object):
pass
def make_binding(name):
def f(self, *args):
print 'Do %s with %s given %r.' % (name, self, args)
return f
for name in 'foo', 'bar', 'baz':
setattr(Foo, name, make_binding(name))
f = Foo()
f.foo(1, 2, 3)
f.bar('some input')
f.baz()
给你:
Do foo with <__main__.Foo object at 0x10053e3d0> given (1, 2, 3).
Do bar with <__main__.Foo object at 0x10053e3d0> given ('some input',).
Do baz with <__main__.Foo object at 0x10053e3d0> given ().
答案 1 :(得分:9)
这会奏效。但我不确定这是不是你想要的
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partial(f,1, self) # <= passing `self` also.
x = Foo()
x.g(2)
答案 2 :(得分:6)
这只是我认为最正确的(也就是pythonic :)解决方法的一个具体例子 - 因为最好的解决方案(类上的定义!)从未被揭示 - @MikeBoers解释其他方面是固定的
我已经使用了这种模式(最近用于代理API),并且它在没有丝毫不规则的情况下在无数生产时间内存活下来。
from functools import update_wrapper
from functools import partial
from types import MethodType
class Basic(object):
def add(self, **kwds):
print sum(kwds.values())
Basic.add_to_one = MethodType(
update_wrapper(partial(Basic.add, a=1), Basic.add),
None,
Basic,
)
x = Basic()
x.add(a=1, b=9)
x.add_to_one(b=9)
...产率:
10
10
...这里的关键主要点是MethodType(func, inst, cls)
,它从另一个可调用的方法创建一个未绑定的方法(你甚至可以使用它来将实例方法链接/绑定到不相关的类......当实例化时+调用原始实例方法将收到 BOTH self
个对象!)
注意关键字参数的独占使用!虽然可能有更好的方法来处理,但args通常是PITA,因为自我的位置变得不那么可预测。另外,IME无论如何,使用*args
和最底层函数中的**kwds
在以后证明非常有用。
答案 3 :(得分:4)
functools.partialmethod()可用于python 3.4以实现此目的。
import functools
class Foo(object):
def __init__(self):
def f(a,self,b):
print a+b
self.g = functools.partialmethod(f,1)
x=Foo()
x.g(2)