面向對象進階


一,面向對象結構與成員

1,1 面向對象結構分析:

如下面的圖所示:面向對象整體大致分兩塊區域:

那么每個大區域又可以分為多個小部分:

class A:

    company_name = '老男孩教育'  # 靜態變量(靜態字段)
    __iphone = '1353333xxxx'  # 私有靜態變量(私有靜態字段)


    def __init__(self,name,age): #特殊方法

        self.name = name  #對象屬性(普通字段)
        self.__age = age  # 私有對象屬性(私有普通字段)

    def func1(self):  # 普通方法
        pass

    def __func(self): #私有方法
        print(666)


    @classmethod  # 類方法
    def class_func(cls):
        """ 定義類方法,至少有一個cls參數 """
        print('類方法')

    @staticmethod  #靜態方法
    def static_func():
        """ 定義靜態方法 ,無默認參數"""
        print('靜態方法')

    @property  # 屬性
    def prop(self):
        pass
View Code

類有這么多的成員,那么我們先從那些地方研究呢? 可以從私有與公有部分,方法的詳細分類兩個方向去研究.

1,2面向對象的私有與公有

對於每一個類的成員而言都有兩種形式:

  • 公有成員,在任何地方都能訪問
  • 私有成員,只有在類的內部才能方法

私有成員和公有成員的訪問限制不同

靜態字段(靜態變量)

  • 公有靜態字段:類可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有靜態字段:僅類內部可以訪問;
class C:

    name = "公有靜態字段"

    def func(self):
        print C.name

class D(C):

    def show(self):
        print C.name


C.name         # 類訪問

obj = C()
obj.func()     # 類內部可以訪問

obj_son = D()
obj_son.show() # 派生類中可以訪問
公有靜態字段
class C:

    __name = "私有靜態字段"

    def func(self):
        print C.__name

class D(C):

    def show(self):
        print C.__name


C.__name       # 不可在外部訪問

obj = C()
obj.__name  # 不可在外部訪問
obj.func()     # 類內部可以訪問   

obj_son = D()
obj_son.show() #不可在派生類中可以訪問  
私有靜態字段

普通字段(對象屬性)

  • 公有普通字段:對象可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有普通字段:僅類內部可以訪問;
class C:
    
    def __init__(self):
        self.foo = "公有字段"

    def func(self):
        print self.foo  # 類內部訪問

class D(C):
    
    def show(self):
        print self.foo # 派生類中訪問

obj = C()

obj.foo     # 通過對象訪問
obj.func()  # 類內部訪問

obj_son = D();
obj_son.show()  # 派生類中訪問
公有普通字段
class C:
    
    def __init__(self):
        self.__foo = "私有字段"

    def func(self):
        print self.foo  # 類內部訪問

class D(C):
    
    def show(self):
        print self.foo # 派生類中訪問

obj = C()

obj.__foo     # 通過對象訪問    ==> 錯誤
obj.func()  # 類內部訪問        ==> 正確

obj_son = D();
obj_son.show()  # 派生類中訪問  ==> 錯誤
私有普通字段

方法:

  • 公有方法:對象可以訪問;類內部可以訪問;派生類中可以訪問
  • 私有方法:僅類內部可以訪問;
class C:

    def __init__(self):
        pass
    
    def add(self):
        print('in C')

class D(C):

    def show(self):
        print('in D')
        
    def func(self):
        self.show()
obj = D()
obj.show()  # 通過對象訪問   
obj.func()  # 類內部訪問    
obj.add()  # 派生類中訪問  
公有方法
class C:

    def __init__(self):
        pass

    def __add(self):
        print('in C')

class D(C):

    def __show(self):
        print('in D')

    def func(self):
        self.__show()
obj = D()
obj.__show()  # 通過不能對象訪問
obj.func()  # 類內部可以訪問
obj.__add()  # 派生類中不能訪問
私有方法

總結:

對於這些私有成員來說,他們只能在類的內部使用,不能再類的外部以及派生類中使用.

ps:非要訪問私有成員的話,可以通過 對象._類__屬性名,但是絕對不允許!!!

為什么可以通過._類__私有成員名訪問呢?因為類在創建時,如果遇到了私有成員(包括私有靜態字段,私有普通字段,私有方法)它會將其保存在內存時自動在前面加上_類名.

1.3面向對象的成員

1.3.1 字段

字段包括:普通字段和靜態字段,他們在定義和使用中有所區別,而最本質的區別是內存中保存的位置不同,

  • 普通字段屬於對象
  • 靜態字段屬於
class Province:

    # 靜態字段
    country = '中國'

    def __init__(self, name):

        # 普通字段
        self.name = name


# 直接訪問普通字段
obj = Province('河北省')
print obj.name

# 直接訪問靜態字段
Province.country
示例

上述代碼可以看出【普通字段需要通過對象來訪問】【靜態字段通過類訪問】,在使用上可以看出普通字段和靜態字段的歸屬是不同的。其在內容的存儲方式類似如下圖:

由上圖可是:

  • 靜態字段在內存中只保存一份
  • 普通字段在每個對象中都要保存一份

應用場景: 通過類創建對象時,如果每個對象都具有相同的字段,那么就使用靜態字段

1.3.2方法

方法包括:普通方法、靜態方法和類方法,三種方法在內存中都歸屬於類,區別在於調用方式不同。

實例方法

    定義:第一個參數必須是實例對象,該參數名一般約定為“self”,通過它來傳遞實例的屬性和方法(也可以傳類的屬性和方法);

    調用:只能由實例對象調用。

類方法

    定義:使用裝飾器@classmethod。第一個參數必須是當前類對象,該參數名一般約定為“cls”,通過它來傳遞類的屬性和方法(不能傳實例的屬性和方法);

    調用:實例對象和類對象都可以調用。

靜態方法

    定義:使用裝飾器@staticmethod。參數隨意,沒有“self”和“cls”參數,但是方法體中不能使用類或實例的任何屬性和方法;

    調用:實例對象和類對象都可以調用。

實例方法

簡而言之,實例方法就是類的實例能夠使用的方法。這里不做過多解釋。

類方法

使用裝飾器@classmethod。

原則上,類方法是將類本身作為對象進行操作的方法。假設有個方法,且這個方法在邏輯上采用類本身作為對象來調用更合理,那么這個方法就可以定義為類方法。另外,如果需要繼承,也可以定義為類方法。

如下場景:

假設我有一個學生類和一個班級類,想要實現的功能為:
    執行班級人數增加的操作、獲得班級的總人數;
    學生類繼承自班級類,每實例化一個學生,班級人數都能增加;
    最后,我想定義一些學生,獲得班級中的總人數。

思考:這個問題用類方法做比較合適,為什么?因為我實例化的是學生,但是如果我從學生這一個實例中獲得班級總人數,在邏輯上顯然是不合理的。同時,如果想要獲得班級總人數,如果生成一個班級的實例也是沒有必要的。

復制代碼
class ClassTest(object):
    __num = 0

    @classmethod
    def addNum(cls):
        cls.__num += 1

    @classmethod
    def getNum(cls):
        return cls.__num

    # 這里我用到魔術函數__new__,主要是為了在創建實例的時候調用人數累加的函數。
    def __new__(self):
        ClassTest.addNum()
        return super(ClassTest, self).__new__(self)


class Student(ClassTest):
    def __init__(self):
        self.name = ''

a = Student()
b = Student()
print(ClassTest.getNum())
復制代碼

靜態方法

使用裝飾器@staticmethod。

靜態方法是類中的函數,不需要實例。靜態方法主要是用來存放邏輯性的代碼,邏輯上屬於類,但是和類本身沒有關系,也就是說在靜態方法中,不會涉及到類中的屬性和方法的操作。可以理解為,靜態方法是個獨立的、單純的函數,它僅僅托管於某個類的名稱空間中,便於使用和維護。

譬如,我想定義一個關於時間操作的類,其中有一個獲取當前時間的函數。

復制代碼
import time

class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())


print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)
復制代碼

如上,使用了靜態方法(函數),然而方法體中並沒使用(也不能使用)類或實例的屬性(或方法)。若要獲得當前時間的字符串時,並不一定需要實例化對象,此時對於靜態方法而言,所在類更像是一種名稱空間。

其實,我們也可以在類外面寫一個同樣的函數來做這些事,但是這樣做就打亂了邏輯關系,也會導致以后代碼維護困難。

1.3.2屬性

什么是特性property

property是一種特殊的屬性,訪問它時會執行一段功能(函數)然后返回值

例一:BMI指數(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

成人的BMI數值:
過輕:低於18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高於32
  體質指數(BMI)=體重(kg)÷身高^2(m)
  EX:70kg÷(1.75×1.75)=22.86
class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People('egon',75,1.85)
print(p1.bmi)
例一代碼

為什么要用property

將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式遵循了統一訪問的原則

由於新式類中具有三種訪問方式,我們可以根據他們幾個屬性的訪問特點,分別將三個方法定義為對同一個屬性:獲取、修改、刪除

class Foo:
    @property
    def AAA(self):
        print('get的時候運行我啊')

    @AAA.setter
    def AAA(self,value):
        print('set的時候運行我啊')

    @AAA.deleter
    def AAA(self):
        print('delete的時候運行我啊')

#只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

或者:
class Foo:
    def get_AAA(self):
        print('get的時候運行我啊')

    def set_AAA(self,value):
        print('set的時候運行我啊')

    def delete_AAA(self):
        print('delete的時候運行我啊')
    AAA=property(get_AAA,set_AAA,delete_AAA) #內置property三個參數與get,set,delete一一對應

f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
View Code
class Goods(object):

    def __init__(self):
        # 原價
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 實際價格 = 原價 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deltter
    def price(self, value):
        del self.original_price

obj = Goods()
obj.price         # 獲取商品價格
obj.price = 200   # 修改商品原價
del obj.price     # 刪除商品原價
商品實例

 二,面相對象的特殊成員及相關內置函數

2.1 isinstance與issubclass

isinstance(obj,cls)檢查是否obj是否是類 cls 的對象

class A: pass

class B(A): pass

abj = B()
print(isinstance(abj,B))  #True
print(isinstance(abj,A))  #True
示例

issubclass(sub, super)檢查sub類是否是 super 類的派生類

class A: pass

class B(A): pass

abj = B()

print(issubclass(B,A)) #True
示例

2.2 反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)。這一概念的提出很快引發了計算機科學領域關於應用反射性的研究。它首先被程序語言的設計領域所采用,並在Lisp和面向對象方面取得了成績。

python面向對象中的反射:通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象(都可以使用反射)

四個可以實現自省的函數

下列方法適用於類和對象(一切皆對象,類本身也是一個對象)

class Foo:
    f = '類的靜態變量'
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def say_hi(self):
        print('hi,%s'%self.name)

obj=Foo('egon',73)

#檢測是否含有某屬性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))

#獲取屬性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()

print(getattr(obj,'aaaaaaaa','不存在啊')) #報錯

#設置屬性
setattr(obj,'sb',True)
setattr(obj,'show_name',lambda self:self.name+'sb')
print(obj.__dict__)
print(obj.show_name(obj))

#刪除屬性
delattr(obj,'age')
delattr(obj,'show_name')
delattr(obj,'show_name111')#不存在,則報錯

print(obj.__dict__)
對實例化對象的示例
復制代碼
class Foo(object):
 
    staticField = "old boy"
 
    def __init__(self):
        self.name = 'wupeiqi'
 
    def func(self):
        return 'func'
 
    @staticmethod
    def bar():
        return 'bar'
 
print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')
對類的示例
import sys


def s1():
    print 's1'


def s2():
    print 's2'


this_module = sys.modules[__name__]

hasattr(this_module, 's1')
getattr(this_module, 's2')
對當前模塊的示例
#一個模塊中的代碼
def test():
    print('from the test')
"""
程序目錄:
    module_test.py
    index.py
 
當前文件:
    index.py
"""
# 另一個模塊中的代碼
import module_test as obj

#obj.test()

print(hasattr(obj,'test'))

getattr(obj,'test')()
其他模塊的示例

2.3 __len__

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __len__(self):
        return len(self.__dict__)
a = A()
print(len(a))
示例

2.4__hash__

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __hash__(self):
        return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))
View Code

 2.5 __str__

如果一個類中定義了__str__方法,那么在打印 對象 時,默認輸出該方法的返回值。

class A:
    def __init__(self):
        pass
    def __str__(self):
        return '太白'
a = A()
print(a)
print('%s' % a)
View Code

 2.6 __repr__

如果一個類中定義了__repr__方法,那么在repr(對象) 時,默認輸出該方法的返回值。

class A:
    def __init__(self):
        pass
    def __repr__(self):
        return '太白'
a = A()
print(repr(a))
print('%r'%a)
View Code

2.7__call__

對象后面加括號,觸發執行。

注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 執行 __init__
obj()       # 執行 __call__
View Code

2.8__eq__

class A:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __eq__(self,obj):
        if  self.a == obj.a and self.b == obj.b:
            return True
a = A()
b = A()
print(a == b)
View Code

2.9__del__

析構方法,當對象在內存中被釋放時,自動觸發執行。

注:此方法一般無須定義,因為Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。

 2.10__new__

class A:
    def __init__(self):
        self.x = 1
        print('in init function')
    def __new__(cls, *args, **kwargs):
        print('in new function')
        return object.__new__(A, *args, **kwargs)

a = A()
print(a.x)
View Code
class A:
    __instance = None
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            obj = object.__new__(cls)
            cls.__instance = obj
        return cls.__instance
單例模式
單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱為單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。
【采用單例模式動機、原因】
對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統;一個系統只能有一個計時工具或ID(序號)生成器。如在Windows中就只能打開一個任務管理器。如果不使用機制對窗口對象進行唯一化,將彈出多個窗口,如果這些窗口顯示的內容完全一致,則是重復對象,浪費內存資源;如果這些窗口顯示的內容不一致,則意味着在某一瞬間系統有多個狀態,與實際不符,也會給用戶帶來誤解,不知道哪一個才是真實的狀態。因此有時確保系統中某個對象的唯一性即一個類只能有一個實例非常重要。
如何保證一個類只有一個實例並且這個實例易於被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。
【單例模式優缺點】
【優點】
一、實例控制
單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例。
二、靈活性
因為類控制了實例化過程,所以類可以靈活更改實例化過程。
【缺點】
一、開銷
雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
二、可能的開發混淆
使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用new關鍵字實例化對象。因為可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
三、對象生存期
不能解決刪除單個對象的問題。在提供內存管理的語言中(例如基於.NET Framework的語言),只有單例類能夠導致實例被取消分配,因為它包含對該實例的私有引用。在某些語言中(如 C++),其他類可以刪除對象實例,但這樣會導致單例類中出現懸浮引用
單例模式具體分析

2.11 item系列

class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]時,我執行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key時,我執行')
        self.__dict__.pop(item)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)
View Code

 


免責聲明!

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



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