Python 全攻略 第11節:Chapter 6 - Object Oriented Programming (96-101)

▌OOP (Object-Oriented Programming) 物件導向程式設計

物件導向程式設計 - 維基百科,自由的百科全書
物件導向程式設計(英語:Object-oriented programming,縮寫:OOP)是種具有物件概念的程式設計典範,同時也是一種程式開發的抽象方針。它可能包含資料、特性、程式碼與方法。物件則指的是類別(class)的實例。它將物件作為程式的基本單元,將程式和資料封裝其中,以提高軟體的重用性、靈活性和擴充性,物件裡的程式可以訪問及經常修改物件相關連的資料。在物件導向程式程式設計裡,電腦程式會被設計成彼此相關的物件。

▌OOP、Class、constructor

  • OOP is a method of structuring a program by bundling related properties and behaviors into individual objects.
  • OOP 是一種創造自已的 class,自己的 data type 的方式
  • A class is a code template for creating objects. We can consider “class” as a new data type that we can create on our own.
  • class 是一種 template(模板),包含使用者定義的 properties (特性)和 behaviors (行為)
  • The naming convention of class name is — always capitalize the first letter!
  • constructor A constructor is a special method used to initialize the attributes of an object when it is created. It sets up the initial state of the object and is automatically invoked upon instantiation.
  • constructor (ctor) 建構子 是一個類別裡用於建立對象的特殊子程式。它能初始化一個新建的對象,並時常會接受參數用以設定實例變數。

Class name 與 Object name

# Class name: Robot
class Robot:
    '''Robot class is for creating robots'''
    # Constructor
    # Initialize attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

# Object name: my_robot_1
my_robot_1 = Robot("Wilson", 25)
print(type(my_robot_1))  # <class '__main__.Robot'>
print(my_robot_1.name)   # Wilson
print(my_robot_1.age)    # 25
print(my_robot_1.__doc__)# Robot class is for creating robots

Properties 與 Behaviors

class Robot:
    '''Robot class is for creating robots'''
    # properties
    def __init__(self, name, age):
        self.name = name
        self.age = age
    # behaviors
    def walk(self):
        print(f"{self.name} is walking...")

my_robot_1 = Robot("Wilson", 25)
my_robot_1.walk()
# Wilson is walking...

▌ Class Attribute

  • A class attributes is Python variable that belongs to a class rather than a particular object. It is shared between all the objects of this class, and it is defined outside the constructor function.
  • Class Arrtibute: 所有 Class 共享這個 attribute
  • 將 ingredient 獨立為 attribute,減少占用記憶體空間

Attribute 位置於 ouside function 與 inside function

class Robot:
		```diff + ingredient = "metal"  # ouside function 較佳做法

    def __init__(self, name, age):
        self.name = name
        self.age = age
        - self.ingredient = "metal"  # inside function 不建議

▌ Class Atribute can be accessed by

Class attribute belongs to the class so that we can get the attribute directly from the class itself. Class attributes can be accessed by either.

  • self._class_.attribute (inside method definition)
  • objectname.attribute (outside method definition)
  • classname.attribute (try not to use this in method definition)
  • 避免使用 classname.attribute:未來維護如果修改了 Class name,底下相應的 attribute 都得要修改

Attribute accessed by different positions

class Robot:
    '''Robot class is for creating robots'''
    ingredient = "metal" # class attribute

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def walk(self):
        print(f"{self.name} is walking...")

    def greet(self):
        print(f"Hi, my name is {self.name}, and I am made of {self.__class__.ingredient}") # self.__class__.attribute

my_robot_1 = Robot("Wilson", 25)
my_robot_2 = Robot("Grace", 30)

my_robot_1.greet() # Hi, my name is Wilson, and I am made of metal
print(my_robot_1.ingredient) # metal # objectname.attribute
print(Robot.ingredient) # metal # classname.attribute
print(self.__class__.ingredient) # NameError: name 'self' is not defined
print(my_robot_1.__class__.ingredient)  # metal

▌ Class Method vs Static Method

  • Class 裡面的 function 的第一個參數都需要指向自己,但有兩個例外: staticmethod 與 classmethod
  • 定義方式: 在 function 前一行加上 decorator (如: @staticmethod@classmethod)
  • Class Method: 所有 Class 共享這個 method,第一個 parameter 需要是 cls 或自定義名稱
  • Static Method: 不需要設定 parameter
class Circle:
    """This class creates circle"""
    pi = 3.14159
    all_circles = []

    def __init__(self, radius):
        self.radius = radius
        self.__class__.all_circles.append(self)

    def area(self):
        return self.__class__.pi * (self.radius ** 2)

    @staticmethod # decorator
    def total_area():
        total = 0
        for circle in Circle.all_circles:
            total += circle.area()
        return total

    @classmethod # decorator
    def total_area2(cls):
        total = 0
        for circle in cls.all_circles:
            total += circle.area()
        return total

    # 用屬性呈現
    def total_area3(self):
        total = 0
        for circle in self.all_circles:
            total += circle.area()
        return total

c1 = Circle(10)
c2 = Circle(15)
print(c1.total_area()) # 1021.01675
print(c1.total_area2()) # 1021.01675
print(c1.total_area3()) # 1021.01675

What’s the benefit of using the class method over the static method?

Well, if we ever change the class name, then the hard-coded classname inside the function will not work anymore. We will have to manually change all the class name inside the methods’code if we hardcode class name.

  • 將 Class name “Circle” 改為 “Circles” 後, print(c1._class_.total_area()) 會出現報錯,因為 Static method 裡面有寫到 Circle.all_circles,因此建議使用 Class method 可以用 cls 代替 class name
  • 如果我們知道某個 method 可以寫成 class method,就應該要這麼做,節省記憶體用量
class Circles:
			.....(中間略).....

c1 = Circles(10)
c2 = Circles(15)
print(c1.total_area()) # NameError: name 'Circle' is not defined
print(c1.total_area2()) # 1021.01675

Inheritance

Inheritance allows us to define a class that inherits all the methods and properties from another class. The parent class is the class being inherited from, also called the base class. The child class is the class that inherits from another class, also called the derived class.

  • 如果子 class 也有自己定義一個新的 method,將會覆蓋掉原本從母 class 繼承而來的內容
  • The super() method returns a proxy object (temporary object of the superclass) that allows us to access methods of the base class.
class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def sleep(self):
        print(f"{self.name} is sleeping...")

    def eat(self):
        print(f"{self.name} is eating food")

class Student(People): # Inherits methods and properties from People class
    def __init__(self, name, age, student_id):
        # People.__init__(self, name, age) 避免 hard code
        super().__init__(name, age) # 使用 super() 必須刪除 self
        self.student_id = student_id

    def eat(self, food):
        print(f"{self.name} is now eating {food}")

student_one = Student("Wilson", 25, 100)
student_one.sleep() # Wilson is sleeping...
student_one.eat("beef") # Wilson is now eating beef
print(f"ID_{student_one.student_id}: {student_one.name}, age: {student_one.age}") 
# ID_100: Wilson, age: 25

▌ Multiple Inheritance

Java, Javascript C++ Python
Not allowed Allow, but complicated Allow, Python supports inheriting from more than one parent class

Class C、F、G 裡面都有 do_stuff(),到底 A 會繼承自哪一個?

  • 它的判別方式採用MRO
  • The basic principle of method resolution order (MRO) uses a depth-first graph traversal algorithm
  • MRO: A → B → E → B → F
# MRO: A, B, E, F, C, D, G
class E:
    pass

class F:
    def do_stuff(self):
        print("doing stuff from F")

class G:
    def do_stuff(self):
        print("doing stuff from G")

class B(E, F): # B inherits E, F
    pass

class C:
    def do_stuff(self):
        print("doing stuff from C")

class D(G): # D inherits G
    pass

class A(B, C, D): # A inherits B, C, D
    pass

a = A()
a.do_stuff() # doing stuff from F

▌ Python built-in find MRO

If you cannot determine the MRO, we can use the Python built-in:

  • classname.mro()
  • classname._mro_
print(A.mro())
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
print(A.__mro__)
# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>)

▌ C3 Linearization

  • 可解決原先 python 2 遇到 diamond problem 時,重複拜訪 A 的問題
  • C3 linearization 是 深度優先搜尋(Depth-First Search,DFS) 和 廣度優先搜尋(Breadth-First Search, BFS) 的混合體
  • Python2 MRO: D → B → A → C (採用 DFS)
  • Python3 MRO: D → B → C → A (採用 C3)
# Python2 MRO: D, B, A, C, A
# Python3 MRO: D, B, C, A
class A:
    pass

class B(A): # B inherits A
    def do_stuff(self):
        print("doing stuff from B")

class C(A): # C inherits A
    def do_stuff(self):
        print("doing stuff from C")

class D(B, C): # D inherits B, C
    pass

d = D()
d.do_stuff() # doing stuff from B
print(D.mro()) #[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
1個讚