Python 重载另一个类属性的类的类型

Python 重载另一个类属性的类的类型,python,class,oop,python-asyncio,python-typing,Python,Class,Oop,Python Asyncio,Python Typing,我正在尝试创建一个类,该类使用\uuuu getattr\uuuu调用另一个类属性,以便包装类调用 from aiohttp import ClientSession from contextlib import asynccontextmanager class SessionThrottler: def __init__(self, session: ClientSession, time_period: int, max_tasks: int

我正在尝试创建一个类,该类使用
\uuuu getattr\uuuu
调用另一个类属性,以便包装类调用

from aiohttp import ClientSession
from contextlib import asynccontextmanager


class SessionThrottler:

    def __init__(self, session: ClientSession,
                 time_period: int, max_tasks: int):
        self._obj = session
        self._bucket = AsyncLeakyBucket(max_tasks=max_tasks,
                                        time_period=time_period)

    def __getattr__(self, name):
        @asynccontextmanager
        async def _do(*args, **kwargs):
            async with self._bucket:
                res = await getattr(self._obj, name)(*args, **kwargs)
                yield res
        return _do

    async def close(self):
        await self._obj.close()

那么我可以做:

async def fetch(session: ClientSession):
    async with session.get('http://localhost:5051') as resp:
        _ = resp


session = ClientSession()
session_throttled = SessionThrottler(session, 4, 2)
await asyncio.gather(
    *[fetch(session_trottled) 
      for _ in range(10)]
)

此代码工作正常,但我如何才能将
session\u throttled
推断为
ClientSession
,而不是
sessionrottler
(有点像
functools.wrapps
)?

我取决于“推断为”需要什么

创建客户端会话的ThrotledSessions实例 对类执行此操作的自然方式是通过继承-如果您的
sessionrotler
继承自
ClientSession
,那么它自然也是
ClientSession
。 “小缺点”是
\uuu getattr\uuuu
将无法按预期工作,因为只对实例中找不到的属性调用,Python将在ThrottleSession对象中“查看”来自
ClientSession
的原始方法,并调用它们

当然,这也需要静态继承类,并且您可能希望它动态工作。(我指的是静态的 必须编写
类SessionRotler(ClientSession):
-或者,如果要包装的不同会话类数量有限,则至少为每个会话类编写一个子类,该子类也继承自ThrottledClass:

class ThrottledClient会话(ThrottledSession,ClientSession):
...
如果这对您有用,那么就是通过创建
\uuuu getattribute\uuuuu
而不是
\uuuu getattr\uuuuuu
来修复属性访问。两者之间的区别在于
\uuu getattribte\uuuuu
完成所有属性查找步骤,并在请求查找时调用当所有其他操作都失败时,etattr\uuuuuu作为正常查找的一部分被调用(在
\uuuu getattribute\uuuu
的标准算法中)

class SessionRottlerMixin:
定义初始化(self,session:ClientSession,
时间段:int,最大任务:int):
self.\u bucket=AsyncLeakyBucket(max\u tasks=max\u tasks,
时间段=时间段)
def uu getattribute_uu(self,name):
attr=super()
如果不是name.startswith(“u”)或不可调用(attr):
返回属性
@异步上下文管理器
异步定义do(*args,**kwargs):
与self.\u bucket异步:
res=等待属性(*args,**kwargs)
收益率
返回
类throttledclientsession(SessionThrottlerMixin,ClientSession):
通过
如果您正在从其他代码获取
CLientSession
实例,并且不希望或无法使用此基类替换基类,则可以在所需实例上执行此操作,方法是指定
\uuuuu class\uuuu
属性: 如果
ClientSession
是一个普通的Python类,不从Python内置的特殊基继承,不使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu:
session.\uuuu class\uuuuu=ThrottledClientSession

以这种方式分配的类不会运行新类
\uuuuu init\uuuuu
。因为需要创建
\u bucket
,所以可以有一个类方法来创建bucket并进行替换-因此,在具有
\uuu getattribute\uuuuu
的版本中添加如下内容:


课程会话Rottler:
...
@类方法
定义包裹(cls,实例,时间段:int,最大任务:int):
cls.\uuuuu类\uuuuu=cls
实例。_bucket=AsyncLeakyBucket(max_tasks=max_tasks,
时间段=时间段)
返回实例
...
throttled_session=throttledclientsession._wrap(session,4,2)
如果您有很多父类希望以这种方式包装,并且您不想声明它的
限制版本,那么可以动态地进行此操作-但我只会在只有这样做的情况下才这样做。声明大约10个stub-thotited版本,每个版本3行,会更可取

虚拟子类化 如果您可以更改
ClientSession
类(以及您想要包装的其他类)的代码,这是最不显眼的方式-

Python有一个模糊的OOP特性,称为
虚拟子类化
,其中一个类可以注册为另一个类的子类,而不需要真正的继承。然而,要成为“父”的类必须有
abc.ABCMeta
作为其元类,否则这就非常不显眼了

以下是它的工作原理:


In[13]:从abc导入abc
在[14]中:A类(ABC):
…:通过
...:                                                                                                                             
在[15]中:B类:#不继承
…:通过
在[16]中:A.寄存器(B)
Out[16]:uu main_uuu.B
在[17]中:i实例(B(),A)
Out[17]:对
因此,在原始代码中,如果您可以使
ClientSession
继承自
abc.abc
(没有任何其他更改),然后执行以下操作:

ClientSession.register(sessionrottler)
而且它只会起作用(如果您的意思是“推断为”与对象类型有关)

请注意,如果ClientSession和其他人具有不同的元类,则添加
abc.abc
作为其基础之一将失败,并导致元类冲突。如果您可以更改他们的代码,这仍然是错误的
import typing as T
...
session = ClientSession()
session_throttled = T.cast(ClientSession, SessionThrottler(session, 4, 2))
import asyncio
import functools
import contextlib

class Counter:
    async def infinite(self):
        cnt = 0
        while True:
            yield cnt
            cnt += 1
            await asyncio.sleep(1)

def limited_infinite(f, limit):
    @functools.wraps(f)
    async def inner(*a, **kw):
        cnt = 0
        async for res in f(*a, **kw):
            yield res
            if cnt == limit:
                break
            cnt += 1
    return inner

@contextlib.contextmanager
def throttler(limit, counter):
    orig = counter.infinite
    counter.infinite = limited_infinite(counter.infinite, limit)
    yield counter
    counter.infinite = orig

async def main():
    with throttler(5, Counter()) as counter:
        async for x in counter.infinite():
            print('res: ', x)

if __name__ == "__main__":
    asyncio.run(main())