from functools import partial, wraps
from types import MethodType
class subscriptable:
_SUBSCRIPT_KEY = '___SUBSCRIPT_KEY'
_HAS_SUBSCRIPTABLE_METHODS = '___HAS_SUBSCRIPTABLE_METHODS'
def __init__ ( self , method) :
self ._method = method
def _bind( self , instance) :
self ._method = MethodType( self ._method, instance)
def __getitem__ ( self , specified_type, *args, **kwargs) :
if not isinstance ( specified_type, type ) :
raise TypeError ( "Only Type Accessors are supported - must be an instance of `type`" )
return partial( self .__call__ , **{ self .__class__._SUBSCRIPT_KEY: specified_type} )
def __call__ ( self , *args, **kwargs) :
"""A transparent passthrough to the wrapped method"""
return self ._method( *args, **kwargs)
def __str__ ( self ) :
return f"<{self.__class__.__name__} {self._method}>"
@ classmethod
def has_key( cls, foo_kwargs) :
"""A utility method to determine whether the provided kwargs has the expected subscript key"""
return cls._SUBSCRIPT_KEY in foo_kwargs
@ classmethod
def key( cls, foo_kwargs:dict ) :
"""A utility method that allows the subscript key to be consumed by the wrapped method, without needing to know the inner workings"""
return foo_kwargs.pop ( cls._SUBSCRIPT_KEY, None )
@ classmethod
def container( cls, clazz) :
"""A decorator for classes containing `subscriptable` methods"""
if not hasattr ( clazz, cls._HAS_SUBSCRIPTABLE_METHODS) :
orig_init = clazz.__init__
@ wraps( clazz.__init__ )
def __init__ ( self , *args, **kwargs) :
for attr_name in dir ( self ) :
attr_value = getattr ( self , attr_name)
if isinstance ( attr_value, cls) :
attr_value._bind( self )
orig_init( self , *args, **kwargs)
clazz.__init__ = __init__
setattr ( clazz, cls._HAS_SUBSCRIPTABLE_METHODS, True )
return clazz
@ subscriptable.container
class MyClass:
@ subscriptable
def compute_typed_value( self , value, *args, **kwargs) :
print ( self , args, kwargs)
if subscriptable.has_key ( kwargs) :
value = subscriptable.key ( kwargs) ( value)
self .my_other_method ( )
return value
def my_other_method( self ) :
print ( 'Doing some other things!' )
return 3
a = MyClass( )
b = MyClass( )
value = a.compute_typed_value [ int ] ( '12345' )
ZnJvbSBmdW5jdG9vbHMgaW1wb3J0IHBhcnRpYWwsIHdyYXBzCmZyb20gdHlwZXMgaW1wb3J0IE1ldGhvZFR5cGUKCmNsYXNzIHN1YnNjcmlwdGFibGU6CiAgICBfU1VCU0NSSVBUX0tFWSA9ICdfX19TVUJTQ1JJUFRfS0VZJwogICAgX0hBU19TVUJTQ1JJUFRBQkxFX01FVEhPRFMgPSAnX19fSEFTX1NVQlNDUklQVEFCTEVfTUVUSE9EUycKCiAgICBkZWYgX19pbml0X18oc2VsZiwgbWV0aG9kKToKICAgICAgICBzZWxmLl9tZXRob2QgPSBtZXRob2QKCiAgICBkZWYgX2JpbmQoc2VsZiwgaW5zdGFuY2UpOgogICAgICAgIHNlbGYuX21ldGhvZCA9IE1ldGhvZFR5cGUoc2VsZi5fbWV0aG9kLCBpbnN0YW5jZSkKCiAgICBkZWYgX19nZXRpdGVtX18oc2VsZiwgc3BlY2lmaWVkX3R5cGUsICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgaWYgbm90IGlzaW5zdGFuY2Uoc3BlY2lmaWVkX3R5cGUsIHR5cGUpOgogICAgICAgICAgICByYWlzZSBUeXBlRXJyb3IoIk9ubHkgVHlwZSBBY2Nlc3NvcnMgYXJlIHN1cHBvcnRlZCAtIG11c3QgYmUgYW4gaW5zdGFuY2Ugb2YgYHR5cGVgIikKICAgICAgICAgICAgCiAgICAgICAgcmV0dXJuIHBhcnRpYWwoc2VsZi5fX2NhbGxfXywgKip7c2VsZi5fX2NsYXNzX18uX1NVQlNDUklQVF9LRVk6IHNwZWNpZmllZF90eXBlfSkKICAgIAogICAgZGVmIF9fY2FsbF9fKHNlbGYsICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgIiIiQSB0cmFuc3BhcmVudCBwYXNzdGhyb3VnaCB0byB0aGUgd3JhcHBlZCBtZXRob2QiIiIKICAgICAgICByZXR1cm4gc2VsZi5fbWV0aG9kKCphcmdzLCAqKmt3YXJncykKICAgIAogICAgZGVmIF9fc3RyX18oc2VsZik6CiAgICAgICAgcmV0dXJuIGYiPHtzZWxmLl9fY2xhc3NfXy5fX25hbWVfX30ge3NlbGYuX21ldGhvZH0+IgogICAgCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBoYXNfa2V5KGNscywgZm9vX2t3YXJncyk6CiAgICAgICAgIiIiQSB1dGlsaXR5IG1ldGhvZCB0byBkZXRlcm1pbmUgd2hldGhlciB0aGUgcHJvdmlkZWQga3dhcmdzIGhhcyB0aGUgZXhwZWN0ZWQgc3Vic2NyaXB0IGtleSIiIgogICAgICAgIHJldHVybiBjbHMuX1NVQlNDUklQVF9LRVkgaW4gZm9vX2t3YXJncwogICAgCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBrZXkoY2xzLCBmb29fa3dhcmdzOmRpY3QpOgogICAgICAgICIiIkEgdXRpbGl0eSBtZXRob2QgdGhhdCBhbGxvd3MgdGhlIHN1YnNjcmlwdCBrZXkgdG8gYmUgY29uc3VtZWQgYnkgdGhlIHdyYXBwZWQgbWV0aG9kLCB3aXRob3V0IG5lZWRpbmcgdG8ga25vdyB0aGUgaW5uZXIgd29ya2luZ3MiIiIKICAgICAgICByZXR1cm4gZm9vX2t3YXJncy5wb3AoY2xzLl9TVUJTQ1JJUFRfS0VZLCBOb25lKQogICAgCiAgICBAY2xhc3NtZXRob2QKICAgIGRlZiBjb250YWluZXIoY2xzLCBjbGF6eik6CiAgICAgICAgIiIiQSBkZWNvcmF0b3IgZm9yIGNsYXNzZXMgY29udGFpbmluZyBgc3Vic2NyaXB0YWJsZWAgbWV0aG9kcyIiIgogICAgICAgIGlmIG5vdCBoYXNhdHRyKGNsYXp6LCBjbHMuX0hBU19TVUJTQ1JJUFRBQkxFX01FVEhPRFMpOgogICAgICAgICAgICBvcmlnX2luaXQgPSBjbGF6ei5fX2luaXRfXwogICAgICAgICAgICBAd3JhcHMoY2xhenouX19pbml0X18pCiAgICAgICAgICAgIGRlZiBfX2luaXRfXyhzZWxmLCAqYXJncywgKiprd2FyZ3MpOgogICAgICAgICAgICAgICAgZm9yIGF0dHJfbmFtZSBpbiBkaXIoc2VsZik6CiAgICAgICAgICAgICAgICAgICAgYXR0cl92YWx1ZSA9IGdldGF0dHIoc2VsZiwgYXR0cl9uYW1lKQogICAgICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UoYXR0cl92YWx1ZSwgY2xzKToKICAgICAgICAgICAgICAgICAgICAgICAgYXR0cl92YWx1ZS5fYmluZChzZWxmKQogICAgICAgICAgICAgICAgb3JpZ19pbml0KHNlbGYsICphcmdzLCAqKmt3YXJncykKICAgICAgICAgICAgY2xhenouX19pbml0X18gPSBfX2luaXRfXwogICAgICAgICAgICBzZXRhdHRyKGNsYXp6LCBjbHMuX0hBU19TVUJTQ1JJUFRBQkxFX01FVEhPRFMsIFRydWUpCiAgICAgICAgcmV0dXJuIGNsYXp6CgpAc3Vic2NyaXB0YWJsZS5jb250YWluZXIKY2xhc3MgTXlDbGFzczoKCiAgICBAc3Vic2NyaXB0YWJsZQogICAgZGVmIGNvbXB1dGVfdHlwZWRfdmFsdWUoc2VsZiwgdmFsdWUsICphcmdzLCAqKmt3YXJncyk6CiAgICAgICAgcHJpbnQoc2VsZiwgYXJncywga3dhcmdzKQogICAgICAgIGlmIHN1YnNjcmlwdGFibGUuaGFzX2tleShrd2FyZ3MpOgogICAgICAgICAgICB2YWx1ZSA9IHN1YnNjcmlwdGFibGUua2V5KGt3YXJncykodmFsdWUpCiAgICAgICAgc2VsZi5teV9vdGhlcl9tZXRob2QoKQogICAgICAgIHJldHVybiB2YWx1ZQoKICAgIGRlZiBteV9vdGhlcl9tZXRob2Qoc2VsZik6CiAgICAgICAgcHJpbnQoJ0RvaW5nIHNvbWUgb3RoZXIgdGhpbmdzIScpCiAgICAgICAgcmV0dXJuIDMKCgphID0gTXlDbGFzcygpCmIgPSBNeUNsYXNzKCkKdmFsdWUgPSBhLmNvbXB1dGVfdHlwZWRfdmFsdWVbaW50XSgnMTIzNDUnKQ==
stdout
<__main__.MyClass object at 0x14614b38dc70> ('12345',) {'___SUBSCRIPT_KEY': <class 'int'>}
stderr
Traceback (most recent call last):
File "./prog.py", line 71, in <module>
File "./prog.py", line 22, in __call__
File "./prog.py", line 60, in compute_typed_value
TypeError: int() argument must be a string, a bytes-like object or a number, not 'MyClass'