Python(八)类与对象

1.改变对象的字符串显示

要改变一个实例的字符串表示,可重新定义它的 __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)

>>> p = Pair(3, 4)
>>> p
Pair(3, 4) # __repr__() output
>>> print(p)
(3, 4) # __str__() output
>>>

!r 格式化代码指明输出使用 __repr__() 来代替默认的 __str__() 

>>> p = Pair(3, 4)
>>> print('p is {0!r}'.format(p))
p is Pair(3, 4)
>>> print('p is {0}'.format(p))
p is (3, 4)
>>>

2.自定义字符串的格式化

为了自定义字符串的格式化,我们需要在类上面定义 __format__() 方法

_formats = {
    '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 = _formats[code]
        return fmt.format(d=self)

>>> d = Date(2012, 12, 21)
>>> format(d)
'2012-12-21'
>>> format(d, 'mdy')
'12/21/2012'
>>> 'The date is {:ymd}'.format(d)
'The date is 2012-12-21'
>>> 'The date is {:mdy}'.format(d)
'The date is 12/21/2012'
>>>

3.让对象支持上下文管理协议

写一个表示网络连接的类:

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.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        self.sock = None


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

分析上述代码。但是初始化的时候并不会做任何事情(比如它并没有建立一个连接)。 连接的建立和关闭是使用 with 语句自动完成的

当出现 with 语句的时候,对象的 __enter__() 方法被触发, 如果有返回值会被赋值给 as 声明的变量。然后,with 语句块里面的代码开始执行。 最后with语句块执行结束,__exit__() 方法被触发进行清理工作。​​​​​​​

4.创建大量对象时节省内存方法

class Date:
    __slots__ = ['year', 'month', 'day']
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

 定义 __slots__ 后,Python就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典。 在 __slots__ 中列出的属性名在内部被映射到这个数组的指定下标上。 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在 __slots__ 中定义的那些属性名

5.在类中封装属性名

Python语言并没有类似于c++的访问控制(public private,protected)

通过遵循一定的属性和方法命名规约来达到这个效果

(1)任何以单下划线_开头的名字都应该是内部实现

class A:
    def __init__(self):
        self._internal = 0 # An internal attribute
        self.public = 1 # A public attribute

    def public_method(self):
        '''
        A public method
        '''
        pass

    def _internal_method(self):
        pass

(2)双下划线保证继承过程中不会被覆盖

class B:
    def __init__(self):
        self.__private = 0

    def __private_method(self): #私有属性会被分别重命名_B__private 和 _B__private_method 
        pass



class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1 

    # Does not override B.__private_method()
    def __private_method(self): #私有属性会被分别重命名_C__private 和 _C__private_method 
        pass

6.调用父类方法

想在子类中调用父类的某个已经被覆盖的方法。为了调用父类(超类)的一个方法,可以使用 super() 函数

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()  # Call parent spam()

(1)常见用法是在 __init__() 方法中确保父类被正确的初始化

class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

(2)常见用法出现在覆盖Python特殊方法的代码

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) # 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的 __setattr__() 
        else: # 否则的话就委派给内部的代理对象 self._obj 去处理
            setattr(self._obj, name, value)

注意:

class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print('B.__init__')

class C(A,B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print('C.__init__')

>>> c = C()
Base.__init__
A.__init__
Base.__init__ #发现 Base.__init__() 被调用两次
B.__init__
C.__init__
>>>



class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')

class C(A,B):
    def __init__(self):
        super().__init__()  # Only one call to super() here
        print('C.__init__')

>>> c = C()
Base.__init__ #每个 __init__() 方法只会被调用一次
B.__init__
A.__init__
C.__init__
>>>

合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

​​​​​​​​​​​​​​7.创建新的类或实例属性

一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类, 分别为 __get__() 、__set__() 和 __delete__() 这三个特殊的方法

class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            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:
    x = Integer('x')
    y = Integer('y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

# 所有对描述器属性(比如x或y)的访问会被 __get__() 、__set__() 和 __delete__() 方法捕获到
>>> p = Point(2, 3)
>>> p.x # Calls Point.x.__get__(p,Point)
2
>>> p.y = 5 # Calls Point.y.__set__(p, 5)

8.使用延迟计算属性

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 #装饰器lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @lazyproperty #同名装饰器lazyproperty 利用这一点,使用 __get__() 方法在实例中存储计算出来的值, 这个实例使用相同的名字作为它的property。 这样一来,结果值被存储在实例字典中并且以后就不需要再去计算这个property了
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius


>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area
Computing area  #消息 Computing area 和 Computing perimeter 仅仅出现一次
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
>>>

9.定义接口或者抽象基类

抽象类的一个特点是它不能直接被实例化

抽象类的目的就是让别的类继承它并实现特定的抽象方法

from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta): #使用 abc 模块可以很轻松的定义抽象基类
    @abstractmethod
    def read(self, maxbytes=-1):
        pass

    @abstractmethod
    def write(self, data):
        pass


class SocketStream(IStream): #(1)类继承它并实现抽象基类特定的抽象方法
    def read(self, maxbytes=-1):
        pass

    def write(self, data):
        pass


import io
#(2)也可以通过注册方式来让某个类实现抽象基类
IStream.register(io.IOBase)

10.实现自定义容器

实现一个自定义的类来模拟内置的容器类功能,比如列表和字典

import collections
import bisect

class SortedItems(collections.Sequence): #继承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)


items = SortedItems([5, 1, 3])
print(list(items))
print(items[0], items[-1])
items.add(2)
print(list(items))

'''
[1, 3, 5]
(1, 5)
[1, 2, 3, 5]


'''

持所有常用操作,包括索引、迭代、包含判断,甚至是切片操作。 

11.属性的代理访问

将某个实例的属性访问代理到内部另一个实例中去,代理是一种编程模式,它将某个操作转移给另外一个对象来实现

(1)简单代理  继承方法重写

class A:
    def spam(self, x):
        pass

    def foo(self):
        pass


class B1:
    """简单的代理"""

    def __init__(self):
        self._a = A()

    def spam(self, x):
        # Delegate to the internal self._a instance
        return self._a.spam(x)

    def foo(self):
        # Delegate to the internal self._a instance
        return self._a.foo()

    def bar(self):
        pass

(2)如果有大量的方法需要代理, 那么使用 __getattr__() 方法或许或更好些:

class A:
    def spam(self, x):
        pass

    def foo(self):
        pass

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):
        #这个方法在访问的属性方法不存在的时候被调用
        return getattr(self._a, name)


b = B()
b.bar() 
b.spam(42) # 调用 B.__getattr__('spam') B中没有该方法 从A中找

注意:

__getattr__()只有在属性不存在时才会调用。 因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的

12.在类中定义多个构造器

想实现一个类,除了使用 __init__() 方法外,还有其他方式可以初始化它

import time
class Date:
    # 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)

	
a = Date(2012, 12, 21) 
print(a.year,a.month,a.day)
b = Date.today()
print(b.year,b.month,b.day)

'''
(2012, 12, 21)
(2022, 5, 3)

'''

13.创建不调用init方法的实例

可以通过 __new__() 方法创建一个未初始化的实例

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
	

d = Date.__new__(Date) #不调用 __init__() 方法 来创建这个Date实例
data = {'year':2012, 'month':8, 'day':29}#需要手动初始化
for key, value in data.items():
     setattr(d, key, value)
print(d.year,d.month,d.day)

14.通过字符串调用对象方法

调用一个方法实际上是两步独立操作,第一步是查找属性,第二步是函数调用

(方法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)  # 点(2,3)到点(0,0)距离

(方法2)operator.methodcaller() 创建一个可调用对象,并同时提供所有必要参数, 然后调用的时候只需要将实例对象传递给它即可

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)


import operator
operator.methodcaller('distance', 0, 0)(p)

15.实现访问者模式

当计算1 + 2 * (3 - 4) / 5 涉及不同类型的加减乘除

设计一个运算基类:

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

但对于每个不同类型的表达式,每次都要重新定义一遍 

设计访问者visit模式

class NodeVisitor:
    def visit(self, node):
        methname = 'visit_' + type(node).__name__ #在访问者模式中 添加前缀重写属性名进行区分
        meth = getattr(self, methname, None) #根据新属性名查找并返回属性值
        if meth is None:
            meth = self.generic_visit
        return meth(node)

    def generic_visit(self, node):
        raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))

class Evaluator(NodeVisitor):
    def visit_Number(self, node):
        return node.value

    def visit_Add(self, node):  #按照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_Negate(self, node):
        return -node.operand

上述方法本质为递归,对于较深的结构将不适用,避免递归的一个通常方法是使用一个栈或队列的数据结构。 例如,深度优先的遍历算法,第一次碰到一个节点时将其压入栈中,处理完后弹出栈。

class Evaluator(NodeVisitor):
    def visit_Number(self, node):
        return node.value

    def visit_Add(self, node):
        yield (yield node.left) + (yield node.right)

    def visit_Sub(self, node):
        yield (yield node.left) - (yield node.right)

    def visit_Mul(self, node):
        yield (yield node.left) * (yield node.right)

    def visit_Div(self, node):
        yield (yield node.left) / (yield node.right)

    def visit_Negate(self, node):
        yield - (yield node.operand)

它会将 node.left 返回给 visit() 方法,然后 visit() 方法调用那个节点相应的 visit_Name() 方法。 yield暂时将程序控制器让出给调用者,当执行完后,结果会赋值给value

16.让类支持比较操作

让某个类的实例支持标准的比较运算(比如>=,!=,<=,<等),但是又不想去实现那一大堆的特殊方法

使用python装饰器 functools.total_ordering来装饰一个类,只需定义一个 __eq__() 方法, 外加其他方法(__lt__, __le__, __gt__, or __ge__)中的一个即可。 然后装饰器会自动为你填充其它比较方法。

以房屋面积大小比较为例,建立房屋基类,实例化房间

# Build a few houses, and add rooms to them
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print('Is h1 bigger than h2?', h1 > h2) # prints True
print('Is h2 smaller than h3?', h2 < h3) # prints True
print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False
print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split'
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)

'''

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HT . WANG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值