Wednesday, June 10, 2026

Singleton Example in Python


All Questions From This Chapter    « Previously    Next »

can you explain this line by line in layman terms:

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

db1 = Database()
db2 = Database()
assert db1 is db2




Yes — this code is a Singleton pattern, which means it makes sure only one object of a class is ever created.

Here is the line-by-line meaning in simple terms:

Python
class SingletonMeta(type):

This creates a custom metaclass.
A metaclass is like a “class for classes” — it controls how classes behave when they are created or called.

Python
    _instances = {}

This is a dictionary used to store created objects.
It keeps track of whether an object for a class has already been made.

Python
    def __call__(cls, *args, **kwargs):

This defines what happens when you do something like Database().
Normally, calling a class creates a new object. Here, we are changing that behavior.

  • cls means the class being called, like Database

  • *args and **kwargs mean any arguments passed to the class

Python
        if cls not in cls._instances:

This checks: “Has an object of this class already been created?”

  • If not, create one

  • If yes, reuse the old one

Python
            cls._instances[cls] = super().__call__(*args, **kwargs)

This creates the object in the normal way and stores it in _instances.

So the first time you call Database(), it makes a real object and saves it.

Python
        return cls._instances[cls]

This returns the stored object every time.

So after the first creation, every later call gives back the same object.


Python
class Database(metaclass=SingletonMeta):

This creates a class called Database, but instead of using the normal class behavior, it uses SingletonMeta.

That means Database() will follow the singleton rule.

Python
    pass

This means the class has no extra code inside it right now.


Python
db1 = Database()
db2 = Database()

Both lines try to create a Database object.

But because of the singleton logic:

  • db1 gets the first and only object

  • db2 gets the same exact object, not a new one

Python
assert db1 is db2

This checks whether db1 and db2 are literally the same object in memory.

is means “same object”, not just “same value”.

This assertion passes because both variables point to the same singleton instance.


In plain English:

This code says:
“No matter how many times someone tries to create a Database, always return the same one.”

A simple real-life example is a printer service, config manager, or database connection manager where you want only one shared instance.


All Questions From This Chapter    « Previously    Next »
Tags: Python,Algorithms,

Explain the concept of the metaobject protocol in Python


All Questions From This Chapter    « Previously    Next »

In Python, the Metaobject Protocol (MOP) refers to the set of hooks and mechanisms that let you control how the language’s object system itself behaves — things like how classes are created, how attributes are accessed, or how instances are instantiated. Instead of treating the object model as fixed, Python exposes many of its internals so you can customize the rules from within your code. The MOP is not a single class or function; it’s the collection of special methods, metaclasses, and protocols that together make the object system programmable.


What a Metaobject Protocol does

A metaobject protocol lets you answer questions like:

  • “What happens when I access obj.attr?”

  • “How is a class created when I write class Foo:?”

  • “Can I change what it means for an object to be callable or iterable?”

By overriding certain magic methods or using metaclasses, you can rewrite the behaviour that the interpreter normally provides behind the scenes.


The core building blocks in Python

1. Metaclasses — controlling class creation

The default class factory is type. When you write

python
Copy
Download
class Dog:
    pass

Python roughly does:

python
Copy
Download
Dog = type('Dog', (), {})

You can replace type with your own metaclass by passing the metaclass keyword:

python
Copy
Download
class MyMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Add a class attribute automatically
        namespace['created_by'] = 'MyMeta'
        return super().__new__(mcs, name, bases, namespace)

class Cat(metaclass=MyMeta):
    pass

print(Cat.created_by)   # 'MyMeta'

Metaclasses give you control over:

  • The namespace of the class body (via __prepare__)

  • The creation of the class object (__new__)

  • The initialisation of the class object (__init__)

  • How instances of the metaclass (i.e., classes) behave when called (__call__)

Example: a singleton metaclass

python
Copy
Download
class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    pass

db1 = Database()
db2 = Database()
assert db1 is db2

Here, __call__ intercepts instance creation (Database()), letting us enforce the singleton pattern.


2. Attribute access — controlling obj.attr

Python’s attribute machinery is fully customisable through these methods, which belong to every object (or its class):

  • __getattr__(self, name) – called only when normal lookup fails.

  • __getattribute__(self, name) – called unconditionally on every attribute read.

  • __setattr__(self, name, value) – called on obj.x = value.

  • __delattr__(self, name) – called on del obj.x.

Example: logging all attribute access

python
Copy
Download
class Logged:
    def __getattribute__(self, name):
        print(f"Accessing {name}")
        return super().__getattribute__(name)

    def __setattr__(self, name, value):
        print(f"Setting {name} = {value}")
        super().__setattr__(name, value)

obj = Logged()
obj.x = 10       # prints "Setting x = 10"
print(obj.x)     # prints "Accessing x" then 10

3. Descriptors — reusable attribute behaviour

Descriptors are objects that define __get__, __set__, or __delete__. They are the magic behind property, staticmethod, classmethod, and many ORM fields.

python
Copy
Download
class PositiveNumber:
    def __set_name__(self, owner, name):
        self.public_name = name
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        return getattr(obj, self.private_name, 0)

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError("Must be non-negative")
        setattr(obj, self.private_name, value)

class BankAccount:
    balance = PositiveNumber()

acc = BankAccount()
acc.balance = 100   # ok
# acc.balance = -50 # raises ValueError

Descriptors let you intercept attribute access at the class level, making the protocol even more granular.


4. Customising instance creation and deletion

Beyond the class‑level __call__, you can also modify:

  • __new__ – allocate a new instance (rarely overridden).

  • __init__ – initialise it.

  • __del__ – define cleanup when the object is garbage collected.


5. The namespace dictionary and __slots__

Every instance normally has a __dict__ for attributes. You can replace it by defining __slots__, which trades dynamic attributes for memory efficiency. This is another MOP hook: Python changes how attribute storage works based on that declaration.


6. Method resolution order and __mro__

The MRO is computed when a class is created, but you can read cls.__mro__ and even affect it through metaclasses (by passing a custom mro() method). Libraries sometimes do this to inject mixins or enforce traits.


Why the MOP matters

The Metaobject Protocol makes Python incredibly flexible. It enables:

  • Frameworks like Django – model fields are descriptors; metaclasses turn model classes into SQL tables.

  • ORM systems – attribute access is transparently converted to database queries.

  • Mock libraries – easily replace any object behaviour for testing.

  • Aspect‑Oriented Programming – add logging, locking, or caching to whole families of objects without touching their source code.

  • Domain‑Specific Languages – create classes that look and behave exactly like your problem domain needs.


A note of caution

With great power comes great complexity. Changing fundamental behaviour can make code harder to read, debug, and maintain. Use the MOP sparingly — when you genuinely need to alter the rules of the language rather than just implementing business logic. Often a simple decorator or composition is a better fit. But when you do need it, Python’s open protocol is one of its most elegant features.


All Questions From This Chapter    « Previously    Next »