【问题标题】:Can classmethod/staticmethod access attributes of initial caller?classmethod/staticmethod 可以访问初始调用者的属性吗?
【发布时间】:2021-05-13 00:18:27
【问题描述】:

我正在学习并尝试为一些第三方API编写python api客户端 为简单起见:
我有 2 个类:Session、EndpointGroup:

class Session:
    def __init__(self, token=None):
        self.token = token
        self.group1 = EndpointGroup

class EndpointGroup:
    @classmethod
    def ping(cls, token, **kwargs):
        return f'Calling ping() with params: {kwargs} and token {token}'
          

我有许多 EndpointGroups,其中包含同类别的 api 调用。
我想让最终用户调用这样的方法:

session = Session(token='123')
r = session.group1.ping(data='somedata') # Calling ping with params: {'data':'somedata'} and token 123

我想自动/隐式地将令牌传递给可调用对象,这样用户就不必在每次调用时都传递给可调用对象,例如 session.group1.ping(data='somedata', token=session.token)
有“内置”解决方案吗?因为使用 _getattribute_ 我只能访问 EndpointGroup 类,但不能访问被调用的方法。
我已经尝试过:

  • 使用 _getattribute_
  • 创建传递会话/令牌的 EndpointGroup 实例(不喜欢解决方案,因为它为每个会话创建了太多实例)
  • 让 Session 继承 EndpointGroup 方法,但后来我失去了像 session.groupN.method_Xsession.method_X 这样的理想“接口”
  • 在 EndpointGroup 类中设置 attr 'token' 并在每次调用时更改它,以便方法可以访问当前调用者的此令牌(会话)(不喜欢此解决方案,因为这样我将无法异步工作)李>

【问题讨论】:

  • “不喜欢解决方案,因为它为每个会话创建了太多实例”我不确定我是否理解这个问题
  • 您的第一次尝试可能是正确的道路。但是,不是每次调用EndpointGroup 时都创建一个新实例,而是要“记忆”它,以便如果给定参数已经存在一个现有对象,它可以返回一个现有对象。 (即,如果x == y,则EndpointGroup(x) is EndpointGroup(y) 为真。)
  • @chepner 目前我有使用这种方式的代码,但对我来说似乎是错误的。例如:每个 1 个实例会话有 20 个 EndpointGroups 实例。因此,如果某个假设的 Web 服务将有 1000 个会话,那么它将有 20000 个端点组实例。这种情况似乎不对

标签: python oop async-await


【解决方案1】:

您可以使用一些代理来像偏函数一样:


class PartialProxy:
    def __init__(self, method, **kwargs):
        self.method = method
        self.kwargs = kwargs
        
    def __call__(self, **kwargs):
        k = {**self.kwargs, **kwargs}
        return self.method(**k)
        
class PartialProxyProxy:
    def __init__(self, klass, **kwargs):
        self.klass = klass
        self.kwargs = kwargs
    
    def __getattr__(self, attr):
        return PartialProxy(getattr(self.klass, attr), **self.kwargs)


class Session:
    def __init__(self, token=None):
        self.token = token
        self.group1 = PartialProxyProxy(EndpointGroup, token=token)

class EndpointGroup:
    @classmethod
    def ping(cls, token, **kwargs):
        return f'Calling ping() with params: {kwargs} and token {token}'

session = Session(token='123')
r = session.group1.ping(data='somedata')
print(r)

输出:

Calling ping() with params: {'data': 'somedata'} and token 123

【讨论】:

  • 这种方式创建了许多部分代理实例而不是端点组实例,这仍然会导致我第一次尝试中描述的不良行为。这些代理如何需要更少的内存,所以它部分解决了问题。谢谢。
  • 无论如何,如果我能理解它是如何工作的,那么我可以在调用“groupX”时在会话 _gettattr_ 中创建/调用 partialrpoxyproxy 实例。所以看起来该实例只会在调用期间存在并在它之后被销毁。
【解决方案2】:
class Session:
    def __init__(self, token=None):
        self.token = token
        self.group1 = EndpointGroup

    def __getattribute__(self, item):
        attribute = super(Session, self).__getattribute__(item)
        if isinstance(attribute, type(EndpointGroup)):
            return ProxyEndpointGroup(self.token,attribute)
        else:
            return attribute
import functools
class ProxyEndpointGroup:
    def __init__(self, token, endpoint_group):
        self.token = token
        self.endpoint_group = endpoint_group

    def __getattr__(self, item):
        return functools.partial(getattr(self.endpoint_group,item), token=self.token)

class EndpointGroup:
    @classmethod
    def ping(cls,token, **kwargs):
        return f'Calling ping() with params: {kwargs} and token {token}'

session = Session(token='12345')
r = session.group1.ping(data='endpoint data')
print(r)

>> Calling ping() with params: {'data': 'endpoint data'} and token 12345

这样你可以只在调用添加令牌参数时创建代理对象并返回部分方法最终销毁代理对象。

它解决了您不必在内存中创建多个不必要的对象的担忧 它还解决了异步调用作为令牌传入每个调用的问题

【讨论】:

    【解决方案3】:

    我看不出为什么ping() 应该是类方法而不是实例方法,如果它是实例方法,您的问题会变得更容易:

    class Session:
        def __init__(self, token=None):
            self.token = token
            self.group1 = EndpointGroup(session=self)
    

    将会话传递给EndpointGroup,然后其实例方法ping可以直接访问token:

    class EndpointGroup:
        def __init__(self, session):
            self.session = session
        def ping(self, token, **kwargs):
            return f'Calling ping() with params: {kwargs} and token {self.session.token}'
    

    【讨论】:

    • 好像错了。例如:我每个 1 个实例会话有 20 个 EndpointGroups 实例。因此,如果某个假设的 Web 服务将有 1000 个会话,那么它将有 20000 个端点组实例。这种情况似乎不对
    • @ArtemChirkov:为什么一个会话需要 20 个端点?您是说如果您的 1000 个会话中有 574 个具有端点“A”,那么这 574 个端点“A”之间就没有区别了吗?
    【解决方案4】:

    类似

    class Session:
        class Empty:
            pass
        def __init__(self, token=None):
            self.token = token
            self.group1 = self.Empty
            setattr(self.group1, "ping", lambda p : EndpointGroup.ping(self.token, **p))
    
    class EndpointGroup:
        @classmethod
        def ping(cls, token, **kwargs):
            return f'Calling ping() with params: {kwargs} and token {token}'
    
    a = Session("123")
    r = a.group1.ping({"param_key":"param_val"})
    r
    "Calling ping() with params: {'param_key': 'param_val'} and token 123"
    

    或者第二种方式(我认为更接近 OOP):

    class Session:
        def __init__(self, token=None):
            class EndpointGroupWithToken(EndpointGroup):
                @classmethod
                def ping(cls, **kwargs):
                    return super().ping(self.token, **kwargs)
            self.token = token
            self.group1 = EndpointGroupWithToken
    
    class EndpointGroup:
        @classmethod
        def ping(cls, token, **kwargs):
            return f'Calling ping() with params: {kwargs} and token {token}'
    
    a = Session("123")
    r = a.group1.ping(data='somedata')
    r
    "Calling ping() with params: {'data': 'somedata'} and token 123"
    

    但我认为您为应用程序选择了错误的架构。也许 Mixins 会更适合你的需求?

    class Ping1Mixin:
        def ping1(self, **kwargs):
            return f'Calling ping1() with params: {kwargs} and token {self.token}'
            
    class Ping2Mixin:
        def ping2(self, **kwargs):
            return f'Calling ping2() with params: {kwargs} and token {self.token}'
            
    class Session(Ping1Mixin, Ping2Mixin):
        def __init__(self, token=None):
            self.token = token
    
    a = Session("123")
    r = a.ping1(data='somedata')
    r = a.ping2(data='somedata')
    

    感谢您提供有趣的谜题:)

    【讨论】:

      【解决方案5】:

      似乎您有“会话”和“端点组”,它们不一定必须紧密耦合。假设您的 api 方法(例如ping)不必是类方法(它们似乎不需要),那么一个简单的解决方案是创建一个会话对象,然后将其提供给端点组目的。然后,您拥有 N 个唯一会话对象和 M 个唯一端点组,并根据需要组合端点组以向它们提供正确的令牌。这样可以避免您似乎试图避免的过多物体。

      例子:

      class Session:
          def __init__(self, token):
              self.token = token
      
      class EndpointGroup:
          def __init__(self, session):
              self.session = session
          def ping(self, **kwargs):
              # do a ping
      
      session_abc = Session('ABC')
      session_xyz = Session('XYZ')
      
      endpoint_group_1 = EndpointGroup(session_abc)
      endpoint_group_2 = EndpointGroup(session_abc)
      endpoint_group_3 = EndpointGroup(session_xyz)
      

      如果将会话与端点组匹配似乎是“额外工作”,您可以使用工厂方法轻松处理。

      【讨论】:

        【解决方案6】:

        你应该这样做。
        这将是您的正确答案。

        class Session:
            def __init__(self, token=None):
                self.token = token
                self.group1 = EndpointGroup(token)
        
        class EndpointGroup:
            @classmethod
            def __init__(self, token=None):
                self.token = token
            def ping(self, **kwargs):
                return f'Calling ping() with params: {kwargs} and token {self.token}'
        

        执行:

        session = Session(token='123')
        r = session.group1.ping(data='somedata')
        print(r)
        

        输出:

        "Calling ping() with params: {'data': 'somedata'} and token 123"
        

        【讨论】:

        • 这是最糟糕的解决方案!非常糟糕和错误的模式! session1=Session(token='123');session2=Session(token='567');r1=session1.group1.ping(data='somedata1');r2=session2.group1.ping(data='somedata2');print(r1);print(r2) 将打印:Calling ping() with params: {'data': 'somedata1'} and token 567Calling ping() with params: {'data': 'somedata2'} and token 567
        猜你喜欢
        • 2019-08-14
        • 2012-08-24
        • 2021-08-14
        • 1970-01-01
        • 1970-01-01
        • 2019-04-28
        • 2012-07-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多