1. 程式人生 > >Python3面向物件:例項(instance)

Python3面向物件:例項(instance)

  如果說類是一種資料結構的定義,那麼例項就是聲明瞭一個該型別的變數.在Python3.x中預設所有的類都為新式類,型別的例項就是這種型別的物件,定義一個新的類,即建立了一個新的類.

建立例項

  在大部分面向物件的語言中,提供了關鍵字new來建立類的例項.Python更加簡單,通過跟函式呼叫的形式來完成類的初始化和例項的建立.

class MyClass():
    pass
# 初始化類並建立例項
mc = MyClass()

_init_()方法(構造器constructor)

  建立例項時,類會被呼叫建立類的例項物件,然後Python會檢查是否實現了_init_(),預設情況下,如果沒有重寫_init_()的話,則執行預設的操作,返回一個預設情況下的例項.如果需要對例項增加一些初始的資料屬性的操作,就需要重寫(覆蓋)_init_()方法.
  如果在類的定義中重寫了特殊方法_init_(),在建立類的例項後會被呼叫,例項物件作為第一個引數self被傳入_init_(),完成類的初始化和例項建立._init_()方法是類定義的特殊方法之一,在預設情況下,不進行任何操作,如果需要定製一些類的屬性,就要重寫特殊方法,覆蓋它的的預設行為.

_new_()方法(構造器)

  類的特殊方法_new_()[靜態方法]其實跟特殊方法init()功能類似,都是在需要對類的例項增加一些特定的操作時要通過重寫這些特殊方法來實現,不同點是:init()方法是對從object基類繼承的類使用的特殊方法._new_()方法是當用戶要對Python的內建型別進行派生是用到的特殊方法.比如說要對字串型別,浮點型別資料進行派生時,要用到_new_()方法類初始化類._new_()方法會呼叫父類的_new_()建立物件.

_del_()方法(解構器destructor)

  特書方法_del_()很少被重寫(覆蓋),因為例項物件大都是被隱式的釋放掉,因為Python的垃圾物件回收機制是通過引用計數來實現的,當一個例項所有的引用都被清除後,_del_()方法就會被呼叫.

class P():
     def __del__(self):
            print('deleted')
class L(P):
    def __init__(self):
        print('initialized')
    def __del__(self):
        P.__del__(self)  # 呼叫父類的__del__方法
# 初始化,建立例項,列印initialized,呼叫__init__()
c1 = L()
# 別名對例項的引用
c2 = c1
c3 = c1
# 內建函式id()來確定不同別名是對同一個物件的引用
print(id(c1),id(c2),id(c3))
initialized
139950121340488 139950121340488 139950121340488
del c1
del c2
del c3 # 在對類的例項物件引用計數歸零的時候,__del__()被呼叫
deleted

總結:
  解構器只會被呼叫一次,一旦引用計數為0,物件就會被清除.
  從父類派生出的新類,在重寫解構器的時候,應該首先呼叫父類的解構器.
  呼叫del c1不表示呼叫了_del_(),_del_()總是隱式的被呼叫.
  如果對一個例項物件進行迴圈引用,導致計數不斷增加,_del_()將不會被呼叫.
 _del_()未捕獲的異常可能會被忽略,在解構器被呼叫的時候有些變數可能已經被刪除了.
 解構器如果被重寫了,並且例項是某個迴圈的一部分,垃圾回收機制將不會終止這個迴圈,需要顯示的呼叫解構器

跟蹤例項

  在Python中雖然有物件引用的計數機制,但是沒對例項建立的個數計數,所以要想知道一個類的多少個例項被建立了,需要手動的增加這個功能,可以通過覆蓋_init_(),_del_()方法來實現.使用一個靜態的類成員變數來計數.

class InstCount():
    count = 0     #類屬性

    # 每當建立一個例項,類屬性count就+1
    def __init__(self):
        InstCount.count += 1  

    def __del__(self):
        InstCount.count -=1

    # 返回例項數量
    def howMany(self):
        return InstCount.count
a1 = InstCount()
a2 = InstCount()
a3 = InstCount()
a1.howMany()
3
del a2
print('InsCount.count=%d, a1.howMany():%d'%(InstCount.count, a1.howMany()))
InsCount.count=2, a1.howMany():2
del a3
del a1
print('InsCount : %d'%InstCount.count)
InsCount : 0

例項屬性

  例項的方法屬性是從父類繼承,資料屬性既可以從父類繼承,也可以在例項建立後任意設定.還可以在類定義中在_init_()中設定例項的屬性.在靜態型別的語言中所有的屬性在使用前都要有明確的宣告和定義.作為動態語言Python中,例項的屬性可以動態的建立.這動態語言的優點同時也是它的一個缺陷.__如果屬性在條件語句中被建立,條件語句可能不會被執行,屬性也就不存在,如果在後面的程式碼中訪問個這個屬性就會丟擲Attribute異常,需要謹慎.

在構造器中設定例項屬性
  在例項建立時第一個呼叫的方法是_init_(),構造器執行完畢後返回例項物件,完成例項化.例項物件是自動在例項化後返回的.所以構造器不返回任何物件.在構造器中和普通的函式一樣可以設定預設引數,這樣在建立例項的時候就不需要顯式給構造器傳遞值了.但是在需要改變資料屬性的時候也可以通過顯式的傳遞新的值來覆蓋預設值.
 下面是一個租房的例子,假設某地租房要交8.5%的銷售稅,10%的房間稅.每天的房租不捨預設值.

class HotelRoomCalc():
    def __init__(self, rt, sales=0.085, rm=0.1):
        self.roomRate = rt
        self.salesTax = sales
        self.roomTax = rm
    def calcTotal(self, days=1):
        daily = round(self.roomRate*(1+self.salesTax+self.roomTax), 2)
        return float(days)*daily
# 日租金
f1 = HotelRoomCalc(299)
f1.calcTotal()
354.31
f1.calcTotal(100)
35431.0
# 新例項,另一個地方的房租情況
f2 = HotelRoomCalc(200, 0.25, 0.15)
f2.calcTotal()
280.0

例項屬性和類屬性都可以通過內建函式dir()列印所有的例項屬性:

print(dir(f1))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'calcTotal', 'roomRate', 'roomTax', 'salesTax']

例項也'繼承'了類的_dict_屬性

print(f1.__dict__)  # vars()內建函式也支援例項
{'roomRate': 299, 'salesTax': 0.085, 'roomTax': 0.1}

修改__dict_
  對於類和例項,_dict_屬性是可修改的,但是不建議這樣做,除非有非常明確的目的.這寫修改可能會破壞OOP,造成一些副作用.如果非要修改_dict_,還要重寫_setattr_()方法來實現,覆蓋setattr方法本身就是很冒險的.例如無窮的遞迴和破壞例項物件.

內建型別和例項:
  內建型別就是Python自帶的資料型別,列表,字典,浮點數,整數,浮點數,複數等等.內建型別和自定義型別一樣,可以同過dir()來檢視類例項屬性,內建型別沒有_dict_屬性

# 內建型別複數例項
x = 2+0.3j
type(x), x.__class__
(complex, complex)
print(dir(x))
['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__int__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', 'conjugate', 'imag', 'real']
print("x's imag:%.1f, x's real:%.1f"%(x.imag, x.real))
x's imag:0.3, x's real:2.0
# 共軛
print("x's conjugate:",x.conjugate())
x's conjugate: (2-0.3j)

例項屬性.類屬性

  例項屬性是動態屬性,可以在任何時候增加,刪除,修改,類的資料屬性像靜態成員一樣被引用,在多次例項化中類資料屬性不變.例項可以訪問類屬性,不改變類的屬性,修改類屬性需要類名.如果例項有同名屬性會覆蓋類屬性,此時可以通過類名來訪問類屬性.

class C():
    x = 1
c1 = C()
c1.x = 2
# 覆蓋類屬性
c1.x
2
del c1.x 
c1.x   # 有可以訪問到類屬性
1
# 類屬性改變,例項也改變
C.x = 3
c1.x
3

類屬性可變的情況

(類屬性是一個字典型別)

class P():
    d = {1:'dog'}
c = P()
c.d
{1: 'dog'}
c.d[2]='cat'
c.d
{1: 'dog', 2: 'cat'}
P.d  # 類屬性被改變了
{1: 'dog', 2: 'cat'}
del c.d
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-80-07492612cbc3> in <module>()
----> 1 del c.d


AttributeError: d

AttributeError是因為例項c並沒有建立同名的屬性,只是從類繼承的屬性