面向對象之魔術方法



一、魔術方法(魔術方法特殊方法)

__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 :整數
  • 模型案例
  • 描述器實現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())


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM