用類儲存資料
類實際上就是一個資料結構,對於python而言,它是一個類似於字典的結構。當根據類建立了物件之後,這個物件就有了一個資料結構,包含一些賦值了的屬性。在這一點上,它和其它語言的struct的作用是類似的:儲存資料並提供資料檢索功能。
例如,下面是史上最簡單的類:
class Person: pass
pass關鍵字表示這個類沒有實際的邏輯體。這裡只是定義了一個類,這個類的物件初始化時不會存放任何資料。現在,構造一個物件,讓它和dict一樣存放一些資料:
p = Person() # 構造物件
p.name = "longshuai" # 建立物件的屬性name
p.age = 23 # 建立物件的屬性age
現在,Person的例項物件p中就存放了兩個屬性:p.name和p.age。可以直接去檢索存放在p中的資料:
print(p.name) # 輸出"longshuai"
print(p.age) # 輸出23
也可以使用dict來儲存這些資料:
>>> d={}
>>> d["name"]="longshuai"
>>> d["age"]=23
>>> print(d["name"])
longshuai
>>> print(d["age"])
23
在資料儲存方面,它們的作用是完全等價的。實際上物件/類在內部就是使用一個名為__dict__
的dict型別來存放它所擁有的資料的。
>>> p.__dict__
{`name`: `longshuai`, `age`: 23}
__init__()構造物件初始資料
上面的name和age屬性是在構建了物件之後附加上去的,如果想要建立物件時就存放好資料,可以定義類的建構函式__init__()
。例如:
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
然後建立物件的時候,傳遞name引數和age引數即可。
>>> p = Person("longshuai",23)
>>> p.__dict__
{`name`: `longshuai`, `age`: 23}
如果想定義這個類公有的資料,可以將公有屬性定義為類的屬性。比如中國人都是黃皮膚:
class Person:
skin = "yellow"
def __init__(self,name,age):
self.name = name
self.age = age
這樣每次建立Person的物件例項時,每個物件都會有相同的膚色:yellow。但注意,這個skin屬性是類屬性,不是物件屬性,它是存放在類的名稱空間中的。當物件真的需要這個屬性的時候,會臨時去檢索類的名稱空間來獲取。看下面的__dict__
字典即可知道:
>>> p = Person("longshuai",23)
>>> p.__dict__
{`name`: `longshuai`, `age`: 23}
>>> p.skin
`yellow`
但注意,按照物件導向的封裝原則,在類中定義類變數屬性是不合理的,因為要在外部訪問它需要通過x.y
的方式,這意味著開啟了封裝好的”黑匣子”,暴露了屬性。除非真的有需要,否則可以將類變數的定義放進建構函式__init__()
中,這樣每個初始化的物件都會有該屬性。
setter和getter方法
在物件導向的角度上考慮,一般是不建議直接在類的外部通過x.name
的方式賦值、取值的。而是定義對應的方法,通過方法來取得對應的值。這兩類方法稱為setter、getter方法:setter用於賦值或設定屬性值,getter用於取得屬性值。
class Person:
skin = "yellow"
def __init__(self,name,age):
self.name = name
self.age = age
def set(self,job):
self.job = job
return self
def get(self):
return self.name,self.age,self.job
上面的set方法用於設定一個新屬性job。get用於返回物件的3個屬性。
>>> p = Person("longshuai",23)
>>> p.set("manager")
>>> name, age, job = p.get()
>>> print([name,age,job])
[`longshuai`, 23, `manager`]
需要注意,setter方法可以有多種型別的返回值,常用的有4種:
- 返回設定後的值
- 返回設定前的值
- 返回物件自身
- 返回布林值,表示是否設定成功
這4種返回值都很常見,特別是第三種用來串聯物件方法的時候非常好用。修改上面的set方法:
class Person:
skin = "yellow"
def __init__(self,name,age):
self.name = name
self.age = age
def set(self,job):
self.job = job
return self
def get(self):
return self.name,self.age,self.job
上面的set()返回self物件自身。於是串聯set()和get():
>>> p = Person("longshuai",23)
>>> name,age,job = p.set("manager").get()
無論使用何種返回值方式,都不會真正影響程式的使用。但使用合理的返回值型別,可能會簡化程式碼的編寫。另外,決定了返回值的方式後,就不要再去修改,因為很可能會牽一髮而動全身。
上面的getter返回了多個值,但一般來說getter只返回一個對應的屬性。比如getname()返回name屬性,getage()返回age屬性等。這樣需要定義多個getter方法。
def get_name(self):
return self.name
def get_age(self):
return self.age
def get_job(self):
return self.job
合併setter和getter
很多時候可以合併setter和getter方法。合併的方式是判斷方法的引數,如果呼叫方法的時候給了引數,就表示setter,沒有給定引數,就表示是getter。
例如,對於job屬性:
def set_get_job(self, job=None):
if job:
self.job = job
else:
return self.job
現在可以以給引數和不給引數兩種不同的方式來呼叫set_get_job()方法:
p = Person("longshuai", 23)
p.set_get_job("manager") # 給了引數,說明是setter
job = p.set_get_job() # 沒給引數,說明是getter
python的屬性管理
上面解釋了各種setter、getter的方式,還解釋了將它們進行合併。
實際上在python中訪問、設定、刪除物件屬性的時候,大概有以下幾種方式:
- 使用內建函式getattr()、setattr()和delattr()
- 自己編寫
getter()
、setter()
、deleter()
方法 - 過載
__getattr__()
、__setattr__()
、__delattr__()
運算子,這決定了x.y
的訪問、賦值方式以及del x.y
的方式 - 使用
__getattribute__()
方法 - 使用描述符協議
- 使用property協議,它是一種特殊的描述符
這些還未介紹到的屬性管理方式,在後面的文章中會逐漸展開解釋。
總結
本文介紹了各種設定物件屬性的方式,屬性其實就是資料,物件/類就是屬性的容器,這一點很重要。我最開始學java的物件導向時,雖然對類和物件有那些教科書式的理解,但始終沒有感受到類/物件其實就是一種用來儲存資料的資料結構。直到學習了Python/Perl,我才意識到這一點,然後理解物件導向就容易的多了。