Python中面向物件的概念
1、語言的分類
1)面向機器
抽象成機器指令,機器容易理解。代表:組合語言。
2)面向過程
做一件事,排除步驟,第一步做什麼,第二步做什麼,如果出現A問題,做什麼處理,出現b問題,做什麼處理。問題規模小,步驟化,按部就班處理。 代表:c語言。
(按照步驟進行處理的。)
面向物件和麵向過程的差異(一步一步的走,都有誰做or抽象成為不同的類,誰能做。)
程式設計是多正規化的,面向物件只是一種正規化。
3)面向物件ocp
隨著計算機需要解決的問題規模擴大,情況越來越複雜,需要很多人,很多部門協作,面向過程程式設計太不適合了。代表:c++ java python等
三要素:封裝,父類有的,子類直接繼承,多繼承少修改,
繼承是為了多複用。單一繼承、多繼承。Mixin技術
2、面向物件
1)定義 (是一種認識世界,分析世界的方法論,用類來表現實實在在的物件。)
c語言是面向過程的,Python和Java是面向物件的。(大型專案的話利用類抽象)面向物件一種是抽象的,一種是具體的。
一種認識世界、分析世界的方法論,將萬事萬物抽象為類。
2)類class
類是抽象的概念,是萬事萬物的抽象,是一類實物的共同特徵的集合。
用計算機語言來描述類,就是屬性和方法的集合。
3)物件instance、object
物件是類的巨象,是一個實體。
對於每個人這個個體,都是抽象概念人類的不同的實體。
(你吃魚,你就是物件。魚,也是物件;吃是動作。你是具體的人,是具體的動作。你屬於人類,人類是個抽象的概念,是無數具體的個體的抽象。魚也是具體的物件,就是說你吃的是一條具體的魚,這條魚屬於魚類,是無數的魚抽象出來的概念。)
(吃,是具體的動作,也是操作,也是方法,這個吃是你的動作,也就是人類具有的方法,如果說的是魚吃人,吃就是魚類的動作了)
(吃是個動作,許多動物都具有的動作,人類和魚類都屬於動物類,而動物類是抽象的概念,是動物都有吃的動作,但是吃法不同而已)
(駕駛車,這個車也是車類的具體的物件(例項),駕駛這個動作是魚類不具有的,是人類具有的方法。)
4)屬性
是物件狀態的抽象,用資料結構來描述。
5)操作
他是物件行為的抽象,用操作名和實現該操作方法的描述。
(每個人都有名字,身高,體重等資訊,這些資訊都是個人的屬性,但是,這些資訊不能儲存在人類中,因為他是抽象的概念,不能保留具體的值。)
(而人類的例項,就是具體的人,他可以儲存這些具體的特性,而且可以不同的人有不同的屬性)
6)哲學
一切皆物件
物件是資料和操作的封裝
物件是獨立的,但是物件之間可以相互作用。
目前oop是最接近人類認知的程式設計正規化。
3、面向物件3要素****
1)封裝
*組裝:將資料和操作組裝到一起。
*對外只是暴露一些介面,通過介面訪問物件。(可供操作的屬性,暴漏的屬性)
2)繼承
*多複用,繼承來的就不用自己寫了。(用到父類已經做好的實現和操作,在自己內部無需實現了,相同的就不寫了,寫自己獨有的特點。)
*多繼承少修改,ocp(open-closed princile),使用繼承來改變,來體現個性。(父類的基礎上少修改)。
3)多型
*面向物件程式設計最靈活的地方,動態繫結。
人類就是封裝:
人類繼承自動物類,孩子繼承父母的特徵,分為單一繼承、多繼承;
多型,繼承自動物類的人類,貓類的操作吃的不同。
(類物件)類的定義
(類的物件)類的例項。
4、Python的類
1)定義
Class ClassName:
語句塊 (放屬性和方法,例項的屬性。例項化過程。)
(1)必須使用class關鍵字。
(2)類名必須是用大駝峰命名。
(3)類定義完成後,就產生了一個類物件,綁定了標示符ClassName上。
(4)舉例
class MyClass:
"""A example class"""
x = 1 # 類屬性
def foo(self): #類屬性foo,也是方法
return " My Class"
print(MyClass.x)
print(MyClass.foo)
print(MyClass.foo(1))
print(MyClass.__doc__)
No1 print: 1
No2 print: <function MyClass.foo at 0x0000002E421852F0> (說是個函式類在某個地址裡面)
No3 print: My Class
No4 print: A example class
|
(標示符就是一個屬性)
5、類物件及類屬性
1)類物件
類的定義就會產生一個類物件。
2)類屬性
類定義中的變數和類中定義的方法都是類的屬性。
3)類變數
上類中的x是類MyClass的變數。
(加括號呼叫方法,不加是呼叫的屬性。)
4)總結
MyClass中 x、foo都是類的屬性。
foo方法都是類的屬性,如同吃是人類的方法。但是每個具體的人才能吃東西,也就說吃人的例項才能呼叫的方法。
foo 是方法物件method,不是普通的函式物件function了,他一般要求至少有一個引數。第一個引數可以是self(self是標示符),這個引數位置就留給了self
6、類的例項
呼叫MyClass().Show() (new函式和init函式都呼叫了) 帶有self的都是方法。
呼叫 a = MyClass()
背後是要進行繫結的。
前後都有下劃線的稱為魔術方法。初始化方法。
7、例項化
a = MyClass()
1) 定義方法:
使用上面的語法,在類物件名稱後面加上一個括號,就是呼叫類的例項化方法,完成例項化。例項化就是真正建立一個該類的物件(例項)。
tom = MyClass()
Jerry = MyClass()
tom /jerry都是MyClass的例項,通過例項化生成了2個例項。
每次例項化後獲得例項,是不同的例項,即使是使用同樣的引數例項化,也得到不同的物件。
Python類例項化後,會自動呼叫__init__,這個方法第一個引數都必須留給self,其它引數隨意。
2)__init__
3)MyClass()實際上呼叫的是__init__(self)方法,可以不定義,如果沒有定義會在例項化後隱式呼叫。
作用:對例項進行初始化(self當前物件的本身)。例項帶初始化。
初始化函式可以多個引數,第一個引數位置必須是self。
__init__()方法不能有返回值,也就是隻能是None。
class MyClass:
"""A example class"""
x = 1 # 類屬性
def foo(self): #類屬性foo,也是方法
return " My Class"
# print(MyClass) #不會被呼叫
print(MyClass()) #會呼叫
a = MyClass() # 會被呼叫。。結果和2一樣。
print(a)
No1 print:不會被呼叫
No2 print :<__main__.MyClass object at 0x0000001101903908>
No2 print :<__main__.MyClass object at 0x0000001101903908>
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def showage(self):
print('{} is {}'.format(self.name,self.age))
tom = Person('Tom',20)
cat = Person('cat',12)
tom.showage() # Tom is 20
cat.showage() # cat is 12
print(tom.name,cat.name) # Tom cat
cat.age += 1
print(cat.age) # 13
cat.showage() # cat is 13
每次例項化後都會生成不同的物件。(id地址不同)
8、例項物件instance
1)類例項化後一定會獲得一個物件,就是例項物件。
tom cat 就是Person類的例項。
__init__ 方法的第一引數self就是指代某一例項。
類例項化後,得到一個例項物件,例項物件都會繫結方法,呼叫時採用tom.showage()的方式。函式簽名是showage(self)。這個self就是tom,python中會把方法的呼叫者作為第一引數self作為實參傳入。
Self,name就是tom物件的name,name是儲存在tom物件上面,而不是Person的類上面,所有稱為例項變數。
2) Self 例項化。
class MyClass:
def __init__(self):
print('self in init = {}'.format(id(self)))
c = MyClass()
print('c = {}'.format(id(c)))
self in init = 583263140104
c = 583263140104
Self就是呼叫者,就是c對應的例項物件。
Self這個名字只是一個慣例,可以修改,最好不要修改,影響可讀性。
9、例項變數和類變數
class Person:
age = 3
def __init__(self,name):
self.name = name
tom = Person('tom')
cat = Person('cat')
print(tom.name,tom.age)
print(cat.name,cat.age)
print(Person.age)
Person.age = 30
print(Person.age,tom.age,cat.age)
tom 3
cat 3
3
30 30 30
例項變數是每一個例項自己的變數,是自己獨有的。類變數是累的變數,是類的所有例項共享和使用的方法。
特殊屬性 |
含義 |
__name__ |
物件名 |
__class__ |
物件的模型 |
__dict__ |
物件的屬性的字典 |
__qualname__ |
類的限定名 |
Python中每一種物件都擁有不同的屬性,函式、類、都是物件,類的例項也是物件。
class Person:
age = 3
def __init__(self,name):
self.name = name
print('class-----------')
print(Person.__class__)
print(sorted(Person.__dict__.items()),end='\n\n')
tom = Person('tom')
print('----instance tom ----')
print(tom.__class__)
print(sorted(tom.__dict__.items()),end='\n\n')
print('-----tom class------')
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end= '\n\n')
class-----------
<class 'type'>
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
----instance tom ----
<class '__main__.Person'>
[('name', 'tom')]
-----tom class------
Person
[('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x000000FD0DFF52F0>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3)]
class Person:
age = 3
hight = 170
def __init__(self,name,age=18):
self.name = name
self.age = age
tom = Person('tom')
cat = Person('cat',20)
Person.age = 30
print(Person.age,tom.age,cat.age)
print(Person.hight,tom.hight,cat.hight)
cat.hight = 175
print(Person.hight,tom.hight,cat.hight)
tom.hight +=10
print(Person.hight,tom.hight,cat.hight)
Person.hight +=15
print(Person.hight,tom.hight,cat.hight)
Person.weight = 70
print(Person.weight,tom.weight,cat.weight)
print(tom.__dict__['hight'])
# print(tom.__dict__['weight']) 不可執行,因為後來定義的字典內沒有
30 18 20
170 170 170
170 170 175
170 180 175
185 180 175
70 70 70
180
總結:
是類的,也就是這個類的所有例項,其例項都可以訪問的到,是例項的,就是這個例項自己,通過類訪問不到。
類變數是屬於類的變數,這個類的所有例項可以共享這個變數。
例項可以動態的給自己增加一個屬性。例項.__dict__,如果沒有,然後通過屬性__class__找到自己的類,再去類的__dict__中找。
注意,如果例項使用__dict__[變數名]訪問變數,將不會按照上面的順序查詢變量了,這是指明使用字典的key查詢,不是屬性來查詢。
一般來說,類變數使用全部大寫來命名。
特殊屬性
例項的字典裡面放的是和self有關的資訊,類字典裡面放的是類的資訊,類的字典的items。
屬性訪問的:的找自己的找不到自己的找類的。
字典訪問的:指定的是自己的字典。
賦值即定義,定義屬性。
Person().normal()例項呼叫。必須傳引數。不用。
Person.normal()類呼叫。
10、裝飾一個類
def add_name(cls):
cls.NAME = name
return cls
@add_name('tom')
class Person:
AGE = 3
def add_name(name):
def wrapper(cls):
cls.NAME = name
return cls
return wrapper
@add_name('tom')
class Person:
AGE = 3
11、類方法和靜態方法。
__init__等方法,這些方法的本身都是類的屬性,第一個引數必須是self,而self必須指向一個物件,也就是類必須例項化以後,由例項來呼叫這個方法。
1)普通函式:
class Person:
def normal_method():
print('normal')
# Person.normal_method() #可以呼叫
# Person().normal_method() #不可以呼叫
print(Person.__dict__)
Person.normal_method()
可以被呼叫,因為這個方法只是被Person這個名詞空間管理的一個普通方法,normal只是一個屬性而已。
由於normal_method在定義的時候沒有指定self。所以不能完成例項物件的banding,不能用,Person().Normal_method()繫結。 雖然語法對的,但是禁止這麼寫。
2)類方法
class Person:
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
Person.class_method()
print(Person.__dict__)
class=Person(<class '__main__.Person'>)
{'__module__': '__main__', 'class_method': <classmethod object at 0x000000C59D4A3908>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}
(1)在類定義中,使用@clasmethod裝飾器修飾的方法,
(2)必須至少有一個引數,且第一個引數留給了cls,cls指代呼叫者即類物件自身。
(3)cls這個標示符可以是任意合法名稱,但是為了易讀,不建議修改。
(4)通過cls可以直接操作類的屬性。
(3)靜態方法。
class Person:
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
@staticmethod
def static_method():
print(Person.HIGHT)
Person.class_method()
Person.static_method()
print(Person.__dict__)
class=Person(<class '__main__.Person'>)
170
{'__module__': '__main__', 'class_method': <classmethod object at 0x000000E7C4993908>, 'static_method': <staticmethod object at 0x000000E7C49938D0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HIGHT': 170}
(1)staticmethod 靜態方法
(2)不需要傳參。
(3)在類定義中,使用@staticmethod裝飾器修飾的方法。
(4)呼叫時不會隱身傳入引數,靜態方法,只是表明這個方法屬於這個名詞空間,函式歸在一起,方便管理。
12、方法的呼叫
class Person:
def normal_method():
print('normal')
def method(self):
print('{} method'.format(self))
@classmethod
def class_method(cls):
print('class={0.__name__}({0})'.format(cls))
cls.HIGHT = 170
@staticmethod
def static_method():
print(Person.HIGHT)
print('---類訪問---')
print(1,Person.normal_method()) # 可以呼叫,返回None。
# print(2,Person.method()) #不可以被呼叫,必須傳入引數,一個self
print(3,Person.class_method()) ##返回None
print(4,Person.static_method()) #返回None
print(Person.__dict__)
print('例項訪問')
print('tom')
tom = Person()
# print(1,tom.normal_method()) #不能呼叫,報錯 需要引數
print(2,tom.method()) #可以
print(3,tom.class_method()) #可以
print(4,tom.static_method()) #可以
總結:
類幾乎可以呼叫所有內部定義的方法,但是呼叫普通方法的時候會報錯,原因是第一引數必須是類的例項。
例項幾乎可以呼叫所有,普通的函式的呼叫一般不可能出現,因為不允許定義。
類除了普通方法都可以呼叫,普通方法需要物件的例項作為第一引數。
例項可以呼叫所有類中定義的方法,(靜態,類方法),普通方法傳入例項本身,靜態方法和類方法需要找到例項的類。
13、訪問控制
1)私有屬性(private)
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def growup(self,i=1): #控制邏輯
if i>0 and i <150:
self.age +=1
p1 = Person('tom')
p1.growup(20)
p1.age =160
print(p1.age)
想通過方法來控制屬性,但是由於屬性在外部可以訪問,就直接繞過方法,直接修改屬性。私有屬性則解決了這個問題。
私有屬性:
使用雙下劃線開頭的屬性名。就是私有屬性。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制邏輯
if i>0 and i <150:
self.__age +=1
p1 = Person('tom')
p1.growup(20)
# p1.age =160
print(p1.__age)
print(p1.__age)
AttributeError: 'Person' object has no attribute '__age'
訪問不到 __age ..
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制邏輯
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
print(p1.getage())
Print 19
2)私有屬性的本質,
外部訪問不到,能夠動態增加一個__age?
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制邏輯
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())
年齡沒有被覆蓋,全部存在__dict__的字典裡面了。
私有變數本質:
類定義的是,如果生命一個例項變數的時候,使用雙下劃線,python的直譯器就會將其改名,轉換為_類名__變數名 的名稱了,所以用原來的名字訪問不到了。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def growup(self,i=1): #控制邏輯
if i>0 and i <150:
self.__age += 1
def getage(self):
return self.__age
p1 = Person('tom')
p1.__age =28
print(p1.__age)
# print(p1.getage())
p1._Person__age =15
print(p1.getage())
print(p1.__dict__)
28
15
{'name': 'tom', '_Person__age': 15, '__age': 28}
##
3)保護變數
在變數名前面使用一個下劃線,稱為保護變數。
class Person:
def __init__(self,name,age = 18):
self.name = name
self._age = age
tom = Person('tom')
print(tom._age)
print(tom.__dict__)
18
{'name': 'tom', '_age': 18}
_age屬性沒有改變名稱,和普通屬性一樣,直譯器不做任何處理,只是開發者共同的約定,看見這樣的變數,就如同私有變數,不直接使用。
4)私有方法
參展保護變數,私有變數,使用單下劃線,雙下劃線命名方法。
class Person:
def __init__(self,name,age =18):
self.name = name
self._age = age
def _getname(self):
return self.name
def __getage(self):
return self._age
tom = Person('tom')
print(tom._getname()) ##沒改名
# print(tom.__getage()) ##m沒有次屬性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage()) #改名了
tom
{'name': 'tom', '_age': 18}
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x000000F1ECDB52F0>, '_getname': <function Person._getname at 0x000000F1ECDB5620>, '_Person__getage': <function Person.__getage at 0x000000F1ECDB56A8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
18
5)私有方法的本質
當下劃線的方法只是開發者之間的約定,直譯器不做任何改變
雙下劃線的方法,是私有方法,直譯器會改名,改名策略和私有變數相同,_類名__昂發名。
方法變數都在類的 __dict__ 裡面可以找到。
6)私有成員總結;
在python中使用_單下劃線或者_雙下劃線來標示一個成員被保護或者被私有化隱藏起來,但是不管使用什麼樣的訪問控制,都不能真正的組織使用者修改類的成員,python中沒有決定的安全環保成員或者私有成員。
因此,前道的下劃線只是一種警告或者提醒,要遵守這個約定,除非有必要,否則不能修好或者使用保護成員或者私有成員,更不能修改。
14、屬性裝飾器
一般很好的設計是:把例項的屬性保護起來,不讓外部直接訪問,外部使用getter讀取屬性和setter方法設定屬性。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age =age
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
self.__age = age
tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)
使用property裝飾器的時候,三個方法必須同名。
Property裝飾器:
後面跟的函式名就是以後的屬性名,他就是getter,這個必須有,有了它至少有了只讀屬性。
Setter裝飾器
與屬性名同名,且接受兩個引數,第一個是self,第二個是將要賦值的值,有了它,屬性可寫。
Deleter裝飾器
可以控制是否刪除屬性,很少用。
Property裝飾器必須在前,setter 和 delter裝飾器在後面。
Property裝飾器能通過簡單的方式,吧對方法的操作變成對屬性的訪問,並起到了一定的隱藏效果。
其它寫法:
class Person:
def __init__(self,name,age =20):
self.name = name
self.__age = age
def getage(self):
return self.__age
def setage(self,age):
self.__age =age
def delage(self):
del self.__age
print('del')
age = property(getage,setage,delage,'age property')
tom = Person('tom')
print(tom.age)
tom.age = 25
del tom.age
class Person:
def __init__(self,name,age=20):
self.name =name
self.__age = age
age = property(lambda self:self.__age)
tom = Person('tom')
print(tom.age)
可讀、可寫。
15、補丁。
可以通過修改或者替換類的成員。使用著呼叫的方式沒有改變,但是,類提供的功能可以已經改變。
猴子補丁:
在執行時,對屬性,方法,函式等進行動態替換。
其目的是為了替換,修改來增強,擴充套件原有的程式碼的能力。
慎重使用。
上例中,假設Person類和get_score方法是從資料庫中拿資料,但是測試的時候不方便。
使用猴子補丁的方法,替換了get_score方法,返回模擬的資料。
16、物件的銷燬。
__del__ 方法,稱為解構函式(方法)
作用:銷燬類的時候呼叫,以釋放佔用的資源,其中就放些清理資源的程式碼。比如釋放連結。
這個方法不能讓物件真正的小虎,只是物件銷燬的時候會自動呼叫它。
使用del的語句刪除例項,引用計數減1,當引用計數為0時候,會idong呼叫__del__方法。
import time
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
def __del__(self):
print('delete{}'.format(self.name))
def test():
tom = Person('tom')
tom.__del__()
tom.__del__()
tom.__del__()
print('=======')
tom1 = tom
tom3 = tom1
print(1,'del')
del tom
time.sleep(3)
print(2,'del')
del tom3
time.sleep(3)
print('-----')
del tom3
time.sleep(3)
print('........')
test()
由於垃圾回收物件銷燬時候,才會真正清理物件,還會再之間自動呼叫__del__ 方法。除非知道自己的目的,否則不會手動清理;不會手動呼叫這個方法。
17、方法過載。
Python沒有過載,不需要過載
Python中,方法(函式)定義中,形參非常靈活,不需要指定型別(就算指定了也只是說明一個說明而非約束,),形參個數也不固定(可變引數),一個函式的定義可以實現很多種不同形式實參的呼叫,所喲python不需要方法過載。
或者說python本身就是實現了其他語言的過載。
18、封裝:
面向物件的三要素之一,封裝。
封裝:
將資料和操作組織到類中,即屬性和方法。
將資料隱藏起來,給使用者提供操作(方法),使用者通過操作就可以獲取或者修改資料。
Getter和setter。
通過控制訪問,暴漏給適當的資料和操作給使用者,該隱藏的隱藏起來,例如保護成員或者私有成員。
19、習題:
1)隨機整數生成類
##初步程式碼:
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
def array(self):
lst= [random.randint(self.start,self.end)for i in range(self.length)]
print(lst)
j = Ran()
j.length=5
j.array()
###第二步改進控制列印長度的
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
def array(self,length=0):
length = self.length if length<=0 else length
lst= [random.randint(self.start,self.end)for i in range(length)]
print(lst)
j = Ran()
j.array(20)
####三,利用工具實現的方式
import random
class Ran:
@classmethod
def array(cls,start = 1,end =100,length =10):
return [random.randint(start,end)for _ in range(length)]
a = Ran()
print(a.array())
###利用生成器
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
self._gen = self._array()
def _array(self):
while True:
yield random.randint(self.start,self.end)
def array(self,length=0):
if length <= 0:
return [next(self._gen)for _ in range(self.length)]
else:
return [next(self._gen)for i in range(length)]
j = Ran()
print(j.array(20))
print(j.array())
import random
class Ran:
def __init__(self,start=0,end=100,length=10):
self.start = start
self.end = end
self.length = length
self._gen = self._array()
def _array(self):
while True:
yield [random.randint(self.start,self.end)for _ in range(self.length)]
def array(self,length=0):
if length > 0:
self.length = length
return next(self._gen)
j = Ran()
print(j.array(20))
print(j.array())
2)利用上次的隨機數生成20次,兩兩組和,列印座標。
import random
class Ran:
def __init__(self,start=0,end=100,count=20):
self.start = start
self.end= end
self.count = count
def array(self,count=0):
count = self.count if count<=0 else count
lst = [random.randint(self.start,self.end)for i in range(count)]
return lst
a = Ran()
a.array()
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
# points = zip(a.array(),a.array())
# for x in points:
# print(x)
points = [Point(x,y)for x,y in zip(a.array(),a.array())]
for p in points:
print('{}:{}'.format(p.x,p.y))
3)車輛資訊
class Car:
def __init__(self, mark, color, price, speed, **kwargs):
# self.id = genid
self.mark = mark
self.color = color
self.price = price
self.speed = speed
self.__dict__.update(kwargs)
class CarInfo:
cars = []
def addcar(self,car:Car):
self.cars.append(car)
def getall(self):
return self.cars
c1 = CarInfo()
car = Car('audi','red',100,100)
c1.addcar(car)
print(c1.getall())
4、實現溫度的處理。
class Temp:
def __init__(self,t,unit='c'):
self._c = None
self._f = None
self._k =None
#都先轉換為攝氏溫度,然後以後訪問呼叫計算其他的溫度的值
if unit == 'k':
self._k = t
self._c = self.k2c(t)
elif unit == 'f':
pass
else:
self._c = t
@property
def c(self):
return self._c
@property
def k(self):
pass
@property
def f(self):
pass
#溫度轉換
@classmethod
def c2f(cls,c):
return 9*c/5 +32
@classmethod
def f2c(cls,f):
return 5*(f-32)/9
@classmethod
def c2k(cls,c):
return c +273.15
@classmethod
def k2c(cls,k):
return k -273.15
@classmethod
def f2k(cls,f):
return cls.c2k(cls.f2c(f))
@classmethod
def k2f(cls,k):
return cls.c2f(cls.k2c(k))
print(Temp.c2f(40))
print(Temp.c2k(40))
print(Temp.f2c(40))
print(Temp.k2c(40))
print(Temp.k2f(40))
print(Temp.f2k(40))
t = Temp(37)
print(t.c,t.k,t.f)
5、模擬購物車購物
class Item:
def __init__(self,**kwargs):
self.__spec = kwargs
def __repr__(self):
return str(sorted(self.__spec.items()))
class Cart:
def __init__(self):
self.items = []
def additem(self,item:Item):
self.items.append(item)
def getallitems(self):
return self.items
mycart = Cart()
myphone = Item(mark='apple',menory='4G')
mycart.additem(myphone)
print(mycart.getallitems())
一、類的繼承
1、概念;
1)面向物件的三要素之一,繼承inheritance
人類和貓類都是繼承動物類。
個體繼承自父母,繼承了父母的一部分特徵,但也可以有自己的個性。
在面向物件的世界中,從父類中繼承,就可以直接擁有父類的屬性和方法,這個可以減少程式碼,增加多複用,子類可以定義自己的屬性和方法。
class Animal:
def shout(self):
print('Animal shouts')
class Cat:
def shout(self):
print('Cat shouts')
class Dog:
def shout(self):
print('Dog shouts')
a = Animal()
a.shout()
c = Cat()
c.shout()
d = Dog()
d.shout()
Animal shouts
Cat shouts
Dog shouts
上面的例子的類雖然有關係,但是定義時候並沒有建立這種關係,而是各自完成定義。。
class Animal:
def __init__(self,name):
self._name = name
def shout(self):
print('{} shouts'.format(self.__class__.__name__))
@property
def name(self):
return self._name
a = Animal('monster')
a.shout()
class Cat(Animal):
pass
cat = Cat('garfield')
cat.shout()
class Dog(Animal):
pass
dog = Dog('ahuang')
dog.shout()
print(dog.name)
上例可以看出,通過繼承,貓類,狗類,直接繼承了父類的屬性和方法。
2)繼承
Class cat(Animal)這種形式就是從父類繼承,括號中寫繼承的類的列表。
繼承可以讓子類從父類獲取特徵(屬性和方法)
父類Animal 就是cat的父類,也稱為基類,超類。
子類
Cat 就是Animal的子類,也成為派生類。
2、定義
格式:
Class 子類名(基類1,[基類2,...........]):
語句塊
如果類定義的時候,沒有基類列表,等同於繼承自object,在python3種,object類是所有物件的根基類。
Class A:
Pass
#等價於
Class A(object):
Pass
Python支援多繼承,繼承也可以多級。
檢視繼承的特殊屬性和方法:
特殊屬性和方法 |
含義 |
舉例 |
__base__ |
類的基類 |
|
__bases__ |
類的基類元組 |
|
__mro__ |
顯示方法查詢順序,基類的元組 |
|
mro()方法 |
同上 |
Int.mro() |
__subclasses__() |
類的子類列表 |
Int.__subclasses__() |
3、繼承中的訪問控制
class Animal:
__COUNT = 100
HEIGHT = 0
def __init__(self,age,weight,height):
self.__COUNT += 1
self.age = age
self.__weight = weight
self.HEIGHT = height
def eat(self):
print('{}eat'.format(self.__class__.__name__))
def __getweight(self):
print(self.__weight)
@classmethod
def showcount1(cls):
print(cls.__COUNT)
@classmethod
def __showcount2(cls):
print(cls.__COUNT)
def showcount3(self):
print(self.__COUNT)
class Cat(Animal):
NAME = 'CAT'
__COUNT = 200
# c = Cat() 函式引數錯誤,傳參錯誤。
c = Cat(3,5,15)
c.eat() ##cat eat
print(c.HEIGHT) ##15
# print(c.__COUNT) ##私有不可訪問
# c.__getweight() #私有不可訪問
c.showcount1() #100因為__是改名的,在例項的類找不到,所以利用繼承的。
# c.__showcount2() #不可以,私有的不能訪問
c.showcount3() #101
print(c.NAME) # CAT
print('{}'.format(Animal.__dict__))
print('{}'.format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())
後四個print
1/{'_Animal__getweight': <function Animal.__getweight at 0x000000D34976B0D0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, 'HEIGHT': 0, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'showcount3': <function Animal.showcount3 at 0x000000D34976B268>, 'showcount1': <classmethod object at 0x000000D349769748>, 'eat': <function Animal.eat at 0x000000D349764BF8>, '_Animal__COUNT': 100, '__module__': '__main__', '__init__': <function Animal.__init__ at 0x000000D349764D08>, '_Animal__showcount2': <classmethod object at 0x000000D349769780>, '__doc__': None}
2/{'NAME': 'CAT', '__module__': '__main__', '_Cat__COUNT': 200, '__doc__': None}
3/{'_Animal__COUNT': 101, 'HEIGHT': 15, '_Animal__weight': 5, 'age': 3}
4/[<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]
從父類繼承,自己沒有的,就到父類中去找。
私有的都是不可以訪問的,但是本質上依然是改了名字放在這個屬性的類的__dict__ 中,知道這個新名稱就可以直接找到這個隱藏的變數。
(類外部是不會被訪問的,私有屬性,內部可以。)
繼承順序:例項,自己的類,父類。繼承類;
總結:
繼承時候,公有的,子類和例項都可以隨意訪問,私有成員被隱藏,子類和例項不可直接訪問,當私有變數所在的類內的方法都可以訪問這個私有變數。
屬性查詢順序:
例項的__dict__>> 類__dict__如果有繼承==〉父類__dict__.
dir()來檢視繼承屬性。
4、方法的重寫、覆蓋override
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
#覆蓋了父類的方法
def shout(self):
print('miao')
a = Animal()
a.shout()
c =Cat()
c.shout()
print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)
{}
{}
{'__dict__': <attribute '__dict__' of 'Animal' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None, 'shout': <function Animal.shout at 0x000000E832DF4D08>}
{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x000000E832DF4BF8>}
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
#覆蓋了父類的方法
def shout(self):
print('miao')
#覆蓋本身的方法,呼叫父類的方法
def shout(self):
print(super())
print(super(Cat,self))
super().shout()
super(Cat,self).shout() #等價於super()
# self.__class__.__bases__.shout(self)
a = Animal()
a.shout()
c =Cat()
c.shout()
print(a.__dict__)
print(c.__dict__)
print(Animal.__dict__)
print(Cat.__dict__)
Animal shouts
<super: <class 'Cat'>, <Cat object>>
<super: <class 'Cat'>, <Cat object>>
Animal shouts
Animal shouts
{}
{}
{'__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Animal' objects>, 'shout': <function Animal.shout at 0x0000001852BD4D08>, '__doc__': None, '__dict__': <attribute '__dict__' of 'Animal' objects>}
{'__module__': '__main__', '__doc__': None, 'shout': <function Cat.shout at 0x0000001852BDB0D0>}
Super()可以訪問到父類的屬性。
class Animal:
@classmethod
def class_method(cls):
print('class_method_animal')
@staticmethod
def static_method():
print('statice_method_animal')
class Cat(Animal):
@classmethod
def class_method(cls):
print('class_method_cat')
@staticmethod
def static_method():
print('statice_method_animal')
c = Cat()
c.class_method()
c.static_method()
class_method_cat
statice_method_animal
這些都可以進行被覆蓋,原因是字典的搜尋順序。
5、繼承中的初始化
上圖中類B定義時候生命繼承自類A,則在類B中__base__中是可以看到A的
但是這是和呼叫類A的構造方法是兩回事。
如果B中呼叫了A的構造方法,就可以擁有父類的屬性了。
class A:
def __init__(self,a,b=1):
self.a = a
self.__b = b
class B(A):
def __init__(self,b,c):
A.__init__(self,b + c,b -c )
self.b = b
self.c = c
def printv(self):
print(self.b)
print(self.a)
f = B(4,5)
print(f.__dict__)
print(f.__class__.__bases__)
f.printv()
{'c': 5, 'a': 9, '_A__b': -1, 'b': 4}
(<class '__main__.A'>,)
4
9在B中呼叫了父類的__init__函式。 如果父類中定義了__init__,子類中就應該呼叫他。
如果利用自動呼叫父類的__init__方法呢。
class A:
def __init__(self):
self.a1 = 'a1'
self.__a2 = 'a2'
class B:
pass
b =B()
print(b.__dict__)
子類沒有自己的屬性和方法,直接繼承。。。會自動呼叫的。
此種類型不會自動呼叫,需要手動處理呼叫:
class A:
def __init__(self):
self.a1 = 'a1'
self.__a2 = 'a2'
print('Ainit')
class B(A):
def __init__(self):
self.b1 ='b1'
print('B init')
b =B()
print(b.__dict__)
B init
{'b1': 'b1'}
正確初始化。
class A:
def __init__(self,age):
print('Ainit')
self.age = age
def show(self):
print(self.age)
class B(A):
def __init__(self,age,weight):
print('Binit')
self.age = age +1
self.weight = weight
super().__init__(age)
b = B(10,5)
b.show()
呼叫父類的__init__方法,出現在不同的位置,導致出現的結果不同。
class A:
def __init__(self,age):
print('Ainit')
self.__age = age
def show(self):
print(self.__age)
class B(A):
def __init__(self,age,weight):
print('Binit')
self.__age = age +1
self.__weight = weight
super().__init__(age)
b = B(10,5)
b.show()
打印出10,原因在__dict__,父類A的show方法中__age h會被解釋為_A__age因此顯示的就是10,而不是11.
自己私有的屬性,就該自己讀取和修改,不要藉助其他類的方法,即使是父類或者派生類。
繼承中的初始化
(繼承加覆蓋就是多型。)
二、多繼承
1、Python不同的版本的類
Python2.2之前是沒有共同祖先的,之後,引入object的類,他是所有類的共同 的祖先object。
Python2中為了相容,分為古典類(舊式類)和新式類。
Python3中全部都是新式類。
新式類都是繼承自object的,新式類可以使用super。
2、Ocp原則
多用繼承,少用修改。
繼承的用途,增強基類,實現多型。
多型:
在面向物件中,父類,子類通過繼承聯絡在一起,如果通過一套方法,就可以實現不同的表現,就是多型。
一個類繼承自多個類就是多繼承,他將會有多個類的特徵。
3、多繼承弊端
多繼承很好的模擬了世界,因為事物很少是單一繼承的,但是捨棄簡單,必然引入複雜性,帶來了衝突。
如同一個孩子繼承了來自父母雙方的特徵,那麼到底眼睛像爸爸還是媽媽呢,孩子究竟該誰多一點呢。
多繼承的實現會導致編譯器設計的複雜度增加,所以很多語言捨棄了類的多繼承。
C++支援多繼承,JAVA也捨棄了多繼承。
Java中,一個類可以實現多個介面,一個介面也可以繼承多個解耦,Java的介面很純粹,只是方法的生命。繼承者必須實現這些方法,就具有了這些能力,就能幹什麼。
多繼承可能會帶來二義性,例如,貓和狗都繼承自動物類,現在如果一個類多繼承了貓和狗類,貓和狗都有shout方法,子類究竟繼承誰的shout呢。
解決的方案。
實現多繼承的語言,要解決二義性,深度優先或者廣度優先。
4、Python多繼承實現。
Class ClassName(基類列表)
類體
左圖是多繼承,右圖是單一繼承。
多繼承帶來路徑選擇問題,究竟繼承哪個父類的特徵呢。
Python使用MRO(method resolution order)解決基類搜尋順序問題。
歷史原因:MRO有三個搜尋演算法:
經典演算法,按照定義從左到右,深度優先策略,2.2之前左圖的MRO是MYclass dbaca
新式演算法,經典演算法的升級,重複的是隻保留最後一個,2.2左圖的mro是myclassd,b,c,a,object
C3演算法,在類被創建出來的時候,就計算出一個MRO有序列表,2.3之後,Python3唯一支援的演算法。
左圖中MRO是myclass,d,b,c,a,object的列表。
C3演算法解決了多繼承的二義性。
5、多繼承的缺點
當類很多,繼承複雜的情況下,繼承路徑太多,很難說清什麼樣的繼承路徑。
Python語法是允許很多繼承的,但Python程式碼是解釋執行的,只有執行到的時候,才會發現錯誤。
團隊協作開發,如果引入多繼承,name程式碼將會不可控。
不管程式語言是否支援多繼承,都應避免使用多繼承。
6、Mixin類 (繼承加覆蓋等於多型。)
類有下面的繼承關係。
本質上是:多繼承的來實現,體現的是組合的設計模式。
文件Document類是其他所有文件類的抽象基類;
Word PDF類是Document的子類。
為document子類提供列印能力:
方法:
1)在Document中提供print方法。
<