Minimum Example
The following ME might be a bit too minimum, because it can be solved in a different way which wouldn't solve my real problem. I start with this ME anyway because I feel this boils down the question to the point where I lack understanding of Python.
Assume I want to have a factory function for creating a bunch of dynamic functions. I have reduced my problem down to the following minimum example:
def dynamic_functions(constants):
d = {}
for c in constants:
d[c] = lambda x: c * x
return d
I now get the following:
>>> fn_2 = dynamic_functions(constants=[2, 3])[2]
>>> fn_2(10) # I would want to get `20`
30
I came to understand that this is because the variable c
within the lambda function is not evaluated during the dict comprehension, but at the time when the function is called. At that point c
evaluates to 3
, because this is the last value it has had in the for-loop. (This does not change if I turn the lambda function into named function definitions, btw.) This understanding is backed by the fact that the adding a del c
right after the for-loop like this:
def dynamic_functions(constants):
d = {}
for c in constants:
d[c] = lambda x: c * x
del c # NB that this line has been added.
return d
raises NameError: free variable 'c' referenced before assignment in enclosing scope
.
How can I circumvent this problem and create a factory which does what I want, i.e. dynamically creates functions with arbitrary but fixed constants?
My Real Problem
Of course, the minimum example itself could be solved by simply writing a function that takes two parameters, c
and x
. But I want to solve what I understand to be a scoping issue, because in my real use case I want to define properties on a factored class based on the parameters passed to the factory. So my use case is more complex and is not open to what would be that easy fix of the above ME. (Or is it? I would be glad to learn how.)
So here is my real problem:
def Structure(params):
properties = {
param_name: property(
fget=lambda self: getattr(self, f"_{param_name}"),
fset=lambda self, value: setattr(self, f"_{param_name}", value)
)
for param_name in params
}
def __init__(self, **kwargs):
for parameter in params:
setattr(self, f"_{parameter}", kwargs.get(parameter))
methods = {
"__init__": __init__
}
_Structure = type(
"some_name", (),
{
**methods,
**properties
}
)
return _Structure
>>> Struct = Structure(params=["x", "y"])
>>> struct = Struct(x=1, y=2)
>>> struct._x
1 # That's correct
>>> struct.x
2 # That is actually the value of struct._y
This is still a reduced code version of my problem, my real use case has more complex getters and setters.
How can I circumvent this problem and create a factory which does what I want, i.e. dynamically creates classes with dynamic properties?