Attributes and properties...as I understand them...


Article.introduction

If you’ve ever had to google “What’s the difference between an attribute and a property in Python?”, well…you’re not alone—I’ve been there, squinting at the screen, flipping through docs, trying to figure out exactly why some variables are called attributes, why others are called properties, and I haven’t found a source that communicates some of the vital points of these in a way that resonated with me.

After some training research and experimenting of my own, I have developed a much better understanding than I had. In this post, I’ll try my best to clear this up as I understand it. We’ll see where attributes live, how properties act like attributes but pack a little extra punch, and how to create them in Python.


Attributes in Python

In Python, when we talk about attributes we usually mean a value attached to an object. Think “A variable that belongs to either a Class or an instance of a class.

class Car:
    wheel_count = "4" #This is a class attribute any class instanciated is going to have 4 wheels

    def __init__(self, make, model):
        self.make = make      #This is an instance attribute
        self.model = model

dreamcar_1 = Car("Porsche", "911")
prit(dreamcar_1.make) #"Porsche"

In this example:

  • We have wheel_count (conventionally we would want this to be an integer if we wanted to ever use it to do math but for now the sake of example we will leave it as a string) which is a Class Attribute. Im gonna go out on a limb here and say that we can assume most cars have 4 wheels. With that being said, every Car we instantiate will by default have 4 wheels.

  • We also have make and model, not every Car is a Porsche 911 (although that’d be pretty cool). What we CAN take from that is that every Porsche 911 is a Car. This means make and model are going to be our Instance attributes.

So in short: All Cars have 4 wheels, not all cars are the same make and model.


Properties: Like Attributes…But With Superpowers

So what’s a Property? At first glance you’d assume it’s just a misnamed attribute. You say my_object.property_name, and you get (or set, but we’ll get there) a value. The key difference here is that a property lets you run methods() behind the scenes to get, set, or delete that value.

So:

  • An attribute is basically a normal variable that is attached to your class.

  • A property is an attribute that has extra logic when you access or modify it.

So then why would I want to use a property?

Properties are great for encapsulation—when you want the rest of your code to treat something like a simple attribute, but under the hood(or trunk if were still on the 911 thing), you might want to:

  • Validate data (Make sure a name is a string that is greater than 0 letters)

  • Transform data on the fly (Make that same string upper case)

  • Log or trigger some sort of action whenever a value is changed


Creating a Property With @property

A common way to create and manage data behind the scenes is through the @property decorator. It lets you treat methods like normal attributes—perfect for adding validation or extra logic without changing how your code uses them. Here’s what it looks like using a car as an example:

class Car:
    def __init__(self, make, model, mileage=0):
        self.make = make
        self.model = model
        self._mileage = mileage  # Notice the underscore for “internal use”

    @property
    def mileage(self):
        # Called when you do car.mileage
        return self._mileage

    @mileage.setter
    def mileage(self, value):
        # Called when you do car.mileage = value
        if value < 0:
            raise ValueError("Mileage cannot be negative!")
        self._mileage = value

# Example usage:
dream_car = Car("Porsche", "911", 100)
print(dream_car.mileage)   # Accesses via @property → prints 100
dream_car.mileage = 150    # Uses @mileage.setter → sets _mileage to 150
dream_car.mileage = -10    # Raises ValueError

Breaking It Down

  1. self._mileage is just a normal instance attribute we’re treating as “private” by convention (leading underscore). By doing this, you can keep an eye on how it’s modified, so you don’t accidentally mess up your car’s data behind the scenes.

  2. @property in front of mileage() means, “Whenever someone does dream_car.mileage, we’ll quietly run this method to fetch and return _mileage.” To the outside world, it looks just like a typical attribute.

  3. @mileage.setter says, “When you do dream_car.mileage = some_value, let’s first check if everything’s cool (e.g., no negative mileage) before actually updating _mileage.” This is super handy for validating data or doing other behind-the-scenes tasks while still keeping your code clean and readable.

The end result is that dream_car.mileage acts like a regular variable, but under the hood, there’s a bit of magic going on to keep everything running smoothly. That’s the real beauty of properties!


Common Use Cases (and Pitfalls)

  1. Validation
    Like preventing a negative radius in a circle, you might want to ensure a car’s mileage is never negative. With properties, you can throw an error if someone tries to assign an invalid value.

  2. Calculation on the Fly
    Maybe you store the car’s total mileage, but sometimes you want to calculate how many miles you can drive until your next maintenance. A property could compute or transform that data as needed without requiring a separate attribute.

  3. Changing Underlying Data Representation
    If you once stored mileage in kilometers but now prefer miles (or vice versa), you can keep the property name the same—mileage—and just do the conversion behind the scenes. This way, the rest of your code doesn’t need to change.

Watch Out For…

  • Infinite recursion: If you try to do something like self.mileage = value from inside your @mileage.setter, you’ll end up calling the setter repeatedly. Always update the actual attribute (e.g., self._mileage) to avoid that loop.

  • Name collisions: Typically, you’ll name the property method the same as its underlying attribute (mileage). To prevent confusion, store the real data in a separate variable with an underscore, like self._mileage.


A Quick Comparison

FeatureAttributesProperties
DefinitionBasic data, stored as instance or class variablesEnhanced attribute with built-in getters/setters
Access Stylemy_car.makemy_car.mileage (actually calls a method behind the scenes)
Main BenefitStraightforward, easy to set and get dataControl, validation, computed values, and more
Typical SyntaxJust self.make = make in __init__@property and @prop.setter decorators

Example in Action

Let’s take a look at a simple example. We want to make sure the car’s mileage is never set to a negative number—because that would be, well, a bit odd:

class Car:
    def __init__(self, make, model, mileage=0):
        self.make = make
        self.model = model
        self._mileage = mileage  # Using an underscore to indicate "internal" usage

    @property
    def mileage(self):
        return self._mileage

    @mileage.setter
    def mileage(self, value):
        if value < 0:
            raise ValueError("Mileage cannot be negative!")
        self._mileage = value

# Usage
my_car = Car("Porsche", "911", 100)
print(my_car.make)      # "Porsche"
print(my_car.model)     # "911"
print(my_car.mileage)   # 100

my_car.mileage = 150    # Valid update
print(my_car.mileage)   # 150

my_car.mileage = -10    # Raises ValueError

Here, we’re “secretly” checking the incoming mileage to ensure it isn’t negative before we store it in _mileage. To the rest of our code, my_car.mileage looks and behaves just like a plain old attribute—but we’re adding a layer of validation behind the scenes.


Conclusion

  • Attributes are regular variables on your instance or class—no special behavior when you set or get them.

  • Properties are enhanced attributes that look normal, but actually call methods behind the scenes, letting you do validation, run calculations, or log activity each time they’re accessed or modified.

Properties are a great way to keep your code clean. You get the ease of object.attribute syntax while still having the power of method calls. That’s why you’ll often see them in Python code: they allow you to encapsulate functionality without sacrificing readability.

Now that you’ve seen how attributes vs. properties can work in a simple car example, I hope they feel a bit less mysterious. And if it’s still fuzzy, remember: I’m sharing this as I understand it. Keep tinkering with them, experiment with getters and setters, and before you know it, it’ll all click. Happy coding!