Is it possible and recommended to modify string representation of python function and parameters for interactive use?
Use case is to use functions as dict keys since functions are hashable. For example:
def addn(n):
return lambda x: x n
can then define keys:
{addn(1): 'foo', addn(2): 'bar'}
and then use the keys computationally:
{k(10):v for k,v in {addn(1): 'foo', addn(2): 'bar'}.items()}
>> {11: 'foo', 12: 'bar'}
In interactive notebooks, would be desirable to see 'addn(1)' etc as string rather than:
{<function __main__.addn.<locals>.<lambda>(x)>: 'some-json',
<function __main__.addn.<locals>.<lambda>(x)>: 'more-json'}
**edit: changed the values to indicate that typical values of interest are usually complex data - previously i had 'foo' and 'bar'. I likely confused some, the values are independent of the keys - this is not an antipattern, it's a common pattern in data science with complex keys - generalization of frozendict keys.
Could this be done w decorator, without defining a derivative function class and without introducing undesirable consequence?
Note it's critical to display the parameters as well as function name as these are data of interest.
CodePudding user response:
Im pretty sure you need to use a class to wrap this
note im just answering the question .... I dont think this is a very good idea
import inspect
class BaseX:
NO_RESULT = object()
def __call__(self,*args,**kwargs):
if self.result is not BaseX.NO_RESULT and callable(self.result):
return self.result(*args,**kwargs)
return BaseX(self.fn,args,kwargs)
def __init__(self,fn,args=None,kwargs=None):
self.called_with = None
self.result = BaseX.NO_RESULT
if args is not None and kwargs is not None:
self.result = fn(*args,**kwargs)
msg = "("
if args:
msg = ", ".join(map(str,args))
if kwargs:
msg = ", " ", ".join(f"{k}={v!r}" for k,v in kwargs.items())
msg = f")"
self.called_with = msg
self.fn = fn
self.args = inspect.signature(fn)
def __str__(self):
return self.fn.__name__ (self.called_with if self.called_with else str(self.args))
def __repr__(self):
return str(self)
def __hash__(self):
return hash(str(self))
@BaseX
def addn(n):
return lambda x: x n
a1 = {addn(1): 'foo', addn(2): 'bar'}
print("A1:",a1)
a2 = {k(10):v for k,v in {addn(1): 'foo', addn(2): 'bar'}.items()}
print(a2)
CodePudding user response:
Still not sure I understand the purpose and think this is XY problem, but in this example case (still not 100% what you ask):
from functools import partial
def addn(n):
return lambda x: x n
spam = {partial(addn, 1):'foo', partial(addn, 2):'bar'}
print(spam)
print({k()(10):v for k, v in spam.items()})
output
{functools.partial(<function addn at 0x7f34703b01e0>, 1): 'foo', functools.partial(<function addn at 0x7f34703b01e0>, 2): 'bar'}
{11: 'foo', 12: 'bar'}
or even better
from functools import partial
def addn(n, x):
return x n
spam = {partial(addn, 1):'foo', partial(addn, 2):'bar'}
print(spam)
print({k(10):v for k, v in spam.items()})
EDIT, thinking further, another way (I complicated it with 2 params)
def addn(n, m):
return lambda x: (x n)*m
spam = {(addn(1, 2), 1, 2):'foo', (addn(2, 2), 2, 2):'bar'}
print(spam)
print({func(10):v for (func, *_), v in spam.items()})
output
{(<function addn.<locals>.<lambda> at 0x7f644e52a268>, 1, 2): 'foo', (<function addn.<locals>.<lambda> at 0x7f644ce44488>, 2, 2): 'bar'}
{22: 'foo', 24: 'bar'}
not very nice, quick and dirty, but still the function and params are clear. Of course you can always define own class instead as suggested by jonrsharpe