我想使用Python3 partialmethod 而我无法调用结果函数。我的代码基于Python文档https://docs.python.org/release/3.6.4/library/functools.html
中的示例from functools import partialmethod
class AClass():
def __init__(self):
self.v = val
def _fun(self, x):
z = x + self.v # some computation
return z
def fun(self, x):
return partialmethod(self._fun, x)
a = AClass(10)
b = a.fun(1)
print(b()) # TypeError: 'partialmethod' object is not callable
print(a.b()) # AttributeError: 'AClass' object has no attribute 'b'
我理解为什么a.b()
不正确,因为b未在实例化的a中定义。 b()
的错误消息没有提供足够的信息来了解正在发生的事情。
如何定义带有绑定参数的方法并从封闭类外部调用它?这有可能,还是有更好的方法来实现这个结果?
答案 0 :(得分:0)
我偶然发现了同一问题,并找到了您的问题。我更仔细地阅读了partialmethod
,我想起了描述符。也就是说,记得我真的不记得描述符。因此,在进一步研究之后,这是我对正在发生的事情的理解:
Python函数和方法与python中的其他任何对象一样,都是对象。它们是可调用的,因为它们实现了可调用协议,即,它们实现了特殊方法__call__()
。您可以通过这种方式将任何东西设为可调用项:
class Igors:
pass
igor = Igors()
try:
igor() # --> raises TypeError
except TypeError:
print("Is it deaf?")
Igors.__call__ = lambda self: print("Did you call, Marster?")
igor() # --> prints successfully
打印:
Is it deaf?
Did you call, Marster?
(请注意,您不能仅在类Special method lookup上为实例分配特殊方法)
您通常会这样做,而不是:
class A:
def __call__(self):
print("An A was called")
a = A()
a() # prints "An A was called"
一些有用的链接,但还有许多其他链接:
Python描述符是实现__get__
,__set__
或__delete__
方法之一的对象。它们简化了默认属性查找机制。
如果对象具有“普通”属性x
,则在访问obj.x
时,python会在通常的可疑对象中查找x
的值:实例的__dict__
,实例的类__dict__
,然后在其基类中返回它。
如果另一方面,对象具有作为描述符的属性,则在查找对象后,python将使用两个参数调用实例的__get__()
:实例和实例的类型(类)。
注意:讨论更加复杂。有关__set__
和__delete__
以及数据和“非数据”描述符以及查找它们的顺序的更多详细信息,请参见链接的“描述符方法指南”。
这是另一个愚蠢的例子:
class Quack:
DEFAULT = "Quack! Quack!"
def __get__(self, obj, obj_type=None):
print(f">>> Quacks like {obj} of type {obj_type} <<<")
try:
return obj.QUACK
except AttributeError:
return Quack.DEFAULT
class Look:
def __get__(self, obj, obj_type):
print(f">>> Looks like {obj} <<<")
return lambda: "a duck!"
class Duck:
quack = Quack()
look = Look()
class Scaup(Duck):
"""I'm a kind of a duck"""
QUACK = "Scaup! Scaup!"
# looking up on the class
print(f"All ducks quack: {Duck.quack}\n")
# looking up on an object
a_duck = Duck()
print(f"A duck quacks like {a_duck.quack}\n")
a_scaup = Scaup()
print(f"A scaup quacks like {a_scaup.quack}\n")
# descriptor returns a callable
print(f"A duck look like {a_duck.look} ... ooops\n")
print(f"Again, a duck look() like {a_duck.look()}\n")
哪些印刷品:
>>> Quacks like None of type <class '__main__.Duck'> <<<
All ducks quack: Quack! Quack!
>>> Quacks like <__main__.Duck object at 0x103d5bd50> of type <class '__main__.Duck'> <<<
A duck quacks like Quack! Quack!
>>> Quacks like <__main__.Scaup object at 0x103d5bc90> of type <class '__main__.Scaup'> <<<
A scaup quacks like Scaup! Scaup!
>>> Looks like <__main__.Duck object at 0x103d5bd50> <<<
A duck look like <function Look.__get__.<locals>.<lambda> at 0x103d52dd0> ... ooops
>>> Looks like <__main__.Duck object at 0x103d5bd50> <<<
Again, a duck look() like a duck!
您需要记住的是,当python查找属性以进行__get__()
查找时,调用描述符的特殊方法(在这种情况下为obj.attribute
)的妙处
运行a_duck.look()
python时(好,object.__getattribute__()
机制)或多或少像往常一样查找“ look”,获取作为描述符的值(class Look
实例) ,神奇地称为__get__()
partialmethod()返回一个不是可调用的描述符。相反,其__get__()
方法将返回可调用对象,在这种情况下为适当的functools.partial()
对象。类似于方法,部分方法或静态方法,部分方法应该是对象的属性。
这里有一些使用偏方法的方法。注意,它的行为是不同的,具体取决于您是在描述符(如方法,类方法等)还是非描述符可调用对象上调用它。从其文档中:
当func是描述符(例如普通的Python函数,classmethod(),staticmethod(),abstractmethod()或partialmethod的另一个实例)时,对
__get__
的调用将委派给基础描述符,并且返回适当的部分对象作为结果。当func是可调用的非描述符时,将动态创建适当的绑定方法。当用作方法时,其行为类似于普通的Python函数:self参数将作为第一个位置参数插入,甚至在提供给partialmethod构造函数的args和关键字之前。
from functools import partialmethod
class Counter:
def __init__(self, initial):
self._value = 0
def __str__(self):
return str(self._value)
def increase(self, by):
self._value += by
# on descriptor (a method is a descriptor too, that is doing the "self" magic)
increment = partialmethod(increase, 1)
# on non-descriptor
name = lambda self: f"Counter of {self}"
increment2 = partialmethod(name)
# partialmethod generator
def increment_returner(self, by):
return partialmethod(Counter.increase, by)
# partialmethod used as intended on methods:
c = Counter(0)
c.increment()
print(f"incremented counter: {c}") # --> 1
print(f"c.increment: {c.increment}") # --> functools.partial(<bound method Counter.increase of <__main__.Counter object at 0x108fa0610>>, 1)
print(f"c.increment has __call__: {hasattr(c.increment, '__call__')}") # --> True
print()
# partialmethod used (as intended?), on non-descriptor callables
print(f"c.name() returns: {c.name()}") # --> "Counter of 1"
print(f"c.name is: {c.name}") # --> <bound method Counter.<lambda> of <__main__.Counter object at 0x10208dc10>>
print()
# a "partialmethod" generator
incrementer = c.increment_returner(2)
print(f"icrementer: {incrementer}") # --> functools.partialmethod(<bound method Counter.increase of <__main__.Counter object at 0x104e74790>>, 2, )
print(f"incrementer has __call__: {hasattr(incrementer, '__call__')}") # --> False
print(f"incrementer has __get__: {hasattr(incrementer, '__get__')}") # --> True
incrementer.__get__(c, Counter)()
print(f"counter after 'simulating' python's magic: {c}") # --> 3
print(f"'simulated' invocation of attribute lookup: {incrementer.__get__(c, Counter)}") # --> functools.partial(<bound method Counter.increase of <__main__.Counter object at 0x10d7b7c50>>, 2)
输出:
incremented counter: 1
c.increment: functools.partial(<bound method Counter.increase of <__main__.Counter object at 0x101fffb10>>, 1)
c.increment has __call__: True
c.name() returns: Counter of 1
c.name is: <bound method Counter.<lambda> of <__main__.Counter object at 0x101fffb10>>
icrementer: functools.partialmethod(<function Counter.increase at 0x102008050>, 2, )
incrementer has __call__: False
incrementer has __get__: True
counter after 'simulating' python's magic: 3
'simulated' invocation of attribute lookup: functools.partial(<bound method Counter.increase of <__main__.Counter object at 0x101fffb10>>, 2)
在您的示例中,b()
不起作用,因为:
partialmethod
返回一个描述符,该描述符的__get__()
返回一个经过适当设计的可调用对象,该部分对象的工作方式类似于绑定方法(“注入”自身)。b.__get__(a, AClass)()
也会失败,因为self._fun
已经绑定到self
,所以您得到TypeError: _fun() takes 2 positional arguments but 3 were given
。如果我没记错的话,self
被注入两次。据我所知,您希望能够生成带有绑定参数的方法。我猜你可以做类似的事情:
from functools import partial, partialmethod
class AClass():
def __init__(self, val):
self.v = val
def _fun(self, x):
z = x + self.v # some computation
return z
def fun1(self, x):
def bound_fun_caller():
return self._fun(x)
return bound_fun_caller
def fun2(self, x):
# quite silly, but here it is
return partialmethod(AClass._fun, x).__get__(self, AClass)
def fun3(self, x):
return partial(AClass._fun, self, x)
# for completeness, binding to a known value
plus_four = partialmethod(_fun, 4)
def add_fun(self, name, x):
# Careful, this might hurt a lot...
setattr(AClass, name, partialmethod(AClass._fun, x))
a = AClass(10)
b1 = a.fun1(1)
print(b1())
b2 = a.fun2(2)
print(b2())
b3 = a.fun3(3)
print(b3())
print(a.plus_four())
a.add_fun("b5", 5)
print(a.b5())