Python cookbook学习笔记ch8_03

Jupyter notebook模式下查看效果更加噢!

8.12定义接口和抽象基类

  • 问题:想要定义一个接口或者抽象基类,并且通过执行类型检查来确保子类实现了某些特定的方法
  • 方案:使用abc模块可以很容易的定义抽象基类
from abc import ABCMeta,abstractmethod
class IStream(metaclass = ABCMeta):
    @abstractmethod
    def read(self, maxbytes = -1):
        pass
    @abstractmethod
    def write(self, data):
        pass
  • 抽象类的特点是它不能直接被实列化
a = IStream()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-5-a1e77104e354> in <module>()
----> 1 a = IStream()


TypeError: Can't instantiate abstract class IStream with abstract methods read, write
  • 抽象类的目的就是让其他的类继承并实现特定的抽象方法
class SocketStream(IStream):
    def read(self, maxbytes = -1):
        pass
    def write(self, data):
        pass
  • 抽象类的一个主要作用是在代码中检查某些类是否为特定类型,实现了特定的接口
def serialize(obj, stream):
    if isinstance(stream, IStream):
        raise TypeError("Expected an IStream")
    pass
  • 除了继承来实现抽象基类外,还可以通过注册方式来实现
import io
IStream.register(io.IOBase)
f = open('data_file/foo.txt')
isinstance(f,IStream)
True
  • @abstractmethod还能实现注解静态方法、类方法和properties。只需要保证这个注解紧靠在函数定义前即可

class A(metaclass = ABCMeta):
    # 注解property
    @property
    @abstractmethod
    def name(self):
        pass
    @name.setter
    @abstractmethod
    def name(self, value):
        pass
    # 注解类方法
    @classmethod
    @abstractmethod
    def method1(cls):
        pass
    # 注解静态方法
    @staticmethod
    @abstractmethod
    def method2():
        pass
  • 可以使用预定义的抽象基类来执行更加通用的类型检查
import collections
x = [1,2,3]
if isinstance(x,collections.Sequence):
    pass
if isinstance(x,collections.Iterable):
    pass
if isinstance(x,collections.Sized):
    pass
if isinstance(x,collections.Mapping):
    pass

8.13 实现数据模型的类型约束

  • 问题:想要定义在某些属性赋值上面有限制的数据结构
  • 方案:使用描述器,下面的代码使用描述器实现了一个系统类型和赋值验证框架
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
        
class Typed(Descriptor):
    expected_type = type(None)
    def  __set__(self,instance,value):
        if not isinstance(value, self.expected_type):
            raise TypeError("expected a(an) " + str(self.expected_type))
        super().__set__(instance, value)
        
class Unsigned(Descriptor):
    def __set__(self,instance, value):
        if value < 0:
            raise ValueError("Expected >= 0")
        super().__set__(instance, value)

class MaxSized(Descriptor):
    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError("missing size option")
        super().__init__(name, **opts)
    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError("size must < {}".format(str(self.size)))
        super().__set__(instance,value)
  • 上面是要创建的数据模型或类型系统的基础构建模块,下面定义各种不同的数据类型
class Integer(Typed):
    expected_type = int
class UnsignedInteger(Integer, Unsigned):
    pass

class Float(Typed):
    expected_type = float
class UnsignedFloat(Float, Unsigned):
    pass

class String(Typed):
    expected_type = str
class SizedString(String, MaxSized):
    pass   
  • 然后使用这些自定义数据类型
class Stock:
    name = SizedString('name', size=8)
    shares = UnsignedInteger('shares')
    price = UnsignedFloat('price')
    
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
  • 然后测试这个类的属性赋值约束,会发现有些属性的赋值违反了约束会报错
s = Stock('ACME', 10,20.2)
s.name
'ACME'
s.shares =  75
s.shares = -10
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-6-56bbeda8cc58> in <module>()
----> 1 s.shares = -10


<ipython-input-1-4ea2d79314e4> in __set__(self, instance, value)
     12         if not isinstance(value, self.expected_type):
     13             raise TypeError("expected a(an) " + str(self.expected_type))
---> 14         super().__set__(instance, value)
     15 
     16 class Unsigned(Descriptor):


<ipython-input-1-4ea2d79314e4> in __set__(self, instance, value)
     17     def __set__(self,instance, value):
     18         if value < 0:
---> 19             raise ValueError("Expected >= 0")
     20         super().__set__(instance, value)
     21 


ValueError: Expected >= 0
s.price = 'a lot'
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-7-9fa57c04f45f> in <module>()
----> 1 s.price = 'a lot'


<ipython-input-1-4ea2d79314e4> in __set__(self, instance, value)
     11     def  __set__(self,instance,value):
     12         if not isinstance(value, self.expected_type):
---> 13             raise TypeError("expected a(an) " + str(self.expected_type))
     14         super().__set__(instance, value)
     15 


TypeError: expected a(an) <class 'float'>
s.name = "assadasfaf"
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-8-47da3b5e7c48> in <module>()
----> 1 s.name = "assadasfaf"


<ipython-input-1-4ea2d79314e4> in __set__(self, instance, value)
     12         if not isinstance(value, self.expected_type):
     13             raise TypeError("expected a(an) " + str(self.expected_type))
---> 14         super().__set__(instance, value)
     15 
     16 class Unsigned(Descriptor):


<ipython-input-1-4ea2d79314e4> in __set__(self, instance, value)
     27     def __set__(self, instance, value):
     28         if len(value) >= self.size:
---> 29             raise ValueError("size must < {}".format(str(self.size)))
     30         super().__set__(instance,value)


ValueError: size must < 8
  • 还有其他的技术可以简化上面的代码,其中之一是使用 类装饰器(有问题,欢迎指点)
def check_attribute(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                value.name = key
                setattr(cls, key, value)
            else:
                setattr(cls, key, value(key))
        return cls
    
@check_attribute(name=SizedString(size=8),shares=UnsignedInteger, price=UnsignedFloat)
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-14-bf06768068a5> in <module>()
      9         return cls
     10 
---> 11 @check_attribute(name=SizedString(size=8),shares=UnsignedInteger, price=UnsignedFloat)
     12 class Stock:
     13     def __init__(self, name, shares, price):


TypeError: 'NoneType' object is not callable
  • 另外一种方式是使用元类
class checkedmeta(type):
    def __new__(cls, clsname, bases, methods):
        for key, value in methods.items():
            if isinstance(value, Descriptor):
                value.name = key
            return type.__new__(cls, clsname, bases, methods)
class Stock2(metaclass=checkedmeta):
    name = SizedString(size=8)
    shares = UnsignedInteger()
    price = UnsignedFloat()
    
    def __init__(self,name,shares,price):
        self.name = name
        self.price = price
        self.shares = shares
    

8.14 实现自定义容器

  • 问题:想实现一个自定义的类来模拟内置的容器功能,例如字典。
  • 方案:collections定义了很多抽象基类,当你想让你的容器类支持迭代时,可以继承collections.Iterable
import collections
class A(collections.Iterable):
    pass
# 但是你要实现Iterable所有的抽象方法,否则会报错
a = A()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-18-e70d9beb36ab> in <module>()
      1 # 但是你要实现Iterable所有的抽象方法,否则会报错
----> 2 a = A()


TypeError: Can't instantiate abstract class A with abstract methods __iter__
# 只要实现__iter__()方法就好了
class B(collections.Iterable):
    def __iter__(self):
        print("ok")
b = B()
  • 但是一个基类有哪些方法需要实现呢?可以先试着实例化一个对象,在错误的提示中看看需要实现哪些方法
collections.Iterable()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-21-8f1024fc7985> in <module>()
----> 1 collections.Iterable()


TypeError: Can't instantiate abstract class Iterable with abstract methods __iter__
# 下面是一个实例,继承自Sequence抽象类,并且实现了元素按照顺序存储
import bisect
class SortedItems(collections.Sequence):
    def __init__(self,initial=None):
        self._items = sorted(initial) if initial is not None else []
    def __getitem__(self, index):
        return self._items[index]
    def __len__(self):
        return len(self._items)
    def add(self, item):
        bisect.insort(self._items, item)

items = SortedItems([5,3,4])
print(list(items))
print(items[0],items[-1])
items.add(6)
print(list(items))

[3, 4, 5]
3 5
[3, 4, 5, 6]
  • 使用collections中的抽象基类可以确保自定义的容器实现了所有的必要的方法,并且还能简化类型检查
items = SortedItems()
import collections
isinstance(items, collections.Iterable)
True
isinstance(items, collections.Sequence)
True
  • collections中很多抽象基类会为常见的容器提供默认的操作,这样你只需要实现自己最感兴趣的方法即可。
class Items(collections.MutableSequence):
    def __init__(self, initial=None):
        self._items = list(initial) if initial is not None else []
    def __getitem__(self, index):
        print('Getting:', index)
        return self._items[index]
    def __setitem__(self, index, value):
        print("setting [{}] to {}".format(index, value))
        self._items[index] = value
    def __delitem__(self, index):
        print("delete :", index)
        del self._items[index]
    def insert(self,index, value):
        print("Inserting:", index,value)
        self._items.insert(index, value)
    def __len__(self):
        print("Len")
        return len(self._items)
a = Items([1,2,3])
len(a)
Len





3
a.append(4)
Len
Inserting: 3 4
a.append(0)
Len
Inserting: 4 0
a.count(2)
Getting: 0
Getting: 1
Getting: 2
Getting: 3
Getting: 4
Getting: 5





1
a.remove(3)
Getting: 0
Getting: 1
Getting: 2
delete : 2

属性的代理访问

  • 问题:想要将某个实列的属性访问代理到内部另一个实例中,目的可能是作为继承的一个代替方法或者实现代理模式
  • 方案:简单来说,代理是一种编程模式,它将某个操作转移给另外一个对象来实现。就像下面这样:
class A:
    def spam(self, x):
        print('A spam')
    def foo(self):
        print('A foo')
class B1:
    '''简单的代理'''
    def __init__(self):
        self._a = A()
    def spam(self, x):
        print('B spam')
        return self._a.spam(x)
    def foo(self):
        return self._a.foo()
    def bar(self):
        pritn('B1 bar')
b = B1()
b.foo()
A foo
b.spam(2)
B spam
A spam
  • 如果仅有两个方法需要代理,向上面那样就可以,但是如果有大量的方法需要代理,可以使用__getattr__()
class B2:
    '''使用__getattr__()代理,代理比较多的时候'''
    def __init__(self):
        self._a = A()
    def bar(self):
        print("B2 bar")
    def __getattr__(self,name):
        '''这个方法在访问attribute不存在的时候被调用'''
        return getattr(self._a, name)

b2 = B2()
b2.bar()
B2 bar
b2.spam(42)# call B.__getattr__('spam')
A spam
  • 另一个代理例子是实现代理模式
class Proxy:
    def __init__(self,obj):
        self._obj = obj
    def __getattr__(self, name):
        print("getattr: ",name)
        return getattr(self._obj, name)
    def __setattr__(self, name, value):
        if name.startswith("_"):
            super().__setattr__(name, value)
        else:
            print("setattr : ", name ,value)
            setattr(self._obj, name, value)
    def __delattr__(self, name):
        if name.startswith("_"):
            super().__delattr__(name)
        else:
            print("delattr: ", name)
            delattr(self._obj,name)
# 使用这个代理类时,只需要用它来包装其它类即可
class Spam:
    def __init__(self,x):
        self.x = x
    def bar(self,y):
        print("Spam.bar = ",self.x, y)
s = Spam(2)
p = Proxy(s)
p.x
getattr:  x





2
p.bar
getattr:  bar





<bound method Spam.bar of <__main__.Spam object at 0x00B98790>>
p.x = 37
setattr :  x 37
  • 代理类有时候可以作为继承的替代方案
class A:
    def spam(self, x):
        print("A.spam ",x)
    def foo(self):
        print("a.foo")
class B(A):
    def spam(self, x):
        print('B.spam')
        super().spam(x)
    def bar(self):
        print("b.bar")
  • 使用代理的话,如下:
class A:
    def spam(self, x):
        print("A.spam ", x)
    def foo(self):
        print("A.foo")
class B:
    def __init__(self):
        self._a = A()
    def spam(self, x):
        print("B.spam ", x)
        self._a.spam(x)
    def bar(self):
        print("B.bar")
    def __getattr__(self,name):
        return getattr(self._a,name)
b = B()
b.bar()
B.bar
b.spam(1)
B.spam  1
A.spam  1
b.foo()
A.foo
  • 还有一点需要注意:__getattr__()对于双下划线开始和结尾的属性并不适用
class ListLike:
    def __init__(self,):
        self._items = []
    def __getattr__(self, name):
        return getattr(self._items, name)
# 如果创建一个ListLike对象,会发现它支持普通的列表方法,append(),insert(),但是不支持len(),元素查找等
a = ListLike()
a.append(2)
a.insert(0,1)
a._items
[1, 2]
a[0]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-79-6a1284577a36> in <module>()
----> 1 a[0]


TypeError: 'ListLike' object does not support indexing
len(a)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-80-1a2e6ec5f1e3> in <module>()
----> 1 len(a)


TypeError: object of type 'ListLike' has no len()
# 为了让其支持上述方法,需要手动逐一定义
class ListLike:
    def __init__(self,):
        self._items = []
    def __getattr__(self, name):
        return getattr(self._items, name)
    def __len__(self):
        return len(self._items)
    def __getitem__(self, index):
        return self._items[index]
    def __setitem__(self,index, value):
        self._items[index] = value
    def __delitem__(self,index):
        del self._items[index]
a = ListLike()
a.append(10)
a.append(20)
a.insert(1,15)
a._items
[10, 15, 20]
len(a)
3
a[1]
15
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值