1. 程式人生 > >Python Cookbook學習筆記ch8_02

Python Cookbook學習筆記ch8_02

8.8子類中擴充套件property

  • 問題:在子類中想要擴充套件在父類中的property功能
  • 方案:見下述程式碼
class Person:
    def __init__(self,name):
        self.name = name
    @property
    def name(self):
        return self._name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('Expected a string'
) self._name = value @name.deleter def name(self): raise AttributeError("can't delete attribute")
class SubPerson(Person):
    @property
    def name(self):
        print("getting name")
        return super().name
    @name.setter
    def name(self, value):
        print("set name to : "
,value) super(SubPerson, SubPerson).name.__set__(self, value) @name.deleter def name(self): print("deleting name") super(SubPerson,SubPerson).name.__delete__(self)
s = SubPerson("Guido")
set name to :  Guido
s.name
getting name





'Guido'
s.name = "Lily"
set name to :  Lily
s.name
getting name





'Lily'
s.name = 32
set name to :  32



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-29-02ef1b0bc8a7> in <module>()
----> 1 s.name = 32


<ipython-input-24-fa5c72e8866d> in name(self, value)
      7     def name(self, value):
      8         print("set name to : ",value)
----> 9         super(SubPerson, SubPerson).name.__set__(self, value)
     10     @name.deleter
     11     def name(self):


<ipython-input-23-65c22ea7e09b> in name(self, value)
      8     def name(self,value):
      9         if not isinstance(value,str):
---> 10             raise TypeError('Expected a string')
     11         self._name = value
     12     @name.deleter


TypeError: Expected a string
  • 如果僅想擴充套件property的某一個方法,可以這樣寫:
class SubPerson(Person):
    @Person.name.getter
    def name(self):
        print("getting name")
        return super().name
  • 或者僅僅想修改setter的方法
class SubPerson(Person):
    @Person.name.setter
    def name(self, value):
        print("set name as: ", value)
        super(SubPerson, Subperson).name,__set__(self, value)
s2 =  SubPerson('FLC')
set name as:  FLC



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-32-338adad0e003> in <module>()
----> 1 s2 =  SubPerson('FLC')


<ipython-input-23-65c22ea7e09b> in __init__(self, name)
      1 class Person:
      2     def __init__(self,name):
----> 3         self.name = name
      4     @property
      5     def name(self):


<ipython-input-31-52c694d0c8e2> in name(self, value)
      3     def name(self, value):
      4         print("set name as: ", value)
----> 5         super(SubPerson, Subperson).name,__set__(self, value)


TypeError: super(type, obj): obj must be an instance or subtype of type
s2.name = "Lj"
s2.name
getting name





'Lj'
  • 注意:在子類中擴充套件property時,首先確定是否要重新定義所有的方法,還是隻修改其中一個。因為一個property是setter、getter、deleter的方法的集合,而不是單個方法。

  • 上面演示的的第一種技術還可以用來擴充套件一個描述器

# 描述器
class String:
    def __init__(self, name):
        self.name = name
    def __get__(self, instance, cls):
        if isintance is not None:
            return self
        return instance.__dict__[self.name]
    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("Expected a string")
        instance.__dict__[self.name] = value 
        
# 帶有描述器的類
class Person:
    name = String('name')
    def __init__(self):
        self.name = name

# 使用property擴充套件描述器
class SubPerson(Person):
    @property
    def name(self):
        print("get name")
        return super().name
    @name.setter
    def name(self,value):
        print("set name as : ", value)
        super(SubPerson,SubPerson).name.__set__(self, value)
    @name.deleter
    def name(self):
        print("delete name")
        super(SubPerson, SubPerson).name.__delete__(self)

8.9建立新的類或者例項屬性

  • 問題:想要建立一個新的擁有額外功能的例項屬性型別,比如型別檢查
  • 方案:可以通過一個描述器的形式來定義它的功能
# 用描述器屬性增加一個整形型別檢查
class Interger:
    def __init__(self,name):
        self.name = name
    def __get__(self,instance,cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]
    def __set__(self,instance, value):
        if not isinstance(value, int):
            raise TypeError("Expected an int")
        instance.__dict__[self.name] = value
    def __delete__(self, instance):
        del instance.__dict__[self.name]
  • 一個描述器就是實現了三個核心的屬性訪問操作(get,set,delete),分別為__get__()、__set__()、__delete__()這三個特殊的方法,這些方法接收一個例項作為輸入。
  • 為了使用一個描述器,需要將這個描述器的例項作為類屬性放到一個類的定義中去
class Point:
    x = Interger('x')
    y = Interger('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y
  • 這樣做之後,所有對描述器屬性的訪問會被__get__()、__set__()、__delete__()方法捕獲到.
  • 描述器只能在類級別被定義,而不能為每個例項單獨定義
p = Point(2,3)
p.x   #呼叫Point.x.__get__(p,Point)
2
p.y   #呼叫Point.y.__get__(p,Point)
3
p.y = 5   #呼叫Point.y.__set__(p,5)
p.x = 2.3 #呼叫Point.x.__get__(p,2.3)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-41-8af2790a336d> in <module>()
      1 p.y = 5   #呼叫Point.y.__set__(p,5)
----> 2 p.x = 2.3 #呼叫Point.x.__get__(p,2.3)


<ipython-input-33-bbc6eaba675a> in __set__(self, instance, value)
     10     def __set__(self,instance, value):
     11         if not isinstance(value, int):
---> 12             raise TypeError("Expected an int")
     13         instance.__dict__[self.name] = value
     14     def __delete__(self, instance):


TypeError: Expected an int
p = Point(10,20)
p.x
10
Point.x
<__main__.Interger at 0x1970fb0>
  • 描述器通常作為使用到裝飾器或者元類的大型框架中的元件
#型別檢查的描述器
class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]
    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError("Expected " + str(self.expected_type))
        instance.__dict__[self.name] = value
    def __delete__(self, instance):
        del instance.__dict__[self.name]

#選擇屬性的類裝飾器
def typeassert(**kwargs):
    def decorate(cls):
        for name, expected_type in kwargs.items():
            setattr(cls, name, Typed(name, expected_type))
        return cls
    return decorate

@typeassert(name = str, shares = int, price = float)
class Stock:
    def __init__(self,name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

8.10使用延遲計算屬性

  • 問題:想將一個只讀屬性定義成一個 property,並且只在訪問的時候才會計算結果。但是一旦被訪問後,你希望結果值被快取起來,不用每次都去計算
  • 定義一個延遲屬性的一種高效方法是通過使用一個描述器類
class lazyproperty:
    def __init__(self,func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            setattr(instance, self.func.__name__, value)
            return value
import math 
class Circle:
    def __init__(self, radius):
        self.radius = radius
    @lazyproperty
    def area(self):
        print("Computing area")
        return math.pi* self.radius**2
    @lazyproperty
    def perimeter(self):
        print("computing %perimeter")
        return 2*math.pi*self.radius
c = Circle(4.0)
c.radius
4.0
c.area
Computing area





50.26548245743669
c.area #注意結果中沒有 Computing area
50.26548245743669
c.perimeter
computing %perimeter





25.132741228718345
c.perimeter
25.132741228718345
c = Circle(5.0)
vars(c)# 獲取例項的變數
{'radius': 5.0}
c.area
Computing area





78.53981633974483
vars(c)
{'radius': 5.0, 'area': 78.53981633974483}
c.area
78.53981633974483
del c.area
vars(c)
{'radius': 5.0}
c.area
Computing area





78.53981633974483
c.perimeter
computing %perimeter





31.41592653589793
vars(c)
{'radius': 5.0, 'area': 78.53981633974483, 'perimeter': 31.41592653589793}
  • 上述方案的缺陷是:計算出來的值可以被修改
c.area
78.53981633974483
c.area =100
c.area
100
  • 現對其修改
def lazyproperty(func):
    name = '_lazy_' + func.__name__
    @property
    def lazy(self):
        if hasattr(self, name):
            return getattr(self, name)
        else:
            value = func(self)
            setattr(self, name, value)
            return value
    return lazy

import math 
class Circle:
    def __init__(self, radius):
        self.radius = radius
    @lazyproperty
    def area(self):
        print("Computing area")
        return math.pi* self.radius**2
    @lazyproperty
    def perimeter(self):
        print("computing %perimeter")
        return 2*math.pi*self.radius
c = Circle(10.0)
c.area
Computing area





314.1592653589793
c.area = 102
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-76-83b141cbd3c4> in <module>()
----> 1 c.area = 102


AttributeError: can't set attribute
c.area
314.1592653589793

8.11 簡化資料結構的初始化

  • 問題:寫了很多僅僅用作資料結構的類,但是不想寫太多煩人的__init__()
  • 方案:可以在基類中寫一個共用的初始化函式
import math 
class Structure1:
    _fields = []
    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError("Expected {} arguments, but {} was given".format(len(self._fields),len(args)))
        for name,value in zip(self._fields, args):
            setattr(self, name, value)
            
#然後使其他類繼承自該類
class Stock(Structure1):
    _fields = ['name', 'shares', 'price']
class Point(Structure1):
    _fields = ['x', 'y']
class Circle(Structure1):
    _fields = ['radius']
    def area(self):
        return math.pi * self.radus ** 2
s = Stock('Tencent', 50, 90.11)
s.name
'Tencent'
p = Point(2,1)
p.x
2
c = Circle(4.5)
c.area
<bound method Circle.area of <__main__.Circle object at 0x014BAFB0>>
s2 = Stock('Baidu',100)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-86-ec541ef34bcc> in <module>()
----> 1 s2 = Stock('Baidu',100)


<ipython-input-79-21dc5e74d621> in __init__(self, *args)
      4     def __init__(self, *args):
      5         if len(args) != len(self._fields):
----> 6             raise TypeError("Expected {} arguments, but {} was given".format(len(self._fields),len(args)))
      7         for name,value in zip(self._fields, args):
      8             setattr(self, name, value)


TypeError: Expected 3 arguments, but 2 was given
  • 如果想要支援關鍵字引數,可以將關鍵字引數設定為例項屬性
class Structure2:
    _fields = []
    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError("Expected {} arguments, but {} was given".format(len(self._fields),len(args)))
        for name, value in zip(self._fields, args):
            setattr(self, name, value)
        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))
        if kwargs:
            raise TypeError('Invalid arguments :{}'.format(','.join(kwargs)))
            
if __name__ == "__main__":
    class Stock(Structure2):
        _fields = ['name','shares', 'price']
        
    s1 = Stock('ACME',100,99.1)
    s2 = Stock('ACMM',101,price = 99.1)
    s3 = Stock('ACEE',shares = 102, price = 991)
s1.name
'ACME'
s2.name
'ACMM'
s3.price
991
s4 = Stock('AMMM',shares = 10, price = 9,aa=1)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-100-94c197750f60> in <module>()
----> 1 s4 = Stock('AMMM',shares = 10, price = 9,aa=1)


<ipython-input-94-7913a8431947> in __init__(self, *args, **kwargs)
      9             setattr(self, name, kwargs.pop(name))
     10         if kwargs:
---> 11             raise TypeError('Invalid arguments :{}'.format(','.join(kwargs)))
     12 
     13 if __name__ == "__main__":


TypeError: Invalid arguments :aa
  • 還可以將不在_fields中的名稱加入到屬性中去
class Structure3:
    _fields = []
    def __init__(self,*args, **kwargs):
        if len(args) != len(self._fields):
            raise TypeError("Expected {} arguments".format(len(self._fields)))
        for name, value in zip(self._fields, args):
            setattr(self,name,value)
        extra_args = kwargs.keys() - self._fields
        for name in extra_args:
            setattr(self, name, kwargs.pop(name))
        if kwargs:
            raise TypeError("Duplicate values for {}".format(','.join(kwargs)))
if __name__=="__main__":
    class Stock(Structure3):
        _fields = ['name','shares','price']
    
    ss1 = Stock('ACC', 20, 20.9)
    ss2 = Stock('ACB', 30, 10.4,data = '2012/10/28')
ss1.name
'ACC'
ss2.data
'2012/10/28'
ss2.name
'ACB'