01.16 單例、異常、eval函數

一、單例

01. 單例設計模式

  • 設計模式 設計模式前人工作的總結和提煉,通常,被人們廣泛流傳的設計模式都是針對 某一特定問題 的成熟的解決方案 使用 設計模式 是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性
  • 單例設計模式 目的 —— 讓 創建的對象,在系統中 只有 唯一的一個實例 每一次執行 類名() 返回的對象,內存地址是相同的

單例設計模式的應用場景

  • 音樂播放 對象
  • 回收站 對象
  • 打印機 對象
  • ……

02. __new__ 方法

  • 使用 類名() 創建對象時, Python 的解釋器 首先 會 調用 __new__ 方法為對象 分配空間
  • __new__ 是一個 由 object 基類提供的 內置的靜態方法,主要作用有兩個: 1) 在內存中為對象 分配空間 2) 返回 對象的引用
  • Python 的解釋器獲得對象的 引用 後,將引用作為 第一個參數,傳遞給 __init__ 方法

重寫 __new__ 方法 的代碼非常固定!

  • 重寫 __new__ 方法 一定要 return super().__new__(cls)
  • 否則 Python 的解釋器 得不到 分配了空間的 對象引用就不會調用對象的初始化方法
  • 注意: __new__ 是一個靜態方法,在調用時需要 主動傳遞 cls 參數
單例、異常、eval函數

<code> class MusicPlayer(object):

def __new__(cls, *args, **kwargs):
# 如果不返回任何結果,
return super().__new__(cls)

def __init__(self):
print("音樂播放器初始化")


yunplayer = MusicPlayer()
print(yunplayer)/<code>

03. Python 中的單例

單例 —— 讓 類 創建的對象,在系統中 只有 唯一的一個實例

  1. 定義一個 類屬性,初始值是 None,用於記錄 單例對象的引用
  2. 重寫 __new__ 方法
  3. 如果 類屬性 is None,調用父類方法分配空間,並在類屬性中記錄結果
  4. 返回 類屬性
    中記錄的 對象引用
單例、異常、eval函數

<code> class MusicPlayer(object):

# 定義類屬性記錄單例對象引用
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:
return super().__new__(cls)

return cls.instance/<code>

只執行一次初始化工作

  • 在每次使用 類名() 創建對象時, Python 的解釋器都會自動調用兩個方法: __new__分配空間__init__對象初始化
  • 在上一小節對 __new__ 方法改造之後,每次都會得到 第一次被創建對象的引用
  • 但是:初始化方法還會被再次調用

需求

  • 初始化動作 只被 執行一次

解決辦法

  1. 定義一個類屬性 init_flag 標記是否 執行過初始化動作,初始值為 False
  2. 在 __init__ 方法中,判斷 init_flag,如果為 False 就執行初始化動作
  3. 然後將 __init__ 設置為 True
  4. 這樣,再次 自動 調用 __init__ 方法時,初始化動作就不會被再次執行
<code> class MusicPlayer(object):

# 記錄第一個被創建對象的引用
instance = None
# 記錄是否執行過初始化動作
init_flag = False

def __new__(cls, *args, **kwargs):
# 1.判斷類屬性是否為空對象
if cls.instance is None:
# 2.調用父類方法,為第一個對象分配空間
return super().__new__(cls)
# 3.返回類屬性保存的對象引用
return cls.instance

def __init__(self):
if not MusicPlayer.init_flag:
print("音樂播放器初始化")

MusicPlayer.init_flag = True


# 創建對個對象
player1 = MusicPlayer()
print(player1)
player2 = MusicPlayer()
print(player2)/<code>

二、異常

01. 異常的概念

  • 程序在運行時,如果 Python 解釋器 遇到 到一個錯誤,會停止程序的執行,並且提示一些錯誤信息,這就是 異常
  • 程序停止執行並且提示錯誤信息 這個動作,我們通常稱之為:拋出(raise)異常
單例、異常、eval函數

程序開發時,很難將 所有的特殊情況 都處理的面面俱到,通過 異常捕獲 可以針對突發事件做集中的處理,從而保證程序的 穩定性和健壯性

02. 捕獲異常

2.1 簡單的捕獲異常語法

  • 在程序開發中,如果 對某些代碼的執行不能確定是否正確,可以增加 try(嘗試) 來 捕獲異常
  • 捕獲異常最簡單的語法格式:
<code>try:
嘗試執行的代碼
except:
出現錯誤的處理/<code>
  • try 嘗試,下方編寫要嘗試代碼,不確定是否能夠正常執行的代碼
  • except 如果不是,下方編寫嘗試失敗的代碼

簡單異常捕獲演練 —— 要求用戶輸入整數

<code>try:
# 提示用戶輸入一個數字
num = int(input("請輸入數字:"))
except:
print("請輸入正確的數字")/<code>

2.2 錯誤類型捕獲

  • 在程序執行時,可能會遇到 不同類型的異常,並且需要 針對不同類型的異常,做出不同的響應,這個時候,就需要捕獲錯誤類型了
  • 語法如下:
<code>try:
# 嘗試執行的代碼
pass
except 錯誤類型1:
# 針對錯誤類型1,對應的代碼處理
pass

except (錯誤類型2, 錯誤類型3):
# 針對錯誤類型2 和 3,對應的代碼處理
pass
except Exception as result:
print("未知錯誤 %s" % result)/<code>
  • 當 Python 解釋器 拋出異常 時,最後一行錯誤信息的第一個單詞,就是錯誤類型

異常類型捕獲演練 —— 要求用戶輸入整數

需求

  1. 提示用戶輸入一個整數
  2. 使用 8 除以用戶輸入的整數並且輸出
<code>try:
num = int(input("請輸入整數:"))
result = 8 / num
print(result)
except ValueError:
print("請輸入正確的整數")
except ZeroDivisionError:
print("除 0 錯誤")/<code>

捕獲未知錯誤

  • 在開發時,要預判到所有可能出現的錯誤,還是有一定難度的
  • 如果希望程序 無論出現任何錯誤,都不會因為 Python 解釋器 拋出異常而被終止,可以再增加一個 except

語法如下:

<code>except Exception as result:
print("未知錯誤 %s" % result)/<code>

2.3 異常捕獲完整語法

  • 在實際開發中,為了能夠處理複雜的異常情況,完整的異常語法如下: 提示: 有關完整語法的應用場景,在後續學習中,結合實際的案例會更好理解現在先對這個語法結構有個印象即可
<code>try:
# 嘗試執行的代碼
pass
except 錯誤類型1:
# 針對錯誤類型1,對應的代碼處理
pass

except 錯誤類型2:
# 針對錯誤類型2,對應的代碼處理
pass
except (錯誤類型3, 錯誤類型4):
# 針對錯誤類型3 和 4,對應的代碼處理
pass
except Exception as result:
# 打印錯誤信息
print(result)
else:
# 沒有異常才會執行的代碼
pass
finally:
# 無論是否有異常,都會執行的代碼
print("無論是否有異常,都會執行的代碼")/<code>
  • else 只有在沒有異常時才會執行的代碼
  • finally 無論是否有異常,都會執行的代碼
  • 之前一個演練的 完整捕獲異常 的代碼如下:
<code>try:
num = int(input("請輸入整數:"))
result = 8 / num
print(result)
except ValueError:
print("請輸入正確的整數")
except ZeroDivisionError:
print("除 0 錯誤")
except Exception as result:

print("未知錯誤 %s" % result)
else:
print("正常執行")
finally:
print("執行完成,但是不保證正確")/<code>

03. 異常的傳遞

  • 異常的傳遞 —— 當 函數/方法 執行 出現異常,會 將異常傳遞 給 函數/方法 的 調用一方
  • 如果 傳遞到主程序,仍然 沒有異常處理,程序才會被終止

  提示

  • 在開發中,可以在主函數中增加 異常捕獲
  • 而在主函數中調用的其他函數,只要出現異常,都會傳遞到主函數的
    異常捕獲
  • 這樣就不需要在代碼中,增加大量的 異常捕獲,能夠保證代碼的整潔

需求

  1. 定義函數 demo1() 提示用戶輸入一個整數並且返回
  2. 定義函數 demo2() 調用 demo1()
  3. 在主程序中調用 demo2()
<code>def demo1():
return int(input("請輸入一個整數:"))


def demo2():
return demo1()

try:
print(demo2())
except ValueError:
print("請輸入正確的整數")
except Exception as result:
print("未知錯誤 %s" % result)/<code>

04. 拋出 raise 異常

4.1 應用場景

  • 在開發中,除了 代碼執行出錯 Python 解釋器會 拋出 異常之外
  • 還可以根據 應用程序 特有的業務需求 主動拋出異常

示例

  • 提示用戶 輸入密碼,如果 長度少於 8,拋出 異常
單例、異常、eval函數

注意

  • 當前函數 只負責 提示用戶輸入密碼,如果 密碼長度不正確,需要其他的函數進行額外處理
  • 因此可以 拋出異常,由其他需要處理的函數 捕獲異常

4.2 拋出異常

  • Python 中提供了一個 Exception 異常類
  • 在開發時,如果滿足 特定業務需求時,希望 拋出異常,可以: 創建 一個 Exception 的 對象 使用 raise 關鍵字
    拋出 異常對象

需求

  • 定義 input_password 函數,提示用戶輸入密碼
  • 如果用戶輸入長度 < 8,拋出異常
  • 如果用戶輸入長度 >=8,返回輸入的密碼
<code>def input_password():

# 1. 提示用戶輸入密碼
pwd = input("請輸入密碼:")

# 2. 判斷密碼長度,如果長度 >= 8,返回用戶輸入的密碼
if len(pwd) >= 8:
return pwd

# 3. 密碼長度不夠,需要拋出異常
# 1> 創建異常對象 - 使用異常的錯誤信息字符串作為參數
ex = Exception("密碼長度不夠")

# 2> 拋出異常對象
raise ex


try:
user_pwd = input_password()
print(user_pwd)

except Exception as result:
print("發現錯誤:%s" % result)/<code>

三、eval 函數

eval() 函數十分強大 —— 將字符串 當成 有效的表達式 來求值 並 返回計算結果

<code># 基本的數學計算
In [1]: eval("1 + 1")
Out[1]: 2

# 字符串重複
In [2]: eval("'*' * 10")
Out[2]: '**********'

# 將字符串轉換成列表
In [3]: type(eval("[1, 2, 3, 4, 5]"))
Out[3]: list

# 將字符串轉換成字典
In [4]: type(eval("{'name': 'xiaoming', 'age': 18}"))
Out[4]: dict/<code>

案例 - 計算器

需求

  1. 提示用戶輸入一個 加減乘除混合運算
  2. 返回計算結果
<code>input_str = input("請輸入一個算術題:")

print(eval(input_str))/<code>

不要濫用 eval

在開發時千萬不要使用 eval 直接轉換 input 的結果

<code>__import__('os').system('ls')/<code>
  • 等價代碼
<code>import os

os.system("終端命令")/<code>
  • 執行成功,返回 0
  • 執行失敗,返回錯誤信息


分享到:


相關文章: