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))

2.5.20. 不用递归实现访问者模式

2.5.21. 让类支持比较操作