0

I believe this is a similar case, however, I'm not sure how to apply the solutions to my case.

I am creating a function within a loop that is supplied by an argument. The function is called later, but it is stored for later and is supplied with the argument.

for type_str in ["type_1", "type_2", "type_3"]:
  @decorator_exec.register(...)
  def to_exec(num: int):
    print(type_str, num)

# later...
# this is an example
i = 0
for func in decorator_exec.funcs:
  func(i)
  i++

Unfortunately, this is what would be produced:

type_3 0
type_3 1
type_3 2

I would like the output to ensure that the type_str variable (and any other variables that are included in to_exec() body), be what it should be when it is defined.

type_1 0
type_2 1
type_3 2

The to_exec() that is within the loop isn't used later within the loop, or outside the loop. It is solely stored by the decorator code and is called from its stored functions later. If it isn't an XY problem, a possible solution would be to use a function factory (I'm not sure how that would work...)

Dan
  • 1
  • 2
  • Please describe the problem you are trying to solve. `for type_str in ...` does not do what you expect it would do ... – Maurice Meyer Nov 08 '21 at 19:43
  • This has nothing to do with how decorators work. It is just demonstrating that function bodies are late-binding. You can, however, leverage the fact that function parameters are early-binding with `def to_exec(num: int, type_str: str=type_str):` therefore binding the value of `type_str` to it's value at the time of definition. Actually, looking at the question you linked, that is precisely the problem you are having. – Axe319 Nov 08 '21 at 19:50
  • The reason you get `type_3` in all cases is because the refference to the vaiable `type_str` changes in each itteration and will stay with the last one which is `type_3` – mama Nov 08 '21 at 20:13
  • *"however, I'm not sure how to apply the solutions to my case."* - the solutions in that question work exactly the same way. What specific problem are you having when trying to apply them? – kaya3 Nov 08 '21 at 20:49

2 Answers2

-1

You are not storing the functions for later, you are assigning three functions to the same name, so the last one is what sticks: the previous two get overwritten by the next one.

You can achieve what you're looking for with something like this:

# This is a dictionary created with a dict comprehension.
functions = {
    # A decorator is just a function that takes a function and 
    #  returns a function, so you can call it like a normal
    #  function. The first argument is your own function,
    #  and the result will be the decorated function.
    type_str: decorator_exec.register(
        # This is an anonymous function. You don't need to give
        #  a name to the function in this case because it will
        #  be called with the dict key it's stored in anyway.
        lambda num: print(type_str, num),
        ...,  # Any arguments the decorator needs
    )
    for type_str in ["type_1", "type_2", "type_3"]
}

This will create a dictionary with one entry per string in your list, such that functions["type_1"] will have the function defined with "type_1" and so on, which you can call, for example, like this: functions["type_2"](420).

You could achieve the same by adding each entry to an empty dict using a for loop, but comprehensions are usually more idiomatic. Because of the decorator, in this case it's probably more readable to do it the wordy way (and it allows you to use type annotations too):

functions = {}

for type_str in ["type_1", "type_2", "type_3"]:
    @decorator_exec.register(...)
    def the_func(num: int):
        print(type_str, num)
    
    functions[type_str] = the_func

The key takeaway is that you cannot use the same name more than once and expect different results, so you have to store your functions in a data structure. If you don't care about the name of the functions at all, you can use a list instead of a dictionary, and simply retrieve/call each function with its index.

theberzi
  • 2,142
  • 3
  • 20
  • 34
-1

You need to keep the reference to the correct string in memory. you can do that by accessing the items with an index variable.

The 2 first lines are relevant for this.

The rest is just to provide a working example,

def generate_funcs(xs, i=0): 
    return [lambda x: print(xs[i], x)] + generate_funcs(xs, i+1) if i<len(xs) else []

f = generate_funcs(["type_1", "type_2", "type_3"])

for (func, i) in zip(f, range(len(f))):
  func(i)
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
mama
  • 2,046
  • 1
  • 7
  • 24