草根學Python(十)Python 的 Magic Method

前言

距離上一篇已經三個多星期了,最近比較累,下班回到家,很早就休息了,因此更新的進度有點慢。python

目錄

草根學Python(十) Python 的 Magic Method
草根學Python(十) Python 的 Magic Method

1、Python 的 Magic Method

在 Python 中,全部以 "" 雙下劃線包起來的方法,都統稱爲"魔術方法"。好比咱們接觸最多的 `init__` 。魔術方法有什麼做用呢?編程

使用這些魔術方法,咱們能夠構造出優美的代碼,將複雜的邏輯封裝成簡單的方法。數據結構

那麼一個類中有哪些魔術方法呢?app

咱們可使用 Python 內置的方法 dir() 來列出類中全部的魔術方法.示例以下:函數

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    pass


if __name__ == '__main__':
    print(dir(User()))複製代碼

輸出的結果:spa

Python 類的魔術方法
Python 類的魔術方法

能夠看到,一個類的魔術方法仍是挺多的,截圖也沒有截全,不過咱們只須要了解一些常見和經常使用的魔術方法就行了。3d

2、構造(__new__)和初始化(__init__)

經過上一篇的內容,咱們已經知道定義一個類時,咱們常常會經過 __init__(self) 的方法在實例化對象的時候,對屬性進行設置。好比下面的例子:code

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __init__(self, name, age):
        self.name = name;
        self.age = age;

user=User('兩點水',23)複製代碼

實際上,建立一個類的過程是分爲兩步的,一步是建立類的對象,還有一步就是對類進行初始化。__new__ 是用來建立類並返回這個類的實例, 而__init__ 只是將傳入的參數來初始化該實例.__new__ 在建立一個實例的過程當中一定會被調用,但 __init__ 就不必定,好比經過pickle.load 的方式反序列化一個實例時就不會調用 __init__ 方法。orm

Python類建立的過程
Python類建立的過程

def __new__(cls) 是在 def __init__(self) 方法以前調用的,做用是返回一個實例對象。還有一點須要注意的是:__new__ 方法老是須要返回該類的一個實例,而 __init__ 不能返回除了 None 的任何值cdn

具體的示例:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __new__(cls, *args, **kwargs):
        # 打印 __new__方法中的相關信息
        print('調用了 def __new__ 方法')
        print(args)
        # 最後返回父類的方法
        return super(User, cls).__new__(cls)

    def __init__(self, name, age):
        print('調用了 def __init__ 方法')
        self.name = name
        self.age = age


if __name__ == '__main__':
    usr = User('兩點水', 23)複製代碼

看看輸出的結果:

調用了 def __new__ 方法
('兩點水', 23)
調用了 def __init__ 方法複製代碼

經過打印的結果來看,咱們就能夠知道一個類建立的過程是怎樣的了,先是調用了 __new__ 方法來建立一個對象,把參數傳給 __init__ 方法進行實例化。

其實在實際開發中,不多會用到 __new__ 方法,除非你但願可以控制類的建立。一般講到 __new__ ,都是牽扯到 metaclass(元類)的。

固然當一個對象的生命週期結束的時候,析構函數 __del__ 方法會被調用。可是這個方法是 Python 本身對對象進行垃圾回收的。

3、屬性的訪問控制

以前也有講到過,Python 沒有真正意義上的私有屬性。而後這就致使了對 Python 類的封裝性比較差。咱們有時候會但願 Python 可以定義私有屬性,而後提供公共可訪問的 get 方法和 set 方法。Python 其實能夠經過魔術方法來實現封裝。

方法 說明
__getattr__(self, name) 該方法定義了你試圖訪問一個不存在的屬性時的行爲。所以,重載該方法能夠實現捕獲錯誤拼寫而後進行重定向, 或者對一些廢棄的屬性進行警告。
__setattr__(self, name, value) 定義了對屬性進行賦值和修改操做時的行爲。無論對象的某個屬性是否存在,都容許爲該屬性進行賦值.有一點須要注意,實現 __setattr__ 時要避免"無限遞歸"的錯誤,
__delattr__(self, name) __delattr____setattr__ 很像,只是它定義的是你刪除屬性時的行爲。實現 __delattr__ 是同時要避免"無限遞歸"的錯誤
__getattribute__(self, name) __getattribute__ 定義了你的屬性被訪問時的行爲,相比較,__getattr__ 只有該屬性不存在時纔會起做用。所以,在支持 __getattribute__的 Python 版本,調用__getattr__ 前一定會調用 __getattribute__``__getattribute__ 一樣要避免"無限遞歸"的錯誤。

經過上面的方法表能夠知道,在進行屬性訪問控制定義的時候你可能會很容易的引發一個錯誤,能夠看看下面的示例:

def __setattr__(self, name, value):
    self.name = value
    # 每當屬性被賦值的時候, ``__setattr__()`` 會被調用,這樣就形成了遞歸調用。
    # 這意味這會調用 ``self.__setattr__('name', value)`` ,每次方法會調用本身。這樣會形成程序崩潰。

def __setattr__(self, name, value):
    # 給類中的屬性名分配值
    self.__dict__[name] = value  
    # 定製特有屬性複製代碼

上面方法的調用具體示例以下:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __getattr__(self, name):
        print('調用了 __getattr__ 方法')
        return super(User, self).__getattr__(name)

    def __setattr__(self, name, value):
        print('調用了 __setattr__ 方法')
        return super(User, self).__setattr__(name, value)

    def __delattr__(self, name):
        print('調用了 __delattr__ 方法')
        return super(User, self).__delattr__(name)

    def __getattribute__(self, name):
        print('調用了 __getattribute__ 方法')
        return super(User, self).__getattribute__(name)


if __name__ == '__main__':
    user = User()
    # 設置屬性值,會調用 __setattr__
    user.attr1 = True
    # 屬性存在,只有__getattribute__調用
    user.attr1
    try:
        # 屬性不存在, 先調用__getattribute__, 後調用__getattr__
        user.attr2
    except AttributeError:
        pass
    # __delattr__調用
    del user.attr1複製代碼

輸出的結果:

調用了 __setattr__ 方法
調用了 __getattribute__ 方法
調用了 __getattribute__ 方法
調用了 __getattr__ 方法
調用了 __delattr__ 方法複製代碼

4、對象的描述器

通常來講,一個描述器是一個有「綁定行爲」的對象屬性 (object attribute),它的訪問控制被描述器協議方法重寫。這些方法是 __get__(), __set__() , 和 __delete__() 。有這些方法的對象叫作描述器。

默認對屬性的訪問控制是從對象的字典裏面 (__dict__) 中獲取 (get) , 設置 (set) 和刪除 (delete) 。舉例來講, a.x 的查找順序是, a.__dict__['x'] , 而後 type(a).__dict__['x'] , 而後找 type(a) 的父類 ( 不包括元類 (metaclass) ).若是查找到的值是一個描述器, Python 就會調用描述器的方法來重寫默認的控制行爲。這個重寫發生在這個查找環節的哪裏取決於定義了哪一個描述器方法。注意, 只有在新式類中時描述器纔會起做用。在以前的篇節中已經提到新式類和舊式類的,有興趣能夠查看以前的篇節來看看,至於新式類最大的特色就是全部類都繼承自 type 或者 object 的類。

在面向對象編程時,若是一個類的屬性有相互依賴的關係時,使用描述器來編寫代碼能夠很巧妙的組織邏輯。在 Django 的 ORM 中,models.Model中的 InterField 等字段, 就是經過描述器來實現功能的。

咱們先看下下面的例子:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class User(object):
    def __init__(self, name='兩點水', sex='男'):
        self.sex = sex
        self.name = name

    def __get__(self, obj, objtype):
        print('獲取 name 值')
        return self.name

    def __set__(self, obj, val):
        print('設置 name 值')
        self.name = val


class MyClass(object):
    x = User('兩點水', '男')
    y = 5


if __name__ == '__main__':
    m = MyClass()
    print(m.x)

    print('\n')

    m.x = '三點水'
    print(m.x)

    print('\n')

    print(m.x)

    print('\n')

    print(m.y)複製代碼

輸出的結果以下:

獲取 name 值
兩點水


設置 name 值
獲取 name 值
三點水


獲取 name 值
三點水


5複製代碼

經過這個例子,能夠很好的觀察到這 __get__()__set__() 這些方法的調用。

再看一個經典的例子

咱們知道,距離既能夠用單位"米"表示,也能夠用單位"英尺"表示。
如今咱們定義一個類來表示距離,它有兩個屬性: 米和英尺。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-


class Meter(object):
    def __init__(self, value=0.0):
        self.value = float(value)

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = float(value)


class Foot(object):
    def __get__(self, instance, owner):
        return instance.meter * 3.2808

    def __set__(self, instance, value):
        instance.meter = float(value) / 3.2808


class Distance(object):
    meter = Meter()
    foot = Foot()


if __name__ == '__main__':
    d = Distance()
    print(d.meter, d.foot)
    d.meter = 1
    print(d.meter, d.foot)
    d.meter = 2
    print(d.meter, d.foot)複製代碼

輸出的結果:

0.0 0.0
1.0 3.2808
2.0 6.5616複製代碼

在上面例子中,在尚未對 Distance 的實例賦值前, 咱們認爲 meter 和 foot 應該是各自類的實例對象, 可是輸出倒是數值。這是由於 __get__ 發揮了做用.

咱們只是修改了 meter ,而且將其賦值成爲 int ,但 foot 也修改了。這是 __set__ 發揮了做用.

描述器對象 (Meter、Foot) 不能獨立存在, 它須要被另外一個全部者類 (Distance) 所持有。描述器對象能夠訪問到其擁有者實例的屬性,好比例子中 Foot 的 instance.meter

5、自定義容器(Container)

通過以前編章的介紹,咱們知道在 Python 中,常見的容器類型有: dict, tuple, list, string。其中也提到過可容器和不可變容器的概念。其中 tuple, string 是不可變容器,dict, list 是可變容器。 可變容器和不可變容器的區別在於,不可變容器一旦賦值後,不可對其中的某個元素進行修改。固然具體的介紹,能夠看回以前的文章,有圖文介紹。

那麼這裏先提出一個問題,這些數據結構就夠咱們開發使用嗎?不夠的時候,或者說有些特殊的需求不能單單隻使用這些基本的容器解決的時候,該怎麼辦呢?

這個時候就須要自定義容器了,那麼具體咱們該怎麼作呢?

功能 說明
自定義不可變容器類型 須要定義 __len____getitem__ 方法
自定義可變類型容器 在不可變容器類型的基礎上增長定義 __setitem____delitem__
自定義的數據類型須要迭代 需定義 __iter__
返回自定義容器的長度 需實現 __len__(self)
自定義容器能夠調用 self[key] ,若是 key 類型錯誤,拋出TypeError ,若是無法返回key對應的數值時,該方法應該拋出ValueError 須要實現 __getitem__(self, key)
當執行 self[key] = value 調用是 __setitem__(self, key, value)這個方法
當執行 del self[key] 方法 其實調用的方法是 __delitem__(self, key)
當你想你的容器能夠執行 for x in container: 或者使用 iter(container) 須要實現 __iter__(self) ,該方法返回的是一個迭代器

來看一下使用上面魔術方法實現 Haskell 語言中的一個數據結構:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class FunctionalList:
    ''' 實現了內置類型list的功能,並豐富了一些其餘方法: head, tail, init, last, drop, take'''

    def __init__(self, values=None):
        if values is None:
            self.values = []
        else:
            self.values = values

    def __len__(self):
        return len(self.values)

    def __getitem__(self, key):
        return self.values[key]

    def __setitem__(self, key, value):
        self.values[key] = value

    def __delitem__(self, key):
        del self.values[key]

    def __iter__(self):
        return iter(self.values)

    def __reversed__(self):
        return FunctionalList(reversed(self.values))

    def append(self, value):
        self.values.append(value)

    def head(self):
        # 獲取第一個元素
        return self.values[0]

    def tail(self):
        # 獲取第一個元素以後的全部元素
        return self.values[1:]

    def init(self):
        # 獲取最後一個元素以前的全部元素
        return self.values[:-1]

    def last(self):
        # 獲取最後一個元素
        return self.values[-1]

    def drop(self, n):
        # 獲取全部元素,除了前N個
        return self.values[n:]

    def take(self, n):
        # 獲取前N個元素
        return self.values[:n]複製代碼

6、運算符相關的魔術方法

運算符相關的魔術方法實在太多了,j就大概列舉下面兩類:

一、比較運算符

魔術方法 說明
__cmp__(self, other) 若是該方法返回負數,說明 self < other; 返回正數,說明 self > other; 返回 0 說明 self == other。強烈不推薦來定義 __cmp__ , 取而代之, 最好分別定義 __lt__, __eq__ 等方法從而實現比較功能。 __cmp__ 在 Python3 中被廢棄了。
__eq__(self, other) 定義了比較操做符 == 的行爲
__ne__(self, other) 定義了比較操做符 != 的行爲
__lt__(self, other) 定義了比較操做符 < 的行爲
__gt__(self, other) 定義了比較操做符 > 的行爲
__le__(self, other) 定義了比較操做符 <= 的行爲
__ge__(self, other) 定義了比較操做符 >= 的行爲

來看個簡單的例子就能理解了:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

class Number(object):
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        print('__eq__')
        return self.value == other.value

    def __ne__(self, other):
        print('__ne__')
        return self.value != other.value

    def __lt__(self, other):
        print('__lt__')
        return self.value < other.value

    def __gt__(self, other):
        print('__gt__')
        return self.value > other.value

    def __le__(self, other):
        print('__le__')
        return self.value <= other.value

    def __ge__(self, other):
        print('__ge__')
        return self.value >= other.value


if __name__ == '__main__':
    num1 = Number(2)
    num2 = Number(3)
    print('num1 == num2 ? --------> {} \n'.format(num1 == num2))
    print('num1 != num2 ? --------> {} \n'.format(num1 == num2))
    print('num1 < num2 ? --------> {} \n'.format(num1 < num2))
    print('num1 > num2 ? --------> {} \n'.format(num1 > num2))
    print('num1 <= num2 ? --------> {} \n'.format(num1 <= num2))
    print('num1 >= num2 ? --------> {} \n'.format(num1 >= num2))複製代碼

輸出的結果爲:

__eq__
num1 == num2 ? --------> False 

__eq__
num1 != num2 ? --------> False 

__lt__
num1 < num2 ? --------> True 

__gt__
num1 > num2 ? --------> False 

__le__
num1 <= num2="" ?="" --------=""> True 

__ge__
num1 >= num2 ? --------> False
  
  
  

 複製代碼

二、算術運算符

魔術方法 說明
__add__(self, other) 實現了加號運算
__sub__(self, other) 實現了減號運算
__mul__(self, other) 實現了乘法運算
__floordiv__(self, other) 實現了 // 運算符
___div__(self, other) 實現了/運算符. 該方法在 Python3 中廢棄. 緣由是 Python3 中,division 默認就是 true division
__truediv__(self, other) 實現了 true division. 只有你聲明瞭 from __future__ import division 該方法纔會生效
__mod__(self, other) 實現了 % 運算符, 取餘運算
__divmod__(self, other) 實現了 divmod() 內建函數
__pow__(self, other) 實現了 ** 操做. N 次方操做
__lshift__(self, other) 實現了位操做 <<
__rshift__(self, other) 實現了位操做 >>
__and__(self, other) 實現了位操做 &
__or__(self, other) 實現了位操做 ` `
__xor__(self, other) 實現了位操做 ^

最後,若是對本文感興趣的,能夠關注下公衆號:

公衆號
公衆號
相關文章
相關標籤/搜索