functools.partial 和 partialmethod 的困惑

2024 年 10 月 6 日
 julyclyde

我在写一个 telegram bot ,自己寨了一个小的 lib

class Client:
    def callAPI(self, method="getMe", **kwargs):
       调用 requests.post(....)
       
    getMe = functools.partialmethod(callAPI, "getMe")

这样是成功的,可以用 getMe=functools.partialmethod 的方法定义一个名为 getMe 但实际上偏函数调用 callAPI 的方法

但是另外几种写法都不对,我不明白为什么: 第一个:

def __getattr__(self, APIname):
        return functools.partialmethod(callAPI, APIname)

会导致错误 NameError: name 'callAPI' is not defined 我不明白为什么 getMe 直接赋值的时候可以找到 callAPI 方法,但是在__getattr__函数里就找不到 callAPI 方法,必须用 self.callAPI 的方式来找。是不是因为前者在 class 内,而后者在 method 内的,scope 不同的缘故?

第二个:

def __getattr__(self, APIname):
        return functools.partialmethod(self.callAPI, APIname)

c=Client(token="....") c.getMe()会发生 TypeError: 'partialmethod' object is not callable

但是 c.getMe.func() 就可以正常执行

我不明白,为什么直接用 partialmethod 赋值出来那个函数就是 callable 的,但这里用__getattr__返回的却不是 callable 的呢

最后找到正确写法是:

    def __getattr__(self, APIname):
        return functools.partial(self.callAPI, APIname)

但是该用 partialmethod 的地方用了 partial ,总感觉不正经

8234 次点击
所在节点    Python
11 条回复
killerirving
2024 年 10 月 6 日
1. “是不是因为前者在 class 内,而后者在 method 内的,scope 不同的缘故” 是的
2. 这种情况应该使用 partial 。partialmethod 是声明为 class descriptor 使用的,被读取时会调用__get__();而在函数中直接调用的 method 是__call__(),partialmethod class 中并没有定义该方法所以会有 not callable 的报错
julyclyde
2024 年 10 月 6 日
@killerirving 求教,为什么之前的写法,在 class 直属层直接使用 getMe = functools.partialmethod(callAPI, "getMe")是可以的呢?
按说如果 partialmethod 返回的那个对象需要再 dot func 才能调用,那这种写法下的 getMe 到底是个啥东西??为什么不需要 dot func 就可以用?
killerirving
2024 年 10 月 7 日
c.getMe.func()其实是个错误使用,func 是 partialmethod class 的成员变量,也就是 self.callAPI 这个参数
```
class partial:
"""New function with partial application of the given arguments
and keywords.
"""

__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"

def __new__(cls, func, /, *args, **keywords):
if not callable(func):
raise TypeError("the first argument must be callable")

if hasattr(func, "func"):
args = func.args + args
keywords = {**func.keywords, **keywords}
func = func.func

self = super(partial, cls).__new__(cls)

self.func = func
self.args = args
self.keywords = keywords
return self
````
julyclyde
2024 年 10 月 7 日
@killerirving 我的疑问其实是:

我最开始的写法
```
class Client:
def callAPI(self, method="getMe", **kwargs):
调用 requests.post(....)

getMe = functools.partialmethod(callAPI, "getMe")
```
这里 getMe 是一个 partialmethod object ,是 callable 的

但是为什么我改成
```
def __getattr__(self, APIname):
return functools.partialmethod(callAPI, APIname)
```
然后__getattr__返回的对象就不是 callable 呢?

看起来这俩都是 partialmethod object 啊?
或者可能,前者( getMe 直接赋值 partialmethod )并不是一个 partialmethod object ??
keakon
2024 年 10 月 7 日
partial 实现很简单,它的 __call__() 方法将新老参数合并在一起调用原函数。
因此 c.getMe() -> c. __getattr__('getMe') -> functools.partial(self.callAPI, 'getMe') -> self.callAPI('getMe')

partialmethod 是一个没有定义 __call__() 方法的 descriptor ,而它的 __get__ 方法主要实现是调用 partial()。
因此 functools.partialmethod(self.callAPI, APIname) 返回的是一个不能调用的 partialmethod 对象。
而 getMe = functools.partialmethod(callAPI, "getMe") 是给 Client 类定义了一个叫 'getMe' 的 descriptor 。此时,c.getMe() -> functools.partialmethod(callAPI, "getMe").__get__(c, Client) -> Client.callAPI(c, "getMe")。
julyclyde
2024 年 10 月 7 日
@keakon 哦。因为我不懂什么叫 descriptor 所以看不懂你说的……
我滚回去复习文档去了
julyclyde
2024 年 10 月 7 日
@keakon 是不是可以理解为:
直接赋值那个,it doesn't HAVE TO be callable?
而__getattr__那个必须是 callable ,所以才暴露出来了 partialmethod 返回值不是 callable 这个问题?
keakon
2024 年 10 月 7 日
@julyclyde 你先看看 descriptor 的作用吧。简单来说,如果一个类( Client )的属性( getMe )是 descriptor ,那么在访问这个类的实例( c )的同名属性( getMe )时,访问的实际是这个 descriptor 的 __get__() 方法。
getMe = functools.partialmethod(callAPI, "getMe") 正是给类定义了一个 descriptor ,而它的 __get__() 方法里返回了一个 callable 。
而 return functools.partialmethod(self.callAPI, APIname) 这个实现虽然返了 descriptor ,但它不是类的属性,因此访问时并不会调用 __get__()。
julyclyde
2024 年 10 月 7 日
@keakon 谢谢
我回去重读一下这段

以前觉得这段文档不重要哈哈哈哈……现在发现了还是必须得掌握
NoOneNoBody
2024 年 10 月 10 日
尽量避免用 partialmethod 吧,刚出来时有人提过 issue ,当时没有确信解释,应是个 bug (结果不可预料),后续版本我就没关注,不知道修了没有

可以用取巧方法,如闭包+partial ,partial 还是稳定的
julyclyde
2024 年 10 月 11 日
@NoOneNoBody 为啥会不可预料呢?按说如果是用错了,应该稳定失败才对啊?

闭包听起来很邪教,外观看着像函数,但实际上是个有状态的对象,上次执行有可能对下次执行有影响,用起来不安心

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1077892

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX