日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

简明Python教程学习笔记_6_面向对象编程

發布時間:2024/7/23 python 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简明Python教程学习笔记_6_面向对象编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

?

  • 面向對象編程https://www.liaoxuefeng.com/wiki/897692888725344/923030496738368
  • 面向對象高級編程https://www.liaoxuefeng.com/wiki/897692888725344/923030538012640

?

?

1、對象

?

類?和?對象?是面向對象編程的兩個主要方面。

  • 是創建一個 新類型
  • 對象是這個類 的 實例
類(Class)::用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。 對象 :通過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。 實例化 :創建一個類的實例,類的具體對象。

方法:在 Python 中,方法(method)專指 "類中定義的函數",即在類中的函數才叫方法

方法?和?函數?在 python中是不同的概念,

  • 方法類中定義的函數,即在類中的函數才叫方法
  • 函數是就是一般的函數
數據成員: 類變量或者實例變量用于處理類及其實例對象的相關的數據。 方法重載: 如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),重載。 實例變量: 定義在方法中的變量,只作用于當前實例的類。 類變量 : 類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作為實例變量使用。 繼承 : 即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。

給 C/C++/Java/C#程序員的注釋

注意:Python 中即便是整數也被作為對象 ( 屬于int類 )。這和 C++、Java(1.5版之前)把整數純粹作為類型是不同的。通過 help(int) 了解更多這個類的詳情。?
? ? ?C#和Java 1.5程序員會熟悉這個概念,因為它類似與封裝與解封裝 的概念。
? ? ?對象可以使用普通的 屬于 對象的變量存儲數據。屬于一個對象或類的變量被稱為域。
? ? ?對象也可以使用 屬于類的函數來具有功能。這樣的函數被稱為類的方法。
? ? ?這些術語幫助我們把它們與孤立的函數和變量區分開來。域和方法可以合稱為類的屬性。
? ? ?域有兩種類型——屬于每個實例/類的對象或屬于類本身。它們分別被稱為實例變量和類變量。
? ? ?類使用class關鍵字創建。類的域和方法被列在一個縮進塊中。

在 Python類中定義的方法通常有三種:實例方法、類方法、靜態方法。這三者之間的區別是:

  • 實例方法:一般都以 self 作為第一個參數,必須和具體的對象實例進行綁定才能訪問。實例方法的第一個參數是實例對象 self,那么通過 self 引用的可能是類屬性、也有可能是實例屬性(這個需要具體分析)。不過在存在相同名稱的類屬性和實例屬性的情況下,實例屬性優先級更高。可以直接在類外通過對象名訪問,如果想定義成私有的,則需在前面加2個下劃線 ' __'
  • 類方法 ? ?:以 cls 作為第一個參數,cls表示類本身,定義時使用@classmethod,那么通過 cls 引用的必定是類對象的屬性和方法;
  • 靜態方法:不需要默認的任何參數,跟一般的普通函數類似。定義的時候使用@staticmethod,靜態方法中不需要額外定義參數,因此在靜態方法中引用類屬性的話,必須通過類對象來引用。

構造方法 __init__() 析構方法 __del__()

  • __init__(self) 方法是一種特殊的方法,被稱為類的構造函數或初始化方法,當創建了這個類的實例時就會調用該方法。構造方法支持重載,如果沒有構造方法,系統就自動執行默認的構造方法。
  • __del__(self) 方法也是一種特殊的方法,叫做 析構方法。在釋放對象時調用,支持重載,可以在里面進行一些釋放資源的操作,不需要顯示調用。

?

創建 實例對象?類的實例

?

要創建一個 類的實例,你可以使用類的名稱,并通過 __init__ 方法接受參數。

class Employee(object):def __init__(self, name, age):self.name = nameself.age = agepassdef __del__(self):passdef display_employee(self):print(f'name:{self.name}, age:{self.age}')emp1 = Employee("one", 2000) # "創建 Employee 類的第一個對象" emp2 = Employee("two", 5000) # "創建 Employee 類的第二個對象"

?

訪問 屬性

# 使用點(.)來訪問對象的屬性。使用如下類的名稱訪問類變量: emp1.display_employee() # 你可以添加,刪除,修改類的屬性,如下所示: emp1.age = 7 # 添加一個 'age' 屬性 emp1.age = 8 # 修改 'age' 屬性 del emp1.age # 刪除 'age' 屬性

示例:

class TestClass(object):name = 'TestClass'passobj = TestClass()t_name = getattr(obj, 'name') # 訪問對象的屬性。 print(t_name)is_have = hasattr(obj, 'name') # 檢查是否存在一個屬性。 print(is_have)setattr(obj, 'age', 100) # 設置一個屬性。如果屬性不存在,會創建一個新屬性。 # delattr(obj, 'name') # 刪除屬性。

?

Python內置的?屬性

  • __dict__ : ?類的屬性(包含一個字典,由類的數據屬性組成)
  • __doc__ ?: ?類的文檔字符串
  • __name__ : ?類名
  • __module__: 類定義所在的模塊(類的全名是'__main__.className',如果類位于一個導入模塊mymod中,那么className.__module__ 等于 mymod)
  • __bases__ : 類的所有父類構成元素(包含了以個由所有父類組成的元組) ?

?

Python對象 的 銷毀(垃圾回收)

在 Python 內部記錄著所有使用中的對象各有多少引用。一個內部跟蹤變量,稱為一個引用計數器。當對象被創建時, 就創建了一個引用計數, 當這個對象不再需要時, 這個對象的引用計數變為0 時,它被垃圾回收。但是回收不是"立即"的, 由解釋器在適當的時機,將垃圾對象占用的內存空間回收。

?

self 參數

?

類的方法普通的函數只有一個特別的區別:即?類的方法?第一個參數必須是 self。但是在調用這個方法的時候你不為這個參數賦值,Python會提供這個值。這個特別的變量指對象本身,按照慣例它的名稱是self。雖然你可以給這個參數任何名稱,但是 強烈建議 你使用self這個名稱——其他名稱都是不贊成你使用的。使用一個標準的名稱有很多優點——你的程序讀者可以迅速識別它,如果使用self的話,還有些IDE(集成開發環境)也可以幫助你。

給C/C++/Java/C#程序員的注釋

Python 中的 self 等價于 C++ 中的 this?指針。

Python 如何給 self 賦值以及為何你不需要給它賦值?舉一個例子會使此變得清晰:假如你有一個類稱為 MyClass 和這個類的一個實例MyObject。當你調用這個對象的方法 MyObject.method(arg1, arg2) 的時候,這會由 Python 自動轉為 MyClass.method(MyObject, arg1, arg2),這就是self的原理了。這也意味著如果你有一個不需要參數的方法,你還是得給這個方法定義一個self 參數。

?

創建一個

?

#!/usr/bin/python # Filename: simplestclass.pyclass Person:pass # An empty blockp = Person() print(p)# 結果 # <__main__.Person object at 0x0000023C03EA97F0>

使用 class 后跟類名,創建一個新的類。這后面跟著一個縮進的語句塊形成類體。這個例子中,使用 pass 語句表示空語句。然后使用類名后跟一對圓括號來創建一個對象/實例。為了驗證,我們簡單地打印了這個變量的類型。它告訴我們我們已經在__main__模塊中有了一個Person類的實例。可以注意到存儲對象的計算機內存地址也打印了出來。這個地址在你的計算機上會是另外一個值,因為Python可以在任何空位存儲對象。

?

使用 對象的方法

#!/usr/bin/python # Filename: method.pyclass Person:def sayHi(self):print('Hello, how are you?')p = Person() p.sayHi() # This short example can also be written as Person().sayHi()# 輸出 # $ python method.py # Hello, how are you?

這里可以看到 self 的用法。注意 sayHi 方法沒有任何參數,但仍然在函數定義時有 self。

?

__init__?方法

?

在 Python 的類中,有很多方法的名字有特殊的重要意義。現在看下 __init__方法的意義。__init__ 方法在類的一個對象被建立時,馬上運行。這個方法可以用來對你的對象做一些你希望的 初始化 。注意,這個名稱的開始和結尾都是雙下劃線 __init__

#!/usr/bin/python # Filename: class_init.pyclass Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop') p.sayHi()Person('Swaroop').sayHi()# 輸出 # $ python class_init.py # Hello, my name is Swaroop # Hello, my name is Swaroop

這個__init__方法有兩個參數,分別是 selfname,方法里面是 self.name = name。注意它們是兩個不同的變量,盡管它們有相同的名字。點號使我們能夠區分它們。最重要的是,我們沒有專門調用__init__方法,只是在創建一個類的新實例的時候,把參數包括在圓括號內跟在類名后面,從而傳遞給__init__方法。

給C/C++/Java/C#程序員的注釋:__init__方法類似于C++、C#和Java中的 constructor 。

?

?

2、類?與?對象的方法

?

已經討論了類與對象的功能部分,現在來看一下它的數據部分。

事實上,它們只是與類和對象的名稱空間 綁定 的普通變量,即這些名稱只在這些類與對象的前提下有效。

兩種類型的 域 ——類的變量(屬于類 的作用域)對象的變量(屬于實例的作用域),它們根據是類還是對象 擁有 這個變量而區分。

類的變量 由一個類的所有對象(實例)共享使用。只有一個類變量的拷貝,所以當某個對象對類的變量做了改動的時候,這個改動會反映到所有其他的實例上。

對象的變量 由類的每個對象/實例擁有。因此每個對象有自己對這個域的一份拷貝,即它們不是共享的,在同一個類的不同實例中,雖然對象的變量有相同的名稱,但是是互不相關的。通過一個例子會使這個易于理解。

?

使用 對象?的 變量

?

#!/usr/bin/python # Filename: objvar.pyclass Person:"""Represents a person."""population = 0def __init__(self, name):"""Initializes the person's data."""self.name = nameprint('(Initializing %s)' % self.name)# When this person is created, he/she# adds to the populationPerson.population += 1def __del__(self):"""I am dying."""print('%s says bye.' % self.name)Person.population -= 1if Person.population == 0:print('I am the last one.')else:print('There are still %d people left.'% Person.population)def sayHi(self):"""Greeting by the person.Really, that's all it does."""print('Hi, my name is %s.' % self.name)def howMany(self):"""Prints the current population."""if Person.population == 1:print('I am the only person here.')else:print('We have %d persons here.'% Person.population)swaroop = Person('Swaroop') swaroop.sayHi() swaroop.howMany()kalam = Person('Abdul Kalam') kalam.sayHi() kalam.howMany()swaroop.sayHi() swaroop.howMany()

結果:

(Initializing Swaroop) Hi, my name is Swaroop. I am the only person here. (Initializing Abdul Kalam) Hi, my name is Abdul Kalam. We have 2 persons here. Hi, my name is Swaroop. We have 2 persons here. Swaroop says bye. There are still 1 people left. Abdul Kalam says bye. I am the last one.

這里,

  • population 屬于 Person類因此是一個類的變量
  • name變量?屬于?對象(它使用self賦值),因此是對象的變量

在 __init__ 方法中,我們讓 population 增加1,這是因為我們增加了一個人。同樣可以發現,self.name 的值根據每個對象指定,這表明了它作為對象的變量的本質。

記住,你只能使用 self變量 來 引用 同一個對象的變量和方法。

在這個程序中,還可以看到 docstring 對于類和方法同樣有用。我們可以在運行時使用 Person.__doc__ 和 Person.sayHi.__doc__來分別訪問類與方法的文檔字符串,就如同 __init__ 方法一樣,還有一個特殊的方法 __del__,它在對象消逝的時候被調用。對象消逝即對象不再被使用,它所占用的內存將返回給系統作它用。在這個方法里面,我們只是簡單地把 Person.population 減 1。
當對象不再被使用時,__del__方法運行,但很難保證這個方法究竟在 什么時候 運行。如果你想要指明它的運行,你就得使用 del 語句。

?

給C/C++/Java/C#程序員的注釋

  • Python 中所有的?類成員(包括數據成員)都是 公共的 ,所有的方法都是 有效的 。
  • 只有一個例外:如果你使用的數據成員名稱以 "雙下劃線前綴", 比如 __privatevar,Python 的名稱管理體系會有效地把它作為私有變量
  • 這樣就有一個慣例,如果某個變量?只想在類或對象中使用,就應該以 "單下劃線前綴"。而其他的名稱都將作為公共的,可以被其他類/對象使用。記住這只是一個慣例,并不是Python所要求的(與雙下劃線前綴不同)。
  • 同樣,注意 __del__ 方法與 destructor 的概念類似。

?

?

3、繼承

?

面向對象的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過 繼承 機制。繼承完全可以理解成:類之間的類型和子類型 關系。
假設寫個程序來記錄學校中教師和學生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學生的成績和學費。
可以為教師和學生建立兩個獨立的類來處理它們,但這樣做的話,如果要增加一個新的共有屬性,就意味著要在這兩個獨立的類中都增加這個屬性。這很快就會顯得不實用。
一個比較好的方法是創建一個共同的類稱為SchoolMember然后讓教師和學生的類 繼承 這個共同的類。即它們都是這個類型(類)的子類型,然后我們再為這些子類型添加專有的屬性。使用這種方法有很多優點。如果我們增加/改變了SchoolMember中的任何功能,它會自動地反映到子類型之中。例如,你要為教師和學生都增加一個新的身份證域,那么你只需簡單地把它加到SchoolMember類中。然而,在一個子類型之中做的改動不會影響到別的子類型。另外一個優點是你可以把教師和學生對象都作為SchoolMember對象來使用,這在某些場合特別有用,比如統計學校成員的人數。一個子類型在任何需要父類型的場合可以被替換成父類型,即對象可以被視作是父類的實例,這種現象被稱為多態現象。另外,我們會發現在 重用 父類的代碼的時候,我們無需在不同的類中重復它。而如果我們使用獨立的類的話,我們就不得不這么做了。
在上述的場合中,SchoolMember類被稱為 基本類 或 超類 。而Teacher和Student類被稱為導出類 或子類 。
現在,我們將學習一個例子程序。

?

使用繼承

?

# !/usr/bin/python # Filename: inherit.pyclass SchoolMember:"""Represents any school member."""def __init__(self, name, age):self.name = nameself.age = ageprint('(Initialized SchoolMember: %s)' % self.name)def tell(self):"""Tell my details."""print('Name:"%s" Age:"%s"' % (self.name, self.age))class Teacher(SchoolMember):"""Represents a teacher."""def __init__(self, name, age, salary):SchoolMember.__init__(self, name, age)self.salary = salaryprint('(Initialized Teacher: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Salary: "%d"' % self.salary)class Student(SchoolMember):"""Represents a student."""def __init__(self, name, age, marks):SchoolMember.__init__(self, name, age)self.marks = marksprint('(Initialized Student: %s)' % self.name)def tell(self):SchoolMember.tell(self)print('Marks: "%d"' % self.marks)t = Teacher('Mrs. Shrividya', 40, 30000) s = Student('Swaroop', 22, 75)print() # prints a blank linemembers = [t, s] for member in members:member.tell() # works for both Teachers and Students# 輸出 # (Initialized SchoolMember: Mrs. Shrividya) # (Initialized Teacher: Mrs. Shrividya) # (Initialized SchoolMember: Swaroop) # (Initialized Student: Swaroop) # # Name:"Mrs. Shrividya" Age:"40" # Salary: "30000" # Name:"Swaroop" Age:"22" # Marks: "75"

為了使用繼承,我們把基本類的名稱作為一個元組跟在定義類時的類名稱之后。然后,我們注意到基本類的__init__方法專門使用self變量調用,這樣我們就可以初始化對象的基本類部分。這一點十分重要——Python不會自動調用基本類的constructor,你得親自專門調用它。我們還觀察到我們在方法調用之前加上類名稱前綴,然后把self變量及其他參數傳遞給它。注意,在我們使用SchoolMember類的tell方法的時候,我們把Teacher和Student的實例僅僅作為SchoolMember的實例。另外,在這個例子中,我們調用了子類型的tell方法,而不是SchoolMember類的tell方法。可以這樣來理解,Python總是首先查找對應類型的方法,在這個例子中就是如此。如果它不能在導出類中找到對應的方法,它才開始到基本類中逐個查找。基本類是在類定義的時候,在元組之中指明的。一個術語的注釋——如果在繼承元組中列了一個以上的類,那么它就被稱作 多重繼承 。

?

繼承語法 :

基類名寫在括號里,基本類是在類定義的時候,在元組之中指明的。

class 派生類名( 基類1,[基類2, 基類3, ... 基類N] ):pass

示例:

class SubClassName (ParentClass1[, ParentClass2, ...]):'Optional class documentation string'class_suite

?

單繼承

?

示例:

# coding=utf-8 # !/usr/bin/python class Parent: # 定義父類parentAttr = 100def __init__(self):print("調用父類構造函數")def parentMethod(self):print('調用父類方法')def setAttr(self, attr):Parent.parentAttr = attrdef getAttr(self):print("父類屬性 :", Parent.parentAttr)class Child(Parent): # 定義子類def __init__(self):print("調用子類構造方法")def childMethod(self):print('調用子類方法 child method')c = Child() # 實例化子類 c.childMethod() # 調用子類的方法 c.parentMethod() # 調用父類方法 c.setAttr(200) # 再次調用父類的方法 c.getAttr() # 再次調用父類的方法

執行結果如下:

調用子類構造方法 調用子類方法 child method 調用父類方法 父類屬性 : 200

?

多繼承

?

class A: # 定義類 A
? ? .....
class B: # 定義類 B
? ? .....
class C(A, B): # 繼承類 A 和 B
? ? .....
你可以使用issubclass()或者isinstance()方法來檢測。
issubclass() - 布爾函數判斷一個類是另一個類的子類或者子孫類,語法:issubclass(sub,sup)
isinstance(obj, Class) 布爾函數如果obj是Class類的實例對象或者是一個Class子類的實例對象則返回true。

?

在 Python 中繼承中的一些特點:

1:在繼承中基類的構造(__init__()方法)不會被自動調用,它需要在其派生類的構造中親自專門調用。 2:在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數變量。區別于在類中調用普通函數時并不需要帶上self參數 3:Python總是首先查找對應類型的方法,如果它不能在派生類中找到對應的方法,它才開始到基類中逐個查找。(先在本類中查找調用的方法,找不到才去基類中找)。 如果在繼承元組中列了一個以上的類,那么它就被稱作"多重繼承" 。

?

super()? 【繼承順序說明】

?

在 Python 類的方法(method)中,要調用父類的某個方法,在Python 2.2以前,通常的寫法如下:

class A:def __init__(self):print "enter A"print "leave A"class B(A):def __init__(self):print "enter B"A.__init__(self)print "leave B">>> b = B() enter B enter A leave A leave B

使用非綁定的類方法(用類名來引用的方法),并在參數列表中,引入待綁定的對象(self),從而達到調用父類的目的。這樣做的缺點是,當一個子類的父類發生變化時(如類B的父類由A變為C時),必須遍歷整個類定義,把所有的通過非綁定的方法的類名全部替換過來,例如下面代碼,

class B(C): # A --> Cdef __init__(self):print "enter B"C.__init__(self) # A --> Cprint "leave B"

如果代碼簡單,這樣的改動或許還可以接受。但如果代碼量龐大,這樣的修改可能是災難性的。因此,自Python 2.2開始,Python 添加了一個關鍵字 super,來解決這個問題。

class A(object): # A must be new-style classdef __init__(self):print "enter A"print "leave A"class B(C): # A --> Cdef __init__(self):print "enter B"super(B, self).__init__()print "leave B"

嘗試執行上面同樣的代碼,結果一致,但修改的代碼只有一處,把代碼的維護量降到最低,是一個不錯的用法。因此在開發過程中,super 關鍵字被大量使用,

在我們的印象中,對于 super(B, self).__init__() 是這樣理解的:super(B, self) 首先找到B的父類(就是類A),然后把類B的對象 self 轉換為類A的對象,然后 "被轉換"?的類A對象調用自己的__init__函數。考慮到 super 中只有指明子類的機制,因此,在多繼承的類定義中,通常我們保留使用類似代碼段1的方法。

有一天某同事設計了一個相對復雜的類體系結構(我們先不要管這個類體系設計得是否合理,僅把這個例子作為一個題目來研究就好),代碼如下:

class A(object):def __init__(self):print "enter A"print "leave A"class B(object):def __init__(self):print "enter B"print "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D"class E(B, C):def __init__(self):print "enter E"B.__init__(self)C.__init__(self)print "leave E"class F(E, D):def __init__(self):print "enter F"E.__init__(self)D.__init__(self)print "leave F"f = F()結果: enter F enter E enter B leave B enter C enter D enter A leave A leave D leave C leave E enter D enter A leave A leave D leave F

明顯地,類A和類D的初始化函數被重復調用了2次,這并不是我們所期望的結果!我們所期望的結果是最多只有類A的初始化函數被調用2次——其實這是多繼承的類體系必須面對的問題。我們把代碼段4的類體系畫出來,如下圖:

按我們對 super 的理解,從圖中可以看出,在調用類C的初始化函數時,應該是調用類A的初始化函數,但事實上卻調用了類D的初始化函數。好一個詭異的問題!

也就是說,mro中記錄了一個類的所有基類的類類型序列。查看 mro 的記錄,發覺包含7個元素,7個類名分別為:F E B C D A object

從而說明了為什么在 C.__init__ 中使用 super(C, self).__init__() 會調用類D的初始化函數了。?

我們把代碼段改寫為:

class A(object):def __init__(self):print "enter A"super(A, self).__init__() # newprint "leave A"class B(object):def __init__(self):print "enter B"super(B, self).__init__() # newprint "leave B"class C(A):def __init__(self):print "enter C"super(C, self).__init__()print "leave C"class D(A):def __init__(self):print "enter D"super(D, self).__init__()print "leave D" class E(B, C):def __init__(self):print "enter E"super(E, self).__init__() # changeprint "leave E"class F(E, D):def __init__(self):print "enter F"super(F, self).__init__() # changeprint "leave F"f = F()結果: enter F enter E enter B enter C enter D enter A leave A leave D leave C leave B leave E leave F

明顯地,F 的初始化不僅完成了所有的父類的調用,而且保證了每一個父類的初始化函數只調用一次。再看類結構:

E-1,D-2 是 F 的父類,其中表示 E類在前,即 F(E,D)。所以初始化順序可以從類結構圖來看出 : F --->?E ---> B --> C --> D --> A

由于C,D 有同一個父類,因此會先初始化 D 再是 A。

延續的討論

  我們再重新看上面的類體系圖,如果把每一個類看作圖的一個節點,每一個從子類到父類的直接繼承關系看作一條有向邊,那么該體系圖將變為一個有向圖。不能發現mro的順序正好是該有向圖的一個拓撲排序序列。

  從而,我們得到了另一個結果——Python是如何去處理多繼承。支持多繼承的傳統的面向對象程序語言(如C++)是通過虛擬繼承的方式去實現多繼承中父類的構造函數被多次調用的問題,而Python則通過mro的方式去處理。

  但這給我們一個難題:對于提供類體系的編寫者來說,他不知道使用者會怎么使用他的類體系,也就是說,不正確的后續類,可能會導致原有類體系的錯誤,而且這樣的錯誤非常隱蔽的,也難于發現。

小結

  • 1. super 并不是一個函數,是一個類名,形如super(B, self)事實上調用了super類的初始化函數,產生了一個super對象;
  • 2. super類的初始化函數并沒有做什么特殊的操作,只是簡單記錄了類類型和具體實例;
  • 3. super(B, self).func的調用并不是用于調用當前類的父類的func函數;
  • 4. Python的多繼承類是通過mro的方式來保證各個父類的函數被逐一調用,而且保證每個父類函數只調用一次(如果每個類都使用super);
  • 5. 混用super類和非綁定的函數是一個危險行為,這可能導致應該調用的父類函數沒有調用或者一個父類函數被調用多次。

示例:

class A(object):x = 1class B(A):passclass C(A):passclass D(B, C):passprint(A.x, B.x, C.x, D.x) # 1 1 1 1C.x = 2 print(A.x, B.x, C.x, D.x) # 1 1 2 2B.x = 3 print(A.x, B.x, C.x, D.x) # 1 3 2 3

?

方法重寫

?

如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法,示例:

#coding=utf-8 #!/usr/bin/python class Parent: # 定義父類def myMethod(self):print '調用父類方法'class Child(Parent): # 定義子類def myMethod(self):print '調用子類方法'c = Child() # 子類實例 c.myMethod() # 子類調用重寫方法

輸出結果如下:

調用子類方法 基礎重載方法

?

Python?運算符重載

?

示例:

#!/usr/bin/python class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self,other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2,10) v2 = Vector(5,-2) print v1 + v2 以上代碼執行結果如下所示: Vector(7,8)

屬性方法

  • 類?私有屬性:__private_attrs:兩個下劃線開頭,聲明該屬性為私有,不能在類地外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs。
  • 類?方法 ? ?:以cls作為第一個參數,cls表示類本身,定義時使用@classmethod,那么通過cls引用的必定是類對象的屬性和方法;

實例方法:一般都以self作為第一個參數,必須和具體的對象實例進行綁定才能訪問,

靜態方法:不需要默認的任何參數,跟一般的普通函數類似。定義的時候使用@staticmethod,靜態方法中不需要額外定義參數,因此在靜態方法中引用類屬性的話,必須通過類對象來引用。

# coding=utf-8 # !/usr/bin/python class JustCounter:__secretCount = 0 # 私有變量publicCount = 0 # 公開變量def count(self):self.__secretCount += 1self.publicCount += 1print(self.__secretCount)counter = JustCounter() counter.count() counter.count() print(counter.publicCount) print(counter.__secretCount) # 報錯,實例不能訪問私有變量

上面代碼最后一行報錯,因為 Python不允許 實例化的對象 訪問私有數據但你可以使用 object._className__attrName 訪問屬性。

將上面最后一行代碼換成:print(counter._JustCounter__secretCount)? 即可訪問私有屬性

?

python _、__和__xx__的區別

?

來源:http://www.cnblogs.com/coder2012/p/4423356.html

來源:https://segmentfault.com/a/1190000002611411

http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python
http://www.zhihu.com/question/19754941

?

"_"單下劃線

Python 中不存在真正的私有方法。為了實現類似于 C++ 中私有方法,可以在類的方法或屬性前加一個 "_"?單下劃線,意味著該方法或屬性不應該去調用,它并不屬于API。

在使用 property 時,經常出現這個問題:

class BaseForm(StrAndUnicode):...def _get_errors(self):"Returns an ErrorDict for the data provided for the form"if self._errors is None:self.full_clean()return self._errorserrors = property(_get_errors)

上面的代碼片段來自于 django 源碼(django/forms/forms.py)。這里的 errors 是一個屬性,屬于 API 的一部分,但是 _get_errors 是私有的,是不應該訪問的,但可以通過 errors 來訪問該錯誤結果。

?

"__"雙下劃線

這個雙下劃線更會造成更多混亂,但它并不是用來標識一個方法或屬性是私有的,真正作用是用來避免子類覆蓋其內容。

讓我們來看一個例子:

class A(object): def __method(self): print "I'm a method in A" def method(self): self.__method() a = A() a.method()

輸出是這樣的:

$ python example.py I'm a method in A

很好,出現了預計的結果。 現在我們給A添加一個子類,并重新實現一個__method:

class B(A): def __method(self): print "I'm a method in B" b = B() b.method()

現在,結果是這樣的:

$ python example.py I'm a method in A

就像我們看到的一樣,B.method()不能調用B.__method的方法。實際上,它是"__"兩個下劃線的功能的正常顯示。

因此,在我們創建一個以"__"兩個下劃線開始的方法時,這意味著這個方法不能被重寫,它只允許在該類的內部中使用。

在Python中如是做的?很簡單,它只是把方法重命名了,如下:?

a = A() a._A__method() # never use this!! please! $ python example.py I'm a method in A

如果你試圖調用a.__method,它還是無法運行的,就如上面所說,只可以在類的內部調用__method。

?

"__xx__"前后各雙下劃線 (特殊的屬性和方法)

當你看到"__this__"的時,就知道不要調用它。為什么?因為它的意思是它是用于Python調用的,如下:

>>> name = "igor" >>> name.__len__() 4 >>> len(name) 4 >>> number = 10 >>> number.__add__(20) 30 >>> number + 20 30

“__xx__”經常是操作符或本地函數調用的magic methods。在上面的例子中,提供了一種重寫類的操作符的功能。

在特殊的情況下,它只是python調用的hook。例如,__init__()函數是當對象被創建初始化時調用的;__new__()是用來創建實例。

class CrazyNumber(object):def __init__(self, n): self.n = n def __add__(self, other): return self.n - other def __sub__(self, other): return self.n + other def __str__(self): return str(self.n) num = CrazyNumber(10) print num # 10 print num + 5 # 5 print num - 20 # 30

另一個例子

class Room(object):def __init__(self): self.people = [] def add(self, person): self.people.append(person) def __len__(self): return len(self.people)room = Room() room.add("Igor") print len(room) # 1

?

結論

  • 使用 _one_underline 來表示該方法或屬性是私有的,不屬于API;
  • 當創建一個用于 python 調用或一些特殊情況時,使用 __two_underline__;
  • 使用 __just_to_underlines,來避免子類的重寫!

?

?

4、Python中面向對象編程

?

一. 如何定義一個類

  在進行python面向對象編程之前,先來了解幾個術語:類,類對象,實例對象,屬性,函數和方法。

  類是對現實世界中一些事物的封裝,定義一個類可以采用下面的方式來定義:

class className: // blockpass

  注意類名后面有個冒號,在 block 塊里面就可以定義屬性和方法了。當一個類定義完之后,就產生了一個類對象。類對象支持兩種操作:引用和實例化。引用操作是通過類對象去調用類中的屬性或者方法,而實例化是產生出一個類對象的實例,稱作實例對象。比如定義了一個people類:

class People:name = 'jack' # 定義一個 類 的 屬性 # 定義一個 實例 的 方法 def printName(self):print(self.name)

  people類定義完成之后就產生了一個全局的類對象,可以通過類對象來訪問類中的屬性和方法了。當通過people.name(至于為什么可以直接這樣訪問屬性后面再解釋,這里只要理解類對象這個概念就行了)來訪問時,people.name中的people稱為類對象,這點和C++中的有所不同。當然還可以進行實例化操作,p=people( ),這樣就產生了一個people的實例對象,此時也可以通過實例對象p來訪問屬性或者方法了(p.name).

  理解了類、類對象和實例對象的區別之后,我們來了解一下Python中屬性、方法和函數的區別。

  在上面代碼中注釋的很清楚了,name是一個屬性,printName( )是一個方法,與某個對象進行綁定的函數稱作為方法。一般在類里面定義的函數與類對象或者實例對象綁定了,所以稱作為方法;而在類外定義的函數一般沒有同對象進行綁定,就稱為函數。

?

二. 屬性

在類中我們可以定義一些屬性,比如: class People:name = 'jack'age = 12p = People() print(p.name, p.age)

  定義了一個 People 類,里面定義了name和age屬性,默認值分別為'jack'和12。在定義了類之后,就可以用來產生實例化對象了,這句p = People( )實例化了一個對象p,然后就可以通過p來讀取屬性了。這里的name和age都是公有的,可以直接在類外通過對象名訪問,如果想定義成私有的,則需在前面加2個下劃線 ' __'。

class People:__name = 'jack'__age = 12p = People() print(p.__name, p.__age)

  這段程序運行會報錯:

Traceback?(most?recent?call?last):??File?"C:/PycharmProjects/FirstProject/oop.py",?line?6,?in?<module>??print?p.__name,p.__age?? AttributeError:?people?instance?has?no?attribute?'__name??

  提示找不到該屬性,因為私有屬性是不能夠在類外通過對象名來進行訪問的。在Python中沒有像C++中public和private這些關鍵字來區別公有屬性和私有屬性,它是以屬性命名方式來區分,如果在屬性名前面加了2個下劃線'__',則表明該屬性是私有屬性,否則為公有屬性(方法也是一樣,方法名前面加了2個下劃線的話表示該方法是私有的,否則為公有的)。

?

三. 方法

在類中可以根據需要定義一些方法,定義方法采用def關鍵字,在類中定義的方法至少會有一個參數,一般以名為'self'的變量作為該參數(用其他名稱也可以),而且需要作為第一個參數。下面看個例子: class People:__name = 'jack'__age = 12def getName(self):return self.__namedef getAge(self):return self.__agep = People() print(p.getName(), p.getAge())

  如果對 self 不好理解的話,可以把它當做C++中類里面的this指針一樣理解,就是對象自身的意思,在用某個對象調用該方法時,就將該對象作為第一個參數傳遞給self。

?

四. 類中內置的方法

  在 Python 中有一些內置的方法,這些方法命名都有比較特殊的地方(其方法名以2個下劃線開始然后以2個下劃線結束)。類中最常用的就是構造方法和析構方法。

  • 構造方法 __init__(self,....):在生成對象時調用,可以用來進行一些初始化操作,不需要顯示去調用,系統會默認去執行。構造方法支持重載,如果沒有構造方法,系統就自動執行默認的構造方法。
  • 析構方法 __del__(self):在釋放對象時調用,支持重載,可以在里面進行一些釋放資源的操作,不需要顯示調用。

還有其他的一些內置方法,比如 __cmp__( ), __len( )__等。下面是常用的內置方法:

?內置方法?說明
?__init__(self,...)?初始化對象,在創建新對象時調用
?__del__(self)?釋放對象,在對象被刪除之前調用
?__new__(cls,*args,**kwd)?實例的生成操作
?__str__(self)?在使用print語句時被調用
?__getitem__(self,key)?獲取序列的索引key對應的值,等價于seq[key]
?__len__(self)?在調用內聯函數len()時被調用
?__cmp__(stc,dst)?比較兩個對象src和dst
?__getattr__(s,name)?獲取屬性的值
?__setattr__(s,name,value)?設置屬性的值
?__delattr__(s,name)?刪除name屬性
?__getattribute__()?__getattribute__()功能與__getattr__()類似
?__gt__(self,other)?判斷self對象是否大于other對象
?__lt__(slef,other)?判斷self對象是否小于other對象
?__ge__(slef,other)?判斷self對象是否大于或者等于other對象
?__le__(slef,other)?判斷self對象是否小于或者等于other對象
?__eq__(slef,other)?判斷self對象是否等于other對象
?__call__(self,*args)?把實例對象作為函數調用

  __init__():?__init__方法在類的一個對象被建立時,馬上運行。這個方法可以用來對你的對象做一些你希望的初始化。注意,這個名稱的開始和結尾都是雙下劃線。代碼例子:

# Filename: class_init.py class Person:def __init__(self, name):self.name = namedef sayHi(self):print('Hello, my name is', self.name)p = Person('Swaroop') p.sayHi()# 輸出: # Hello, my name is Swaroop

  __new__():?__new__() 在 __init__() 之前被調用,用于生成實例對象。利用這個方法和類屬性的特性可以實現設計模式中的單例模式。單例模式是指創建唯一對象嗎,單例模式設計的類只能實例化一個對象。

# -*- coding: UTF-8 -*- class Singleton(object):__instance = None # 定義實例 def __init__(self):passdef __new__(cls, *args, **kwd): # 在__init__之前調用 if Singleton.__instance is None: # 生成唯一實例 Singleton.__instance = object.__new__(cls, *args, **kwd)return Singleton.__instance

  __getattr__()、__setattr__() 和__getattribute__() :當讀取對象的某個屬性時,python會自動調用__getattr__()方法。例如,fruit.color將轉換為fruit.__getattr__(color)。當使用賦值語句對屬性進行設置時,python會自動調用__setattr__()方法。__getattribute__()的功能與__getattr__()類似,用于獲取屬性的值。但是__getattribute__()能提供更好的控制,代碼更健壯。注意,python中并不存在__setattribute__()方法。代碼例子:

# -*- coding: UTF-8 -*- class Fruit(object):def __init__(self, color="red", price=0):self.__color = colorself.__price = pricedef __getattribute__(self, item): # 獲取屬性的方法return object.__getattribute__(self, item)def __setattr__(self, key, value):self.__dict__[key] = valueif __name__ == "__main__":fruit = Fruit("blue", 10)print(fruit.__dict__.get("_Fruit__color")) # 獲取color屬性fruit.__dict__["_Fruit__price"] = 5print(fruit.__dict__.get("_Fruit__price")) # 獲取price屬性

Python 不允許實例化的類訪問私有數據,但你可以使用 object._className__attrName 訪問這些私有屬性。

__getitem__():?如果類把某個屬性定義為序列,可以使用__getitem__()輸出序列屬性中的某個元素.假設水果店中銷售多鐘水果,可以通過__getitem__()方法獲取水果店中的沒種水果。代碼例子:

# -*- coding: UTF-8 -*- class FruitShop:def __getitem__(self, i): # 獲取水果店的水果return self.fruits[i]if __name__ == "__main__":shop = FruitShop()shop.fruits = ["apple", "banana"]print(shop[1])for item in shop: # 輸出水果店的水果print(item)# 結果 # banana # apple # banana

  __str__():?__str__()用于表示對象代表的含義,返回一個字符串.實現了__str__()方法后,可以直接使用print語句輸出對象,也可以通過函數str()觸發__str__()的執行。這樣就把對象和字符串關聯起來,便于某些程序的實現,可以用這個字符串來表示某個類。代碼例子:

# -*- coding: UTF-8 -*- class Fruit:"""''Fruit類""" # 為Fruit類定義了文檔字符串def __str__(self): # 定義對象的字符串表示return self.__doc__if __name__ == "__main__":fruit = Fruit()print(str(fruit)) # 調用內置函數str()觸發__str__()方法,輸出結果為:Fruit類print(fruit) # 直接輸出對象fruit,返回__str__()方法的值,輸出結果為:Fruit類

  __call__():?在類中實現__call__()方法,可以在對象創建時直接返回__call__()的內容。使用該方法可以模擬靜態方法。代碼例子:

# -*- coding: UTF-8 -*- class Fruit:class Growth: # 內部類def __call__(self):print("grow ...")grow = Growth() # 調用Growth(),此時將類Growth作為函數返回,即為外部類Fruit定義方法grow(),grow()將執行__call__()內的代碼if __name__ == '__main__':fruit = Fruit()fruit.grow() # 輸出結果:grow ...Fruit.grow() # 輸出結果:grow ...

?

五. 類屬性、實例屬性、類方法、實例方法以及靜態方法

  在了解了類基本的東西之后,下面看一下python中這幾個概念的區別。

  先來談一下類屬性和實例屬性

  在前面的例子中我們接觸到的就是類屬性,顧名思義,類屬性就是類對象所擁有的屬性,它被所有類對象的實例對象所共有,在內存中只存在一個副本,這個和C++中類的靜態成員變量有點類似。對于公有的類屬性,在類外可以通過類對象和實例對象訪問。

class people:name = 'jack' # 公有的類屬性 __age = 12 # 私有的類屬性 p = people()print(p.name) # 正確 print(people.name) # 正確 print(p.__age) # 錯誤,不能在類外通過實例對象訪問私有的類屬性 print(people.__age) # 錯誤,不能在類外通過類對象訪問私有的類屬性

  實例屬性是不需要在類中顯示定義的,比如:

class People:name = 'jack'p = People() p.age = 12 print(p.name) # 正確 print(p.age) # 正確print(People.name) # 正確 print(People.age) # 錯誤

對類對象People進行實例化之后,產生了一個實例對象p,然后p.age = 12這句給p添加了一個實例屬性age,賦值為12。這個實例屬性是實例對象p所特有的,注意,類對象 People 并不擁有它(所以不能通過類對象來訪問這個age屬性)。當然還可以在實例化對象的時候給age賦值。

class People:name = 'jack'# __init__()是內置的構造方法,在實例化對象時自動調用 def __init__(self, age):self.age = agep = People(12) print(p.name) # 正確 print(p.age) # 正確 print(People.name) # 正確 print(People.age) # 錯誤

  如果需要在類外修改類屬性,必須通過類對象去引用然后進行修改。如果通過實例對象去引用,會產生一個同名的實例屬性,這種方式修改的是實例屬性,不會影響到類屬性,并且之后如果通過實例對象去引用該名稱的屬性,實例屬性會強制屏蔽掉類屬性,即引用的是實例屬性,除非刪除了該實例屬性。

class People:country = 'china'print(People.country) p = People() print(p.country) p.country = 'japan' print(p.country) # 實例屬性會屏蔽掉同名的類屬性 print(People.country) del p.country # 刪除實例屬性 print(p.country)

  下面來看一下類方法、實例方法靜態方法 的區別。

  類方法:是類對象所擁有的方法,需要用修飾器"@classmethod"來標識其為類方法,對于類方法,第一個參數必須是類對象,一般以"cls"作為第一個參數(當然可以用其他名稱的變量作為其第一個參數,但是大部分人都習慣以'cls'作為第一個參數的名字,就最好用'cls'了),能夠通過實例對象和類對象去訪問。

class People:country = 'china'# 類方法,用classmethod來進行修飾 @classmethoddef getCountry(cls):return cls.countryp = People() print(p.getCountry()) # 可以用過實例對象引用 print(People.getCountry()) # 可以通過類對象引用

  類方法還有一個用途就是可以對類屬性進行修改:

class People:country = 'china'# 類方法,用classmethod來進行修飾 @classmethoddef getCountry(cls):return cls.country@classmethoddef setCountry(cls, country):cls.country = countryp = People() print(p.getCountry()) # 可以用過實例對象引用 print(People.getCountry()) # 可以通過類對象引用 p.setCountry('japan')print(p.getCountry()) print(People.getCountry())

運行結果:

china?? china?? japan?? japan?

  結果顯示在用類方法對類屬性修改之后,通過類對象和實例對象訪問都發生了改變。

  實例方法:在類中最常定義的成員方法,它至少有一個參數并且必須以實例對象作為其第一個參數,一般以名為'self'的變量作為第一個參數(當然可以以其他名稱的變量作為第一個參數)。在類外實例方法只能通過實例對象去調用,不能通過其他方式去調用。

class People:country = 'china'# 實例方法 def getCountry(self):return self.countryp = People() print(p.getCountry()) # 正確,可以用過實例對象引用 print(People.getCountry()) # 錯誤,不能通過類對象引用實例方法

  ?靜態方法:?需要通過修飾器"@staticmethod"來進行修飾,靜態方法不需要多定義參數。

class People:country = 'china'@staticmethod# 靜態方法 def getCountry():return People.countryprint(People.getCountry())

  對于類屬性和實例屬性,如果在類方法中引用某個屬性,該屬性必定是類屬性,而如果在實例方法中引用某個屬性(不作更改),并且存在同名的類屬性,此時若實例對象有該名稱的實例屬性,則實例屬性會屏蔽類屬性,即引用的是實例屬性,若實例對象沒有該名稱的實例屬性,則引用的是類屬性;如果在實例方法更改某個屬性,并且存在同名的類屬性,此時若實例對象有該名稱的實例屬性,則修改的是實例屬性,若實例對象沒有該名稱的實例屬性,則會創建一個同名稱的實例屬性。想要修改類屬性,如果在類外,可以通過類對象修改,如果在類里面,只有在類方法中進行修改。

  從類方法和實例方法以及靜態方法的定義形式就可以看出來,類方法的第一個參數是類對象cls,那么通過cls引用的必定是類對象的屬性和方法;而實例方法的第一個參數是實例對象self,那么通過self引用的可能是類屬性、也有可能是實例屬性(這個需要具體分析),不過在存在相同名稱的類屬性和實例屬性的情況下,實例屬性優先級更高。靜態方法中不需要額外定義參數,因此在靜態方法中引用類屬性的話,必須通過類對象來引用。

?

六. 繼承、多重繼承

  上面談到了類的基本定義和使用方法,這只體現了面向對象編程的三大特點之一:封裝。下面就來了解一下另外兩大特征:繼承和多態。

  在Python中,如果需要的話,可以讓一個類去繼承一個類,被繼承的類稱為父類或者超類、也可以稱作基類,繼承的類稱為子類。并且Python支持多繼承,能夠讓一個子類有多個父類。

  Python中類的繼承定義基本形式如下:

# 父類 class superClassName: block # 子類 class subClassName(superClassName): block

  在定義一個類的時候,可以在類名后面緊跟一對括號,在括號中指定所繼承的父類,如果有多個父類,多個父類名之間用逗號隔開。以大學里的學生和老師舉例,可以定義一個父類UniversityMember,然后類Student和類Teacher分別繼承類UniversityMember:

# -*- coding: UTF-8 -*- class UniversityMember:def __init__(self, name, age):self.name = nameself.age = agedef getName(self):return self.namedef getAge(self):return self.ageclass Student(UniversityMember):def __init__(self, name, age, sno, mark):UniversityMember.__init__(self, name, age) # 注意要顯示調用父類構造方法,并傳遞參數self self.sno = snoself.mark = markdef getSno(self):return self.snodef getMark(self):return self.markclass Teacher(UniversityMember):def __init__(self, name, age, tno, salary):UniversityMember.__init__(self, name, age)self.tno = tnoself.salary = salarydef getTno(self):return self.tnodef getSalary(self):return self.salary

  在大學中的每個成員都有姓名和年齡,而學生有學號和分數這2個屬性,老師有教工號和工資這2個屬性,從上面的代碼中可以看到:

  1)在Python中,如果父類和子類都重新定義了構造方法__init( )__,在進行子類實例化的時候,子類的構造方法不會自動調用父類的構造方法,必須在子類中顯示調用。

  2)如果需要在子類中調用父類的方法,需要以”父類名.方法“這種方式調用,以這種方式調用的時候,注意要傳遞self參數過去。

  對于繼承關系,子類繼承了父類所有的公有屬性和方法,可以在子類中通過父類名來調用,而對于私有的屬性和方法,子類是不進行繼承的,因此在子類中是無法通過父類名來訪問的。

  Python支持多重繼承。對于多重繼承,比如

  class SubClass(SuperClass1,SuperClass2)

  此時有一個問題就是如果SubClass沒有重新定義構造方法,它會自動調用哪個父類的構造方法?這里記住一點:以第一個父類為中心。如果SubClass重新定義了構造方法,需要顯示去調用父類的構造方法,此時調用哪個父類的構造方法由你自己決定;若SubClass沒有重新定義構造方法,則只會執行第一個父類的構造方法。并且若SuperClass1和SuperClass2中有同名的方法,通過子類的實例化對象去調用該方法時調用的是第一個父類中的方法。

?

七. 多態

  多態即多種形態,在運行時確定其狀態,在編譯階段無法確定其類型,這就是多態。Python中的多態和Java以及C++中的多態有點不同,Python中的變量是弱類型的,在定義時不用指明其類型,它會根據需要在運行時確定變量的類型(個人覺得這也是多態的一種體現),并且Python本身是一種解釋性語言,不進行預編譯,因此它就只在運行時確定其狀態,故也有人說Python是一種多態語言。在Python中很多地方都可以體現多態的特性,比如內置函數len(object),len函數不僅可以計算字符串的長度,還可以計算列表、元組等對象中的數據個數,這里在運行時通過參數類型確定其具體的計算過程,正是多態的一種體現。這有點類似于函數重載(一個編譯單元中有多個同名函數,但參數不同),相當于為每種類型都定義了一個len函數。這是典型的多態表現。有些朋友提出Python不支持多態,我是完全不贊同的。

  本質上,多態意味著可以對不同的對象使用同樣的操作,但它們可能會以多種形態呈現出結果。len(object)函數就體現了這一點。在C++、Java、C#這種編譯型語言中,由于有編譯過程,因此就鮮明地分成了運行時多態和編譯時多態。運行時多態是指允許父類指針或名稱來引用子類對象,或對象方法,而實際調用的方法為對象的類類型方法,這就是所謂的動態綁定。編譯時多態有模板或范型、方法重載(overload)、方法重寫(override)等。而Python是動態語言,動態地確定類型信息恰恰體現了多態的特征。在Python中,任何不知道對象到底是什么類型,但又需要對象做點什么的時候,都會用到多態。

  能夠直接說明多態的兩段示例代碼如下:

?

1、方法多態

# -*- coding: UTF-8 -*- _metaclass_ = type # 確定使用新式類class Calculator:def count(self, args):return 1calc = Calculator() # 自定義類型from random import choiceobj = choice(['hello,world', [1, 2, 3], calc]) # obj是隨機返回的 類型不確定 print(type(obj)) print(obj.count('a')) # 方法多態

對于一個臨時對象 obj,它通過 Python 的隨機函數取出來,不知道具體類型(是字符串、元組還是自定義類型),都可以調用count方法進行計算,至于count由誰(哪種類型)去做怎么去實現我們并不關心。

有一種稱為 "鴨子類型(?duck typing )" 的東西,講的也是多態:

_metaclass_ = type # 確定使用新式類class Duck:def quack(self):print("Quaaaaaack!")def feathers(self):print("The duck has white and gray feathers.")class Person:def quack(self):print("The person imitates a duck.")def feathers(self):print("The person takes a feather from the ground and shows it.")def in_the_forest(duck):duck.quack()duck.feathers()def game():donald = Duck()john = Person()in_the_forest(donald)in_the_forest(john)game()

  就 in_the_forest 函數而言,參數對象是一個鴨子類型,它實現了方法多態。但是實際上我們知道,從嚴格的抽象來講,Person類型和 Duck 完全風馬牛不相及。

?

2、運算符多態

def add(x, y):return x + yprint(add(1, 2)) # 輸出3 print(add("hello,", "world")) # 輸出hello,world print(add(1, "abc")) # 拋出異常 TypeError: unsupported operand type(s) for +: 'int' and 'str'

上例中,顯而易見,Python 的加法運算符是 "多態"?的,理論上,我們實現的add方法支持任意支持加法的對象,但是我們不用關心兩個參數x和y具體是什么類型。

Python同樣支持運算符重載,實例如下:

class Vector:def __init__(self, a, b):self.a = aself.b = bdef __str__(self):return 'Vector (%d, %d)' % (self.a, self.b)def __add__(self, other):return Vector(self.a + other.a, self.b + other.b)v1 = Vector(2, 10) v2 = Vector(5, -2) print(v1 + v2)

  一兩個示例代碼當然不能從根本上說明多態。普遍認為面向對象最有價值最被低估的特征其實是多態。我們所理解的多態的實現和子類的虛函數地址綁定有關系,多態的效果其實和函數地址運行時動態綁定有關。在C++, Java, C#中實現多態的方式通常有重寫和重載兩種,從上面兩段代碼,我們其實可以分析得出Python中實現多態也可以變相理解為重寫和重載。在Python中很多內置函數和運算符都是多態的。

?

參考文獻:

http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html

http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的简明Python教程学习笔记_6_面向对象编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。