列表:數據封裝、函數:語句層面的封裝、對象:數據、函數的封裝java
現實世界是先有對象再有類,程序中先定義類,後再有對象python
面向對象設計 OOD(object oriented design):將一類具體事務的數據和動做整合到一塊兒linux
面向對象編程 OOP(object-oriented programming):用定義類+實例/對象的方式去實現面向對象的設計程序員
實例化:由類抽象對象的過程實例化算法
不是隻有用 class 定義類纔是面向對象,def 定義函數就是函數相關,跟面向對象沒有關係:編程
面向對象設計:c#
def school(name,addr,type): def init(name, addr, type): sch = { 'name': name, 'addr': addr, 'type': type, 'kao_shi': kao_shi, 'zhao_sheng': zhao_sheng, } return sch def kao_shi(school): print('%s 學校訂在考試' %school['name']) def zhao_sheng(school): print('%s %s 正在招生' %(school['type'],school['name'])) return init(name,addr,type) s1 = school('清華大學', '中關村北大街', '公立學校') print(s1) print(s1['name']) s1['kao_shi'](s1)
{'name': '清華大學', 'addr': '中關村北大街', 'type': '公立學校', 'kao_shi': <function school.<locals>.kao_shi at 0x0000000005382D90>, 'zhao_sheng': <function school.<locals>.zhao_sheng at 0x0000000005382BF8>} 清華大學 清華大學 學校訂在考試
面向對象編程:服務器
class School: def __init__(self, name, addr, types): self.name = name self.addr = addr self.types = types def kao_shi(self): print('%s 正在考試' % self.name) def zhao_sheng(self): print('%s %s 正在招生' %(self.types, self.name)) s1 = School('清華大學', '中關村北大街', '公立學校') s1.kao_shi() print(s1.__dict__)
清華大學 正在考試 {'name': '清華大學', 'addr': '中關村北大街', 'types': '公立學校'}
總結:網絡
Python 是一門面向對象的語言,可是它不強制要求必須使用面向對象去設計代碼,它不像 Java 同樣萬物皆是類。app
類聲明
class 類名: '類體字符串' 類體 def func(): '函數文檔字符串' 函數體
self
若是把對象比做一個房子,那麼 self 就是門牌號,有了 self 就能夠輕鬆找到本身的房子。
當一個對象的方法被調用時,對象會將 自身的引用做爲第一個參數傳給該方法,那麼 Python 就知道須要操做哪一個對象的方法了。
class Students: def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s 分數是:%s' % (self.name, self.score)) s1 = Students('rose', 98) # 至關於 s1 = Stuents.__init__(s1, 'rose', 98) s1.print_score() rose 分數是:98
構造方法
一般把 __init__()
方法稱爲構造方法,只有實例化一個對象,這個方法就會在對象被建立時自動調用。實例化傳入的參數自動傳入 __init__()
中,能夠經過重寫這個方法來自定義對象的初始化操做:
class Students: def __init__(self, name): self.name = name def print_score(self): print('名字 %s' % self.name) s1 = Students('rose') # 自動調用 __init__(),參數自動傳入 __init__() 中 s1.print_score()
實例屬性
對象只有數據屬性,沒有函數屬性,用的時候找類要:
# s1.__dict__ 查看對象屬性 class Students: color = 'black' def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s 分數是:%s' % (self.name, self.score)) s1 = Students('rose', 98) print(s1.__dict__) # 查看對象屬性字典,發現只有屬性屬性 print(id(Students.color)) # 對比發現類和對象的數據屬性共享的 print(id(s1.color)) print(Students.print_score) # 發現類的函數屬性與實例的函數屬性不同,內存地址不同 print(s1.print_score)
{'name': 'rose', 'score': 98} 82934784 82934784 <function Students.print_score at 0x0000000004F85620> <bound method Students.print_score of <__main__.Students object at 0x0000000004F98BE0>> # bound method :綁定方法(綁定了 類 Students 的 print_score 方法)
能夠看出對象只有數據屬性沒有函數屬性,類的函數屬性與實例的函數屬性不同,內存地址不同。
類屬性又稱爲靜態變量,或靜態數據,這些數據與它們所屬的類對象綁定,不依賴任何實例。若是是 java 程序員,這種類型數據至關於在一個變量聲明前加上 static 關鍵字
類名:大寫、函數屬性:動詞+名詞
數據屬性
class Chinese: country = 'China' # 數據屬性 def __init__(self, name): self.name = name def play_ball(self, ball): print('%s 正在打 %s' % (self.name)) p1 = Chinese('rose') # 查看類屬性 print(Chinese.country) # 修改 Chinese.country = 'CHINA' print(Chinese.country) # 增長 Chinese.gender = 'male' print(Chinese.__dict__) # 刪除 del Chinese.gender print(Chinese.__dict__)
China CHINA {'__module__': '__main__', 'country': 'CHINA', '__init__': <function Chinese.__init__ at 0x0000000004F85D08>, 'play_ball': <function Chinese.play_ball at 0x0000000004F85BF8>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None, 'gender': 'male'} {'__module__': '__main__', 'country': 'CHINA', '__init__': <function Chinese.__init__ at 0x0000000004F85D08>, 'play_ball': <function Chinese.play_ball at 0x0000000004F85BF8>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None}
函數屬性
# 增長一個 eat 方法 def eat(self, food): print('%s 正在吃 %s' % (self.name, food)) Chinese.test = eat # test 指向 eat p1.test('fish') print(Chinese.__dict__) # Chinese.play_ball = ball 修改屬性
rose 正在吃 fish {'__module__': '__main__', 'country': 'China', '__init__': <function Chinese.__init__ at 0x0000000004FA82F0>, 'play_ball': <function Chinese.play_ball at 0x0000000004FA8268>, '__dict__': <attribute '__dict__' of 'Chinese' objects>, '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, '__doc__': None, 'test': <function eat at 0x0000000004F850D0>}
class Students: color = 'black' def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s 分數是:%s' % (self.name, self.score)) s1 = Students('rose', 98) # 查看數據、函數屬性 print(s1.__dict__) print(s1.name) print(s1.print_score) # <bound method Students.print_score of <__main__.Students object at 0x0000000004FDD198>> 能夠看出實例的函數屬性綁定的是 類的函數屬性(<bound method Students.print_score) # 增長 s1.age = 18 print(s1.__dict__) # 修改 s1.age = 20 print(s1.__dict__) #不要修改底層的屬性字典,會形成 OOP 不穩定 # s1.__dict__['age']='30' # print(s1.__dict__) # print(s1.sex) # 刪除 del s1.age print(s1.__dict__)
{'name': 'rose', 'score': 98} rose <bound method Students.print_score of <__main__.Students object at 0x0000000004FDD198>> {'name': 'rose', 'score': 98, 'age': 18} {'name': 'rose', 'score': 98, 'age': 20} {'name': 'rose', 'score': 98}
屬性做用域
在 class 類中定義的數據屬性,屬於類的屬性。在 __init__()
中定義的是實例的數據屬性。無論是類的仍是實例的數據屬性,都只能使用點(.)方式訪問,其餘方式訪問皆爲普通變量。
country = '中國》》》' class Chinese: country = '中國' def __init__(self, name): self.name = name print('---->', country) # 這裏訪問的就是普通變量,沒有選擇類的屬性 country = '中國',而是選擇了普通變量 country = '中國》》》' def eat(self): pass p1 = Chinese('林雷') # p1.country = '》》》中國' # 其實這裏是增長實例的數據屬性 # print('類的數據屬性:', Chinese.country) 類的數據屬性: 中國 # print('實例的數據屬性:', p1.country) 實例的數據屬性: 》》》中國 print(Chinese.country) print(p1.country) ----> 中國》》》 中國 中國
總結
咱們知道類既有數據屬性也有函數屬性,而實例只有數據屬性。在使用實例調用類的函數屬性時,總要帶上函數後面的括號才能運行,否則調用的是函數的內存地址:
class Room: def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length def calc_area(self): print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length)) r1 = Room('臥室', 'rose', 10, 3.5, 16) r1.calc_area # <bound method Room.calc_area of <__main__.Room object at 0x0000000004FC9EB8>>
問題:那麼有什麼方法能夠向調用數據屬性同樣調用函數屬性呢?
類中提供了 @property
關鍵字,它的做用是在調用函數屬性時,能夠像調用數據屬性同樣並容許該函數,相似於函數中的裝飾器。
class Room: def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length @property def calc_area(self): print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length)) # return self.width * self.length r1 = Room('臥室', 'rose', 10, 3.5, 16) # r1.calc_area() r1.calc_area # 省略括號
只需在須要被裝飾的函數上面,加上 @property
便可。須要注意的是:被裝飾的函數不能有除 self 參數之外的參數
若是不進行實例化,直接用類名調用類的函數屬性,會報缺乏位置參數:
class Room: def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length @property def calc_area(self): print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length)) def open_door(self): print('打開%s的門' % self.name) Room.open_door()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-60-9b6abeb723cc> in <module>() 13 print('打開%s的門' % self.name) 14 ---> 15 Room.open() TypeError: open_door() missing 1 required positional argument: 'self'
傳入 self 作參數,也會報 name 'self' is not defined
。這樣由於 self 是特殊意義的,是實例自己,只有在實例化時纔有效。爲了解決這種問題,咱們引入了 @classmethod
,:
class Room: size = 'small' def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length @property def calc_area(self): print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length)) @classmethod def open_door(cls, color): print(cls) # cls 至關於 類自己,在定義類方法時自動補全 print('請打開那扇 %s 的 %s門' % (color, cls.size)) Room.open_door('red') --------------------------------------------------- <class '__main__.Room'> 請打開那扇 red 的 small門
能夠看出一旦類的某個函數屬性被定義爲類方法時,能夠直接使用類名調用類的函數屬性,而不用擔憂參數的問題。在定義類方法時,Python 會自動補全一個名叫 cls 的參數,能夠看出它就是類自己,在調用類函數屬性時不須要手動傳入,自己會自動傳入,與 self 相似。
若是有想在類中定義一個函數,要求該函數的參數與類、實例都無關(沒有綁定),咱們可使用靜態方法 @staticmethod
:
class Room: size = 'small' def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length @staticmethod def close_door(position, color): print('請關閉 %s 的 %s 小門' % (position, color)) r1 = Room('臥室', 'rose', 10, 20, 30) Room.close_door('廚房', 'black') r1.close_door('廁所', 'white')
被定義爲靜態方法的函數屬性,名義上歸類管理,但不能使用類變量和實例變量,只是類的工具包。
class Room: size = 'small' def __init__(self, name, owner, width, heigh, length): self.name = name self.owner = owner self.width = width self.heigh = heigh self.length = length @property def calc_area(self): print('%s 住的%s,總面積:%s' % (self.owner, self.name, self.width * self.length)) @classmethod def open_door(cls, color): #print(cls) print('請打開那扇 %s 的 %s門' % (color, cls.size)) @staticmethod def close_door(position, color): print('請關閉 %s 的 %s 小門' % (position, color)) def test(a, b): print(a, '-->', 'b') r1 = Room('臥室', 'rose', 10, 20, 30) # 靜態屬性 Room.calc_area # 不能調用 r1.calc_area # rose 住的臥室,總面積:300 # 類方法 Room.open_door('white') # 請打開那扇 white 的 small門 r1.open_door('white') # 請打開那扇 white 的 small門 # 靜態方法 Room.close_door('臥室', 'black') # 請關閉 臥室 的 black 小門 r1.close_door('臥室', 'black') # 請關閉 臥室 的 black 小門 # 常規函數 Room.test(1, 2) # 1 --> b r1.test(1, 2) # 不能調用
組合就是把一個類實例化傳入另外一個類中使用。類與類之間相互關聯,而且小的類組成大的類,這個時候能夠用組合。
class Turtle: def __init__(self, x): self.x = x class Fish: def __init__(self, x): self.x = x class Pool: def __init__(self, x, y): self.tutle = Turtle(x) self.fish = Fish(y) def print_num(self): print('水池裏總共有%s只烏龜,%s條魚' % (self.tutle.x, self.fish.x)) p1 = Pool(2, 3) p1.print_num()
水池裏總共有2只烏龜,3條魚
選課系統:
class School: def __init__(self, name, addr): self.name = name self.addr = addr def zhao_sheng(self): print('%s正在招生' % self.name) class Course: def __init__(self, name, price, period, school): self.name = name self.price = price self.period = period self.school = school class Teacher: def __init__(self, name, gender, age): self.name = name self.gender = gender self.age = age s1 = School('oldboy', '北京') s2 = School('oldboy', '南京') s3 = School('oldboy', '東京') t1 = Teacher('林老師', '男', '45') t2 = Teacher('李老師', '女', '28') t3 = Teacher('孫老師', '女', '32') c1 = Course('linux', '10000', '1', s1) print(c1.__dict__) msg = """ 1 老男孩 北京校區 2 老男孩 南京校區 3 老男孩 東京校區 4 林老師 5 李老師 6 孫老師 """ while True: print(msg) menu = { '1': s1, '2': s2, '3': s3, '4': t1, '5': t2, '6': t3 } choice = input('請選擇校區:') choice_teacher = input('請選擇老師:') teacher_obj = menu[choice_teacher] school_obj = menu[choice] name = input('課程名:') price = input('課程費用:') period = input('課程週期:') new_course = Course(name, price, period, school_obj) # school_obj = s1 print('課程【%s】屬於【%s】學校' % (new_course.name, new_course.school.name)) # new_courese.school = s1 s1.name = name = oldboy new_teacher = Course(name, price, period, teacher_obj) print('%s由%s教' % (new_teacher.name, new_teacher.school.name))
繼承、多肽、封裝
類的繼承與現實生活中的父、子繼承關係同樣。被繼承的類稱爲父類、基類或超類,繼承者稱爲子類。Python 繼承分爲:單繼承和多繼承(繼承多個類)。
class Parent1: pass class Parent2: pass class Sub1(Parent1): pass class Sub2(parent1, Parent2): pass
class Dad: """父類""" money = 100000 def __init__(self, name): print('爸爸') self.name = name def hit_son(self): print('%s正在打兒子' % self.name) class Son(Dad): pass print(Son.money) 100000 Son.hit_son() # hit_son() missing 1 required positional argument: 'self' # hit_son() 有一個位置參數 self,使用類名調用,不能傳入 self,只能用實例調用 print(Dad.__dict__) print(Son.__dict__) s1 = Son('tom') # 爸爸 print(s1.money) # 100000 s1.hit_son() # tom正在打兒子 print(s1.__dict__) # {'name': 'tom'}
子類繼承父類的全部屬性,調用父類的方法只能用實例調用。
class Son(Dad): money = 9000 def __init__(self, name, age): self.name = name self.age = age def hit_son(self): print('來自子類') s2 = Son('rose', 18) print(s2.money) print(Dad.money) s2.hit_son()
9000 100000 來自子類
若是子類中定義與父類同名的方法或屬性,不會覆蓋父類對應的方法或屬性,只是優先調用本身類自己的方法或屬性。
class Cat: def 喵喵叫(self): pass def 吃(self): pass def 喝(self): pass class Dog: def 汪汪叫(self): pass def 吃(self): pass def 喝(self): pass
狗和貓都屬於動物,提取它們的共性,作成基類:
class Animal: def 吃(self): pass def 喝(self): pass class Cat(Animal): def 喵喵叫(self): # 派生 pass class Dog(Animal): def 汪汪叫(self): # 派生 pass
喵喵叫、汪汪叫是派生而來的,吃喝拉撒是繼承而來
派生(衍生):子類在繼承父類的基礎上衍生出新的屬性,如:Cat 的喵喵叫、Dog 的汪汪叫。也能夠是子類定義與父類重名的屬性,子類也稱爲派生類。
接口繼承:
抽象:即提取相似部分
接口:基類的函數屬性,只提供函數名,不實現具體功能
定義一個基類,在基類中利用裝飾器 (@abc.abstractclassmethod
)將本身的函數屬性定義成接口函數,在子類中必須實現該接口函數,不然沒法實例化。
接口繼承實質上是要求 作出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的全部對象 —— 這在程序上,叫作歸一化。
歸一化使的高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合 —— 就比如 linux 的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、硬盤、網絡仍是屏幕(對於底層設計者來講,也能夠區分出 字符串設備 和 塊設備,而後作出針對性的設計,細緻到什麼程度,視需求而定)。
import abc class All_file(metaclass=abc.ABCMeta): # 基類 @abc.abstractmethod # 裝飾器定義兩個接口函數 read、write def read(self): # 那麼子類必須實現這兩個函數,否沒法實例化 pass @abc.abstractmethod def write(self): pass class Disk(All_file): def read(self): print('disk read') def write(self): print('disk write') class Memory(All_file): def read(self): print('memory read') d1 = Disk() d1.read() d1.write() m1 = Memory() m1.read()
disk read disk write --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-53d88a516ad1> in <module>() 23 d1.write() 24 ---> 25 m1 = Memory() 26 m1.read() TypeError: Can't instantiate abstract class Memory with abstract methods write
能夠看出 Disk 子類實現了基類的接口函數,能夠正常實例化並調用函數屬性。而 Memory 子類,沒有實現 Write()
,實例化失敗。
Python 能夠繼承多個類,c#、java 只能繼承一個。若是繼承多個,那麼其尋找方式有兩種:深度優先和廣度優先。
問題:Python 是如何實現繼承的?按照什麼樣的方式去查找的呢?
對於定義的每個類,Python 會計算出一個方法解析順序(MRO)元組,它是一個簡單的全部基類的線性順序元組,即一旦實現了繼承。就已經計算好查找順序,並存儲在元組中。查看方式:
Class.__mro__ # F.__mro__
爲了實現繼承,Python 會在 MRO 元組上從左到右開始查找基類,直至找到第一個匹配這個屬性的類位置。
而這個 MRO 元組的構造是經過一個 C3 線性化算法來實現的。實際上就是合併全部父類的 MRO 元組並遵循以下三條準則:
F(D, E)
D 要優先 Eclass A(object): def test(self): print('A') class B(A): def test(self): print('B') class C(A): def test(self): print('C') class D(B): def test(self): print('E') class E(C): def test(self): print('E') class F(D, E): def test(self): print('F') f1 = F() f1.test() print(F.__mro__) # F --> D --> B --> E --> C --> A --> object
F (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
在 Python 2.x 中有新式類和經典類之分,新式類與 Python3.x 的查找順序同樣。而經典類的查找順序爲:F --> D --> B --> A --> E --> C
。
若是想在子類中調用父類的方法(函數屬性),就須要重寫父類方法,不然會報錯:
class Vehicle: """交通工具""" Country = 'China' def __init__(self, name, speed, load, power): """名字、速度、負載、能量""" self.name = name self.speed = speed self.load = load self.power = power def run(self): print('%s今天開通~' % self.name) class Subway(Vehicle): """地鐵""" def __init__(self, line): """名字、速度、負載、能量、幾號線""" self.line = line def test(): pass line1 = Subway(1) line1.run() # AttributeError: 'Subway' object has no attribute 'name'
子類 Subway
中調用父類 Vehicle
的 run 方法,報 AttributeError: 'Subway' object has no attribute 'name'
。顯然調用沒有成功。
那麼咱們能夠重寫父類的方法:
class Subway(Vehicle): """地鐵""" def __init__(self, name, speed, load, power, line): # 調用父類的屬性 """名字、速度、負載、能量、幾號線""" self.name = name self.speed = speed self.load = load self.power = power self.line = line def run(self): print('%s今天開通~' % self.name) print('%s%s號線開通啦~' % (self.name, self.line)) line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1) line1.run() # 調用與父類同名的方法 ------------------------------ 長沙地鐵今天開通~ 長沙地鐵1號線開通啦~
當子類中與父類方法同名時,優先調用子類自己的。可是若是也想實現父類中的功能,就須要在子類相應的方法中重寫,如有幾十上百行代碼,重寫就顯得有點麻煩。
Python 給咱們提供了兩種解決方法:
調用未綁定的父類方法
只需在子類中須要調用父類的方法中,加上這樣一行代碼便可:
類名.__init__(self, x, y...) # 調用父類中的變量 類名.test(self) # 調用父類方法
具體示例以下:
class Subway(Vehicle): """地鐵""" def __init__(self, name, speed, load, power, line): """名字、速度、負載、能量、幾號線""" Vehicle.__init__(self, name, speed, load, power) # 調用幾個變量就添加幾個 self.line = line def run(self): Vehicle.run(self) print('%s%s號線開通啦~' % (self.name, self.line)) line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1) line1.run() ---------------------------------------- 長沙地鐵今天開通~ 長沙地鐵1號線開通啦~
在子類中調用父類方法,使用上面的方法,有個缺點:一旦父類名字變化,後面調用邏輯就須要大量更改被繼承的方法。Python 還提供了另外一種方法:super()函數。
它能夠自動找到父類的方法,而且自動傳入 self 參數。無需關心父類的名字,不須要去大量修改被繼承的方法:
super().meth([args])
class Subway(Vehicle): """地鐵""" def __init__(self, name, speed, load, power, line): """名字、速度、負載、能量、幾號線""" super().__init__(name, speed, load, power) self.line = line def run(self): super.run() print('%s%s號線開通啦~' % (self.name, self.line)) line1 = Subway('長沙地鐵', '200km/h', 1000, 'electric', 1) line1.run() ---------------------------------------- 長沙地鐵今天開通~ 長沙地鐵1號線開通啦~
由不一樣的類實例化獲得的對象,調用同一個方法,執行的邏輯不一樣。對象如何經過他們共同的屬性和動做來操做及訪問,而不考慮他們具體的類。
#_*_coding:utf-8_*_ __author__ = 'Hubery_Jun' class H2O: def __init__(self, name, temperature): self.name = name self.temperature = temperature def turn_ice(self): if self.temperature < 0: print('[%s]水溫過低,結冰了' % self.name) elif self.temperature > 0 and self.temperature < 100: print('[%s]液態化成水' % self.name) elif self.temperature > 100: print('[%s]溫度過高變成水蒸氣了' % self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass w1 = Water('水', 25) i1 = Water('冰', -25) s1 = Water('水蒸氣', 3000) # w1.turn_ice() # i1.turn_ice() # s1.turn_ice() def func(obj): obj.turn_ice() func(w1) func(i1) func(s1)
[水]液態化成水 [冰]水溫過低,結冰了 [水蒸氣]溫度過高變成水蒸氣了
對象 w一、i一、s1
由不一樣的類實例而來,可是它們調用同一個方法 turn_ice()
。咱們能夠定義一個函數 func
,專門用來處理 turn_ice()
方法,不須要關心類內部邏輯,只需考慮對象的類型,這就是多肽。
通常面向對象編程語言都有公有私有數據屬性,Java、C++ 使用 public 和 private 關鍵字來區分。而 Python 默認對象的屬性和方法都是公開的(即公有的),能夠直接經過點(.)來訪問。
class Person: name = 'rose' def __init__(self, age, gender): self.age = age self.gender = gender p1 = Person(18, 'female') print(p1.name) ---------------- rose
爲了實現相似私有變量的特性,Python 有一種約束,能夠對要私有化的變量,在其前面加一條下劃線(_)。可是從外部也能夠訪問,這是一種約束,約束看到這種變量就不去調用:
_name = 'rose'
Python 內部採用一種叫 name mangling(名字改編)
的技術,只**需在變量名前加雙下劃線便可(__),那麼這個變量就變成了私有變量**:
__name = 'rose' print(p1.__name) # AttributeError: 'Person' object has no attribute '_name'
其實 Python 目前的私有機制是僞私有,在外部仍是能夠經過 _類名__變量名
的方式去訪問私有變量:
print(p1._Person__rose()) print(Person.__dict__)
{'__module__': '__main__', '_Person__name': 'rose', '__init__': <function Person.__init__ at 0x00000000053D6598>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} rose
能夠發如今 Python 內部,將原有的變量名改變了名字:(__rose
變成了 _Person__name
。
class Person: name = 'rose' def __init__(self, age, gender): self.__age = age self.__gender = gender # 接口函數 def interface_func(self): return self.__age, self.__gender p1 = Person(18, 'female') print(p1.interface_func()) -------------------------- (18, 'female')
對於類中已經封裝的方法,只能在內部訪問,外部要想訪問。咱們能夠定義一個接口函數,而後調用接口函數間接使用封裝的方法。可是在實際開發中應謹慎使用接口函數,濫用接口函數就會致使類 千瘡百孔。
面向對象是一種更高級的結構化編程方式,其優勢有兩點:
反射是指程序能夠訪問、檢查和修改它自己狀態或行爲的一種能力(自省)。Python 內部實現自省的四個函數:hasattr()、getattr()、setattr()、delattr()
。
class Animal: def __init__(self, name, color): self.name = name self.color = color def eat(self): print('【%s】正則吃東西' % self.name) a1 = Animal('dog', 'black')
1. hasattr(object, name)
判斷一個對象中是否有指定的屬性,第一個參數爲對象,第二個爲屬性名。
print(hasattr(a1, 'name')) # True print(hasattr(a1, 'eat')) # True print(hasattr(a1, 'edfa')) # False
2. getattr(object, name[,default])
返回對象的指定屬性,若屬性不存在且未指定默認值,拋出 AttributeError
異常。指定默認值返回默認值。
print(getattr(a1, 'name')) # dog print(getattr(a1, 'eat')) # <bound method Animal.eat of <__main__.Animal object at 0x00000000025ACEF0>> #print(getattr(a1, 'edfa')) # AttributeError: 'Animal' object has no attribute 'edfa' print(getattr(a1, 'edfa', '沒有找到')) # 沒有找到
3. setattr(object, name, value)
設置對象中指定屬性的值,如屬性不存在則新增,存在則修改。
# a1.name = 'cat' # 常規修改方法 setattr(a1, 'name', 'cat') # 修改 setattr(a1, 'age', 1) # 新增
4. delattr(object, name)
刪除對象指定的屬性,屬性不存在拋出 AttributeError
異常。
# a1.name # 常規修改方法 delattr(a1, 'name')
反射的有兩大好處:一是能夠實現可插拔機制,二是動態導入模塊(基於反射當前模塊成員)
實現可插拔機制
好比說有兩個程序員,分別負責開發兩個模塊ftp_client.py、user.py
。user.py
依賴 ftp_client.py
。若哪天負責開發 ftp_client.py
的程序員忽然去有事了,爲了避免影響總體進度,咱們能夠利用反射判斷 ftp_client.py
是否實現了某個功能,即便沒實現也不會報錯:
# ftp_client.py class FtpClient: 'ftp 客戶端,具體功能還沒有實現' def __init__(self, addr): print('正在鏈接服務器【%s】' % addr) self.addr = addr
利用 hasattr()
判斷 FtpClient 類
中是否有 put 方法,如有則獲取,不然繼續實現其餘功能邏輯:
# user.py from ftp_client import FtpClient f1 = FtpClient('8.8.8.8') if hasattr(f1, 'put'): func_put = getattr(f1, 'put') func_put() else: print('put 功能還沒有實現') print('其餘功能邏輯')
動態導入模塊
若導入的模塊是字符串形式,咱們以前能夠採用 __import__()
函數導入。
包 h 下有個 a1 模塊,要想在另外一個模塊中調用 a1 中的 test()
函數:
r = __import__('h.a1') r.a1.test()
另外一種方式是利用 importlib 模塊
,動態導入模塊:
import importlib r = importlib.import_module('h.a1') r.test()
__getattr__、__setarrr__、__delattr__
__getattr__
當獲取不存在的屬性時觸發:
class Foo: x = 1 def __init__(self, y): self.y = y def __getattr__(self, item): print('屬性【%s】不存在~' % item) f1 = Foo(10) print(f1.y) # 10 print(f1.s) # 屬性【s】不存在~、None
對象 f1 傳給 self,y 或 s 傳給 item。
__setattr__
設置屬性時觸發
def __setattr__(self, key, value): print('正在設置屬性~') # self.key = value # RecursionError: maximum recursion depth exceeded in comparison(超出最大遞歸深度) self.__dict__[key] = value f1 = Foo(10) f1.m = 20 print(f1.__dict__) # 正在設置屬性~ # 正在設置屬性~ # {'y': 10, 'm': 20}
__delattr__
刪除屬性時觸發
def __delattr__(self, item): print('正在刪除~') f1 = Foo(10) del f1.y del f1.x # 正在刪除~ # 正在刪除~
Python 提供了不少內置的標準數據類型,以及內置方法。在不少場景下咱們須要基於標準數據類型來定製咱們本身的類型,新增或改寫方法。咱們能夠用繼承/派生來對標準類型進行二次加工:
需求:
基於 list 新增一個方法用於返回列表中間元素,修改 list 內置的 append 方法,要求只有當新增的元素是字符串時才能添加進去。
class List(list): """繼承 list 全部的屬性,也派生本身的,好比 append,mid""" def append(self, str_obj): """派生本身的 append:類型檢查""" if isinstance(str_obj, str): super().append(str_obj) # 調用父類的 append()方法,調用本身的會無限遞歸直至溢出 else: print('必須是字符串類型') def mid(self): """新增屬性""" middle = int(len(self)/2) return '列表%s,中間元素是【%s】' % (self, self[middle]) # 新增屬性 # List 繼承 list,那麼 List('hello')至關於 list('hello') ,self 爲實例自己,那麼 self = list(‘heelo')’) l1 = List('hello') # 列表['h', 'e', 'l', 'l', 'o'],中間元素是【l】 l1.mid() # # 派生本身的 append # l2 = List('hello') # l2.append('2') # print(l2) # ['h', 'e', 'l', 'l', 'o', '2'] # l2.append(3) # 必須是字符串類型
受權
受權是包裝的另外一特性,它與包裝的區別在於:
受權的關鍵點在於覆蓋 __getattr__
需求: 模擬 open 函數的read、write 方法,要求 read 方法交給類方法處理,write 方法受權給對象默認屬性:
read 方法
class FileHandle: """模擬 open 函數""" def __init__(self, file, mode='r', encoding='utf-8'): self.file = open(file, mode, encoding=encoding) # 文件句柄或文件對象 <_io.TextIOWrapper name='a.txt' mode='r+' encoding='utf-8'> def __getattr__(self, item): # print('不存在') print(self, item) # self:f1,item:'read' return getattr(self.file, item) # 在文件對象中查找 字符串 read f1 = FileHandle('a.txt', 'r+') print(f1.read()) # read = f1.read, print(read()) # f1.read 不存在則會觸發 __getattr__ -------------- # aaaaa
類 FileHandle 的構造函數中以組合的形式,將參數傳給 open() 函數。得到文件句柄 self.file
,利用文件句柄咱們能夠對文件進行讀寫操做。
類中不存在 read 方法,所以 f1.read
會觸發 __getattr__()
,而後利用 getattr
得到 open 函數的 read 屬性(或方法),最後返回。
write 方法
import time def write(self, line): t = time.strftime('%Y-%m-%d %X') # 獲取當前字符串時間 time.sleep(0.1) self.file.write('%s %s' % (line, t)) # 把 write 受權給 FileHandle f1 = FileHandle('a.txt', 'r+') f1.write('第一行\n') f1.write('第二行\n') f1.write('第三行\n') f1.seek(0) # seek 不存在,觸發 __getattr__ print(f1.read())