Python Cookbook学习笔记ch8_02

这里可以查看Jupyter notebook模式,效果更加!

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'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值