-1

I would like to have a function that allows generating classes with custom class attributes. Like so:

def test_factory(a):
    class Test:
        a = a
    return Test

However, when I try to call test_factory, I get an error:

test_factory(1)
> NameError: name 'a' is not defined

The expected behaviour would be:

t1 = test_factory(1)
t2 = test_factory(2)
print(t1.a, t2.a)
> 1, 2

How can I create classes that differ in their class attributes by calling a function?

Arco Bast
  • 3,595
  • 2
  • 26
  • 53
  • 1
    Seems like you're coming from JS. More pythonic way would be to use a dictionary – Maxxik CZ Apr 27 '20 at 13:46
  • 1
    there is this [link](https://stackoverflow.com/questions/4296677/creating-a-class-within-a-function-and-access-a-function-defined-in-the-containi) I found. It might be worth a read – Maxxik CZ Apr 27 '20 at 13:50
  • Note that you would get a similar error if ``Test`` were a function instead of a class. [Name resolution rules](https://docs.python.org/3/reference/executionmodel.html#resolution-of-names) in Python cause every assignment to imply the variable is local to that block. – MisterMiyagi Apr 27 '20 at 14:08
  • @MaxxikCZ thanks - that link indeed was worth a read. – Arco Bast Apr 27 '20 at 18:00

2 Answers2

2

You have to rename the function argument to not collide with the name of the class attribute:

def test_factory(b):
    class Test:
        a = b
    return Test

>>> t1 = test_factory(1)
>>> t2 = test_factory(2)
>>> print(t1.a, t2.a)
1 2
user2390182
  • 72,016
  • 6
  • 67
  • 89
1

When parsing the class statement, the assignment to a defines it as part of the temporary class namespace, similar to an assignment to a local variable in a function definition. As such, the name a then shadows the name of the parameter in the enclosing function scope.

You can either change the parameter name (as shown by schwobaseggl)

def test_factory(a_value):
    class Test:
        a = a_value
    return Test

or set the attribute after the definition:

def test_factory(a):
    class Test:
        pass
    Test.a = a
    return Test

or call type directly:

def test_factory(a):
    return type('Test', (), {'a': a})
chepner
  • 497,756
  • 71
  • 530
  • 681