8

Suppose I need to create 10 functions called add1, add2, ..., add10. For each i, addi takes in an integer x and outputs x+i.

Python gave me a NameError when I tried the following and evaluated add2(10).

for i in range(10):
    def addi(x):
        return x + (i+1)

I know the above code is wrong, since the addi functions I've defined aren't mutable; I'm pretty sure all I've done is redefine addi 10 times. How can these 10 functions be quickly defined?

jpp
  • 159,742
  • 34
  • 281
  • 339
jessica
  • 231
  • 1
  • 3
  • 9
  • 2
    Why would you want multiple functions that increases each return by 1 in reference of the previous function? This feels like an XY problem. what are you using it for? Maybe there's a better solution out there for you. – MooingRawr Apr 03 '18 at 13:23
  • 1
    @MooingRawr I'm learning about generic operators right now. The current homework involves defining five binary operators (on \C \times \Z) by first representing the integer as a complex number, then using the appropriate binary operator on \C. This resulted in a lot of similar looking functions, so I was hoping for a way to shorten my code. – jessica Apr 03 '18 at 13:38
  • Surely a more conventional approach might be to use classes with methods. – D.L Apr 18 '22 at 08:14

5 Answers5

8

Use functools.partial combined with a dictionary in this situation.

I assume what you really want to do is more complex, since multiple functions are not necessary for this specific task.

from functools import partial

def add(x, i):
    return x + i

d = {f'add{k}': partial(add, i=k) for k in range(1, 10)}

d['add3'](5)  # 8

Explanation

  • It is good practice to store a variable number of related objects in a specially defined dictionary.
  • functools.partial is a higher order function which returns an input function with selected argument(s) fixed.

Polluting the namespace

From the comments, a commonly asked question goes:

OP seems to be asking if there's a better way of just doing def add1:... def add2:... While I agree with your solution, I don't agree with the fact that it's the solution to the current question. - MooingRawr

Why is using an iterator to define functions a bad idea? It seemed like a good way to shorten code to me. Is it generally better to define them one by one? – jessica

My short answer:

Using globals().update to pollute the namespace is a bad idea.. Hold related variables in specially made collections. It makes sense from any viewpoint (maintainability, calling, modifying, etc). – jpp

@BoarGules's extend answer:

It's not a good idea because it creates functions dynamically, but they can only be called by static code (otherwise the code wouldn't know what to call), so what's the point of them being dynamic? Creating them dynamically makes the code hard to read (you can't easily search for the function definition) and gives IDEs unnecessary trouble in their efforts to help you. You save tedious, repetitive coding, but programming sometimes entails tedious and careful repetition. The effort and skill would be better spent speeding up the coding (clever search/replace, macros, whatever). – BoarGules

jpp
  • 159,742
  • 34
  • 281
  • 339
5

Functions aren't mutable in general; but they may have references to data that is. In your snippet, this reference is a little unclear, but i only occurs once in the function body, as a read. It therefore reads from some outer scope, typically the function or module your for loop is contained within. Because this happens to be a shared context, every addi function will end up with the same i.

Another issue is that you're using the name addi on every iteration, and the function never appeared under another name. So whatever addi functions were defined earlier are lost. This leads us to the third question; why would you want to create names (such as function names) dynamically? It's almost always better to use a collection, such as the d dictionary in jpp's answer. Otherwise, what code would even refer to the functions you created?

All that said, it is still possible to do what you asked for, albeit very strange. Here's one way:

def addfunc(n):
    def addn(x):
        return x+n
    return addn

for i in range(1,10):
    globals()['add{}'.format(i)] = addfunc(i)

This abuses globals to inject dynamically created names into the module's namespace, and nests each of these functions within another to create the namespaces holding their individual n values. Another classic hack was using a default argument, and partial application of operator.add is a neater functional style.

Yann Vernier
  • 15,414
  • 2
  • 28
  • 26
  • Yeah, I was aware of how `addi` is being redefined with each iteration, so my attempt would result in a function `addi` that adds 10. I've only just learnt about dictionaries and wasn't aware they could be used this way, so thanks! – jessica Apr 03 '18 at 13:46
3

A solution:

from types import FunctionType
from copy import copy

def copy_function(fn, name):
    return FunctionType(
    copy(fn.func_code),
    copy(fn.func_globals),
    name=name,
    argdefs=copy(fn.func_defaults),
    closure=copy(fn.func_closure)
)

for i in range(10):
    name = 'add' + str(i)
    def _add(x):
        return x + (i+1)
    globals()[name] = copy_function(_add, name)


print add1(2) # 4
print add3(8) # 12 

Using the copy function from https://stackoverflow.com/a/34796018/7529716

For Python3, change copy_function to:

def copy_function(fn, name):

    return FunctionType(copy(fn.__code__), 
                        copy(fn.__globals__), name=name, argdefs=copy(fn.__defaults__),
                        closure=copy(fn.__closure__))
idnavid
  • 1,795
  • 17
  • 20
Yassine Faris
  • 951
  • 6
  • 26
1

does this help?

def createFunc(i):
  def func(x):
    return x + (i+1)
  return func

Then just create the functions and store them in a array:

add = [createFunc(i) for i in range(10)]
print add[0](1)  # 1+(0+1) = 2
print add[0](2)  # 2+(0+1) = 3
print add[2](10) # 10+(2+1) = 13
print add[2](11) # 11+(2+1) = 14
r3v3r53
  • 142
  • 1
  • 1
  • 10
1

As others have noted, it's questionable if you should do this in production code. However, for educational purposes, here's how to do it by generating function names and and injecting them in the global namespace:

>>> def f(x, i):
...     return x + i + 1
... 
>>> from functools import partial
>>> for i in range(10):
...     globals()['add%s' % i] = partial(f, i=i)
... 
>>> add8(5)
14
>>> add3(5)
9

functools.partial implements currying in Python to create a copy of a single function definition where each copy "hardcodes" one or more of the function arguments.

Erik Cederstrand
  • 9,643
  • 8
  • 39
  • 63