1. 程式人生 > >python面向對象魔術方法補充

python面向對象魔術方法補充

返回結果 isp src 通過 存在 xxxxx eight span lap

一、描述符

  在 面向對象 編程中 定義一個(沒有定義方法)類:class person , 在這個類裏面,有name,age, heigth, weight,等等屬性, 這個類就可以看作一個對 person 的描述符,而具體的實例則是具體的“被描述物”。

  而在python中,描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議。
  __get__():調用一個屬性時,觸發
  __set__():為一個屬性賦值時,觸發d
  __delete__():采用del刪除屬性時,觸發

class Foo: #在python3中Foo是新式類,它實現了三種方法,這個類就被稱作一個描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass

  2、描述符是幹什麽的:描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)

class Foo:
    
def __get__(self, instance, owner): print(觸發get) def __set__(self, instance, value): print(觸發set) def __delete__(self, instance): print(觸發delete) #包含這三個方法的新式類稱為描述符,由這個類產生的實例進行屬性的調用/賦值/刪除,並不會觸發這三個方法 f1=Foo() f1.name=egon f1.name del f1.name #疑問:何時,何地,會觸發這三個方法的執行
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(Str調用)
    def __set__(self, instance, value):
        print(Str設置...)
    def __delete__(self, instance):
        print(Str刪除...)

#描述符Int
class Int:
    def __get__(self, instance, owner):
        print(Int調用)
    def __set__(self, instance, value):
        print(Int設置...)
    def __delete__(self, instance):
        print(Int刪除...)

class People:
    name=Str()
    age=Int()
    def __init__(self,name,age): #name被Str類代理,age被Int類代理,
        self.name=name
        self.age=age

#何地?:定義成另外一個類的類屬性

#何時?:且看下列演示

p1=People(alex,18)

#描述符Str的使用
p1.name
p1.name=egon
del p1.name

#描述符Int的使用
p1.age
p1.age=18
del p1.age

#我們來瞅瞅到底發生了什麽
print(p1.__dict__)
print(People.__dict__)

#補充
print(type(p1) == People) #type(obj)其實是查看obj是由哪個類實例化來的
print(type(p1).__dict__ == People.__dict__)

  3、描述符分為兩種:

  數據描述符:至少實現了__get__()和__set__()

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)

  4、註意事項

# 描述符本身應該定義成新式類,被代理的類也應該是新式類
# 必須把描述符定義成這個類的類屬性,不能為定義到構造函數中
# 要嚴格遵循該優先級,優先級由高到底分別是
#1.類屬性
#2.數據描述符
#3.實例屬性
#4.非數據描述符
#5.找不到的屬性觸發__getattr__()
技術分享圖片
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(Str調用)
    def __set__(self, instance, value):
        print(Str設置...)
    def __delete__(self, instance):
        print(Str刪除...)

class People:
    name=Str()
    def __init__(self,name,age): #name被Str類代理,age被Int類代理,
        self.name=name
        self.age=age


#基於上面的演示,我們已經知道,在一個類中定義描述符它就是一個類屬性,存在於類的屬性字典中,而不是實例的屬性字典

#那既然描述符被定義成了一個類屬性,直接通過類名也一定可以調用吧,沒錯
People.name #恩,調用類屬性name,本質就是在調用描述符Str,觸發了__get__()

People.name=egon #那賦值呢,我去,並沒有觸發__set__()
del People.name #趕緊試試del,我去,也沒有觸發__delete__()
#結論:描述符對類沒有作用-------->傻逼到家的結論

‘‘‘
原因:描述符在使用時被定義成另外一個類的類屬性,因而類屬性比二次加工的描述符偽裝而來的類屬性有更高的優先級
People.name #恩,調用類屬性name,找不到就去找描述符偽裝的類屬性name,觸發了__get__()

People.name=‘egon‘ #那賦值呢,直接賦值了一個類屬性,它擁有更高的優先級,相當於覆蓋了描述符,肯定不會觸發描述符的__set__()
del People.name #同上
‘‘‘
類屬性>數據描述符 技術分享圖片
#描述符Str
class Str:
    def __get__(self, instance, owner):
        print(Str調用)
    def __set__(self, instance, value):
        print(Str設置...)
    def __delete__(self, instance):
        print(Str刪除...)

class People:
    name=Str()
    def __init__(self,name,age): #name被Str類代理,age被Int類代理,
        self.name=name
        self.age=age


p1=People(egon,18)

#如果描述符是一個數據描述符(即有__get__又有__set__),那麽p1.name的調用與賦值都是觸發描述符的操作,於p1本身無關了,相當於覆蓋了實例的屬性
p1.name=egonnnnnn
p1.name
print(p1.__dict__)#實例的屬性字典中沒有name,因為name是一個數據描述符,優先級高於實例屬性,查看/賦值/刪除都是跟描述符有關,與實例無關了
del p1.name
數據描述符>實例屬性 技術分享圖片
class Foo:
    def func(self):
        print(我胡漢三又回來了)
f1=Foo()
f1.func() #調用類的方法,也可以說是調用非數據描述符
#函數是一個非數據描述符對象(一切皆對象麽)
print(dir(Foo.func))
print(hasattr(Foo.func,__set__))
print(hasattr(Foo.func,__get__))
print(hasattr(Foo.func,__delete__))
#有人可能會問,描述符不都是類麽,函數怎麽算也應該是一個對象啊,怎麽就是描述符了
#笨蛋哥,描述符是類沒問題,描述符在應用的時候不都是實例化成一個類屬性麽
#函數就是一個由非描述符類實例化得到的對象
#沒錯,字符串也一樣


f1.func=這是實例屬性啊
print(f1.func)

del f1.func #刪掉了非數據
f1.func()
實例屬性>非數據屬性 技術分享圖片
class Foo:
    def func(self):
        print(我胡漢三又回來了)

    def __getattr__(self, item):
        print(找不到了當然是來找我啦,item)
f1=Foo()

f1.xxxxxxxxxxx
非數據描述符>找不到

  5.描述符的使用

技術分享圖片
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(name)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(egon,18,3231.3)

#調用
print(p1.__dict__)
p1.name

#賦值
print(p1.__dict__)
p1.name=egonlin
print(p1.__dict__)

#刪除
print(p1.__dict__)
del p1.name
print(p1.__dict__)
version1 技術分享圖片
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(name)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

#疑問:如果我用類名去操作屬性呢
People.name #報錯,錯誤的根源在於類去操作屬性時,會把None傳給instance

#修訂__get__方法
class Str:
    def __init__(self,name):
        self.name=name
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(name)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary
print(People.name) #完美,解決
version2 技術分享圖片
class Str:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        if not isinstance(value,self.expected_type): #如果不是期望的類型,則拋出異常
            raise TypeError(Expected %s %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)


class People:
    name=Str(name,str) #新增類型限制str
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)#傳入的name因不是字符串類型而拋出異常
version3 技術分享圖片
class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        if not isinstance(value,self.expected_type):
            raise TypeError(Expected %s %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)


class People:
    name=Typed(name,str)
    age=Typed(name,int)
    salary=Typed(name,float)
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(123,18,3333.3)
p1=People(egon,18,3333.3)
p1=People(egon,18,3333)
version4

  雖然我們已然能實現功能了,但是問題是,如果我們的類有很多屬性,你仍然采用在定義一堆類屬性的方式去實現,這就顯示很low。

  我們先來看看類裝飾如何實現。

  無參類裝飾器:

def decorate(cls):
    print("類裝飾器開始")
    return cls


@decorate
class People:
    def __init__(self,name):
        self.name = name

p1 = People("hello world")

  有參類裝飾器:

def typeassert(**kwargs):
    def decorate(cls):
        print(類的裝飾器開始運行啦------>,kwargs)
        return cls
    return decorate


# 有參:1.運行typeassert(...)返回結果是decorate,此時參數都傳給kwargs 2.People=decorate(People)


@typeassert(name=str, age=int, salary=float) 
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

p1=People(xiaohuar,18,3333.3)

  

技術分享圖片
class Typed:
    def __init__(self,name,expected_type):
        self.name=name
        self.expected_type=expected_type
    def __get__(self, instance, owner):
        print(get--->,instance,owner)
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print(set--->,instance,value)
        if not isinstance(value,self.expected_type):
            raise TypeError(Expected %s %str(self.expected_type))
        instance.__dict__[self.name]=value
    def __delete__(self, instance):
        print(delete--->,instance)
        instance.__dict__.pop(self.name)

def typeassert(**kwargs):
    def decorate(cls):
        print(類的裝飾器開始運行啦------>,kwargs)
        for name,expected_type in kwargs.items():
            setattr(cls,name,Typed(name,expected_type))
        return cls
    return decorate
@typeassert(name=str,age=int,salary=float) #有參:1.運行typeassert(...)返回結果是decorate,此時參數都傳給kwargs 2.People=decorate(People)
class People:
    def __init__(self,name,age,salary):
        self.name=name
        self.age=age
        self.salary=salary

print(People.__dict__)
p1=People(egon,18,3333.3)
View Code

  描述符總結:描述符是可以實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性。描述符是很多高級庫和框架的重要工具之一,描述符通常是使用到裝飾器或者元類的大型框架中的一個組件.

二、實現property

  利用描述符原理完成一個自定制@property,實現延遲計算(本質就是把一個函數屬性利用裝飾器原理做成一個描述符:類的屬性字典中函數名為key,value為描述符類產生的對象)

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(alex,1,1)
print(r1.area)

  

class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print(這是我們自己定制的靜態屬性,r1.area實際是要執行r1.area())
        if instance is None:
            return self
        return self.func(instance) #此時你應該明白,到底是誰在為你做自動傳遞self的事情

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @Lazyproperty #area=Lazyproperty(area) 相當於定義了一個類屬性,即描述符
    def area(self):
        return self.width * self.length

r1=Room(alex,1,1)
print(r1.area)

三、實現classmethod

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身,
        def feedback():
            print(在這裏可以加功能啊...)
            return self.func(owner)
        return feedback

class People:
    name=hi world
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls):
        print(你好啊,帥哥 %s %cls.name)

People.say_hi()

p1=People()
p1.say_hi()
#疑問,類方法如果有參數呢,好說,好說

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身,
        def feedback(*args,**kwargs):
            print(在這裏可以加功能啊...)
            return self.func(owner,*args,**kwargs)
        return feedback

class People:
    name=hi,world
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls,msg):
        print(你好啊,帥哥 %s %s %(cls.name,msg))

People.say_hi(你是那偷心的賊)

p1=People()
p1.say_hi(你是那偷心的賊)

四、實現StaticMethod

class StaticMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #類來調用,instance為None,owner為類本身,實例來調用,instance為實例,owner為類本身,
        def feedback(*args,**kwargs):
            print(在這裏可以加功能啊...)
            return self.func(*args,**kwargs)
        return feedback

class People:
    @StaticMethod# say_hi=StaticMethod(say_hi)
    def say_hi(x,y,z):
        print(------>,x,y,z)

People.say_hi(1,2,3)

p1=People()
p1.say_hi(4,5,6)

五、再看property

  一個靜態屬性property本質就是實現了get,set,delete三種方法

class Foo:
    @property
    def AAA(self):
        print(get的時候運行我啊)

    @AAA.setter
    def AAA(self,value):
        print(set的時候運行我啊)

    @AAA.deleter
    def AAA(self):
        print(delete的時候運行我啊)

# 只有在屬性AAA定義property後才能定義AAA.setter,AAA.deleter
f1 = Foo()
f1.AAA
f1.AAA = aaa
del f1.AAA

  

class Foo:
    def get_AAA(self):
        print(get的時候運行我啊)

    def set_AAA(self,value):
        print(set的時候運行我啊)

    def delete_AAA(self):
        print(delete的時候運行我啊)
    AAA = property(get_AAA, set_AAA, delete_AAA)  # 內置property三個參數與get,set,delete一一對應

f1 = Foo()
f1.AAA  # 觸發get
f1.AAA = aaa  # 觸發set
del f1.AAA  # 觸發del

python面向對象魔術方法補充