目錄
一.類和類的實例
二.類屬性和實例屬性
三.私有屬性和公有屬性
四.靜態方法和類方法
?五.__init__方法,__new__方法和__del__方法:
六.私有方法和公有方法
七.方法的重載
八.方法的繼承
九.方法的重寫
十.對象的特殊方法
十一.對象的引用,淺拷貝和深拷貝
一.類和類的實例
類(Class):用來描述具有相同的屬性和方法的對象的集合。定義了該集合中每個對象所共有的屬性和方法。
類的實例:每個對象都屬于特定的類,并被稱為該類的實例(類的具體實體)。
看點實際的:
class Person1: #定義類Person1pass # 類體為空語句
print(Person1, type(Person1), id(Person1))結果:
<class '__main__.Person1'> <class 'type'> 2077119090192
Person1:表示類對象的名稱,屬于"__main__"模塊
type(Person1):表示Person1一個類
id(Person1):表示Person1類的唯一標識符,用來區別其他對象
class Person1: #定義類Person1pass # 類體為空語句
p1 = Person1() #實例化該類并創建一個對象p1
print(p1, type(p1), id(p1))結果:
<__main__.Person1 object at 0x0000026DD4176DD0> <class '__main__.Person1'> 2670732996048
p1:p1是Person1類的一個對象,屬于“__main__”模塊,其內存地址為0x0000026DD4176DD0
type(p1):表示p1所屬的類是Person1,屬于“__main__”模塊
id(p1):表示p1對象的唯一標識符
二.類屬性和實例屬性
類變量(屬性):類變量在整個實例化的對象中是公用的,定義在類中且在函數體之外,通常不作為實例變量使用,屬于類本身,可以通過類名訪問/修改
class Person2:count = 0 #定義屬性count,表示計數name = "Person" #定義屬性name,表示名稱def __init__(self):passdef fun(self):pass
#測試代碼
Person2.count += 1 #通過類名訪問,計數加1
print(Person2.count) #類名訪問,讀取并顯示類屬性
print(Person2.name) #類名訪問,讀取并顯示類屬性結果:
1
Person
實例變量:在類的聲明中,屬性是用變量來表示的,定義在方法內,比如定義到構造方法中,普通的方法內,通過self.變量名定義的屬性。
class Person3: #定義類Person3def __init__(self, name,age): #__init__方法self.name = name #初始化self.name,self.age = age #初始化self.age,def say_hi(self): #定義類Person3的函數say_hi()print('您好, 我叫', self.name)
#測試代碼
p1 = Person3('張三',25) #對象實例化
p1.say_hi () #調用對象的方法
print(p1.age) #通過p1.age(obj1.變量名)讀取成員變量age結果:
您好,我叫張三
25
改變類屬性值或實例變量值
class Person4:count = 0 #定義屬性count,表示計數name = "Person" #定義屬性name,表示名稱
#測試代碼
p1 = Person4() #創建實例對象1
p2 = Person4() #創建實例對象2
print((p1.name, p2.name)) #通過實例對象訪問實例變量
Person4.name = "雇員" #通過類變量訪問,設置類屬性值
print((p1.name, p2.name)) #讀取實例變量
p1.name = "員工" #通過實例變量訪問,設置實例變量的值
print((p1.name, p2.name)) #讀取實例變量的值
結果:
三.私有屬性和公有屬性
Python類的成員沒有訪問控制限制
約定兩個下劃線開頭,但不以兩個下劃線結束的屬性是私有的(private),其他為公共的(public)
class A:def __init__(self):self.__name = 'class A' #私有類屬性def get_name(self):print(self.__name) #在類方法中訪問私有類屬性
#測試代碼
Aa = A()
Aa.get_name()
Aa.__name #導致錯誤,不能直接訪問私有類屬性
結果:
@property
面向對象程序設計的封裝性原則要求不直接訪問類中的數據成員,@property裝飾器裝飾訪問私有屬性的方法,使訪問方式更友好。
class Person1:def __init__(self, name):self.__name = name@propertydef name(self):return self.__name
#測試代碼
p = Person1('王五')
print(p.name)結果:
王五
@property裝飾器默認提供一個只讀屬性,可使用對應的getter、setter和deleter裝飾器實現其他訪問器函數,@property裝飾器會將方法轉換為相同名稱的只讀屬性,可以與所定義的屬性配合使用,這樣可以防止屬性被修改。
class Person12:def __init__(self, name):self.__name = name@propertydef name(self):return self.__name@name.setterdef name(self, value):self.__name = value@name.deleterdef name(self):del self.__name
#測試代碼
p = Person12('姚六')
p.name = '王依依'
print(p.name)結果:
王依依
property的調用格式
property(fget=None, fset=None, fdel=None, doc=None) #fget為get訪問器,fset為set訪問器,fdel為del訪問器
class Person1:def __init__(self, name):self.__name = namedef getname(self):return self.__namedef setname(self, value):self.__name = valuedef delname(self):del self.__namename = property(getname, setname, delname, "I'm the 'name' property.")
#測試代碼
p = Person1('張三')
print(p.name)
p.name = '李四'
print(p.name)結果:
張三
李四
Python對象包含許多以雙下劃線開始和結束的變量,稱為特殊屬性?:
特殊方法 | 含義 | 示例 |
object.__dict__ | 對象的屬性字典 | int.__dict__ #mappingproxy({'__new__': <built-in method __new__... |
instance.__class__ | 對象所屬的類 | i.__class__ #<class ‘int’> |
int.__class__ #<class ‘type’> | ||
class.__bases__ | 類的基類元組 | int.__bases__ #(<class ‘object’>) |
class.__base__ | 類的基類 | int.__base__ #<class ‘object’> |
class.__name__ | 類的名稱 | int.__name__ # ‘int’ |
class.__qualname__ | 類的限定名稱 | int.__qualname__ #’int’ |
class.__mro__ | 查看繼承關系,基類元組 | int.__mro__ #(<class ‘int’>, <class ‘object’>) |
class.mro() | 同上,可被子類重寫 | int.mro()#[<class ‘int’>, <class ‘object’>] |
class.__subclasses__() | 子類列表 | int.__subclasses__()#[<class ‘bool’>,<enum ‘IntEnum’>,... |
自定義屬性:
對象可以通過特殊屬性__dict__存儲自定義屬性:
class C1:pass
o=C1()
o.name='custom name'
print(o.__dict__)
結果:
攔截屬性的訪問:
可通過重載__getattr__和__setattr__攔截對成員的訪問,從而自定義屬性的行為
__getattr__只有在訪問不存在的成員時才會調用
__getattribute__攔截所有(包括不存在的成員)的獲取操作
注意:不要使用return self.__dict__[name]返回結果,因為self.__dict__[name]同樣會被__getattribute__攔截,造成無限遞歸死循環
__getattr__(self, name) #獲取屬性,比__getattribute__()優先調用
__getattribute__(self, name)# 獲取屬性
__setattr__(self, name, value)#設置屬性
__delattr__(self,name) #刪除屬性
class CustomAttribute(object):def __init__(self): #__init__方法(構造函數)pass #空語句def __getattribute__(self, name): #獲取屬性,攔截所有的獲取操作return str.upper(object.__getattribute__(self, name))#轉換為大寫def __setattr__(self, name, value): #設置屬性object.__setattr__(self, name, str.strip(value))#去除收尾空格
#測試代碼
o = CustomAttribute() #創建實例對象
o.firstname=' mary ' #設置成員變量的值
print(o.firstname) # 讀取并顯示成員變量的值
?結果:
四.靜態方法和類方法
靜態方法
?聲明與類的對象實例無關的方法
?靜態方法不對特定實例進行操作,訪問對象實例會導致錯誤
?靜態方法通過裝飾器@staticmethod來定義
?靜態方法一般通過類名來訪問,也可以通過對象實例調用
靜態方法的聲明和調用
#聲明
@staticmethod
def 靜態方法名([形參列表])
? ? ? ? 函數體
#調用
類名.靜態方法名([實參列表])
class TemperatureConverter:@staticmethoddef c2f(t_c): #攝氏溫度到華氏溫度的轉換t_c = float(t_c)t_f = (t_c * 1.8) + 32return t_f@staticmethoddef f2c(t_f): #華氏溫度到攝氏溫度的轉換t_f = float(t_f)t_c = (t_f - 32) /1.8return t_c
#測試代碼
print("1. 從攝氏溫度到華氏溫度.")
print("2. 從華氏溫度到攝氏溫度.")
choice = int(input("請選擇轉換方向:"))
if choice == 1:t_c = float(input("請輸入攝氏溫度: "))t_f = TemperatureConverter.c2f(t_c)print("華氏溫度為: {0:.2f}".format(t_f))
elif choice == 2:t_f = float(input("請輸入華氏溫度: "))t_c = TemperatureConverter.f2c(t_f)print("攝氏溫度為: {0:.2f}".format(t_c))
else:print("無此選項,只能選擇1或2!")
類方法
?Python允許聲明屬于類本身的方法,即類方法
?類方法不對特定實例進行操作,訪問對象實例會導致錯誤
?類方法通過裝飾器@classmethod來定義
?第一個形式參數必須為類對象本身,通常為cls
類方法的聲明和調用
#聲明
@classmethod
def 類方法名(cls,[形參列表])
????????函數體
#調用
類名.類方法名([實參列表])?
?
class Foo:classname = "Foo"def __init__(self, name):self.name = namedef f1(self): #實例方法print(self.name)@staticmethoddef f2(): #靜態方法print("static")@classmethoddef f3(cls): #類方法print(cls.classname)
#測試代碼
f = Foo("李")
f.f1()
Foo.f2()
Foo.f3()
結果:
?五.__init__方法,__new__方法和__del__方法:
?__init__方法即構造函數(構造方法),用于執行類實例的初始化。創建完對象后調用,初始化當前對象的實例,無返回值
?__new__方法是一個類方法,創建對象時調用,返回當前對象的一個實例,一般無需重載該方法
#__init__方法
class Point:def __init__(self, x = 0, y = 0): #構造函數self.x = xself.y = y
p1 = Point() #創建對象
print("p1({0},{1})".format(p1.x, p1.y))
p1 = Point(5, 5) #創建對象
print("p1({0},{1})".format(p1.x, p1.y))結果:
p1(0,0)
p1(5,5)
?__del__方法:
__del__方法即析構函數(析構方法),用于實現銷毀類的實例所需的操作,如釋放對象占用的非托管資源(例如:打開的文件、網絡連接等)
默認情況下,當對象不再被使用時,__del__方法運行,由于Python解釋器實現自動垃圾回收,即無法保證這個方法究竟在什么時候運行
通過del語句,可以強制銷毀一個對象實例,從而保證調用對象實例的__del__方法
class Person:count = 0 #定義類域count,表示計數def __init__(self, name,age): #構造函數self.name = name self.age = age Person.count += 1 #創建一個實例時,計數加1def __del__(self): #析構函數Person.count -= 1 #銷毀一個實例時,計數減1def get_count(): #定義類Person的方法get_count()print('總計數為:', Person.count)
print('總計數為:',Person.count) #類名訪問
p1 = Person('張三',25) #創建對象
Person.get_count() #通過類名訪問
p2 = Person('李四',28) #創建對象
Person.get_count() #通過類名訪問
del p1 #刪除對象p1
Person.get_count() #通過類名訪問
del p2 #刪除對象p2
Person.get_count() #通過類名訪問
結果:
六.私有方法和公有方法
?兩個下劃線開頭,但不以兩個下劃線結束的方法是私有的(private),其他為公共的(public)
?以雙下劃線開始和結束的方法是Python的專有特殊方法。
?不能直接訪問私有方法,但可以在其他方法中訪問
class Book:def __init__(self, name, author, price):self.name = nameself.author = authorself.price = pricedef __check_name(self): #定義私有方法,判斷name是否為空if self.name == '' : return Falseelse: return Truedef get_name(self): #定義類Book的方法get_nameif self.__check_name():print(self.name,self.author) #調用私有方法else:print('No value')
b = Book('Python語言程序設計','嵩天',50.0) #創建對象
b.get_name() #調用對象的方法
b.__check_name()
結果:
七.方法的重載
?其他程序語言方法可以重載,即定義多個重名的方法,而方法簽名唯一(方法名、參數數量和參數類型)
?Python本身是動態語言,方法的參數沒有聲明類型(在調用傳值時確定參數的類型),參數的數量可變。故Python對象方法不需要重載,定義一個方法即可實現多種調用,從而實現相當于其他程序設計語言的重載功能
class Person21: #定義類Person21def say_hi(self, name=None): #定義類方法say_hiself.name = name if name==None: print('您好! ')else: print('您好, 我叫', self.name)
p21 = Person21() #創建對象
p21.say_hi() #調用對象的方法,無參數
p21.say_hi('威爾遜') #調用對象的方法,帶參數
結果:
再看以下代碼:
在Python類體中可以定義多個重名的方法,雖然不會報錯,但只有最后一個方法有效,所以建議不要定義重名的方法
class Person22:def say_hi(self, name): #帶兩個參數print('您好, 我叫', self.name)def say_hi(self, name, age): #帶三個參數print('hi, {0}, 年齡:{1}'.format(name,age))
p22 = Person22() #創建對象
p22.say_hi('Lisa', 22) #調用對象的方法
p22.say_hi('Bob') # error
結果:
?
八.方法的繼承
?派生類:Python支持多重繼承,即一個派生類可以繼承多個基類
?如果類定義中沒有指定基類,默認基類為object。object是所有對象的根基類
?聲明派生類時,必須在其構造函數中調用基類的構造函數
派生類的聲明和調用:
#聲明
class 派生類名(基類1,[基類2...]):
? ? ? ? 類體
#調用
基類名.__init__(self, 參數列表)
或者:
super().__init__(參數列表)?
class Person: #基類def __init__(self, name, age): #構造函數self.name = nameself.age = agedef say_hi(self): #定義基類方法say_hiprint('您好, 我叫{0}, {1}歲'.format(self.name, self.age))
class Student(Person): #派生類def __init__(self, name, age, stu_id): #構造函數Person.__init__(self, name, age) #調用基類構造函數self.stu_id = stu_id #學號def say_hi(self): #定義派生類方法say_hiPerson.say_hi(self) #調用基類方法say_hiprint('我是學生, 我的學號為:', self.stu_id)
p1 = Person('張王一', 33) #創建對象
p1.say_hi()
s1 = Student('李姚二', 20, '2018101001') #創建對象
s1.say_hi()
?結果:
通過類的方法mro()或類的屬性__mro__可以輸出其繼承的層次關系
>>> class A: pass
>>> class B(A):pass
>>> class C(B):pass
>>> class D(A):pass
>>> class E(B,D):pass
>>> D.mro()
[<class '__main__.D'>, <class '__main__.A'>, <class 'object'>]
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
?
九.方法的重寫
通過繼承,派生類繼承基類中除構造方法之外的所有成員,如果在派生類中重新定義從基類繼承的方法,則派生類中定義的方法覆蓋從基類中繼承的方法
class Dimension: #定義類Dimensionsdef __init__(self, x, y): #構造函數self.x = x #x坐標self.y = y #y坐標def area(self): #基類的方法area()pass
class Circle(Dimension): #定義類Circle(圓)def __init__(self, r): #構造函數Dimension.__init__(self, r, 0)def area(self): #覆蓋基類的方法area()return 3.14 * self.x * self.x #計算圓面積
class Rectangle(Dimension): #定義類Rectangle(矩形)def __init__(self, w, h): #構造函數Dimension.__init__(self, w, h)def area(self): #覆蓋基類的方法area()return self.x * self.y #計算矩形面積
d1 = Circle(2.0) #創建對象:圓
d2 = Rectangle(2.0, 4.0) #創建對象:矩形
print(d1.area(), d2.area()) #計算并打印圓和矩形面積
結果:
十.對象的特殊方法
特殊方法 | 含義 |
__lt__、__add__等 | 對應運算符<,+等 |
__init__、__del__ | 創建或銷毀對象時調用 |
__len__ | 對應內置函數len() |
__setitem__、__getitem__ | 按索引賦值、取值 |
__repr__(self) | 對應于內置函數repr() |
__str__(self) | 對應于內置函數str() |
__bytes__(self) | 對應于內置函數bytes() |
__format__(self,format_spec) | 對應于內置函數format() |
__bool__(self) | 對應于內置函數bool() |
__hash__(self) | 對應于內置函數hash() |
__dir__(self) | 對應于內置函數dir() |
對象的特殊方法實例:?
class Person:def __init__(self, name, age): #特殊方法(構造函數)self.name = nameself.age = agedef __str__(self): #特殊方法return '{0}, {1}'.format(self.name,self.age)def __repr__(self): #特殊方法return '{},{}'.format('李四','24')
#測試代碼
p1 = Person('張三', 23)
print(p1)
print(repr(p1))結果:
張三,23
李四,24
運算符的重載與對象的特殊方法
Python的運算符實際上是通過調用對象的特殊方法實現的
運算符 | 特殊方法 | 含義 |
<,<=,==,>,>=,!= | __lt__(),__le__(),__eq__(),__gt__() ,__ge__(),__ne__() | 比較運算符 |
|,^,& | __or__(),__ror__(),__xor__(), __rxor__(),__and__(),__rand__() | 按位或、異或、與 |
|=,^=,&= | __ior__(),__ixor__(),__iand__() | 按位復合賦值運算 |
<<,>> | __lshift__(),__rlshift__(),__rshift__(),__rrshift__() | 移位運算 |
<<=,>>= | __ilshift__(),__irlshift__(),__irshift__(),__irrshift__() | 移位復合賦值運算 |
+,- | __add__(),__radd__(),__sub__(),__rsub__() | 加法與減法 |
+=,-+ | __iaddr__(),__isub__() | 加減復合賦值運算 |
*,/,%,// | __mul__(),__rmul__(),__truediv__(),__rtruediv__(), __mod__(),__rmod__(),__floordiv__(),__rfloordiv__() | 乘、除、取余、整數除法 |
*=,/=,%=.//= | __imul__(),__idiv__(),__itruediv__(),__imod__(),__ifloordiv__() | 乘除復合賦值運算 |
+x,-x | __pos__(),__neg__() | 正負號 |
~x | __invert__() | 按位翻轉 |
**, **= | __pow__(),__rpow__(),__ipow__() | 指數運算 |
示例:
class MyList():def __init__(self, *args):self.__mylist = [] #初始化私有屬性,空列表for i in args:self.__mylist.append(i)def __add__(self, other): #重載運算符"+",每個元素增加nfor i in range(len(self.__mylist)):self.__mylist[i] += otherdef __sub__(self, other): #重載運算符"-",每個元素減少nfor i in range(len(self.__mylist)):self.__mylist[i] -= otherdef __mul__(self, other):for i in range(len(self.__mylist)):self.__mylist[i] *= otherdef __truediv__(self, other):for i in range(len(self.__mylist)):self.__mylist[i] /= otherdef __len__(self):return len(self.__mylist)def __repr__(self):str1 = ''for i in range(len(self.__mylist)):str1 += str(self.__mylist[i])+' 'return str1
alist = MyList(1,2,3,4,5);
alist + 2;
print(repr(alist));
alist - 1;
print(repr(alist));
alist * 2;
print(repr(alist));
alist/2;print(repr(alist));
print(len(alist))
@functools.total_ordering:
支持大小比較的對象需要實現特殊方法:__eq__、__lt__、__le__、__ge__、__gt__,但使用functools模塊的total_ordering裝飾器裝飾類,則只需要實現__eq__,以及__lt__、__le__、__ge__、__gt__中的任意一個
import functools
@functools.total_ordering
class Student:def __init__(self, firstname, lastname): #姓和名self.firstname = firstnameself.lastname = lastnamedef __eq__(self, other): #判斷姓名是否一致return ((self.lastname.lower(), self.firstname.lower()) ==(other.lastname.lower(), other.firstname.lower()))def __lt__(self, other): #self姓名<other姓名return ((self.lastname.lower(), self.firstname.lower()) <(other.lastname.lower(), other.firstname.lower()))
#測試代碼
if __name__ == '__main__':s1 = Student('Mary','Clinton')s2 = Student('Mary','Clinton')s3 = Student('Charlie','Clinton')print(s1==s2)print(s1>s3)print(s1<s3)
結果:
__call__方法:
__call__方法的對象稱之為可調用對象(callable),即該對象可以像函數一樣被調用
class GDistance: #類:自由落體距離def __init__(self, g): #構造函數self.g = g def __call__(self, t): #自由落體下落距離return (self.g*t**2)/2
#測試代碼
if __name__ == '__main__':e_gdist = GDistance(9.8) #地球上的重力加速度for t in range(11): #自由落體0~10秒的下落距離print('下落距離為{:0.2f}m'.format(e_gdist(t)))
結果:
十一.對象的引用,淺拷貝和深拷貝
對象的引用:在創建一個對象并賦值給一個變量時,該變量是指向該對象的引用,其id()返回值保持一致
>>> acc10=['Charlie', ['credit', 0.0]]
#創建列表對象(信用卡賬戶),變量acc10代表主卡
>>> acc11=acc10
#變量acc11代表副卡,指向acc10(主卡)的對象
>>> id(acc10),id(acc11)
#二者id相同,輸出:
(2739033039112, 2739033039112)
對象的淺拷貝:
對象的拷貝可以使用以下方法
切片操作,例如,acc11[:]。
對象實例化,例如,list(acc11)。
copy模塊的copy函數,例如,copy.copy(acc1)。
import copy
acc1=['Charlie',['credit',0.0]]
acc2=acc1[:] #使用切片方式拷貝對象
acc3=list(acc1) #使用對象實例化方法拷貝對象
acc4=copy.copy(acc1) #使用copy.copy函數拷貝對象
print(id(acc1),id(acc2),id(acc3),id(acc4)) #拷貝對象id各不相同
acc2[0]='Mary' #acc2的第一個元素賦值,即戶主為'Mary'
acc2[1][1]=-99.9 #acc2的第二個元素的第二個元素賦值,即消費金額99.9
print(acc1, acc2) #注意,acc2消費金額改變99.9,acc1也隨之改變
結果:
對象的深拷貝:
Python復制一般為淺拷貝,即復制對象時對象中包含的子對象并不復制,而是引用同一個子對象。如果要遞歸復制對象中包含的子對象,需使用深拷貝
深拷貝需要使用copy模塊的deepcopy函數,拷貝對象中包含的子對象
>>> acc5[0]='Clinton' #acc5的第1個元素賦值,即戶主為'Clinton'
>>> acc5[1][1]=-19.9?? #acc5的第2個元素的第2個元素賦值,即消費金額19.9
>>> acc1,acc5
(['Charlie', ['credit', 0.0]], ['Clinton', ['credit', -19.9]])
>>> id(acc1),id(acc5),id(acc1[1]),id(acc5[1])
(2739033040648, 2739033040264, 2739033040520, 2739033039688)