Python數據模型


引言

像大多數人一樣,我在對一直傳統的面向過程語言C一知半解之后,走進了面向對象的世界,盡管對OOP一無所知,還好Python還保留有函數式編程,這使得我才不那么抵觸,直到現在,習慣了面向對象之后,也習慣了接口這些叫法,而不是函數。

在看到len(collection)與collection.len(),也越來越習慣后者,他所代表的強大的思想,(其實是調用的collection對象的內部__len__方法),這種設計思想完全體現在 Python 的數據模型上,而數據模型所描述的 API,為使用最地道的語言特性來構建你自己的

對象提供了工具。數據模型其實是對 Python 框架的描述,它規范了這門語言自身構建模塊的接口,這些模塊包括但不限於序列、迭代器、函數、類等。

一、私有和被保護的屬性

類的私有屬性:

__private_attrs:兩個下划線開頭,聲明該屬性為私有,不能在類地外部被使用或直接訪問。
在類內部的方法中使用時  self.__private_attrs。

類的方法:

在類地內部,使用def關鍵字可以為類定義一個方法,與一般函數定義不同,類方法必須包含參數self,且為第一個參數

類的私有方法 :

__private_method:兩個下划線開頭,聲明該方法為私有方法,不能在類地外部調用。
在類的內部調用 self.__private_methods

默認情況下,Python中的成員函數和成員變量都是公開的(public),在python中沒有類似public,private等關鍵詞來修飾成員函數和成員變量。
在python中定義私有變量只需要在變量名或函數名前加上 ”__“兩個下划線,那么這個函數或變量就是私有的了。
在內部,python使用一種 name mangling 技術,將 __membername替換成 _classname__membername,也就是說,類的內部定義中,
所有以雙下划線開始的名字都被"翻譯"成前面加上單下划線和類名的形式。

     例如:為了保證不能在class之外訪問私有變量,Python會在類的內部自動的把我們定義的__spam私有變量的名字替換成為
_classname__spam(注意,classname前面是一個下划線,spam前是兩個下划線),因此,用戶在外部訪問__spam的時候就會
提示找不到相應的變量。   python中的私有變量和私有方法仍然是可以訪問的;訪問方法如下:

私有變量:實例._類名__變量名
私有方法:實例._類名__方法名()
 1 class people():
 2     __place = "nanjing"
 3     _age1 = 20
 4 
 5     def __init__(self, name):
 6         self.name = name
 7 
 8     def __sayhello(self):
 9         print("%s say hello" % self.name)
10 
11 class teacher(people):
12     pass
13 
14 t1 = teacher("cmz")
15 print(t1._people__place)  # 訪問私有變量
16 t1._people__sayhello()    # 訪問私有方法
17 
18 結果是
19 nanjing
20 cmz say hello
21 
22 python私有屬性和方法案例
View Code

其實,Python並沒有真正的私有化支持,但可用下划線得到偽私有。   盡量避免定義以下划線開頭的變量!

(1)_xxx      "單下划線" 開始的成員變量叫做保護變量,意思是只有類實例和子類實例能訪問到這些變量,
               需通過類提供的接口進行訪問;不能用'from module import *'導入
(2)__xxx    類中的私有變量/方法名 (Python的函數也是對象,所以成員方法稱為成員變量也行得通。),
              " 雙下划線 " 開始的是私有成員,意思是只有類對象自己能訪問,連子類對象也不能訪問到這個數據。
(3)__xxx__ 系統定義名字,前后均有一個“雙下划線” 代表python里特殊方法專用的標識,如 __init__()代表類的構造函數。
 1 class people():
 2     __place = "nanjing"
 3     _age = 20
 4 
 5     def __init__(self, name):
 6         self.name = name
 7 
 8     def _test(self):
 9         print("from people test")
10 
11     def __sayhello(self):
12         print("%s say hello" % self.name)
13 
14 class teacher(people):
15     pass
16 
17 t1 = teacher("cmz")
18 print(t1._age)
19 print(people._age)
20 t1._test()
21 people._test(t1) # 傳入對象t1
22 
23 結果是
24 20
25 20
26 from people test
27 from people test
View Code

 

 1 class people():
 2     __place = "nanjing"
 3     _age = 20
 4 
 5     def __init__(self, name):
 6         self.name = name
 7 
 8     def __sayhello(self):
 9         print("%s say hello" % self.name)
10 
11 class teacher(people):
12     pass
13 
14 t1 = teacher("cmz")
15 print(t1._people__place)
16 t1._people__sayhello()
17 
18 結果是
19 nanjing
20 cmz say hello
View Code

 

二、搭建一摞pythonic的紙牌

python的另一強大之處就是豐富的標准庫,還有許許多多的第三方庫,這使得不用重復造輪子

import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
  ranks = [str(n) for n in range(2, 11)] + list('JQKA')
  suits = 'spades diamonds clubs hearts'.split()
  def __init__(self):
    self._cards = [Card(rank, suit) for suit in self.suits
              for rank in self.ranks]
  def __len__(self):
    return len(self._cards)
  def __getitem__(self, position):
    return self._cards[position]

deck = FrenchDeck()
for i in deck[:10]:  # 其實這里調用的是deck這個可迭代對象背后其實用的是 iter(x),而這個函數的背后則是 x.__iter__() 方法

  print(i)

#打印十張紙牌
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
Card(rank='5', suit='spades')
Card(rank='6', suit='spades')
Card(rank='7', suit='spades')
Card(rank='8', suit='spades')
Card(rank='9', suit='spades')
Card(rank='10', suit='spades')
Card(rank='J', suit='spades')
# 對紙牌進行排序

suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0) def spades_high(card): rank_value = FrenchDeck.ranks.index(card.rank) return rank_value * len(suit_values) + suit_values[card.suit] for card in sorted(deck, key=spades_high): print(card) Card(rank='2', suit='clubs') Card(rank='2', suit='diamonds') Card(rank='2', suit='hearts') ... (46 cards ommitted) Card(rank='A', suit='diamonds') Card(rank='A', suit='hearts') Card(rank='A', suit='spades')

  

三、特殊方法

下面來看看特殊方法

beer_card = Card('7', 'diamonds')
>>> beer_card
Card(rank='7', suit='diamonds')

len()方法與特殊方法__len__,

特殊方法的存在是為了被 Python 解釋器調用的,你自己並不需要調用它們。也就是說沒有 my_object.__len__() 這種寫法,而應該使用 len(my_object)。在執行 len(my_object) 的時候,如果my_object 是一個自定義類的對象,那么 Python 會自己去調用其中由

你實現的 __len__ 方法。abs也是同理,

如果是 Python 內置的類型,比如列表(list)、字符串(str)、字節序列(bytearray)等,那么 CPython 會抄個近路,__len__ 實際上會直接返回 PyVarObject 里的 ob_size 屬性。PyVarObject 是表示內存中長度可變的內置對象的 C 語言結構體。直接讀取這

個值比調用一個方法要快很多。

>>> deck = FrenchDeck()
>>> len(deck)
52

從一疊牌中抽取特定的一張紙牌,比如說第一張或最后一張,是很容易的:deck[0] 或 deck[-1]。這都是由 __getitem__ 方法提供的

字典中也有這種用法,類似dic[k], 其背后也是__getitem__在默默支持,不過這里返回的值而是鍵k所對應的值value

>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts'

3.1、運算符重載

說到特殊方法來看一下運算符重載,運算符重載只是意味着在類方法中攔截內置的操作——當類的實例出現在內置操作中,Python自動調用你的方法,並且你的方法的返回值變成了相應操作的結果。

 

# Number類提供了一個方法來攔截實例的構造函數(__init__),此外還有一個方法捕捉減法表達式(__sub__)。這種特殊的方法是鈎子,可與內置運算綁定。
>>> class Number: def __init__(self,start): self.data = start def __sub__(self,other): return Number(self.data - other) >>> X = Number(5) >>> X.data 5 >>> Y = X - 2 >>> Y.data 3

 3.2、 方法重載

overloading method:是在一個類里面,方法名字相同,而參數不同。返回類型呢?可以相同也可以不同。重載是讓類以統一的方式處理不同類型數據的一種手段。
函數重載主要是為了解決兩個問題。
1.可變參數類型。
2.可變參數個數。

另外,一個基本的設計原則是,僅僅當兩個函數除了參數類型和參數個數不同以外,其功能是完全相同的,此時才使用函數重載,如果兩個函數的功能其實不同,那么不應當使用重載,而應當使用一個名字不同的函數。
 
對於情況 1 ,函數功能相同,但是參數類型不同,python 如何處理?答案是根本不需要處理,因為 python 可以接受任何類型的參數,如果函數的功能相同,那么不同的參數類型在 python 中很可能是相同的代碼,沒有必要做成兩個不同函數。

對於情況 2 ,函數功能相同,但參數個數不同,python 如何處理?大家知道,答案就是缺省參數。對那些缺少的參數設定為缺省參數即可解決問題。因為你假設函數功能相同,那么那些缺少的參數終歸是需要用的。

鑒於情況 1 跟 情況 2 都有了解決方案,python 自然就不需要函數重載了。

子類不想原封不動地繼承父類的方法,而是想作一定的修改,這就需要采用方法的重寫。方法重寫(overriding method)又稱方法覆蓋。

 

3.3、常見的特殊方法

當然也可以使用dir內置函數來查看常見並比較的數據結構的特殊方法,如list,dict等。

dir(list)
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__imul__',
 '__init__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__reversed__',
 '__rmul__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'append',
 'clear',
 'copy',
 'count',
 'extend',
 'index',
 'insert',
 'pop',
 'remove',
 'reverse',
 'sort']
View Code
dir(tuple)
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'count',
 'index']
View Code

 


免責聲明!

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



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