Python 类方法的装饰器问题

2021-09-27 23:01:08 +08:00
 MiketsuSmasher
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'
>>>

请问这是什么原因?

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

要么 @self.decorator 要么 @staticmethod def decorator()。
MiketsuSmasher
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
2021-09-27 23:17:30 +08:00
... def valid_before_logout(func):
... ... def execute(self, *args, **kwargs):
rpman
2021-09-28 01:07:19 +08:00
你装饰器的写法是错的
要把装饰器写在类里的方法: https://medium.com/@vadimpushtaev/decorator-inside-python-class-1e74d23107f6

当然我觉得写在类外也很合理
weyou
2021-09-28 02:05:27 +08:00
不确定,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
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
2021-09-28 08:44:58 +08:00
def valid_before_logout(func):
def execute(*args, **kwargs):
if args[0]._is_valid:
SjwNo1
2021-09-28 09:09:20 +08:00
你这是个带参装饰器,你需要填充 self
Ritter
2021-09-28 09:18:21 +08:00
定义在类外面就简单多了
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
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 装饰
2i2Re2PLMaDnghL
2021-09-28 10:15:26 +08:00
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
2021-09-29 13:21:20 +08:00
self 和 func 是两个参数,两个都不能省,所以你的装饰器是个必须带参数的装饰器,因为不带参数的话,第一个传入的参数的内容是一个函数,但却赋给了 self 了
featureoverload
2021-10-13 15:31:01 +08:00
@2i2Re2PLMaDnghL 正解。

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

你的第一个 solution 不是一种正常的写法;只有某种极为特殊的情况才有可能不得不这么写。

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

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

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

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

© 2021 V2EX