In python it is valid to use an import
statement inside a class to define class variables that are just stolen from other modules:
class CustomMath:
from math import pi
assert isinstance(CustomMath.pi, float) # passes
It is also valid to refer to a global variable to the class with the very confusing statement x=x
x = 1
class Test:
# this loads the global variable x and stores in in the class scope
x = x
assert Test.x == 1 # passes
However if I am using a function to generate classes is there a similar way to copy a variable from the function arguments to the class body?
I want something of this nature:
def generate_meta_options(model, design):
class Meta:
# copy the nonlocal variable model to this scope
# but this isn't valid syntax
model = (nonlocal model)
design = translate_old_design_spec_to_new_format((nonlocal design))
return Meta
The parameter name matters because it is being passed by keyword not by order generate_meta_options(design="A", model=Operation)
and the name in the class has to be the same, I have found a work around by making variable aliases to all of the inputs but this is kind of annoying
def generate_meta_options_works(model, design):
_model = model
_design = design
class Meta:
model = _model
design = translate_old_design_spec_to_new_format(_design)
return Meta
Is there a better way of achieving this using the nonlocal
keyword? It seems that python does allow nonlocal
and global
to be used in a class but the variables are not retained in the class so I can't imagine a use case for it.
gl = 'this is defined globally'
def nonlocal_in_class_test():
lo = 'this is defined locally'
class Test:
nonlocal lo
global gl
lo = 'foo'
gl = 'bar'
assert lo == 'foo' # passes
assert gl == 'bar' # passes
assert hasattr(Test, 'lo') or hasattr(Test, 'gl'), 'lo nor gl was put in the class scope' # fails
I'm sure the library I'm using just needs an object with attribute lookup so I could move away from classes but every example I've seen uses classes (I suspect because inheriting multiple configs is natural) so I am hesitant to go that route.
It seems really odd that it is easier to copy a variable from a different module than it is to copy one right in the above scope so I thought I'd ask, is there a way to copy a variable from a nonlocal
scope into a class attribute without creating a separate variable with a distinct name?
As a tangent is there any case where using the nonlocal
or global
keywords inside a class body is useful? I assume it is not useful but there is also no reason to do extra work to disallow it.
CodePudding user response:
nonlocal
wouldn't work in any event, because variables have only one scope in the case where nonlocal
applies (function locals, which are subtly different from class definition scope); by trying to use nonlocal
, you'd say model
was never part of the class definition scope, just something from outside it.
I personally prefer your kinda hacky reassignment so _model
outside the class and model
inside the class don't conflict, but if you hate it, there is an option to directly access the class-in-progress's namespace, vars()
(or locals()
; the two are equivalent in this case, but I don't think of the class scope as being locals, even though they act a lot like it).
Because the scope is not really a function scope, you can in fact mutate it through the dict
from vars
/locals
, allowing your desired result to look like:
def generate_meta_options(model, design):
class Meta:
vars().update(
model=model,
design=translate_old_design_spec_to_new_format(design)
)
return Meta
Using the keyword argument passing form of dict.update
means the code even looks mostly like normal assignment. And Python won't complain if you then use those names earlier (seeing outer scope) or later (seeing newly defined names) in the class definition:
def generate_meta_options(model, design):
print("Before class, in function:", model, design) # Sees arguments
class Meta:
print("Inside class, before redefinition:", model, design) # Sees arguments
vars().update(
model=model,
design=design 1
)
print("Inside class, after redefinition:", model, design) # Sees class attrs
print("After class, in function:", model, design) # Sees arguments
return Meta
MyMeta = generate_meta_options('a', 1)
print(MyMeta.model, MyMeta.design)