ORM
ORM物件關系映射:將物件映射成資料表中的一條條記錄
類 映射為 ---> 資料表名
物件 ---> 資料記錄(一條條資料 比如: "張三 18歲 男")
物件.屬性 ---> 資料欄位(一條條資料中的具體資料 比如: "男" 或者 "18歲" 或者 "張三")
# 演示映射關系(偽代碼)
""" 比如User表中有名字,年齡,性別: User表: 名字、 年齡、 性別 apple、80、female class User: # User為表名 pass user_obj = User() # 獲取記錄 user_obj.name屬性 # 獲取user表中的name資料資訊 user_obj.age屬性 = 80 # 給user表添加資料 """
# 欄位型別的定義,每個欄位都應該有 欄位名、欄位型別、是否為主鍵、是否有默認值
# 欄位型別父類 class Field: def __init__(self, name, column_type, primary_key=False, default=None): self.name = name self.column_type = column_type self.primary_key = primary_key self.default = default # varchar欄位型別 class StringField(Field): def __init__(self, name, column_type="varchar(64)", primary_key=False, default=None): super().__init__(name, column_type, primary_key, default) # int欄位型別 class IntegerField(Field): def __init__(self, name, column_type="int", primary_key=False, default=0): super().__init__(name, column_type, primary_key, default)
# Models類
# 問題1:每一張表類,都必須定義一個__init__方法,但是無法確定每張表的欄位個數以及欄位名字
# 解決1:通過繼承dict字典類,讓用戶輸入所需要添加的欄位以及欄位名字,解決該問題
# 問題2:繼承的字典無法通過 "物件.屬性" 的取值方式來取值,也無法通過 "物件.屬性=屬性值" 的方式來賦值
# 解決2:__getattr__方法解決了取值方式,__setattr__方法解決了賦值方式
# 1.讓Models類繼承dict字典類,所有子類繼承Models就等于繼承了dict類 class Models(dict): # 2.在 "物件.屬性" 獲取屬性時,若 "沒有該屬性" 時觸發 __getattr__ def __getattr__(self, item): # print(item) return self.get(item) # 2.當 "物件.屬性 = 屬性值" ,"添加或修改屬性" 時觸發 __setattr__ def __setattr__(self, key, value): self[key] = value # 假如User表中有 用戶名和密碼 兩個欄位 class User(Models): pass if __name__ == '__main__': # print(dict(username='apple', password='123')) # {'username': 'apple', 'password': '123'} # User類繼承了dict類,相當于 User(username='apple', password='123') == dict(username='apple', password='123') user_obj = User(username='apple', password='123') print(user_obj) # print(res.get("username")) # 字典的取值方式一 # print(res["username"]) # 字典的取值方式二 print(user_obj.username) # 字典的取值方式三,也是我們需要實作的取值方式 ---> " user_obj.name屬性 # 獲取user表中的name資料資訊 " print('添加屬性前: ---> ', user_obj) # User類實體化出來的 user_obj 普通物件 添加屬性的方式 user_obj.user_type = "admin" # 實作通過 物件.屬性=屬性值 添加資料 ---> " user_obj.age屬性 = 80 # 給user表添加資料 " print(user_obj.user_type) print('添加屬性后: ---> ', user_obj) # 驗證是否將 user_type="admin" 添加到名稱空間中
執行結果:
{'username': 'apple', 'password': '123'}
apple
添加屬性前: ---> {'username': 'apple', 'password': '123'}
admin
添加屬性后: ---> {'username': 'apple', 'password': '123', 'user_type': 'admin'}
# 元類控制表類的創建
欄位型別的定義 + Model類 + 元類
# 創建表的三個注意點:
1.保證一張表必須要有表名字
2.保證一張表中只能有一個主鍵
3.將所有 "欄位名"與"欄位物件" 添加到一個獨立的字典(mappings)中,以key(欄位名):value(欄位物件) 添加到類的名稱空間中,方便后期使用
# 欄位型別父類 class Field: def __init__(self, name, column_type, primary_key, default): self.name = name self.column_type = column_type self.primary_key = primary_key self.default = default # varchar欄位型別 class StringField(Field): def __init__(self, name, column_type="varchar(64)", primary_key=False, default=None): super().__init__(name, column_type, primary_key, default) # int欄位型別 class IntegerField(Field): def __init__(self, name, column_type="varchar(64)", primary_key=False, default=0): super().__init__(name, column_type, primary_key, default) # 自定義OrmMetaclass元類:控制類的創建程序 class OrmMetaclass(type): # __call__:先呼叫__new__,只要定義類就會觸發__new__ def __new__(cls, class_name, class_bases, class_dict): # class_name:類名 class_bases:基類 class_dict:類的名稱空間 # print(f"類名:{class_name}") # print(f"基類:{class_bases}") # print(f"類的名稱空間:{class_dict}") # 過濾掉Models類,我們限制的是用戶創建表的程序(可以說是用戶創建表的規范),所以在此處過濾掉Models類,因為我們不限制Models類 if class_name == 'Models': return type.__new__(cls, class_name, class_bases, class_dict) # 第一個注意點:保證表必須要有表名, 獲取table表名,若自定義則獲取、沒有則默認使用類名 # dict.get(key, class_name) key若有回傳對應的值,若沒有則回傳默認值class_name就是默認值 table_name = class_dict.get("table_name", class_name) # 獲取類名稱空間的自定義表名table_name,若沒有則默認以類名為表名 print(table_name) # userinfo,若未定義表名,那么表名為類名,此處即User # 自定義一個主鍵值標識,后面做邏輯判斷使用 # 主鍵值:主鍵名為 欄位名 primary_key = None # 比如主鍵是id,那么id欄位就是主鍵值的名字(primary_key = id) # 第三個注意點:定義一個字典用來存放 "欄位名"與"欄位物件" mappings = {} print(class_dict) # 列印類中的名稱空間 """ 執行結果: {'__module__': '__main__', '__qualname__': 'User', 'table_name': 'userinfo', 'id': <__main__.IntegerField object at 0x000000000287F1C8>, 'username': <__main__.StringField object at 0x000000000287F088>, 'password': <__main__.StringField object at 0x000000000287F388>} 上面class_dict的名稱空間中,我們需要的僅僅是后面的id、username、password鍵值對屬性,前兩個不需要,我們需要將其剔除,后面將剔除后的結果重新放進mappings字典中 """ # 使用for回圈將類中的名稱空間字典 迭代依次取出 for k, v in class_dict.items(): # k為欄位名,v為欄位物件 # 將類中的名稱空間無需的屬性進行剔除篩選 # isinstance():判斷一個物件是否是另一個類的實體 if isinstance(v, Field): # id、username、password的value都是Field的實體 # print(k, v) # print(v.__dict__) # {'name': 'password', 'column_type': 'varchar(64)', 'primary_key': False, 'default': None} # 第三個注意點:將所有的 "欄位名" 與 "欄位物件" 添加到一個定義好的獨立的字典(mappings)中 mappings[k] = v """ # 此處有一個坑: # class_attr.pop(k) # 這里當字典被迭代時,不能修改其屬性 """ # 第二個注意點:保證一張表只能有一個唯一的主鍵 # 判斷欄位物件如果有主鍵 primary_key,則為primary_key變數賦值 if v.primary_key: # 如果建的表中有主鍵,執行下一步,若沒有不執行下一步 # 若第二次執行到此處,primary_key有值,證明有主鍵,拋出例外 if primary_key: raise TypeError("一張表只能有一個主鍵!") primary_key = v.name # 將自定義的主鍵值標識修改為 欄位物件中的名字(和欄位名一樣) # 給類的名稱空間 添加table_name、primary_key、mappings屬性 class_dict["table_name"] = table_name # 將表名添加到類的名稱空間中 class_dict["primary_key"] = primary_key # 將主鍵添加到類的名稱空間中 class_dict["mappings"] = mappings # 將存放 "欄位名"與"欄位物件"的字典添加到類的名稱空間中 print(class_dict) """ 執行結果: {'__module__': '__main__', '__qualname__': 'User', 'table_name': 'userinfo', 'id': <__main__.IntegerField object at 0x000000000237F1C8>, 'username': <__main__.StringField object at 0x000000000237F088>, 'password': <__main__.StringField object at 0x000000000237F388>, 'table_name': 'User', 'primary_key': 'id', 'mappings': {'id': <__main__.IntegerField object at 0x000000000237F1C8>, 'username': <__main__.StringField object at 0x000000000237F088>, 'password': <__main__.StringField object at 0x000000000237F388>}} 上面class_dict的名稱空間中,有重復的"欄位名"與"欄位物件",在此將class_dict中重復的"欄位名"與"欄位物件"過濾掉 """ # 過濾掉類名稱空間中重復的欄位屬性 for key in mappings.keys(): # 根據mappings中的欄位屬性來過濾掉class_dict中的欄位屬性,因為mappings中的欄位屬性在class_dict中的欄位屬性都存在 class_dict.pop(key) print(class_dict) """ 執行結果: {'__module__': '__main__', '__qualname__': 'User', 'table_name': 'userinfo', 'primary_key': 'id', 'mappings': {'id': <__main__.IntegerField object at 0x000000000283F1C8>, 'username': <__main__.StringField object at 0x000000000283F088>, 'password': <__main__.StringField object at 0x000000000283F388>}} 過濾完成! """ # 若建立的表中所有欄位都沒有主鍵(規定每一張表中都需要有一個主鍵),拋出例外 if not primary_key: raise TypeError("表中必須要有一個主鍵!") return type.__new__(cls, class_name, class_bases, class_dict) # 讓Models類繼承dict字典類,所有子類繼承Models就等于繼承了dict類 class Models(dict, metaclass=OrmMetaclass): # 在 "物件.屬性" 獲取屬性時,若 "沒有該屬性" 時觸發 __getattr__ def __getattr__(self, item): # print(item) return self.get(item) # 當 "物件.屬性 = 屬性值" ,"添加或修改屬性" 時觸發 __setattr__ def __setattr__(self, key, value): self[key] = value # 假如User表中有 id、用戶名和密碼 三個欄位 # 問題3:一個表中只能有一個唯一的主鍵,在當前表類中,無法控制用戶定義類的行為 class User(Models): table_name = "userinfo" # 自定義一個表名,若此處不寫,表名默認即為類名User # 欄位類中的name屬性必須與User表中類屬性同名 id = IntegerField(name="id", primary_key=True) username = StringField(name="username") password = StringField(name="password")
測驗代碼:
(錯誤演示)第一種:創建欄位時不添加主鍵primary_key

(錯誤演示)第二種:創建欄位時添加多個主鍵primary_key

(正確演示):創建欄位時只添加一個主鍵primary_key

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/222882.html
標籤:Python
