【Python】解析Python中類的使用

目錄結構:python

contents structure [-]

1.類的基本使用

下面是類使用的一個簡單案例,編程

class person:
    "person 類的描述信息"
    def __init__(self,name="",age=0):
        self.name = name
        self.age = age

    def setName(self,name):
    '''設置名稱'''
        self.name = name

    def getName(self):
    '''獲取名稱'''
        return self.name

    def setAge(self,age):
        self.age = age

    def getAge(self):
        return self.age

    def __del__(self):
        print("person (name:"+self.name+",age:"+str(self.age)+") is deleting")

p = person("jame",12);
#Output:jame
print(p.getName())
#效果和下面的調用方式是同樣的
#person.getName(p)

#Output:12
print(p.getAge())
#效果和下面的調用方式是同樣的
#person.getAge(self)

#Output: person 類的描述信息
print(p.__doc__)

#輸出person類的幫助信息
print(help(person))

del p

輸出:編程語言

jame
12
person (name:jame,age:12) is deleting
person 類的描述信息
Help on class person in module __main__:

class person(builtins.object)
 |  person 類的描述信息
 |  
 |  Methods defined here:
 |  
...


__init__函數是構造函數,在構造對象的時候會自動調用該函數。
__del__函數是析構函數,在使用del刪除目標對象時會自動調用該方法。

Python中,類不能夠定義多個構造方法,只能定義一個構造方法。全部成員實例函數的第一個參數都必須是self參數(也能夠有非self參數的成員函數,下面會講解)。python會自動建立成員屬性,無需提早定義,例如self.name=name。函數

 

類屬性

類屬性的定義無需使用self參數,能夠直接定義到類中。類屬性能夠直接經過類名調用。ui

class Person:
    type = "哺乳類"

    def __init__(self,name,age):
        self.name = name
        self.age = age

p = Person("luyis",13)
#經過類名調用類屬性
print(Person.type)
#經過對象調用類屬性
print(p.type)

 

靜態方法

靜態方法是類中的函數,不須要實例。靜態方法主要是用來存放邏輯性的代碼,主要是一些邏輯屬於類,可是和類自己沒有交互,即在靜態方法中,不會涉及到類中的方法和屬性的操做。用 @staticmethod 裝飾的不帶 self 參數的方法,類的靜態方法能夠沒有參數,能夠直接使用類名調用。spa

import time
class TimeTest:
    def __init__(self):
        pass

    #靜態方法
    @staticmethod
    def show_time():
        print(time.strftime("%H:%M:%S", time.localtime()))

TimeTest.show_time()

 

 

類方法

類方法是將類自己做爲對象進行操做的方法。默認有個 cls 參數,能夠被類和對象調用,須要加上 @classmethod 裝飾器。調試

class ClassTest:
    num = 0
    def __init__(self,num):
        ClassTest.num = num

    #類方法
    @classmethod
    def showNum(cls):
        print(cls.num)


c = ClassTest(20)
#經過類直接調用
ClassTest.showNum()
#經過實例對象調用
c.showNum()

 

 

私有成員

Python中的私有成員,不能在類外面直接訪問。私有成員只須要在成員前面加上兩條下劃線(__)就代表該成員是私有的了。code

class Person:
    #__type是私有成員
    __type = "哺乳類"

    def __init__(self,name="",age=0,country="中國"):
    #__name,__age是私有成員
        self.__name = name
        self.__age = age
        self.__setcountry(country)

    def get_name(self):
        return self.__name

    def get_age(self):
        return self.__age

    def set_name(self,name):
        self.__name = name

    def set_age(self,age):
        self.__age = age

    #__setcountry是私有函數
    def __setcountry(self,cty):
        self.country = cty
    
    @classmethod
    def get_type(cls):
        return cls.__type

    @classmethod
    def set_type(cls,type):
        cls.__type = type

p = Person("jame",21)
print(p.get_name())
print(p.get_age())
print("--------------------")

p.set_name("charlse")
p.set_age(31)
print(p.get_name())
print(p.get_age())
print("-------------------")

print(Person.get_type())
print("------------------")

Person.set_type("靈長類")
print(Person.get_type())

輸出:對象

jame
21
--------------------
charlse
31
-------------------
哺乳類
------------------
靈長類


定義私有成員的格式:
__xxx:私有成員,只有類對象本身能訪問,子類對象不能直接訪問到這個成員,

注意:雖然不能在類外直接訪問到類的私有成員,但在對象外部能夠經過「 對象名._類名__xxx 」這樣的特殊方式來訪問類的私有成員。應此,在Python中不存在嚴格意義上的私有成員。blog

class Person:
    #__type是私有成員
    __type = "哺乳類"

    def __init__(self,name="",age=0):
    #__name,__age是私有成員
        self.__name = name
        self.__age = age


p = Person("Emma",32);
#直接訪問私有成員的值
print(p._Person__type)
print(p._Person__name)
print(p._Person__age)

#直接修改私有成員的值
p._Person__age = 33
p._Person__name = "Angela"
p._Person__type = "靈長類"

print("-------------------")
print(p._Person__type)
print(p._Person__name)
print(p._Person__age)

輸出:

哺乳類
Emma
32
-------------------
靈長類
Angela
33

 

 

2.專有方法

Python除了自定義私有變量和方法外,還能夠定義專有方法。專有方法是在特殊狀況下或使用特殊語法時由python調用的,而不是像普通方法同樣在代碼中直接調用。看到形如__XXX__的變量或函數名時就須要注意下,這在python中是有特殊用途的,下面是Python中的部分專用方法:
__init__ : 構造函數,在生成對象時調用
__del__ : 析構函數,釋放對象時使用
__str__: 打印,轉換,適合於展現給用戶看的
__repr__ : 打印,轉換,適合開發者調試
__setitem__ : 按照索引賦值
__getitem__: 按照索引獲取值
__len__: 得到長度
__call__: 函數調用
__add__: 加運算
__sub__: 減運算
__mul__: 乘運算
__div__: 除運算
__mod__: 求餘運算
__pow__: 乘方
__doc__: 說明文檔信息

上面筆者已經講解過__init__和__del__函數的使用了,應此筆者再也不這裏重複綴述了。筆者接下來說解其它的專有方法的用法:

 

__str__,__repr__

都是用於將對象轉化爲字符串的內置方法。
可是 repr() 函數將對象轉化爲供解釋器讀取的形式,__str__只是覆蓋了__repr__以獲得更友好的用戶顯示。
例如:

>>> y = 'a string'
>>> repr(y)
"'a string'"
>>> str(y)
'a string'


repr函數的返回字符串能夠再次傳遞給eval函數。可是str函數的返回值傳遞給eval顯然是不合適。

>>> y == eval(repr(y))
True
>>> y == eval(str(y))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    a string
           ^
SyntaxError: unexpected EOF while parsing

 



repr返回的字符串應該是編譯器可以再次解析的,str返回的字符串應該能夠是易閱讀的

在知道了repr和str的區別後,咱們就知道該如何自定義__repr__和__str__函數了。

class person:
    def __init__(self,name="",age=0):
        self.__name = name
        self.__age = age

    def __str__(self):
        return "name:%s,age:%d"%(self.__name,self.__age)

    def __repr__(self):
        return "person(name='%s',age=%d)"%(self.__name,self.__age)

p = person("jame",12)
#Output: name:jame,age=12
print(str(p))#same as print(p)

#Output: person(name='jame',age=12)
print(repr(p))

p2 = eval(repr(p))   
#Output: name:jame,age=12
print(p2)

 

 

__setitem__,__getitem__

用於經過下標來設置和獲取值,能夠直接使用[]符來操做。

class DicDecorate:

    def __init__(self,dic):
        self.__dic = dic

    def __getitem__(self,key):
        return self.__dic[key]

    def __setitem__(self,key,val):
        self.__dic[key] = val


dicdec = DicDecorate({})

dicdec[0] = "hello"
dicdec[1] = "word"

print(dicdec[0])
print(dicdec[1])

__len__():

當調用len函數時會調用該方法。

lass DicDecorate:
    def __init__(self,dic):
        self.__dic = dic

    def __len__(self):
        return len(self.__dic);

dicdec = DicDecorate({})
print(len(dicdec))

 

__call__()

關於 __call__ 方法,不得不先提到一個概念,就是可調用對象(callable),咱們平時自定義的函數、內置函數和類都屬於可調用對象,但凡是能夠把一對括號()應用到某個對象身上均可稱之爲可調用對象,判斷對象是否爲可調用對象能夠用函數 callable。

若是在類中實現了 __call__ 方法,那麼實例對象也將成爲一個可調用對象。

class Entity:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __call__(self, x, y):
        self.x, self.y = x, y

    def __str__(self):
        return "x=%s,y=%s"%(self.x,self.y)

e = Entity(2, 3) # 建立實例
if(callable(e)):
    e(4, 5) #實例能夠象函數那樣執行,並傳入x y值,修改對象的x y
#Output:x=4,y=5
print(e)

 


__add__: 加運算 __sub__: 減運算 __mul__: 乘運算 __div__: 除運算 __mod__: 求餘運算 __pow__: 冪運算

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)

   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

 

 

3. 繼承

3.1 單重繼承

Python繼承的語法格式:

class ClassName1(ClassName2):
    statement

其中
ClassName1:派生類
ClassName2:基類

class Shape:
    def __init__(self,type,area):
        self.type = type
        self.area = area
    def describe(self):
        return "Share type:%s,area=%f"%(self.type,self.area)
class Square(Shape):
    def __init__(self,area):
        super().__init__("square",area);
square = Square(12.1)
print(square.describe())

和其它編程語言同樣,能夠重寫父類中的方法,能夠繼承父類中開放的屬性和方法(不能繼承父類中的私有屬性和方法)。

 

3.2 多重繼承

除了單重繼承,Python還支持多重繼承

class ClassName1(ClassName2,ClassName3,ClassName4...):
    statement

Python多重繼承中順序繼承是一個很是重要的概念,若是繼承的多個父類中有相同的方法名,但在派生類中使用時未指定父類名,則Python解釋器將從左至右搜索,即調用先繼承的類中的同名方法。

class A:
    def test(self):
        print("I am in Class A");
class B:
    def test(self):
        print("I am in Class B");

class C(A,B):
    def __call__(self):
    #調用A類中的test方法
        self.test()
        #能夠經過類名顯示指定調用B類中的test方法
        B.test(self)

class D(B,A):
    def __call__(self):
    #調用B類的test方法
        self.test()
    #經過類名顯示指定要調用A類中的test方法
    A.test(self)

c = C()
c()
print("---------------");
d = D()
d()

輸出:

I am in Class A
I am in Class B
-----------------
I am in Class B
I am in Class A

經過上面的結果能夠看出,子類在父類中查找方法的順序是從左到右的。

 

3.3 磚石繼承

磚石繼承問題是Python多重繼承中的典型問題,下面先經過一張圖片看看什麼是磚石繼承。


經過這張圖片看出,D繼承了B和C,B和C又派生於A,在調用D類的test()方法時,會再去調用B和C的test()方法,B和C又會去調用A的test()方法,因此A類的test()方法在理論應該會被調用兩次,例如:

class A:
    def test(self):
        print("I am in Class A");
class B(A):
    def test(self):
        A.test(self);
        print("I am in Class B")
class C(A):
    def test(self):
        A.test(self);
        print("I am in Class C");
        
class D(B,C):
    def test(self):
        B.test(self)
        C.test(self)
        print("I am in Class D")

d = D()
d.test()

輸出:

I am in Class A
I am in Class B
I am in Class A
I am in Class C
I am in Class D


從上面的輸出結果能夠看出,A類的test()被調用了兩次。在有些狀況這種邏輯將會形成極大的Bug,好比一筆銀行轉帳記錄轉了兩次。要避免這種狀況,可使用super()方法,當使用super()調用父類的test方法時,會轉而去調用下一個重寫了該super類的test方法,若沒有的話纔會去調用父類的test方法。
案例:

class A:
    def test(self):
        print("I am in Class A")
class B(A):
    def test(self):
        super().test() #不能替換爲A.test(self)
        print("I am in Class B")
class C(A):
    def test(self):
        super().test()
        print("I am in Class C")
        
class D(B,C):
    def test(self):
        super().test() #能夠替換爲B.test(self)
        print("I am in Class D")

d = D()
d.test()

輸出:

I am in Class A
I am in Class C
I am in Class B
I am in Class D


經過上面的結果咱們能夠看出,super.test()和父類.test(self)是不同的,在B類中使用super()調用父類的test()時,會去尋找B實例後的下一個類是否重寫A類的test()方法,因爲類D繼承了類B和類C,因此類C在類B後,並且類C又重寫了test()方法,應此會直接調用類C的test()方法。在類C的test()方法中,又使用了super().test()調用父類的test(),因此又會去尋找C實例後的下一個類有重寫A類的test方法,由於類D只繼承了B和C,而且類C是最後一個,因此C後沒有實例直接重寫A類的test()方法,所以直接去調用A類的test()方法。最終完整的方法壓棧順序是D->B->C->A。

下面的邏輯和上面案例中使用super()的邏輯是等同的:

class A:
    def test(self):
        print("I am in Class A")
class B(A):
    def test(self):
    C.test(self)#調用C
class C(A):
    def test(self):
        A.test(self)#調用A
class D(B,C):
    def test(self):
        B.test(self)#調用B

上面的邏輯變成了如圖所示:

相關文章
相關標籤/搜索