第八章 面向物件程式設計
Python3.5筆記
第8章 面向物件程式設計
面向物件術語介紹
- 類:用來描述具有相同屬性和方法的物件的集合。類定義中集合了每個物件共有的屬性和方法。物件是類的示例。
- 類變數(屬性):類屬性在整個例項化的物件中是公用的。類變數定義在類中,且在方法之外。類常亮通常不作為適量變數使用。類變數也稱作屬性。
- 資料成員:類變數或例項變數用於處理類及其例項變數的相關資料。
- 方法重寫:如果從父類繼承的方法,不能滿足子類的需求,就可以對其重寫,這個過程叫做方法的覆蓋,也稱為方法的重寫。
- 例項變數:定義在方法中的變數,只作用於當前例項的類。
- 多型:對不同類的物件使用同樣的操作。
- 封裝:對外部物件隱藏物件的工作細節。
- 繼承:即一個派生類繼承基類的欄位和方法。繼承允許把一個派生類的物件作為一個基類物件對待,以普通類為基礎建立專門的類物件。
- 例項化:建立一個類的例項,類的具體物件。
- 方法:類中定義的函式。
- 物件:通過類定義的資料結構例項。物件包括兩個資料成員(類變數和例項變數)和方法。
類的定義
示例:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class MyClass(object):
i = '小明'
def f(self):
return 'hello world %s' % self.i
類定義的寫法:
- 使用class關鍵字,class關鍵字後面緊跟類名,類名通常首字母大寫
- 類名後緊跟的是(object),表明該類是從哪個類中繼承而來的,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
- 類通常包含屬性和方法。在類中定義方法時,第一個引數必須是self。除第一個引數外,類的方法和普通函式沒有什麼區別。
類的使用
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class MyClass(object):
i = '小明'
def f(self):
return 'hello world %s' % self.i
my_class = MyClass()
print('類中的屬性是:' ,my_class.i)
print('類中方法的返回是:',my_class.f())
輸出:
類中的屬性是: 小明
類中方法的返回是: hello world 小明
類的使用比函式呼叫多了幾個操作:
- my_class = MyClass() 這步叫做類的例項化,即建立一個類的例項。由此得到的my_class變數稱為類的具體物件。
- 呼叫類中定義的方法,除了self不用傳遞外,其他引數正常傳入。
- 類屬性引用的語法為:obj.name,obj為類物件,name代表屬性。
類的構造方法
在Python中,_init_()方法是一個特殊方法,在物件例項化時會被呼叫。這個方法的寫法是:先輸入兩個下劃線,後面接著輸入init,再接著兩個下劃線。這個方法也叫構造方法。在定義類時,如果不顯示的定義_init_()方法,則程式預設會呼叫一個無參的構造方法。示例如下:
class MyClass2(object):
i = 123
def __init__(self,name1,age1):
self.name = name1
self.age = age1
def f(self):
return 'hello %s,you are %s years old!' % (self.name,self.age)
my_class2 = MyClass2('小明','23')
print('類中的屬性是:',my_class2.name)
print('類中的屬性是:',my_class2.age)
print('類中方法的返回是:',my_class2.f())
輸出:
類中的屬性是: 小明
類中的屬性是: 23
類中方法的返回是: hello 小明,you are 23 years old!
一個類中可以定義多個構造方法,但例項化類時只例項化最後的構造方法,即後面的構造方法會覆蓋前面的構造方法,並且需要根據最後一個構造方法的形參進行例項化。建議一個類中,只定義一個構造方法。
類的訪問許可權
直接訪問公有屬性和方法
在類的內部有屬性和方法,外部程式碼可以直接呼叫屬性和方法。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def info(self):
print('hello %s,you are %s years old!' % (self.name,self.age))
stu = Student('小明','23')
stu.info()
stu.name = '小張'
stu.age = '24'
stu.info()
輸出:
hello 小明,you are 23 years old!
hello 小張,you are 24 years old!
如何定義和訪問私有屬性
要讓類的內部屬性不被外部訪問,可以在屬性名稱前加兩個下劃線__。在Python中,例項的變數名如果以兩個下劃線開頭,就會變成私有變數,只有內部可以訪問,外部不能訪問。此時,如果直接訪問就會出錯。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def info(self):
print('hello %s,you are %s years old!' % (self.__name, self.__age))
stu = Student('小明','23')
stu.info()
stu.__name = '小張'
stu.__age = '24
stu.info()
輸出:
Traceback (most recent call last):
File "D:/pyspace/hellopython/Chapter8.py", line 60, in <module>
stu.info()
File "D:/pyspace/hellopython/Chapter8.py", line 57, in info
print('hello %s,you are %s years old!' % (self.name,self.age))
AttributeError: 'Student' object has no attribute 'name'
這時,可以通過為類增加get_attrs()方法,獲取類中的私有變數。可以為類新增set_attrs()方法,修改類中的私有變數。示例如下:
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def info(self):
print('hello %s,you are %s years old!' % (self.__name,self.__age))
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self,age):
self.__age = age
stu = Student('小明','23')
stu.info()
stu.set_name('小張')
print('修改後的姓名是:',stu.get_name())
stu.set_age('24')
print('修改後的年齡是',stu.get_age())
輸出:
hello 小明,you are 23 years old!
修改後的姓名是: 小張
修改後的年齡是 24
如何定義和訪問私有方法
通過在方法名前加兩個下劃線,可以把方法定義為私有方法。私有方法不能通過外部程式碼直接訪問,只能在類中通過公共方法呼叫。例項如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name,age):
self.name = name
self.age = age
def __info(self):
print('hello %s,you are %s years old!' % (self.name,self.age))
def foo(self):
self.__info()
stu = Student('小明','23')
stu.foo()
輸出:
hello 小明,you are 23 years old!
繼承
當我們定義一個class時,可以從某個現有的class繼承,定義的新class稱為子類(SubClass),而被繼承的class稱為基類、父類或者超類(BaseClass、Super Class)。繼承的格式如下:
class DerivedClassName(BaseClassName):
statemnt-1
...
statement-n
繼承語法class子類名(基類名)時,基類名寫在括號內,在元組中指明。特點如下:
- 在繼承中,基類的構造方法不會被自動呼叫,需要在子類的構造方法中專門呼叫。
- 在呼叫基類的方法時,需要加上基類的類名字首,並帶上self引數變數。區別於在類中呼叫普通方法時,不需要帶self引數。
- 在Python中,首先查詢對應型別的方法,如果在子類中找不到對應的方法,才到基類中逐個查詢。
class Animal(object):
def run(self):
print('animal is running....')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run()
cat = Cat()
cat.run()
輸出:
animal is running....
animal is running....
子類不能繼承父類的私有方法,也不能呼叫父類的私有方法。
多型
當子類和父類有相同的方法時,子類的方法會覆蓋父類的方法,在方法呼叫時,總是會首先呼叫子類的方法,這種情況就是多型。示例如下:
#! /user/bin/python3
# -*- coding:UTF-8 -*-
class Animal(object):
def run(self):
print('animal is running....')
class Dog(Animal):
def run(self):
print('dog is running....')
class Cat(Animal):
def run(self):
print('cat is running...')
dog = Dog()
dog.run()
cat = Cat()
cat.run()
print('dog是否是animal型別',isinstance(dog,Animal))
print('dog是否是dog型別',isinstance(dog,Animal))
輸出:
dog is running....
cat is running...
dog是否是animal型別 True
dog是否是dog型別 True
例如上面的示例中,dog和cat分別繼承了animal,並且分別定義了自己的run方法,最後呼叫的是各自的run方法。
使用多型的好處是:當我們需要傳入Dog,Cat等子類物件時,只需要接收父類Animal物件就可以了,因為Dog,Cat等都是Animal型別,按照Animal型別傳入引數即可。由於Animal型別都有run()方法,因此傳入的型別只要是Animal類或者是Animal的子類,都會自動呼叫例項型別的方法。
多重繼承
上一節講述的是單繼承,Python還支援多重繼承。多重繼承的類定義如下:
class DerivedClassName(Base1,Base2,Base3):
statment-1
...
statement-n
可以看到,多重繼承就是有多個基類(父類或者超類)。
需要注意圓括號中父類的順序,若父類中有相同的方法名,在子類使用時未指定,Python會從左到右搜尋。若方法在子類中未找到,則從左到右查詢父類中是否包含此方法。
獲取物件資訊
使用type()函式
判斷基本型別可以用type()函式判斷。如:
print('-----------type()函式-----------')
print(type('abc'))
print(type(123))
print(type(None))
print(type(abs))
print(type('abc')==str)
print(type(123)==int)
print('-----------判斷一個物件是否是函式-----------')
import types
def func():
pass
print(type(func)==types.FunctionType)
print(type(abs)==types.BuiltinFunctionType)
print(type(lambda x,y,z:x+y+z)==types.LambdaType)
print(type(x for x in range(1,10))==types.GeneratorType)
輸出:
-----------type()函式-----------
<class 'str'>
<class 'int'>
<class 'NoneType'>
<class 'builtin_function_or_method'>
True
True
-----------判斷一個物件是否是函式-----------
True
True
True
True
使用isinstance()函式
使用isinstance()函式可以告訴我們一個物件是否是某種型別。
print('dog是否是animal型別',isinstance(dog,Animal))
print('dog是否是dog型別',isinstance(dog,Animal))
輸出
dog是否是animal型別 True
dog是否是dog型別 True
使用dir()函式
如果要獲得一個物件的所有屬性和方法,就可以使用dir()函式。dir()函式返回一個字串的list。如:
print(dir('abc'))
輸出:
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
類的專有方法
_str_
相當於java中的to_string()方法。如:
class Student(object):
def __init__(self,name,age):
self.__name = name
self.__age = age
def __str__(self):
return '學生名稱是:%s,學生的年齡是:%s' % (self.__name,self.__age)
print(Student('小明','23'))
stu_abc = Student('小宇','24')
print(stu_abc)
輸出:
學生名稱是:小明,學生的年齡是:23
學生名稱是:小宇,學生的年齡是:24
注意:__str__方法必須返回一個字串
_iter_
如果要將一個類用於for…in迴圈,類似list或者tuple一樣,就必須實現一個_iter_()方法。該方法返回一個迭代物件,Python的for迴圈會不斷呼叫迭代物件的__next()方法,獲得迴圈的下一個值,直到遇到StopIteration錯誤時退出迴圈。如:
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個計數器a、b
def __iter__(self):
return self # 例項本身就是迭代物件,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100000: # 退出迴圈的條件
raise StopIteration();
return self.a # 返回下一個值
for n in Fib():
print(n)
輸出:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
_getitem()_
要像list一樣按照下標取出元素,需要實現__getitem()__方法,示例如下:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
print(Fib()[3])
輸出:
3
_getattr_
使用__getattr__方法動態返回一個屬性。如:
class Student1(object):
def __getattr__(self, attr):
if attr == 'score':
return 95
stu1 = Student1()
print(stu1.score)
輸出:
95
_call_
通過給一個類定義__call__方法,可以直接對例項進行呼叫並得到結果。__call__還可以定義引數。對例項進行直接呼叫就像對一個函式呼叫一樣,完全可以把物件看成函式,把函式看成物件。因為這兩者本來就沒有根本區別。如果把物件看成函式,函式本身就可以在執行期間動態創建出來,因為類的例項都是執行期間創建出來的。這樣一來,就模糊了物件和函式的界限。例項如下:
#! /usr/bin/python3
# -*- coding:UTF-8 -*-
class Student(object):
def __init__(self,name):
self.name = name
def __call__(self, *args, **kwargs):
print('姓名是:%s' % self.name)
stu2 = Student('小明')
stu2()
輸出:
姓名是:小明
可以使用Callable()函式,判斷一個物件是否可以被呼叫。例項如下:
print(callable(Student('小明')))
print(callable(Student1()))
print(callable(max))
print(callable([1,2,3]))
輸出:
True
False
True
False
牛刀小試——出行建議
小智今天想出去,但不清楚今天的天氣是否適合出行,需要一個給他提供建議的程式,程式建議要求輸入daytime和night,根據可見度和溫度給出出行建議和使用的交通工具,需要考慮需求變更的可能。
需求分析:使用本章所學的封裝、繼承、多型比較容易實現,由父類封裝檢視可見度和溫度的方法,子類繼承父類。如果有需要,子類可以覆蓋父類的方法,做自己的實現。子類也可以自定義方法。
class WeatherSeach(object):
def __init__(self,input_daytime):
self.input_daytime = input_daytime
def search_visible(self):
visible = 0
if self.input_daytime == 'daytime':
visible =