# 139. Using super() for Method Resolution

In Python, the `super()` function is used to call methods from a superclass in the context of method resolution order (MRO), especially in multiple inheritance scenarios. It helps to access methods in the inheritance chain without explicitly referring to the superclass by name.

Here are 10 Python code snippets demonstrating how to use `super()` for method resolution in various inheritance scenarios:

**1. Basic Single Inheritance with `super()`**

In simple inheritance, `super()` can be used to call a method from the parent class.

```python
class Animal:
    def speak(self):
        print("Animal speaks")

class Dog(Animal):
    def speak(self):
        super().speak()  # Calling parent class method
        print("Dog barks")

dog = Dog()
dog.speak()
```

**Output:**

```
Animal speaks
Dog barks
```

**2. Multiple Inheritance with `super()`**

In multiple inheritance, `super()` follows the method resolution order (MRO) to call methods from all superclasses.

```python
class A:
    def method(self):
        print("Method from class A")

class B:
    def method(self):
        print("Method from class B")

class C(A, B):
    def method(self):
        super().method()  # Calls method from class A (MRO order)
        print("Method from class C")

c = C()
c.method()
```

**Output:**

```
Method from class A
Method from class C
```

**3. Using `super()` with `__init__()` in Multiple Inheritance**

When initializing classes with `__init__()` in multiple inheritance, `super()` ensures the initialization of all parent classes.

```python
class A:
    def __init__(self):
        print("A initialized")

class B:
    def __init__(self):
        print("B initialized")

class C(A, B):
    def __init__(self):
        super().__init__()

c = C()
```

**Output:**

```
A initialized
```

In this example, since class `A` appears first in the MRO, its `__init__()` method is called.

**4. `super()` in Diamond Problem (Multiple Inheritance)**

When multiple classes share a common base class, `super()` resolves the diamond problem (a situation where a class inherits from two classes that have the same parent).

```python
class A:
    def method(self):
        print("Method from class A")

class B(A):
    def method(self):
        super().method()
        print("Method from class B")

class C(A):
    def method(self):
        super().method()
        print("Method from class C")

class D(B, C):
    def method(self):
        super().method()
        print("Method from class D")

d = D()
d.method()
```

**Output:**

```
Method from class A
Method from class C
Method from class B
Method from class D
```

In this case, the MRO ensures that `super()` calls methods in the correct order.

**5. Calling a Parent Method from a Subclass in Multiple Inheritance**

Use `super()` to call a method from a parent class in cases where multiple parent classes share the same method name.

```python
class A:
    def hello(self):
        print("Hello from A")

class B:
    def hello(self):
        print("Hello from B")

class C(A, B):
    def hello(self):
        super().hello()  # Calls hello() from class A (based on MRO)
        print("Hello from C")

c = C()
c.hello()
```

**Output:**

```
Hello from A
Hello from C
```

**6. Avoiding Explicit Class Names with `super()`**

You don't need to refer to the parent class explicitly when calling `super()`, making it more flexible in future changes.

```python
class A:
    def show(self):
        print("Class A")

class B(A):
    def show(self):
        super().show()  # Avoids explicitly using class name
        print("Class B")

b = B()
b.show()
```

**Output:**

```
Class A
Class B
```

**7. Super with `__del__()` Method**

You can use `super()` in the `__del__()` method to ensure proper cleanup when an object is deleted.

```python
class A:
    def __del__(self):
        print("Class A cleanup")

class B(A):
    def __del__(self):
        super().__del__()
        print("Class B cleanup")

b = B()
del b
```

**Output:**

```
Class A cleanup
Class B cleanup
```

**8. Super in Method Overriding with Arguments**

Use `super()` to call overridden methods and pass arguments.

```python
class A:
    def speak(self, message):
        print(f"A says: {message}")

class B(A):
    def speak(self, message):
        super().speak(message)  # Passes argument to the parent method
        print(f"B says: {message}")

b = B()
b.speak("Hello!")
```

**Output:**

```
A says: Hello!
B says: Hello!
```

**9. Calling `super()` with `staticmethod`**

You can use `super()` in a static method to call a static method from a parent class.

```python
class A:
    @staticmethod
    def greet():
        print("Hello from A")

class B(A):
    @staticmethod
    def greet():
        super().greet()  # Calls static method from A
        print("Hello from B")

b = B()
b.greet()
```

**Output:**

```
Hello from A
Hello from B
```

**10. Using `super()` in a Method with Arguments in Multiple Inheritance**

In complex inheritance, `super()` helps in invoking methods from parent classes that accept arguments.

```python
class A:
    def greet(self, name):
        print(f"Hello, {name} from A")

class B:
    def greet(self, name):
        print(f"Hello, {name} from B")

class C(A, B):
    def greet(self, name):
        super().greet(name)  # Calls greet() from A
        print(f"Hello, {name} from C")

c = C()
c.greet("Alice")
```

**Output:**

```
Hello, Alice from A
Hello, Alice from C
```

**Explanation:**

* The `super()` function follows the method resolution order (MRO) and calls methods from superclasses in the defined order.
* In a multiple inheritance scenario, `super()` allows you to avoid explicitly calling a class name, making the code more maintainable and flexible.

**Summary:**

* `super()` is used to call methods from parent classes, especially when dealing with multiple inheritance.
* It helps in avoiding redundant method calls and simplifies the inheritance structure.
* The method resolution order (MRO) ensures the correct order of method calls in complex class hierarchies.
