python 類相關概念理解

什麼是類,對象,實例,類## 標題變量(類屬性),實例變量(實例屬性)

面向對象編程(Object-oriented Programming,簡稱 OOP),是一種封裝代碼的方法。比如說,將亂七八糟的數據扔進列表中,這就是一種簡單的封裝,是數據層面的封裝;把常用的代碼塊打包成一個函數,這也是一種封裝,是語句層面的封裝。面向對象編程,也是一種封裝的思想,把描述特征的數據和代碼塊(函數)封裝到一起。

面向對象中,常用術語包括類,對象,屬性,方法:

:可以理解是一個模板,通過它可以創建出無數個具體實例(又稱對象)。
對象(實例):類並不能直接使用,通過類創建出的實例才能使用。
屬性:類中的所有變量稱為屬性。
方法:類中的所有函數通常稱為方法。不過,和函數所有不同的是,類方法至少要包含一個 self 參數。類方法無法單獨使用,隻能和類的對象一起使用。

類變量和類屬性的分類

無論是類屬性還是類方法,都無法像普通變量或者函數那樣,在類的外部直接使用它們。我們可以將類看做一個獨立的空間,則類屬性其實就是在類體中定義的變量,類方法是在類體中定義的函數。

在類體中,根據變量定義的位置不同,以及定義的方式不同,類屬性又可細分為以下 3 種類型:

類體中、所有函數之外:此范圍定義的變量,稱為類屬性或類變量;
類體中,所有函數內部:以“self.變量名”的方式定義的變量,稱為實例屬性或實例變量;
類體中,所有函數內部:以“變量名=變量值”的方式定義的變量,稱為局部變量。

和類屬性一樣,類方法也可以進行更細致的劃分,具體可分為類方法、實例方法和靜態方法。在實際編程中,幾乎不會用到類方法和靜態方法,因為我們完全可以使用函數代替它們實現想要的功能,但在一些特殊的場景中(例如工廠模式中),使用類方法和靜態方法也是很不錯的選擇。

和類屬性的分類不同:
類方法:采用 @classmethod 修飾的方法
靜態方法:采用 @staticmethod> 修飾的方法
實例方法:不用任何修改的方法

構造方法:在創建類時,我們可以手動添加一個 init() 方法,該方法是一個特殊的類實例方法,稱為構造方法(或構造函數)。構造方法用於創建對象時使用,每當創建一個類的實例對象時,Python 解釋器都會自動調用它。

類調用實例方法

實例方法的調用方式其實有 2 種,既可以采用類對象調用,也可以直接通過類名調用。

class CLanguage :
    # 下面定義瞭2個類變量
    name = "python語言"
    add = "http://c.biancheng.net.python"
    # __init__是構造方法,也屬於實例方法  
    def __init__(self,name,add):
        #下面定義 2 個實例變量
        self.name = name
        self.add = add
        print(name,"網址為:",add)
    # 下面定義瞭一個say實例方法
    def say(self, content):
        print(content)
    #下面定義瞭一個count實例方法   
    def count(self,money):
    #下面定義瞭一個局部變量sale
        sale = 0.8*money
        print("優惠後的價格為:",sale)
    #下面定義瞭一個類方法
    @classmethod
    def info(cls):
        print("正在調用類方法",cls)
    #下面定義瞭一個靜態方法
    @staticmethod
    def info(name,add):
        print(name,add)
# 將該CLanguage對象賦給clanguage變量
# 通過類名直接調用實例方法
# CLanguage.say("通過類名直接調用實例方法")會報錯,必須手動將 clang 這個類對象傳給self 參數
clang = CLanguage("C語言中文網1","http://c.biancheng.net")#傳入的參數要和init的一樣
CLanguage.say(clang, "通過類名直接調用實例方法")
# 通過類對象直接調用實例方法
clang2 = CLanguage("C語言中文網2","http://c.biancheng.net")
clang2.say("通過類對象直接調用實例方法")
#輸出
C語言中文網1 網址為: http://c.biancheng.net
通過類名直接調用實例方法
C語言中文網2 網址為: http://c.biancheng.net
通過類對象直接調用實例方法

類的封裝(enclosure),繼承和多態

1.封裝

簡單的理解封裝(Encapsulation),即在設計類時,刻意地將一些屬性和方法隱藏在類的內部,這樣在使用此類時,將無法直接以“類對象.屬性名”(或者“類對象.方法名(參數)”)的形式調用這些屬性(或方法),而隻能用未隱藏的類方法間接操作這些隱藏的屬性和方法。

Python 類如何進行封裝?

和其它面向對象的編程語言(如 C++、Java)不同,Python 類中的變量和函數,不是公有的(類似 public 屬性),就是私有的(類似 private),這 2 種屬性的區別如下:

public:公有屬性的類變量和類函數,在類的外部、類內部以及子類(後續講繼承特性時會做詳細介紹)中,都可以正常訪問;

private:私有屬性的類變量和類函數,隻能在本類內部使用,類的外部以及子類都無法使用。

Python 並沒有提供 public、private 這些修飾符。為瞭實現類的封裝,Python 采取瞭下面的方法:
默認情況下,Python 類中的變量和方法都是公有(public)的,它們的名稱前都沒有下劃線(_);
如果類中的變量和函數,其名稱以雙下劃線“”開頭,但是沒有以雙下劃線“”結尾,則該變量(函數)為私有變量(私有函數),其屬性等同於
private。

封裝的具體細節參考

2.繼承和多態

在OOP(Object Oriented Programming)程序設計中,當我們定義一個class的時候,可以從某個現有的class 繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。

繼承有什麼好處?最大的好處是子類獲得瞭父類的全部屬性及功能。

使用 class subclass_name(baseclass_name) 來表示繼承

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex
    def print_title(self):
        if self.sex == "male":
            print("man")
        elif self.sex == "female":
            print("woman")
class Child(Person):                            # Child 繼承 Person
    pass
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex)    # 子類繼承父類方法及屬性
May.print_title()
Peter.print_title()
#輸出
May female Peter male
woman
man

isinstance() 及 issubclass()

Python 與其他語言不同點在於,當我們定義一個 class 的時候,我們實際上就定義瞭一種數據類型。我們定義的數據類型和Python自帶的數據類型,比如str、list、dict沒什麼兩樣。

Python 有兩個判斷繼承的函數:isinstance() 用於檢查實例類型;issubclass() 用於檢查類繼承。參見下方示例:

class Person(object):
    pass
class Child(Person):                 # Child 繼承 Person
    pass
May = Child()
Peter = Person()    
print(isinstance(May,Child))         # True
print(isinstance(May,Person))        # True
print(isinstance(Peter,Child))       # False
print(isinstance(Peter,Person))      # True
print(issubclass(Child,Person))      # True

在說明多態是什麼之前,我們在 Child 類中重寫 print_title() 方法:若為male,print boy;若為female,print girl

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex
    def print_title(self):
        if self.sex == "male":
            print("man")
        elif self.sex == "female":
            print("woman")
class Child(Person):                # Child 繼承 Person
    def print_title(self):
        if self.sex == "male":
            print("boy")
        elif self.sex == "female":
            print("girl")
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex)
May.print_title()
Peter.print_title()

當子類和父類都存在相同的 print_title()方法時,子類的 print_title() 覆蓋瞭父類的 print_title(),在代碼運行時,會調用子類的 print_title()

這樣,我們就獲得瞭繼承的另一個好處:多態。

多態的好處就是,當我們需要傳入更多的子類,例如新增 Teenagers、Grownups 等時,我們隻需要繼承 Person 類型就可以瞭,而print_title()方法既可以直不重寫(即使用Person的),也可以重寫一個特有的。這就是多態的意思。調用方隻管調用,不管細節,而當我們新增一種Person的子類時,隻要確保新方法編寫正確,而不用管原來的代碼。這就是著名的**“開閉”原則**:

對擴展開放(Open for extension):允許子類重寫方法函數
對修改封閉(Closed for modification):不重寫,直接繼承父類方法函數

子類重寫構造函數

子類可以沒有構造函數,表示同父類構造一致;子類也可重寫構造函數;現在,我們需要在子類 Child 中新增兩個屬性變量:mother 和 father,我們可以構造如下(建議子類調用父類的構造方法,參見後續代碼):

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex
class Child(Person):                          # Child 繼承 Person
    def __init__(self,name,sex,mother,father):
        Person.__init__(self,name,sex)        # 子類對父類的構造方法的調用
        self.mother = mother
        self.father = father
May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)   

多重繼承

多重繼承的概念應該比較好理解,比如現在需要新建一個類 baby 繼承 Child , 可繼承父類及父類上層類的屬性及方法,優先使用層類近的方法,代碼參考如下:

class Person(object):
    def __init__(self,name,sex):
        self.name = name
        self.sex = sex
    def print_title(self):
        if self.sex == "male":
            print("man")
        elif self.sex == "female":
            print("woman")
class Child(Person):
    pass
class Baby(Child):
    pass
May = Baby("May","female")        # 繼承上上層父類的屬性
print(May.name,May.sex)         # May female
May.print_title()                 #woman 可使用上上層父類的方法

class Child(Person):
    def print_title(self):
        if self.sex == "male":
            print("boy")
        elif self.sex == "female":
            print("girl")
class Baby(Child):
    pass
May = Baby("May","female")
May.print_title()                # girl 優先使用上層類的方法

迭代,迭代器(iterator),可迭代對象(iterable object),生成器(generator)

迭代:是訪問集合元素的一種方式。如果給定一個list或tuple,我們可以通過for循環來遍歷這個list或tuple,這種遍歷我們稱為迭代(Iteration)。

迭代對象: 如果類中定義瞭__iter__方法,且返回瞭一個迭代器對象, 那麼稱這個創建的對象為可迭代對象

字符串,列表,元祖,字典,集合等等,都是可迭代對象。
(沒有__next__)
可迭代對象,則可用for循環

# 字符串,列表,元祖,字典,集合等等,都是可迭代對象
for i in [1, 2, 3]:
    print(i)
obj = {"a": 123, "b": 456}
for k in obj:
    print(k)

創建可迭代對象

class foo(object):
	def __iter__(self):
		return 迭代器對象(生成器對象)
obj = foo() #obj是可迭代對象
#創建迭代器
class IT(object):
	def __init__(self):
		self.counter = 0
	def __iter__(self):
		return self
	def __next__(self):
		self.counter += 1
		if self.counter == 3:
			raise StopIteration()
#創建可迭代器對象
class foo(object):
	def __iter__(self):
		return IT()
obj = foo() #obj是可迭代對象
for item in obj:   #循環可迭代對象時,內部先執行obj.__iter__()並獲取迭代器對象,然後再不斷的執行迭代器對象的__next__方法
	print(item)

迭代器:迭代器是一個可以記住遍歷的位置的對象

類中定義瞭__iter__和__next__兩個方法
__iter__返回對象本身,即self
__next__返回下一個數據,如果沒有,就返回一個stopiteration 的異常。
迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結束。迭代器隻能往前不會後退。
迭代器有兩個基本的方法(內置函數):iter() 和 next() ;內置函數 iter() 將可迭代對象轉化為迭代器. 通過 next() 方法逐一讀取下一個元素

創建一個迭代器

創建迭代器共有3種方法如下:

通過python內置函數iter()將可迭代對象轉換為迭代器
自己創建一個迭代器, 滿足(1)類中定義瞭__iter__和__next__兩個方法(2)__next__返回下一個數據,如果沒有,就返回一個stopiteration> 的異常 2個條件
通過生成器(generator)創建

# 方法1: iter()將可迭代對象轉換為迭代器
list=[1, 2, 3, 4]
it = iter(list)    # 創建迭代器對象
#使用next()遍歷數據
print (next(it))  #1
print (next(it))  #2
#使用for循環遍歷數據,for循環由於簡潔,更常用
#for循環會執行迭代器的iter並獲得返回的對象,一直反復的去執行next(對象)
for x in it:
    print (x, end=" ")   # 3 4
print (next(it))   #StopIteration
# 方法2: 創建一個迭代器
class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
  def __next__(self):
    if self.a <= 20:
      x = self.a
      self.a += 1
      return x
    else:
      raise StopIteration
#通過iter()和next()訪問 
myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
for x in myiter:
  print(x)
print(next(myiter))

#輸出
StopIteration
1
2
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Process finished with exit code 1

生成器:使用瞭 yield 的 函數 被稱為生成器(generator)

具體細節參考https://www.jb51.net/article/63929.htm

相比迭代器,生成器最明顯的優勢就是節省內存空間,即它不會一次性生成所有的數據,而是什麼時候需要,什麼時候生成。

創建一個生成器

定義一個以 yield 關鍵字標識返回值的函數;

調用剛剛創建的函數,即可創建一個生成器

要想使生成器函數得以執行,或者想使執行完 yield 語句立即暫停的程序得以繼續執行,有以下 2 種方式:

通過生成器(上面程序中的 num)調用 next() 內置函數或者 next() 方法;

通過 for 循環遍歷生成器。

#創建瞭一個 num 生成器對象。顯然,和普通函數不同,intNum() 函數的返回值用的是 yield 關鍵字,而不是 return 關鍵字,此類函數又成為生成器函數。
#創建生成器函數
def intNum():
    print("開始執行")
    for i in range(5):
        yield i
        print("繼續執行")
#創建生成器對象num
num = intNum()
#調用 next() 內置函數
print(next(num))  #0
#調用 __next__() 方法
print(num.__next__())  #1
#通過for循環遍歷生成器
for i in num:  #2 , 3, 4
    print(i)

#輸出
開始執行
0
繼續執行
1
繼續執行
2
繼續執行
3
繼續執行
4
繼續執行

Process finished with exit code 0

除此之外,還可以使用 list() 函數和 tuple() 函數,直接將生成器能生成的所有值存儲成列表或者元組的形式

num = intNum()
print(list(num))
num = intNum()
print(tuple(num)

#輸出
開始執行
繼續執行
繼續執行
繼續執行
繼續執行
繼續執行
[0, 1, 2, 3, 4]
開始執行
繼續執行
繼續執行
繼續執行
繼續執行
繼續執行
(0, 1, 2, 3, 4)

Process finished with exit code 0

參考:

1. C語言中文網

2.Python生成器(Generator)詳解

3.Python迭代用法實例教程

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: