首页 新闻 会员 周边 捐助

python调用带有装饰器的方法不经过装饰器内部

0
悬赏园豆:200 [已关闭问题] 关闭于 2024-10-31 16:37

请问:第三个为什么是no?怎么实现为yes,即调用时会经过limiter和action装饰器闭包部分的代码,像是直接调用那样,我确定我的装饰器顺序没有错误

from functools import wraps
from typing import Callable, Any, TypeVar

DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])


def limiter(times: int = 1):
    def decorator(func):
        if not hasattr(func, 'action'):
            raise AttributeError(
                "The function is missing the 'action' attribute."
                " Ensure that the @action decorator is applied before @limiter."
            )

        @wraps(func)
        async def wrapper(*args, **kwargs):
            if times <= 0:
                return 'too many request'
            return await func(*args, **kwargs)

        return wrapper

    return decorator


class A:
    def __init__(self):
        self._actions = {}

    def action(self, name: str) -> Callable[[DecoratedCallable], DecoratedCallable]:
        def decorator(func: DecoratedCallable) -> DecoratedCallable:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                return await func(*args, **kwargs)

            wrapper.action = name
            self._actions[name] = wrapper
            return wrapper

        return decorator

    async def handle(self, action_name: str, *args, **kwargs) -> Any:
        if action_name in self._actions:
            return await self._actions[action_name](*args, **kwargs)
        raise ValueError(f"Action '{action_name}' not found")


a = A()


@limiter(times=0)
@a.action(name="message")
async def message():
    return 'hello'


@limiter(times=1)
@a.action(name="gift")
async def gift():
    return 'hello'


import asyncio


async def main():
    if await message() == 'too many request':
        print('yes')
    else:
        print('no')

    if await gift() == 'hello':
        print('yes')
    else:
        print('no')

    if await a.handle('message') == 'too many request':
        print('yes')
    else:
        print('no')
    if await a.handle('gift') == 'hello':
        print('yes')
    else:
        print('no')
 


asyncio.run(main())
   # yes
    # yes
    # no
    # yes
半醒着的阳光的主页 半醒着的阳光 | 初学一级 | 园豆:14
提问于:2024-10-31 15:34
< >
分享
所有回答(2)
0

把limiter并入类里,虽然我不是很想这样写,但是这样是我目前能想到的解决办法了

    def limiter(self, times: int = 1):
        def decorator(func):
            if not hasattr(func, 'action'):
                raise AttributeError(
                    "The function is missing the 'action' attribute."
                    " Ensure that the @action decorator is applied before @limiter."
                )
            action_name = func.action
            print(action_name)

            @wraps(func)
            async def wrapper(*args, **kwargs):
                if times <= 0:
                    return 'too many request'
                return await func(*args, **kwargs)

            self._actions[action_name] = wrapper
            return wrapper

        return decorator
半醒着的阳光 | 园豆:14 (初学一级) | 2024-10-31 16:32
@a.limiter(times=0)
@a.action(name="message")
async def message():
    return 'hello'

这样使用

支持(0) 反对(0) 半醒着的阳光 | 园豆:14 (初学一级) | 2024-10-31 17:11
1

这个问题应该是装饰器顺序的问题. 按你的定义顺序limiter里的func=A.wrapper; A里的func=message.所以直接调用时候顺序是这样的: limiter.wrapper -> A.wrapper -> message
a._actions里存的是A.wrapper,所以你通过_actions调用,是从中间A.wrapper这一层开始调用的.

from functools import wraps
from typing import Callable, Any, TypeVar

DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])


def limiter(times: int = 1):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            if times <= 0:
                return 'too many request'
            return await func(*args, **kwargs)

        return wrapper

    return decorator


class A:
    def __init__(self):
        self._actions = {}

    def action(self, name: str) -> Callable[[DecoratedCallable], DecoratedCallable]:
        def decorator(func: DecoratedCallable) -> DecoratedCallable:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                return await func(*args, **kwargs)

            wrapper.action = name
            self._actions[name] = wrapper
            return wrapper

        return decorator

    async def handle(self, action_name: str, *args, **kwargs) -> Any:
        if action_name in self._actions:
            return await self._actions[action_name](*args, **kwargs)
        raise ValueError(f"Action '{action_name}' not found")


a = A()


@a.action(name="message")
@limiter(times=0)
async def message():
    return 'hello'


@a.action(name="gift")
@limiter(times=1)
async def gift():
    return 'hello'


import asyncio


async def main():
    if await message() == 'too many request':
        print('yes')
    else:
        print('no')

    if await gift() == 'hello':
        print('yes')
    else:
        print('no')

    if await a.handle('message') == 'too many request':
        print('yes')
    else:
        print('no')
    if await a.handle('gift') == 'hello':
        print('yes')
    else:
        print('no')



asyncio.run(main())
   # yes
    # yes
    # yes
    # yes
www378660084 | 园豆:1074 (小虾三级) | 2024-10-31 16:33

谢谢大佬的回答,不过很遗憾这不是装饰器的顺序的问题
如果调换了顺序没有经过action进行的标注,就会诱发AttributeError的异常
执行顺序是先limiter再action(这里指被包装的函数之前执行的装饰器部分),但是标注顺序却是先action再limiter的

def decorator_a(func):
    def wrapper(*args, **kwargs):
        print("Decorator A: Before calling the function")
        result = func(*args, **kwargs)
        print("Decorator A: After calling the function")
        return result
    return wrapper

def decorator_b(func):
    def wrapper(*args, **kwargs):
        print("Decorator B: Before calling the function")
        result = func(*args, **kwargs)
        print("Decorator B: After calling the function")
        return result
    return wrapper

@decorator_a
@decorator_b
def my_function():
    print("Inside my_function")

# 调用被装饰的函数
my_function()

结果

Decorator A: Before calling the function
Decorator B: Before calling the function
Inside my_function
Decorator B: After calling the function
Decorator A: After calling the function
支持(0) 反对(0) 半醒着的阳光 | 园豆:14 (初学一级) | 2024-10-31 16:42

@半醒着的阳光: 这个调用顺序,我感觉就是包含的关系
@decorator_a
@decorator_b
def my_function():
就相当于
decorator_a(decorator_b(my_function))
你得基于这个去设计,这样在b里是没办法调用到a的.

支持(0) 反对(0) www378660084 | 园豆:1074 (小虾三级) | 2024-10-31 16:46

@半醒着的阳光: 我给你改了一个版本,在limiter里覆盖action,你试试看

from functools import wraps
from typing import Callable, Any, TypeVar

DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])


def limiter(times: int = 1):
    def decorator(func):
        if not hasattr(func, 'action'):
            raise AttributeError(
                "The function is missing the 'action' attribute."
                " Ensure that the @action decorator is applied before @limiter."
            )

        @wraps(func)
        async def wrapper(*args, **kwargs):
            if times <= 0:
                return 'too many request'
            return await func(*args, **kwargs)

        func.call = wrapper
        return wrapper

    return decorator


class A:
    def __init__(self):
        self._actions = {}

    def action(self, name: str) -> Callable[[DecoratedCallable], DecoratedCallable]:
        def decorator(func: DecoratedCallable) -> DecoratedCallable:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                return await func(*args, **kwargs)

            wrapper.action = name
            wrapper.call = wrapper
            self._actions[name] = wrapper
            return wrapper

        return decorator

    async def handle(self, action_name: str, *args, **kwargs) -> Any:
        if action_name in self._actions:
            return await self._actions[action_name].call(*args, **kwargs)
        raise ValueError(f"Action '{action_name}' not found")


a = A()


@limiter(times=0)
@a.action(name="message")
async def message():
    return 'hello'


@limiter(times=1)
@a.action(name="gift")
async def gift():
    return 'hello'


import asyncio


async def main():
    if await message() == 'too many request':
        print('yes')
    else:
        print('no')

    if await gift() == 'hello':
        print('yes')
    else:
        print('no')

    if await a.handle('message') == 'too many request':
        print('yes')
    else:
        print('no')
    if await a.handle('gift') == 'hello':
        print('yes')
    else:
        print('no')



asyncio.run(main())
   # yes
    # yes
    # yes
    # yes
支持(1) 反对(0) www378660084 | 园豆:1074 (小虾三级) | 2024-10-31 17:00

@www378660084: 大佬🐂🍺,这个方法好,这样我不用写到类里面了

支持(0) 反对(0) 半醒着的阳光 | 园豆:14 (初学一级) | 2024-10-31 17:05
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册