李寧老師已經在「極客起源」 微信公衆號推出《Python編程思想》電子書,囊括了Python的核心技術,以及Python的主要函數庫的使用方法。讀者可以在「極客起源」 公衆號中輸入 160442 開始學習。
《Python編程思想》總目錄
目錄
在類體內定義的變量,默認屬於類本身。如果把類當成類命名空間,那麼該類變量其實就是定義在類命名空間內的變量。
1. 類變量和實例變量
在類命名空間內定義的變量就屬於類變量,Python可以使用類來讀取、修改類變量。例如,下面代碼定義了一個 Teacher類,併爲該類定義了多個類變量。
示例代碼:class_var.py
class Teacher :
name = '李寧'
salary = 66666.66
post_code = '12345678'
def print_info (self):
# 嘗試直接訪問類變量
# print(name) # 報錯
# 通過類來訪問類變量
print(Teacher.name) # 輸出 廣州
print(Teacher.post_code) # 輸出 510660
# 通過類來訪問Address類的類變量
print(Teacher.name)
teacher = Teacher()
teacher.print_info()
# 修改Teacher類的類變量
Teacher.name = '王軍'
Teacher.post_code = '87654321'
teacher.print_info()
在這段代碼中爲Teacher類定義了兩個類變量。
對於類變量而言,它們就是屬於在類命名空間內定義的變量,因此程序不能直接訪問這些變量,程序必須使用類名來調用類變量。不管是在全局範圍內還是函數內訪問這些類變量,都必須使用類名進行訪問。
當程序第1次調用 Teacher對象的print_info()方法輸出兩個類變量時,將會輸出這兩個類變量的初始值。接下來程序通過 Teacher類修改了兩個類變量的值,因此當程序第2次通過print_info方法輸出兩個類變量時,將會輸出這兩個類變量修改之後的值。
運行上面代碼,將會看到如下輸出結果:
李寧
李寧
12345678
王軍
87654321
實際上,Python完全允許使用對象來訪問該對象所屬類的類變量。看下面的程序:
示例代碼:class_instance_access_classvar.py
class Country:
# 定義兩個類變量
value1 = '中國'
value2 = 960
def print_info (self):
print('info方法中: ', self.value1)
print('info方法中: ', self.value2)
country = Country()
print(country.value1) # 中國
print(country.value2) # 960
country.print_info()
# 修改Country類的兩個類變量
Country.value1 = '美國'
Country.value2 = 1234
# 調用print_info()方法
country.print_info()
這段代碼的Country類中定義了兩個類變量,接下來程序完全可以使用 Country對象來訪問這兩個類變量。
在這段代碼的Country類的print_info方法中,程序使用self訪問 Country類的類變量,此時self代表print_info方法的調用者,也就是 Country對象,因此這是合法的。
在主程序代碼區,程序創建了 Country對象,並通過對象調用Country對象的value1和value2類變量,這也是合法的。
實際上,程序通過對象訪問類變量,其本質還是通過類名在訪問類變量。運行上面程序,將看到如下輸出結果:
中國
960
info方法中: 中國
info方法中: 960
info方法中: 美國
info方法中: 1234
由於通過對象訪問類變量的本質還是通過類名在訪問,因此如果類變量發生了改變,當程序訪問這些類變量時也會讀到修改之後的值。例如爲程序增加如下代碼(接前面的代碼),修改Country類的兩個類變量。
Country.value1='韓國'
Country.value2 = 250
# 調用info()方法
country.print_info()
上面程序修改了Country類的兩個類變量,然後通過對象調用print)info實例方法。運行上面代碼,將看到如下輸出結果。
info方法中: 韓國
info方法中: 250
從上面的輸出結果可以看到,通過實例訪問類變量的本質依然是通過類名在訪問。需要說明的是,Python允許通過對象訪問類變量,但如果程序通過對象嘗試對類變量賦值,此時性質就變了。Python是動態語言,賦值語句往往意味着定義新變量。因此,如果程序通過對象對類變量賦值,其實不是對“類變量賦值”,而是定義新的實例變量。
看下面的代碼:
實例代碼:new_class_var.py
class Product:
# 定義兩個類變量
name = 'iMac'
price = 11000
# 定義實例方法
def buy(self, name, price):
# 下面賦值語句不是對類變量賦值,而是定義新的實例變量
self.name = name
self.price = price
# 創建Product對象
product = Product()
product.buy('iPhone', 8000)
# 訪問product的name和price實例變量
print(product.name) # iPhone
print(product.price) # 8000
# 訪問Product的name和price類變量
print(Product.name) # iMac
print(Product.price) # 11000
Product.name = '類變量name'
Product.price = '類變量price'
# 訪問product的name和price實例變量
print(product.name)
print(product.price)
product.name = '實例變量name'
product.price = '實例變量price'
print(Product.name)
print(Product.price)
在這段代碼中通過實例對name和price變量賦值,看上去很像是對類變量賦值,但實際上不是,而是重新定義了兩個實例變量(如果第1次調用該方法)。
在這段diamante中調用 Product對象的 buy()方法之後,訪問 Product對象的name和price變量。由於該對象本身已有這兩個實例變量,因此程序將會輸出該對象的實例變量的值。接下來程序通過Product訪問它的name和price類變量,此時纔是真的訪問類變量。
運行上面程序,將看到如下輸出結果:
iPhone
8000
iMac
11000
iPhone
8000
類變量name
類變量price
如果程序通過類修改了兩個類變量的值,程序中 Product的實例變量的值也不會受到任何影響。例如如下代碼。
Product.name = '類變量name'
Product.price = '類變量price'
# 訪問product的name和price實例變量
print(product.name)
print(product.price)
運行上面代碼,可以看到如下輸出結果
iPhone
8000
上面程序開始就修改了Product類中兩個類變量的值,但這種修改對 Inventory對象的實例變量沒有任何影響。同樣,如果程序對一個對象的實例變量進行了修改,這種修改也不會影響類變量和其他對象的實例變量。例如如下代碼。
product.name = '實例變量name'
product.price = '實例變量price'
print(Product.name)
print(Product.price)
運行上面代碼,將會看到如下輸出結果。
類變量name
類變量 price
從上面的輸出結果來看,程序輸出的依然是之前對類變量所賦的兩個值。
2. 使用 property函數定義屬性
如果爲 Python類定義了getter和setter等訪問器方法,可使用 property函數將它們定義成屬性(相當於實例變量)。
property函數的語法格式如下:
property(fget=None, fset=None, fdel-None, doc=None)
從上面的語法格式可以看出,在使用 property函數時,可傳入4個參數,分別代表 getter方法、 setter方法、del方法和doc,其中doc是一個文檔字符串,用於說明該屬性。當然,開發者調用 property也可傳入0個(既不能讀,也不能寫的屬性)、1個(只讀屬性)、2個(讀寫屬性)、3個(讀寫屬性,也可刪除)和4個(讀寫屬性,也可刪除,包含文檔說明)參數。
例如,如下程序定義了一個 Rectangle類,該類使用 property函數定義了一個size屬性。
示例代碼:rectangle.py
class Rectangle:
# 定義構造方法
def __init__(self, width, height):
self.width = width
self.height = height
# 定義set_size()函數
def set_size (self , size):
self.width, self.height = size
# 定義getsize()函數
def get_size (self):
return self.width, self.height
# 定義getsize()函數
def del_size (self):
self.width, self.height = 0, 0
# 使用property定義屬性
size = property(get_size, set_size, del_size, '用於描述矩形大小的屬性')
# 訪問size屬性的說明文檔
print(Rectangle.size.__doc__)
# 通過內置的help()函數查看Rectangle.size的說明文檔
help(Rectangle.size)
rect = Rectangle(5, 6)
# 訪問rect的size屬性
print(rect.size) # (5, 6)
# 對rect的size屬性賦值
rect.size = 10, 12
# 訪問rect的width、height實例變量
print(rect.width) # 10
print(rect.height) # 12
# 刪除rect的size屬性
del rect.size
# 訪問rect的width、height實例變量
print(rect.width) # 0
print(rect.height) # 0
這段代碼使用 property函數定義了一個size屬性,在定義該屬性時一共傳入了4個參數,這意味着該屬性可讀、可寫、可刪除,也有說明文檔。所以,該程序嘗試對Rectangle對象的size屬性進行讀、寫、刪除操作,其實這種讀、寫、刪除操作分別被委託給 get_size()、 set_size()和 del_size()方法來實現。
運行上面程序,將會看到如下輸出結果:
用於描述矩形大小的屬性
Help on property:
用於描述矩形大小的屬性
(5, 6)
10
12
0
0
在使用 property函數定義屬性時,也可根據需要只傳入少量的參數。例如,如下代碼使用property函數定義了一個讀寫屬性,該屬性不能刪除。
class Person :
def __init__ (self, first, last):
self.first = first
self.last = last
def get_fullname(self):
return self.first + ',' + self.last
def set_fullname(self, fullname):
first_last = fullname.rsplit(',');
self.first = first_last[0]
self.last = first_last[1]
# 使用property()函數定義fullname屬性,只傳入2個參數
# 該屬性是一個讀寫屬性,但不能刪除
fullname = property(get_fullname, set_fullname)
p = Person('子龍', '趙')
# 訪問fullname屬性
print(p.fullname)
# 對fullname屬性賦值
p.fullname = '雲長,關'
print(p.first)
print(p.last)
在這段代碼中使用 property函數定義了 fullname屬性,該程序使用 property()函數時只傳入兩個參數,分別作爲 getter和 setter方法,因此該屬性是一個讀寫屬性,不能刪除。
運行這段代碼,將看到如下輸出結果:
子龍,趙
雲長
關
-----------------支持作者請轉發本文,也可以加李寧老師微信:unitymarvel,或掃描下面二維碼加微信--------
歡迎關注 極客起源 微信公衆號,更多精彩視頻和文章等着你哦!