描述符(__get__和__set__和__delete__)

103-描述符(get,set,delete)-預警.gif

1、描述符

  • 描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱爲描述符協議緩存

    • __get__():調用一個屬性時,觸發框架

    • __set__():爲一個屬性賦值時,觸發函數

    • __delete__():採用del刪除屬性時,觸發工具

  • 定義一個描述符3d

class Foo:  # 在python3中Foo是新式類,它實現了__get__(),__set__(),__delete__()中的一個三種方法的一個,這個類就被稱做一個描述符
    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 = 'nick'
f1.name
del f1.name

2.1 什麼時候,何地,會觸發這三個方法的執行

class Str:
    """描述符Str"""

    def __get__(self, instance, owner):
        print('Str調用')

    def __set__(self, instance, value):
        print('Str設置...')

    def __delete__(self, instance):
        print('Str刪除...')


class Int:
    """描述符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設置...
Int設置...
  • 描述符Str的使用
p1.name
p1.name = 'nick'
del p1.name
Str調用
Str設置...
Str刪除...
  • 描述符Int的使用
p1.age
p1.age = 18
del p1.age
Int調用
Int設置...
Int刪除...
  • 咱們來瞅瞅到底發生了什麼
print(p1.__dict__)
print(People.__dict__)
{}
{'__module__': '__main__', 'name': <__main__.Str object at 0x107a86940>, 'age': <__main__.Int object at 0x107a863c8>, '__init__': <function People.__init__ at 0x107ba2ae8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
  • 補充
print(type(p1) == People)  # type(obj)實際上是查看obj是由哪一個類實例化來的
print(type(p1).__dict__ == People.__dict__)
True
True

3、兩種描述符

3.1 數據描述符

  • 至少實現了__get__()和__set__()
class Foo:
    def __set__(self, instance, value):
        print('set')

    def __get__(self, instance, owner):
        print('get')

3.2 非數據描述符

  • 沒有實現__set__()
class Foo:
    def __get__(self, instance, owner):
        print('get')

4、描述符注意事項

103-描述符(get,set,delete)-注意.jpg?x-oss-process=style/watermark

  1. 描述符自己應該定義成新式類,被代理的類也應該是新式類代理

  2. 必須把描述符定義成這個類的類屬性,不能爲定義到構造函數中code

  3. 要嚴格遵循該優先級,優先級由高到底分別是對象

    1.類屬性blog

    2.數據描述符

    3.實例屬性

    4.非數據描述符

    5.找不到的屬性觸發__getattr__()

5、使用描述符

  • 衆所周知,python是弱類型語言,即參數的賦值沒有類型限制,下面咱們經過描述符機制來實現類型限制功能

5.1 牛刀小試

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('nick', 18, 3231.3)
set---> <__main__.People object at 0x107a86198> nick
  • 調用
print(p1.__dict__)
{'name': 'nick', 'age': 18, 'salary': 3231.3}
print(p1.name)
get---> <__main__.People object at 0x107a86198> <class '__main__.People'>
nick
  • 賦值
print(p1.__dict__)
{'name': 'nick', 'age': 18, 'salary': 3231.3}
p1.name = 'nicklin'
print(p1.__dict__)
set---> <__main__.People object at 0x107a86198> nicklin
{'name': 'nicklin', 'age': 18, 'salary': 3231.3}
  • 刪除
print(p1.__dict__)
{'name': 'nicklin', 'age': 18, 'salary': 3231.3}
del p1.name
print(p1.__dict__)
delete---> <__main__.People object at 0x107a86198>
{'age': 18, 'salary': 3231.3}

5.2 拔刀相助

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


# 疑問:若是我用類名去操做屬性呢
try:
    People.name  # 報錯,錯誤的根源在於類去操做屬性時,會把None傳給instance
except Exception as e:
    print(e)
get---> None <class '__main__.People'>
'NoneType' object has no attribute '__dict__'
  • 修訂__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)  # 完美,解決
get---> None <class '__main__.People'>
<__main__.Str object at 0x107a86da0>

5.3 磨刀霍霍

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


try:
    p1 = People(123, 18, 3333.3)  # 傳入的name因不是字符串類型而拋出異常
except Exception as e:
    print(e)
set---> <__main__.People object at 0x1084cd940> 123
Expected <class 'str'>

5.4 大刀闊斧

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


try:
    p1 = People(123, 18, 3333.3)
except Exception as e:
    print(e)
set---> <__main__.People object at 0x1082c7908> 123
Expected <class 'str'>
try:
    p1 = People('nick', '18', 3333.3)
except Exception as e:
    print(e)
set---> <__main__.People object at 0x1078dd438> nick
set---> <__main__.People object at 0x1078dd438> 18
Expected <class 'int'>
p1 = People('nick', 18, 3333.3)
set---> <__main__.People object at 0x1081b3da0> nick
set---> <__main__.People object at 0x1081b3da0> 18
set---> <__main__.People object at 0x1081b3da0> 3333.3
  • 大刀闊斧以後咱們已然能實現功能了,可是問題是,若是咱們的類有不少屬性,你仍然採用在定義一堆類屬性的方式去實現,low,這時候我須要教你一招:獨孤九劍

103-描述符(get,set,delete)-獨孤九劍.jpg?x-oss-process=style/watermark

5.4.1 類的裝飾器:無參

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


@decorate  # 無參:People = decorate(People)
class People:
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


p1 = People('nick', 18, 3333.3)
類的裝飾器開始運行啦------>

5.4.2 類的裝飾器:有參

def typeassert(**kwargs):
    def decorate(cls):
        print('類的裝飾器開始運行啦------>', kwargs)
        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


p1 = People('nick', 18, 3333.3)
類的裝飾器開始運行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}

5.5 刀光劍影

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('nick', 18, 3333.3)
類的裝飾器開始運行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>}
{'__module__': '__main__', '__init__': <function People.__init__ at 0x10797a400>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x1080b2a58>, 'age': <__main__.Typed object at 0x1080b2ef0>, 'salary': <__main__.Typed object at 0x1080b2c18>}
set---> <__main__.People object at 0x1080b22e8> nick
set---> <__main__.People object at 0x1080b22e8> 18
set---> <__main__.People object at 0x1080b22e8> 3333.3

6、描述符總結

  • 描述符是能夠實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性

  • 描述父是不少高級庫和框架的重要工具之一,描述符一般是使用到裝飾器或者元類的大型框架中的一個組件.

103-描述符(get,set,delete)-私人訂製.jpg?x-oss-process=style/watermark

7、自定製@property

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

7.1 property回顧

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)
1

7.2 自定製property

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)
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
1

7.3 實現延遲計算功能

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
        else:
            print('--->')
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)  # 計算一次就緩存到實例的屬性字典中
            return value


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)  # 先從本身的屬性字典找,沒有再去類的中找,而後出發了area的__get__方法
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
--->
1
print(r1.area)  # 先從本身的屬性字典找,找到了,是上次計算的結果,這樣就不用每執行一次都去計算
1

8、打破延遲計算

  • 一個小的改動,延遲計算的好夢就破碎了
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
        else:
            value = self.func(instance)
            instance.__dict__[self.func.__name__] = value
            return value
        # return self.func(instance) # 此時你應該明白,究竟是誰在爲你作自動傳遞self的事情
    def __set__(self, instance, value):
        print('hahahahahah')


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
print(Room.__dict__)
{'__module__': '__main__', '__init__': <function Room.__init__ at 0x107d53620>, 'area': <__main__.Lazyproperty object at 0x107ba3860>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}
r1 = Room('alex', 1, 1)
print(r1.area)
print(r1.area)
print(r1.area)
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
1
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
1
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
1
print(
    r1.area
)  #緩存功能失效,每次都去找描述符了,爲什麼,由於描述符實現了set方法,它由非數據描述符變成了數據描述符,數據描述符比實例屬性有更高的優先級,於是全部的屬性操做都去找描述符了
這是咱們本身定製的靜態屬性,r1.area實際是要執行r1.area()
1

9、自定製@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 = 'nick'

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


People.say_hi()

p1 = People()
在這裏能夠加功能啊...
你好啊,帥哥 nick
p1.say_hi()
在這裏能夠加功能啊...
你好啊,帥哥 nick
  • 疑問,類方法若是有參數呢,好說,好說
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 = 'nick'

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


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

p1 = People()
在這裏能夠加功能啊...
你好啊,帥哥 nick 你是那偷心的賊
p1.say_hi('你是那偷心的賊')
在這裏能夠加功能啊...
你好啊,帥哥 nick 你是那偷心的賊

一10、自定製@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()
在這裏能夠加功能啊...
------> 1 2 3
p1.say_hi(4, 5, 6)
在這裏能夠加功能啊...
------> 4 5 6

103-描述符(get,set,delete)-放假.jpg?x-oss-process=style/watermark

相關文章
相關標籤/搜索