1. 程式人生 > 實用技巧 >Python基礎學習(26)classmethod/staticmethod 裝飾器 部分內建魔術方法

Python基礎學習(26)classmethod/staticmethod 裝飾器 部分內建魔術方法

Python基礎學習(26)classmethod/staticmethod 裝飾器 部分內建魔術方法

一、今日內容大綱

  • classmethod staticmethod 裝飾器
  • 部分內建魔術方法

二、classmethod staticmethod 裝飾器

  1. classmethod 裝飾器:對裝飾的繫結方法會變成類方法

    為了瞭解 classmethod 裝飾器到底有什麼作用,我們繼續使用之前舉過的售賣蘋果的例子:

    class Goods:
        __discount = 0.8
        def __init__(self):
            self.__price = 5
            self.price = self.__price * self.__discount
    
    apple = Goods()
    print(apple.price)  # 4.0
    

    這時我們如果想修改折扣為 6 折,可以進行下列修改:

    # 修改折扣 0.6
    class Goods:
        __discount = 0.8
        def __init__(self):
            self.__price = 5
            self.price = self.__price * self.__discount
        def change_discount(self, new_discount):  # self引數沒有利用
            Goods.__discount = new_discount
    # 雖然成功修改了,但是好像有點兒不和邏輯
    apple = Goods()
    apple.change_discount(0.6)
    apple2 = Goods()
    print(apple2.price)  # 3.0
    

    可以看到,雖然我們能夠成功實現修改購物商場的全場折扣,但是內建方法中的 self 引數是完全沒有利用的,且修改全場折扣要利用到個別物件的繫結方法,也就是說我們如果想要修改整個全場折扣,還要首先例項化一個物件,再依靠物件來修改折扣。這樣顯然是不符合正常邏輯的,所以我們使用 classmethod 裝飾器來將此方法定義為一個類方法:只可以被類呼叫(實際使用其實也可以被物件呼叫,但是一般不可以這麼使用):

    # 定義了一個方法,預設傳self,但這個self沒被使用,
    # 這時我們使用classmethod裝飾器
    class Goods:
        __discount = 0.8
        def __init__(self):
            self.__price = 5
            self.price = self.__price * self.__discount
        @classmethod
        def change_discount(cls, new_discount):  # 將self換成類名傳入
            cls.__discount = new_discount
    
    Goods.change_discount(0.6)  # 可以直接使用方法名修改,無需先例項化
    apple2 = Goods()
    print(apple2.price)  # 3.0
    

    所以一般什麼情況下,才使用 classmethod 裝飾器呢?

    • 定義了一個方法,預設傳 self,但是這個 self 沒有被使用;
    • 這個方法裡用到了當前的類名,或者你準備使用這個類的記憶體空間中的名字的時候;

    我們來舉一個實際應用中的例子:定義一個類Date,內部有年、月、日等例項變數,Date.today()可以返回一個儲存當天年月日的物件。

    import time
    class Date:
        def __init__(self, year, month, day):
            self.year = year
            self.month = month
            self.day = day
        @classmethod
        def today(cls):
            obj = cls(time.localtime().tm_year, time.localtime().tm_mon, time.localtime().tm_mday)
            return obj
    
    time_obj = Date.today()
    print(time_obj.__dict__)
    
  2. staticmethod 裝飾器:被裝飾的繫結方法會變成一個靜態方法

    class User:
        pass
        @staticmethod  # 本身是一個普通的函式,被挪到類的內部執行,那麼直接給這個函式添加了@staticmethod裝飾器就可以了
        def login():
            print('login')
            # 在函式的內部不會用到self變數,也不會用到cls類
    
    User.login()  # 除了前面要加上類名,其他使用方式與普通函式一致
    
  3. 小結

    能定義到類的內容主要有:

    • 靜態方法
    • 繫結方法
    • 類方法:利用 classmethod 裝飾器裝飾繫結方法實現
    • 靜態方法:利用 staticmethod 裝飾器裝飾繫結方法實現
    • 屬性/例項變數:可直接在__init__中定義或者利用 property 裝飾器裝飾繫結方法實現
    • 靜態變數

三、部分內建魔術方法

  1. 總覽

    魔術方法主要是指 Python 內建的__func__形式的方法,主要包括:

    • __new__
    • __call__
    • __len__
    • __eq__
    • __str__
    • __repr__
    • __del__
    • __enter__
    • __exit__
  2. __call__方法:object_name()呼叫此物件所屬類的__call__方法。

    class A:
        def __call__(self, *args, **kwargs):
            print('----------')
    
    obj = A()
    print(callable(obj))  # True
    obj()  # 呼叫__call__方法 ----------
    
  3. __len__方法:len(object_name)呼叫此物件所屬類的__call__方法。

    class Cls:
        def __init__(self, name):
            self.name = name
            self.students = []
        def __len__(self):
            return len(self.students)
    py22 = Cls('py22')
    py22.students.append('duxiangxi')
    py22.students.append('taibai')
    py22.students.append('dazhuang')
    print(len(py22.students))  # 3
    # 這兩者是等價的
    print(len(py22))  # 3
    print(py22.__len__())  # 3
    
  4. __new__方法:__new__方法就是在例項化之前所完成的操作,例項化的過程會先執行__new__方法,再執行__init__方法,例項化過程返回的物件也就是__new__返回的物件。

    # __new__
    class A:
        def __new__(cls, *args, **kwargs):
            o = super().__new__(cls)
            # o = object.__new__(cls)  # 因為父類是object也可以這麼寫
            print('new', o)
            return o
    
        def __init__(self):
            print('init', self)
    
    
    A()
    
    
    # new <__main__.A object at 0x0000016947968B38>
    # init <__main__.A object at 0x0000016947968B38>
    
    # 例項化的時候
    # 先建立一塊物件空間,有一個指標能指向類 -> 由__new__完成
    # 呼叫__init__
    
    # __new__是一個構造方法
    
    
    # 為什麼要自己實現一個__new__呢?
    
    
    # 設計模式 --> 單例模式
    # 一個類 從頭到尾 只會建立一次self的空間
    class Baby:
        __instance = None
    
        def __new__(cls, *args, **kwargs):
            if cls.__instance is None:
                cls.__instance = super().__new__(cls)
            return cls.__instance
    
        def __init__(self, cloth, pants):
            self.cloth = cloth
            self.pants = pants
    
    
    b1 = Baby('紅毛衣', '綠皮褲')
    b2 = Baby('白襯衫', '黑豹紋')
    print(b1)  # <__main__.Baby object at 0x0000022322CFDB00>
    print(b2)  # <__main__.Baby object at 0x0000022322CFDB00>
    print(b1.cloth)  # 白襯衫
    print(b2.cloth)  # 白襯衫
    
    
    # 利用模組的方式實現單例模式是最便捷的方法
    
  5. __str____repr__方法:在列印一個物件或者len(object_name)的時候,呼叫物件內部的__str__方法,如果物件內部不存在__str__方法則會呼叫備用的__repr__方法。

    # 2.__str__  __repr__
    class Course:
        def __init__(self, name, price, period):
            self.name = name
            self.price = price
            self.period = period
    
        def __str__(self):  # 它只能返回str資料型別
            return ','.join([self.name, str(self.price), self.period])
    
    
    python = Course('python', 21800, '6 months')
    linux = Course('linux', 19800, '3 months')
    mysql = Course('mysql', 12800, '4 months')
    go = Course('python', 21800, '4 months')
    print(go)  # python,21800,4 months
    lst = [python, linux, mysql, go]
    for course in lst:
        print(course)
    # for index, c in enumerate(lst, 1):
    #     print(index, c.name, c.price, c.period)
    # num = int(input('>>>'))
    # course = lst[num-1]
    # print(f'you choose {course.name} {course.price}.')
    # 在列印一個物件的時候,呼叫__str__方法
    # 在str一個物件的時候,呼叫__str__方法
    
    # 當我們列印一個物件的時候, 用%s進行字串批結,或者str(物件) 總是呼叫這個物件的__str__方法
    # 如果找不到__str__就呼叫__repr__方法
    
    # __repr__不僅僅是__str__的替代品,還有自己的功能
    # 用%r進行字串拼接 或者用repr(物件)的時候總是呼叫這個物件的__repr__方法