Back to Python
2026-03-157 min read

Object Properties (Python Programming)

Learn Object Properties (Python Programming) step by step with clear examples and exercises.

Title: Object Properties in Python Programming - A full guide

Why This Matters

Understanding object properties is crucial for mastering Python programming, as it allows you to work efficiently with data structures and write more flexible, maintainable code. This concept comes into play during real-world scenarios such as debugging complex applications, optimizing performance, and preparing for interviews or exams.

With a solid grasp of object properties, you'll be able to:

  1. Debug and understand the behavior of objects in your code more effectively.
  2. Optimize performance by leveraging built-in properties like __len__() and __getitem__().
  3. Write more secure and maintainable code by implementing custom getters, setters, and validation rules using descriptors.
  4. Prepare for interviews or exams by demonstrating your understanding of Python's object model and its built-in properties.

Prerequisites

Before diving into object properties, make sure you have a solid grasp of the following topics:

  1. Python basics (variables, data types, operators)
  2. Control structures (if-else statements, loops)
  3. Functions and modules
  4. Data structures (lists, tuples, dictionaries)
  5. Classes and objects in Python

Core Concept

What are Object Properties?

In Python, an object is any entity that has a type and can be manipulated using operations defined for that type. Every object in Python has built-in properties, which provide information about the object itself or allow you to perform certain actions on it. User-defined properties (descriptors) can also be created to extend the functionality of objects.

Built-in Properties

Some common built-in properties of objects include:

  1. __class__: Returns the class (type) of the object.
  2. __dict__: Provides a dictionary-like interface for accessing an object's attributes and methods.
  3. __dir__(): Returns a list of an object's attributes, methods, and other properties.
  4. __len__(): Returns the length of an object (e.g., the number of elements in a list or dictionary).
  5. __str__(): Returns a string representation of an object when it is converted to a string using the str() function or printed with the print() statement.
  6. __repr__(): Returns a more detailed, unambiguous string representation of an object that can be used to recreate the original object using the eval() function.
  7. __hash__(): Returns a hash value for an object, which is useful when working with sets and dictionaries.
  8. __delattr__(): Deletes an attribute from an object.
  9. __getattr__(): Called when an attribute of an object does not exist, allowing you to define a default value or behavior for such cases.
  10. __setattr__(): Called when an attribute is assigned a value for the first time or when an existing attribute is modified.
  11. __getattribute__(): Called whenever an attribute of an object is accessed, allowing you to intercept and manipulate attribute access.
  12. __iter__(): Returns an iterator for an object that can be used in for loops or with the next() function.
  13. __contains__(): Defines how the in operator behaves when checking if an object contains a specific value.
  14. __bool__(): Determines whether an object is considered "true" or "false" in a boolean context (e.g., when used in conditional statements).

User-Defined Properties (Descriptors)

In addition to built-in properties, you can create your own user-defined properties using descriptors. Descriptors allow you to define custom behaviors for attributes of your classes, such as automatically modifying their values or implementing custom getters and setters.

Types of Descriptors

  1. Data Descriptors: Attributes with a __get__() method are data descriptors, which means they can be accessed using the dot notation (e.g., obj.attribute) and appear in an object's __dict__.
  2. Non-Data Descriptors: Attributes without a __get__() method are non-data descriptors, which do not appear in an object's __dict__ and can only be accessed using the obj.__getattribute__("attribute") syntax or by calling their __get__() method directly.

Worked Example

Let's create a simple class with a custom property that keeps track of the number of times an attribute is accessed:

class AccessCounter:
def __init__(self, initial_value=0):
self._value = initial_value
self.__access_count = 0

def __getattr__(self, name):
self.__access_count += 1
return f"Accessed {name} ({self.__access_count} times)"

class MyClass(AccessCounter):
def __init__(self):
super().__init__()
self.data = None

my_obj = MyClass()
print(my_obj.data) # Accessed data (1 time)
print(my_obj.data) # Accessed data (2 times)

In this example, we define a AccessCounter class with a custom __getattr__ method that increments an internal counter every time an attribute is accessed. We then create a subclass MyClass that inherits from AccessCounter and defines a new attribute data. When we try to access the data attribute of an instance of MyClass, the custom behavior defined in __getattr__ is invoked, displaying the number of times the attribute has been accessed.

Common Mistakes

  1. Forgetting to define a default value for a user-defined property (descriptor) when it does not exist.
  2. Using built-in properties like __dict__ or __dir__() without understanding their behavior and potential side effects.
  3. Assuming that accessing an attribute of an object will always return the expected result, without considering the possibility of exceptions or custom behaviors defined in a class's __getattr__ method.
  4. Ignoring the difference between __str__() and __repr__(), leading to confusing or incorrect string representations of objects.
  5. Failing to implement proper accessor (getter) and mutator (setter) methods for sensitive data in classes, potentially exposing private information.
  6. Not properly implementing descriptors, leading to unexpected behavior when working with class attributes.
  7. Misusing or overusing descriptors, leading to complex, hard-to-understand code.

Subheadings under Common Mistakes:

  1. Incorrect use of data and non-data descriptors
  2. Forgetting to define __set__() for mutable properties
  3. Implementing inefficient or unnecessary custom behaviors
  4. Failing to properly document custom properties and their behavior
  5. Ignoring the impact of descriptors on inheritance and class composition

Practice Questions

  1. Write a class that defines a custom property __my_property that increments an internal counter every time it is accessed. The property should return the current count when accessed and reset to 0 after being assigned a value.
  2. Create a class Rectangle with properties width and height. Implement a custom property area that calculates and returns the rectangle's area (width * height) using a getter method. Also, implement a setter method for the width property that ensures it is always greater than or equal to 1.
  3. Write a class Counter with a user-defined property count. Implement a custom property incr that increments the count by a specified value when called (e.g., my_counter.incr(5) should increase the count by 5).
  4. Define a class Person with properties name, age, and gender. Implement a custom property info that returns a string containing all three properties in the format "Name: [name], Age: [age], Gender: [gender]".

FAQ

What is the difference between __str__() and __repr__()?

  • __str__() returns a user-friendly string representation of an object, while __repr__() provides a more detailed, unambiguous representation that can be used to recreate the original object using eval().

Can I modify built-in properties like __dict__ or __dir__() directly?

  • It is generally not recommended to modify built-in properties directly, as it may lead to unexpected behavior and conflicts with Python's internal mechanisms. Instead, consider using custom methods or descriptors if you need to extend or manipulate an object's properties.

How can I access a non-existent attribute of an object in Python?

  • When trying to access a non-existent attribute of an object, Python will first check for the existence of that attribute using the __getattr__ method (if defined). If no such method exists and the attribute does not exist, you'll receive an AttributeError.

What is the purpose of descriptors in Python?

  • Descriptors allow you to define custom behaviors for attributes of your classes, such as automatically modifying their values or implementing custom getters and setters. This can help make your code more flexible, maintainable, and secure by encapsulating sensitive data and enforcing validation rules.

How do I create a data descriptor in Python?

  • To create a data descriptor, define a class with a __get__() method that takes an object (the instance of the class) and a name (the attribute's name) as parameters. The __get__() method should return a descriptor object, which can be used to access or modify the attribute's value.

How do I create a non-data descriptor in Python?

  • To create a non-data descriptor, define a class without a __get__() method and implement the desired behavior using other built-in properties or methods like __getattr__(), __setattr__(), or custom methods. Non-data descriptors do not appear in an object's __dict__.

What are some best practices for using descriptors in Python?

  • Some best practices for using descriptors include:
  • Keeping descriptor code as simple and concise as possible to avoid confusion and potential bugs.
  • Documenting custom properties and their behavior to help other developers understand your code.
  • Using descriptors judiciously, avoiding unnecessary complexity or overuse that can make your code harder to read and maintain.
  • Ensuring that descriptor behavior is consistent with the expectations of users accessing or modifying the attributes they represent.
Object Properties (Python Programming) | Python | XQA Learn