Day 12 : 物件导向

在进入机器学习之前,想先大家深入了解一些 python 的进阶操作。接下来的操作会有点抽象,请好好品嚐/images/emoticon/emoticon12.gif

回到主题,什麽是物件导向程序设计? 大家或多或少打开别人写的程序码会看到 class (类别),写成这个样式有什麽优点吗? 以下列举三项:

  • 提供软件的重复利用性
  • 扩充性
  • 方便维护

我们接着会将想实作的概念抽象化,方便大家使用。python 的套件基本上都是一个个物件哦!前面介绍的 pandas、matplotlib、seaborn,原始码都是写成物件导向。

OS:若想要往更高阶的 python 技巧,物件导向可是非常重要一定要会的!

1. 类别的定义与使用

定义一个物件有的属性(attribute)和方法(method),举例定义一个动物,他的名称和颜色是属性,而动作是方法。Animal 是一个类别名称,在类别内定义的变数称为属性、而定义的函式为方法。提醒一下这边开始会看到很多 self 的关键字,这个代表自己的意思(想像成英文的代名词),楼下这个 self 就是 animal。

你会在每个 method 最前面的第一个参数都必定写 self

class Animal:
    
    # 定义属性 (Attribut)
    name = '小黑'
    color = '黑色'
    
    # 定义方法 (Method)
    def walk(self):
        return '走路'

若想操作类别的属性和方法,必须先宣告实体化该物件

  • object.属性: 物件里定义好的属性,举例汽车是一个物件,颜色就是它的属性。
  • object.方法(): 物件里定义好的方法(就是function),举例汽车是一个物件,开车就是它的方法。
  • 实体化:基本上是把物件指向某个变数,例如汽车是一个物件,所以我写 x = 汽车()
# 先实体化後指定给该变数,而该变数则为物件
a = Animal()
print("Type :", type(a))
print("Attribute 1 :", a.name)
print("Attribute 2 :", a.color)
print("Method :", a.walk())
>>> Type : <class '__main__.Animal'>
    Attribute 1 : 小黑
    Attribute 2 : 黑色
    Method : 走路

2. 类别的建构元

在建立物件的同时,程序会自动执行的方法称为建构元,又称为初始化。

这个通常是我们写物件的「起手式」。

class Animal:
    
    def __init__(self, name, color):
        print("Initialize")
        self.name = name
        self.color = color
    
    def call(self):
        print(f"The color of my {self.name} is {self.color}")

# 实体化的时候就会进行初始化的动作
a = Animal('小黑', '黑色')
>>> Initialize
a.call()
>>> The color of my 小黑 is 黑色
print(a.name)
>>> 小黑

小小提醒

  • self.radius 是物件的属性
  • radius 是 __init__ 的区域变数
class Circle:
    
    def __init__(self):
        self.radius = 10
        radius = 5
c = Circle()
c.radius
>>> 10

3. 类别变数

  • (1) 可以直接存取该类别变数,而该类别变数与 Car 类别的任何物件无关
class Car:
    wheel_num = 4
    
    def __init__(self, brand):
        self.brand = brand
    
    def drive(self):
        print(f"It has {Car.wheel_num} wheels.")
        print(f"I can drive my {self.brand} car.")
Car.wheel_num
>>> 4
c = Car('Toyota')
c.drive()
>>> It has 4 wheels.
    I can drive my Toyota car.
class Tricycle:
    wheel_num = 3
    
    def __init__(self, brand):
        self.brand = brand
    
    def drive(self):
        print(f"It has {Car.wheel_num} wheels.")
        print(f"I can drive my {self.brand} car.")
c = Tricycle('Toyota')
c.drive()
>>> It has 4 wheels.
    I can drive my Toyota car.
  • (2) 如果用类别名称来存取变数会有缺点,当有一天这个类别 Car 改成其他名称就会出错
    以下可以改成用 __class__ 的方式取代 Car.wheel_num
class Car:
    wheel_num = 4
    
    def __init__(self, brand):
        self.brand = brand
    
    def drive(self):
        print(f"It has {self.__class__.wheel_num} wheels.")
        print(f"I can drive my {self.brand} car.")
c = Car('Toyota')
c.drive()
>>> It has 4 wheels.
    I can drive my Toyota car.
  • (3) 建立物件变数并没有修改到类别变数
class Circle:
    pi = 3.14
    
    def __init__(self, radius):
        self.radius = radius
        
    def calculate_area(self):
        self.area = (radius**2) * self.__class__.pi

倘若我去实例化去修改它,c2.pi 、Circle.pi 没有仍然被修改到

c1 = Circle(1)
c2 = Circle(2)
c1.pi = 3.14159
print("c1.pi:", c1.pi)
print("c2.pi:", c2.pi)
print("Circle.pi:", Circle.pi)
>>> c1.pi: 3.14159
    c2.pi: 3.14
    Circle.pi: 3.14

4. 私有属性与方法

刚刚介绍的类别,其类别内的属性与方法都可以让外部引用,称为公有属性或公有方法

如果我们想要隐密一点,创建私有的属性和方法,则称为「封装」(Encapsulation)。

class Bank:
    
    def __init__(self, name):
        self.name = name
        self.balance = 0
        
    def save_money(self, money):
        self.balance += money
        
    def withdraw_money(self, money):
        self.balance -= money
    
    def show_money(self):
        print(f"{self.name} balance is NT${self.balance}.")
b = Bank("David")
b.save_money(1500)
b.show_money()
>>> David balance is NT$1500.
b.balance = 100000000
b.show_money()
>>> David balance is NT$100000000.

为了避免有心人士窜改数据,我们要求外部不得直接更改数据

class Bank:
    
    def __init__(self, name):
        self.name = name
        self.__balance = 0
        
    def save_money(self, money):
        self.__balance += money
        
    def withdraw_money(self, money):
        self.__balance -= money
    
    def show_money(self):
        print(f"{self.name} balance is NT${self.__balance}.")
b = Bank("David")
b.balance = 100000000
b.show_money()
>>> David balance is NT$0.

私有属性其实只是换上不同名称(python将该属性的名称做调整),告诉其他开发者说这个不要串改或使用

b._Bank__balance = 10000000
b.show_money()
>>> David balance is NT$10000000.

小小提醒

除了双底线开头的私有变数之外,单底线开头(不以底线结尾)的变数也算是私有变数

这是属於约定俗成的写法,依旧可以存取

虽然可以存取,但别人设定写私有就不要随意取用

其他资源参考

5. 类别的继承

我们想定义动物的物件,目前我家只养了两只动物就要写两个物件

但一旦到了动物园,有很多动物,每个动物叫声(方法)都不太一样,那不就要写很多次吗?

我们是不是可以用抽象的方式来定义呢?

class Dog:
    
    def __init__(self, name):
        self.name = name
        
    def sleep(self):
        print("I like sleeping.")
    
    def bark(self):
        print("bark bark bark")

class Cat:
    
    def __init__(self, name):
        self.name = name
        
    def sleep(self):
        print("I like sleeping.")
        
    def meow(self):
        print("meow meow meow")

上述的做法没有不对,但是不够好

改良一下把他们抽象化一点,把一样的东西抽出来,加上继承吧!

class Animals:
    
    def __init__(self, name):
        self.name = name
        print(f"My name is {self.name}")
        
    def talk(self):
        pass
    
class Dog(Animals):
    
    def __init__(self, dog_name):
        super().__init__(dog_name)
    
    def talk(self):
        print("bark bark bark")

class Cat(Animals):
    
    def __init__(self, cat_name):
        super().__init__(cat_name)
    
    def talk(self):
        print("meow meow meow")

实际呼叫看看吧!

d = Dog('小黑')
d.talk()
>>> My name is 小黑
    bark bark bark
c = Cat('小皮')
c.talk()
>>> My name is 小皮
    meow meow meow

在物件导向的程序设计,类别是可以被继承的哦!

被继承的类别为父类别;继承的类别为子类别

小小提醒

类别定义外的私有属性或方法,子类别无法读取,使用上请注意!

class Animals:
    
    def __init__(self, name):
        self.name = name
        print(f"My name is {self.name}")
        
    def talk(self):
        pass
    
    def author(self):
        self.__title = 'Erik'
        
    def author_title(self):
        return self.__title
    
class Dog(Animals):
    
    def __init__(self, dog_name):
        super().__init__(dog_name)
    
    def talk(self):
        print("bark bark bark")

class Cat(Animals):
    
    def __init__(self, cat_name):
        super().__init__(cat_name)
    
    def talk(self):
        print("meow meow meow")

d = Dog('小黑')
>>> My name is 小黑
# 无法直接取得!
d.author()
d.__title()
>>> AttributeError: 'Dog' object has no attribute '__title'
# 我有给你方法,所以请透过我!
d.author_title()
>>> 'Erik'

<<:  【Day 09】if ... else

>>:  Angular RxJs 各种解订阅方式

Day3 安装 Kubernetes & Open-Match 核心

在昨天我们简单介绍了框架是如何产生配对後,今天我们要来部署 Open-Match 所需要的环境与核心...

观赏鱼辨识系统说明-Day 01

观赏鱼辨识系统说明-Day 01 在接下来的30天会制作一个完整的系统包含前端-手机/网页,後端-N...

[Day08] Let's Build!

接续昨天提到的,我们今天将会实际跑一次指令,如果看到这里的你还在犹豫的话,别犹豫了,跟我一起开始吧!...

[Day 31] - 手把手跨出第一步!将Arduino打造成JavaScript的环境-Part 1

大家好,我是17King~ d(`・∀・)b 好久不见,因为最近比较多事情! 但!我没有忘记要po(...

Day 27 RSpec 的 Mock & Stub

该文章同步发布於:我的部落格 在我一开始学习写 Rails 测试时,会有很常见的问题,就是到底什麽...