Back to all posts

Advance Python Concept

The @property decorator in Python is used to make a method behave like an attribute . It allows you to define getters, setters, and deleters in an elegant …

The @property decorator in Python is used to make a method behave like an attribute. It allows you to define getters, setters, and deleters in an elegant and Pythonic way.


1. Why use @property?

  • Normally, in object-oriented programming, you hide internal data and access it using get_ and set_ methods.

Example without @property:

Python
class Person:
    def __init__(self, age):
        self._age = age

    def get_age(self):      # Getter
        return self._age

    def set_age(self, value): # Setter
        if 0 <= value <= 150:
            self._age = value
        else:
            print("Invalid age!")

p = Person(30)
print(p.get_age())  # 30
p.set_age(40)       # Update age
print(p.get_age())  # 40

Problem: Calling get_age() and set_age() every time is not Pythonic.


2. Using @property (Pythonic way)

Python
class Person:
    def __init__(self, age):
        self._age = age  # Internal storage

    @property
    def age(self):  # Getter
        return self._age

    @age.setter
    def age(self, value):  # Setter with validation
        if 0 <= value <= 150:
            self._age = value
        else:
            print("Invalid age!")

Usage:

PHP
p = Person(30)
print(p.age)  # ✅ Looks like attribute access (calls getter)
p.age = 40    # ✅ Looks like attribute assignment (calls setter)
p.age = -5    # Prints "Invalid age!"

Output:

Plain Text
30
40
Invalid age!

3. How @property Works Internally

  1. @property converts the age() method into a getter property.
  2. @age.setter links a setter method to the same property name.
  3. Accessing p.age → Calls the getter.
  4. Assigning p.age = value → Calls the setter.

4. Advantages

  1. Cleaner syntax – Access attributes like normal variables.
  2. Encapsulation – Control how attributes are read/written.
  3. Validation – Add checks before updating a value.
  4. Backward compatibility – You can add properties later without breaking existing code.

5. Full Property Example

Python
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):          # Getter
        return self._radius

    @radius.setter
    def radius(self, r):       # Setter
        if r > 0:
            self._radius = r
        else:
            print("Radius must be positive!")

    @property
    def area(self):            # Read-only property
        return 3.14 * (self._radius ** 2)

c = Circle(5)
print(c.radius)   # 5
c.radius = 10     # Updates radius
print(c.area)     # 314.0 (computed dynamically)
c.radius = -3     # Invalid

Key Points

  • @property = Getter
  • @<property>.setter = Setter
  • @<property>.deleter = Optional, to delete the attribute
  • Makes your class clean, safe, and Pythonic.

Access Modifiers

Python में Access Modifiers का concept होता है, लेकिन ये Java / C++ जैसे strict नहीं होते
Python में कोई private, public, protected keyword नहीं होते।
इसके बदले हम naming conventions और name mangling पर depend करते हैं।


1. Types of Access Modifiers in Python

Python में 3 प्रकार के Access Levels follow किए जाते हैं:

TypeConventionExampleAccess Rule
Publicकोई underscore नहींself.ageकहीं से भी access कर सकते हैं
ProtectedSingle _self._ageConvention: "Internal use only", पर फिर भी access कर सकते हैं
PrivateDouble __self.__ageName Mangling होता है, accidental access से बचाता है

2. Public Attributes (Default)

  • Python में default सब कुछ public होता है।
  • कहीं से भी access कर सकते हैं।
Python
class Person:
    def __init__(self):
        self.age = 30  # Public

p = Person()
print(p.age)  # ✅ 30
p.age = 40    # ✅ Update allowed

3. Protected Attributes (_)

  • Single underscore _ conventionally बताता है कि यह internal use के लिए है।
  • Python इसे रोकता नहीं है, सिर्फ developers को hint देता है।
Python
class Person:
    def __init__(self):
        self._age = 30  # Protected

p = Person()
print(p._age)  # ⚠️ Possible but not recommended (30)
p._age = 40    # ⚠️ Possible but not recommended
print(p._age)  

4. Private Attributes (__)

  • Double underscore __ से शुरू होने वाले attributes name mangling use करते हैं।
  • Python internally attribute का नाम _ClassName__AttributeName कर देता है।
Python
class Person:
    def __init__(self):
        self.__age = 30  # Private

p = Person()
print(p.__age)        # ❌ AttributeError
print(p._Person__age) # ✅ 30 (Name mangled access)
  • इसका use accidental access से बचाने के लिए होता है।
  • फिर भी आप चाहे तो _ClassName__var लिखकर access कर सकते हैं।

Python philosophy:

“We are all consenting adults here.”
मतलब Python में strict restriction नहीं होती, devs पर trust किया जाता है कि वो rules follow करेंगे।

Decorators in Python

What is a Decorator?

  • A decorator is like a helper that adds extra features to a function without changing its code.
  • It takes a function as input and gives back a new function with extra work.
Python
def my_decorator(func):
    def wrapper():
        print("Before function")
        func()
        print("After function")
    return wrapper


def say_hello():
    print("Hello!")

    
say_hello()

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
Python
# Decorator with Function Arguments
def smart_divide(func):
    def wrapper(a, b):
        if b == 0:
            print("❌ Cannot divide by zero")
            return
        return func(a, b)
    return wrapper

@smart_divide
def divide(a, b):
    print(a / b)

divide(10, 2)  # 5.0
divide(5, 0)   # ❌ Cannot divide by zero

Python में *args और **kwargs इसलिए use करते हैं ताकि decorator हर type के function handle कर सके, चाहे उसमें कितने भी arguments हों।

Python
def demo(*args):
    print(args)

demo(1, 2, 3) # output of tuple: (1, 2, 3)


def demo(**kwargs ):
    print(kwargs)

demo(name="Himanshu", age=25) # output of dict: {'name': 'Himanshu', 'age': 25}

Multiple Decorators

Python
def uppercase(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

def exclaim(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) + "!!!"
    return wrapper

@exclaim
@uppercase
def greet():
    return "hello"

print(greet())  # HELLO!!!

Class Decorators

Decorators can also be applied to classes:

Python
def add_repr(cls):
    cls.__repr__ = lambda self: f"{self.__class__.__name__}({self.__dict__})"
    return cls

@add_repr
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Himanshu", 25)
print(p)  # Person({'name': 'Himanshu', 'age': 25})

Summary

Common Uses of Decorators:

  1. Logging – Keep track of when a function runs and what arguments it got.
    Example: Record each time someone calls your function.
  2. Timing – Measure how long a function takes to finish.
    Example: Check performance of slow functions.
  3. Authentication & Authorization – Verify if the user is allowed to use a function.
    Example: Only admin can delete data.
  4. Caching – Save the result of a function so next time it’s faster.
    Example: Don’t calculate the same thing again and again.
  5. Rate Limiting – Control how many times a function can be used.
    Example: Prevent a user from clicking a button too often.
  6. Input Validation – Automatically check if the input values are correct.
    Example: Ensure age is a positive number before running the function.
  7. Instrumentation / Monitoring – Collect data about function usage.
    Example: Count how many times a function is used or check its performance.

Frameworks like Flask and Django use decorators extensively for routing, authentication, and defining middleware.

Keep building your data skillset

Explore more SQL, Python, analytics, and engineering tutorials.