定義類創建實例
舉個例子,
class Foo(object):
def __init__(self, x, y=0):
self.x = x
self.y = y
foo = Foo(1,y=2)
對Foo的調用到底調用了什麼函數或方法?
第一反應肯定是__init__方法,但仔細想想並不是正確答案,因爲它沒有返回一個對象,但是調用Foo(1,y=2)確實返回了一個對象,其實它的調用順序是這樣的:
- 第一步,Foo(1,y=2)等價於Foo.call(1,y=2)
- 第二步,既然Foo是一個type的實例,Foo.call(1,y=2)實際調用的是type.call(Foo,1,y=2)
- 第三步,type.call(Foo,1,y=2)調用type.new(Foo,1,y=2),然後返回一個對象。
- 第四步,obj隨後通過調用obj.init(1,y=2)被初始化。
- 第五步,obj被返回。
總的來說,__new__方法爲對象分配了內存空間,構建它爲一個“空"對象然後__init__方法被調用來初始化它。
讓我們來看看__new__方法,它負責實際對象的創建方法,分配內存空間並返回該對象。用它來實現單例模式是很方便的。
"""
__new__方法實現單例模式
"""
class A(object):
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
s1 = A()
s2 = A()
print(s1 is s2) # True
再來看,如何創建類的屬性,
class A(object): # 使用class定義類,所有的類都是從object類繼承
version = 1.0 # 類的屬性直接在類的內部定義,當實例屬性和類屬性重名時,實例屬性優先級高、
def __init__(self,a,b):
self.a = a
self.b = b
if __name__ == '__main__':
a = A(10,20)
print(A.version) # 直接通過類.屬性訪問
print(a.version) # 也可以通過實例.屬性訪問
# 類的屬性可以動態修改
A.version = '1.3'
print(A.version)
# 類的屬性一經修改,所有訪問的屬性值也隨之修改
print(a.version)
實例的創建
創建實例使用類名+(),類似函數調用的形式創建
class A(object):
pass
a = A() # 創建實例
a.name = 'frank' # 創建實例屬性
初始化實例屬性
class A(object):
version = 2.0 # 定義類屬性
def __init__(self,name,age): # self代表實例,通過self訪問實例對象的變量和函數
self.name = name
self.__age = age # 實例的私有屬性無法從外部訪問,從類的內部是可以訪問的
# 定義實例方法
def get_age(self):
return self.__age # 實例方法,定義在類內部,可以訪問實例的私有屬性__age
# 定義類方法
@classmethod
def how_many(cls): # 類方法的第一個參數爲cls,cls.version相當於A.version
return cls.version # 類方法中無法調用任何實例的變量,只能獲得類引用
p1 = A('frank',23)
print(p1.get_age()) # 實例方法的調用,self不需要顯式傳入
順帶引申一下單下劃線和雙下劃線的區別:
以單下劃線開頭(foo)的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,那麼以“”開頭的名稱都不會被導入,即不能用“from xxx import *”而導入,除非模塊或包中的“all”列表顯式地包含了它們;以雙下劃線開頭的(__foo)代表類的私有成員,只有類本身能訪問,其子類對象也不能訪問到這個數據。
“單下劃線” 開始的成員變量叫做保護變量,意思是隻有類對象和子類對象自己能訪問到這些變量;”雙下劃線” 開始的是私有成員,意思是隻有類對象自己能訪問,連子類對象也不能訪問到這個數據。
舉個例子,
class Foo(object):
def __init__(self):
pass
def public_method(self):
print('this is public method')
def __fullprivate_method(self):
print('this is full private method')
def _halfprivate_method(self):
print('this is half private method')
f = Foo()
print(f.public_method()) # OK
print(f._halfprivate_method()) # OK
# print(f.__fullprivate_method()) # AttributeError: 'Foo' object has no attribute '__fullprivate_method'
print(f._Foo__fullprivate_method()) # OK
f._halfprivate_method()可以直接訪問,根據python的約定,應該將其視作private,而不要在外部使用它們。
以單下劃線開頭_foo的代表不能直接訪問的類屬性,需通過類提供的接口進行訪問,不能用“from xxx import *”而導入;以雙下劃線開頭的__foo代表類的私有成員;以雙下劃線開頭和結尾的__foo__代表python裏特殊方法專用的標識,如 init()代表類的構造函數。
__init__構造函數
在定義一個類時,什麼時候用__init__函數,什麼時候不用,用不用有什麼區別?
首先__init__是爲了初始化用的,但是初始化的時候不一定要用這個,直接定義也是可以的,比如
class A(object):
test_a = '123'
而我們用__init__的好處在於可以接受任何參數並初始化
def __init__(self,a):
test_a = a
這樣類可以初始化一個動態的變量,更加靈活,直接test(‘123’)就將test_a初始化成123了
再舉一個例子,如下
# 不用init方法定義類
class Rectangle(object):
def getPeri(self,a,b):
return (a+b)*2
def getArea(self,a,b):
return a*b
rec = Rectangle()
print(rec.getPeri(3,4)) # 14
print(rec.getArea(3,4)) # 12
print(rec.__dict__) # {}
從上例可以看到,沒有定義__init__方法,也能得到周長和麪積,但是通過rec.__dict__來看這個實例的屬性,是空的,這就是沒有定義__init__的原因。
並且,在實例化對象時,rec=Rectangle()參數爲空,沒有指定a,b的值,只有在調用函數的時候才指定,且類中定義的每個方法的參數都有a,b,有點多餘了。
因此,需要在類中定義__init__方法,方便創建實例的時候,需要給實例綁定上屬性,也方便類中的方法定義。
上述同樣的例子,用__init__方法定義類,如下
# 使用__init__方法定義類
class Rectangle(object):
def __init__(self,a,b):
self.a = a
self.b = b
def getPeri(self):
return (self.a+self.b)*2
def getArea(self):
return self.a*self.b
rec = Rectangle(3,4)
print(rec.getPeri()) # 14
print(rec.getArea()) # 12
print(rec.__dict__) # {'a': 3, 'b': 4}
定義完init()方法後,創建的每個實例都有自己的屬性,也方便直接調用類中的函數。
最後一個要說的就是__init__,__new__執行順序的探索