functools.partial 和 partialmethod 的困惑 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
julyclyde
V2EX    Python

functools.partial 和 partialmethod 的困惑

  •  
  •   julyclyde
    julyclyde 2024-10-06 14:14:02 +08:00 6998 次点击
    这是一个创建于 368 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我在写一个 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 ,总感觉不正经

    11 条回复    2024-10-11 10:30:20 +08:00
    killerirving
        1
    killerirving  
       2024-10-06 16:43:45 +08:00   1
    1. “是不是因为前者在 class 内,而后者在 method 内的,scope 不同的缘故” 是的
    2. 这种情况应该使用 partial 。partialmethod 是声明为 class descriptor 使用的,被读取时会调用__get__();而在函数中直接调用的 method 是__call__(),partialmethod class 中并没有定义该方法所以会有 not callable 的报错
    julyclyde
        2
    julyclyde  
    OP
       2024-10-06 17:40:32 +08:00
    @killerirving 求教,为什么之前的写法,在 class 直属层直接使用 getMe = functools.partialmethod(callAP, "getMe")是可以的呢?
    按说如果 partialmethod 返回的那个对象需要再 dot func 才能调用,那这种写法下的 getMe 到底是个啥东西??为什么不需要 dot func 就可以用?
    killerirving
        3
    killerirving  
       2024-10-07 03:49:52 +08:00
    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
        4
    julyclyde  
    OP
       2024-10-07 12:04:53 +08:00
    @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
        5
    keakon  
       2024-10-07 12:31:57 +08:00
    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
        6
    julyclyde  
    OP
       2024-10-07 13:10:42 +08:00
    @keakon 哦。因为我不懂什么叫 descriptor 所以看不懂你说的……
    我滚回去复习文档去了
    julyclyde
        7
    julyclyde  
    OP
       2024-10-07 13:12:57 +08:00
    @keakon 是不是可以理解为:
    直接赋值那个,it doesn't HAVE TO be callable?
    而__getattr__那个必须是 callable ,所以才暴露出来了 partialmethod 返回值不是 callable 这个问题?
    keakon
        8
    keakon  
       2024-10-07 17:00:46 +08:00   1
    @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
        9
    julyclyde  
    OP
       2024-10-07 20:45:12 +08:00
    @keakon 谢谢
    我回去重读一下这段

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

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

    闭包听起来很邪教,外观看着像函数,但实际上是个有状态的对象,上次执行有可能对下次执行有影响,用起来不安心
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     938 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 18:57 PVG 02:57 LAX 11:57 JFK 14:57
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86