改变对象的字符串显示
问题:
你想改变对象实例的打印或显示输出,让它们更具可读性
解决方案:
要改变一个实例的字符串表示,可重新定义它的str () 和repr () 方法。例如:
1 class Pair: 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 6 def __repr__(self): 7 #另外一种写法 return 'Pair(%r,%r)'%(self.x, self.y ) 8 return 'Pair({0.x!r}, {0.y!r})'.format(self) 9 10 def __str__(self): 11 return '({0.x!s}, {0.y!s})'.format(self) 12 13 p = Pair(3, 4) 14 15 print("__str__()的输出效果:", p) 16 17 print('__repr__()的输出效果:', repr(p))
以上代码执行的结果为:
__str__()的输出效果: (3, 4) __repr__()的输出效果: Pair(3, 4)
自定义字符串的格式化
问题:
你想通过format() 函数和字符串方法使得一个对象能支持自定义的格式化。
解决方案:
为了自定义字符串的格式化,我们需要在类上面定义format () 方法。例如:
1 _formats = { 2 'ymd' : '{d.year}-{d.month}-{d.day}', 3 'mdy' : '{d.month}/{d.day}/{d.year}', 4 'dmy' : '{d.day}/{d.month}/{d.year}' 5 } 6 7 class Date: 8 def __init__(self, year, month, day): 9 self.year = year 10 self.month = month 11 self.day = day 12 13 def __format__(self, format_spec): 14 if format_spec == '': 15 format_spec = 'ymd' 16 17 fmt = _formats[format_spec] 18 return fmt.format(d=self) 19 20 d = Date(2017, 8, 7) 21 print('默认格式的输出:', format(d)) 22 23 print('mdy类型的输出:', format(d, 'mdy')) 24 25 print('The date is {:ymd}'.format(d)) 26 27 print('The date is {:mdy}'.format(d)) 28 29 from datetime import date 30 d = date(2017, 8, 7) 31 print(format(d)) 32 33 print(format(d,'%A, %B %d, %Y')) 34 35 print('The end is {:%d %b %Y}. Goodbye'.format(d))
以上代码执行的结果为:
默认格式的输出: 2017-8-7 mdy类型的输出: 8/7/2017 The date is 2017-8-7 The date is 8/7/2017 2017-08-07 Monday, August 07, 2017 The end is 07 Aug 2017. Goodbye
让对象支持上下文管理协议
问题:
你想让你的对象支持上下文管理协议(with 语句)
解决方案:
为了让一个对象兼容with 语句,你需要实现enter () 和exit () 方法。例如,考虑如下的一个类,它能为我们创建一个网络连接:
1 from socket import socket,AF_INET,SOCK_STREAM 2 3 class LazyConnection: 4 ''' 5 连接端 6 ''' 7 def __init__(self, addresss, family=AF_INET, type=SOCK_STREAM): 8 self.address = addresss 9 self.family = family 10 self.type = type 11 self.sock = None 12 13 def __enter__(self): 14 if self.sock is not None: 15 raise RuntimeError('Already connected') 16 17 self.sock = socket(self.family, self.type) 18 self.sock.connect(self.address) 19 return self.sock 20 21 def __exit__(self, exc_type, exc_val, exc_tb): 22 self.sock.close() 23 self.sock = None 24 25 26 from functools import partial 27 28 conn = LazyConnection(('www.python.org', 80)) 29 30 with conn as s: 31 ''' 32 通过with管理上下文 33 ''' 34 # conn.__enter__() executes: connection open 35 s.send(b'GET /index.html HTTP/1.0 ') 36 s.send(b'Host: www.python.org ') 37 s.send(b' ') 38 resp = b''.join(iter(partial(s.recv, 8192), b'')).decode('utf-8') 39 # conn.__exit__() executes: connection closed 40 print(resp)
以上代码执行的结果为:
HTTP/1.1 301 Moved Permanently Server: Varnish Retry-After: 0 Location: https://www.python.org/index.html Content-Length: 0 Accept-Ranges: bytes Date: Mon, 07 Aug 2017 02:34:49 GMT Via: 1.1 varnish Connection: close X-Served-By: cache-nrt6134-NRT X-Cache: HIT X-Cache-Hits: 0 X-Timer: S1502073289.426123,VS0,VE0 Strict-Transport-Security: max-age=63072000; includeSubDomains
总结:
编写上下文管理器的主要原理是你的代码会放到with 语句块中执行。当出现with语句的时候,对象的enter () 方法被触发,它返回的值(如果有的话) 会被赋值给as 声明的变量。然后,with 语句块里面的代码开始执行。最后, exit () 方法被触发进行清理工作。
不管with 代码块中发生什么,上面的控制流都会执行完,就算代码块中发生了异常也是一样的。事实上, exit () 方法的第三个参数包含了异常类型、异常值和追溯信息(如果有的话)。exit () 方法能自己决定怎样利用这个异常信息,或者忽略它并返回一个None 值。如果exit () 返回True ,那么异常会被清空,就好像什么都没发生一样, with 语句后面的程序继续在正常执行。
创建大量对象时节省内存方法
问题:
你的程序要创建大量(可能上百万) 的对象,导致占用很大的内存
解决方案:
对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加slots 属性来极大的减少实例所占的内存。比如:
1 class Date: 2 __slots__ = ['year', 'month', 'day'] 3 def __init__(self, year, month, day): 4 self.year = year 5 self.month = month 6 self.day = day
注意:
当你定义slots 后,Python 就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在slots 中列出的属性名在内部被映射到这个数组的指定小标上。使用slots 一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在slots中定义的那些属性名
在类中封装属性名
问题:
你想封装类的实例上面的“私有”数据,但是Python 语言并没有访问控制
解决方案:
Python 程序员不去依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名规约来达到这个效果。第一个约定是任何以单下划线_开头的名字都应该是内部实现。比如:
1 class A: 2 def __init__(self): 3 self._internal = 0 4 self.public = 1 5 6 def public_method(self): 7 print('public method') 8 9 def _internal_method(self): 10 print('internal method')
你还可能会遇到在类定义中使用两个下划线(__ ) 开头的命名。比如:
1 class B: 2 def __init__(self): 3 self.__private = 0 4 5 def __private_method(self): 6 print('private method...外部不能调用,只能内部中调用') 7 8 def public_method(self): 9 print('公用方法调用内部的__private_method..') 10 self.__private_method()
使用双下划线开始会导致访问名称变成其他形式。比如,在前面的类B 中,私有属性会被分别重命名为_B__ private 和_B __private method 。这时候你可能会问这样重命名的目的是什么,答案就是继承——这种属性通过继承是无法被覆盖的。比如:
1 class C(B): 2 ''' 3 C类中的__private 在外部调用为_C__private 4 C类中的__private_method在外部调用为_C__private_method 5 ''' 6 7 def __init__(self): 8 super().__init__() 9 self.__private = 1 #这样写就不会覆盖掉B中的__private 10 11 def __private_mothod(self): #同样不会覆盖掉B中的__private_method 12 print('C类中的private_mothd')
总结:
上面提到有两种不同的编码约定(单下划线和双下划线) 来命名私有属性,那么问题就来了:到底哪种方式好呢?大多数而言,你应该让你的非公共名称以单下划线开头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。
创建可管理的属性
问题:
你想给某个实例attribute 增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证
解决方案:
自定义某个属性的一种简单方法是将它定义为一个property。例如,下面的代码定义了一个property,增加对一个属性简单的类型检查:
1 class Person: 2 def __init__(self, first_name): 3 self.first_name = first_name 4 5 @property #把方法变成一个属性,外部调用的时候,不需要加上() 6 def first_name(self): 7 return self._first_name 8 9 @first_name.setter #给frist_name方法接收一个值作为self._first_name的返回 10 def first_name(self, value): 11 if not isinstance(value, str): 12 raise TypeError('Expected a string') 13 self._first_name = value 14 15 @first_name.deleter #如果first_name属性没被创建则报错 16 def first_name(self): 17 raise ArithmeticError("Can't delete attribute") 18 19 a = Person('demon') 20 print(a.first_name) 21 22 #a.first_name = 50 #报错raise TypeError('Expected a string') 23 24 #del a.first_name #报错ArithmeticError: Can't delete attribute
以上代码执行的结果为:
demon
使用已存在的get 和set 方法基础上定义property。例如:
1 class Person: 2 def __init__(self, first_name): 3 self.set_first_name(first_name) 4 5 def get_first_name(self): 6 return self._first_name 7 8 def set_first_name(self, value): 9 if not isinstance(value, str): 10 raise TypeError('Expected a string') 11 self._first_name = value 12 13 def del_first_name(self): 14 raise AttributeError("Can't delete attribute") 15 16 name = property(get_first_name, set_first_name, del_first_name) 17
调用父类方法
问题:
你想在子类中调用父类的某个已经被覆盖的方法
解决方案:
为了调用父类(超类) 的一个方法,可以使用super() 函数,比如:
1 class A: 2 def spam(self): 3 print('A.spam') 4 5 class B(A): 6 def spam(self): 7 print('B.spam') 8 super().spam() 9 10 a = A() 11 b = B() 12 13 a.spam() #调用A类中的spam方法 14 print('我是分割符'.center(50, '*')) 15 b.spam() #先调用B类中的spam方法,然后通过super继承A类中的spam方法返回输出
以上代码执行的结果为:
A.spam **********************我是分割符*********************** B.spam A.spam
super() 函数的一个常见用法是在init () 方法中确保父类被正确的初始化了:
1 class A: 2 def __init__(self): 3 self.x = 0 4 5 class B(A): 6 def __init__(self): 7 super().__init__() 8 self.y = 1 9
super() 的另外一个常见用法出现在覆盖Python 特殊方法的代码中,比如:
1 class Proxy: 2 def __init__(self, obj): 3 self._obj = obj 4 5 def __getattr__(self, name): 6 return getattr(self._obj, name) 7 8 def __setattr__(self, name, value): 9 if name.startswith('_'): 10 super().__setattr__(name, value) #__setattr__()实现包含一个名字检查,如果某个属性名称以_开头,就通过super调用 11 else: 12 setattr(self._obj, name , value) #否则就委派给内部的_obj来处理
在上面代码中, __setattr__ () 的实现包含一个名字检查。如果某个属性名以下划线( _) 开头,就通过super() 调用原始的__setattr__ () ,否则的话就委派给内部的代理对象self. _obj 去处理。这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。
子类中扩展property
问题:
在子类中,你想要扩展定义在父类中的property 的功能
解决方案:
考虑如下的代码,它定义了一个property:
1 class Person: 2 def __init__(self, name): 3 self._name = name 4 5 @property 6 def name(self): 7 return self._name 8 9 @name.setter 10 def name(self, value): 11 if not isinstance(value, str): 12 raise TypeError('Expected a string') 13 self._name = value 14 15 @name.deleter 16 def name(self): 17 raise AttributeError("Can't delete attribute") 18 19 20 class SubPerson(Person): 21 @Person.name.getter 22 def name(self): 23 print('Getting name') 24 return super().name 25 26 @Person.name.setter 27 def name(self, value): 28 print('Setting name to', value) 29 super(SubPerson, SubPerson).name.__set__(self, value)
创建新的类或实例属性
问题:
你想创建一个新的拥有一些额外功能的实例属性类型,比如类型检查
解决方案:
如果你想创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功能。下面是一个例子:
1 class Integer: 2 def __init__(self, name): 3 self.name = name 4 5 def __get__(self, instance, cls): 6 if instance is None: 7 return self 8 else: 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 if not isinstance(value, int): 13 raise TypeError('Expected an int') 14 instance.__dict__[self.name] = value 15 16 def __delattr__(self, instance): 17 del instance.__dict__[self.name] 18 19 20 class Point: 21 x = Integer('x') 22 y = Integer('y') 23 24 def __init__(self, x, y): 25 self.x = x 26 self.y = y 27 28 p = Point(2, 3) 29 print(p.x) #x.__get__(p, Point) 30 print(p.y) 31 32 print(Point.x) #x.__get__(None, Point)
super() 的另外一个常见用法出现在覆盖Python 特殊方法的代码中,比如:
2
3
<__main__.Integer object at 0x101c456a0>
使用延迟计算属性
问题:
你想将一个只读属性定义成一个property,并且只在访问的时候才会计算结果。但是一旦被访问后,你希望结果值被缓存起来,不用每次都去计算
解决方案:
定义一个延迟属性的一种高效方法是通过使用一个描述器类,如下所示:
1 class lazyproperty: 2 def __init__(self, func): 3 self.func = func 4 5 def __get__(self, instance, cls): 6 if instance is None: 7 return self 8 else: 9 value = self.func(instance) 10 setattr(instance, self.func.__name__, value) 11 return value 12 13 14 import math 15 16 17 class Circle: 18 def __init__(self, radius): 19 self.radius = radius 20 21 @lazyproperty 22 def area(self): 23 print('Computing area') 24 return math.pi * self.radius ** 2 25 26 @lazyproperty 27 def perimeter(self): 28 print('Computing perimeter') 29 return 2 * math.pi * self.radius 30 31 32 c = Circle(4.0) 33 print('radius:', c.radius) 34 print('area:', c.area) 35 print('perimeter:', c.perimeter)
以上代码执行的结果为:
radius: 4.0 Computing area area: 50.26548245743669 Computing perimeter perimeter: 25.132741228718345
简化数据结构的初始化
问题:
你写了很多仅仅用作数据结构的类,不想写太多烦人的init () 函数
解决方案:
可以在一个基类中写一个公用的init () 函数:
1 import math 2 3 4 class Structurel: 5 _fields = [] 6 7 def __init__(self, *args): 8 if len(args) != len(self._fields): 9 raise TypeError('Expected {} arguments'.format(len(self._fields))) 10 for name,value in zip(self._fields, args): 11 setattr(self, name, value) 12 13 14 class Stock(Structurel): 15 _fields = ['name', 'shares', 'price'] 16 17 18 class Point(Structurel): 19 _fields = ['x', 'y'] 20 21 22 class Circle(Structurel): 23 _fields = ['radius'] 24 25 def area(self): 26 return math.pi * self.radius ** 2 27 28 s = Stock('ACME', 50, 91.1) 29 p = Point(2, 3) 30 c = Circle(4.5) 31 print(c.area()) 32 # c = Stock('ACME', 50) #报错,会抛出raise的TypeError的异常
以上代码执行的结果为:
63.61725123519331
定义接口或者抽象基类
问题:
你想定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法
解决方案:
使用abc 模块可以很轻松的定义抽象基类:
1 from abc import ABCMeta, abstractclassmethod 2 3 class IStream(metaclass=ABCMeta): 4 @abstractclassmethod 5 def read(self, maxbytes=-1): 6 pass 7 8 @abstractclassmethod 9 def write(self, data): 10 pass 11 12 #抽象类的一个特点是它不能直接被实例化,比如你想像下面这样做是不行的: 13 #a = IStream() TypeError Can't instantiate abstract class 14 15 #抽象类的目的就是让别的类继承它并实现特定的抽象方法: 16 class SocketStream(IStream): 17 def read(self, maxbytes=-1): 18 pass 19 20 def write(self, data): 21 pass
实现数据模型的类型约束
问题:
你想定义某些在属性赋值上面有限制的数据结构
解决方案:
在这个问题中,你需要在对某些实例属性赋值时进行检查。所以你要自定义属性赋值函数,这种情况下最好使用描述
1 class Descriptor: 2 def __init__(self, name=None, **opts): 3 self.name = name 4 for key,value in opts.items(): 5 setattr(self, key, value) 6 7 def __set__(self, instance, value): 8 instance.__dict__[self.name] = value 9 10 11 class Typed(Descriptor): 12 expected_type = type(None) 13 14 def __set__(self, instance, value): 15 if not isinstance(instance, self.expected_type): 16 raise TypeError('expected ' + str(self.expected_type)) 17 super().__set__(instance, value) 18 19 20 class Unsigned(Descriptor): 21 def __set__(self, instance, value): 22 if value <= 0: 23 raise ValueError('Expected >= 0') 24 super().__set__(instance, value) 25 26 27 class MaxSized(Descriptor): 28 def __init__(self, name=None, **opts): 29 if 'size' not in opts: 30 raise TypeError('missing size option') 31 super().__init__(name, **opts) 32 33 def __set__(self, instance, value): 34 if len(value) >= self.size: 35 raise ValueError('size must be < ' + str(self.size)) 36 super().__set__(instance, value) 37 38 39 class Integer(Typed): 40 expected_type = int 41 42 43 class UnsignedInteger(Integer, Unsigned): 44 pass 45 46 47 class Float(Typed): 48 expected_type = float 49 50 51 class UnsignedFloat(Float, Unsigned): 52 pass 53 54 55 class String(Typed): 56 expected_type = str 57 58 59 class SizedString(String, MaxSized): 60 pass 61 62 63 class Stock: 64 name = SizedString('name', size=8) 65 shares = UnsignedInteger('shares') 66 price = UnsignedFloat('price') 67 68 def __init__(self, name, shares, price): 69 self.name = name 70 self.shares = shares 71 self.price = price
实现自定义容器
问题:
你想实现一个自定义的类来模拟内置的容器类功能,比如列表和字典。但是你不确定到底要实现哪些方法
解决方案:
collections 定义了很多抽象基类,当你想自定义容器类的时候它们会非常有用。比如你想让你的类支持迭代,那就让你的类继承collections.Iterable 即可:
1 from collections import Iterable 2 3 4 class A(Iterable): 5 6 def __iter__(self): 7 return iter() 8 9 10 11 12 #另外一种实现的方式 13 import bisect 14 import collections 15 16 class SortedItem(collections.Sequence): 17 def __init__(self, initial=None): 18 self._item = sorted(initial) if initial is not None else [] 19 20 def __getitem__(self, index): 21 return self._item[index] 22 23 def __len__(self): 24 return len(self._item) 25 26 def add(self, item): 27 bisect.insort(self._item, item) #可以保证元素插入后还保持顺序 28 29 30 items = SortedItem([5, 1, 3]) 31 print('列表排序的结果'.center(30, '*')) 32 print(list(items)) 33 print('获取列表索引位置的值'.center(30, '-')) 34 print(items[0], items[-1]) 35 print('添加元素以后的列表'.center(30, '*')) 36 items.add(2) 37 print(list(items))
以上代码执行的结果为:
***********列表排序的结果************ [1, 3, 5] ----------获取列表索引位置的值---------- 1 5 **********添加元素以后的列表*********** [1, 2, 3, 5]
collections 中很多抽象类会为一些常见容器操作提供默认的实现, 这样一来你只需要实现那些你最感兴趣的方法即可。假设你的类继承自collections.MutableSequence ,如下:
1 import collections 2 3 class Item(collections.MutableSequence): 4 def __init__(self, initial=None): 5 self._item = initial if initial is not None else [] 6 7 def __getitem__(self, index): 8 print('Getting:', index) 9 return self._item[index] 10 11 def __setitem__(self, index, value): 12 print('Setting', index, value) 13 self._item[index] = value 14 15 def __delitem__(self, index): 16 print('Deleting:', index) 17 del self._item[index] 18 19 def insert(self, index, value): 20 print('Inserting:', index, value) 21 self._item.insert(index, value) 22 23 def __len__(self): 24 return len(self._item) 25 26 a = Item([1, 2, 3, 4, 5]) 27 print('长度:', len(a)) 28 29 a.append(10) 30 a.append(2) 31 print(list(a)) 32 print('2在a列表中出现的次数:', a.count(2)) 33 a.remove(10) 34 print('删除列表中元素为10:', list(a))
以上代码执行的结果为:
长度: 5 Inserting: 5 10 Inserting: 6 2 Getting: 0 Getting: 1 Getting: 2 Getting: 3 Getting: 4 Getting: 5 Getting: 6 Getting: 7 [1, 2, 3, 4, 5, 10, 2] Getting: 0 Getting: 1 Getting: 2 Getting: 3 Getting: 4 Getting: 5 Getting: 6 Getting: 7 2在a列表中出现的次数: 2 Getting: 0 Getting: 1 Getting: 2 Getting: 3 Getting: 4 Getting: 5 Deleting: 5 Getting: 0 Getting: 1 Getting: 2 Getting: 3 Getting: 4 Getting: 5 Getting: 6 删除列表中元素为10: [1, 2, 3, 4, 5, 2]
属性的代理访问
问题:
你想将某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式
解决方案:
简单来说,代理是一种编程模式,它将某个操作转移给另外一个对象来实现。最简单的形式可能是像下面这样:
1 class A: 2 def spam(self, x): 3 self._x = x 4 5 def foo(self): 6 pass 7 8 9 class B1: 10 '''简单代理''' 11 def __init__(self): 12 self._a = A() 13 14 def spam(self, x): 15 return self._a.spam(x) 16 17 def foo(self): 18 return self._a.foo() 19 20 def bar(self): 21 pass 22 23 24 #如果有大量的方法需要代理,那么使用getattr () 方法或许或更好些 25 class B2: 26 def __init__(self): 27 self._a = A() 28 29 def bar(self): 30 pass 31 32 def __getattr__(self, name): 33 return getattr(self._a, name)
另外一个代理例子是实现代理模式,例如:
1 #简单的实现代理模式 2 class Proxy: 3 def __init__(self, obj): 4 self._obj = obj 5 6 def __getattr__(self, name): 7 print('getattr:', name) 8 return getattr(self._obj, name) 9 10 def __setattr__(self, name, value): 11 if name.startswith('_'): 12 super().__setattr__(name, value) 13 else: 14 print('setattr:', name, value) 15 setattr(self._obj, name, value) 16 17 def __delattr__(self, name): 18 if name.startswith('_'): 19 super().__delattr__(name) 20 else: 21 print('delattr:', name) 22 delattr(self._obj, name) 23 24 25 class Spam: 26 def __init__(self, x): 27 self.x = x 28 29 def bar(self, y): 30 print('Spam.bar:', self.x, y) 31 32 33 #实例化Spam 34 s = Spam(2) 35 #实例化Proxy,并把实例化的Spam传入进去 36 p = Proxy(s) 37 #调用Proxy中的getattr 38 print(p.x) 39 #调用Proxy中的bar方法 40 p.bar(3) 41 #切换x值 42 p.x = 30 43 print(p.x)
以上代码执行的结果为:
getattr: x 2 getattr: bar Spam.bar: 2 3 setattr: x 30 getattr: x 30
在类中定义多个构造器
问题:
你想实现一个类,除了使用init () 方法外,还有其他方式可以初始化它
解决方案:
为了实现多个构造器,你需要使用到类方法。例如:
1 import time 2 3 4 class Date: 5 ''' 6 方法一:使用类方法 7 ''' 8 def __init__(self, year, month, day): 9 self.year = year 10 self.month = month 11 self.day = day 12 13 @classmethod 14 def today(cls): 15 t = time.localtime() 16 return cls(t.tm_year, t.tm_mon, t.tm_mday) 17 18 19 20 class NewDate(Date): 21 pass 22 23 a = Date(2017, 8, 8) 24 b = Date.today() 25 c = NewDate.today() #创建并返回最终的实例
创建不调用init 方法的实例
问题:
你想创建一个实例,但是希望绕过执行init () 方法
解决方案:
可以通过new () 方法创建一个未初始化的实例。例如考虑如下这个类:
1 class Date: 2 def __init__(self, year, month, day): 3 self.year = year 4 self.month = month 5 self.day = day 6 7 d = Date(2017, 8, 8) 8 print('可以使用init来初始化的:', d.year) 9 10 ''' 11 d1 = Date.__new__(Date) 12 print(d1.day) 13 #会报这个错误 AttributeError: 'Date' object has no attribute 'day' 14 ''' 15 #解决方法如下.这个Date day 还不存在,所以你需要手动初始化 16 data = {'year':2017, 'month':8 , 'day':8} 17 d1 = Date.__new__(Date) 18 for key,value in data.items(): 19 setattr(d1, key, value) 20 21 print('需要手动设置并初始化的(new):', d1.day)
以上代码执行的结果为:
可以使用init来初始化的: 2017
需要手动设置并初始化的(new): 8
当我们在反序列对象或者实现某个类方法构造函数时需要绕过init () 方法来创建对象。例如,对于上面的Date 来来讲,有时候你可能会像下面这样定义一个新的构造函数today() :
1 from time import localtime 2 3 class Date: 4 def __init__(self, year, month, day): 5 self.year = year 6 self.month = month 7 self.day = day 8 9 @classmethod 10 def today(cls): 11 d = cls.__new__(cls) 12 t = localtime() 13 d.year = t.tm_year 14 d.month = t.tm_mon 15 d.day = t.tm_mday 16 return d 17 18 d = Date(2017, 8, 8)
让类支持比较操作
问题:
你想让某个类的实例支持标准的比较运算(比如>=,!=,<=,< 等),但是又不想去实现那一大丢的特殊方法
解决方案:
装饰器functools.total ordering 就是用来简化这个处理的。使用它来装饰一个来,你只需定义一个eq () 方法,外加其他方法( lt , le , gt , or ge ) 中的一个即可。然后装饰器会自动为你填充其它比较方法
作为例子,我们构建一些房子,然后给它们增加一些房间,最后通过房子大小来比较它们:
1 from functools import total_ordering 2 3 4 class Room: 5 def __init__(self, name, length, width): 6 self.name = name 7 self.length = length 8 self.width = width 9 self.square_feet = self.length * self.width 10 11 12 @total_ordering 13 class House: 14 def __init__(self, name, style): 15 self.name = name 16 self.style = style 17 self.rooms = list() 18 19 @property 20 def living_space_footage(self): 21 return sum(r.square_feet for r in self.rooms) 22 23 def add_room(self, room): 24 self.rooms.append(room) 25 26 def __str__(self): 27 '{}: {} square foot {}'.format(self.name, 28 self.living_space_footage, 29 self.style) 30 31 def __eq__(self, other): 32 return self.living_space_footage == other.living_space_footage 33 34 def __lt__(self, other): 35 return self.living_space_footage < other.living_space_footage 36 37 def __gt__(self, other): 38 return self.living_space_footage > other.living_space_footage 39 40 41 h1 = House('h1', 'Cape') 42 h1.add_room(Room('Master Bedroom', 14, 21)) 43 h1.add_room(Room('Living Room', 18, 20)) 44 h1.add_room(Room('Kitchen', 12, 16)) 45 h1.add_room(Room('Office', 12, 12)) 46 h2 = House('h2', 'Ranch') 47 h2.add_room(Room('Master Bedroom', 14, 21)) 48 h2.add_room(Room('Living Room', 18, 20)) 49 h2.add_room(Room('Kitchen', 12, 16)) 50 h3 = House('h3', 'Split') 51 h3.add_room(Room('Master Bedroom', 14, 21)) 52 h3.add_room(Room('Living Room', 18, 20)) 53 h3.add_room(Room('Office', 12, 16)) 54 h3.add_room(Room('Kitchen', 15, 17)) 55 56 houses = [h1, h2, h3] 57 58 print('Is h1 bigger than h2?', h1 > h2) # prints True 59 print('Is h2 smaller than h3?', h2 < h3) # prints True 60 print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False 61 #print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split' 62 #print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch
以上代码执行的结果为:
Is h1 bigger than h2? True Is h2 smaller than h3? True Is h2 greater than or equal to h1? False