<jupyter_start><jupyter_text>Inheritance: Base Classes and Subclasses* Often, an object of one class _is an_ object of another class as well* a `CarLoan` _is a_ `Loan` as are `HomeImprovementLoan`s and `MortgageLoan`s* Class `CarLoan` can be said to inherit from class `Loan`. * In this context, class `Loan` is a base class, and class `CarLoan` is a subclass* A `CarLoan` _is a_ specific type of `Loan`, but it’s incorrect to claim that every `Loan` _is a_ `CarLoan`  Inheritance: Base Classes and Subclasses (cont.)* Table of base classes and subclasses—base classes tend to be “more general” and subclasses “more specific”: | Base class	| Subclasses| --------	| --------| `Student`	| `GraduateStudent`, `UndergraduateStuden`| `Shape`	| `Circle`, `Triangle`, `Rectangle`, `Sphere`, `Cube`| `Loan`	| `CarLoan`, `HomeImprovementLoan`, `MortgageLoan`| `Employee`	| `Faculty`, `Staff`| `BankAccount`	| `CheckingAccount`, `SavingsAccount`  Inheritance: Base Classes and Subclasses (cont.)* Every subclass object _is an_ object of its base class* The set of objects represented by a base class is often larger than the set of objects represented by any of its subclasses  `CommunityMember` Inheritance Hierarchy* Inheritance relationships form tree-like _hierarchical_ structures* A base class exists in a hierarchical relationship with its subclasses* With **single inheritance**, a class is derived from _one_ base class* With **multiple inheritance**, a subclass inherits from _two or more_ base classes* Sample class hierarchy, also called an **inheritance hierarchy** for a university community  ![Sample class hierarchy for a university community](ch10images/AAEMYRT0.png "Sample class hierarchy for a university community") * Each arrow in the hierarchy represents an _is-a_ relationship    * “an `Employee` _is a_ `CommunityMember`”     * “a `Teacher` _is a_ `Faculty` member” * `CommunityMember` is the direct base class of `Employee`, `Student` and `Alum` and is an indirect base class of all the other classes in the diagramStarting from the bottom, you can follow the arrows and apply the _is-a_ relationship up to the topmost superclass  `Shape` Inheritance Hierarchy ![Shape inheritance hierarchy](ch10images/AAEMYRU0.png "Shape inheritance hierarchy")  “is a” vs. “has a”* Inheritance produces **“is-a” relationships** in which an object of a subclass type may also be treated as an object of the base-class type* In “has-a” (composition) relationships, a class has references to one or more objects of other classes as members  Building an Inheritance Hierarchy; Introducing Polymorphism* Hierarchy containing types of employees in a company’s payroll app* All employees of the company have a lot in common    * _commission employees_ (who will be represented as objects of a base class) are paid a percentage of their sales    * _salaried commission employees_ (who will be represented as objects of a subclass) receive a percentage of their sales _plus_ a base salary   Base Class `CommissionEmployee` Class `CommissionEmployee` provides the following features: * Method `__init__` creates the data attributes `_first_name`, `_last_name` and `_ssn` (Social Security number), and uses the setter's of properties `gross_sales` and `commission_rate` to create their corresponding data attributes* Read-only properties `first_name`, `last_name` and `ssn`, which return the corresponding data attributes* Read-write properties `gross_sales` and `commission_rate` in which the `setter`s perform data validation* Method `earnings`, which calculates and returns a `CommissionEmployee`’s earnings* Method `__repr__`, which returns a string representation of a `CommissionEmployee` ```python commmissionemployee.py"""CommissionEmployee base class."""from decimal import Decimalclass CommissionEmployee:    """An employee who gets paid commission based on gross sales."""    def __init__(self, first_name, last_name, ssn,                  gross_sales, commission_rate):        """Initialize CommissionEmployee's attributes."""        self._first_name = first_name        self._last_name = last_name        self._ssn = ssn        self.gross_sales = gross_sales   validate via property        self.commission_rate = commission_rate   validate via property    @property    def first_name(self):        return self._first_name    @property    def last_name(self):        return self._last_name    @property    def ssn(self):        return self._ssn    @property    def gross_sales(self):        return self._gross_sales    @gross_sales.setter    def gross_sales(self, sales):        """Set gross sales or raise ValueError if invalid."""        if sales < Decimal('0.00'):            raise ValueError('Gross sales must be >= to 0')                self._gross_sales = sales            @property    def commission_rate(self):        return self._commission_rate    @commission_rate.setter    def commission_rate(self, rate):        """Set commission rate or raise ValueError if invalid."""        if not (Decimal('0.0') < rate < Decimal('1.0')):            raise ValueError(               'Interest rate must be greater than 0 and less than 1')                self._commission_rate = rate    def earnings(self):        """Calculate earnings."""           return self.gross_sales * self.commission_rate    def __repr__(self):        """Return string representation for repr()."""        return ('CommissionEmployee: ' +             f'{self.first_name} {self.last_name}\n' +            f'social security number: {self.ssn}\n' +            f'gross sales: {self.gross_sales:.2f}\n' +            f'commission rate: {self.commission_rate:.2f}')```  All Classes Inherit Directly or Indirectly from Class `object`* _Every_ Python class inherits from an existing class* When you do not explicitly specify the base class for a new class, Python assumes that the class inherits directly from class `object`* Class `CommissionEmployee`’s header could have been written as>```pythonclass CommissionEmployee(object):```* The parentheses after `CommissionEmployee` indicate inheritance and may contain     * a single class for single inheritance     * a comma-separated list of base classes for multiple inheritance  All Classes Inherit Directly or Indirectly from Class `object` (cont.)* `CommissionEmployee` inherits all the methods of class `object`* Two of the many methods inherited from `object` are `__repr__` and `__str__`    * So _every_ class has these methods that return string representations of the objects on which they’re called* When a base-class method implementation is inappropriate for a derived class, that method can be **overridden** (i.e., redefined) in the derived class with an appropriate implementation    * Method `__repr__` overrides the default implementation from class `object`  Testing Class `CommissionEmployee`  * test some of `CommissionEmployee`’s features<jupyter_code>from commissionemployee import CommissionEmployee
from decimal import Decimal
c = CommissionEmployee('Sue', 'Jones', '333-33-3333', 
    Decimal('10000.00'), Decimal('0.06'))
c<jupyter_output><empty_output><jupyter_text>* calculate and display the `CommissionEmployee`’s earnings<jupyter_code>print(f'{c.earnings():,.2f}')<jupyter_output>600.00<jupyter_text>* change the `CommissionEmployee`’s gross sales and commission rate, then recalculate the earnings<jupyter_code>c.gross_sales = Decimal('20000.00')
c.commission_rate = Decimal('0.1')
print(f'{c.earnings():,.2f}')<jupyter_output>2,000.00<jupyter_text>Subclass `SalariedCommissionEmployee` * With single inheritance, the subclass starts essentially the same as the base class* The real strength of inheritance comes from the ability to define in the subclass additions, replacements or refinements for the features inherited from the base class. * Many of a `SalariedCommissionEmployee`’s capabilities are similar, if not identical, to those of class `CommissionEmployee`    * Both types of employees have first name, last name, Social Security number, gross sales and commission rate data attributes, and properties and methods to manipulate that data* Inheritance enables us to “absorb” the features of a class _without_ duplicating code  Declaring Class `SalariedCommissionEmployee` * Subclass `SalariedCommissionEmployee` _inherits_ most of its capabilities from class `CommissionEmployee`* A `SalariedCommissionEmployee` _is a_ `CommissionEmployee` (because inheritance passes on the capabilities of class `CommissionEmployee`)* Class `SalariedCommissionEmployee` also has the following features:    * Method `__init__`, which initializes all the data inherited from class `CommissionEmployee`, then uses the `base_salary` property’s `setter` to create a `_base_salary` data attribute    * Read-write property `base_salary`, in which the `setter` performs data validation.    * A customized version of method `earnings`    * A customized version of method `__repr__` ```python salariedcommissionemployee.py"""SalariedCommissionEmployee derived from CommissionEmployee."""from commissionemployee import CommissionEmployeefrom decimal import Decimalclass SalariedCommissionEmployee(CommissionEmployee):    """An employee who gets paid a salary plus     commission based on gross sales."""    def __init__(self, first_name, last_name, ssn,                  gross_sales, commission_rate, base_salary):        """Initialize SalariedCommissionEmployee's attributes."""        super().__init__(first_name, last_name, ssn,                          gross_sales, commission_rate)        self.base_salary = base_salary   validate via property    @property    def base_salary(self):        return self._base_salary    @base_salary.setter    def base_salary(self, salary):        """Set base salary or raise ValueError if invalid."""        if salary < Decimal('0.00'):            raise ValueError('Base salary must be >= to 0')                self._base_salary = salary    def earnings(self):        """Calculate earnings."""           return super().earnings() + self.base_salary    def __repr__(self):        """Return string representation for repr()."""        return ('Salaried' + super().__repr__() +                  f'\nbase salary: {self.base_salary:.2f}')```  Inheriting from Class `CommissionEmployee````pythonclass SalariedCommissionEmployee(CommissionEmployee):```* specifies that class `SalariedCommissionEmployee` _inherits_ from `CommissionEmployee`* Don't see class `CommissionEmployee`’s data attributes, properties and methods in class `SalariedCommissionEmployee`, but they are there  Method `__init__` and Built-In Function `super` * _Each subclass `__init__` must explicitly call its base class’s `__init__` to initialize the data attributes inherited from the base class_    * This call should be the first statement in the subclass’s `__init__` method* The notation `super().__init__` uses the built-in function **`super`** to locate and call the base class’s `__init__` method  Overriding Method `earnings`* Class `SalariedCommissionEmployee`’s `earnings` method overrides class `CommissionEmployee`’s `earnings` method to calculate the earnings of a `SalariedCommissionEmployee`    * Obtains the portion of the earnings based on _commission alone_ by calling `CommissionEmployee`’s `earnings` method with the expression `super().earnings()`     Overriding Method `__repr__`* `SalariedCommissionEmployee`’s `__repr__` method overrides class `CommissionEmployee`’s `__repr__` method to return a `String` representation that’s appropriate for a `SalariedCommissionEmployee`* `super().__repr__()` calls `CommissionEmployee`'s `__repr__` method  Testing Class `SalariedCommissionEmployee`<jupyter_code>from salariedcommissionemployee import SalariedCommissionEmployee
s = SalariedCommissionEmployee('Bob', 'Lewis', '444-44-4444',
        Decimal('5000.00'), Decimal('0.04'), Decimal('300.00'))
print(s.first_name, s.last_name, s.ssn, s.gross_sales, 
      s.commission_rate, s.base_salary)<jupyter_output><NAME> 444-44-4444 5000.00 0.04 300.00<jupyter_text>* `SalariedCommissionEmployee` object has _all_ of the properties of classes `CommissionEmployee` _and_ `SalariedCommissionEmployee`* Calculate and display the `SalariedCommissionEmployee`’s earnings<jupyter_code>print(f'{s.earnings():,.2f}')<jupyter_output>500.00<jupyter_text>* Modify the `gross_sales`, `commission_rate` and `base_salary` properties, then display the updated data via the `SalariedCommissionEmployee`’s `__repr__` method<jupyter_code>s.gross_sales = Decimal('10000.00')
s.commission_rate = Decimal('0.05')
s.base_salary = Decimal('1000.00')
print(s)<jupyter_output>SalariedCommissionEmployee: <NAME>
social security number: 444-44-4444
gross sales: 10000.00
commission rate: 0.05
base salary: 1000.00<jupyter_text>* Calculate and display the `SalariedCommissionEmployee`’s updated earnings<jupyter_code>print(f'{s.earnings():,.2f}')<jupyter_output>1,500.00<jupyter_text>Testing the “is a” Relationship Functions **`issubclass`** and **`isinstance`** are used to test “is a” relationships* `issubclass` determines whether one class is derived from another<jupyter_code>issubclass(SalariedCommissionEmployee, CommissionEmployee)<jupyter_output><empty_output><jupyter_text>* `isinstance` determines whether an object has an “is a” relationship with a specific type<jupyter_code>isinstance(s, CommissionEmployee)
isinstance(s, SalariedCommissionEmployee)
type(s)<jupyter_output><empty_output><jupyter_text>Processing `CommissionEmployee`s and `SalariedCommissionEmployee`s Polymorphically* With inheritance, every object of a subclass also may be treated as an object of that subclass’s base class* Can take advantage of this relationship to place objects related through inheritance into a list, then iterate through the list and treat each element as a base-class object    * Allows a variety of objects to be processed in a _general_ way<jupyter_code>employees = [c, s]
for employee in employees:
    print(employee) # employee = s
    print(f'{employee.earnings():,.2f}\n')<jupyter_output>CommissionEmployee: <NAME>
social security number: 333-33-3333
gross sales: 20000.00
commission rate: 0.10
2,000.00

SalariedCommissionEmployee: <NAME>
social security number: 444-44-4444
gross sales: 10000.00
commission rate: 0.05
base salary: 1000.00
1,500.00<jupyter_text>* Correct string representation and earnings are displayed for each employee* This is called _polymorphism_—a key capability of object-oriented programming (OOP)  Duck Typing and Polymorphism* Most object-oriented programming languages require inheritance-based “is a” relationships to achieve polymorphic behavior* Python also supports **duck typing**, which the Python documentation describes as:> _A programming style which does not look at an object’s type to determine if it has the right interface; instead, the method or attribute is simply called or used (“If it looks like a duck and quacks like a duck, it must be a duck.”)_. * When processing an object at execution time, its type does not matter* As long as the object has the data attribute, property or method (with the appropriate parameters) you wish to access, the code will work   Duck Typing and Polymorphism (cont.)* Reconsider the loop at the end of Section 10.8.3``` pythonfor employee in employees:    print(employee)    print(f'{employee.earnings():,.2f}\n')```* Works properly as long as `employees` contains only objects that:    * can be displayed with `print` (that is, they have a string representation)     * have an `earnings` method which can be called with no arguments  Duck Typing and Polymorphism (cont.)* All classes inherit from `object` directly or indirectly, so they _all_ inherit the default methods for obtaining string representations that print can display* If a class has an `earnings` method that can be called with no arguments, we can include objects of that class in the list `employees`, even if the object’s class does not have an “is a” relationship with class `CommissionEmployee`* Consider class `WellPaidDuck`:<jupyter_code>class WellPaidDuck:
    def __repr__(self):
        return 'I am a well-paid duck'
    def earnings(self):
        return Decimal('1_000_000.00')<jupyter_output><empty_output><jupyter_text>* Clearly not meant to be employees* But, will work with the preceding loop<jupyter_code>from decimal import Decimal
from commissionemployee import CommissionEmployee
from salariedcommissionemployee import SalariedCommissionEmployee
c = CommissionEmployee('Sue', 'Jones', '333-33-3333',
                       Decimal('10000.00'), Decimal('0.06'))
s = SalariedCommissionEmployee('Bob', 'Lewis', '444-44-4444',
    Decimal('5000.00'), Decimal('0.04'), Decimal('300.00'))
d = WellPaidDuck()
employees = [c, s, d]<jupyter_output><empty_output><jupyter_text>* Use duck typing to _polymorphically_ process all three objects in the list<jupyter_code>for employee in employees:
    print(employee)
    print(f'{employee.earnings():,.2f}\n')<jupyter_output>CommissionEmployee: <NAME>
social security number: 333-33-3333
gross sales: 10000.00
commission rate: 0.06
600.00

SalariedCommissionEmployee: <NAME>
social security number: 444-44-4444
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00
500.00

I am a well-paid duck
1,000,000.00<jupyter_text>Test-Driving Class Complex<jupyter_code>from complexnumber import Complex
x = Complex(real=2, imaginary=4)
x
y = Complex(real=5, imaginary=-1)
y
x + y
x
y
x += y
x
y<jupyter_output><empty_output><jupyter_text>Class `Complex` Definition Method `__init__` ```python complexnumber.py"""Complex class with overloaded operators."""class Complex:    """Complex class that represents a complex number     with real and imaginary parts."""    def __init__(self, real, imaginary):        """Initialize Complex class's attributes."""        self.real = real        self.imaginary = imaginary``` Overloaded `+` Operator```python    def __add__(self, right):        """Overrides the + operator."""        return Complex(self.real + right.real,                        self.imaginary + right.imaginary)``` Overloaded `+=` Augmented Assignment```python    def __iadd__(self, right):        """Overrides the += operator."""        self.real += right.real        self.imaginary += right.imaginary        return self``` Method `__repr__````python    def __repr__(self):       """Return string representation for repr()."""        return (f'({self.real} ' +                 ('+' if self.imaginary >= 0 else '-') +                f' {abs(self.imaginary)}i)')```  Operator Overloading * Can use **operator overloading** to define how Python’s operators should handle objects of your own types* Can overload most operators* For every overloadable operator, class `object` defines a special method    * e.g., `__add__` for addition (`+`) or `__mul__` for multiplication (`*`)* Overriding these methods enables you to define how a given operator works for objects of your custom class* Complete list of special methods >https://docs.python.org/3/reference/datamodel.htmlspecial-method-names  Operator Overloading Restrictions* Precedence cannot be changed by overloading* Left-to-right or right-to-left grouping of an operator cannot be changed * “Arity” of an operator—whether it’s unary or binary—cannot be changed* Cannot create new operators* How an operator works on objects of built-in types cannot be changed * Works only with objects of custom classes or with a mixture of an object of a custom class and an object of a built-in type   Complex Numbers * We’ll define a class named `Complex` that represents complex numbers* Complex numbers, like –3 + 4i and 6.2 – 11.73i, have the form ```pythonrealPart `+` imaginaryPart `* i```` * `i` is the square root of -1* We'll overload `+` and `+=`   Test-Driving Class `Complex`<jupyter_code>from complexnumber import Complex<jupyter_output><empty_output><jupyter_text>* Create and display a couple of `Complex` objects<jupyter_code>x = Complex(real=2, imaginary=4)
x
y = Complex(real=5, imaginary=-1)
y<jupyter_output><empty_output><jupyter_text>* Use the `+` operator to add the `Complex` objects `x` and `y`* Adds the real parts of the two operands and the imaginary parts of the two operands, then returns a new `Complex` object<jupyter_code>x + y<jupyter_output><empty_output><jupyter_text>* `+` does not modify either of its operands<jupyter_code>x
y<jupyter_output><empty_output><jupyter_text>* Use `+=` to add `y` to `x` and store the result in `x`* `+=` operator _modifies_ its left operand<jupyter_code>x += y
x
y
x -= y<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09143524147658187, 'fraction_numerical': 0.017411604493944847, 'mean_word_length': 5.0, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 81, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '177881', 'license': 'MIT', 'n_tokens_mistral': 6081, 'n_tokens_neox': 5538, 'n_words': 2210, 'path': 'OOP/17 inheritance.ipynb', 'repo_name': 'Rafiei-tayebe/AP4002', 'size': '20579'}