python中類與物件的名稱空間(靜態屬性的陷阱)、__dict__ 和 dir() 在繼承中使用說明

2022-10-05 06:01:23

1. 物件導向的概念

  1)類是一類抽象的事物,物件是一個具體的事物;用類建立物件的過程,稱為範例化。

  2)類就是一個模子,只知道在這個模子裡有什麼屬性、什麼方法,但是不知道這些屬性、方法具體是什麼;

    所以,我們要在這個模子的基礎上 造出一個具體的範例(物件),這個範例就會具體化屬性、方法

  3)所有的資料型別都是類,都是抽象的;根據資料型別定義變數,該變數就是一個具體的值(物件)。

程式導向 --> 物件導向的轉變

  定義一個函數   《==》 定義一個類

  函數的返回值(字典)   《==》類的物件(類似字典,只不過呼叫方式發生了改變)

  函數的執行過程 《==》類的範例化

  請看 程式碼展示1 和 程式碼展示2

 1 # 用物件導向的思想、程式導向的語法 去實現 Rectangle的計算
 2 def Rectangle(length=0, width=0):
 3     self = {}  # 儲存屬性值
 4     def __init__(*args):  # 初始化函數 -- 完成對矩陣rectangle的長、寬初始化,以及面積、周長的呼叫方式
 5         self['length'] = args[0]
 6         self['width'] = args[1]
 7         self['area'] = area
 8         self['perimeter'] = perimeter
 9     def area():
10         return self['length'] * self['width']
11     def perimeter():
12         return 2 * (self['length'] + self['width'])
13     __init__(length, width)  # 呼叫初始化函數
14     return self
15 
16 rec_obj = Rectangle(10,5)   # rec_obj 相當於類的一個範例
17 print(rec_obj)      # rec_obj中存放了範例的屬性、方法,通過範例可以檢視屬性 與 呼叫方法
18 print('length:%s, width:%s, area:%s, perimeter:%s'\
19       %(rec_obj['length'], rec_obj['width'], rec_obj['area'](), rec_obj['perimeter']()))
程式碼展示1
 1 class Rectangle:
 2     temVar = 'over' #定義靜態屬性,共用於類中的每個物件
 3     def __init__(self, *args):  # 建立物件後執行的第一個函數,self就是類建立的物件,該函數返回類的物件self
 4         self.length = args[0]
 5         self.width = args[1]
 6     def area(self):
 7         return self.length * self.width
 8     def perimeter(self):
 9         return 2 * (self.length + self.width)
10 
11 rec_obj1 = Rectangle(10, 5) # 範例化一個具體物件
12 
13 # 通過 物件 檢視屬性(包括靜態屬性)與呼叫方法
14 print('length:%s, width:%s, area:%s, perimeter:%s'\
15       %(rec_obj1.length, rec_obj1.width, rec_obj1.area(), rec_obj1.perimeter()))
16 print(rec_obj1.temVar)  # 靜態屬性
17 
18 # 通過 類名 呼叫方法 、類中的靜態屬性
19 print('area:%s, perimeter:%s'%( Rectangle.area(rec_obj1), Rectangle.perimeter(rec_obj1)))
20 print(Rectangle.temVar)  # 靜態屬性
21 
22 # 通過物件名修改屬性(若self裡 存在該屬性,是修改;若self裡 不存在該屬性,是新增新屬性)
23 # rec_obj1.length = 20
24 # rec_obj1.temVar = 'object_over'  # 給物件中新增一個新屬性 'temVar': 'object_over'
25 # 通過類名修改屬性(若類裡 存在該屬性<靜態屬性>,是修改;若類裡 不存在該屬性,是新增新屬性,<靜態屬性>)
26 # Rectangle.length = 50   # 在類中新增一個新屬性 'length': 50
27 # Rectangle.temVar = 'class_over'
28 
29 # __dict__的使用
30 print(rec_obj1.__dict__)    # 檢視物件的所有屬性,即self屬性
31 print(Rectangle.__dict__)   # 檢視類的所有靜態屬性、方法
32 # __dict__ 對於 物件的 增刪改查操作都可以通過字典的語法進行
33 # __dict__ 對於 類中的名字只能看 不能操作
程式碼展示2
22 # 通過物件名修改屬性(若self裡 存在該屬性,是修改;若self裡 不存在該屬性,是新增新屬性)
23 # rec_obj1.length = 20
24 # rec_obj1.temVar = 'object_over' # 給物件中新增一個新屬性 'temVar': 'object_over'
25 # 通過類名修改屬性(若類裡 存在該屬性<靜態屬性>,是修改;若類裡 不存在該屬性,是新增新屬性,<靜態屬性>)
26 # Rectangle.length = 50 # 在類中新增一個新屬性 'length': 50
27 # Rectangle.temVar = 'class_over'

總結:

# 物件 = 類名()

# 範例化的過程:

  # 類名() -> 會創造出一個物件,即建立了一個self變數

    # 呼叫__init__(self)方法,類名括號裡的引數會被這裡接收

    # 執行__init__(self)方法

    # 返回self

# 物件能做的事:

  # 檢視屬性(自己的屬性 和 類中靜態屬性)

  # 呼叫方法

  # __dict__ 對於物件的增刪改查操作都可以通過字典的語法進行

# 類名能做的事:

   # 範例化

   # 呼叫類中的屬性,也就是呼叫靜態屬性  

   # 呼叫方法 : 只不過要自己傳遞self引數

   # __dict__ 對於類中的名字只能看 不能操作

2. 類與物件的關係(類與物件的名稱空間問題)

(1)類的名稱空間

  建立一個類,就會自動建立一個該類的名稱空間,在該名稱空間中儲存類的屬性(靜態屬性、動態屬性(方法));

  靜態屬性:直接在類中定義的變數;(靜態屬性屬於類,即屬於所有物件)

  動態屬性:在類中定義的函數;(動態屬性繫結到所有物件)

(2)物件的名稱空間

  範例化一個物件,就會自動建立一個該物件的名稱空間,在該名稱空間中存放物件的屬性;同時,在範例化之後,就後產生一個指向類物件指標,用來指向當前物件所屬類的名稱空間,這樣就可以存取類的靜態屬性與動態屬性。

  在物件尋找屬性的過程中,優先從物件的名稱空間中搜尋,然後去類的名稱空間中查詢,最後在父類別的名稱空間中查詢...,若沒有找到該屬性,程式就會丟擲異常。

  注:類與物件的名稱空間是獨立儲存的

 完整程式碼展示:

class Family:
    '''
    定義一個公共賬號 ,只要有人上班,就將錢存到這個賬號上
    '''
    share_money = 0  # 不可變資料型別做靜態屬性
    native_place = ['china']    # 可變資料型別做靜態屬性
    def __init__(self, role, name, salary):
        self.role = role
        self.name = name
        self.salary = salary

    def work(self):
        Family.share_money += self.salary   # 將每個的錢都存放到這個公共賬號上
        print('the account remains ¥%s '%Family.share_money)

member1 = Family('father', 'lilei', 1000)
member2 = Family('mother', 'zhanghua', 500)
member1.work()  # the account remains ¥1000
member2.work()  # the account remains ¥1500
member1.share_money = 200   # 為自己獨立開了個小金庫,並存入200元   -- 在物件member1中新增這一屬性
member1.share_money += 100  # 以後就可以在自己的小金庫中存放私房錢,即總金額=200+100=300
member2.share_money += 400  # 將公有賬號作為自己的私有賬號,並存入400元,即總金額=1000+500+400=1900
print(Family.share_money)   # 1000+500=1500
print(member1.share_money)  # 200+100=300
print(member2.share_money)  # 1000+500+400=1900

"""
可變資料型別做靜態屬性的影響:
Family.native_place = 'america'
# member1.native_place[0] = 'america' # 修改的是類中的native_place,會影響所有物件(同上)
# member2.native_place[0] = 'america' # 修改的是類中的native_place,會影響所有物件(同上)
print(member1.__dict__) 
print(member2.__dict__)
print(Family.__dict__)

{'role': 'father', 'name': 'lilei', 'salary': 1000, 'share_money': 300}
{'role': 'mother', 'name': 'zhanghua', 'salary': 500, 'share_money': 1900}
{'__module__': '__main__', '__doc__': '\n    定義一個公共賬號 ,只要有人上班,就將錢存到這個賬號上\n    ', 
'share_money': 1500, 'native_place': ['america'], '__init__': <function Family.__init__ at 0x0000021C360084C8>, 
'work': <function Family.work at 0x0000021C3629C048>, '__dict__': <attribute '__dict__' of 'Family' objects>, 
'__weakref__': <attribute '__weakref__' of 'Family' objects>}
"""

"""
可變資料型別做靜態屬性的影響:
member1.native_place = 'america'    # 重新賦值,在當前物件的名稱空間中新增這個屬性,不會影響其它物件
print(member1.__dict__) 
print(member2.__dict__)
print(Family.__dict__)

{'role': 'father', 'name': 'lilei', 'salary': 1000, 'share_money': 300, 'native_place': 'america'}
{'role': 'mother', 'name': 'zhanghua', 'salary': 500, 'share_money': 1900}
{'__module__': '__main__', '__doc__': '\n    定義一個公共賬號 ,只要有人上班,就將錢存到這個賬號上\n    ', 
'share_money': 1500, 'native_place': ['china'], '__init__': <function Family.__init__ at 0x000002E4747684C8>, 
'work': <function Family.work at 0x000002E4749FC048>, '__dict__': <attribute '__dict__' of 'Family' objects>, 
'__weakref__': <attribute '__weakref__' of 'Family' objects>}
"""

對類中靜態屬性存取規則:

(1)對於不可變資料型別來說,最好用類名操作靜態屬性;
  若用物件名操作靜態屬性,其修改 和 重新賦值 都是獨立的(獨立的:物件與類的名稱空間分開存放)
   1)若用物件名第一次修改靜態屬性,首先會到類的名稱空間中找到該靜態屬性的屬性值,然後在當前物件的名稱空間中再做修改
      2)若用物件名直接給靜態屬性重新賦值,那麼直接會在當前物件的名稱空間中新增這一屬性
(2)對於可變資料型別來說,用物件名修改是 共用的, 用物件名重新賦值是 獨立的
  因為修改的是指標變數所指向記憶體中的值,故是 共用的
  !!!總結操作靜態屬性,最好用類名操作靜態屬性;

補充:python中不可變資料型別可變資料型別

  不可變資料型別:對於相同的值對應的記憶體地址是不變的;

 1 a = 1
 2 b = 1
 3 c = 2
 4 d = a + b
 5 print(" id(a) = %d\n id(b) = %d\n id(c) = %d\n id(d) = %d\n"
 6       % (id(a), id(b), id(c), id(d)))
 7 
 8 """
 9  id(a) = 1461563616
10  id(b) = 1461563616
11  id(c) = 1461563648
12  id(d) = 1461563648
13 """

  可變的資料型別:對於相同值的記憶體地址是可變的;

 1 al = [1, 2, 3]
 2 bl = [1, 2, 3]
 3 print(" id(al) = %d\n id(bl) = %d\n" % (id(al), id(bl)))
 4 al.append(4)
 5 bl += [4]
 6 print(" id(al) = %d\n id(bl) = %d\n" % (id(al), id(bl)))
 7 print(" al:%s\n bl:%s\n" % (al, bl))
 8 
 9 """
10  id(al) = 2353965003720
11  id(bl) = 2353964735816
12 
13  id(al) = 2353965003720
14  id(bl) = 2353964735816
15 
16  al:[1, 2, 3, 4]
17  bl:[1, 2, 3, 4]
18 """

類屬性的補充

一:我們定義的類的屬性到底存到哪裡了?有兩種方式檢視
dir(類名):查出的是一個名字列表
類名.__dict__:查出的是一個字典,key為屬性名,value為屬性值

二:特殊的類屬性
類名.__name__   # 類的名字(字串)
類名.__doc__    # 類的檔案字串
類名.__base__   # 類的第一個父類別
類名.__bases__  # 類所有父類別構成的元組
類名.__dict__   # 類的字典屬性
類名.__module__ # 類定義所在的模組
類名.__class__  # 範例對應的類(僅新式類中)

 3 __dict__ 與 dir() 的使用

"""
物件名.__dict__:檢視物件的屬性(self物件中儲存的變數)
類名.__dict__:檢視類的屬性(在類中能看到的靜態屬性與動態屬性)

dir(物件名):檢視物件的所有屬性(此時包括self物件、類屬性、內建方法)
dir(類名):檢視類的所有屬性(不包括self物件)

注:在繼承中,子類名.__dict__中看不到父類別中的類屬性,但實際上包括父類別的類屬性
"""

案例分析

class Family:
    '''
    定義一個公共賬號 ,只要有人上班,就將錢存到這個賬號上
    '''
    share_money = 0  # 不可變資料型別做靜態屬性
    native_place = ['china']    # 可變資料型別做靜態屬性
    def __init__(self, role, name, salary):
        self.role = role
        self.name = name
        self.salary = salary

    def work(self):
        Family.share_money += self.salary   # 將每個人的錢都存放到這個公共賬號上
        print('the account remains ¥%s '%Family.share_money)

    def fun(self):
        pass

class NewFamily(Family):
    new_account = 0
    def __init__(self, role, name, salary, kind):
        super(NewFamily, self).__init__(role, name, salary)
        self.kind = kind

    def work(self):
        pass

    # 使用__定義私有屬性
    # python中不存在嚴格的私有屬性,在類的外部可通過正真的函數名【_類名__函數名,即 _NewFamily__expenditure】間接呼叫
    def __expenditure(self):
        pass


f = Family('father', 'lilei', 1000)
nf = NewFamily("son", "liwei", 2000, "salesman")

print("-"*20, "nf.__dict__ 與 f.__dict__ 對比", "-"*20)
print(f.__dict__)
print(nf.__dict__)
print(set(nf.__dict__) - set(f.__dict__))

print("-"*20, "NewFamily.__dict__ 與 Family.__dict__ 對比", "-"*20)
print(Family.__dict__)
print(NewFamily.__dict__)
print(set(NewFamily.__dict__) - set(Family.__dict__))

print("-"*20, "dir(nf) 與 dir(f) 對比", "-"*20)
print(dir(f))
print(dir(nf))
print(set(dir(nf)) - set(dir(f)))

print("-"*20, "dir(NewFamily) 與 dir(Family) 對比", "-"*20)
print(dir(Family))
print(dir(NewFamily))
print(set(dir(NewFamily)) - set(dir(Family)))

輸出結果:

"""
-------------------- nf.__dict__ 與 f.__dict__ 對比 --------------------
{'role': 'father', 'name': 'lilei', 'salary': 1000}
{'role': 'son', 'name': 'liwei', 'salary': 2000, 'kind': 'salesman'}
{'kind'}
-------------------- NewFamily.__dict__ 與 Family.__dict__ 對比 --------------------
{'__module__': '__main__', '__doc__': '\n    定義一個公共賬號 ,只要有人上班,就將錢存到這個賬號上\n    ', 'share_money': 0, 'native_place': ['china'], '__init__': <function Family.__init__ at 0x000001EC8159D288>, 'work': <function Family.work at 0x000001EC8159D318>, 'fun': <function Family.fun at 0x000001EC8159D3A8>, '__dict__': <attribute '__dict__' of 'Family' objects>, '__weakref__': <attribute '__weakref__' of 'Family' objects>}
{'__module__': '__main__', 'new_account': 0, '__init__': <function NewFamily.__init__ at 0x000001EC8159D438>, 'work': <function NewFamily.work at 0x000001EC8159D4C8>, '_NewFamily__expenditure': <function NewFamily.__expenditure at 0x000001EC8159D558>, '__doc__': None}
{'_NewFamily__expenditure', 'new_account'}
-------------------- dir(nf) 與 dir(f) 對比 --------------------
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun', 'name', 'native_place', 'role', 'salary', 'share_money', 'work']
['_NewFamily__expenditure', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun', 'kind', 'name', 'native_place', 'new_account', 'role', 'salary', 'share_money', 'work']
{'_NewFamily__expenditure', 'new_account', 'kind'}
-------------------- dir(NewFamily) 與 dir(Family) 對比 --------------------
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun', 'native_place', 'share_money', 'work']
['_NewFamily__expenditure', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fun', 'native_place', 'new_account', 'share_money', 'work']
{'_NewFamily__expenditure', 'new_account'}

"""