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 · 12 天前 · 1159 次点击

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

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

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

    闭包听起来很邪教,外观看着像函数,但实际上是个有状态的对象,上次执行有可能对下次执行有影响,用起来不安心
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2638 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 11:22 · PVG 19:22 · LAX 04:22 · JFK 07:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.