类别(class)这个概念在很多程序语言中都会出现,
感觉上挺值得一学的,
对于管理大型程序分工上也蛮有帮助的。
之前小马在自学c++时也碰到这个概念:
【c++类别class的语法大全】(1) 物件导向概念; 封装与存取权限; class基础语法; 预设建构子与拷贝建构子
基础语法简介
这边举个介绍类别中最常见的例子- 动物好了
class Animal():
def __init__(self, name):
self.name = name
a = Animal('老虎')
print(a.name)
结果: 老虎
几个重点:
def __init__(self, ): 这个是类别的「起手式」,当类别被创造时会去执行这个函数,所以通常用来写初始属性的设置
类别方法(函数)的第一个参数一定是 self ,他是 Python 类别定义中预设的参数,代表物件本身
要呼叫一个类别的属性或方法都是用.号
类别的概念简介
有人说,
类别像是一个蛋糕的模子,这个蛋糕的模子可以重複制作出相同的蛋糕,就像类别可以宣告出相同的物件,可以让程序不断地被重複利用。
至于为什么写程序需要使用类别,
我认为是主要的好处是封装,
因为开发大型程序时,
可能要团队合作写程序,
不一定所有的程序都是自己写,
有可能别人写好的程序码你需要拿来用。
如果把程序包装成一个物件,
你不用完全了解他里面的程序码是怎么写的,
可以直接拿来用
次要的好处是表达属于关係,
譬如说name(名字)是动物的一个属性,
把name写在动物这个类别里面,
就可以清楚知道说动物有「名字」这个属性。
a = Animal('老虎')
print(a.name)
若单纯宣告一个字串表示动物的名字,
语义可能就没有这么明确
name = '老虎'
不专业语法介绍(想要看很正式介绍的话自行按右上角叉叉去找教科书来读,这边仅分享学习笔记)
继续介绍语法吧,
由于用动物介绍类别已经在别人的文章中使用非常多次了,
这边小马也尝试天马行空写自己的版本,
假设现在我想要写一个「勇者斗恶龙」的游戏,
可能会创造很多不同的角色,
因此我创造一个Charactor的类别:
class Charactor():
def __init__(self, name):
self.name = name
cha = Charactor('小黑')
继承
但是我要创造的角色可能有分「人」和「怪物」两类,
他们的行为可能不太一样,
我们便可以创造新的类别「人」去继承「角色」这个类别
原有的类别被称为基础类别(base class)或双亲类别(parent class) ,新的类别被称为衍生类别(derived class)或子类别(child class),这个衍生类别就自动拥有基础类别的变数与函式。
使用「class 衍生类别(基础类别)」来定义类别间的继承关係,衍生类别就继承了基础类别;在衍生类别中使用「super().基础类别的函式」可以呼叫基础类别的函式来帮忙,若衍生类别所需要的功能已经在基础类别定义过了,就可以呼叫基础类别帮忙,重複利用已经撰写过的程序码。
小範例:
class Charactor():
def __init__(self, name):
self.name = name
class Person(Charactor):
def __init__(self, name, personality):
super().__init__(name)
self.personality = personality
def show(self):
print("我是"+self.personality+"的"+self.name)
p1 = Person("阿古斯", "英勇")
p1.show()
结果:
我是英勇的阿古斯
在这个例子中,我让Person继承Charactor这个类别,
并且Person比Charactor多了「个性」(personality)这个属性,
show()就定义成显示Person的个人资讯
另外再写一个class继承Charactor
另外,角色还有另外一类叫「怪物」,
行为跟「人」可能不太一样,
所以可以另外再写一个类别
(这边接续上面的程序继续写)
class Charactor():
def __init__(self, name):
self.name = name
class Person(Charactor):
def __init__(self, name, personality):
super().__init__(name)
self.personality = personality
def show(self):
print("我是"+self.personality+"的"+self.name)
class Monster(Charactor):
def __init__(self, name, race):
super().__init__(name)
self.race = race
def show(self):
print(self.race+": "+self.name)
p1 = Person("阿古斯", "英勇")
p1.show()
m = Monster("泡泡龙", "龙族")
m.show()
结果:
我是英勇的阿古斯
龙族: 泡泡龙
譬如说我为Monster定义了race(种族)这个属性,
用m = Monster("泡泡龙", "龙族")宣告一只龙,
名字就叫做「泡泡龙」
抽象类别
定义类别,本身就是在进行抽象化,如果一个类别定义时不完整,有些状态或行为必须留待子类别来具体实现,则它是个抽象类别(Abstract Class)。
譬如说我制作的「勇者斗恶龙」游戏的角色都有显示角色资讯(show()函数)这项功能,
但是不同种类的角色显示角色资讯的方式可能不同,
Charactor()这个基础类别中定义show()这个抽象函数(见参考资料3),
让子类别再去实作细节即可
小範例:
from abc import ABCMeta, abstractmethod
class Charactor(metaclass=ABCMeta):
def __init__(self, name):
self.name = name
@abstractmethod
def show(self):
pass
class Person(Charactor):
def __init__(self, name, personality):
super().__init__(name)
self.personality = personality
def show(self):
print("我是"+self.personality+"的"+self.name)
class Monster(Charactor):
def __init__(self, name, race):
super().__init__(name)
self.race = race
def show(self):
print(self.race+": "+self.name)
p1 = Person("阿古斯", "英勇")
p1.show()
m = Monster("泡泡龙", "龙族")
m.show()
语法重点:
写抽象类别时,要引用from abc import ABCMeta, abstractmethod这行
基础类别内要写metaclass=ABCMeta (我其实也不知道这是什么意思)
定义成抽象类别的方法上方会挂上@abstractmethod代表他是子类别必须实作的方法
课后自我练习
在codewar上看到一题跟class有关的问题,
记录下自己的解题历程:
参考题目: CodeWar- 6kyu DefaultList
题意: 实作一个类别,跟python的内建list型别有相同的功能,但是用中括号[]取值时,若超过範围则回传default值 (类似python的defaultdict的感觉,若key值不存在回传default值)
不过问题是怎么定义用中括号[]对这个类别取值的行为呢?
为了做这一题,另外学到了class的特殊方法(又称魔术方法、魔法方法、magic method)
魔术方法
存在于类别内的特殊函式,Python会让运算子或内建函式可以与特殊函式自动对应,例如判断两物件是否相等的运算子「==」会自动与类别内特殊函式「eq」,所以在类别内重新定义特殊函式「eq」,类别中使用运算子「==」的运算就会直接使用特殊函式「eq」进行是否相等的判断
我觉得这个在参考资料- Python类别与例外-高中资讯科技概论教师黄建庭的教程网站中整理的蛮好的,
魔术方法的形式均为____, 前后有两个下底线,
譬如说用中括号[]取值的运算便对应到__getitem__。
我原本是想说,既然要求list有的功能defaultdict都要有,
我的原始答案就乖乖的把list的方法重新定义了一遍:
class DefaultList:
def __init__(self, L, defualt):
self.dList = L
self.defualt = defualt
def extend(self, L):
self.dList.extend(L)
def append(self, i):
self.dList.append(i)
def remove(self, i):
self.dList.remove(i)
def insert(self, idx, val):
self.dList.insert(idx, val)
def pop(self, idx):
self.dList.pop(idx)
def __getitem__(self, index):
return self.dList[index] if -len(self.dList)<=index
后来看了别人的解答后,才惊呼到其实让defaultdict继承list这个类别就好了嘛,
简化后的答案:
class DefaultList(list):
def __init__(self,L, default):
super().__init__(L)
self.default=default
def __getitem__(self,idx):
try:
return super().__getitem__(idx)
except IndexError:
return self.default
参考资料
[自学Python纪录] HackerRank 新手30天挑战-Day04
Python类别与例外-高中资讯科技概论教师黄建庭的教程网站
抽象类别
Python进阶技巧 (2) — Static/Class/Abstract Methods之实现
Python – Magic Methods