1

In Python it is easy to create new functions programmatically. How would I assign this to programmatically determined names in the current scope?

This is what I'd like to do (in non-working code):

obj_types = ('cat', 'dog', 'donkey', 'camel')
for obj_type in obj_types:
    'create_'+obj_type = lambda id: id

In the above example, the assignment of lambda into a to-be-determined function name obviously does not work. In the real code, the function itself would be created by a function factory.

The background is lazyness and do-not-repeat-yourself: I've got a dozen and more object types for which I'd assign a generated function. So the code currently looks like:

create_cat   = make_creator('cat')
# ...
create_camel = make_creator('camel')

The functions create_cat etc are used hardcoded in a parser.

If I would create classes as a new type programmatically, types.new_class() as seen in the docs seems to be the solution. Is it my best bet to (mis)use this approach?

Community
  • 1
  • 1
cfi
  • 10,915
  • 8
  • 57
  • 103
  • You can create a variable with `locals()[name] = value` – Vaughn Cato Apr 24 '13 at 13:55
  • related: [generating variable names on fly in python](http://stackoverflow.com/q/4010840/4279) – jfs Apr 24 '13 at 13:56
  • @Vaughn: Oh! That is simple, indeed. Why not convert it into an answer? – cfi Apr 24 '13 at 13:56
  • @VaughnCato: you shouldn't use `locals()` in that way. – jfs Apr 24 '13 at 13:57
  • related: [How can you dynamically create variables in Python via to a while loop?](http://stackoverflow.com/q/5036700/4279) – jfs Apr 24 '13 at 13:58
  • 1
    @Vaughn: To provide the reasoning behind J.F.'s rejection of using `locals()` that way, here's what the [docs think about that](http://docs.python.org/3/library/functions.html#locals): "Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter" – cfi Apr 24 '13 at 14:05
  • if you want to create (class/instance) methods programmatically; you could use class decorators, metaclasses, mixins. Here's [an example that shows how to create properties dynamically](http://stackoverflow.com/a/11922028/4279). – jfs Apr 24 '13 at 14:18

2 Answers2

3

One way to accomplish what you are trying to do (but not create functions with dynamic names) is to store the lamda's in a dict using the name as the key. Instead of calling create_cat() you would call create['cat'](). That would dovetail nicely with not hardcoding names in the parser logic as well.

D.Shawley
  • 58,213
  • 10
  • 98
  • 113
  • This answer and the comment by J.F. Sebastian pushed me to use a class with dynamically defined methods. Will post the code in an answer. – cfi Apr 24 '13 at 14:30
0

Vaughn Cato points out that one could just assign into locals()[object_type] = factory(object_type). However the Python docs prohibit this: "Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter"

D. Shawley points out that it would be wiser to use a dict() object which entries would hold the functions. Access would be simple by using create['cat']() in the parser. While this is compelling I do not like the syntax overhead of the brackets and ticks required.

J.F. Sebastian points to classes. And this is what I ended up with:

# Omitting code of these classes for clarity
class Entity:
    def __init__(file_name, line_number):
        # Store location, good for debug, messages, and general indexing

# The following classes are the real objects to be generated by a parser
# Their constructors must consume whatever data is provided by the tokens
# as well as calling super() to forward the file_name,line_number info.
class Cat(Entity): pass
class Camel(Entity): pass

class Parser:
    def parse_file(self, fn):
        # ...

        # Function factory to wrap object constructor calls
        def create_factory(obj_type):
            def creator(text, line_number, token):
                try:
                    return obj_type(*token,
                                    file_name=fn, line_number=line_number)
                except Exception as e:
                    # For debug of constructor during development
                    print(e)
            return creator

        # Helper class, serving as a 'dictionary' of obj construction functions
        class create: pass
            for obj_type in (Cat, Camel):
                setattr(create,
                        obj_type.__name__.lower(),
                        create_factory(obj_type))

        # Parsing code now can use (again simplified for clarity):
        expression = Keyword('cat').setParseAction(create.cat)

This is helper code for deploying a pyparsing parser. D. Shawley is correct in that the dict would actually more easily allow to dynamically generate the parser grammar.

cfi
  • 10,915
  • 8
  • 57
  • 103