欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python面向?qū)ο蟪绦蛟O計OOP深入分析【構造函數(shù),組合類,工具類等】

 更新時間:2019年01月05日 14:40:38   作者:KLeonard  
這篇文章主要介紹了Python面向?qū)ο蟪绦蛟O計OOP,較為詳細的深入分析了Python面向?qū)ο蟮臉嬙旌瘮?shù),組合類,工具類等相關概念、使用方法及操作注意事項,需要的朋友可以參考下

本文深入分析了Python面向?qū)ο蟪绦蛟O計OOP。分享給大家供大家參考,具體如下:

下面是一個關于OOP的實例,模塊文件為person.py

# File person.py(start)
class Person:
  def __init__(self, name, job=None, pay=0):
    self.name = name
    self.job = job
    self.pay = pay
  def last_name(self):
    return self.name.split()[-1]
  def give_raise(self, percent):
    self.pay = int(self.pay * (1+percent))
    print('total percent:%f' % percent)
  def __str__(self):
    return '[Person: %s, %s]' % (self.name, self.pay)
class Manager(Person):
  # 這是一種不太好的方法重載的方法,實際應用中我們采用下面的方法
  def give_raise(self, percent, bonus=.1):
    self.pay = int(self.pay * (1+percent+bonus))
  # 這個方法利用了這樣的一個事實:類方法總是可以在一個實例中調(diào)用。
  # 其實常規(guī)的實例調(diào)用,也是轉(zhuǎn)換為類的調(diào)用
  # instance.method(args...) 由Python自動地轉(zhuǎn)換為 class.method(instance,args...)
  # 所以要記得直接通過類進行調(diào)用時,必須手動傳遞實例,這里就是self參數(shù)
  # 而且不能寫成self.give_raise,這樣會導致循環(huán)調(diào)用
  #
  # 那么為什么采用這種形式呢?因為它對未來的代碼的維護意義重大,因為give_raise現(xiàn)在
  # 只在一個地方,即Person的方法,將來需要修改的時候,我們只需要修改一個版本
  def give_raise(self, percent, bonus=.1):
    Person.give_raise(self, percent+bonus)
if __name__ == '__main__':
  # self-test code
  bob = Person('Bob Smith')
  sue = Person('Sue Jones', job='dev', pay=100000)
  print(bob)
  print(sue)
  print(bob.last_name(), sue.last_name())
  sue.give_raise(.1)
  print(sue)
  print('-'*20)
  tom = Manager('Tom Jones', 'mgr', 50000)
  tom.give_raise(.1)
  print(tom.last_name())
  print(tom)
  print('--All three--')
  for obj in (bob, sue, tom):
    obj.give_raise(.1)
    print(obj)

這個示例定義了Person類,并且Person類的構造函數(shù)采用了默認關鍵字參數(shù),重載了__str__方法用以print輸出,定義了得到last_name的方法,定義了give_raise漲工資方法。類Manager繼承自Person,Manager重新定義了自己的give_raise方法,獲得額外的bonus=0.1的獎金。

在代碼最后,寫了自測試的代碼if __name__ == '__main__',用以測試。輸出如下:

[Person: Bob Smith, 0]
[Person: Sue Jones, 100000]
Smith Jones
total percent:0.100000
[Person: Sue Jones, 110000]
--------------------
total percent:0.200000
Jones
[Person: Tom Jones, 60000]
--All three--
total percent:0.100000
[Person: Bob Smith, 0]
total percent:0.100000
[Person: Sue Jones, 121000]
total percent:0.200000
[Person: Tom Jones, 72000]

這里也可以給Manager增加自己獨有的方法。

定制構造函數(shù)

現(xiàn)在的代碼可以正常工作,但是,如果仔細研究會發(fā)現(xiàn),當我們創(chuàng)建Manager對象的時候,必須為它提供一個mgr工作名稱似乎沒有意義:這已經(jīng)由類自身暗示了。

所以,為了改善這點,我們要重新定義Manager中的__init__方法,從而提供mgr字符串,而且和give_raise的定制一樣,通過類名的調(diào)用來運行Person中最初的__init__。

def __init__(self, name, pay):
    Person.__init__(self, name, 'mgr', pay)

那么之后的實例化就變成了:

tom = Manager('Tom Jones', 50000)

OOP比我們認為的簡單

這是Python的OOP機制中幾乎所有重要的概念:

  1. 實例創(chuàng)建——填充實例屬性
  2. 行為方法——在類方法中封裝邏輯
  3. 運算符重載——為打印這樣的內(nèi)置操作提供行為
  4. 定制行為——重新定義子類中的方法以使其特殊化
  5. 定制構造函數(shù)——為超類步驟添加初始化邏輯。

組合類的其他方式

有時候,我們可以以其他的方式組合類。例如,一種常用的編碼模式是把對象彼此嵌套以組成復合對象,而不是繼承。如下的替代方法使用__getattr__運算符重載方法來攔截未定義屬性的訪問。這時候,我們的代碼如下:

class Person:
  ...same...
class Manager():
  def __init__(self, name, pay):
    self.person = Person(name, 'mgr', pay)
  def give_raise(self, percent, bonus=.1):
    self.person.give_raise(percent+bonus)
  def __getattr__(self, attr):
    return getattr(self.person, attr)
  def __str__(self):
    return str(self.person)
if __name__ == '__main__':
  ...same...

實際上,這個Manager替代方案是一種叫做委托的常用代碼模式的一個代表,委托是一種基于符合的結(jié)構,它管理一個包裝的對象并且把方法調(diào)用傳遞給它。

這里Manager不是一個真正的Person,因此,我們必須增加額外的代碼為嵌入的對象分派方法,比如像__str__這樣的運算符重載方法必須重新定義。所以它需要的代碼量增加,對于這個例子來說,沒有哪個明智的Python程序員會按照這種方式組織代碼,但是,當嵌入的對象比直接定制隱藏需要與容器之間有更多有限的交互時,對象嵌入以及基于其上的設計模式還是很適合的。

下述代碼假設Department可能聚合其他對象,以便將它們當做一個集合對待。

class Department:
  def __init__(self, *args):
    self.members = list(args)
  def add_member(self, person):
    self.members.append(person)
  def give_raise(self, percent):
    for person in self.members:
      person.give_raise(percent)
  def show_all(self):
    for person in self.members:
      print(person)
development = Department(bob,sue)
  development.add_member(tom)
  development.give_raise(.1)
  development.show_all()

這里的代碼使用了繼承和復合——Department是嵌入并控制其他對象的聚合的一個復合體,但是,嵌入的Person和Manager對象自身使用繼承來定制。作為另一個例子,一個GUI可能類似地使用繼承來定制標簽和按鈕的行為或外觀,但也會復合以構建嵌入的掛件(如輸入表單、計算器和文本編輯器)的一個更大的包。

使用內(nèi)省類工具

我們定制構造函數(shù)之后的Manager類還有幾點小問題如下:

  1. 打印的時候,Manager會把他標記為Person。如果能夠用最確切(也就是說最低層)的類來顯示對象,這可能會更準確些。
  2. 其次,當前的顯示格式只是顯示了包含在__str__中的屬性,而沒有考慮未來的目標。例如,我們無法通過Manager的構造函數(shù)驗證tom工作名已經(jīng)正確地設置為mgr,因為我們?yōu)镻erson編寫的__str__沒有打印出這一字段。更糟糕的是,如果我們改變了在__init__中分配給對象的屬性集合,那么還必須記得也要更新__str__以顯示新的名字,否則,將無法隨著時間的推移而同步。

我們可以使用Python的內(nèi)省工具來解決這兩個問題,它們是特殊的屬性和函數(shù),允許我們訪問對象實現(xiàn)的一些內(nèi)部機制。例如,在我們的代碼中,有兩個鉤子可以幫助我們解決問題:

  1. 內(nèi)置的instance.__class__屬性提供了一個從實例到創(chuàng)建它的類的鏈接。類反過來有一個__name__,還有一個__bases__序列,提供了超類的訪問。我們使用這些來打印創(chuàng)建的一個實例的類的名字,而不是通過硬編碼來做到。
  2. 內(nèi)置的object.__dict__屬性提供了一個字典,帶有一個鍵/值對,以便每個屬性都附加到一個命名空間對象(包括模塊、類和實例)。由于它是字典,因此我們可以獲取鍵的列表、按照鍵來索引、迭代其值等等。我們使用這些來打印出任何實例的每個屬性,而不是在定制顯示中硬編碼。

下面是這些工具在交互模式下的實際使用情形:

>>> from person import Person
>>> bob = Person('Bob Smith')
>>> print(bob)
[Person: Bob Smith, 0]
>>> bob.__class__
<class 'person.Person'>
>>> bob.__class__.__name__
'Person'
>>> list(bob.__dict__.keys())
['name', 'pay', 'job']
>>> for key in bob.__dict__:
...   print(key,'=>',bob.__dict__[key])
...
name => Bob Smith
pay => 0
job => None
>>> for key in bob.__dict__:
...   print(key,'=>',getattr(bob,key))
...
name => Bob Smith
pay => 0
job => None

一種通用的顯示工具

新打開一個文件,并編寫如下代碼:它是一個新的、獨立的模塊,名為classtools.py,僅僅實現(xiàn)了這樣一個類。由于其__str__,print重載用于通用的內(nèi)省工具,它將會對任何實例有效,不管實例的屬性集合是什么。并且由于這是一個類,所以它自動變成一個公用的工具:得益于繼承,他可以混合到想要使用它顯示格式的任何類中。作為額外的好處,如果我們想要改變實例的顯示,只需要修改這個類即可。

# File classtools.py
"""Assorted class utilities and tools"""
class AttrDisplay:
  """
  Provides an inheritable print overload method that displays
  instances with their class names and a name-value pair for
  each attribute stored on the instance itself(but not attrs
  inherited from its classes).Can be mixed into any class,
  and will work on any instance.
  """
  def gatherAttrs(self):
    attrs = []
    for key in sorted(self.__dict__):
      attrs.append('%s = %s' % (key,getattr(self,key)))
    return ','.join(attrs)
  def __str__(self):
    return '[%s:%s]' % (self.__class__.__name__, self.gatherAttrs())
if __name__ == '__main__':
  class TopTest(AttrDisplay):
    count = 0
    def __init__(self):
      self.attr1 = TopTest.count
      self.attr2 = TopTest.count + 1
      TopTest.count += 2
  class SubTest(TopTest):
    pass
  x, y = TopTest(), SubTest()
  print(x)
  print(y)

注意這里的文檔字符串,作為通用的工具,我們需要添加一些功能來產(chǎn)生文檔。

這里定義的__str__顯示了實例的類,及其所有的屬性名和值,按照屬性名排序。

[TopTest:attr1 = 0,attr2 = 1]
[SubTest:attr1 = 2,attr2 = 3]

工具類的命名考慮

最后一點需要考慮的是,由于classtools模塊中的AttrDisplayz類旨在和其他任意類混合的通用性工具,所以我們必須注意與客戶類潛在的無意的命名沖突。如果一個子類無意地自己定義了一個gatherAttrs名稱,它很可能會破壞我們的類。

為了減少這樣的名稱沖突的機會,Python程序員常常對于不想做其他用途的方法添加一個【單個下劃線】的前綴,在我們這個例子中就是_gatherAttrs。這不是很可靠,如果一個子類也定義了_gatherAttrs,該如何是好?但是它通常已經(jīng)夠用了,并且對于類內(nèi)部的方法,這是常用的Python命名慣例。

一種更好但是不太常用的方法是,只在方法名前面使用【兩個下劃線】符號,__gatherAttrs,Python自動擴展這樣的名稱,以包含類的名稱,從而使它們變得真正唯一。這一功能通常叫做【偽私有類屬性】,將在以后介紹。

首先要使用打印這一通用工具,所需要做的是從其模塊中導入它,使用繼承將其混合到頂層類中,并且刪除我們之前編寫的更專門的__str__方法。新的打印重載方法將會由Person的實例繼承,Manager的實例也會繼承。

下面就是類的最終形式:

# File person.py(start)
from classtools import AttrDisplay
class Person(AttrDisplay):
  """
  Create and process person records
  """
  def __init__(self, name, job=None, pay=0):
    self.name = name
    self.job = job
    self.pay = pay
  def last_name(self):
    return self.name.split()[-1]
  def give_raise(self, percent):
    self.pay = int(self.pay * (1+percent))
    print('total percent:%f' % percent)
class Manager(Person):
  """
  A customized Person with special requirements
  """
  def __init__(self, name, pay):
    Person.__init__(self, name, 'mgr', pay)
  def give_raise(self, percent, bonus=.1):
    Person.give_raise(self, percent+bonus)
if __name__ == '__main__':
  # self-test code
  bob = Person('Bob Smith')
  sue = Person('Sue Jones', job='dev', pay=100000)
  print(bob)
  print(sue)
  print(bob.last_name(), sue.last_name())
  sue.give_raise(.1)
  print(sue)
  print('-'*20)
  tom = Manager('Tom Jones', 50000)
  tom.give_raise(.1)
  print(tom.last_name())
  print(tom)

在這個版本中,也添加了一些新的注釋來記錄所做的工作和每個最佳實踐慣例——使用了功能性描述的文檔字符串和用于簡短注釋的#?,F(xiàn)在運行這段代碼,將會看到對象的所有屬性,并且最終的問題也解決了:由于AttrDisplay直接從self實例中提取了類名,所有每個對象都顯示其最近的(最低的)類的名稱——tom現(xiàn)在顯示為Manager,而不是Person。

[Person:job = None,name = Bob Smith,pay = 0]
[Person:job = dev,name = Sue Jones,pay = 100000]
Smith Jones
total percent:0.100000
[Person:job = dev,name = Sue Jones,pay = 110000]
--------------------
total percent:0.200000
Jones
[Manager:job = mgr,name = Tom Jones,pay = 60000]

這正是我們所追求的更有用的顯示,我們屬性顯示類已經(jīng)變成了一個【通用工具】,可以通過繼承將其混合到任何類中,從而利用它所定義的顯示格式。

最后:把對象存儲到數(shù)據(jù)庫中

我們創(chuàng)建的對象還不是真正的數(shù)據(jù)庫記錄,他們只是內(nèi)存中的臨時對象,而沒有存儲到文件這樣更為持久的媒介中。所以,現(xiàn)在要使用Python的一項叫做【對象持久化】的功能把對象保存。

Pickle和Shelve

對象持久化通過3個標準的庫模塊來實現(xiàn),這3個模塊在Python中都可用:

  1. pickle:任意的Python對象和字節(jié)串之間的序列化
  2. dbm:實現(xiàn)一個可通過鍵訪問的文件系統(tǒng),以存儲字符串
  3. shelve:使用另兩個模塊按照把Python對象存儲在一個文件中。

在Shelve數(shù)據(jù)庫中存儲對象

讓我們編寫一個新的腳本,把類的對象存儲到shelve中。在文本編輯器中,打開一個名為makedb.py的新文件,導入shelve模塊,用一個外部文件名打開一個新的shelve,把對象賦給shelve中的鍵,當我們操作完畢之后關閉這個shelve,因為已經(jīng)做了修改:

# File makedb.py:store Person objects on a shelve database
from person import Person, Manager
import shelve
bob = Person('Bob Smith')
sue = Person('Sue Jones', job='dev', pay='100000')
tom = Manager('Tom Jones', 50000)
db = shelve.open('persondb')
for obj in (bob, sue, tom):
  db[obj.name] = obj
db.close()

注意,這里我們把對象的名字用作鍵,從而把他們賦給shelve,這么做只是為了方便,在shelve中,鍵可以是任何的字符串,唯一的規(guī)則是,鍵必須是字符串并且是唯一的。這樣,我們就可以針對每個鍵只存儲一個對象。然而,我們在鍵之下的值可以是幾乎任何類型的Python對象:像字符串、列表和字典這樣的內(nèi)置對象,用戶定義的類實例,以及所有這些嵌套式的組合。

運行這段代碼,沒有輸出,意味著他可能有效。

交互式探索shelve

此時,當前的目錄下會有一個或多個真實的文件,它們的名字都以‘persondb'開頭。這就是我們存儲的文件,也就是我們的數(shù)據(jù)庫,是我們備份或移動存儲的時候需要復制和轉(zhuǎn)移的內(nèi)容。

在交互式命令窗口中可以查看這些文件:

>>> import glob
>>> glob.glob('person*')
['person.py', 'person2.py', 'persondb.bak', 'persondb.dat', 'persondb.dir']
>>> print(open('persondb.dir').read())
'Tom Jones', (1024, 91)
'Sue Jones', (512, 100)
'Bob Smith', (0, 80)
>>> print(open('persondb.dat','rb').read())
b'\x80\x03cperson\nPerson\nq\x00)\x81q\x01}q\x02(X\x03\x00\x00\x00jobq\x03NX\x03\x00\x00\x00payq\x04K\x00X\x04\x00\x00\x00nameq\x05X\t\x00\x00\x00Bob
...more omitted...

這些內(nèi)容無法解讀,但是我們可以用常規(guī)的Python語法和開發(fā)模式來處理它,即通過shelve來打開這些文件。

>>> import shelve
>>> db = shelve.open('persondb')
>>> for key in db:
...   print(key, '=>', db[key])
...
Bob Smith => [Person:job = None,name = Bob Smith,pay = 0]
Tom Jones => [Manager:job = mgr,name = Tom Jones,pay = 50000]
Sue Jones => [Person:job = dev,name = Sue Jones,pay = 100000]
>>> len(db)
3
>>> bob = db['Bob Smith']
>>> bob.last_name()
'Smith'

在這里,為了載入或使用存儲的對象,我們不一定必須導入Person或Manager類。因為Python對一個類實例進行pickle操作,它記錄了其self實例屬性,以及實例所創(chuàng)建于的類的名字和類的位置。

這種方法的結(jié)果就是,類實例在未來導入的時候,會自動地獲取其所有的類行為。

更新Shelve中的對象

現(xiàn)在介紹最后一段腳本,編寫一個程序,在每次運行的時候更新一個實例,以此證實我們的對象真的是持久化的。如下的updatadb.py打印出數(shù)據(jù)庫,并且每次把我們所存儲的對象之一增加一次,跟蹤它的變化:

Bob Smith => [Person:job = None,name = Bob Smith,pay = 0]
Sue Jones => [Person:job = dev,name = Sue Jones,pay = 110000]
Tom Jones => [Manager:job = mgr,name = Tom Jones,pay = 50000]
Tom Jones => [Manager:job = mgr,name = Tom Jones,pay = 50000]
Sue Jones => [Person:job = dev,name = Sue Jones,pay = 121000]
Bob Smith => [Person:job = None,name = Bob Smith,pay = 0]
Bob Smith => [Person:job = None,name = Bob Smith,pay = 0]
Sue Jones => [Person:job = dev,name = Sue Jones,pay = 133100]
Tom Jones => [Manager:job = mgr,name = Tom Jones,pay = 50000]

未來方向

通過這個例子,我們可以看到了Python的OOP的所有基本機制的實際運作,并且,學習了在代碼中避免冗余性及其相關可維護性問題的方法,還構建了功能完備的類來完成實際的工作。此外,我們還通過把對象存儲到Python的shelve中創(chuàng)建了正式的數(shù)據(jù)庫記錄,從而使它們的信息持久地存在。

這之后還有更多的內(nèi)容可以探討,比如擴展使用工具的范圍,包括Python附帶的工具以及開源世界中可以免費獲取的工具:

GUI:添加圖形化的用戶界面來瀏覽和更新數(shù)據(jù)庫記錄??梢詷嫿軌蛞浦驳絇ython的tkinter的GUI,或者可以移植到WxPython和PyQt這樣的第三方工具的GUI。tkinter是Python自帶的,允許我們快速地構建簡單的GUI,并且是學習GUI編程技巧的理想工具。

Web站點:盡管GUI方便而且很快,但Web在易用性方面勝出。Web站點可以用Python自帶的基本CGI腳本編程工具來構建,也可以用像Django、TurboGears、Pylons、web2Py、Zope或Google's App Engine這樣的全功能的第三方Web開發(fā)框架來完成。

數(shù)據(jù)庫:如果數(shù)據(jù)庫變得更大或者更關鍵,我們可以將其從shelve轉(zhuǎn)移到像開源的ZODB面向?qū)ο髷?shù)據(jù)庫系統(tǒng)(OODB)這樣一個功能更完備的存儲機制中,或者像MySQL、Oracle、PostgreSQL或SQLLite這樣的一個更傳統(tǒng)的基于SQL的數(shù)據(jù)庫系統(tǒng)中。Python自身帶有一個內(nèi)置的、正在使用的SQLLite數(shù)據(jù)庫。

ORM:如果我們真的遷移到關系數(shù)據(jù)庫中進行存儲,不一定要犧牲Python的OOP工具??梢杂肧QLObject和SQLAlchemy這樣的對象關系映射器(ORM)。

更多關于Python相關內(nèi)容感興趣的讀者可查看本站專題:《Python面向?qū)ο蟪绦蛟O計入門與進階教程》、《Python數(shù)據(jù)結(jié)構與算法教程》、《Python函數(shù)使用技巧總結(jié)》、《Python字符串操作技巧匯總》、《Python編碼操作技巧總結(jié)》及《Python入門與進階經(jīng)典教程

希望本文所述對大家Python程序設計有所幫助。

相關文章

最新評論