Home > OS >  Modify string repr of functions for use in dict keys
Modify string repr of functions for use in dict keys

Time:09-28

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

  • Related