[Python] 08 - Classes --> Objects

故事背景


1、階級關係

1. Programs are composed of modules.
2. Modules contain statements.
3. Statements contain expressions.
4. Expressions create and process objects.html

 

 

2、教學大綱 

  • <Think Python>

 

  • 菜鳥教程

Goto: http://www.runoob.com/python3/python3-class.htmlpython

 

  

3、考點

Ref: [Advanced Python] 11 - Implement a Classc++

[面向對象]express

1、類的定義api

一個類,構造方法,self參數,「私有」屬性&方法,數組

2、類的字典屬性app

去查詢:屬性&方法 getattr()、setattr() 以及 hasattr()。函數

去模擬:字典的用法 __getattr__、__setattr__ 以及 __delattr__。post

3、類的實例this

實例的屬性,類的屬性

類的函數化:__call__ 

 

[類的繼承]

1、單繼承

調用父類的構造函數:<父類名>.__init__(self, <參數>)

2、多繼承

把「父類名」改成「子類的父親」:super(<子類名>, <類的實例>).__init__(self, *args, **kwargs)

3、類方法覆蓋

多態的運用,甚至支持「鴨子類型'。

 

[類的」變量化「]

1、類的屬性

也就是類的字典列表所得結果。

2、裝飾器

@property, @xxx.setter,當作變量同樣去使用。  

3、重載運算符

類內部屬性示範。

 

 

 

 

面向對象


數據封裝(Encapsulation )、繼承(inheritance )和多態(polymorphism)是面向對象的三大特色。

1、類的定義

一個類

#!/usr/bin/python3
 
class MyClass:
    """一個簡單的類實例"""
i= 12345 def f(self): return 'hello world'
# 實例化類 x = MyClass() # 訪問類的屬性和方法 print("MyClass 類的屬性 i 爲:", x.i) print("MyClass 類的方法 f 輸出爲:", x.f())

 

構造方法

賦值的的過程,就自動完成了類內的變量定義。

#!/usr/bin/python3
 
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart
x
= Complex(3.0, -4.5) print(x.r, x.i) # 輸出結果:3.0 -4.5

 

self 參數

類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱

記錄了 「類的地址」,「類的屬性」 等。

class Test:
    def prt(self):
        print(self)        # address print(self.__class__)  # name
 
t = Test()
t.prt()

執行結果:

<__main__.Test instance at 0x100771878>
__main__.Test

注意:self 的名字並非規定死的,也可使用 this,可是最好仍是按照約定使用 self。

 

「私有」 屬性&方法

私有屬性

構造方法給私有屬性賦值。

加上雙下劃線定義屬性爲私有屬性。

#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age  = 0
#定義私有屬性,私有屬性在類外部沒法直接進行訪問 __weight = 0
#定義構造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 說: 我 %d 歲。" %(self.name, self.age)) # 實例化類 p = people('runoob',10,30) p.speak()

 

私有方法

加上雙下劃線定義屬性爲私有方法。

#!/usr/bin/python3
 
class Site:
    def __init__(self, name, url):
        self.name = name       # public
        self.__url = url       #private
 
    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)
 
    def __foo(self):          # 私有方法
        print('這是私有方法')
 
    def foo(self):            # 公共方法
        print('這是公共方法')
        self.__foo()
 
x = Site('菜鳥教程', 'www.runoob.com')
x.who()        # 正常輸出
x.foo()        # 正常輸出
x.__foo()      # 報錯

 

「私有化」 原理

雙下劃線開頭的實例變量是否是必定不能從外部訪問呢?其實也不是。不能直接訪問__name是由於Python解釋器對外把__name變量改爲了_Student__name,因此,仍然能夠經過_Student__name來訪問__name變量:

>>> bart._Student__name
'Bart Simpson'

可是強烈建議你不要這麼幹,由於不一樣版本的Python解釋器可能會把__name改爲不一樣的變量名。

總的來講就是,Python自己沒有任何機制阻止你幹壞事,一切全靠自覺。

 

最後注意下面的這種錯誤寫法:__name與內部解釋後的_stduent__name可不是一個變量了。

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 設置__name變量!
>>> bart.__name
'New Name'

 

 

2、類的字典屬性

查詢 屬性&方法

僅僅把屬性和方法列出來是不夠的,配合getattr()setattr()以及hasattr(),咱們能夠直接操做一個對象的狀態:

>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設置一個屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19

能夠傳入一個default參數,若是屬性不存在,就返回默認值:

>>> getattr(obj, 'z', 404) # 獲取屬性'z',若是不存在,返回默認值404
404

 

模擬 字典效果

若是咱們在Class沒有定義這3個方法,那麼系統會用Python自帶的內部函數;

若是咱們在類裏面自定義了這3個函數,那麼python會先調用咱們本身定義的這3個函數。

Ref: c到底幹了什麼?

這裏要當心「無限循環」!須要經過__dict__操做。

class Cat:

    class_level = '貴族'

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


    def run(self):
        print('%s歲的%s%s正在以%s的速度奔跑' % (self.age, self.type, self.name, self.speed))

    def __getattr__(self, item):
        print('你找的屬性不存在')

    def __setattr__(self, key, value):
        print('你在設置屬性')
        # self.key=value   #這種方法不行,會產生無限遞歸了,由於他自己self.key=value也會觸發__setattr__
        self.__dict__[key] = value #咱們在給對象屬性賦值的時候,內部原理就是操做對象的__dict__字典,因此咱們能夠直接操做對象的字典實現屬性賦值

    #刪除屬性的時候會觸發
    def __delattr__(self, item):
        print('你在刪除屬性')
        # del self.item   #無限遞歸了,和上面的__setattr__原理同樣
        self.__dict__.pop(item)

#實例化,會到__init__函數裏去給實例數據屬性賦值
xiaohua = Cat('小花','波斯貓','10m/s',10)

 

做用:動態調用 (unknown)

能夠把一個類的全部屬性和方法調用所有動態化處理了,不須要任何特殊手段。這種徹底動態調用的特性有什麼實際做用呢?

做用就是,能夠針對徹底動態的狀況做調用:

class Chain(object):

    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))  # 有點遞歸的意思

    def __str__(self):
        return self._path

    __repr__ = __str__    # 不返回地址,返回string; __repr__能夠把對象轉變爲字符串的形式

Output:

>>> Chain().status.user.timeline.list
/status/user/timeline/list
>>> Chain().status.user
/status/user

>>> Chain().status /status

 

 

3、類的實例

實例屬性 & 類屬性

動態語言的特色,二者不是一個東西。

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 建立實例s
>>> print(s.name) # 打印name屬性,由於實例並無name屬性,因此會繼續查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實例綁定name屬性
>>> print(s.name) # 因爲實例屬性優先級比類屬性高,所以,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 可是類屬性並未消失,用Student.name仍然能夠訪問
Student
>>> del s.name # 若是刪除實例的name屬性
>>> print(s.name) # 再次調用s.name,因爲實例的name屬性沒有找到,類的name屬性就顯示出來了
Student

 

實例對象「函數化」

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

>>> s = Student('Michael') >>> s() # self參數不要傳入 My name is Michael.

那麼,怎麼判斷一個變量是對象仍是函數呢?

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

 

 

 

類的繼承


1、單繼承

#!/usr/bin/python3
 
# 父類定義
class people:
    #定義基本屬性
    name     = ''
    age      = 0
    #定義私有屬性,私有屬性在類外部沒法直接進行訪問
    __weight = 0
#定義構造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w
def speak(self): print("%s 說: 我 %d 歲。" %(self.name,self.age))
#單繼承示例 class student(people): grade = ''
def __init__(self, n, a, w, g): #調用父類的構函 people.__init__(self,n,a,w)  # <---- 這個方法可能不是很好 self.grade = g
#覆寫父類的方法 def speak(self): print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade)) s = student('ken',10,60,3) s.speak()

 

 

2、多繼承

(1) 倒三角:調用歧義

(1) 如果父類中有相同的方法名,而在子類使用時未指定,python 從左至右 搜索 即方法在子類中未找到時,從左到右查找父類中是否包含方法。

(2) 子類不重寫 __init__,實例化子類時,會自動調用父類定義的 __init__。

#!/usr/bin/python3
 
#類定義
class people:
    #定義基本屬性
    name = ''
    age = 0
    #定義私有屬性,私有屬性在類外部沒法直接進行訪問
    __weight = 0
    #定義構造方法
    def __init__(self,n,a,w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s 說: 我 %d 歲。" %(self.name,self.age))
 
#單繼承示例 class student(people): grade = '' def __init__(self,n,a,w,g): #調用父類的構函 people.__init__(self,n,a,w) self.grade = g
#覆寫父類的方法 def speak(self): print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade)) #另外一個類,多重繼承以前的準備 class speaker(): topic = '' name = '' def __init__(self,n,t): self.name = n self.topic = t def speak(self): print("我叫 %s,我是一個演說家,我演講的主題是 %s"%(self.name,self.topic))
#多重繼承 class sample(speaker, student):    # <---- 多繼承 a ='' def __init__(self,n,a,w,g,t): student.__init__(self, n, a, w, g) speaker.__init__(self, n, t) test = sample("Tim",25,80,4,"Python") test.speak() #方法名同,默認調用的是在括號中排前面的父類的方法

 

(2) 菱形:路徑二義性

鑽石繼承時,c++中採用虛函數解決;python 怎麼辦?

參見高級部分:[Advanced Python] 11 - Implement a Class

class Father(object):
    def __init__(self, name, *args, **kwargs):
        self.name = name
        print("我是父類__init__")

class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = age class Son_2(Father): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") self.gender = gender super(Son_2, self).__init__(name, *args, **kwargs)
class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender): super(GrandSon, self).__init__(name, age, gender) def say_hello(self): print(self.name, self.age, self.gender) grand_son = GrandSon("老王", 24, "")

 

  

3、類方法覆蓋

方法重寫

實例方法 被覆蓋

經過 super(<子類名>, <子類對象>).<父類方法> 調用「被覆蓋的」 父方法。

#!/usr/bin/python3
 
class Parent:        # 定義父類
   def myMethod(self):
      print ('調用父類方法')
 
class Child(Parent): # 定義子類
   def myMethod(self):
      print ('調用子類方法')
 
c = Child()          # 子類實例
c.myMethod()         # 子類調用重寫方法
super(Child, c).myMethod() #用子類對象調用父類已被覆蓋的方法

輸出:

調用子類方法
調用父類方法

 

類構造方法 被覆蓋

這裏提供了兩種方式。

(1)
super(子類,self).__init__(參數1,參數2,....)

(2) 父類名稱.
__init__(self, 參數1,參數2,...)

 

多態表達

"父類" 做爲參數

animal做爲父類,支持子類做爲參數。

def run_twice(animal):  # 參數用的是」父類「,也能夠直接帶入"其子類"
    animal.run()
    animal.run()

 

"類父類" 做爲參數

動態語言支持「鴨子類型」,具有「必要的」方法就能湊活的用了。

對於Python這樣的動態語言來講,則不必定須要傳入Animal類型。咱們只須要保證傳入的對象有一個run()方法就能夠了:

class Timer(object):
    def run(self):
        print('Start...')

 

 

 

類的"變量化" 


1、類的屬性

類的專有方法

__init__ : 構造函數,在生成對象時調用
__del__  : 析構函數,釋放對象時使用
__repr__ : 打印,轉換 __setitem__ : 按照索引賦值 __getitem__ : 按照索引獲取值 __len__ : 得到長度 __cmp__ : 比較運算 __call__: 函數調用
__add__ : 加運算 __sub__ : 減運算 __mul__ : 乘運算 __div__ : 除運算 __mod__ : 求餘運算 __pow__ : 乘方

 

 

2、裝飾器

一些要點

    • 只有@property表示只讀。
    • 同時有@property和@x.setter表示可讀可寫。
    • 同時有@property和@x.setter和@x.deleter表示可讀可寫可刪除。

過去的策略

分別定義set, get函數操做。

裝飾器策略

將函數當作屬性來使用。

 

封裝性

對外的api是 score 和 name。對內的變量實則 _score 和 _name。

class student(object):  #新式類
    
    def __init__(self,id):  
        self.__id=id  
        
    @property #只讀  
    def score(self):  
        return self._score 
    
    @score.setter #只寫  
    def score(self, value):          
        if not isinstance(value,int):  
            raise ValueError('score must be an integer!')    
        if value<0 or value>100:  
            raise ValueError('score must between 0 and 100')   
        self._score=value  
               
    @property #只讀
    def get_id(self):  
        return self.__id  
    
  
s
=student('123456') s.score=100 # print(s.score) # print(s.__dict__) print (s.get_id) # 只讀 #s.get_id=456 #只能讀,不可寫: AttributeError: can't set attribute
================================================================
class A(object): # 新式類(繼承自object類) def __init__(self): self.__name=None def getName(self): return self.__name def setName(self,value): self.__name=value def delName(self): del self.__name name=property(getName, setName, delName) a=A() print(a.name) # a.name='python' # print(a.name) # del a.name # 刪除 #print a.name #a.name已經被刪除 AttributeError: 'A' object has no attribute '_A__name'

 

再一個例子:展現deleter的使用。

class test1:  #經典類:沒有繼承object    
    def __init__(self):    
        self.__private='alex 1' #私有屬性以2個下劃線開頭 
        
    #讀私有屬性    
    @property    
    def private(self):    
        return self.__private 
    
    #嘗試去寫私有屬性(對於經典類而言,「寫」是作不到的)  
    @private.setter    
    def private(self,value):    
        self.__private=value
        
    #嘗試去刪除私有屬性(對於經典類而言,「刪除」也是作不到的)  
    @private.deleter
def private(self): del self.__private ==================================================== class test2(object):# 新式類:繼承了object def __init__(self): self.__private='alex 2' #私有屬性以2個下劃線開頭 #讀私有屬性 @property def private(self): return self.__private #寫私有屬性 @private.setter def private(self,value): self.__private=value #刪除私有屬性 @private.deleter def private(self): del self.__private

t1
=test1() #print t1.__private # 外界不可直接訪問私有屬性 print (t1.private) # 讀私有屬性 print (t1.__dict__) t1.private='change 1' #對於經典類來講,該語句其實是改變了實例t1的實例變量private print (t1.__dict__) print (t1.private) # 輸出剛剛添加的實例變量private t1.private='change 2' print (t1.__dict__) del t1.private # 刪除剛剛添加的實例變量private print (t1.__dict__) #print (t1.private) #讀私有屬性,由於已經刪除,因此這裏會報錯
print ('-------------------------------------------------------') t2=test2() print (t2.__dict__) print (t2.private) # 繼承了object,添加@private.setter後,才能夠寫 t2.private='change 2' # 修改私有屬性 print (t2.__dict__) print (t2.private) del t2.private #刪除私有變量 #print t2.private #私有變量已經被刪除,執行「讀」操做會報錯:AttributeError: 'test2' object has no attribute '_test2__private' print (t2.__dict__)

 

 

3、運算符重載

類的加法

關鍵字:__add__

#!/usr/bin/python3
 
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)

執行結果:

Vector(7,8)

   

打印類(的字符串)

知識點:直接顯示變量調用的不是__str__(),而是__repr__(),二者的區別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發者看到的字符串,也就是說,__repr__()是爲調試服務的。

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

    def __str__(self):
        return '這我的的名字是%s,已經有%d歲了!'%(self.name,self.age)

a=people('孫悟空',999)
print(a)

 執行結果:

這我的的名字是孫悟空,已經有999歲了!

# 若是沒有重載函數的話輸出的就是一串看不懂的字符串:
# <__main__.people object at 0x00000272A730D278>

 

遍歷類(內部的next)

以前是經過函數實現;這裏則是「類的形式」,看上去比較好一些。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個計數器a,b

    def __iter__(self):
        return self # 實例自己就是迭代對象,故返回本身

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 計算下一個值
        if self.a > 100000: # 退出循環的條件
            raise StopIteration()
        return self.a # 返回下一個值

# Fib()返回的是本身自己;迭代的則是__next__提供的結果。 >>> for n in Fib():   ... print(n) ... 1 1 2 3 5 ... 46368 75025

 

取元素

數組模式

本質上仍是經過從新計算得出結果。

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a


>>> f = Fib()
>>> f[0] 1
>>> f[1] 1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

 

切片模式

class Fib(object):
    def __getitem__(self, n):
if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a
if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L >>> f = Fib() >>> f[0:5] [1, 1, 2, 3, 5] >>> f[:10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

與之對應的是__setitem__()方法,把對象視做list或dict來對集合賦值。最後,還有一個__delitem__()方法,用於刪除某個元素。

 

End.

相關文章
相關標籤/搜索