1. 程式人生 > >Python_基礎_(面向物件進階)

Python_基礎_(面向物件進階)

一,isinstance(obj,cls)  issubclass(sub,super)

isinstance(obj,cls)  # 判斷物件obj是否是cls的一個例項

class Test:
    pass
t = Test()
print(isinstance(t,Test))   # True

issubclass(sub,super)  # 判斷類sub是否為類super的子類

class Test:
    pass
class Sub(Test):
    pass
print(issubclass(Sub,Test))     #
True s = Sub() print(isinstance(s,Sub)) # True print(isinstance(s,Test)) # True

type(f)  # 可以用來看物件f使用哪個類產生的

class Foo:
    pass
f = Foo()
print(type(f))
# <class '__main__.Foo'>

....

二,__getattribute__

## 當 __getattr__和 __getattribute__同時出現時

class Test:
    
def __init__(self,name): self.name = name def __getattr__(self,item): print("執行的是__getattr__") def __getattribute__(self,item): print("執行的是__getattribute__") t = Test("henry") t.name # 當呼叫存在的屬性:觸發 __getattribute__ 不觸發 __getattr t.xxxxxx# 當呼叫不存在的屬性:觸發 __getattribute__

## __getattribute__ 丟擲異常時

class Test:
    def __init__(self,name):
        self.name = name
    
    def __getattr(self,item):
        print("執行的是__getattr__")
        
    def __getattribute__(self,item):
        print("執行的是__getattribute__")
        raise AttributeError("丟擲一個屬性異常")
t = Test("henry")
t.xxxxxx    # 執行 __getattribute__時會丟擲一個屬性異常,不會直接報錯,而後會去執行 __getattr__
## 輸出
執行的是__getattribute__
執行的是__getattr__
# 注:當丟擲的異常不是AttributeError,不會去執行 __getattr__ ,而是直接終止程式

...

三,__setitem__  __getitem__  __delitem__

# 字典形式的操作屬性就是和item相關
# 點的形式操作屬性,就是和內建方法相關

class Test:
    
    def __setitem__(self,key,value):
        parint("__sttitem__")
        self.__dic__[key] = value
    
    def __getitem__(self,item):
        parint("__getitem__")
        
    def __delitem__(self,key):
        print("__delitem__")
        self.__dic__.pop(key)
## 新增
t = Test()
t["name"] = "henry"    # 新增一個屬性和值,會觸發 __setitem__
t["user"] = "alex"
print(t.__dic__)    # {"name":"henry"}

## 刪除
del t.name            # 不會觸發item操作 只是普通的操作
print(t.__dic__)    # {}

del t["name"]        # 會觸發__delitem__
print(t.__dic__)    # {}

## 獲取
t.user                # 不會觸發getitem操作
t["user"]            # 會觸發__getitem__

...

四,__str__  __repr__

# 當列印一個例項化的物件時

class Test:
    pass
t = Test()
print(t)    # <__main__.Test object at 0x00000299CE10EBA8>

# __str__ 可以控制列印例項化的物件時的資訊

class Test:
    def __str__(self):
        return "我是例項化的物件列印的資訊"
t = Test()
print(t)    # 我是例項化的物件列印的資訊

# __repr__ 也是用於控制輸出,應用於直譯器中(cmd)

class Test:
    def __repr__(self):
        return "我是例項化的物件列印的資訊"
t = Test()
print(t)    # 我是例項化的物件列印的資訊

# 注意

## 兩者共存時,優先選擇str
## 如果__str__沒有被定義,那麼就會用__repr__來替代輸出
## str 和 repr 輸出的結果返回的值都為字串型別

class Test:
    def __str__(self):
        return "自定義的物件顯示方式str"
    def __repr__(self):
        return "自定義的物件顯示方式repr"
t = Test()
print(t)    # 輸出:自定義的物件顯示方式str

 ...

五,自定義格式化 __format__

class Test:
    def __init__(self,year,mon,day):
        self.year = year
        self.mon = mon
        self.day = day
t = Test(2018,12,22)
x = "{0.year},{0.mon},{0.day}".format(t)
y = "{0.year}:{0.mon}:{0.day}".format(t)
z = "{0.year}-{0.mon}-{0.day}".format(t)
print(x)    # 2018,12,22
print(y)    # 2018:12:22
print(z)    # 2018-12-22

# 當上方的程式要換一種日期輸出格式時,必須得使用者來定義,這必然是不行的

format_dic = {
    "ymd":"{0.year},{0.mon},{0.day}",
    "y:m:d":"{0.year}:{0.mon}:{0.day}",
    "y-m-d":"{0.year}-{0.mon}-{0.day}"
}
class Test:
    def __init__(self, year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day
    def __format__(self, format_spec):
        if not format_spec or format_spec not in format_dic:
            # 當用戶沒有輸入格式時,程式自己選擇為ymd
            # 當用戶輸入的格式不存在時
            format_spec = "ymd"
        fm = format_dic[format_spec]
        return fm.format(self)  # 相當於"{0,year}-{0,mon}-{0,day}",format(t)

t = Test(2018, 12, 22)
format(t)  # 相當於執行了 t.__format__()
print(format(t, "ymd"))     # 2018,12,22
print(format(t, "y:m:d"))   # 2018:12:22
print(format(t, "y-m-d"))   # 2018-12-22

...

六,__slots__

1:__slots__:是一個變數,變數的值可以是列表,元組,字串,可迭代的物件
2:當用點來訪問一個屬性時,實際上是在訪問類或者物件的__dict__屬性字典(類的屬性字典是共享的,可被每個例項物件訪問;二例項的字典是獨立的)
3:為何使用了__slots__:因為字典會佔用大量的記憶體,使用了__slots__不能再給其新增屬性,只能使用之前在__slots__中定義的
4:定義了__slots__後不在支援一些普通類的特性,比如繼承

# 基本形式

class Test:
    __slots__ = "name"
    # __slots__ = ['name','age']
    
t = Test()
t.name = "henry"
t.age = 18            
# 報錯,當前 __slots__替代了 __dic__,不能新增該屬性
# 限制建立屬性
print(t.__slots__)        # name
print(Test.__slots__)    # name

 ...

七,__doc__  __module__  __class__

# __doc__

# 注:該屬性不能被繼承
class Test:
    '我是文件註釋'
    pass
    
pring(Test.__doc__)

# 不能被繼承
class Test:
    '我是文件註釋'
    pass
class Foo(Test):
    pass
print(Foo.__doc__)    # 無法繼承給子類

# __module__    __class__    

# test1.py位於檔案 lib下
class A:
    def __init__(self):
        self.name = name

# 在test2.py檔案中匯入 test1.py模組
from lib.test1 import A

obj = A()
print(obj.__module__)    # 輸出lib.test1        # 輸出模組
print(obj.__class__)    # 輸出 lib.test1.A    # 輸出類

...

八,__del__析構方法

 1:當物件在記憶體中被釋放時,自動觸發執行

2:在Python中程式設計師無需關心記憶體的分配和釋放,Python直譯器會自動來執行,解構函式的呼叫是由直譯器在進行垃圾回收時自動觸發執行的,該方法一般無需定義

# 當刪除一個物件時,觸發__del__

class Test:
    def __del__(self):
        print("執行了del方法")
t = Test()
del t        # 刪除物件
print("程式執行完畢")
# 輸出
執行了del方法
程式執行完畢

# 當程式執行完畢後,觸發__del__

class Test:
    def __del__(self):
        print("執行了del方法")
t = Test()
print("程式執行完畢")
# 輸出
程式執行完畢
執行了del方法
# 在程式執行完成後,會執行垃圾回收機制

# 當刪除一個屬性時,不會觸發__del__

# 當刪除一個屬性時,不會執行__del__方法
class Test:
    def __init__(self,name):
        self.name = name
    def __del__(self):
        print("執行了del方法")
t = Test("henry")
del t.name
print("程式執行完畢")
# 輸出
程式執行完畢
執行了del方法

...

九,__call__

class Test:
    def __call__(self, *args, **kwargs):
        print("執行了__call__")

t = Test()
t()     # 一個物件後加括號,觸發執行__call__

# 一切皆物件,假如Test類由類Foo產生,則執行Test(),相當於執行了類Foo下的__call__方法

十,__next__  __iter__

 ..待完成

十一,描述符的基本概況

 # 描述符:本質就是在一個新式類中,這個新式類中至少實現 __get__(),__set__(),__delete__()中的一個,這三個也被稱為描述符協議

__get__() # 呼叫一個屬性時觸發

__set__() # 給一個屬性賦值時觸發

__delete__() # 利用del刪除屬性時觸發

# 什麼情況下會觸發著三個方法(以下為錯誤示範)

class Test:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __set__(self, instance, value):
        print("__set__")

    def __get__(self, instance, owner):
        print("__get__")

    def __delete__(self, instance):
        print("__delete__")

t = Test("henry",18)
t.name          # 沒呼叫
t.name = "alex" # 沒呼叫
del t.name      # 沒呼叫

# 什麼情況下會觸發這三個方法(以下為正確示範)

class Test:
    def __get__(self, instance, owner):
        print("觸發__get__")
    def __set__(self, instance, value):
        print("觸發__set__")
    def __delete__(self, instance):
        print("觸發__delete__")

class Bar:
    name = Test()
    # 資料描述符
    # 表示一個描述符 所有與name有關的操作都去找類Test
    # 表示在類Bar中利用類Test來描述name屬性
    def __init__(self,name):    # name被Test()代理
        self.name = name

b = Bar("henry")        # 觸發__set__,instance為b,value為henry
b.name                  # 觸發__get__
b.name = "heihei"       # 觸發__set__
del b.name              # 觸發__delete__


print(b.__dict__)       # {}
print(Bar.__dict__)     # 'name': <__main__.Test object at 0x0000017E78719470>
class Test_1:
    def __get__(self, instance, owner):
        print("Test_1觸發__get__")
    def __set__(self, instance, value):
        print("Test_1觸發__set__")
    def __delete__(self, instance):
        print("Test_1觸發__delete__")
class Test_2:
    def __get__(self, instance, owner):
        print("Test_2觸發__get__")
    def __set__(self, instance, value):
        print("Test_2觸發__set__")
    def __delete__(self, instance):
        print("Test_2觸發__delete__")

class Bar:
    name = Test_1()
    age = Test_2()
    def __init__(self,name,age):    # name被Test_1()代理,age被Test_2()代理
        self.name = name
        self.age = age

b = Bar("henry",18)
# Test_1觸發__set__
# Test_2觸發__set__


# Test_1
b.name
b.name = "heihei"
del b.name
# Test_1觸發__get__
# Test_1觸發__set__
# Test_1觸發__delete__

# Test_2
b.age
b.age = 19
del b.age
# Test_2觸發__get__
# Test_2觸發__set__
# Test_2觸發__delete__


print(b.__dict__)       # {}
print(Bar.__dict__)
# 'name': <__main__.Test_1 object at 0x0000021EF9F7F780>, 
# 'age': <__main__.Test_2 object at 0x0000021EF9F7FCC0>, 
相同的一種示範

# 資料描述符和非資料描述符

# 資料描述符
class Foo:
    def __set__(self, instance, value):
       print('set')    
    def __get__(self, instance, owner):
       print('get')

# 二 非資料描述符:沒有實現__set__()
class Foo:
    def __get__(self, instance, owner):
        print('get')
示例程式碼

# 描述符的注意事項

一:描述符不本身應該定義成新式類,被代理的類也應該時形式類

二:必須把描述符定義成則個類的類屬性,不能為定義到建構函式中

三:要嚴格遵循優先順序

1,類屬性

2,資料描述符     # 具有 __get__()和__set__()

3,例項屬性

4,非資料描述符   # 沒有__set__()

5,找不到屬性觸發 __getattr__()

# 描述符優先順序問題

## 類屬性>資料描述符

class Test:
    def __get__(self, instance, owner):
        print("呼叫get")
    def __set__(self, instance, value):
        print("呼叫set")
    def __delete__(self, instance):
        print("呼叫delete")

class Foo:
    name = Test()
    def __init__(self,name):
        self.name = name

Foo.name            # # 輸出:呼叫get
# 在Foo的屬性字典中,'name': <__main__.Test object at 0x00000262B7BF97B8>,
# 當呼叫屬性name時,屬性name為描述符偽裝成類屬性name,當在類中找不到屬性對飲的值,就到類Test中,觸發了__get__()

Foo.name = "henry"
# 直接賦值類的一個屬性,因為類屬性擁有更高的優先順序,相當於覆蓋了描述符,不會觸發描述符中的__set__()

print(Foo.__dict__)     # "name":"henry"
del Foo.name            # 刪除的為類中的類屬性,沒有觸發__delete__()
示例程式碼

## 資料描述符>例項屬性

class Test:
    def __get__(self, instance, owner):
        print("呼叫get")
    def __set__(self, instance, value):
        print("呼叫set")
    def __delete__(self, instance):
        print("呼叫delete")

class Foo:
    name = Test()
    def __init__(self,name):
        self.name = name

f = Foo("henry")
print(f.__dict__)   # {}
# 例項的屬性字典中沒有name,因為name是一個數據描述符,優先順序高於例項屬性,檢視/賦值/刪除/都和例項無關

f.name = "heihei"   # 輸出:呼叫set
# 先在自己的例項屬性中找,找不到則到類中找
# f.name的呼叫與賦值都是觸發描述符的操作,與f本身無法無關,相當於覆蓋了例項屬性

del f.name  # 呼叫delete
示例程式碼

## 例項屬性>非資料描述符

class Test:
    def __get__(self, instance, owner):
        print("呼叫get")
    # def __delete__(self, instance):
    #     print("呼叫delete")

class Foo:
    name = Test()       # 變成了非資料描述符


# name是一個非資料描述符,因為name=Test() 而Test()沒有實現set方法,因而比例項屬性由更低的優先順序
f = Foo()
f.name  # 呼叫get

f.name = "henry"        # 相當於在類Foo中進行設定屬性的值
print(f.__dict__)       # {'name': 'henry'}
# 例項屬性的優先順序高於非資料描述符,當描述符中沒有set時,就在自己的例項上新增屬性
示例程式碼

...

十二,描述符的應用

# 描述符中的引數

class Test:
    def __get__(self, instance, owner):
        print("執行get")
        print("instance引數為:%s" %instance)
        print("owner引數為:%s" % owner)
    def __set__(self, instance, value):
        print("執行set")
        print("instance引數為:%s" % instance)
        print("value引數為:%s" % value)
    def __delete__(self, instance):
        print("執行delete")
        print("instance引數為:%s" % instance)

class Foo:
    name = Test()
    def __init__(self,name,age,salary):
        self.name = name
        self.age = age
        self.salary = salary

f = Foo("henry",18,10.1)
# 執行set
# instance引數為:<__main__.Foo object at 0x000001F1E436E748>
# value引數為:henry

f.name
# 執行get
# instance引數為:<__main__.Foo object at 0x000001F1E436E748>
# owner引數為:<class '__main__.Foo'>

print(f.__dict__)
# name不存在物件f的字典中
# {'age': 18, 'salary': 10.1}
示例程式碼

# 簡單操作

class Test:
    def __init__(self,key):
        self.key = key

    def __get__(self, instance, owner):
        print("執行get")
        return instance.__dict__[self.key]     # instance為物件f,owner為類Foo

    def __set__(self, instance, value):
        print("執行set")
        instance.__dict__[self.key] = value        # 相當於給物件f新增鍵值對

    def __delete__(self, instance):
        print("執行delete")
        instance.__dict__.pop(self.key)

class Foo:
    name = Test("name")
    def __init__(self,name,age,salary):
        self.name = name
        self.age = age
        self.salary = salary

f = Foo("henry",18,10.1)
print(f.name)       # henry
print(f.__dict__)   # {'key': 'henry', 'age': 18, 'salary': 10.1}
f.name = "heihei"
print(f.__dict__)   # {'key': 'heihei', 'age': 18, 'salary': 10.1}
del f.name
print(f.__dict__)   # {'age': 18, 'salary': 10.1}
示例程式碼

# 針對name賦值時的型別檢查

class Test:
    def __init__(self,key):
        self.key = key

    def __get__(self, instance, owner):
        print("執行get")
        return instance.__dict__[self.key]     # instance為物件f,owner為類Foo

    def __set__(self, instance, value):
        print("執行set")
        if not type(value) == str:  # 判斷型別
            raise TypeError("你傳入的不是字串型別")
        instance.__dict__[self.key] = value

    def __delete__(self, instance):
        print("執行delete")
        instance.__dict__.pop(self.key)

class Foo:
    name = Test("name")
    def __init__(self,name,age,salary):
        self.name = name
        self.age = age
        self.salary = salary

f = Foo(123,18,10.1)
執行set
Traceback (most recent call last):
  File "F:/Python_Project/Test/描述符的應用.py", line 26, in <module>
    f = Foo(123,18,10.1)
  File "F:/Python_Project/Test/描述符的應用.py", line 22, in __init__
    self.name = name
  File "F:/Python_Project/Test/描述符的應用.py", line 12, in __set__
    raise TypeError("你傳入的不是字串型別")
TypeError: 你傳入的不是字串型別
示例程式碼

# 針對name和age賦值時的型別檢查

class Test:
    def __init__(self,key,expected_type):   # expected_type想要檢測的型別
        self.key = key
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print("執行get")
        return instance.__dict__[self.key]     # instance為物件f,owner為類Foo

    def __set__(self, instance, value):
        print("執行set")
        if not type(value) == self.expected_type:  # 判斷型別
            raise TypeError("你傳入的不是%s型別" %self.expected_type)
        instance.__dict__[self.key] = value

    def __delete__(self, instance):
        print("執行delete")
        instance.__dict__.pop(self.key)

class Foo:
    name = Test("name",str)
    age = Test("age",int)
    def __init__(self,name,age,salary):
        self.name = name
        self.age = age
        self.salary = salary

# f = Foo(123,18,10.1)    # TypeError: 你傳入的不是<class 'str'>型別
f = Foo("henry","18",10.1)  # TypeError: 你傳入的不是<class 'int'>型別
示例程式碼

...

十三,__enter__  __exit__   上下文管理協議

## 第一種檔案操作(使用完得關閉檔案)
f = open("xxx","r")
檔案操作
f.close

## 另一種檔案操作(使用完不必關閉檔案)
with open("xxxx","r") as file
    "檔案操作程式碼塊"

# 上述稱為上下文管理協議,即with語句,為了讓一個物件相容with語句,必須在這個物件的類中宣告__enter__和__exit__方法

## 上下文管理協議

# 一 資料描述符:至少實現了__get__()和__set__()
class Open:
    def __init__(self,name):
        self.name = name

    def __enter__(self):        # 在with Open("a.txt") as f:執行時觸發
        print("執行了enter")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):  # 等到程式碼塊執行完成後執行
        print("執行了exit")
        return self

with Open("a.txt") as f:
    print("程式碼塊執行完成")
# 輸出
執行了enter
程式碼塊執行完成
執行了exit

# Open("a.txt")的過程建立了一個物件,當執行with時執行了__enter__(),__enter__()返回一個值(self)賦值給f,f就是類Open產生的一個物件

## 當with中的程式碼塊出現異常時

class Open:
    def __init__(self,name):
        self.name = name

    def __enter__(self):        # 在with Open("a.txt") as f:執行時觸發
        print("執行了enter")

    def __exit__(self, exc_type, exc_val, exc_tb):  # 等到程式碼塊執行完成後執行
        print("執行了exit")
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        # return True        當在exit中返回一個True時,代表不丟擲程式出現異常 

with Open("a.txt") as f:
    print("程式碼塊開始執行執行")
    print(abc)      # 當在with中出現一個錯誤時
    print("程式碼塊執行完成")
print("11111111111")

# 當不return True時的 輸出的結果
  執行了enter
  程式碼塊開始執行執行
  執行了exit
  Traceback (most recent call last):
  <class 'NameError'>
    File "F:/Python_Project/Test/利用描述符定製property.py", line 17, in <module>
  name 'abc' is not defined
      print(abc)      # 當在with中出現一個錯誤時
  <traceback object at 0x000001E2A2D59FC8>
  NameError: name 'abc' is not defined

# 當return True時的結果
執行了enter
程式碼塊開始執行執行
執行了exit
<class 'NameError'>
name 'abc' is not defined
<traceback object at 0x000001E205DB9F88>
11111111111

## exit中的三個引數

exc_type:異常類
exc_val:異常值
exc_tb:異常追蹤資訊


<class 'NameError'>
    +
name 'abc' is not defined
    +
<traceback object at 0x0000016F7FAE9FC8>
    =
NameError: name 'abc' is not defined

## 總結

with obj as f:
"程式碼塊"
1:with obj # 觸發obj.__enter__(),獲得返回值self
2:as f # 將__enter__返回值賦值給f,f = 返回值
3:with obj as f # 等同於 f = obj.__enter__()
4:執行程式碼塊
  1:在沒有發生異常的情況下,整個程式碼塊執行完成後觸發__exit__,exit中的三個引數都為None
  2:在有異常的情況下,從異常出現的位置直接觸發__exit__
    a:如果__exit__的返回值為True,代表吞掉異常,不丟擲異常,程式不報錯
    b:如果__exit__的返回值不為True,表示丟擲異常,程式報錯
    c:__exit__的執行完畢,表示整個with語句的執行完畢

 ...