2. The object.__new__() method is a special method in Python that is called to create a new instance of a class. It is responsible for the low-level creation of an object before its initialization. \
\
3. The object.__init__() method is a special method in Python that is called after an instance of a class has been created by the __new__() method. It is responsible for initializing the object's attributes and performing any necessary setup.\
\
4. The object.__del__() method is a special method in Python that is called when an instance of a class is about to be destroyed or garbage-collected.\
\
5. The cyclic garbage collector in CPython is responsible for detecting and breaking reference cycles, ensuring that memory is properly deallocated even in the presence of reference cycles.\
\
6. the __format__() method allows objects to define their own customized string representations when used with format-related operations.\
\
7. x < y calls x.__lt__(y)\
x <= y calls x.__le__(y)\
x == y calls x.__eq__(y)\
x != y calls x.__ne__(y)\
x > y calls x.__gt__(y)\
x >= y calls x.__ge__(y)\
\
8. The methods __get__, __set__, and __delete__ are used to implement descriptors in Python. Descriptors are a way to customize attribute access in classes. These methods are defined in a descriptor class, and when an instance of the descriptor class is present in an owner class, they are invoked to control attribute access.\
\
9. In Python, a descriptor is an object attribute that has "binding behavior" due to the overriding of attribute access through methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If an object defines any of these methods, it is considered a descriptor.\
\
10. Using __slots__ can save significant memory compared to using __dict__, and it can also improve attribute lookup speed.\
class AustralianPhilosopher(Philosopher, default_name="Bruce"):\
pass\
\
\
This code sets up a base class called Philosopher that defines a custom behavior for its subclasses when they are created. \
The default_name parameter allows each subclass to have a default name attribute, and the AustralianPhilosopher subclass demonstrates the usage by setting its default_name to "Bruce".\
\
12. The purpose of __set_name__() is to provide a hook for objects to perform any necessary initialization or setup when they are assigned to a class attribute. It allows the objects to be aware of their assigned name within the class they belong to, which can be useful for various purposes such as introspection or customization.\
\
13. The C3 linearization algorithm is a method used to determine the order in which base classes are searched for a particular attribute or method during multiple inheritance in Python. It ensures that the resolution order respects the consistency and priority of the inheritance hierarchy.\
\
14. MRO (Method Resolution Order) entries are resolved: This refers to the determination of the order in which base classes are searched for a particular attribute or method. The MRO defines the inheritance hierarchy and is calculated using the C3 linearization algorithm.\
\
15. Type annotations in Python refer to the practice of adding hints or metadata about the types of variables, function parameters, and return values in code. They are used to provide static type checking and improve code clarity, maintainability, and tooling support. They are optional and do not affect the runtime behavior of the code. Python remains a dynamically typed language, but type annotations allow developers to specify the expected types and enable type checking tools to catch potential errors.\
\
x: int = 5\
def add(a: int, b: int) -> int:\
return a + b\
\
16. The purpose of __class_getitem__() is to allow runtime parameterization of standard-library generic classes in order to more easily apply type hints to these classes.}