类与对象
一、修改实例的字符串表示
通过定义__str__()和__repr__()方法来实现。
特殊方法__repr__()返回的是实例的代码表示(code representation)可以用它返回字符串文本来重新创建这个实例。
特殊方法__str__()将实例转换为一个字符串。
特殊的格式化代码!r表示应该使用__repr__()输出,而不是默认的__str__()。
class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self)
对于__repr__(),标准的做法是让它产生的字符串文本能够满足eval(repr(x)) == x。
如果没有定义__str__(),那么就用__repr__()的输出当作备份。
二、自定义字符串的输出格式
__format__()方法在Python的字符串格式化功能中提供了一个钩子。对格式化代码的解释完全取决于类本身。
_formas = { 'ymd':'{d.year}-{d.month}-{d.day}', 'mdy':'{d.month}-{d.day}-{d.year}', 'dmy':'{d.day}-{d.month}-{d.year}', } class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day def __format__(self, code): if code == '': code = 'ymd' fmt = _formas[code].format(d=self) return fmt >>> d = Date(2019, 9, 17) >>>format(d) 2019-9-17 >>> 'The date is {:ymd}'.format(d) The date is 2019-9-17
三、支持上下文管理协议
最常用在需要管理类似文件、网络连接和锁这样的资源的程序中。它们必须显式地进行关闭或者释放才能正确工作。
让对象支持上下文管理协议(context-management protocol),通过with语句触发。
要让对象能够兼容with语句,需要实现__enter__()和__exit__()方法。
主要原则:编写的代码需要包含在由with语句定义的代码块中。
当遇到with语句时,__enter__()方法首先被触发执行。__enter__()的返回值(如果有的话)被放置在由as限定的变量当中。
之后开始执行with代码块中的语句。最后__exit__()方法被触发来执行清理工作。
__exit__()方法的三个参数包含了异常类型、值、和对挂起异常的追溯。
__exit__()返回True,异常就会被清理干净,好像什么也没有发生过一样,而程序也会立刻继续执行with语句块之后的代码。
from socket import socket,AF_INET,SOCK_STREAM class LazyConnection: def __init__(self, address, family=AF_INET, type=SOCK_STREAM): self.address = address self.family = family self.type = type self.connections = [] def __enter__(self): sock = socket(self.family, self.type) sock.connect(self.address) self.connections.append(sock) return sock def __exit__(self, exc_type, exc_val, exc_tb): self.connections.pop().close()
四、当创建大量实例时节省内存
对于那些主要用作简单数据结构的类,通常可以在类定义中增加__slot__属性,以此来大量减少对内存的使用。
当定义了__slots__属性时,Python就会针对实例采用一种更加紧凑的内部表示。不再让每个实例都创建一个__dict__字典。
使用__slots__带来的副作用是我们没法再对实例添加任何新的属性了。
定义了__slots__属性的类不支持某些特定的功能,比如多重继承。
五、将名称封装到类中
想将私有数据封装到类的实例上,但是有需要考虑到Python缺乏对属性的访问控制问题。
通过特定的命名规则来表达出对数据和方法的用途。
第一个规则是:任何以下划线(_)开头的名字应该总是被认为只属于内部实现。
第二个规则:以双下划线(__)开头的名称会导致出现名字重整(name mangling)的行为。
类中的私有属性会被分别重命名为_A__private和_A__private_method。为了继承,这样的属性不能通过继承而覆盖。
第三个规则:定义一个变量但是名称可能会和保留字产生冲突。应该在名称最后加上一个单下划线以示区别,如lambda_。
六、创建可管理的属性
property的全部意义在于我们设置属性时可以执行类型检查。
不要编写定义了大量重复性property的代码。
class Person: def __init__(self, first_name): self.first_name = first_name @property def first_name(self): return self._first_name @first_name.setter def first_name(self, value): if not isinstance(value, str): raise TypeError('Excepted a string') self._first_name = value @first_name.deleter def first_name(self): raise AttributeError("Can't delete attribute") p = Person('David')
七、调用父类中的方法
调用父类(超类)中的方法,可以使用super()函数完成。
super()函数的一种常见用途是调用父类的__init__()方法,确保父类被正确地初始化了。
针对每一个定义的类,Python都会计算出一个称为方法解析顺序(MRO)的列表,MRO列表只是简单地对所有的基类进行线性排列。
要实现继承,Python从MRO列表中最左边的类开始,从左到右一次查找,直到找到待查的属性时为止。
当使用super()函数时,Python会继续从MRO中的下一个类开始搜索。只要每一个重新定义过的方法都使用了super(),并且只调用了它一次,那么控制流最终就可以便利整个MRO列表,并且让每个方法只会被调用一次。
super()并不是一定要关联到某个类的直接父类上,甚至可以在没有直接父类中使用它。
常常会在定义混合类(Mixin class)时以这种方式使用super()。
首先,确保在继承体系中所有同名的方法都有可兼容的调用签名(参数数量相同,参数名称也相同)。
class A: def spam(self): print('A.spam') super().spam() class B: def spam(self): print('B.spam') class C(A,B): pass >>> c = C() >>> c.spam() A.spam B.spam >>> C.__mro__ (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
八、在子类中扩展属性
想在子类中扩展某个属性的功能,而这个属性是在父类中定义的。
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('Setting name to', value) return super(SubPerson, SubPerson).name.__set__(self,value)
在setter函数中,对super(Subperson,SubPerson).name.__set__(self, value)的调用,需要把控制流传递到之前定义的name属性的__set__()方法中去,
但是唯一能调用到这个方法的方式就是以类变量而不是实例变量的方式去访问。
九、创建一种新形式的类属性或实例属性
创建一种新形式的实例属性,拥有额外的功能,比如类型检查。以描述符的形式定义其功能。
描述符只能在类的层次上定义,不能根据实例来产生。
(基于类装饰器的描述符例子)
class Typed: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, owner): if instance is None: return self 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 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
十、让属性具有惰性求值的能力
当把描述符放到类的定义体中时,访问它的属性会触发__get__()、__set__()和__delete__()方法得到执行。
只有当被访问的属性不在底层的实例字典中时,__get__()方法才会得到调用。
class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print(self,instance,owner) 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
十一、简化数据结构的初始化过程
将初始化数据结构的步骤归纳到一个单独的__init__()函数中,将其定义在一个公共的基类中。
class Structure: _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) for name in self._fields[len(args):]: setattr(self, name, kwargs.pop(name)) if kwargs: raise TypeError('Duplicate values for {}'.format(','.join(kwargs))) class Stock(Structure): _fields = ['name', 'shares', 'price']
利用关键字参数来给类添加额外的属性,这些额外的属性是没有定义在_fields中的。
class Structure: _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))) class Stock(Structure): _fields = ['name', 'shares', 'price'] s1 = Stock('Acme',50,91.2) s1 = Stock('Acme',50,91.2,date = '2/8/2019')
利用frame hack技巧实现自动化实例变量初始化处理,编写一个功能函数(这种方法相对慢50%)
def init_fromlocals(self): import sys locs = sys._getframe(1).f_locals for k,v in locs.items(): if k != 'self': setattr(self, k, v) class Stock: def __init__(self, name, shares, prices): init_fromlocals(self)
十二、定义一个接口或抽象基类
定义一个类作为接口或者是抽象基类,这样可以在此之上执行类型检查并确保在子类中实现特定的方法。
抽象基类的核心特征是:不能被直接实例化。
抽象基类是给其他的类当做基类使用的,这些子类需要实现基类中要求的那些方法。主要用途是:强制规定所需的编程接口。
@abstractmethod同样可以施加到静态方法、类方法和property属性上。@abstractmethod要紧挨这函数定义。
from abc import abstractmethod,ABCMeta class IStream(metaclass=ABCMeta): @abstractmethod def read(self, maxbytes=-1): pass @abstractmethod def write(self, data): pass
抽象基类也允许其他的类向其注册,然后实现所需的接口
import io IStream.register(io.IOBase)
f = open('1.txt') isinstance(f, IStream) # True
十三、实现一种数据模型或类型系统
对某些实例属性赋值时进行检查。 自定义属性赋值函数,这种情况下最好使用描述器。
# Base class. Uses a descriptor to set a value 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 # Descriptor for enforcing types class Typed(Descriptor): expected_type = type(None) def __set__(self, instance, value): if not isinstance(value, self.expected_type): raise TypeError('expected ' + str(self.expected_type)) super().__set__(instance, value) # Descriptor for enforcing values 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 be < ' + 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: # Specify constraints 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
(1)使用装饰器简化代码
def check_attributes(**kwargs): def decorate(cls): for key, value in kwargs.items(): if isinstance(value, Descriptor): setattr(cls, key, value) else: setattr(cls, key, value(key)) return cls return decorate @check_attributes(name=SizedString(size=8), shares=UnsignedInteger, price=UnsignedFloat) class Stock: def __init__(self, name, shares, prices): self.name = name self.shares = shares self.prices = prices
(2)使用元类简化代码:
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.shares = shares self.price = price
(3)类装饰器备选方案:
# Decorator for applying type checking def Typed(expected_type, cls=None): if cls is None: return lambda cls: Typed(expected_type, cls) super_set = cls.__set__ def __set__(self, instance, value): if not isinstance(value, expected_type): raise TypeError('expected ' + str(expected_type)) super_set(self, instance, value) cls.__set__ = __set__ return cls # Decorator for unsigned values def Unsigned(cls): super_set = cls.__set__ def __set__(self, instance, value): if value < 0: raise ValueError('Expected >= 0') super_set(self, instance, value) cls.__set__ = __set__ return cls # Decorator for allowing sized values def MaxSized(cls): super_init = cls.__init__ def __init__(self, name=None, **opts): if 'size' not in opts: raise TypeError('missing size option') super_init(self, name, **opts) cls.__init__ = __init__ super_set = cls.__set__ def __set__(self, instance, value): if len(value) >= self.size: raise ValueError('size must be < ' + str(self.size)) super_set(self, instance, value) cls.__set__ = __set__ return cls # Specialized descriptors @Typed(int) class Integer(Descriptor): pass @Unsigned class UnsignedInteger(Integer): pass @Typed(float) class Float(Descriptor): pass @Unsigned class UnsignedFloat(Float): pass @Typed(str) class String(Descriptor): pass @MaxSized class SizedString(String): pass
十四、实现自定义的容器
实现一个自定义的类,用来模仿普通的内建容器类型比如列表或者字典的行为。
collections 定义了很多抽象基类,当你想自定义容器类的时候它们会非常有用。
包括Sequence、MutableSequence、Mapping、MutableMapping、Set以及MutableSet
这些类中有许多事按照功能层次的递增来排列的如:Container、Iterable、Sized、Sqeuence以及MutableSequence。
这样对这些类进行实例化操作,就可以知道需要实现哪些方法才能让自定义的容器具有相同的行为:
>>> import collections >>> collections.Sequence() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class Sequence with abstract methods \ __getitem__, __len__
例子:创建一个 Sequence类,元素总是一排序后的顺序进行存储:
class SortedItems(collections.Sequence): def __init__(self, initial=None): self._items = sorted(initial) if initial is not None else [] # Required sequence methods def __getitem__(self, index): return self._items[index] def __len__(self): return len(self._items) # Method for adding an item in the right location def add(self, item): bisect.insort(self._items, item)
SortedItems的实例表现出的行为和普通序列对象完全一样。支持所有常见的操作索引、迭代、len()、是否包含(in操作符)甚至是分片
collections库中提供的抽象基类,便于我们做类型检查。
>>> items = SortedItems() >>> import collections >>> isinstance(items, collections.Iterable) True >>> isinstance(items, collections.Sequence) True >>> isinstance(items, collections.Container) True >>> isinstance(items, collections.Sized) True >>> isinstance(items, collections.Mapping) False
例子:创建一个Items类,支持列表所有核心方法,如append()、remove()、count()等。
class Items(collections.MutableSequence): def __init__(self, initial=None): self._items = list(initial) if initial is not None else [] # Required sequence methods def __getitem__(self, index): print('Getting:', index) return self._items[index] def __setitem__(self, index, value): print('Setting:', index, value) self._items[index] = value def __delitem__(self, index): print('Deleting:', 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)
十五、委托属性的访问
访问实例的属性时能够将其委托(delegate)到一个内部持有的对象上。
将某个特定的操作转交给另一个不同的对象实现。
class A: def spam(self, x): pass def foo(self): pass class B: """使用__getattr__的代理,代理方法比较多时候""" def __init__(self): self._a = A() def bar(self): pass # Expose all of the methods defined on class A def __getattr__(self, name): """这个方法在访问的attribute不存在的时候被调用 the __getattr__() method is actually a fallback method that only gets called when an attribute is not found""" return getattr(self._a, name)
上述例子,在访问B中未定义的方法时就能把这个操作委托给A。
实现代理例子:
# A proxy class that wraps around another object, but # exposes its public attributes class Proxy: def __init__(self, obj): self._obj = obj # Delegate attribute lookup to internal obj def __getattr__(self, name): print('getattr:', name) return getattr(self._obj, name) # Delegate attribute assignment def __setattr__(self, name, value): if name.startswith('_'): super().__setattr__(name, value) else: print('setattr:', name, value) setattr(self._obj, name, value) # Delegate attribute deletion 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) # Create an instance s = Spam(2) # Create a proxy around it p = Proxy(s) # Access the proxy print(p.x) # Outputs 2 p.bar(3) # Outputs "Spam.bar: 2 3" p.x = 37 # Changes s.x to 37
利用委托替代继承:
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)
首先,__getattr__()实际上是一个回滚方法,只会在某个属性没有找到的时候才会调用。
因此,如果访问的是代理实例本身的属性,这个方法就不会被触发调用。
其次,__setattr__()和__delattr__()方法需要添加一点额外的逻辑来区分代理实例本身的属性和内部对象_obj上的属性。
同时,__getattr__()方法不适用于大部分名称以双下划线开头和结尾的特殊方法,需要自己手动实现。
class ListLike: """__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义""" def __init__(self): self._items = [] def __getattr__(self, name): return getattr(self._items, name) # Added special methods to support certain list operations 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]
十六、类中定义多个构造函数
能够以多种方式创建实例,而不局限于__init__()。
要定义一个含有多个构造函数的类,应该使用类方法。
类方法的一个关键特性就是把类作为其接收的第一个参数(cls)。
import time class Date: 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 = Date(2012, 12, 21) # Primary b = Date.today() # Alternate
十七、不通过调用__init__来创建实例
直接调用类的__new__()方法来创建一个未初始化的实例。
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day >>> d = Date.__new__(Date) >>> d <__main__.Date object at 0x1006716d0> >>> d.year Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Date' object has no attribute 'year'
给实例变量设定合适的初始值
>>> data = {'year':2012, 'month':8, 'day':29} >>> for key, value in data.items(): ... setattr(d, key, value) ... >>> d.year 2012 >>> d.month 8
十八、Mixin技术来扩展类定义
有一些十分有用的方法,希望用它们来扩展其他类的功能。但是,需要添加的这些类之间并不一定属于继承关系。因此,没法将这些方法直接关联到一个共同的基类上。
如:日志记录、类型检查等Mixin类,
class LoggedMappingMixin: """ Add logging to get/set/delete operations for debugging. """ __slots__ = () # 混入类都没有实例变量,因为直接实例化混入类没有任何意义 def __getitem__(self, key): print('Getting ' + str(key)) return super().__getitem__(key) def __setitem__(self, key, value): print('Setting {} = {!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('keys must be strings') return super().__setitem__(key, value)
这些类存在的意义就是要和其他映射型类通过多重继承的方式混合在一起使用。
class LoggedDict(LoggedMappingMixin, dict): pass d = LoggedDict() d['x'] = 23 print(d['x']) del d['x'] from collections import defaultdict class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict): pass d = SetOnceDefaultDict(list) d['x'].append(2) d['x'].append(3) # d['x'] = 23 # KeyError: 'x already set'
mixin类和其他已有的类结合在一起。当他们混合在一起时,所有的类通过一起工作提供所需的功能。
mixin类一般来说是没有状态的。意味着mixin类没有__init__()方法,也没有实例变量,没有属于自己的实例数据。
通过使用super(),将这个任务转交给方法解析顺序MRO上的下一个类。
实现mixin的另一种方法是利用类装饰器。
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 {} = {!r}'.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
十九、带有状态的对象或状态机
为每一个状态定义一个单独的类。
class Connection1: """新方案——对每个状态定义一个类""" def __init__(self): self.new_state(ClosedConnectionState) def new_state(self, newstate): self._state = newstate # Delegate to the state class 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) # Connection state base class 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() # Implementation of different states 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 = Connection() >>> c._state <class '__main__.ClosedConnectionState'> >>> c.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "example.py", line 10, in read return self._state.read(self) File "example.py", line 43, in read raise RuntimeError('Not open') RuntimeError: Not open >>> c.open() >>> c._state <class '__main__.OpenConnectionState'> >>> c.read() reading >>> c.write('hello') writing >>> c.close() >>> c._state <class '__main__.ClosedConnectionState'>
二十、调用对象上的方法,方法名以字符串形式给出
(1)使用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) p = Point(2, 3) d = getattr(p, 'distance')(0, 0) # Calls p.distance(0, 0)
(2)使用operator.methodcaller()
import operator operator.methodcaller('distance', 0, 0)(p) points = [ Point(1, 2), Point(3, 0), Point(10, -3), Point(-5, -7), Point(-1, 8), Point(3, 2) ] # Sort by distance from origin (0, 0) points.sort(key=operator.methodcaller('distance', 0, 0))
operator.methodcaller()创建了一个可调用对象,而且把所需的参数提供给了被调用的方法,提供适当的self参数即可。
二十四、让类支持比较操作
为每种比较操作符实现一个特殊方法,Python中的类可以支持比较操作。
functools.total_ordering装饰器可以用来简化这个过程。只需定义一个 __eq__() 方法, 外加其他方法(__lt__, __le__, __gt__, or __ge__)中的一个即可。
from functools import total_ordering class Room: def __init__(self, name, length, width): self.name = name self.length = length self.width = width self.square_feet = self.length * self.width @total_ordering class House: def __init__(self, name, style): self.name = name self.style = style self.rooms = list() @property def living_space_footage(self): return sum(r.square_feet for r in self.rooms) def add_room(self, room): self.rooms.append(room) def __str__(self): return '{}: {} square foot {}'.format(self.name, self.living_space_footage, self.style) def __eq__(self, other): return self.living_space_footage == other.living_space_footage def __lt__(self, other): return self.living_space_footage < other.living_space_footage
二十五、创建缓存实例
创建类实例时想返回一个缓存引用,让其指向上一个用同样参数创建出的类实例。
logging模块中,给定的一个名称只会关联到一个单独的logger实例。
>>> import logging >>> a = logging.getLogger('foo') >>> b = logging.getLogger('bar') >>> a is b False >>> c = logging.getLogger('foo') >>> a is c True
用一个于类本身相分离的工厂函数:
# The class in question class Spam: def __init__(self, name): self.name = name # Caching support import weakref _spam_cache = weakref.WeakValueDictionary() def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s
(2)修改__new__()方法来实现:
import weakref class Spam: _spam_cache = weakref.WeakValueDictionary() def __new__(cls, name): if name in cls._spam_cache: return cls._spam_cache[name] else: self = super().__new__(cls) cls._spam_cache[name] = self return self def __init__(self, name): print('Initializing Spam') self.name = name
(3)将缓存代码放到另一个单独的管理类中。
import weakref class CachedSpamManager: def __init__(self): self._cache = weakref.WeakValueDictionary() def get_spam(self, name): if name not in self._cache: s = Spam(name) self._cache[name] = s else: s = self._cache[name] return s def clear(self): self._cache.clear() class Spam: manager = CachedSpamManager() def __init__(self, name): self.name = name def get_spam(name): return Spam.manager.get_spam(name)
(4)防止用户直接实例化类,可以将类名用_开头,用类方法创建或者让__init__()抛出异常。