shmee
shmee

Reputation: 5101

Name assignment and reuse in python namespace

I am currently trying to figure out the internals of mappingproxies and namespaces in python. While testing a bit, I came accross some behavior I did not expect.

I define a class, instantiate it, define another one under the same name and instantiate that one.

class A:
    color = 'white'
    def __init__(self, val):
        self.val = val

a1 = A(1)

class A:
    color = 'black'
    def __init__(self, val):
        self.val = val

a2 = A(2)

Now, a1 and a2 are instances of different class objects that both still exist. In the namespace, the second, altered version of the class is now assigned to the name A. The first, initial version of the class object can only be addressed via its instance's __class__ attribute. Still, you can fully interact with the old class object.

print(a1.__class__ is a2.__class__)     # False
print(a1.__class__ is A)                # False
print(a2.__class__ is A)                # True
a3 = a1.__class__(3)
print(a3.__class__ is a1.__class__)     # True
print(a3.color)                         # white
a3.__class__.color = 'red'
print(a1.color)                         # red

I am guessing that pythons's object reference counter is responsible for the fact that the old class object still exists, because the counter was not zero as a1 was still holding a ref, when the new class object was assigned to the name A.

As for my question: Is this intended behavior? If so, what is the reasoning behind it? For me, this looks a bit too implicit. I would have expected this to fail and throw some exception, tbh.

EDIT

To explain why this strikes me and as an answer to Daniel Roseman in the comments below:

Both, a1 and a2 consider themselves instances of __main__.A, when in fact one of them is an instance of what used to be __main__.A. That leaves me with an active, usable object that has no obviously defined handle in my namespace. I think this is exclusive to class objects due to their class-object duality.

I am working on some tool that dynamically builds classes and executes command sequences on instances of them, all based on some strange input. That involves a lot of dynamic imports. Not knowing about this behavior, I could end up debugging classes and their build process when I should actually be looking into some threading issue.

Upvotes: 3

Views: 386

Answers (1)

A_S00
A_S00

Reputation: 223

Is this intended behavior?

Yes.

What is the reasoning behind it?

As Daniel Roseman is getting at in the comments above, this is a specific case of the general rule that everything in Python is an object.

Consider the following code snippet:

some_string_a = 'white'
some_string_b = 'black'

a = some_string_a
print(a)

    >>> white

a = some_string_b
print(a)

    >>> black

print(some_string_a)

    >>> white

I'm sure you're not surprised that you can still interact with some_string_a, even though a variable name that used to refer to it has been reassigned and now refers to something different.

But, since the two classes in your question are also objects, it's an analogous case. Why shouldn't you be able to interact with an object (the first class you defined), just because you've reassigned a variable name (A) such that it now refers to a different object?

As you mention in your edit, this can lead to some namespace weirdness...but that's the price that Python pays for the gains it gets in consistency by treating everything as an object.

Upvotes: 2

Related Questions