###### [Python 教學/](/@NCHUIT/py)
# 類別與物件
> [name=VJ][time= 110,5,11]
---
# 物件
Python是一種**物件導向**的程式語言。在Python中,幾乎所有的東西都是一個**物件**(object)。
**類別**(class)就像**構建物件的「藍圖」**,或者說是一個構建物件的函式。
另外,物件中的***方法***是指在物件裡的***函式***,物件中的***屬性***是指物件裡的***變數***。
----
## 創建一個類別 ~(Class)~
要創建一個類,使用關鍵字 `class`。
例如創建一個名為 `MyClass` 的類別,他有一個名為 `x` 的實例屬性。
```python=!
class MyClass:
def __init__(self):
self.x = 5
```
###### ~注意縮排~
----
## 構建物件
我們可以使用名為 `MyClass` 的類別來構建物件。在物件中的變數叫作屬性。
例如構建一個名為 `p1` 的物件,該物件含有***屬性***`x`。
```python=!
class MyClass:
def __init__(self):
self.x = 5
p1 = MyClass()
print (p1.x)
```
----
可以稍微將***有回傳東西的函式***與***類別***做連結,但物件在函式之上,因為我們可以從它裡面取用東西
```python=!
def myFunc():
x = 1
y = 7
return x
class Myclass:
def __init__(self):
self.x = 1
self.y = 7
f = myFunc()
print (f)
c = MyClass()
print (c.x, c.y)
```
----
:::spoiler 練習 1.1
```python=!
class MyClass:
def __init__(self):
self.x = int() #或任意數字
self.l = list() #[] 也對
self.d = dict() #{} 也對
c = MyClass()
print(c.x, c.l, c.d)
```
:::
試寫一個類別,該類別含有一個`int`變數`x`、一個空的`list`和一個空的`dict`。
----
## 修改物件屬性
我們可以像下面這樣修改用上面構建出來的物件裡的**屬性**(變數)。
```python=!
p1 = MyClass()
p1.x = 327
```
----
:::spoiler 練習 1.2
```python=!
c.l.append(1)
c.d['Mandy']=327
```
:::
承上題,將你定義的那個空的`list`使用`append`放入一個`int`變數`1`,再將你定義的那個空的`dict`放入鍵值對`'Mandy':327`。
----
## Coding Style
通常**類別**取名字的時候首字母會大寫,比容易讓人認得出這是個類別。由於物件是變數,所以可以不用管,但有一派是不管物件或類別都取首字母大寫的名字。另外函式都建議首字母小寫,不會變的變數(constant)用全大寫。
```python=!
def myFunc(): pass # 當一個類別或函式什麼屬性都沒有請這樣寫
class MyClass: pass
myClassX = MyClass()
MyClassX = MyClass()
PI = 3.14159
```
---
## 物件的構建函式
物件除了變數外也可以有函式。不過在物件裡函式叫作方法。
所有的類別都有一個叫做 `__init__()` 的***函式***,它總是在類別被構建成物件時執行。
使用 `__init__()` 函式為物件中的變數賦值,或者在構建物件時進行**其他操作**。
----
例如構建一個名為 `Person` 的類別,使用 `__init__()` 函式為 `name` 和 `age` 賦值。
`self` 是對類別的代稱,用於類別中函式的呼叫或變數的引用。
```python=!
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("Mandy", 20)
print (p1.name)
print (p1.age)
```
###### ~注意:每次用類別構建新物件時,都會自動呼叫類別的~`__init__()`~方法。~
----
前面的程式碼相當於
```python=!
class Person: pass
p1 = Person()
p1.name = "Mandy"
p1.age = 20
print (p1.name)
print (p1.age)
```
###### ~注意:物件的屬性可以隨你變動~
----
:::spoiler 練習 2.1
```python=!
class MyHello:
def __init__(self):
print("Hello")
```
:::
試寫一個名叫`MyHello`的***類別***,使該類別在***構件***時印出 Hello。
```python=!
class MyHello:
#TO-DO
c = MyHello() #印出 Hello
```
----
:::spoiler 練習 2.2
```python=!
class MyString:
def __init__(self,s):
print(s)
```
:::
試寫一個名叫`MyString`的類別,使該類別在構件時印出字串`s`。
```python=!
class MyString:
#TO-DO
s = "Hello"
c = MyString(s) #印出 s
```
---
## 類別屬性(Instance Attribute)與實例屬性(Class Attribute)
在一個類別中,我們有些屬性是可以共用的,例如
```python=
class Car:
weels=4 # (Class Attribute)
def __init__(self,name):
self.name = name #(Instance Attribute)
```
```python=
print(Car.weels)
my_car = Car('SaberSR')
his_car = Car('CottonSR')
print(my_car.weels)
Car.weels=8
print(his_car.weels)
my_car.weels = 4
print(his_car.weels)
```
可以看到,只要我們修改類別的屬性,所有的屬性都會受到影響
## 物件的方法
物件除了變數外也可以有函式。不過在物件裡函式叫作方法。
讓我們在上面的`Person`類別中寫一個方法。
插入一個輸出問候語的函式,並在`p1`物件上執行。
```python=!
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def myfunc(self):
print("Hello my name is " + self.name)
p1 = Person("Mandy", 20)
p1.myfunc()
```
###### ~注意:引數~ `self` ~是實例的代稱,用於類別中對實例方法的呼叫或屬性的引用。~
----
:::spoiler 練習 3
```python=!
class BMI:
def __init__(self,name,h,w):
self.bmi = dict()
self.add(name,h,w)
def add(self,name,h,w):
self.bmi[name]={"身高":h,"體重":w,"BMI":w/(h/100)**2}
```
:::
試寫一個名為`BMItable`的類別,使該類別擁有紀錄人的名字、身高、體重和BMI的能力,規格如下。
```python=!
class BMItable:
def __init__(self, name, high, weight):
self.bmi = dict() #{} 亦可
#TO-DO
c = BMItable("John",171,57)
c.add("Mandy",159,45)
c.add("VJ",163,48)
print (c.bmi)
#印出:
#{'John': {'身高': 171, '體重': 57, 'BMI': 19.493177387914233},
#'Mandy': {'身高': 159, '體重': 45, 'BMI': 17.799928800284796},
#'VJ': {'身高': 163, '體重': 48, 'BMI': 18.06616733787497}}
```
----
## `self`參數
`self` 視作實例方法第一個傳入的引數,用於實例中方法的呼叫或屬性的引用。
不一定要命名為`self`,你可以隨心所欲地調用它,但它必須是類中任何函式的第一個參數。
----
例如,用 `mysillyobject` 和 `abc` 兩個詞代替`self`。
```python=!
class Person:
def __init__(mysillyobject, name, age):
mysillyobject.name = name
mysillyobject.age = age
def myfunc(abc):
print("Hello my name is " + abc.name)
p1 = Person("John", 36)
p1.myfunc()
```
不過不要這麼做好嗎= =,一般來說我們還是會以`self`作為傳入的那個實例的命名
---
# 繼承
Python 中類別間可以**繼承**---允許我們定義一個***複製另一個類別的所有方法和屬性***的類別。
父類別(Parent class)是被繼承的類別,也叫基底類別。
子類別(Child class)是繼承自另一個類別的類別,也叫衍生類別。
----
## 父類別
任何類別都可以是父類別,因此語法與寫任何其他類別一樣,接下來所指的父類別都會是以下例子。
例如我們寫一個名為`Person`的類別,它具有`firstname`和`lastname`屬性,以及一個名為`printname`的方法:
```python=!
class Person:
def __init__(self, fname, lname):
self.firstname = fname
self.lastname = lname
def printname(self):
print(self.firstname, self.lastname)
```
----
使用`Person`類別構建一個物件,然後呼叫`printname`方法:
```python=!
x = Person("John", "Doe")
x.printname()
```
----
## 子類別
若要寫出一個子類別---***繼承其他類別***的類別,請在寫子類別的時後將父類別作為引數傳入:
例如我們寫一個名為`Student`的類別,將該類別繼承`Person`的屬性和方法:
```python=!
class Student(Person): pass
```
----
現在,類別`Student`具有與類別`Person`相同的屬性和方法。
使用類別`Student`構建一個物件,然後呼叫`printname`方法:
```python=!
x = Student("Mike", "Olsen")
x.printname()
```
你也可以再使用Person 來構建,目前來說用法一樣
```python=!
y = Person("John", "Doe")
y.printname()
```
----
:::spoiler 練習 4.1
```python=!
class BMItable2(BMItable): pass
```
:::
試著用名為`BMItable2`的類別***繼承***練習 3 的`BMItable`
----
:::spoiler 練習 4.2
```python=!
class BMItable2(BMItable):
def secret(name):
self.bmi[name]={'身高'='xxx','體重'='xxx','BMI'='xxx'}
```
:::
試著用名為`BMItable2`的類別***繼承***練習 3 的`BMItable`
並且新增method `secret(name)` ,使該name擁有的身高、體重、BMI都變成'xxx'
## `__init __()` 函式的複寫
到目前為止,我們已經成功寫了一個子類別,這個子類別是從父類別複製屬性和方法。
我們要在子類別寫`__init __()`函式(而不是pass關鍵字)。
例如我們在`Student`類別裡**複寫**`__init__()`函數。
```python=!
class Person:
def __init__(self, fname, lname):
#做其他事
```
###### ~注意:如果在子類別中添加一個與~*父類別中的方法*~同名的方法,那麼繼承下來的父類別方法就會被覆蓋。~
----
為了保持對父類別的 `__init__()` 函式的繼承,可以寫成呼叫父類別的 `__init__()` 函式。例如:
```python=!
class Student(Person):
def __init__(self, fname, lname):
Person.__init__(self, fname, lname)
```
----
## 使用`super`()函式
通過使用`super()`函式,你不需要使用父類別的名字,它會自動繼承父類別的方法和屬性。因為需要多呼叫一個函式,雖然會慢一點,但***可讀性***高。
```python=!
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
```
----
:::spoiler 練習 4.2
```python=!
class BMII(BMI):
def __init__(self,name,h,w):
BMI.__init__(self,name,h,w)
print ("這是BMII")
```
:::
承上題(4.1),試著用名為`BMII`的類別***繼承***練習 3 的`BMI`後,印出
```
這是BMII
```
----
## 添加屬性
現在我們已經準備好 `__init__()` 函式了,還同時繼承了父類別,我們準備在 `__init__()` 函式中進行其他操作。例如我們在`Student`類別中多添加一個名為`graduationyear`的屬性:
```python=!
class Student(Person):
def __init__(self, fname, lname):
super().__init__(fname, lname)
self.graduationyear = 108
```
----
在下面的例子中,`108`會是一個變量,並在構建`Student`時傳入。要做到這一點,得在`__init__()`函式中多寫一個引數。添加一個`year`引數,並在構建物件時傳入正確的年份:
```python=!
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
x = Student("Mandy", "Lu", 108)
```
----
:::spoiler 練習 4.3
```python=!
class BMII(BMI):
def __init__(self,name,h,w,d):
self.bmi = d #要先定義不然放下面會覆蓋
BMI.__init__(self,name,h,w)
```
:::
承上題(4.2),試著用名為`BMII`的類別***繼承***練習 3 的`BMI`後,多傳入一個`dict`定義給`bmi`。
```python=!
d = {"VJ":{'身高': 163, '體重': 48, 'BMI': 18.07}}
c = BMII("Mandy",159,45,d)
print (d)
#印出:
#{'VJ': {'身高': 163, '體重': 48, 'BMI': 18.07},
#'Mandy': {'身高': 159, '體重': 45, 'BMI': 17.799928800284796}}
```
----
## 添加方法
例如我們在`Student`類中添加一個名為`welcome`的方法。
```python=!
class Student(Person):
def __init__(self, fname, lname, year):
super().__init__(fname, lname)
self.graduationyear = year
def welcome(self):
print("Welcome", self.firstname, self.lastname, "to the class of", self.graduationyear)
```
----
:::spoiler 練習 4.4
```python=!
class BMII(BMI):
def __init__(self,name,h,w,age):
BMI.__init__(self,name,h,w))
self.bmi[name]['年齡'] = age
def add(self,name,h,w,age = None):
self.bmi[name]={"身高":h,"體重":w,"BMI":w/(h/100)**2}
if age == None: self.bmi[name]['年齡'] = age
```
:::
試著用名為`BMII`的類別***繼承***練習 3 的`BMI`後,多傳入一個引數年齡。
```python=!
c = BMII("VJ",162,48,21)
c.add("Mandy",159,45,20)
print (c.bmi)
#印出:
#{'VJ': {'身高': 163, '體重': 48, 'BMI': 18.06616733787497, '年齡': 21},
#'Mandy': {'身高': 159, '體重': 45, 'BMI': 17.799928800284796, 年齡': 20}}
```
# 補充
## class method
當一個 method 是帶有 class 本身作為參數傳入時,我們稱之為 class method
class method 必須傳入 class 本身作參數,就是**cls**,概念跟 instance 用 self傳入instance method 的參數一樣。
```python=
class Person2:
scientific_name='Homo sapiens'
@classmethod
def evolution(cls):
cls.scientific_name = 'Hyper Homo sapiens'
print(Person2.scientific_name)
Person2.evolution() #使用class呼叫class mothod
print(Person2.scientific_name)
```
其實也可以用instance 呼叫class method
```python=
class Person:
year = 100
def __init__(self, height, weight):
self.height = height
self.weight = weight
@classmethod
def Kokuhaku(cls):
print(f'I love Tatara {cls.year} years')
Person.Kokuhaku()
Yourdaughter = Person(180,100)
Yourdaughter.Kokuhaku()
```
<style>hr{display:none;}</style>
{"metaMigratedAt":"2023-06-16T00:12:23.732Z","metaMigratedFrom":"YAML","title":"類別與物件 - Python 教學","breaks":true,"description":"中興大學資訊研究社1091學期程式分享會主題社課","image":"none","lang":"zh-tw","contributors":"[{\"id\":\"6d6e3ba2-6820-4c6f-9117-f09bccc7f7aa\",\"add\":0,\"del\":205},{\"id\":\"4039c7c6-929e-4623-bcab-ee47f79a408c\",\"add\":1100,\"del\":606},{\"id\":\"4c23290c-4304-45d6-9c21-163639f3ac69\",\"add\":1636,\"del\":363},{\"id\":\"e86b6571-4dea-4aa4-ba20-ece559b0e015\",\"add\":13518,\"del\":5523}]"}