Python Cookbook学习笔记ch8_04

本文探讨了Python类设计的多种高级技巧,包括使用类方法定义多个构造器、绕过__init__方法创建实例、通过Mixins扩展类功能、实现状态机等,提供了丰富的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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)]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值