---
# System prepended metadata

title: '1.13. Object-Oriented Programming in Python: Defining Classes'
tags: [Problem Solving with Algorithms and Data Structures using Python, OOP, python, class]

---

# 1.13. Object-Oriented Programming in Python: Defining Classes

{%hackmd @88u1wNUtQpyVz9FsQYeBRg/r1vSYkogS %}

> Lee Tsung-Tang
> 整理自：[Problem Solving with Algorithms and Data Structures using Python的1.13](https://runestone.academy/runestone/books/published/pythonds/Introduction/ObjectOrientedProgramminginPythonDefiningClasses.html)

###### tags: `python` `OOP` `class` `Problem Solving with Algorithms and Data Structures using Python`

[TOC]

# 1.13.1. A Fraction Class
分數

## 定義class及constructor

```python=
class Fraction:
   #the methods go here
```

The first method that all classes should provide is the `constructor`. The `constructor` defines the way in which data objects are created. To create a Fraction object, we will need to provide two pieces of data, the numerator and the denominator. In Python, the `constructor` method is always called `__init__` (two underscores before and after init)

```python=
class Fraction:

    def __init__(self,top,bottom): # 兩個input value

        self.num = top
        self.den = bottom
```
Notice that the formal parameter list contains three items (self, top, bottom). self is a special parameter that will always be used as a reference back to the **object itself** 

 The notation `self.num` in the constructor defines the fraction object to have an **internal data object** called num as part of its state. 其餘的self.意思亦同

## 實體化/實例化

To create an instance of the Fraction class, we must invoke the constructor. This happens by using the name of the class and passing actual values for the necessary state (note that we never directly invoke `__init__`). For example
```python=
myfraction = Fraction(3,5)
```

![](https://i.imgur.com/5VB1Pb3.png)

```python=
myf = Fraction(3,5)
print(myf)
<__main__.Fraction instance at 0x409b1acc>
```
1. print()需要object能convert成string
2. 無法convert時只能回傳記憶體位置

solve:
1. define a method called show that will allow the Fraction object to print itself as a string

```python=
def show(self):
     print(self.num,"/",self.den)
```

```python=
>>> myf = Fraction(3,5)
>>> myf.show()
3 / 5
>>> print(myf)
<__main__.Fraction instance at 0x40bce9ac>
>>>
```
2. python有組standard methods存在所有的class中(但不見得都有效/有意義)。`__str__`即是其中之一，作用是將object convert to string。上面print()即是調用這個method，但結果不太理想

可以覆寫m，以改良這個method

```python=
def __str__(self):  # 直接用def __str__ 改
    return str(self.num)+"/"+str(self.den)
```

```python=
>>> myf = Fraction(3,5)
>>> print(myf)
3/5
>>> print("I ate", myf, "of the pizza")
I ate 3/5 of the pizza
>>> myf.__str__()
'3/5'
>>> str(myf)
'3/5'
>>>
```

---
## basic arithmetic operations

```python=
>>> f1 = Fraction(1,4)
>>> f2 = Fraction(1,2)
>>> f1+f2

Traceback (most recent call last):
  File "<pyshell#173>", line 1, in -toplevel-
    f1+f2
TypeError: unsupported operand type(s) for +:
          'instance' and 'instance'
>>>
```

If you look closely at the error, you see that the problem is that the “+” operator does not understand the Fraction operands.

可以覆寫standard method `f1.__add__(f2)`為以下行為：

$\frac{a}{b}+\frac{c}{d} = \frac{ad}{bd}+\frac{cb}{bd}=\frac{ad+cb}{bd}$

```python=
def __add__(self,otherfraction):

     newnum = self.num*otherfraction.den + self.den*otherfraction.num
     newden = self.den * otherfraction.den

     return Fraction(newnum,newden)
```

```python=
>>> f1=Fraction(1,4)
>>> f2=Fraction(1,2)
>>> f3=f1+f2
>>> print(f3)
6/8
>>>
```
結果正確但沒有約分成“lowest terms”

## Euclid’s Algorithm

Euclid’s Algorithm 是用來找最大公約數m,n的演算法(greatest common divisor of two integers m and n )
1. 最大公約數最大值為n，因此先看m%n 能否整除
2. 如果無法整除則以 m%n/n ，接著重複疊代



```python=
def gcd(m,n):
    while m%n != 0:
        oldm = m # << 第二輪後變為上一輪n 
        oldn = n # << 第二輪後變為上一輪oldm%oldn

        m = oldn
        n = oldm%oldn
    return n

print(gcd(20,10))
```

代回 class
```python=
def __add__(self,otherfraction):
    newnum = self.num*otherfraction.den + self.den*otherfraction.num
    newden = self.den * otherfraction.den
    common = gcd(newnum,newden)
    return Fraction(newnum//common,newden//common)
```

```python=
>>> f1=Fraction(1,4)
>>> f2=Fraction(1,2)
>>> f3=f1+f2
>>> print(f3)
3/4
>>>
```
![](https://i.imgur.com/5f2v0Vc.png)

## deep equality

Assume we have two Fraction objects, f1 and f2. f1==f2 will only be True if they are references to the same object. **Two *different objects* with the same numerators and denominators would not be equal under this implementation.** This is called shallow equality (see Figure 7)

![](https://i.imgur.com/lMsujZp.png)

修改另一個standard method `__eq__`

```python=
def __eq__(self, other):
    firstnum = self.num * other.den
    secondnum = other.num * self.den

    return firstnum == secondnum
```

## complete fraction class

```python=
def gcd(m,n):
    while m%n != 0:
        oldm = m
        oldn = n

        m = oldn
        n = oldm%oldn
    return n

class Fraction:
     def __init__(self,top,bottom):
         self.num = top
         self.den = bottom

     def __str__(self):
         return str(self.num)+"/"+str(self.den)

     def show(self):
         print(self.num,"/",self.den)

     def __add__(self,otherfraction):
         newnum = self.num*otherfraction.den + \
                      self.den*otherfraction.num
         newden = self.den * otherfraction.den
         common = gcd(newnum,newden)
         return Fraction(newnum//common,newden//common)

     def __eq__(self, other):
         firstnum = self.num * other.den
         secondnum = other.num * self.den

         return firstnum == secondnum
```

```python=
x = Fraction(1,2)
y = Fraction(2,3)
print(x+y)
print(x == y)

# 7/6
# False
```

# 1.13.2. Inheritance: Logic Gates and Circuits

## 繼承
Inheritance is the ability for one class to be related to another class in much the same way that people can be related to one another.

python中子類(child classes)會繼承父類(parents class)的特性

Figure 8 shows the built-in Python collections and their relationships to one another. We call a relationship structure such as this an **inheritance hierarchy**. 

For example, the `list` is a **child** of the **sequential collection**. In this case, we call the list the child and the sequence the parent (or subclass list and superclass sequence). This is often referred to as an IS-A Relationship (the list IS-A sequential collection). This implies that lists inherit important characteristics from sequences, namely the ordering of the underlying data and operations such as concatenation, repetition, and indexing.

![](https://i.imgur.com/wBRzp2f.png)

Lists, tuples, and strings are all types of sequential collections.他們有類似的資料形式與操作形式，他們之間的差異collection是否同質或collection是否不可改變(immutable)

子類會繼承所有父類的特性，但他們可以有額外的特徵

## 邏輯門與邏輯門電路

:::info
邏輯門（Logic Gates)是在集成電路(Integrated Circuit)上的基本組件。簡單的邏輯門可由晶體管組成。這些晶體管的組合可以使代表兩種信號的高低電平在通過它們之後產生高電平或者低電平的信號。高、低電頻可以分別代表邏輯上的“真”與“假”或二進制當中的1和0，從而實現邏輯運算。
:::

- `AND` gates
是執行“與(AND)”運算的基本邏輯門電路。有2個輸入端，1個輸出端。當所有的輸入同時為高電頻（邏輯1）時，輸出才為高電頻，否則輸出為低電頻（邏輯0）。

- `OR` gates
或門有2個輸入端，1個輸出端，多輸入或門可由多個2輸入或門構成。只要輸入中有一個為高電頻時（邏輯1），輸出就為高電頻（邏輯1）；只有當所有的輸入全為低電頻時，輸出才為低電頻。

- `NOT` gates
非門有一個輸入和一個輸出端。邏輯符號中輸出端的圓圈代表反相的意思。當其輸入端為高電頻（邏輯1）時輸出端為低電頻（邏輯0），當其輸入端為低電頻時輸出端為高電頻。也就是說，輸入端和輸出端的電頻狀態總是反相的。

> Circuit是logic gate的組合

![](https://i.imgur.com/5OQedj3.png)

logic gate 可以表示為inheritance hierarchy的結構形式：

1. At the top of the hierarchy, the `LogicGate` class represents the most general characteristics of logic gates: namely, a label for the gate and an output line. 
2. The next level of subclasses breaks the logic gates into two families, those that have *one input line and those that have two*. 
3. Below that, the specific logic functions of each appear.

![](https://i.imgur.com/ht5JoZT.png)

## implement 

從最高階的logic Gate寫起

As noted earlier, each gate has a **label for identification** and a **single output line**. 

In addition, we need methods to allow a user of a gate to ask the gate for its label.

The other behavior that every logic gate needs is the ability to know its output value. 

> `__init__` method 調用父類初始化
```python=
class LogicGate:

    def __init__(self,n):
        self.label = n
        self.output = None

    def getLabel(self):
        return self.label

    def getOutput(self):
        self.output = self.performGateLogic() # performGateLogic 未定義，會在之後的第三子類定義
        return self.output
```

第二階 logic的類型
We categorized the logic gates based on the number of input lines.

- BinaryGate class
    - The `AND` gate has two input lines. 
    - The `OR` gate also has two input lines. 

- UnaryGate class
    - `NOT` gates have one input line. 

```python=
class BinaryGate(LogicGate):

    def __init__(self,n):
        LogicGate.__init__(self,n) # 調用父類初始化

        self.pinA = None
        self.pinB = None

    def getPinA(self):
        return int(input("Enter Pin A input for gate "+ self.getLabel()+"-->"))

    def getPinB(self):
        return int(input("Enter Pin B input for gate "+ self.getLabel()+"-->"))
```
```python=
class UnaryGate(LogicGate):

    def __init__(self,n):
        LogicGate.__init__(self,n)

        self.pin = None

    def getPin(self):
        return int(input("Enter Pin input for gate "+ self.getLabel()+"-->"))
```


The only behavior that the BinaryGate class adds is the ability to get the values from the two input lines. 

Since these values come from some external place, we will simply ask the user via an input statement to provide them. The same implementation occurs for the UnaryGate class except that there is only one input line.