0

I was looking for a way to save a dictionary to file and read it back. I came across this solution: Writing a dict to txt file and reading it back?

My dictionary however contains function pointers (is it called this way in Python?). The concrete dictionary initialisation looks something like this:

dictionary = {
    'string': 'value',
    'integer': 42,
    'function': some_func
  }

with some_func being some global function:

def some_func ():
  print('This function has been called!')

The solution linked above breaks down in this case. To save the dictionary to file, it proposes to produce a string from the dictionary via str and store this string in a file. The string could then, as suggested, be read back again and restored to a dictionary via eval:

dictionary_string = str(dictionary) # String from dictionary
dictionary = eval(dictionary_string) # Restore the dictionary from string

However, Python inserts a type indicator in place of the function pointer in my example, which is of course syntactical nonsense to Python. My dictionary would as a string look like this:

{'string': 'value', 'integer': 42, 'function': <function some_func at 0x00000000002a>}

I'm not giving much on reading in the pointer, I can live without it. But I'd like to find a simple solution which can handle such cases. It can be either by reading in the pointer, even if it points to nowhere, or by just neglecting the type indicator <> (replacing it to None or whatever).

I also considered storing the function name as a string, but I refrained from handling it this way because Python doesn't seem to have a beautiful solution to call a global function by a string. My hope was that there was maybe a solution to my problem that is just a bit less ugly than calling functions by global()[dictionary['function']]().

thorr
  • 15
  • 1
  • "My dictionary however contains function pointers (is it called this way in Python?)." Nope. They are just called functions. Python *doesn't have pointers* – juanpa.arrivillaga Jul 14 '21 at 14:39
  • All the `dict` values are references: `dictionary['function']` is a reference to a `function` object, `dictionary['integer']` is a reference to an `int` object, etc. What you are seeing is the return value of `function.__repr__`. You can't override it, but as long as you aren't expecting to be able to use it to reconstruct the original function, there's no harm in using this string in your `dict`. – chepner Jul 14 '21 at 14:42
  • 1
    If you are planning on reconstituting the `dict`, I would recommend serializing to JSON instead of using `dict.__str__` or `dict.__repr__`. This will allow you to write a custom JSON encoder that can replace the `function` instance with `None`. – chepner Jul 14 '21 at 14:47

1 Answers1

1

Here's an example using JSON to serialize your dictionary, with the custom encoder handling the replacement of any function object with None:

import json
import types


class FunctionEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, types.FunctionType):
            return None
        return super().default(obj)


def some_func():
    pass


dictionary = {
    'string': 'value',
    'integer': 42,
    'function': some_func
    }

dict_json = json.dumps(dictionary, cls=FunctionEncoder)

new_dict = json.loads(dict_json)

print(new_dict)

When run, it outputs

{'string': 'value', 'integer': 42, 'function': None}

You could also serialize the function to its name instead,

class FunctionEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, types.FunctionType):
            return getattr(obj, '__name__', '<anonymous function>')
        return super().default(obj)

(I'm not sure the use of getattr is necessary; return obj.__name__ may work fine.)


A possibility is to define an encoder/decoder pair that serializes a function by encoding its byte code, signature, etc individually, then reconstructing an instance from those parts. If it's possible, it's not something I want to attempt here.

chepner
  • 497,756
  • 71
  • 530
  • 681