目錄
- 雙下划線開頭且結尾的方法我們稱之為魔術方法,python底層幫我是實現了很多的魔術方法,在特定場景下自動觸發
- 點擊跳轉Python官網文檔
- 點擊跳轉大佬博客
一、魔術方法(魔術方法特殊方法)
__int__ 和 __new__ 方法
- __init__ 是在創建對象的時候自動調用,對創建的對象進行初始化設置的
- __new__ 是實力化對象的時候自動調用的
- __new__ 方法在__init__方法之前調用,先實例了對象,在給實例初始化屬性
class Mycalss(object):
def __init__(self, name):
print("這個是init方法")
self.name = name
# 重寫 __new__方法
def __new__(cls, *args, **kwargs):
print("這個是new方法")
# 創建對象是python底層幫我實現,重寫之后需要返回父類的創建對象的方法,不然實例不出對象
return object.__new__(cls)
m = Mycalss("DaBai") # 先進入new方法 在執行init方法
- __init__大家知道用,不做研究
- __new__方法的應用場景:重寫new方法可以實現單例模式
- 所有實例化操作都是實例一個對象,節約內存
- 對象屬性共用,全局化
方式一:類中重寫new方法實現
class Mycalss(object):
instance = None
# 重寫 __new__方法
def __new__(cls, *args, **kwargs):
# 如果 instance 為None 實例化對象,否則用第一次實例的對象
if not cls.instance:
cls.instance = object.__new__(cls)
return cls.instance
else:
return cls.instance
m1 = Mycalss()
m2 = Mycalss()
# id 一樣 同一個對象
print(id(m1))
print(id(m2))
# 所以m1創建的屬性,m2一樣有
m1.name="DaBai"
print(m2.name)
方式二:單例裝飾器
# 裝飾器單例模式
def class_one_case(cls):
# 空字典儲存 類 和 類實例(key:value)
instace = {}
def inner(*args, **kwargs):
# 如果類不在字典中實例化對象儲存,否者用字典中的對象
if cls not in instace:
instace[cls] = cls(*args, **kwargs)
return instace[cls]
else:
return instace[cls]
return inner
@class_one_case
class TestClass(object): # TestClass=class_one_case(TestClass) 調用的時候執行的裝飾器內部inner方法,返回實例
name = ""
def run(self):
print(self.name)
t1 = TestClass()
t2 = TestClass()
print(id(t1))
print(id(t2))
t1.name="Dabai"
# t2 就是t1 name 屬性也都公共也變成"DaBai"
t2.run()
__srt__方法和__repr__方法
- __srt__ 輸出的內容可以理解為是給用戶看的,用戶不關心是說明數據類型,只關心內容
- __repr__ 可以理解為是給開發看的,開發看到這個一眼就能確認是字符轉類型
- 交互環境代碼演示
>>> a = "1"
>>> print(a)
1
>>> a
'1'
>>>
- 問題思考:交互環境下print打印內容和直接輸入變量,返回的內容為什么會不一樣?
- 因為底層觸發的魔術方法不一樣
- print方法觸發的__srt__方法
- 直接輸出是觸發的__repr__方法
- Pycharm演示
a = "123"
print(str(a)) # 123
print(format(a)) # 123
print(repr(a)) # '123'
- 重寫__srt__方法和__repr__方法
- 一定要有return
- 一定要返回字符串類型
class MyStrRepr(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
print("出發__str__方法")
return self.name # 不返回 或者返回的類型不是字符串的時候會報錯
def __repr__(self):
print("出發__repr__方法")
return "MySrtRepr.object.name-%s" % self.gender
s = MyStrRepr("DaBai", "男")
print(s) # print 觸發__str方法
str(s) # srt 觸發__srt__
format(s) # format 觸發__srt__
res = repr(s) # repr 觸發__repr__ 程序員輸出方式
print(res)
- 注意
- 1、如果__srt__方法沒有重寫,print、str、format方法會去觸發__repr__,__repr__沒有就去找父類的__srt__方法
- 2、使用repr方法時,會先找自身__repr__方法,如果沒有就直接去父類里找
- 可以理解為__repr__是__str__備胎-見下圖
__call__方法
- 問題一:在Python中萬物皆對象,函數也是對象,為什么函數可以調用,而其他的對象不行呢?
- 如果想讓類創建出來的對象,可以像函數一樣被調用可以實現么?
- 那么我們只需要在類中定義__call__方法即可
# __call__ 可以被調用的方法 像函數一樣加() 可以被調用,
# 實例不能被調用是因為 實例和函數底層實現的方法不一樣
# 函數底層實現的call方法
def fun():
print("函數")
class My(object):
def __init__(self, name):
print("這個是init方法")
self.name = name
print("函數內部實現的方法", dir(fun)) # 實現了'__call__'
m1 = My("DaBai")
print("實例實現的方法", dir(m1)) # 沒有實現__call
m1() # 被執行會報錯
class My(object):
def __call__(self, *args, **kwargs):
print("__實例被執行了__")
m = My()
m() # 不會報錯 會執行類中__call__方法內的代碼塊
- __call__方法應用小案例:利用類實現裝飾器
# 類裝飾器
class MyCall(object):
def __init__(self, fun_cls):
# print("這個是init方法")
self.fun_cls = fun_cls
def __call__(self, *args, **kwargs):
print("call方法")
return self.fun_cls(*args, **kwargs)
@MyCall
def fun(a): # fun = Mycall(fun) 此時的fun 是 Mycall的實例對象了,被調用時執行call方法
print("函數%s" % a)
@MyCall
class My(object): # My = Mycall(My) 此時的My 是 Mycall的實例對象了,被調用時執行call方法
def __init__(self, name):
self.name = name
print(fun) # <__main__.MyCall object at 0x0000022ECE480320> MyCall的實例對象
fun(1) # 實例被執行 執行的call方法,call方法里面執行了run()函數
print(My) # <__main__.MyCall object at 0x0000012B8FDB03C8> MyCall的實例對象
m = My("DaBai") # MyCall的實例對象執行call方法 返回 My類的實例對象
print(m) # <__main__.My object at 0x0000012B8FDB0470> My的實例對象
上下文管理器
- 問題思考:打開文件加上with關鍵字 文件會自動化關閉?
上下文管理器的概念:上下文管理器是一個Python對象,為操作提供了上下文信息;這種額外的信息,在使用with語句初始化上下文,以及完成with語句的所有代碼時,采用可調用的形式。該場景主要自動化觸發兩個方法:
- object.__enter__(self)
輸入於對象相關的運行時上下文,如果存在的話,with語句將綁定該方法的返回值到 as 子句鍾指定目標
- biject.__exit__(self,exc_type, exc_val, exc_tb)
- exc_type : 異常類型
- exc_val : 異常值
- exc_tb : 異常回溯追蹤
退出此對象相關上下文的操作方法,參數是導致上下文退出時異常報錯時,捕獲到相關異常信息,如果該上下文退出時沒有異常,三個參數都將為None
- 下面看代碼自己實現一個上文管理操作文件的類
# 上下文管理
with open("1.txt", "r+", ) as f:
print(f) # 默認gbk
# with 不是上下文管理器
# 在with 場景下會自動觸發 上下文管理器的__enter__ 方法 和最后執行的__exit__方法
class MyOpen:
"""
實現打開文件的上下文管理器,默認utf-8 內置的open默認gbk
"""
def __init__(self, file_path, open_method, encoding="utf-8"):
self.file_path = file_path
self.open_method = open_method
self.encoding = encoding
def __enter__(self):
print("__enter__")
self.file = open(self.file_path, self.open_method, encoding=self.encoding)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print("最后退出with的執行__exit__")
self.file.close()
with MyOpen("1.txt", "r") as f: # 這里就進入__inter__方法了
print(f.read())
print(f.closed) # with語句全部執行完畢的實話,在執行__exit__, >>> True
- 同理封裝一個操作數據庫上下文管理器
class OpenMysql(object):
"""
數據庫上下文管理器
config : 數據庫配置文件
"""
def __init__(self, config):
self.config = config
self.connect = connector.connect(**self.config)
self.cursor = self.connect.cursor()
def __enter__(self):
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.connect.close()
算數運算的實現
- 思考:Pyton鍾不僅數值之間能相加,字符串、列表、元組 也可以相加,這是怎么實現的?
- 同類型對象之間使用+號這種場景的情況下,實際是自動觸發了__add__ 魔術方法
# 算數運算自己封裝類
class MyStr(object):
def __init__(self, data):
self.data = data
def __str__(self):
return self.data
def __add__(self, other):
print("觸發了魔術方法")
# 打印對象觸發 __str__ 拿對象返回值
print("self:{}".format(self))
print("other:{}".format(other))
# 實現字符串加法
return self.data + other.data
# 減法,python字符串沒有實現減法,我們自己可以簡單實現以下
def __sub__(self, other):
# 把減號后面的字符傳替換成空
return self.data.replace(other.data, "")
s1 = MyStr("11111")
s2 = MyStr("22222")
# 執行可以看出:加號前面的對象 觸發的add方法,加號后面的對象當作參數傳到add方法中other
print(s1 + s2) # 此時直接觸發了add 方法
s3 = MyStr(s1 + s2)
print(s3 - s1) # 簡單實現了減法
- 其他的運算魔術方法
- __mul__(self, other) : 定義乘法行為:*
- __truediv__(self, other) : 定義除法行為:/
- __floordiv__(self, other) : 定義整數觸發行為 : //
- __mod__(self, other) : 定取余行為 : %
- 更多請移步開頭的大佬博客中
二、多態
面向對象三大特征
- 封裝 : 將數據和方法放在一個類中就構成了封裝
- 繼承 : Python中一個類可以繼承於一個類也可以繼承多個類,被繼承的類叫父類(或者叫基類,base class),繼承的類叫子類(可以獲得父類的屬性和方法)
- 多態(Polymorphism):指的是一類事物有多種形態,一個抽象類有多個子類(因而多態的概念依賴於繼承),不同的子類對象調用相同的方法,產生不同的執行結果,多態可以增加代碼的靈活程度
- Python因為是弱類型語音,不需要聲明數據類型,固Python中嚴格上來說不存在多態,而是更加強大的--鴨子模式
多態的實現步驟
- 1、定義一個父類(base),實現某個方法(比如:run)
- 2、定義多個子類,在子類中重寫父類的方法(run),每個子類run方法實現不通的功能
- 3、假設我們定義了一個函數,需要一個Base類型的對象作為參數,那么調用函數的時候,傳入Base類的不同的子類,那么這個函數就會執行不同的功能,這就是多態的提現
- 4、因為Python中對函數的參數沒有數據類型限制,不是Base類型的數據,一樣可以傳進函數中,我們只要隨便寫一個類哪怕不繼承Base類,類中也實現了run方法,此時傳進函數中也能實現不同的run功能,所以才說Python實現的是偽多態,也就是鴨子模式
# 實現一個Base類,子類繼承重寫run方法
class Base:
def run(self):
print("_____base___run___:會跑")
class Cat(Base):
def run(self):
print("_____cat___run___:能抓老鼠")
class Dog(Base):
def run(self):
print("_____dog___run___:能抓兔子")
b_obj = Base()
c_obj = Cat()
d_obj = Dog()
# 問題一:子類都屬於父類類型
print(isinstance(c_obj, Cat)) # >>>True 子類屬於自己的類型
print(isinstance(c_obj, Base)) # >>>True 子類也屬於父類類型
# 函數的參數在Python中是沒有類型限制的
# 其他如java c 等語言,定於參數的時候是先強制聲明類型的
# 假設我們只能傳Base類型的參數--即叫多態
def func(base_obj):
base_obj.run()
# 調用函數只能傳Base類型的數據 就是其他語言所說的多態
# 傳入同一個Base類型的不同的子類,實現了不同的功能
func(b_obj)
func(c_obj)
# 我們在實現一個沒有繼承Base類的,也實現了run方法
class Pig:
def run(self):
print("_____pig___run___:好吃懶做")
p_obj = Pig()
print(isinstance(p_obj,Base)) # >>> Flase 不是Base類型的
# 其實語言強制了數據類型,但是Python不需要
# 傳入沒有繼承Base類型的 Pig類型的實例,只要它自己實現了run方法
# 傳入該函數一樣可以實現不同功能的run方法 --即叫偽多態(鴨子模式)
func(p_obj)
-
鴨子類型概念:
- 它不需要言責的繼承體系,關注的不是對象類型本身,而是它如何使用,一個對象只要"看起來像鴨子,走起路來像鴨子,那它就可以被看做是鴨子"
- 它不需要言責的繼承體系,關注的不是對象類型本身,而是它如何使用,一個對象只要"看起來像鴨子,走起路來像鴨子,那它就可以被看做是鴨子"
-
鴨子類型的體現:
- 靜態語言:對於靜態語言(java,#c)來講上面傳入的對象必須是Base類型或者它的子類(子類是Base類型),否者函數功能將無法實現run()方法----多態
- 動態語言:對於動態語言Python來說,上面傳入的並不一定是Base類型,也可以是其他類型,只要內部也實現了run()方法就可以了,這就是鴨子類型的體現
-
多態意義:開放擴展、封閉修改原則
- 對於一個變量,我們只需要知道他是Base類型,無需確切的知道它的子類,就可以放心的調用run()方法了(自己有執行自己的,自己沒有也會執行Base里的)--調用方只管調用,不管細節
- 當需要新增功能時,只需要一個Base的子類,實現run()方法,就可以在原來的基礎上進行功能擴展,這個就是"開放封閉"原則:
- 對擴展開發:允許新增Base子類
- 對修改封閉:不需要修改Base類里面的run()方法
三、數據和自省
類私有屬性,私有方法,實例私有屬性,私有方法
- "_name" 被單下滑線標識的名稱,意位類中的私有產物,雖然不影響外界的調用,算是口頭協約,外部別用,此屬性或者方法僅為類內部用
- "__name" 被雙下滑線標識的,也是類中的私有產物、外部不能直接調用,實現了偽私有,外界通過"_類名__name" 一樣可以訪問
class MyTest(object):
__age = 18 # 類私有屬性,外界不能直接調用,偽私有,調用_MyTest__age 就等於調用__age
_Age = 18 # 口頭私有,外界可以調用
def __init__(self, name):
self.__name = name
@classmethod # 類方法
def __add(cls):
print("add方法")
# print(cls)
def __run(self):
# 類內部私有的可以直接使用 ,外部不能
self.__add()
print(self.__name)
# 類私有屬性
# 單下滑線口頭協約的可以調用
print(MyTest._Age)
# 雙下滑不能直接被調用
# print(MyTest.__age) # AttributeError: type object 'MyTest' has no attribute '__age'
# print(MyTest.__dict__) # 查看對象屬性的方法 '_MyTest__age': 18, 名字內部做的轉換
print(MyTest._MyTest__age) # 調用轉換的后的名字即可,所以叫做偽私有
# 如下都一個道理
# 私有類方法
MyTest._MyTest__add()
# 實例私有屬性,方法
t = MyTest("DaBai")
print(t._MyTest__name)
t._MyTest__run()
__dict__
- 調用__dict__屬性,返回調用對象的內部屬性和實現的方法,字典的形式儲存
- 類調用,返回類屬性和方法的字典
- 實例調用,返回實例相關的屬性和方法
class MyClass:
name = "DaBai"
age = 18
class A(MyClass):
name = "Bai"
# 類
print(MyClass.__dict__) # 查看類屬性
print(A.__dict__) # 繼承的類 會少一些東西子類不在重復
# 實例
m = MyClass()
m.gender = "男" # 創建一個實例屬性
print(m.__dict__) # 實例默認創建一個字典保存屬性 {'gender': '男'}
內置屬性__slots__
- 默認情況下,類的實例有一個字典用於存屬性(可以讓實例調用__dict__查看),這對於很少實例變量的對象會浪費空間,當創建大量實例的時候,空間消耗可能會變得尖銳
- 可以通過在類中定義__slots__來覆蓋默認的__dict__行為,__slots__聲明接收一個實例變量序列,並在每個實例中保留足夠保存每個變量的空間,就不會為每個實例都創建一個__dict__保存實例實行,大家共用類設定好的__slots__里的屬性變量,等於把屬性寫死了,從而節省了空間
# 限定實例屬性,實例不創建__dict__
class Base:
# 指定實例所能綁定的屬性
# 實例的時候不在創建__dict__,節約內存
# 限制實例屬性
# 類本身不受限制
__slots__ = ["name", "ag[圖片]e", "gender"]
def __init__(self):
self.name = "DaBai" # __slots__中有的可以創建實例屬性
# self.height = "175cm" # __slots__ 中沒有的不能在創建,不然會報錯
m = Base() # 實例
# 類
print(Base.__dict__) # 類本身還有__dict__屬性
Base.geight = "185cm" # 還能增加類屬性
# 類和實例都能取到
print(Base.geight)
print(m.geight)
# 實例
# 實例沒有__dict__實行了,節省空間
print(m.__dict__) # 報錯 'Base' object has no attribute '__dict__'
# 實例也不能添加除了__slots__以為的屬性名
m.geight = "185cm" # 報錯 m.geight = "185cm" # 報錯 'Base' object attribute 'geight' is read-only
- 總結
- 類內部實現了__slots__,實例會去掉__dict__屬性
- 實例屬性被限制死在__slots__里了,不能在添加__slots__以外的屬性
- 類屬性沒有被限制,可以通過給類添加屬性,實例去獲取類的屬性
自定義屬性訪問
可以通過下面的方法來自定義類實例的屬性訪問的含義(訪問,賦值或者刪除屬性)
- object.__getattr__(self, item)
- 找不到屬性的時候觸發
- object.__getattribute__(self, item)
- 查找屬性的時候觸發
- object.__setattr__(self, key, value)
- 設置屬性的時候觸發
- object.__delattr__(self, item)
- 在del 刪除屬性的時候觸發
- 詳情請查看官方文檔-自定義屬性訪問
class Test:
def __init__(self):
self.age = 18
# 官方文檔提示:當找不到屬性的時候要么拋出異常
# 要么返回一個特定的數值
def __getattr__(self, item):
# 當我們訪問屬性的時候,屬性不存在的時候觸發
print("----這個是__getattr__方法----")
# return super().__getattribute__(item)
return 100
def __getattribute__(self, item):
# 訪問屬性的時候第一時間觸發
print("----__getattribute__----")
# 返回父類查看屬性的功能方法
return super().__getattribute__(item)
def __setattr__(self, key, value):
# print("__setattr__=", key) # 屬性名稱
# print("__setattr__=", value) # 屬性值
# 可以重寫這個設置一些干擾操作
if key == "age":
# 這樣屬性在外界對age的修改不會生效
return super().__setattr__(key, 18)
# 返回父類的設置屬性的方法
return super().__setattr__(key, value)
def __delattr__(self, item):
print("__delete__被觸發了")
# 我們可以控制哪個屬性不能被外界刪除
print(item)
if item == "age":
print("不能被刪除")
else:
return super().__delattr__(item)
t = Test()
# 設置屬性的時候 觸發__setattr__
t.name = "DaBai"
# 先觸發查找的方法,找到了不會在去觸發__getattr__方法
print(t.name)
# 先觸發查找方法,找不到才去觸發__getattr__方法
print(t.name1)
# 設置修改age屬性,觸發__setattr__
t.age = 1111111
t.name = "2222222"
print(t.age) # >>>在 __setattr__方法中過濾了,還是18
print(t.name) # 會被修改
# 刪除的時候觸發__delattr__
del t.name
print(t.name) # 屬性刪除了
del t.age
print(t.age) # 過濾了這個屬性不能在被外界刪除了
描述器
描述器時一個具有"綁定行為"的對象屬性,該對象的屬性訪問通過描述其協議覆蓋:__set__()和__get__()和__delete__(),如果一個對象定義了這些方法中的任意一個,它就被成為描述器
- object.__get__(self,instance,owner)
- 獲取屬主類的屬性(類屬性訪問),或者該類的一個實例的屬性(實例屬性訪問),owner始終是主,instance是屬性訪問的實例,當屬性通過owner訪問是則為None,這個方法應該返回(計算后)的屬性值,或者引發一個AttributeError異常
- object.__set__(self,instance,value)
- 設置屬主類的實例instance的屬性為一個新值value
- object.__delete__(self,instance)
- 刪除屬主類的實例instance的屬性
- 詳情請訪問官方文檔
class Field:
"""
一個類中只要出現了以為下面三個方法,那么該類
就是一個描述器類;
這個類不會直接使用,而是定義在別的類的屬性
"""
def __get__(self, instance, owner):
"""
:param self:StrField類的實例
:param instance: 調用了描述器類的,那個類的實例 這里是Model類的實例
:param owner: 調用了描述器類的,那個類本身,這是Model類
:return: 返回設置的屬性值
"""
# print(owner)
print("__get__方法觸發了")
return self.value
def __set__(self, instance, value):
"""
:param self:StrField類的實例 這里是--name
:param instance: 調用了描述器類的,那個類的實例 這里是Model類的實例--m
:param value: 屬性值
"""
# print(self)
# print(instance)
# print(value)
print("__set__方法觸發了")
# 設置屬性值,這里不用返回,返回在__get__中處理
self.value = value
def __delete__(self, instance):
print("__delete__方法觸發")
# del self.value # 外界del屬性,觸發這里實現刪除
self.value = None # 刪除的時候返回None 就可以了
class Model:
# 屬性是描述器對象的時候
# 會覆蓋類中的查找,設置,刪除的屬性的方法
# 去執行描述器類中的的方法
name = Field()
age = Field
# 設置描述器類型的屬性值
m = Model()
m.name = "DaBai" # 設置屬性,觸發描述器類的set、get方法
print(m.name)
# 刪除
del m.name
print(m.name) # 觸發描述器的__delete__方法,重置None
ORM模型
- O(object): 類和對象
- R(Relation): 關系,關系數據庫中的表格
- M(Mapping): 映射
- ORM框架的功能
- 建立模型類和表直接的對應關系,允許我們通過對象的方式來操作數據類
- 根據設計的模型類生成數據庫中的表格
- 通過方便的配置就可以進行數據可的切換
- 數據庫中的類型字段
- mysql常用的數據類型
- 整數:int,bit
- 小數:decimal(表示浮點數,如decimal(5,2)表示共存5位數,小數占2位
- 字符串:varcahar(可變長度),char(不可變長度)
- 日期時間:date,time,detetime
- 枚舉類型: enum
- ORM模型中的對應的的字段(以django的ORM模型中選取的幾個字段)
- BooleanField :布爾類型
- CharField(max_length:最大長度):字符串
- IntegerField :整數
- mysql常用的數據類型
- 模型案例
- 描述器實現ORM模型中的字段類型
- 字符串類型字段
- int類型字段
- 布爾字段
- 可以用描述器簡單實現ORM模型字段,但是ORM模型並不是這么實現的,ORM模型是利用元類實現的
# str 字段
class CharField:
"""
設置一個str屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為str
"""
# 傳入字符串長度
def __init__(self, max_length=20):
self.max_length = max_length
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, str):
if len(value) <= self.max_length:
self.value = value
else:
raise ValueError("str Length should not exceed {}".format(self.max_length))
else:
raise TypeError("Must be a string type not{}".format(type(value)))
def __delete__(self, instance):
self.value = None
# int字段
class IntField:
"""
設置一個Int屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為int
"""
# 傳入字符串長度
def __init__(self, max_value=40):
self.max_value = max_value
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, int):
if value <= self.max_value:
self.value = value
else:
raise ValueError("The value should not be greater than {}".format(self.max_value))
else:
raise TypeError("Must be a int type not{}".format(type(value)))
def __delete__(self, instance):
self.value = None
# 布爾類型
class BooleanField:
"""
設置一個Bool屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為Bool
"""
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, bool):
self.value = value
else:
raise TypeError("%s Not Boolean " % type(value))
def __delete__(self, instance):
self.value = None
class Model:
# 設置描述其類的屬性 字符串類型
name = CharField(max_length=20)
paw = CharField(max_length=40)
age = IntField(30)
status = BooleanField()
m = Model()
# 賦值字符串類型
m.name = "DaBai"
# m.paw = "sfsdfsfsdfasfsadfsdafsdsfasffsfasfdsafsdfsafs" # 超長為空
print(m.name) # 可以設置字符串類型
# m.name = 123 # 設置非字符串類型報錯
# 賦值int
m.age = 18
# m.age = 60 # 超大報錯
# m.age = "111" # 類型報錯
# 布爾類型
m.status = False
# m.status = 111 # 類型報錯
四、元類
說明
- 元類比99%的用戶所憂慮的東西具有更深的魔法
- 如果你猶豫考慮是否需要它們,那么你根本不需要使用元類
- 實際需要他們的人確信他們的需要,並且不需要進行任何解釋
舊式類VS新式類
- 舊式lei
- 對於就是類,類(class)和類型(type)並不完全相同,一個舊式類的實例總是繼承一個名為instance的內置類型
- 如果object是舊式類的實例,那么object.class就表示該類,但是type(object)始終是instance類型
- 新式類
- 新式類統一了類(class)和類型(type)的概念,如果obj是新式類的實例,obj.class和type(obj)相同
- 注意
- 在Python2中,默認所有的類都是舊式類,在Python2.2之前,根本不支持新式類,從Python2.2開始,可以創建新式類,但是必須明確聲明它是新式類
- 總結
- 舊式類繼承intance: python2 默認繼承intance
- 新式類繼承object : python3中默認全部繼承頂級父類object,沒有舊式類的說法了,全部是新式類
類(class)和類型(type)
- 在python中,一切都是對象,類也是對象,所有一個類(class)必須有一個類型(type)
- 實例的類型
- 類的類型
- 類的類型
- 元類(type)
class Test:
pass
print(type(Test)) # 類的類型 >>> <class 'type'>
print(type(Test())) # 實例的類型 >>> <class '__main__.Test'>
-
元類(type)
- 功能一 :查看類型屬性
- 功能二 :創建類,所有類的創建都是依賴於元類創建的
- 元類也是繼承於頂級父類,靠父類的方法創建對象
- object類也是靠元類創建出來的
- 他們是兩個平行線,和先有雞還是先有蛋一個道理
-
利用元類創建類
- type(name,bases,dict)
- name:指定類名稱
- bases:指定繼承的基類元組
- dict:指定包含類主體定義的類屬性和方法
# 類中的方法
def func():
print("test")
# 通過元類創建對象
# 三個參數 name : 創建的類的類型名字,bases:繼承的類,必須是一個元組,dict類內的屬性和方法
Test = type("Test", (object,), {"name": "Dabai", "test": func})
print(Test) # <class '__main__.Test111'>
print(Test.name) # 打印屬性
Test.test() # 調用類方法
- 自定義元類
- 元類就是創建類這種對象的東西,type是Python中唯一的一個內置元類
- 自定義元類必須繼承於type,否者無法創建對象
- 類中創建對象是調用的new方法
- 需要重寫new方法
- 使用自定義元類創建類的時候,必須在創建類的指定用哪里元類創建類,默認是type,用metaclass參數指定
# type 創建類需要三個參數 name,bases,dict
# 簡單做一點點應用處理
class MyMetaClass(type):
# 將類的屬性名變成大寫,操作attr_dict即可
def __new__(cls, name, bases, attr_dict, *args, **kwargs):
print("最基礎的自定義元類")
# 遍歷屬性名成
for k, v in list(attr_dict.items()):
attr_dict.pop(k) # 刪除原來的k
attr_dict[k.upper()] = v # 名稱大寫重新賦值
# 默認給類設置一個__slots__屬性
attr_dict["__slots__"] = ["name","age","gender"]
return super().__new__(cls, name, bases, attr_dict)
# metaclass指定創建類的元類
class Test(metaclass=MyMetaClass):
name = "DaBai"
age = 99
gender = "男"
print(type(Test)) # >>><class '__main__.MyMetaClass'>
print(Test.__dict__) # 屬性名稱變成大寫
# print(Test.name) # 找不到了 因為做了大寫處理
print(Test.NAME)
# 通過自定義的元類創建的類自動綁定了__slots__屬性
# 那這種類的實例都默認去掉了__dict__屬性
# print(Test().__dict__) # 報錯 沒有__dict__實行了
# 元類支持繼承
class MyTest(Test):
pass
# 子類的類型也是父類所定義的MyMetaClass元類類型
print(type(MyTest)) # <class '__main__.MyMetaClass'>
ORM模型實現思路
在我們Python的Django中已經Flask.SQLAlchmey,中操作數據是會用到ORM模型,通常元類用來創建API是非常好的選擇,使用元類的編寫很復雜但是使用者可以非常簡潔的調用API即可
- 實現技術點分析
- 1、類對象表,創建類的時候需要自動生成對應的數據表
- 實例對象對應的一條數據,創建一個對象,需要在數據表中添加一條數據
- 屬性對象字段,修改對象屬性的同時需要修改數據庫中對應的字段
# 創建父類用於,統一字段的類型
# 用於元類創建類的時候判斷屬性類型
class BaseField:
pass
# 定義的好的字段類型
# str 字段
class CharField(BaseField):
"""
設置一個str屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為str
"""
# 傳入字符串長度
def __init__(self, max_length=20):
self.max_length = max_length
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, str):
if len(value) <= self.max_length:
self.value = value
else:
raise ValueError("str Length should not exceed {}".format(self.max_length))
else:
raise TypeError("Must be a string type not{}".format(type(value)))
def __delete__(self, instance):
self.value = None
# int字段
class IntField(BaseField):
"""
設置一個Int屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為int
"""
# 傳入字符串長度
def __init__(self, max_value=40):
self.max_value = max_value
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, int):
if value <= self.max_value:
self.value = value
else:
raise ValueError("The value should not be greater than {}".format(self.max_value))
else:
raise TypeError("Must be a int type not{}".format(type(value)))
def __delete__(self, instance):
self.value = None
# 布爾類型
class BooleanField(BaseField):
"""
設置一個Bool屬性值,別的類在引用這個描述器
給屬性賦值的時候限定了屬性類型為Bool
"""
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if isinstance(value, bool):
self.value = value
else:
raise TypeError("%s Not Boolean " % type(value))
def __delete__(self, instance):
self.value = None
# 第一步創建元類
class FieldMateClass(type):
"""模型類的元類"""
def __new__(cls, name, bases, attrs, *args, **kwargs):
# 模型類的父類不需要創建表名和字段關系,只有模型類才需要
# 過濾一下
if name == "BaseModel":
return super().__new__(cls, name, bases, attrs)
else:
# 類名對應數據類表名,通常為轉成小寫
table_name = name.lower()
# 生成字段和表的映射關系-屬性都保存在attrs中
# 定義一個字典儲存,建立字段映射關系
fields = {}
for k, v in list(attrs.items()): # 遍歷所有的屬性
if isinstance(v, BaseField): # 判斷所有的屬性是不是字段類型的
fields[k] = v # 是字段類型的添加字段對應關系字典中
# print(fields)
# 屬性字典中添加標名和字段映射關系
attrs["table_name"] = table_name
attrs["fields"] = fields
return super().__new__(cls, name, bases, attrs)
# 第二步 定義一個模型類的基類
# 重寫init方法,方便實例的時候賦值
# 好處一:不然模型類每次實例屬性都要一個個添加
# 好處二:每個模型類都能繼承這個基類,不用每個模型類都寫一個init,或者生成sql的方法
class BaseModel(metaclass=FieldMateClass):
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value) # 設置屬性的內置方法,
# 保存一條數據,生成一條對應的sql語句
def save_data(self):
# 獲取表名
# 獲取字段對應關系字典
table_name = self.table_name
fields = self.fields
print(fields) # # 這里面存儲的是字段 和字段對象的關系
field_dict = {} # 創建一個字典存儲字段和字段值
# 遍歷字段映射關系遍歷key,獲取字段值加入到field_dict字段中
for field in self.fields:
try: # 處理非必填, 沒有的字段不收集
field_dict[field] = getattr(self, field) # 內置方法,通過key 獲取值
except AttributeError:
pass
# 生成sql
print(field_dict)
sql = "INSET INTO {} {} VALUE{}".format(table_name, tuple(field_dict.keys()), tuple(field_dict.values()))
return sql
def select_data(self):
# 查詢數據
pass
# 第三步,先自己定義模型類,類對應數據庫中的表
# 繼承模型類基類-實現元類繼承和init初始化操作
class User(BaseModel):
"""用戶模型類"""
# 模型類對應的字段--屬性
user_name = CharField()
pwd = CharField()
age = IntField()
status = BooleanField()
class Oder(BaseModel):
id = IntField()
money = CharField()
# print(User.table_name) # 類屬性中就能拿到表名
# print(User.fields) # 拿到字段的映射關系
# 一個模型類對象就對應一條數據
# 實例的時候一次性傳入實例屬性
xiao_ming = User(user_name="小明", pwd="123456", age=17, status=False)
oder_1 = Oder(id=1, money="1.22")
print(xiao_ming.user_name)
print(oder_1.id)
print(xiao_ming.save_data())