首頁 > 軟體

Python  序列化反序列化和例外處理的問題小結

2022-12-25 14:01:38

1.迭代器

迭代是存取集合元素的一種方式。迭代器是一個可以記住遍歷的位置的物件。迭代器物件從集合的第一個元素開始存取,直到所有的元素被存取完結束。迭代器只能往前不會後退。

1.1 可迭代物件

我們已經知道可以對list、tuple、str等型別的資料使用for...in...的迴圈語法從其中依次拿到資料進行使用,我們把這樣的過程稱為遍歷,也叫迭代

但是,是否所有的資料型別都可以放到for...in...的語句中,然後讓for...in...每次從中取出一條資料供我們使用,即供我們迭代嗎?

>>> for i in 100:
 ...     print(i)
 ...
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 TypeError: 'int' object is not iterable
 >>>
# int整型不是iterable,即int整型不是可以迭代的

我們把可以通過for...in...這類語句迭代讀取一條資料供我們使用的物件稱之為可迭代物件(Iterable)。

1.2 如何判斷一個物件是否可以迭代

可以使用 isinstance() 判斷一個物件是否是 Iterable 物件:

 In [50]: from collections import Iterable
 
 In [51]: isinstance([], Iterable)
 Out[51]: True
 
 In [52]: isinstance({}, Iterable)
 Out[52]: True
 
 In [53]: isinstance('abc', Iterable)
 Out[53]: True
 
 In [54]: isinstance(mylist, Iterable)
 Out[54]: False
 
 In [55]: isinstance(100, Iterable)
 Out[55]: False

1.3 可迭代物件的本質

我們分析對可迭代物件進行迭代使用的過程,發現每迭代一次(即在for...in...中每回圈一次)都會返回物件中的下一條資料,一直向後讀取資料直到迭代了所有資料後結束。那麼,在這個過程中就應該有一個“人”去記錄每次存取到了第幾條資料,以便每次迭代都可以返回下一條資料。我們把這個能幫助我們進行資料迭代的“人”稱為迭代器(Iterator)

可迭代物件的本質就是可以向我們提供一個這樣的中間“人”即迭代器幫助我們對其進行迭代遍歷使用。

可迭代物件通過__iter__方法向我們提供一個迭代器,我們在迭代一個可迭代物件的時候,實際上就是先獲取該物件提供的一個迭代器,然後通過這個迭代器來依次獲取物件中的每一個資料.

那麼也就是說,一個具備了__iter__方法的物件,就是一個可迭代物件。

 from collections.abc import Iterable
 class Demo(object):
     def __init__(self, n):
         self.n = n
         self.current = 0
     def __iter__(self):
         pass
 
 demo = Demo(10)
 print(isinstance(demo, Iterable))  # True
 
 for d in demo:   # 重寫了 __iter__ 方法以後,demo就是一個一個可迭代物件了,可以放在for...in的後面
     print(d)
 
 # 此時再使用for...in迴圈遍歷,會提示 TypeError: iter() returned non-iterator of type 'NoneType'
 # 這是因為,一個可迭代物件如果想要被for...in迴圈,它必須要有一個迭代器

1.4 迭代器Iterator

通過上面的分析,我們已經知道,迭代器是用來幫助我們記錄每次迭代存取到的位置,當我們對迭代器使用next()函數的時候,迭代器會向我們返回它所記錄位置的下一個位置的資料。實際上,在使用next()函數的時候,呼叫的就是迭代器物件的__next__方法(Python3中是物件的__next__方法,Python2中是物件的next()方法)。所以,我們要想構造一個迭代器,就要實現它的*next*方法。但這還不夠,python要求迭代器本身也是可迭代的,所以我們還要為迭代器實現__iter__方法,而__iter__方法要返回一個迭代器,迭代器自身正是一個迭代器,所以迭代器的__iter__方法返回自身即可。

一個實現了*iter*方法和*next*方法的物件,就是迭代器。

 class MyIterator(object):
     def __init__(self, n):
         self.n = n
         self.current = 0
 
     # 自定義迭代器需要重寫__iter__和__next__方法
     def __iter__(self):
         return self
 
     def __next__(self):
         if self.current < self.n:
             value = self.current
             self.current += 1
             return value
         else:
             raise StopIteration
 
 my_it = MyIterator(10)
 
 for i in my_it:    # 迭代器重寫了__iter__方法,它本身也是一個可迭代物件
     print(i)

1.5 如何判斷一個物件是否迭代器

呼叫一個物件的__iter__方法,或者呼叫iter()內建函數,可以獲取到一個可迭代物件的迭代器。

 names = ['hello', 'good', 'yes']
 print(names.__iter__())  # 呼叫物件的__iter__()方法
 print(iter(names))  # 呼叫iter()內建函數

可以使用 isinstance() 判斷一個物件是否是 Iterator 物件:

 from collections.abc import Iterator
 names = ['hello', 'good', 'yes']
 print(isinstance(iter(names), Iterator))

1.6 for...in...迴圈的本質

for item in Iterable 迴圈的本質就是先通過iter()函數獲取可迭代物件Iterable的迭代器,然後對獲取到的迭代器不斷呼叫next()方法來獲取下一個值並將其賦值給item,當遇到StopIteration的異常後迴圈結束。

1.7 迭代器的應用場景

我們發現迭代器最核心的功能就是可以通過next()函數的呼叫來返回下一個資料值。如果每次返回的資料值不是在一個已有的資料集合中讀取的,而是通過程式按照一定的規律計算生成的,那麼也就意味著可以不用再依賴一個已有的資料集合,也就是說不用再將所有要迭代的資料都一次性快取下來供後續依次讀取,這樣可以節省大量的儲存(記憶體)空間。

舉個例子,比如,數學中有個著名的斐波拉契數列(Fibonacci),數列中第一個數為0,第二個數為1,其後的每一個數都可由前兩個數相加得到:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

現在我們想要通過for...in...迴圈來遍歷迭代斐波那契數列中的前n個數。那麼這個斐波那契數列我們就可以用迭代器來實現,每次迭代都通過數學計算來生成下一個數。

 class FibIterator(object):
     """斐波那契數列迭代器"""
     def __init__(self, n):
         """
        :param n: int, 指明生成數列的前n個數
        """
         self.n = n
         # current用來儲存當前生成到數列中的第幾個數了
         self.current = 0
         # num1用來儲存前前一個數,初始值為數列中的第一個數0
         self.num1 = 0
         # num2用來儲存前一個數,初始值為數列中的第二個數1
         self.num2 = 1
 
     def __next__(self):
         """被next()函數呼叫來獲取下一個數"""
         if self.current < self.n:
             num = self.num1
             self.num1, self.num2 = self.num2, self.num1+self.num2
             self.current += 1
             return num
         else:
             raise StopIteration
 
     def __iter__(self):
         """迭代器的__iter__返回自身即可"""
         return self
 
 
 if __name__ == '__main__':
     fib = FibIterator(10)
     for num in fib:
         print(num, end=" ")

2.生成器

利用迭代器,我們可以在每次迭代獲取資料(通過next()方法)時按照特定的規律進行生成。但是我們在實現一個迭代器時,關於當前迭代到的狀態需要我們自己記錄,進而才能根據當前狀態生成下一個資料。為了達到記錄當前狀態,並配合next()函數進行迭代使用,我們可以採用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器

2.1 建立生成器方法1

要建立一個生成器,有很多種方法。第一種方法很簡單,只要把一個列表生成式的 [ ] 改成 ( )

 In [15]: L = [ x*2 for x in range(5)]
 
 In [16]: L
 Out[16]: [0, 2, 4, 6, 8]
 
 In [17]: G = ( x*2 for x in range(5))
 
 In [18]: G
 Out[18]: <generator object <genexpr> at 0x7f626c132db0>
 
 In [19]:

建立 L 和 G 的區別僅在於最外層的 [ ] 和 ( ) , L 是一個列表,而 G 是一個生成器。我們可以直接列印出列表L的每一個元素,而對於生成器G,我們可以按照迭代器的使用方法來使用,即可以通過next()函數、for迴圈、list()等方法使用。

 
In [19]: next(G)
Out[19]: 0
 
In [20]: next(G)
Out[20]: 2
 
In [21]: next(G)
Out[21]: 4
 
In [22]: next(G)
Out[22]: 6
 
In [23]: next(G)
Out[23]: 8
 
In [24]: next(G)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-380e167d6934> in <module>()
----> 1 next(G)
 
StopIteration:
 
In [25]:
In [26]: G = ( x*2 for x in range(5))
 
In [27]: for x in G:
   ....:     print(x)
   ....:     
0
2
4
6
8
 
In [28]:

2.2 建立生成器方法2

generator非常強大。如果推算的演演算法比較複雜,用類似列表生成式的 for 迴圈無法實現的時候,還可以用函數來實現。

我們仍然用上一節提到的斐波那契數列來舉例,回想我們在上一節用迭代器的實現方式:

class FibIterator(object):
    """斐波那契數列迭代器"""
    def __init__(self, n):
        """
        :param n: int, 指明生成數列的前n個數
        """
        self.n = n
        # current用來儲存當前生成到數列中的第幾個數了
        self.current = 0
        # num1用來儲存前前一個數,初始值為數列中的第一個數0
        self.num1 = 0
        # num2用來儲存前一個數,初始值為數列中的第二個數1
        self.num2 = 1
 
    def __next__(self):
        """被next()函數呼叫來獲取下一個數"""
        if self.current < self.n:
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1+self.num2
            self.current += 1
            return num
        else:
            raise StopIteration
 
    def __iter__(self):
        """迭代器的__iter__返回自身即可"""
        return self

注意,在用迭代器實現的方式中,我們要藉助幾個變數(n、current、num1、num2)來儲存迭代的狀態。現在我們用生成器來實現一下。

 
In [30]: def fib(n):
   ....:     current = 0
   ....:     num1, num2 = 0, 1
   ....:     while current < n:
   ....:         yield num1
   ....:         num1, num2 = num2, num1+num2
   ....:         current += 1
   ....:     return 'done'
   ....:
 
In [31]: F = fib(5)
 
In [32]: next(F)
Out[32]: 1
 
In [33]: next(F)
Out[33]: 1
 
In [34]: next(F)
Out[34]: 2
 
In [35]: next(F)
Out[35]: 3
 
In [36]: next(F)
Out[36]: 5
 
In [37]: next(F)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-37-8c2b02b4361a> in <module>()
----> 1 next(F)
 
StopIteration: done

在使用生成器實現的方式中,我們將原本在迭代器__next__方法中實現的基本邏輯放到一個函數中來實現,但是將每次迭代返回數值的return換成了yield,此時新定義的函數便不再是函數,而是一個生成器了。簡單來說:只要在def中有yield關鍵字的 就稱為 生成器

此時按照呼叫函數的方式( 案例中為F = fib(5) )使用生成器就不再是執行函數體了,而是會返回一個生成器物件( 案例中為F ),然後就可以按照使用迭代器的方式來使用生成器了。

In [38]: for n in fib(5):
   ....:     print(n)
   ....:     
1
1
2
3
5
 
In [39]:

但是用for迴圈呼叫generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:

In [39]: g = fib(5)
 
In [40]: while True:
   ....:     try:
   ....:         x = next(g)
   ....:         print("value:%d"%x)      
   ....:     except StopIteration as e:
   ....:         print("生成器返回值:%s"%e.value)
   ....:         break
   ....:     
value:1
value:1
value:2
value:3
value:5
生成器返回值:done
 
In [41]:

總結:

  • 使用了yield關鍵字的函數不再是函數,而是生成器。(使用了yield的函數就是生成器)
  • yield關鍵字有兩點作用:
  • 儲存當前執行狀態(斷點),然後暫停執行,即將生成器(函數)掛起
  • 將yield關鍵字後面表示式的值作為返回值返回,此時可以理解為起到了return的作用
  • 可以使用next()函數讓生成器從斷點處繼續執行,即喚醒生成器(函數)
  • Python3中的生成器可以使用return返回最終執行的返回值,而Python2中的生成器不允許使用return返回一個返回值(即可以使用return從生成器中退出,但return後不能有任何表示式)。

2.3 使用send喚醒

我們除了可以使用next()函數來喚醒生成器繼續執行外,還可以使用send()函數來喚醒執行。使用send()函數的一個好處是可以在喚醒的同時向斷點處傳入一個附加資料。

例子:執行到yield時,gen函數作用暫時儲存,返回i的值; temp接收下次c.send("python"),send傳送過來的值,c.next()等價c.send(None)

In [10]: def gen():
   ....:     i = 0
   ....:     while i<5:
   ....:         temp = yield i
   ....:         print(temp)
   ....:         i+=1
   ....:

使用send

In [43]: f = gen()
 
In [44]: next(f)
Out[44]: 0
 
In [45]: f.send('haha')
haha
Out[45]: 1
 
In [46]: next(f)
None
Out[46]: 2
 
In [47]: f.send('haha')
haha
Out[47]: 3
 
In [48]:

使用next函數

In [18]: f = gen()
 
In [19]: f.__next__()
Out[19]: 0
 
In [20]: f.__next__()
None
Out[20]: 1
 
In [21]: f.__next__()
None
Out[21]: 2
 
In [22]: f.__next__()
None
Out[22]: 3
 
In [23]: f.__next__()
None
Out[23]: 4
 
In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-39ec527346a9> in <module>()
----> 1 f.__next__()
 
StopIteration:

使用__next__()方法(不常使用)

In [18]: f = gen()
 
In [19]: f.__next__()
Out[19]: 0
 
In [20]: f.__next__()
None
Out[20]: 1
 
In [21]: f.__next__()
None
Out[21]: 2
 
In [22]: f.__next__()
None
Out[22]: 3
 
In [23]: f.__next__()
None
Out[23]: 4
 
In [24]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-39ec527346a9> in <module>()
----> 1 f.__next__()
 
StopIteration:

3.property屬性的使用

property屬性是一種用起來像是範例屬性一樣的特殊屬性,可以對應於某個方法。

class Foo:
    def func(self):
        pass
 
    # 定義property屬性
    @property
    def prop(self):
        pass
 
# ############### 呼叫 ###############
foo_obj = Foo()
foo_obj.func()  # 呼叫實體方法
foo_obj.prop  # 呼叫property屬性

property屬性的定義和呼叫要注意一下幾點:

  • 定義時,在實體方法的基礎上新增 @property 裝飾器;並且僅有一個self引數
  • 呼叫時,無需括號
  方法:foo_obj.func()
  property屬性:foo_obj.prop

簡單的範例

對於京東商城中顯示電腦主機的列表頁面,每次請求不可能把資料庫中的所有內容都顯示到頁面上,而是通過分頁的功能區域性顯示,所以在向資料庫中請求資料時就要顯示的指定獲取從第m條到第n條的所有資料 這個分頁的功能包括:

  • 根據使用者請求的當前頁和總資料條數計算出 m 和 n
  • 根據m 和 n 去資料庫中請求資料
# ############### 定義 ###############
class Pager:
    def __init__(self, current_page):
        # 使用者當前請求的頁碼(第一頁、第二頁...)
        self.current_page = current_page
        # 每頁預設顯示10條資料
        self.per_items = 10 
 
    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val
 
    @property
    def end(self):
        val = self.current_page * self.per_items
        return val
 
# ############### 呼叫 ###############
p = Pager(1)
p.start  # 就是起始值,即:m
p.end  # 就是結束值,即:n

從上述可見

Python的property屬性的功能是:property屬性內部進行一系列的邏輯計算,最終將計算結果返回。

3.1 property屬性的兩種方式

  • 裝飾器 即:在方法上應用裝飾器
  • 類屬性 即:在類中定義值為property物件的類屬性

3.1.1 裝飾器

  • 在類的實體方法上應用@property裝飾器

Python中的類有經典類和新式類,新式類的屬性比經典類的屬性豐富。( 如果類繼object,那麼該類是新式類 )

經典類的實現:

class Goods:
    @property
    def price(self):
        return "laowang"
 
obj = Goods()
result = obj.price  # 自動執行 @property 修飾的 price 方法,並獲取方法的返回值
print(result)

新式類的實現:

class Goods:
    """
    只有在python3中才有@xxx.setter  @xxx.deleter
    """
    def __init__(self):
        # 原價
        self.original_price = 100
        # 折扣
        self.discount = 0.8
 
    @property
    def price(self):
        new_price = self.original_price * self.discount
        return new_price
 
    @price.setter
    def price(self, value):
        self.original_price = value
 
    @price.deleter
    def price(self):
        del self.original_price
obj = Goods()
obj.price          # 獲取商品價格
obj.price = 200    # 修改商品原價
del obj.price      # 刪除商品原價

總結:

  • 經典類中的屬性只有一種存取方式,其對應被 @property 修飾的方法
  • 新式類中的屬性有三種存取方式,並分別對應了三個被@property、@方法名.setter、@方法名.deleter修飾的方法

3.1.2 類屬性方式

當使用類屬性的方式建立property屬性時,經典類和新式類無區別。

class Foo:
    def get_bar(self):
        return 'laowang'
    BAR = property(get_bar)
 
obj = Foo()
reuslt = obj.BAR  # 自動呼叫get_bar方法,並獲取方法的返回值
print(reuslt)

property方法中有個四個引數

  • 第一個引數是方法名,呼叫 物件.屬性 時自動觸發執行方法
  • 第二個引數是方法名,呼叫 物件.屬性 = XXX 時自動觸發執行方法
  • 第三個引數是方法名,呼叫 del 物件.屬性 時自動觸發執行方法
  • 第四個引數是字串,呼叫 物件.屬性.doc ,此引數是該屬性的描述資訊
class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'laowang'
 
    def set_bar(self, value): 
        """必須兩個引數"""
        print("setter...")
        return 'set value' + value
 
    def del_bar(self):
        print("deleter...")
        return 'laowang'
 
    BAR = property(get_bar, set_bar, del_bar, "description...")
 
obj = Foo()
 
obj.BAR  # 自動呼叫第一個引數中定義的方法:get_bar
obj.BAR = "alex"  # 自動呼叫第二個引數中定義的方法:set_bar方法,並將「alex」當作引數傳入
desc = Foo.BAR.__doc__  # 自動獲取第四個引數中設定的值:description...
print(desc)
del obj.BAR  # 自動呼叫第三個引數中定義的方法:del_bar方法

總結:

  • 定義property屬性共有兩種方式,分別是【裝飾器】和【類屬性】,而【裝飾器】方式針對經典類和新式類又有所不同。
  • 通過使用property屬性,能夠簡化呼叫者在獲取資料的流程。

到此這篇關於Python  序列化反序列化和例外處理的文章就介紹到這了,更多相關Python  序列化反序列化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com