【问题标题】:Unable to call function defined by partialmethod无法调用部分方法定义的函数
【发布时间】:2018-04-05 01:09:18
【问题描述】:

我想使用 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() 的错误消息没有给我足够的信息来理解发生了什么。

如何定义带有绑定参数的方法并从封闭类外部调用它?这有可能吗,还是有更好的方法来实现这个结果?

【问题讨论】:

  • @Wade,你有想过这个吗?

标签: python-3.x evaluation class-method


【解决方案1】:

我偶然发现了同样的问题,并找到了您的问题。更仔细地阅读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!

您需要记住的是,调用描述符的特殊方法(在本例中为 __get__())的魔力发生在 python 查找 属性 以进行 obj.attribute 查找时。

当运行a_duck.look() python(好吧,object.__getattribute__() 机制)时或多或少像往常一样查找“look”,获得作为描述符的值(class Look 实例),神奇地称之为@987654348 @

部分方法

partialmethod() 返回一个不是可调用的描述符。相反,它的__get__() 方法将返回可调用对象,在本例中是一个适当的functools.partial() 对象。与方法、类方法或静态方法类似,partialmethod 应该是对象的一个​​属性

这里有一些使用部分方法的方法。请注意,它的行为是不同的,具体取决于您是在描述符(如方法、类方法等)还是非描述符可调用对象上调用它。从其文档中:

当 func 是一个描述符(例如普通的 Python 函数、classmethod()、staticmethod()、abstractmethod() 或 partialmethod 的另一个实例)时,对 __get__ 的调用被委托给底层描述符,以及一个适当的 partial对象作为结果返回。

当 func 是非描述符可调用时,会动态创建适当的绑定方法。当用作方法时,它的行为类似于普通的 Python 函数:self 参数将作为第一个位置参数插入,甚至在提供给 partialmethod 构造函数的参数和关键字之前。

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() 不起作用,因为:

  1. partialmethod 返回一个描述符,其 __get__() 将返回一个精心设计的可调用对象,一个像绑定方法一样工作的部分对象(“注入”自我)。
  2. 即使你调用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())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 1970-01-01
    • 2013-09-25
    相关资源
    最近更新 更多