1. 程式人生 > >第八章 面向物件程式設計

第八章 面向物件程式設計

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子類名(基類名)時,基類名寫在括號內,在元組中指明。特點如下:

  1. 在繼承中,基類的構造方法不會被自動呼叫,需要在子類的構造方法中專門呼叫。
  2. 在呼叫基類的方法時,需要加上基類的類名字首,並帶上self引數變數。區別於在類中呼叫普通方法時,不需要帶self引數。
  3. 在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 =