Python Cookbook學習筆記ch8_02
阿新 • • 發佈:2018-12-17
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'