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