Namespaces provide a way for managing identifiers defined within a scope. In other words, they are used to map names to values (or references to the memory location to be more precise).
For example, in the context of a namespace the following expression
x = 10
will associate identifier x to the memory location that holds object with value 10.
In Python, there are essentially two "types" of namespaces; instance and class namespaces.
Instance Namespace manages the mapping between names and values within the scope of a individual object. On the other hand, there is a separate Class Namespace for every class defined in the source code. This type of namespace handles all the members which are shared by all instances of the object.
Example
Now consider the following example where for each member it is denoted whether it belongs to a class or instance namespace:
class Customer: def __init__(self, first_name, last_name, email): # __init__ -> Customer Class Namespace self._first_name = first_name # _first_name -> Instance Namespace self._last_name = last_name # _last_name -> Instance Namespace self._email = email # _email -> Instance Namespace def get_full_name(self): # Customer Class Namespace return f"{self._first_name} {self._last_name}" class PremiumCustomer(Customer): PREMIUM_MEMBERSHIP_COST = 4.99 # PremiumCustomer Class Namespace class Subscription: # PremiumCustomer Class Namespace def __init__(self, customer_email): # Subscription Class Namespace self._customer_email = customer_email # Instance Namespace def __init__(self, first_name, last_name, email, card_number): # PremiumCustomer Class Namespace super().__init__(first_name, last_name, email) self._card_number = card_number # _card_number -> Instance Namespace def get_card_number(self): # PremiumCustomer Class Namespace return self._card_number