Python类与对象

Posted HT . WANG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)

'''

以上是关于Python类与对象的主要内容,如果未能解决你的问题,请参考以下文章

抽象类与私有构造函数

Python初识类与对象

Python初识类与对象

第10天:Python 类与对象

python 面向对象专题:类的空间问题类与对象之间的关系类与类之间的关系

python 面向对象专题:类的空间问题类与对象之间的关系类与类之间的关系