python 歷險記(二)— python 的面向對象

前言

想學爬蟲仍是 python 專業啊,以前一直在用 java, 如今決定嚐嚐鮮,使用 python及爬蟲框架來完成網絡數據採集。
編程語言之間都是相通的,好比都須要模塊化,引入其餘文件來實現功能,使用列表等容器來處理數據,都要使用 jsonxml 來解析和傳輸數據。
你會發現 經過類比的方式,帶着問題去學習,會走的很快
並且我認爲 代碼示例的做用是異常強大的, 我會盡可能使用代碼示例的方式來展現,以知足同窗快速學習的須要,也備後續查詢。java

上篇文章 中,討論了 python 3 中 string, 數據結構(Dict, List, 元組)等重要的主題。
今天我會繼續探險,去征服 python 3 中的面向對象, let's go 讓咱們出發吧!python

類和對象

剛接觸 python 中的類和對象,我也和大多數小夥伴同樣迷茫,不知道它和我所熟知的 java 都有什麼異同點,爲此我還提出了一大堆問題編程

  1. 如何建立和實例化類?
  2. 是否和 java 同樣有訪問修飾符,分爲幾個級別?
  3. 構造函數該怎麼寫?
  4. 怎麼進行 class 的繼承?

下面就一一來探索這些疑惑。json

如何定義和實例化類?

在 java 中要建立一個類就必需要使用 class 關鍵字,要將類實例化,建立一個對象,可使用 new 關鍵字。在 python 中是怎麼樣的呢?網絡

先看代碼數據結構

class Person():
  """這個叫作定義體,用來解釋類的用途"""

print(Person) #  <class '__main__.Person'>
# 因爲是在程序頂層定義的,它的全名就是 '__main__.Person'

person = Person() 
print(person) #  <__main__.Person object at 0x000000000219A1D0>

要定義一個類(class) 只要將 class 關鍵字放在前面便可,類內部也能夠像 java 似的定義變量和函數,這個後面再看。框架

實例化一個類,也就是建立一個對象,並不須要使用 new 關鍵字,只需將 class 當作函數來調用就能夠啦,是否是比 java 簡潔很多。編程語言

瞭解了定義和實例化類,還有兩個問題:模塊化

  1. 要判斷一個對象是否是某個類的實例該怎麼作呢?用 isinstance
print(isinstance (person, Person)) # True
  1. 判斷對象是什麼類型,該怎麼作? 用 type
print(type(person)) # <class '__main__.Person'>

如何定義和使用屬性?

上面的代碼,光有一個空對象是幹不了任何事情的,咱們也要像 java 同樣爲其定義屬性和方法。
java 是不能動態定義一個變量的,必需要把它放在 class 中預先定義好才能夠用;而在 python 中這卻不是問題,不信看代碼~

class Person():
  """這個叫作定義體,用來解釋類的用途"""

person = Person() 
person.age = 5
print(person.age)

雖然在對 Person class 定義時沒有任何屬性的聲明,但在實例化後依然能夠添加 age 屬性,並且也並無看到如 java 中 public, private 等訪問修飾符的存在, python 中有沒有這些概念呢?還真有,變量默認就是 public 公有的,若是 在變量名前添加兩個下劃線,這樣就會認爲是 private 私有變量了,直接訪問是不能夠的。看下面代碼

class Person():
  """這個叫作定義體,用來解釋類的用途"""
  gender = 'male'
  __age = 5

person = Person() 
print(person.gender) # male
print(person.__age) # AttributeError: 'Person' object has no attribute '__age'

上面代碼中,在打印 __age 時會報錯,告知沒有找到這個屬性,其實就是 因爲使用雙下劃線作前綴使其變成私有變量了。
函數名是否是也有私有函數,是否是也在前面加雙下劃線呢 ?猜的沒錯,這個咱們後面再瞭解。

既然 python 對象的屬性操做如此靈活,能夠動態添加,那用戶在使用時就可能會碰到一些異常。
比較典型的就是,訪問一個不存在的屬性,會拋出 AttributeError。對這種狀況有兩種方式能夠處理:

  1. 預先使用內置函數 hasattr 斷定對象是否擁有該屬性(記住,只對公有變量有效哦~)
  2. 使用 try 語句處理
class Person():
  """這個叫作定義體,用來解釋類的用途"""
  gender = 'male'
  __age = 5

person = Person() 

print(hasattr(person, 'gender')) # True
print(hasattr(person, 'name')) # False
print(hasattr(person, '__age')) # False

try:
  name = person.name
except AttributeError:
  name = 'unknown'

print(name)

什麼是方法?

什麼是方法?方法和函數有什麼區別?在上一篇我就介紹了好多 string 的方法,爲何叫作方法,而不叫作 string 的函數呢?一塊兒來了解下~

  • 函數是指能夠執行某種運算,能夠經過名字來調用的一段語句的組合
  • 方法是特殊的函數,是跟一個對象或類相關聯的
  • 方法是書寫在類的定義之中,明確表示和類之間關係的
  • 在調用方法時,前面須要加上類名(函數調用語法)或者實例化的對象名(方法調用語法)

靜態方法和普通方法

調用方法分爲兩種形式,分別是

  • 函數調用語法(靜態方法)
  • 普通方法(動態方法)

先看第一種函數調用語法,這其實和 java 中的靜態方法是同樣的,只是前面不須要 static 關鍵字。

class Person:
  def print_person(person):
    print('name: %s, gender%s, age:%d' % (person.name, person.gender, person.age))


person = Person()
person.name = 'Tom'
person.gender = 'male'
person.age = 10

Person.print_person(person)

函數調用語法的方式其實和單純的函數調用,區別是不大的,由於方法前面的 class 對它沒起什麼做用,活動主體 依然是方法。

再看另一種 方法調用語法,而此次的主體則是調用該方法的 對象

class Person:
  __name = 'Tom'
  __gender = 'male'
  __age = 10

  def print_person(self):
    print('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))


person = Person()
person.print_person()

細心的同窗會發現這裏在定義方法時形參爲 self, 而在調用方法時卻沒有任何入參。
那這個 self 是什麼呢?

若是類比 java 的話,這個 self 能夠看做是 this, 其實就是對當前對象的引用。 java 中定義方法時沒必要將其作入參。而這個 self 在 python 中則是必須聲明的,在調用的時候則沒必要傳入。

注意,這個 self 可不是關鍵字哦,只要佔據方法形參的頭把交椅,你能夠用任何名字。

構造函數該怎麼寫?

在 java 中構造函數是與類同名的,並且會伴隨着實例化的動做而執行。在 python 中呢?

python 中的構造函數叫作 init 方法,全名是 __init__
具體看下面代碼

class Person():
  __gender = 'male'
  __age = '0'

  def __init__(self, gender='male', age=0):
    self.__gender = gender
    self.__age = age

person1 = Person('female', 10)
person2 = Person()
person2 = Person('male')

做爲實例方法, self 入參固然少不了,其餘參數就按照順序排開,若參數不夠,就用默認值來代替。

str 方法怎麼寫?

在java 中, 咱們通常會覆蓋 toString() 方法來返回對象中包含的值得關注的信息。 python 中也有這樣一個方法,叫作 __str__

class Person:
  __name = 'Tom'
  __gender = 'male'
  __age = 10

  def __str__(self):
    return ('name: %s, gender:%s, age:%d' % (self.__name, self.__gender, self.__age))


person = Person()
print(person)

做爲最佳實踐的一部分,建議你在每一個建立的類中都覆蓋這個方法。

多態是什麼?

還記得面向對象的幾個特徵嗎?封裝性,繼承性,多態性。嗯,來聊下 python 對多態的實現。

什麼叫作多態?

在 java 中,若是在一個 class 中有多個函數,函數名相同而參數不一樣(個數或類型不一樣),就叫作多態。

而在 python 中, 多態的概念則更進一步,對於同一個函數,若是可以處理多種類型的數據,也叫作多態。

tuple_list = [(1, 2,), (2, 3,), (4, 5)]
list = [1, 2, 3, 4]
dict1 = {
  'a' : 1,
  'b' : 2
}


def printSomething(something):
  for i in something:
      print(i)

print(tuple_list)
print(dict1)
print(list)

printSomething 一個函數能夠同時打印元組,列表以及字典,充分發揮代碼複用的功效,是否是很方便。

繼承性和 java 是同樣的嗎?

聊完了多態,再來看看面向對象的另外一個特徵:繼承性。

什麼是繼承?繼承就是定義好了一個類 A(父類);再定義一個新類 B(子類),類 B 擁有類 A 的方法和屬性,而且又定義了新的屬性和方法。類 A 稱爲父類,類 B 稱爲子類。

java 中定義兩個類的繼承關係,使用 extends 關鍵字實現,在 python 中呢?

class Father:
  """ 這是一個父類 """
  __age = 45


class Son(Father):
  """ 這是一個子類 """

python 中不須要加關鍵字來講明繼承關係,只須要將父類的名稱放在括號中就能夠了,看起來要比 java 簡潔一些。

父類和子類的初始化函數調用

前面講過, python class 中能夠定義本身的初始化函數,在實例化的時會被調用。那若是父類和子類都有初始化函數或者父類有而子類沒有,那初始化函數該如何執行呢?這裏分爲三種狀況來講明,先來看第一種。

第一種狀況,

父類有 init 而子類沒有, 這時父類的初始化函數會被默認調用

class Father():
  """ 這是一個父類 """
  def __init__(self, age):
    print("Father's init function invoke")
    self.__age = age

class Son(Father):
  """ 這是一個子類 """

son = Son(5)

這裏要注意,父類中須要的 age 參數必定要傳進去哦,要否則會報錯的。

第二種狀況

父類,子類都有 init ,而子類沒有顯式調用父類的 init 方法時,父類初始化函數是不會被調用的

class Father():
  """ 這是一個父類 """
  def __init__(self, age):
    print("Father's init function invoke")
    self.__age = age

  def get_age(self):
    return self.__age

class Son(Father):
  """ 這是一個子類 """
  def __init__(self, age):
    print("Son's init function invoke")
    self.__age = age


son = Son(5) # Son's init function invoke
print(son.get_age()) # AttributeError: 'Son' object has no attribute '_Father__age'

細心的同窗會發現,代碼中的最後一句報錯了,表示 Son 對象沒有 Father 類的 __age 變量。這是由於

  • 父類的初始化函數沒有執行,父類的 __age 變量則沒有初始化
  • get_age 函數是被子類從父類繼承來的,返回的是父類的 __age 變量

那我要是想解決這個錯誤,該怎麼作呢?有兩種方法

  1. 在子類 Son 的初始化函數中顯式調用父類 Father 的初始化函數
  2. 在子類 Son 中從新定義個 get_age 方法,這樣就會覆蓋父類的同名方法,返回的是子類的 _age 變量

第二種方法就不貼代碼了,感興趣的話能夠試試。重點來看第一種方法,這就引出了第 3 種狀況。

第三種狀況

子類在本身定義的 init 方法中,顯式調用父類的 init 方法,父類和子類的屬性都會被初始化

class Father():
  """ 這是一個父類 """
  def __init__(self, age):
    print("Father's init function invoke")
    self.__age = age 

  def get_age(self):
    return self.__age

class Son(Father):
  """ 這是一個子類 """
  def __init__(self, age):
    print("Son's init function invoke")
    self.__age = age
    super(Son, self).__init__(age + 25)

  def get_age(self):
    return self.__age

  def get_father_age(self):
    return super(Son, self).get_age()
son = Son(5) 
# Son's init function invoke
# Father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5

看到代碼中是怎麼調用父類的初始化函數嗎? 對,用的是 super

java 中也有 super 關鍵字,表示對父類的指代, python 的 super 是怎麼用的,原理是什麼?咱們來看下。

super 有哪些用法?

下面說明的只針對 python 單繼承的狀況,多繼承這裏暫不涉及,有興趣的同窗能夠自行充電。

在單繼承中,super 也能夠看作對其父類的指代,它的使用場合就是用來調用父類的方法:

  1. 調用父類的 __init__方法
  2. 實現了和父類相同的功能,還須要調用父類的方法

它的寫法是 super(Son,self).xxx, 固然也能夠寫成 super() 這種簡寫的形式。
來看代碼

class Father():
  """ 這是一個父類 """
  def __init__(self, age):
    print("Father's init function invoke")
    self.__age = age 

  def get_age(self):
    return self.__age

class Son(Father):
  """ 這是一個子類 """
  def __init__(self, age):
    print("Son's init function invoke")
    self.__age = age
    super(Son, self).__init__(age + 25)

  def get_age(self):
    return self.__age

  def get_father_age(self):
    return super(Son, self).get_age()
son = Son(5) 
# Son's init function invoke
# Father's init function invoke
print(son.get_father_age()) # 30
print(son.get_age()) # 5

經過代碼來窺探下它的執行原理,以 super(Son, self).get_age() 爲例

  1. selfSon 的一個實例, superself 轉化爲父類 Father 的一個實例對象
  2. 由於 self 通過了轉化, 那它獲得的 __age, 也是父類初始化時獲得的 __age

結語

看到這裏,不知您對 python 的面向對象有了多少理解,反正我是理解了很多,哈哈。若是有疑問和建議,歡迎留言交流,我將仔細閱讀,認真回覆。

下篇文章中會涉及到 文件, json xml 處理 處理等主題,敬請期待~

相關文章
相關標籤/搜索