1. 程式人生 > >Python——詳解__slots__,property和私有方法

Python——詳解__slots__,property和私有方法

本文始發於個人公眾號:TechFlow,原創不易,求個關注


今天是Python專題的第11篇文章,我們來聊聊面向物件的一些進階使用。

__slots__

如果你看過github當中一些大牛的程式碼,你會發現很多大牛經常在類的頂部加上__slots__關鍵字。如果你足夠好奇,你可能會試著把這個關鍵字去掉再執行試試,你會發現去掉了之後什麼也沒有發生,一切依然執行得很好。

那麼這個__slots__關鍵字究竟是做什麼的呢?

它主要有兩個功能,我們先來說第一個功能,就是限制使用者的使用。

我們都知道Python是一門非常靈活的動態語言,很多在其他語言看起來完全不能容忍的事情在Python當中是可行的,這也是Python的設計理念,為了靈活和程式碼方便犧牲了效率。比如我們來看一個很簡單的例子,由於Python是動態語言,所以類的成員甚至可以在類建立好了之後動態建立。這在靜態語言當中是絕對不行的,我們只能呼叫類當中已有的屬性,是不能或者很難新增新屬性的。

比如這段程式碼:

class Exp:
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

我們定義了一個類叫做Exp,我們為它建立了a和b兩個成員。但是我們在使用的時候,對c成員進行了賦值。要知道Exp類當中是沒有成員c的,但是程式並不會報錯,我們這麼運行了之後它會將c新增進這個例項當中。

從一方面來看,這當然非常靈活,但是另一方面,這也留下了隱患。如果使用者隨意新增屬性,可能會導致未知的問題,尤其在複雜的系統當中。所以有些時候為了嚴謹,我們會不希望使用者做這種動態的修改。__slots__正是用來做這個的。

我們把這個關鍵字加上,再來執行結果就不一樣了:

class Exp:

    __slots__ = ['a', 'b']
    def __init__(self):
        self.a = None
        self.b = None


if __name__ == "__main__":
    exp = Exp()
    exp.c = 3
    print(exp.c)

如果你執行這段程式碼的話,你會得到一個報錯,提示你Exp這個物件當中並沒有c這個成員,也就是說我們只能運用__slots__這個關鍵字當中定義的成員,對於沒有定義的成員不能隨意建立,這樣就限制了使用者的使用。

雖然現在大部分人使用這個關鍵字都是報著這個目的,但是很遺憾的是,Python建立者的初衷其實並不是這個。這就談到了__slots__關鍵字的第二個作用,就是節省記憶體。

如果瞭解過Python底層的實現原理,你會發現在Python當中為每一個例項都建立了一個字典,就是大名鼎鼎的__dict__字典。正是因為背後有一個字典,所以我們才可以創造出原本不存在的成員,也才支援這樣動態的效果。我們可以人工地呼叫這個字典輸出其中的內容,我們在加上__slots__關鍵字之前,輸出的結果是這樣的:

{'a': None, 'b': None}

但是加上了這個關鍵字之後,會得到一個報錯,會告訴你Exp這個物件當中沒有__dict__這個成員。原因很簡單,因為使用dict來維護例項,會消耗大量的記憶體,額外儲存了許多資料,而使用__slots__之後,Python內部將不再為例項建立一個字典來維護,而是會使用一個固定大小的陣列,這樣就節省了大量的空間。這個節省可不是一點半點,一般可以節省一半以上。也就是說犧牲了一定的靈活性,保證了效能。這一點也是__slots__這個關鍵字設計的初衷,但是現在很多人都用錯了地方。

property

這個關鍵字在的文章當中曾經提到過,不過很不好意思的是,由於之前寫文章的時候對它的瞭解還很有限,導致一些闡述存在一些謬誤,所以這裡再提一下這個關鍵字的運用作為彌補。

property可以幫我們繫結類當中一些屬性的賦值和獲取,也就是get和set。我們來看個例子:

class Exp:
    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        self._param = value

這裡的property註解會在我們呼叫.param的時候被執行,而param.setter會在我們為param這個屬性賦值的時候被執行。所以你可能會奇怪,為什麼我們在__init__方法當中初始化的時候用的是self.param = param而不是self._param = param,這是因為我們在執行前者的時候,Python一樣會呼叫@param.setter這個註解,所以我們沒有必要寫成後者的形式。當然你也可以這麼寫,不過兩者是完全等價的。

作為一個前Java程式設計師為類當中所有變數加上get和set方法幾乎成了政治正確,所以我特別喜歡為類當中所有的屬性加上property。但是這是不對的,加上property是非常耗時的,所以如非必要不要這麼做,我們直接呼叫來進行賦值就好了,如果有必要,我們可以手動寫上get和set方法。那麼問題來了,既然不是為了規範,那麼我們又為什麼要用到property呢?

答案很簡單,為了校驗變數型別。

由於Python是動態語言,並且是隱式型別的,所以我們拿到變數的時候並不知道它究竟是什麼型別,也不知道使用者為給它賦值成什麼型別。所以在一些情況下我們可能會希望做好限制,告訴使用者只能將這個變數賦值成這個型別,否則就會報錯。通過使用property,我們可以很方便地做到這點。

class Exp:

    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

除此之外,property還有一個用法是代替函式。舉個例子:

class Exp:

    def __init__(self, param):
        self.param = param

    @property
    def param(self):
        return self._param

    @param.setter
    def param(self, value):
        if not isinstance(value, str):
            raise TypeError('Want a string')
        self._param = value

    @property
    def hello(self):
        return 'hello ' + self.param

這樣我們就可以通過.hello來代替呼叫一個函式,這樣做其實是一種動態計算。hello的結果並沒有被儲存起來,之後當我們呼叫的時候才會執行,在一些場景下這樣做會非常方便。

命名規範

最後我們來看下Python物件當中的命名規範,在之前的文章當中我們曾經說過,在Python當中沒有對public和private的欄位做區分,所有的欄位都是public的,也就是說使用者可以拿到類當中所有的欄位和方法。為了規範,程式設計師們約定俗成,決定所有加了下劃線的方法和變數都看成是private的,即使我們能呼叫,但是一般情況下我們也不這麼幹。

所以我們通常會寫兩個方法,一個是公開的介面,一個是內部的實現。我們呼叫的時候只調用公開的介面,公開的介面再去呼叫內部的實現。這在Python當中已經成了慣例,因為我們在呼叫內部方法的時候,往往還會傳入一些內部的引數。

我們來看個簡單的例子:

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self._private_func()

    def _private_func(self):
        print('private ExpA')


if __name__ == "__main__":
    exp = ExpA()
    exp.public_func()

除了_之外我們經常還會看到一些兩個下劃線的變數和方法,那麼它們之間又有什麼區別呢?

為了回答這個問題,我們來看下面這個例子:

class ExpA:

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpA')

class ExpB(ExpA):

    def __init__(self):
        pass

    def public_func(self):
       self.__private_func()

    def __private_func(self):
        print('private ExpB')

if __name__ == "__main__":
    exp = ExpB()
    exp.public_func()
    exp._ExpB__private_func()
    exp._ExpA__private_func()

請問最後會輸出什麼?

我們試一下就知道,第一行輸出的是private ExpB,這個沒有問題。但是後面兩個是什麼?

後面兩個就是__private_func,只不過系統自動將它重新命名了。重新命名的原因也很簡單,因為Python禁止加了兩個下劃線的方法被子類覆蓋。所以這兩者的區別就在這裡,它們都被認為是private的方法和屬性,但是一個下劃線允許子類覆蓋,而兩個下劃線不行。所以如果我們在開發的時候希望我們某一個方法不會被子類覆蓋,那麼我們就需要加上兩個下劃線。

最後,我們來看一個小問題。在C++當中當我們的變數名和系統的關鍵字衝突的時候,我們往往會在變數前面加上一個_來作為區分。但是由於Python當中下劃線被賦予了含義,所以我們不能這麼幹,那麼當變數衝突的時候應該怎麼辦呢?答案也很簡單,我們可以把下劃線加在後面,比如lambda_。

總結

回顧一下今天的內容,主要是__slots__, property和下劃線在類當中的使用。這三者都是Python面向物件當中經常用到的知識,瞭解它們不但可以讓我們寫出更規範的程式碼,也有助於幫助我們理解其他大牛的原始碼,因此是非常必要的。

今天的文章就是這些,如果覺得有所收穫,請順手點個關注或者轉發吧,你們的舉手之勞對我來說很重要。

相關推薦

Python——__slots__property私有方法

本文始發於個人公眾號:TechFlow,原創不易,求個關注 今天是Python專題的第11篇文章,我們來聊聊面向物件的一些進階使用。 __slots__ 如果你看過github當中一些大牛的程式碼,你會發現很多大牛經常在類的頂部加上__slots__關鍵字。如果你足夠好奇,你可能會試著把這個關鍵字去掉再執行

call()apply()bind()

  之前看了點es6的箭頭函式,為了搞懂箭頭函式的this,看了很多文章,也順便看了幾個繫結函式,發現很多以前沒注意的問題,收穫不少。   之前就在網上的筆試題中看過用js實現bind()函式,沒怎麼在意,以為既然都是用來進行上下文繫結的,用call或者apply應該就能實

python-numpy.array中any()all()方法介紹

0.摘要 本文主要介紹numpy.array.any()和numpy.array.all()的用法和區別。 1.np.array.any()和numpy.array.all() np.array.any()是或操作,將np.array中所有元素進行或操作,然後返回T

Python時間獲取Django獲取時間模板中獲取時間(navie時間aware時間)

# 1、Python獲取到的時間 import pytz from datetime import datetime now = datetime.now() # 這個時間為navie時間(自己不

Python基礎(13):面向物件進階(訪問限制__slots__property獲取物件資訊類屬性例項屬性)

一,訪問限制 原因:直接操作物件屬性有兩個缺點:無法保證資料安全性,無法進行引數校驗。 示例: class fruit(object): #定義一個類 def __init__(self,name): #定義屬性name

python中字典dic-建立遍歷排序

在python的程式設計中,字典dic是最典型的資料結構,看看如下對字典的操作: 建立字典 直接使用 {} 建立空字典: book_price = {} 直接初始化的方式建立字典: book_price = {'a':23,'b':30}

如何在Centos6Centos7兩個版本上執行Cobbler無人值守安裝!

信息 cgroup 內容 manager 外網 oar 關聯 done 足夠 Cobbler介紹: Cobbler是一個Linux服務器快速網絡安裝的服務,而且在經過調整也可以支持網絡安裝windows。該工具使用python開發,小巧輕便(才15k行python代碼),可

java中的arraycopy()copyOf()copyOfRange()方法

java語言提供了很多的方法來對陣列進行復制。其中我們常用的方法有三種,下面一一列舉出來 1. arraycopy()方法 函式原型:arraycopy(Object src, int srcPos, Object dest, int destPos, int length) src: 原陣列

#圖文:從實際理論出發帶你瞭解Java中的多執行緒

這裡並沒有講什麼新東西,只是把多執行緒一些知識來個總結。大家懂得可以複習複習,還有些童鞋對多執行緒朦朧的可以拿這個做為入門~ 舉個栗子說明啥是多執行緒:玩遊戲,前面一堆怪,每個怪都是一個執行緒,你射了一槍,子彈飛出去了,這顆子彈也是一個執行緒。你開啟你的程序管理,看到你遊戲的後臺程序,這就是程序

二叉樹之一BST樹AVL樹及B樹紅黑樹原理分析

BST樹,AVL樹詳解及B樹和紅黑樹原理分析 網際網路面試中樹尤其是BST,AVL是提問的重點也是難點,甚至B樹乃至高階資料結構紅黑樹都是提問的重點,像阿里雲面試就曾經問過map實現機制(紅黑樹)及其原理,這裡我們要做到對BST/AVL完全熟悉能給出全部程式碼實現,紅黑樹、

Dockerfile,並構建docker私有庫,通過maven自動構建映象部署,達到持續整合

上篇給大家簡單的介紹了一下docker的架構,已經常用命令. 今天帶大家通過Dockerfile構建docker映象,並構建docker私有倉庫,通過maven進行打包自動構建映象自動部署,達到持續整合目的 廢話不多說,首先介紹下Dockerfile中常用的命令 FROM

HTTP協議 (三) 壓縮 之前寫過一個篇 【HTTP協議這次繼續介紹HTTP協議中的壓縮。 本文會使用Fiddler來檢視HTTP requestResponse, 如果不熟悉這個工

之前寫過一個篇 【HTTP協議詳解】 ,這次繼續介紹HTTP協議中的壓縮。 本文會使用Fiddler來檢視HTTP request和Response, 如果不熟悉這個工具,可以先參考[Fiddler教程] HTTP壓縮是指: Web伺服器和瀏覽器之間壓縮傳輸的”文字內容“的方法。 HTTP採用通用的壓縮演算

Python time 模組(時間獲取轉換)

time 模組 -- 時間獲取和轉換time 模組提供各種時間相關的功能在 Python 中,與時間處理有關的模組包括:time,datetime 以及 calendar必要說明:   雖然這個模組總是可用,但並非所有的功能都適用於各個平臺。 該模組中定義的大部分函式是呼

python中的面向物件(簡單類的建立以及內建方法私有屬性私有方法的使用)

一、什麼面向物件和麵向過程? 面向物件:--誰來做? 相比較函式,面向物件是更大的封裝,根據職責在一個物件中封裝多個方法 1.在完成某一個需求前,首先確定職責--要做的事(方法) 2.根據職責確定不同的物件,在物件內部封裝不同的方法(多個) 3.最後完成程式碼

陣列連結串列ADT

陣列 先由一個例子複習一下陣列的操作: class HighArray { private long[] a; private int nElems; //-------

Python——__str__, __repr____format__

本文始發於個人公眾號:TechFlow,原創不易,求個關注 今天是Python專題的第10篇文章,我們來聊聊Python當中的類。 列印例項 我們先從類和物件當中最簡單的列印輸出開始講起,列印一個例項是一個非常不起眼的應用,但是在實際的程式設計當中卻非常重要。原因也很簡單,因為我們debug的時候往往會想看

Python | Python中的協程為什麼說它的底層是生成器?

今天是Python專題的第26篇文章,我們來聊聊Python當中的協程。 我們曾經在golang關於goroutine的文章當中簡單介紹過協程的概念,我們再來簡單review一下。協程又稱為是微執行緒,英文名是Coroutine。它和執行緒一樣可以排程,但是不同的是執行緒的啟動和排程需要通過作業系統來處理。並

python的循環質數因子的定義

-1 是否 prime actor 質數 基本 可見 一半 循環 質數:能被1和本書整除的數()任何一個質數都有兩個因子是1和質數本身,比如1,2,3,5,7,11是質數,而4,6,8,9就不是質數,它們還能被2或者3整除 因子:1,2,4的因子分別是(1)(1,2)(1,

跨域問題相關知識(原生jsjquery兩種方法實現jsonp跨域)

syn con 加載 developer 兩種方法 ray exe 編寫 分組 1、同源策略 同源策略(Same origin policy),它是由Netscape提出的一個著名的安全策略。同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽

(轉)Java JVM 工作原理流程

移植 獲得 代碼 適配 調用 tac 階段 main方法 等待 作為一名Java使用者,掌握JVM的體系結構也是必須的。說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術,它由四方面組成:Java編程語言、Java類文件格式、Java虛擬機和Ja