An attribute in Python: the data an object carries

An attribute is a piece of data attached to an object and reached after the dot. It says what the object carries, where a method says what it can do.
5 min read
Believemy logo

Definition

A program rarely handles a value on its own. A bank account is not a number: it is an owner, a balance, a currency. Kept in separate variables, they have to travel together into every function that touches the account, and nothing stops one client's balance from being mixed up with another client's name.

An attribute settles that nuisance by pinning the data onto the object. It is a name reached by writing a dot followed by that name, and it belongs to one particular object: where a variable lives alone in its namespace, an attribute follows its object everywhere.

PYTHON
class Account:
    def __init__(self, owner):
        self.owner = owner       # two attributes set
        self.balance = 0         # as the object is created

account = Account("Ada")
print(account.owner)      # Ada: reading an attribute
account.balance = 250     # writing into the attribute

The dot triggers a search, not a plain read. Python looks for the name on the object, then on its class, then on the classes it inherits from. That climb is what makes methods, written only once on the class, available on every object.

When the name turns up nowhere, Python raises an AttributeError naming the missing attribute and the real type of the object. That last point is often the more useful one: it reveals you were not holding the object you thought you held.


An attribute, a method, and what separates them

Both are written after a dot, which blurs the line more often than people expect. There is nonetheless one category: a method is an attribute whose value can be called. Everything following a dot is an attribute, functions included.

The table below places four writings side by side that look almost identical: what separates them is not the dot, it is the parentheses.

WritingWhat Python does with it
account.balanceHands back the value stored under that name
account.creditHands back the function itself, without running it
account.credit(50)Runs the function, the object as first parameter
account.balance()Tries to call a number, and raises a TypeError

Forgetting the parentheses does not crash on the spot, and that is what makes the mistake expensive: a function is an ordinary value, one Python stores without objecting. So the program carries on with a function where it expected a result, and the failure surfaces much further along, as a comparison that never matches or a display starting with <bound method.


Instance attribute and class attribute

That leaves the question of where to write an attribute: the two possible places do not give the same result.

An attribute set inside __init__ through self belongs to one instance and to that one alone: every object gets its own copy, and changing it concerns nobody else.

An attribute written in the body of the class belongs to the class, and therefore to all of its instances at once: only one copy of it exists.

PYTHON
class Account:
    currency = "EUR"        # class attribute, shared: intended
    operations = []         # class attribute, shared: trap

    def __init__(self, owner):
        self.owner = owner           # instance attribute, one per object

On currency, the sharing is what you want. On operations, it is accidental: since the list is mutable, every account appends its operations into it and the histories blend together, with an unmistakable symptom, a brand new object that arrives already full.

Warning

A mutable class attribute, list or dictionary, stays invisible while a single object exists: the flaw appears once several of them coexist, which means in production. The fix fits in one line, build the structure inside __init__.

Writing, on the other hand, never travels back up to the class: account.currency = "USD" creates an instance attribute which hides the class one, and changes nothing for the other accounts. Remember the asymmetry, reading can come from the class, writing always lands on the instance.


Handling them when the name varies

The dot demands a name written literally, decided at the moment you type the code. Yet it sometimes arrives from a form or a configuration file, and is known only at run time. Three functions then take over: getattr to read, setattr to write, hasattr to check for presence.

PYTHON
field = "balance"
getattr(account, field)             # 250, same as account.balance
getattr(account, "rate", 0)         # 0: fallback value, no error
setattr(account, field, 300)        # same as account.balance = 300
hasattr(account, "owner")           # True

The third argument of getattr deserves a mention: it supplies a fallback value and avoids the exception instead of catching it, which keeps the code readable on optional fields.

To inspect an object whose shape is unknown, vars(account) hands back the dictionary of its instance attributes, and dir(account) the full list of reachable names, inherited ones included. In the REPL, they are faster than a search through the documentation.


Frequently asked questions

Question

Can an attribute be added after the object is created?

Yes, at any moment, and that is what makes a typo silent: account.balnce = 250 reports nothing, it creates one more attribute while the real balance stays at zero. Declaring the expected fields with dataclass, or setting __slots__, closes that door.

Question

How can an attribute be made private?

Python offers no genuine protection, and that is a deliberate choice. One leading underscore signals a convention the rest of the code is expected to honour, without forbidding anything. Two underscores complicate access from outside without closing it. To really control reading and writing, go through a property.

Question

Why does my attribute hold something other than what I put in it?

In the vast majority of cases, two objects share the same mutable value, coming from a class attribute or from a function default. Compare id() on the attribute of both objects: an identical number means a single value, changed from two places.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.