Why does this work in Python? Creating a instance, setting a non existent attribute in that class, then printing that attribute through the instance

Viewed 305

I was watching a Youtube tutorial about python classes.

This is the code he wrote.

class Tweet :
  pass

a = Tweet()

a.message = '140 chars'

print (a.message)

# This works

print(Tweet.message)

# This doesn't

I understand why printing tweet.message doesn't work (or at least I think I do if its as straightforward and simple as it seems) but why does printing a.message does? I'm assuming this is something to do with how python works but what is it?

Actually now that I'm thinking about it, maybe I have it wrong. I thought Tweet.Message doesnt print because tweet does have a message you can set. But does python automatically create the ability to do a.message ? So the reason tweet.message doesnt print it because that is only a blueprint, you can only retrieve data from instances, not the actual class itself?

2 Answers

Python allows dynamically adding new attributes to an object, even if it's not "declared" in the class signature, or assigned during object initialization in the __init__ method.

When you execute the assignment a.message = "...", the message attribute is added only to the a object, but not to the Tweet class, nor to any other objects with the same type. If you have another object b of Tweet type and you try to access its message attribute directly, you would get an AttributeError.

Here's a comprehensive example:

class Tweet:
  def __init__(self):
    self.user = "default_user"

a = Tweet()
print(a.user)     # "default_user"
a.message = "140 chars"
print(a.message)  # "140 chars"

b = Tweet()
print(b.user)     # "default_user"
print(b.message)  # AttributeError: 'Tweet' object has no attribute 'message'

print(Tweet.user)     # AttributeError: type object 'Tweet' has no attribute 'user'
print(Tweet.message)  # AttributeError: type object 'Tweet' has no attribute 'message'

Tweet.message looks for an attribute named message on the class Tweet, which doesn't exist.

a.message looks for an attribute named message on the *instance of Tweet, which does exist. (If it did not, it would fall back to looking for an attribute on the type of a.)

a.message = "140 chars" always sets the value of an attribute (creating it, if necessary) on the object a. It would never change the value of a class attribute, if it existed. For example,

class Tweet:
    length = 140

t = Tweet()
t.length = 19

assert Tweet.length == 140
assert t.length == 19

The Python language provides no way to declare what attributes an instance of a class will have. All instance attributes are created by assignments like t.length = 19. Usually, you create the attributes inside the definition of __init__, so that they are all created in one place and immediately after the instance is created. Thus, rather than

class Tweet:
    pass

t = Tweet()
t.message = "140 chars"

you write

class Tweet:
    def __init__(self):
        self.message = "140 chars"

t = Tweet()

There is no real difference between the assignments t.message = "140 chars" and self.message = "140 chars", beyond when, exactly, they are executed and the name used to refer to the object.

Related