2.5. 类和对象
2.5.1. 改变对象的字符串显示
要改变一个实例的字符串表示,可重新定义它的 __str__() 和 __repr__() 方法
如果 __str__() 没有被定义,那么就会使用 __repr__() 来代替输出。
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)
2.5.2. 自定义字符串显示
为了自定义字符串的格式化,我们需要在类上面定义 __format__() 方法。
_formats = {
'ymd' : '{d.year}-{d.month}-{d.day}',
'mdy' : '{d.month}/{d.day}/{d.year}',
'dmy' : '{d.day}/{d.month}/{d.year}',
# 'y': '{d.year}',
# 'm': '{d.month}',
# 'd': '{d.day}',
}
class MyDate:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
dt = MyDate(2022, 1, 1)
print(format(dt, 'mdy'))
print(format(dt, 'dmy'))
print(format(dt, 'ymd'))
# print(format(dt, 'y m d'))
# print('The date is {:ymd}'.format(dt))
print(format(dt, ''))
2.5.3. 让对象支持上下文管理协议
为了让一个对象兼容 with 语句,你需要实现 __enter__() 和 __exit__() 方法.
import socket
class LazyConnection(object):
def __init__(self, address, family=socket.AF_INET,
socktype=socket.SOCK_STREAM):
self.address = address
self.family = family
self.socktype = socktype
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket.socket(self.family, self.socktype)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_type, exc_val, exc_tb):
self.sock.close()
self.sock = None
print("Connection closed")
from functools import partial
conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
# conn.__enter__() executes: connection open
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
# conn.__exit__() executes: connection closed
2.5.4. 创建大量对象时节省内存方法
对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加 __slots__ 属性来极大的减少实例所占的内存。 当你定义 __slots__ 后,Python就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。 在 __slots__ 中列出的属性名在内部被映射到这个数组的指定小标上。 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在 __slots__ 中定义的那些属性名。
class MyDate:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
2.5.5. 在类中封装属性名
Python程序员不去依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名规约来达到这个效果。 第一个约定是任何以单下划线_开头的名字都应该是内部实现。
2.5.6. 创建可管理的属性
你想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。
class Persion(object):
def __init__(self,first_name) -> None:
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('Expected a string')
self._first_name = value
@first_name.deleter
def first_name(self):
raise AttributeError('Can not delete attribute')
2.5.7. 调用父类方法
为了调用父类(超类)的一个方法,可以使用 super() 函数。
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam() # Call parent spam()
a= A()
b=B()
b.spam()
class AA:
def __init__(self):
self.x = 0
class BB(AA):
def __init__(self):
super().__init__()
self.y = 1
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
2.5.8. 子类中扩展property
class Person:
def __init__(self, name):
self.name = name
# Getter function
@property
def name(self):
return self._name
# Setter function
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._name = value
# Deleter function
@name.deleter
def name(self):
raise AttributeError("Can't delete attribute")
class SubPerson(Person):
@property
def name(self):
print('Getting name')
return super().name
@name.setter
def name(self, value):
print('Setting name to', value)
super(SubPerson, SubPerson).name.__set__(self, value)
@name.deleter
def name(self):
print('Deleting name')
class SubPersonV2(Person):
@Person.name.getter
def name(self):
print('Getting name')
return super().name
sb = SubPerson('Guido')
sb.name
2.5.9. 创建新的类或实例属性
class Interger(object):
def __init__(self,name):
self.name =name
def __get__(self,instance,cls):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self,instance,value):
if not isinstance(value,int):
raise TypeError('Expected an int')
instance.__dict__[self.name] = value
def __delete__(self,instance):
del instance.__dict__[self.name]
class Point(object):
x = Interger('x')
y = Interger('y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(2, 3)
print(p.x)
p.x =5
print(p.x)
p2= Point(4,5)
print(p2.x)
print(p.x)
2.5.10. 使用延迟计算属性
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
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
@lazyproperty
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
c = Circle(4.0)
print(c.radius)
print(c.area)
print(c.area)
2.5.11. 简化数据结构的初始化
import math
class Structure1:
fields = []
def __init__(self, *args):
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)
class Point(Structure1):
fields = ['x', 'y']
class Circle(Structure1):
fields = ['radius']
def area(self):
return math.pi * self.radius ** 2
p = Point(1,2)
print(p.x )
c = Circle(3)
print(c.area())
2.5.12. 定义接口或者抽象基类
标准库中有很多用到抽象基类的地方。collections 模块定义了很多跟容器和迭代器(序列、映射、集合等)有关的抽象基类。
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self, maxbytes=-1):
pass
@abstractmethod
def write(self, data):
pass
class SocketStream(IStream):
def read(self, maxbytes=-1):
pass
def write(self, data):
pass
def serialize(obj, stream):
if not isinstance(stream, IStream):
raise TypeError('Expected an IStream')
pass
import io
# Register the built-in I/O classes as supporting our interface
f = open(__file__, 'r')
print(isinstance(f, IStream))
IStream.register(io.IOBase)
# Open a normal file and type check
print(isinstance(f, IStream)) # Returns True
2.5.13. 实现自定义容器
import collections
collections.Iterable = collections.abc.Iterable
class A(collections.Iterable):
def __iter__(self):
yield 1
pass
a=A()
2.5.14. 代理属性
class A(object):
def spam(self, x):
pass
def foo(self):
pass
class B(object):
def __init__(self) -> None:
self.a = A()
def spam(self, x):
self.a.spam(x)
def foo(self):
self.a.foo()
def bar(self):
pass
a = A()
b = B()
b.spam(1)
b.foo()
b.bar()
class B2:
"""使用__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)
a = A()
b2 = B2()
b2.spam(1)
b2.foo()
b2.bar()
class Proxy(object):
def __init__(self,obj) -> None:
self.obj = obj
def __getattr__(self, name):
return getattr(self.obj,name)
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name,value)
else:
setattr(self.obj,name,value)
def __delattr__(self, name):
if name.startswith('_'):
super().__delattr__(name)
else:
delattr(self.obj,name)
2.5.15. 在类中定义多个构造器
import time
class MyDate:
"""方法一:使用类方法"""
# Primary constructor
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# Alternate constructor
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
class MyDateV2:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
d = cls.__new__(cls)
t = time.localtime()
d.year = t.tm_year
d.month = t.tm_mon
d.day = t.tm_mday
return d
2.5.16. 利用Mixins扩展类功能
class LoggedMappingMixin:
__slots__ = ()
def __getitem__(self, key):
print('Getting ' + str(key))
return super().__getitem__(key)
def __setitem__(self, key, value):
print('Setting {} = {}' .format(key, value))
return super().__setitem__(key, value)
def __delitem__(self, key):
print('Deleting ' + str(key))
return super().__delitem__(key)
class LoggedDict(LoggedMappingMixin, dict):
pass
d = LoggedDict()
d['x']=1
print(d['x'])
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 LoggedDictv2(dict):
pass
d = LoggedDictv2()
d['x'] = 23
print(d['x'])
2.5.17. 实现状态对象或者状态机
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 closed')
self.state = 'CLOSED'
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 = Connection1()
c.open()
c.close()
c.close()
2.5.18. 通过字符串调用对象方法
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)
import operator
operator.methodcaller('distance', 0, 0)(p)
2.5.19. 实现访问者模式
class Node:
pass
class UnaryOperator(Node):
def __init__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self, value):
self.value = value
class Evaluator:
def visit(self, node):
method_name = 'visit_' + type(node).__name__
visitor = getattr(self, method_name, self.generic_visit)
return visitor(node)
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
class Interpreter(Evaluator):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)
def visit_Neg(self, node):
return -self.visit(node.operand)
class InterpreterV2(Evaluator):
def visit_Number(self, node):
return str(node.value)
def visit_Add(self, node):
return '({left} + {right})'.format(left=self.visit(node.left) ,right=self.visit(node.right))
def visit_Sub(self, node):
return '({left} - {right})'.format(left=self.visit(node.left) ,right=self.visit(node.right))
def visit_Mul(self, node):
return '({left} * {right})'.format(left=self.visit(node.left) ,right=self.visit(node.right))
def visit_Div(self, node):
return '({left} / {right})'.format(left=self.visit(node.left) ,right=self.visit(node.right))
def visit_Neg(self, node):
return '(-{value})'.format(value=self.visit(node.value))
t1 = Add(Number(1), Number(2))
t2 = Mul(t1, Number(4))
i = Interpreter()
print(i.visit(t2))
i2 = InterpreterV2()
print(i2.visit(t2))