max
max

Reputation: 52235

python: confusion with local class name

I have the following code:

def f():
    class XYZ:
        # ...
    cls = type('XXX', (XYZ, ), {})
    # ...
    return cls

I am now using it as follows:

C1 = f()
C2 = f()

and it seems to work fine: C1 is C2 returns False, there's no conflict between the class attributes of the two classes, etc.

Question 1

Why is that? How is it possible that C1 and C2 are both shown as class

<'__main__.XXX'>

and yet not the same class?

Question 2

Is there some problem with the fact that I have two identical names for two different classes?

Question 3

I would like to be able to write instead:

f('C1')
f('C2')

with the same effect. Is it possible?

Question 4

If I want C1 to look like a regular class, not main.XXX, is it ok to say:

C1.__name__ = '__main__.C1'

Upvotes: 1

Views: 458

Answers (2)

aaronasterling
aaronasterling

Reputation: 70994

Question 3

To have cls.__name__ be anything you want, (with a nod to delnan's suggestion)

def f(clsname):
    class XYZ:
        # ...
    XYZ.__name__ = XYZ
    # ...
    return XYZ

Question 1

The reason that c1 is not c2 is that they are two different objects stored at two different locations in memory.

Question 4

Try an answer to question 1 and see how it works out for you

Question 2

It can complicate debugging that their class attributes __name__ share a common value and this is bad enough to take pains to avoid. (see question 3). I would maintain though that they don't have the same name. One is named C1 and the other is named C2 (at least in the scope you are showing. If you were to pass them to a function, then there name in that scope would be the same as the name of parameter that they were passed through)

In fact, I'm so sure that they don't have the same name that trying to tell me otherwise is likely to cause me to turn the music up louder and pretend I can't hear you.

In response to comment

It can be done but it's just wrong. I'll illustrate anyway because it's illuminating:

def f(clsname):
    class XYZ(object):
        pass
    XYZ.__name__ = clsname
    globals()[clsname] = XYZ

f('C1')
f('C2')

print C1
print C2

This just works by sticking the class in the globals dict keyed by clsname. But what's the point? You can stick it in the globals dict under any name in fact because this is just another assignment. You are best off just returning the class from the function and letting the caller decide what name to give the class in it's own scope. You still have the __name__ attribute of the class set to the string you pass to the function for debugging purposes.

Upvotes: 2

user395760
user395760

Reputation:

Actually, you don't need to the cls = ... line at all.

>>> def f():
...     class C:
...         pass
...     return C
... 
>>> f() is f()
False

Reason: class (as well as e.g. def) defines a new class each time it is encountered = each time the function is called.

As for cls.__name__, it's really no semantic difference. The name is useful for debugging (you don't expose it directly to the user, do you?) and introspection, but it shouldn't be an issue. But if you absolutely want to have different names, you can change cls.__name__ before returning (also note that after C.__name__ = 'foo', C.__name__ == '__main__.foo'!).

At question 3: It would be possible to inject it directly into global namespace... don't do this. It has no advantages, only disatvantages: nonobvious side effects, bad style, the fact it's a hack at all, etc!

Upvotes: 2

Related Questions