Jupyter notebook阅览效果更佳
8.16在类中定义多个构造器
- 问题:实现一个类,但是想要使用__init__()以外的初始化方式
- 方案:为了实现多个构造器,需要使用类方法
import time
class Data:
'''使用类方法'''
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
a = Data(2012,12,15)
b = Data.today()
- 类方法的一个主要用途是定义多个构造器,他接受一个class作为第一个参数(cls)。在继承时也很好
class NewData(Data):
pass
c = Data.today()
d = NewData.today()
8.17创建不调用init方法的实例
- 问题:想要创建一个实例,但是不想用init方法
- 方案:可以使用__new__()方法创建一个未初始化的实例
class Data:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 下面演示如何不调用__init__()来创建实例
d = Data.__new__(Data)
d
<__main__.Data at 0x6184890>
d.year
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-9-7b2b8d967cb9> in <module>()
----> 1 d.year
AttributeError: 'Data' object has no attribute 'year'
# 可以看到Data的实例还没有year属性,需要手动初始化
data = {'year':2018,'month':10,'day':31}
for key,value in data.items():
setattr(d,key,value)
d.year
2018
d.month
10
- 注:(自己的理解)类在创建一个实例的时候会首先使用new创建对象,然后使用init初始化对象。如果使用new跳过init,则仅仅是创建了对象,需要手动初始化属性
- 当我们在反序列对象或者实现某个类方法构造函数时需要绕过init方法来创建对象,有时候可能会像下面那样定义一个新的构造函数:
from time import localtime
class Date:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
d = cls.__new__(cls)
t = localtime()
d.year = t.tm_year
d.month = t.tm_month
d.day = t.tm_day
return d
8.18使用Mixins扩展功能
- 问题:有很多有用的方法,想使用他们来扩展其他的类,但是它们之间没有任何继承关系
- 方案:通常当你想自定义类的时候会碰上这些问题。可能是某个库提供了一些基础类,你
可以利用它们来构造你自己的类。
假设你想扩展映射对象,给它们添加日志、唯一性设置、类型检查等等功能。下面
是一些混入类
class LoggedMappingMixin:
'''add logging to get/set/delete operations debugging'''
__slots__=()# 混入类都没有实例变量,因为直接实例化混入类没有任何意义
def __getitem__(self, key):
print("Getting " + str(key))
return super().__getitem__(key)
def __setitem__(self,key,value):
print("Setting {} as {!r} " .format(key, value))
return super().__setitem__(key,value)
def __delitem__(self, key):
print("Deleting " + str(key))
return super().__delitem__(key)
class SetOnceMappingMixin:
'''Only allow a key to be set once'''
__slots__ = ()
def __setitem__(self,key,value):
if key in self:
raise KeyError(str(key) + ' already set')
return super().__setitem__(key, value)
class StringKeysMappingMixin:
'''restrict keys to strings only'''
__slots__ = ()
def __setitem__(self,key, value):
if not isinstance(key, str):
raise TypeError("key must be a string")
return super().__setitem__(key,value)
- 上述的类单独使用起来没有任何意义,事实上实例化任何一个类都会产生异常。它们是用来通过多继承来和其他的映射对象混入使用的
class LoggedDict(LoggedMappingMixin, dict):
pass
d = LoggedDict()
d['x'] = 23
print(d['x'])
Setting x as 23
Getting x
23
del d['x']
print(d['x'])
Deleting x
Getting x
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-23-7c22f0ebded3> in <module>()
1 del d['x']
----> 2 print(d['x'])
<ipython-input-21-de7104437910> in __getitem__(self, key)
4 def __getitem__(self, key):
5 print("Getting " + str(key))
----> 6 return super().__getitem__(key)
7 def __setitem__(self,key,value):
8 print("Setting {} as {!r} " .format(key, value))
KeyError: 'x'
from collections import defaultdict
class SetOnceDefaultDict(SetOnceMappingMixin,defaultdict):
pass
d = SetOnceDefaultDict(list)
d['x'].append(2)
d['x'].append(3)
d['x'] = 223
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-25-5a621a0169ad> in <module>()
----> 1 d['x'] = 223
<ipython-input-21-de7104437910> in __setitem__(self, key, value)
17 def __setitem__(self,key,value):
18 if key in self:
---> 19 raise KeyError(str(key) + ' already set')
20 return super().__setitem__(key, value)
21
KeyError: 'x already set'
- 混入类在很多大型标准库中用到,用于扩展功能。下面的代码简单的实现了一个多线程的XML_RPC服务
from xmlrpc.server import SimpleXMLRPCServer
from socketserver import ThreadingMixIn
class ThreadedXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
pass
-
注意:混入类不能直接实例化对象;其次,混入类没有自己的状态信息,也就是说它们没有定义初始化init方法,并没有实例属性
-
还有一种实现混入类的方式是使用类装饰器
def LoggedMapping(cls):
'''使用类装饰器'''
cls_getitem = cls.__getitem__
cls_setitem = cls.__setitem__
cls_delitem = cls.__delitem__
def __getitem__(self, key):
print("Getting " + str(key))
return cls_getitem(self,key)
def __setitem__(self,key,value):
print("Setting {} as {}".format(key,value))
return cls_setitem(self,key,value)
def __delitem__(self,key):
print("Deleting " + str(key))
return cls_delitem(self,key)
cls.__getitem__ = __getitem__
cls.__setitem__ = __setitem__
cls.__delitem__ = __delitem__
return cls
@LoggedMapping
class LoggedDict(dict):
pass
d = LoggedDict()
d['y'] = 20
Setting y as 20
d['y']
Getting y
20
8.19实现状态对象或者状态机
- 问题:想要实现一个状态机或者在不同状态下执行操作的对象,但不想在代码中添加太多的条件判断语句
- 方案:为每一个状态定义一个对象
class Connection:
'''普通方法'''
def __init__(self):
self.state = "CLOSED"
def read(self):
if self.state != "OPEN":
raise RuntimeError('Not open')
print("reading...")
def write(self,data):
if self.state != "OPEN":
raise RuntimeError("Not open")
print("writing...")
def open(self):
if self.state != "OPEN":
raise RuntimeError("Already open")
self.state = "OPEN"
def close(self):
if self.state != "CLOSED":
raise RuntimeError("Already open")
self.state = "CLOSED"
'''上述方法太过复杂'''
- 一种比较好的方法是为每个状态定义一个对象
class Connection1:
def __init__(self):
self.new_state(ClosedConnectionState)
def new_state(self, newstate):
self._state = newstate
def read(self):
return self._state.read(self)
def write(self,data):
return self._state.write(self,data)
def open(self):
return self._state.open(self)
def close(self):
return self._state.close(self)
class ConnectionState:
'''链接状态基类'''
@staticmethod
def read(conn):
raise NotImplementedError()
@staticmethod
def write(conn, data):
raise NotImplementedError()
@staticmethod
def open(conn):
raise NotImplementedError()
@staticmethod
def close(conn):
raise NotImplementedError()
class ClosedConnectionState(ConnectionState):
'''应用'''
@staticmethod
def read(conn):
raise RuntimeError('not open')
@staticmethod
def write(conn, data):
raise RuntimeError('not open')
@staticmethod
def open(conn):
conn.new_state(OpenConnectionState)
@staticmethod
def close(conn):
raise RuntimeError('Already closed')
class OpenConnectionState(ConnectionState):
@staticmethod
def read(conn):
print("reading...")
@staticmethod
def write(conn, data):
print("writing...")
@staticmethod
def open(conn):
raise RuntimeError("Already open")
@staticmethod
def close(conn):
conn.new_state(ClosedConnectionState)
c = Connection1()
c._state
__main__.ClosedConnectionState
c.read()
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-54-aaae037ddc31> in <module>()
----> 1 c.read()
<ipython-input-52-4f7086e0893d> in read(self)
5 self._state = newstate
6 def read(self):
----> 7 return self._state.read(self)
8 def write(self,data):
9 return self._state.write(self,data)
<ipython-input-52-4f7086e0893d> in read(conn)
37 @staticmethod
38 def read(conn):
---> 39 raise RuntimeError('not open')
40
41 @staticmethod
RuntimeError: not open
c.open()
c._state
__main__.OpenConnectionState
c.read()
reading...
c.write('hello')
writing...
c.close()
c._state
__main__.ClosedConnectionState
8.20通过字符串调用对象方法
- 问题:有一个字符窜形式的方法名称,想通过它调用某个对象的对应方法
- 方案:使用getattr()
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Point({!r:},{!r:})'.format(self.x, self.y)
def distance(self,x,y):
return math.hypot(self.x - x, self.y - y)
def Out(self):
print("point({},{})".format(self.x,self.y))
p = Point(2,3)
d = getattr(p,'distance')(0,0) # 调用p.ditance(0,0)
d
3.6055512754639896
- 另外一种调用方式是使用operator.methodcaller()
import operator
operator.methodcaller('distance',0,0)(p)
3.6055512754639896
p = getattr(p,'Out')()
point(2,3)
- 当需要通过相同的参数多次调用某个方法时,使用operator。methodcaller就很方便
points = [
Point(2,1),Point(2,5),Point(-2,-1),Point(5,-3),Point(4,4)
]
points.sort(key=operator.methodcaller('distance',0,0))
points
[Point(2,1), Point(-2,-1), Point(2,5), Point(4,4), Point(5,-3)]