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
MiketsuSmasher
V2EX  ›  Python

Python 类方法的装饰器问题

  •  
  •   MiketsuSmasher · 2021-09-27 23:01:08 +08:00 · 3676 次点击
    这是一个创建于 1148 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class Account:
        def __init__(self, **kwargs):
            [...]
            self._is_valid = True
    
        def valid_before_logout(self, func):
            def execute(*args, **kwargs):
                if self._is_valid:
                    return func(*args, **kwargs)
                else:
                    raise AccountOperatingError('the account is already invalidated or signed out')
    
            return execute
    
        @valid_before_logout
        def refresh(self):
            [...]
    
        @valid_before_logout
        def invalidate(self):
            self._is_valid = False
            [...]
    
        @valid_before_logout
        def signout(self):
            self._is_valid = False
            [...]
    

    Account里面有一个方法valid_before_logout用作装饰器,作用是在执行任何类方法之前检查self._is_valid,如果为False就抛出异常。

    不过在导入这个模块的时候出错:

    >>> import account
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "account.py", line 1, in <module>
        class Account:
      File "account.py", line 15, in <module>
        def refresh(self):
    TypeError: valid_before_logout() missing 1 required positional argument: 'func'
    >>>
    

    请问这是什么原因?

    16 条回复    2021-10-13 15:31:01 +08:00
    casparchen
        1
    casparchen  
       2021-09-27 23:10:51 +08:00 via iPhone
    @self.valid_before_logout
    jaredyam
        2
    jaredyam  
       2021-09-27 23:13:48 +08:00
    Traceback 不是说的很明显么,你在类里面把装饰器作为正常装饰器用了,却还给了个 self,所以就还缺个 func 。

    要么 @self.decorator 要么 @staticmethod def decorator()。
    MiketsuSmasher
        3
    MiketsuSmasher  
    OP
       2021-09-27 23:17:10 +08:00
    @casparchen
    @jaredyam
    换成 @self.valid_before_logout 之后仍然出错:

    ```
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/home/menacing/Projects/mukago/ignored/account.py", line 5, in <module>
    class Account:
    File "/home/menacing/Projects/mukago/ignored/account.py", line 19, in Account
    @self.valid_before_logout
    NameError: name 'self' is not defined
    ```
    hsfzxjy
        4
    hsfzxjy  
       2021-09-27 23:17:30 +08:00
    ... def valid_before_logout(func):
    ... ... def execute(self, *args, **kwargs):
    rpman
        5
    rpman  
       2021-09-28 01:07:19 +08:00 via iPhone
    你装饰器的写法是错的
    要把装饰器写在类里的方法: https://medium.com/@vadimpushtaev/decorator-inside-python-class-1e74d23107f6

    当然我觉得写在类外也很合理
    weyou
        6
    weyou  
       2021-09-28 02:05:27 +08:00 via Android
    不确定,decorator 只能放在类外吧,可以这样做

    def valid_before_logout(func):
    … def wrapper(self, *args, **kwargs):
    … … if self._is_valid:
    … … … return func(self, *args, **kwargs)
    … … else:
    … … … raise AccountOperatingError('the account is already invalidated or signed out')
    … return wrapper

    class Account:
    … def __init__(self, **kwargs):
    … … [...]
    … … self._is_valid = True

    … @valid_before_logout
    … def refresh(self):
    … … [...]
    O5oz6z3
        7
    O5oz6z3  
       2021-09-28 06:26:55 +08:00
    原因是在类定义内使用 valid_before_logout 时,就是个普通函数,不是实例方法,所以会缺少 self 。就和 cls.method() 是一样的,会报错缺少 self 。一开始我也以为装饰成静态方法或类方法就解决了,没想到这种方法不兼容直接调用。
    想到一个简单的思路兼容两种情况,不过没有试验过:
    def valid_before_logout(self, func=None):
    ... func = func or self
    ... #...
    Varchar
        8
    Varchar  
       2021-09-28 08:44:58 +08:00
    def valid_before_logout(func):
    def execute(*args, **kwargs):
    if args[0]._is_valid:
    SjwNo1
        9
    SjwNo1  
       2021-09-28 09:09:20 +08:00
    你这是个带参装饰器,你需要填充 self
    Ritter
        10
    Ritter  
       2021-09-28 09:18:21 +08:00
    定义在类外面就简单多了
    2i2Re2PLMaDnghL
        11
    2i2Re2PLMaDnghL  
       2021-09-28 09:50:27 +08:00
    你对于函数调用模型含混不清。
    写成 C-like OOP 再看看

    @decorator def func 语法实质上是
    func=decorator(__temp_func)
    无论写在 class 里还是 class 外都一样。
    而另一方面,你的 decorator 返回的东西根本不接受 self
    按你的思路应该把这些东西全部放进 __init__ 里去
    Pzqqt
        12
    Pzqqt  
       2021-09-28 09:58:02 +08:00
    @weyou #6 @MiketsuSmasher 此乃正解,不过把 valid_before_logout 定义在 Account 类里边也是可以的,但不能加 staticmethod 装饰器(此时 valid_before_logout 既不是类方法也不是实例方法,它可以在外部通过类名直接调用,但不与类交互更不与实例交互,相当于类属性)。

    这样会带来一个新的问题:如果你要继承 Account 类并重写被 valid_before_logout 装饰过的方法,除非显式调用超类方法或着重新装饰该方法,不然装饰器会失效。举例:

    ....class B(Account):
    ........def signout(self):
    ............self._is_valid = False
    ....b = B()
    ....b._is_valid # True
    ....b.signout()
    ....b._is_valid # False
    ....b.signout() # 此时应该触发异常, 但并没有, 因为该方法已经不再被 valid_before_logout 装饰
    killva4624
        14
    killva4624  
       2021-09-28 15:42:39 +08:00
    我也试过这样的用法,还是写在类外面比较合适,比如一个 requests 的接口工具类:

    def request_verify(func) -> dict:
    """验证请求是否正常,如果异常直接抛错"""

    @wraps(func)
    def inner(cls, *args, **kwargs):
    response = func(cls, *args, **kwargs)
    response.raise_for_status()
    return response.json()

    return inner
    imn1
        15
    imn1  
       2021-09-29 13:21:20 +08:00
    self 和 func 是两个参数,两个都不能省,所以你的装饰器是个必须带参数的装饰器,因为不带参数的话,第一个传入的参数的内容是一个函数,但却赋给了 self 了
    featureoverload
        16
    featureoverload  
       2021-10-13 15:31:01 +08:00
    @2i2Re2PLMaDnghL 正解。

    原贴的装饰器写法完全是错的。

    你的第一个 solution 不是一种正常的写法;只有某种极为特殊的情况才有可能不得不这么写。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3163 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:59 · PVG 21:59 · LAX 05:59 · JFK 08:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.