Python對象 繼承 多態 獲取對象信息 類的屬性

#定義類
#Student這種數據類型被視爲一個對象,這個對象擁有name和score這兩個屬性(Property)。
#如果要打印一個學生的成績,首先必須創建出這個學生對應的對象
#然後,給對象發一個print_score消息,讓對象自己把自己的數據打印出來
#給對象發消息實際上就是調用對象對應的關聯函數,我們稱之爲對象的方法(Method)

class Student(object):
#(object),表示該類是從哪個類繼承下來的,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
    def __init__(self,name,score):
        #self必不可少,必須位於其他形參的前面。
        #當py調用這個__init__()方法來創建實例時,將自動傳入self
        #其本身是一個指向實例本身的引用,讓實例能夠訪問類中的屬性和方法。
        self.name=name
        self.score=score
        #變量被關聯到當前創建的實例
    def print_score(self):
        print('%s:%s' % (self.name,self.score))
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'
#在類中定義的函數只有一點不同,就是第一個參數永遠是實例變量self,並且,調用時,不用傳遞該參數。
#除此之外,類的方法和普通函數沒有什麼區別,所以,你仍然可以用默認參數、可變參數、關鍵字參數和命名關鍵字參數。

bart=Student('Bart Simpson',59)  #有了__init__方法,在創建實例的時候,必須傳入與__init__方法匹配的參數
lisa=Student('Lisa Simpson', 87)
bart.print_score()  #Bart Simpson:59  
lisa.print_score()  #Lisa Simpson:87
bart.score=100
bart.print_score()  #Bart Simpson:100
print(bart.name)    #Bart Simpson

#和靜態語言不同,Python允許對實例變量綁定任何數據,
#也就是說,對於兩個實例變量,雖然它們都是同一個類的不同實例,但擁有的變量名稱都可能不同
bart.age=8
print(bart.age)  #8 此處與C++中不同,允許實例綁定任何數據,而不要求一定是先定義的屬性,但是該屬性不會影響到其他對象的屬性

#print(lisa.age)  #報錯誤:AttributeError: 'Student' object has no attribute 'age'


#如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,
#在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問
class Student2(object):

    def __init__(self, name, score):
        self.__name = name   #私有變量(必須是兩個下劃線)
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

bart2=Student2('Bart Simpson',59)  
lisa2=Student2('Lisa Simpson', 87)
bart2.print_score()  #Bart Simpson:59  
lisa2.print_score()  #Lisa Simpson:87
#print(bart2.__name)  #錯誤 AttributeError: 'Student2' object has no attribute '__name'
#但是仍然可以爲對象重新綁定一個屬性
bart2.__name='Hello'
print(bart2.__name)  #Hello 此時應當是新建了一個屬性,因此要避免爲對象的屬性隨意賦值


#如果外部代碼要獲取name和score可以給Student類增加get_name和get_score這樣的方法
#如果又要允許外部代碼修改score,可以再給Student類增加set_score方法,在方法中,可以對參數做檢查,避免傳入無效的參數
class Student3(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

    def get_name(self):
        return self.__name
    def get_score(self):
        return self.__score
    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')
bart3=Student3('Bart Simpson',59)  
bart3.__name='Hello'
print(bart3.__name)  #Hello
#這個__name變量和class內部的__name變量不是一個變量!
#內部的__name變量已經被Python解釋器自動改成了_Student__name,而外部代碼給bart新增了一個__name變量
print(bart3.get_name())  #Bart Simpson  內部的屬性__name


#繼承
class Animal(object):
    def run(self):
        print('Animal is running……')
class Dog(Animal):
    def run(self):   #子類的run()覆蓋了父類的run()
        print('Dog is running……')
class Cat(Animal):
    def run(self):
        print('Cat is running……')
Animaler=Animal()
Animaler.run()  #Animal is running……
Doger=Dog()
Doger.run()     #Dog is running……
Cater=Cat()
Cater.run()     #Cat is running……

#多態  由於Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法
#對於一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,
#而具體調用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運行時該對象的確切類型決定,
#這就是多態真正的威力:調用方只管調用,不管細節,
#而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
#對擴展開放:允許新增Animal子類;
#對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
def run_twice(x):
    x.run()
    x.run()
run_twice(Animaler)   #Animal is running……    Animal is running……
run_twice(Doger)      #Dog is running……       Dog is running……
run_twice(Cater)      #Cat is running……       Cat is running……

#靜態語言 vs 動態語言
#對於靜態語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。
#對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
class Timer(object):
    def run(self):
        print('Start...')

time=Timer()  
time.run()       #Start...
run_twice(time)  #Start...    Start...即不要求一定爲run_twice傳入Animal類型的對象

#判斷對象的類型
#(1)type()
print(type(123))     #<class 'int'>
print(type('123'))   #<class 'str'>
print(type(None))    #<class 'NoneType'>
print(type(abs))     #<class 'builtin_function_or_method'>
print(type(Animal))  #<class 'type'>
print(type(123)==type(345))    #True
print(type(123)==int )         #True
print(type('123')==str )       #True
print(type('123')==type(123))  #False
#如果要判斷一個對象是否是函數怎麼辦?可以使用types模塊中定義的常量:
import types
print(type(run_twice)==types.FunctionType)   #True
print(type(abs)==types.BuiltinFunctionType)  #True
print(type(lambda x:x*x) ==types.LambdaType) #True
print(type((x for x in range(10)))==types.GeneratorType)  #True

#(2)使用isinstance()
print(isinstance(Animaler,Animal))   #True
print(isinstance(Doger,Dog))         #True
print(isinstance(Cater,Dog))         #False
print(isinstance(Doger,Animal))      #True
print(isinstance(Animaler,Dog))      #False
print(isinstance('a', str))          #True
print(isinstance(b'a', bytes))       #True
#還可以判斷一個變量是否是某些類型中的一種
print(isinstance([1, 2, 3], (list, tuple)))   #True
print(isinstance((1, 2, 3), (list, tuple)))   #True

#(3)使用dir可以獲得一個對象的所有屬性和方法,它返回一個包含字符串的list
print(dir('ABC'))
#['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getat
#tribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__'
#, '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setat
#tr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', '
#expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'isl
#ower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'pa
#rtition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith',
#'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']


#屬性配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態:
class MyObject(object):
    def __init__(self):
        self.x = 9
    def power(self):
        return self.x * self.x
        return self.x * self.x
obj = MyObject()
print(hasattr(obj,'x'))  #True
print(obj.x)             # 9
setattr(obj, 'y', 19)    # 設置一個屬性'y'
print(hasattr(obj, 'y')) # 有屬性'y'嗎? True
print(getattr(obj, 'y')) # 19
print(getattr(obj, 'z', 404))#可以傳入一個default參數,如果屬性不存在,就返回默認值   404
fn = getattr(obj, 'power') # 獲取屬性'power'並賦值到變量fn
print(fn())   #81


#實例屬性和類屬性
#給實例綁定屬性的方法是通過實例變量,或者通過self變量:
class Student4(object):
    def __init__(self, name):
        self.name = name
s = Student4('Bob')
s.score = 90

#如果Student類本身需要綁定一個屬性,可以直接在class中定義屬性,這種屬性是類屬性,歸Student類所有,
#通過實例可以訪問該屬性,當通過實例爲該屬性進行賦值時,屬於實例的屬性的值變化,但是屬於類的屬性的值不變,
#當通過類直接爲該屬性賦值,就會改變其值
class Student4(object):
    name = 'Student4'  #相當於C++中的static靜態成員變量
#定義了一個類屬性後,這個屬性雖然歸類所有,但類的所有實例都可以訪問到。
s = Student4() # 創建實例s
print(s.name) #Student4  打印name屬性,因爲實例並沒有name屬性,所以會繼續查找class的name屬性 
print(Student4.name) # Student4 打印類的name屬性
s.name = 'Michael' # 給實例綁定name屬性
print(s.name) #Michael 由於實例屬性優先級比類屬性高,因此,它會屏蔽掉類的name屬性
print(Student4.name) #Student4 但是類屬性並未消失,用Student.name仍然可以訪問
del s.name # 如果刪除實例的name屬性
print(s.name) #Student4 再次調用s.name,由於實例的name屬性沒有找到,類的name屬性就顯示出來了
Student4.name='Bob'
print(Student4.name)  #Bob  直接爲類的變量賦值,就會改變
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章