栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Python CookBook 第八章 类与对象(上)

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python CookBook 第八章 类与对象(上)

目录

8.1修改实例的字符串表示

#8.2 自定义字符串的输出格式

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

8.5将名称封装在类中

8.6 创建可管理的属性

8.7 调用父类中的方法

8.8在子类中扩展属性

8.9 创建一种新形式的类属性或者实例属性

8.10让属性具有惰性求值的能力

8.11简化数据结构的初始化过程

8.12定义一个接口和抽象基类

8.13实现一种数据模型或类型系统

8.14 实现自定义的容器


8.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) #__repr__ 返回的是实例的代码表示,
        # 内建函数能够可以返回一个字符串,缺少交互解释环境时用来检查实例的值

    def __str__(self):
        return '({0.x!s},{0.y!s})'.format(self)  #特殊方法 __str__将实例转化为一个字符串

p = Pair(3,4)
print('p is {0!r}'.format(p)) #Pair(3,4)  __repr__(self)  !r表示使用__repr__输出
print('p is {0}'.format(p))  #(3,4)  _str__(self)   默认为__str__

#8.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(2022,5,10)
print(format(d))
print('taday is ',format(d,'mdy'))

#对于格式化代码的解释完全取决于类本身
from datetime import date
d = date(2022,5,12)
print(format(d))   #2022-05-12
print(format(d,'%A,%B %d,%Y'))   #Thursday,May 12,2022
print('today is {:%d %b %Y}'.format(d))  #today is 12 May 2022

8.3让对象支持上下文管理协议
# with语句,背后触发的是__enter__()方法,__enter__()的返回值被放置在as限定的变量中,之后开始执行__exit__()方法
#常被用于管理类似文件、网络连接和锁这样的资源程序中
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.connections = []  #将列表变成栈用来保存连接

    def __enter__(self):     #每当enter执行时,由他产生一个新的连接添加到栈,
        sock = socket(self.family,self.type)
        sock.connect(self.address)
        self.connections.append(sock)
        return sock

    def __exit__(self, exc_type, exc_val, exc_tb):  #exit方法简单的将最近加入的那个连接从栈中弹出并关闭他
        self.connections.pop().close()

#使用
from functools import partial

con = LazyConnection(('www.python.org',80))
with con as c:
    c.send(b'GET/index.htlm HTTP/1.0rn')
    c.send(b'Host:www.python.orgrn')
    c.send(b'rn')
    resp = b''.join(iter(partial(c.recv,8192),b''))

    with con as c1:
        pass
# 使用__slot__属性
class Data:
    __slots__ = ['year','month','day']
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day
# 当使用slot属性时会压缩空间,副作用是不能在添加新的属性,不能以字典的方式创建实例

8.5将名称封装在类中
# 第一个规则是任何以 _ 单划线开头的名字应该总是被认为只属于内部实现
class A:
    def __init__(self):
        self._internal = 0  #内部属性
        self.public = 1 #公共属性

    def public_menthod(self):
        pass
    def _internal_method(self):
        pass

#双下划线__ 开头的名称:此时会出现名称重整行为,私有属性会进行分别重命名,这样的属性就不能通过继承而覆盖,如class C
class B:
    def __init__(self):
        self.__private = 0   #  命名为_B__private

    def __private_method(self):
        pass
    def public_method(self):
        self.__private_method()  #命名为_B__private_method()
        pass

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

    def __private_method(self):
        pass

8.6 创建可管理的属性
# 将其定义为property 把函数当做一种属性来使用
class Person:
    def __init__(self,first_name):
        self.first_name = first_name  #在初始化时设置self.first_name实际上调用setter方法,跳过self.first_name直接访问self._first_name

    #Getter function   将first_name定义为了property属性
    @property
    def first_name(self):  #s首先拥有相同的名称
        return self._first_name   #调用时直接访问底层数据实际保存的地方

    #Setter function   附加在了first_name 属性上  执行这一步必须将first_name 定义为property属性
    @first_name.setter
    def first_name(self,value):
        if not isinstance(value,str):
            raise TypeError('Ecpected a string')
        self._first_name = value  #调用时直接访问底层数据实际保存的地方

    #Deleter function
    @first_name.deleter
    def first_name(self):
        raise AttributeError("Cant't delete attribute")

#s首先拥有相同的名称,
a = Person('Alice')
print(a.first_name)  #调用getter
a.first_name = 43   #调用setter
del a.first_name  #调用deleter

# property也可以定义需要计算的属性
import math
class Circle:
    def __init__(self,radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * self.radius ** 2

    @property
    def perimeter(self):
        return 2 * math.pi * self.radius
#接口统一,使用单纯的属性就可以访问
c = Circle(3.0)
print(c.radius)  #3.0
print(c.area)  #没有括号   28.274333882308138
print(c.perimeter)  #18.84955592153876

8.7 调用父类中的方法
# 使用super()函数 完成父类方法的调用
#一种常见的用途调用父类的__init__()方法,确保父类被正确的初始化
class A:
    def __init__(self):
        self.x = 0

class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1
#另一种常见用途就是当覆盖了Python中的特殊方法
class Proxy:
    def __init__(self,obj):
        self.obj = obj

    def __getattr__(self, name):
        return getattr(self.obj,name)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name,value)  #判断名称,如果是_开头的,就通过super()调用的__setattr()__实现。
        else:
            setattr(self.obj,name,value)   #如果不是,就转而内部持有对象的self.obj对象进行操作。
#在多重继承的代码中,super()会有很好的用途
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__()
        print('C.__init__')

c = C()
print(c)
# Base.__init__
# B.__init__
# A.__init__
# C.__init__
print(C.__mro__)  #(, , , , )
#当使用super函数时,能够按照控制流依次遍历MRO中的类,子类——父类——多个父类,按顺序来——多个合法类,按顺序来

8.8在子类中扩展属性
class Person:
    def __init__(self,name):
        self.name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError('Expected a string')
        self._name = value

    @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')
        super(Subperson, Subperson).name.__delete__(self)

s = Subperson('Alice')
s.name
s.name = 'Joe'

#如果只是增加单个属性的方法
class Subperson:
    @Person.name.getter
    def name(self):
        print('Getting name')
        return super().name

8.9 创建一种新形式的类属性或者实例属性
# 可以以描述符的形式定义其功能,就是以特殊方法__get__(),__set__(),__delete__() 的形式
# 实现了三个核心属性访问操作
#然而描述符常常出现在大型 编程框架中
class Typed:
    def __init__(self,name,expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self,instance,cls):
        if instance is None:
            return self

    def __set__(self, instance,value):
        if not isinstance(value,self.expected_type):
            raise TypeError('Expected' + str(self.expected_type))
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

# 用装饰器选择属性
def typeassert(**kwargs):
    def decorate(cls):
        for name,expected_type in kwargs.items():
            setattr(cls,name,Typed(name,expected_type))
        return cls
    return decorate

#调用
@typeassert(name = str,shares = int,price = float)
class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price
        print('name is',name,'shares is',shares,'price is',price)

s = Stock('alice',100,23.9)   #name is alice shares is 100 price is 23.9

8.10让属性具有惰性求值的能力
# 让属性具备惰性求值能力是为了在于提升程序性能,使用描述符类完成,把计算出来的值缓存起来,访问时不是重新计算
class lazyproperty:
    def __init__(self,func):
        self.func = func

    def __get__(self,instance,cls):   #这一步是为了保存计算值在实例字典中,而描述符只定义一个—__get__方法,
        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(3.0)
print(c.area)   #Computing area 28.274333882308138
print(c.area)   #28.274333882308138
print(c.perimeter)   #Computing perimeter   62.01255336059963
print(c.perimeter)   #62.01255336059963
# 实际计算调度时就会发现只计算了一次,之后访问就直接访问其中是计算值
#一个潜在的缺点是属性变为可变的
c.area = 28
print(c.area)  #28
del c.area

8.11简化数据结构的初始化过程
# 定义一个通用型的__init__()方法能够使得在大量小型数据结构中简化初始化
class Structure:
    # 将类变量放进去
    _fileds = []
    def __init__(self,*args):
        if len(args) != len(self._fileds):
            raise TypeError('Expected {} arguments'.format(len(self._fileds)))

        #s设置参数属性
        for name,value in zip(self._fileds,args):
            setattr(self,name,value)

#使用类定义
if __name__ == '__main__':
    class Stock(Structure):
        _fileds = ['name','shares','price']

    class Points(Structure):
        _fileds = ['x','y']

    import math
    class Circle(Structure):
        _fileds = ['radius']
        def area(self):
            return math.pi * self.radius ** 2

s = Stock('Alice',20,90)
p = Points(2,3)
c = Circle(3.0)
#对关键字参数的支持,对关键字参数做映射,使之只对应定义在—_fields中的属性名
class Structure:
    _fileds = []
    def __init__(self,*args,**kwargs):
        if len(args) > len(self._fileds):
            raise TypeError('Expected {} arguments'.format(len(self._fileds)))

        #设置所有的位置参数
        for name,value in zip(self._fileds,args):
            setattr(self,name,value)

        #设置剩余参数
        for name in self._fileds[len(args):]:
            setattr(self,name,kwargs.pop(name))

        #找出剩余未知参数
        if kwargs:
            raise TypeError('Invalid argument(s): {}'.format(','.join(kwargs)))

#调用
if __name__ == '__main__':
    class Stock(Structure):
        _fileds = ['name','shares','price']

        s1 = Stock('Lili',50,90.1)
        # s2 = Stock('Lili',50,price=90.1)
        # s3 = Stock('Lili',shares=50,price=90.1)

#利用关键字参数来给类添加额外的属性,这些属性没有添加在_fileds中
class Structure:
    _fields = []
    def __init__(self,*args,**kwargs):
        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)

        #设置其他额外的参数
        extra_args = kwargs.keys() - self._fields
        for name in extra_args:
            setattr(self,name,kwargs.pop(name))
        if kwargs:
            raise TypeError('Duplicate value for {}'.format(','.join(kwargs)))

#调用
if __name__ == '__main__':
    class Stock(Structure):
        _fields = ['name','shares','price']

    s1 = Stock('Alice',50,90.1)
    s2 = Stock('alice',50,90.1,date='2022/05/13')

8.12定义一个接口和抽象基类
# 可以使用abc模块,用于执行类型检查并确保在子类中实现特定的方法
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 ISteam')
    pass

8.13实现一种数据模型或类型系统
# 定义数据结构中实现对某些特定的属性允许被赋予的值添加一些限制
#就是在设定特定的实例属性时添加检查或者断言,需要对每个属性的设定做定制化处理,因此使用描述符来完成
class Descriptor:   #基类,使用描述符设立一个值
    def __init__(self,name=None,**opts):
        self.name = name
        for key,value in opts.items():
            setattr(self,key,value)

    def __set__(self, instance, value):  #这里只有set  没有get 是因为 目的只是为了从底层字典中找到一个相同名称对应的值,定义get只会让程序更慢
        instance.__dict__[self.name] = value

class Typed(Descriptor):  #限制类型描述符
    expected_type = type(None)
    def __set__(self, instance, value):
        if not isinstance(value,self.expected_type):
            raise TypeError('Expected ' + str(self.expected_type))
        super().__set__(instance, value)

class Unsinged(Descriptor):  #限制值描述符
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected value >= 0')
        super().__set__(instance,value)

class Maxsized(Descriptor):
    def __init__(self,name=None,**opts):   #maxsized在opt中选择所需的属性,然后传递给基类Descriptor,在基类中完成设定
        if 'size' not in opts:
            raise TypeError('missing size option')
        super().__init__(name,**opts)

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be <' + str(self.size))
        super().__set__(instance,value)
#定义不同类型的数据的类
class Integer(Typed):
    expected_type = int

class UnsingedInteger(Integer,Unsinged):
    pass

class Float(Typed):
    expected_type = float

class UnsingedFloat(Float,Unsinged):
    pass

class String(Typed):
    expected_type = str

class SizedString(String,Maxsized):
    pass

#然后定义一个类
class Stock:
    name = SizedString('name',size=8)  #对输入的值进行验证
    shares = UnsingedInteger('shares')
    price = UnsingedFloat('price')
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

s = Stock('Lili',50,90.9)
print(s.name)
s.shares = 70
print(70)  #70
s.shares = -20  #这样就对输入的数据有了一个简单的检验认证
# Traceback (most recent call last):
#   File "E:vippythonPython CookBook第八章 类与对象.py", line 544, in 
#     s.shares = -20
#   File "E:vippythonPython CookBook第八章 类与对象.py", line 493, in __set__
#     super().__set__(instance, value)
#   File "E:vippythonPython CookBook第八章 类与对象.py", line 498, in __set__
#     raise ValueError('Expected value >= 0')
# ValueError: Expected value >= 0
#简化在类中的设定约束的步骤,一种是使用类装饰器,提供最大的灵活性和稳健性,一是不依赖高级的机制,而是可以很容易的根据需要在类定义上添加或移除
def check_attributes(**kwargs):
    def decorate(cls):
        for key,value in kwargs.items():
            if isinstance(value,Descriptor):
                value.name = key
                setattr(cls,key,value)
            else:
                setattr(cls,key,value(key))
        return cls
    return decorate

@check_attributes(
    name=SizedString(size=8),
    shares=UnsingedInteger,
    price=UnsigedFloat)
class Stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price
s1 = Stock('alice',20,99.9)
print(s1.name,s1.price,sep=' ')
# 另一种是使用元类
class checkedmeta(type):
    def __new__(cls,clsname,bases,methods):
        #连接属性名到限制器
        for key,value in methods.items():
            if isinstance(value,Descriptor):
                value.name = key
        return type.__new__(cls,clsname,bases,methods)

class Stock(metaclass=checkedmeta):
    name = SizedString(size=8)
    shares = UnsingedInteger
    price = UnsigedFloat
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price
s1 = Stock('Alice',20,99.9)
print(s1.name,s1.price,sep=' ')
采用类装饰器可以取代mixin类、多重继承以及super()函数的使用,速度会提升一倍
class Descriptor:   #基类,使用描述符设立一个值
    def __init__(self,name=None,**opts):
        self.name = name
        for key,value in opts.items():
            setattr(self,key,value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
#类型检查装饰器
def Typed(expected_type,cls=None):
    if cls is None:
        return lambda cls:Typed(expected_type,cls)

    super_set = cls.__set__
    def __set__(self,instance,value):
        if not isinstance(value,expected_type):
            raise TypeError('expected ' + str(expected_type))
        super_set(self,instance,value)
    cls.__set__ = __set__
    return cls

def Unsinged(cls):
    super_set = cls.__set__
    def __set__(self,instance,value):
        if value < 0:
            raise ValueError('Expected > = 0')
        super_set(self,instance,value)
    cls.__set__ = __set__
    return cls

def MaxSized(cls):
    super_init = cls.__init__
    def __init__(self,name=None,**opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super_init(self,name,**opts)
    cls.__init__ = __init__

    super_set = cls.__set__
    def __set__(self,instance,value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super_set(self,instance,value)
    cls.__set__ = __set__
    return cls
#使用装饰器
@Typed(int)
class Integer(Descriptor):
    pass

@Unsinged
class UnsingedInteger(Integer):
    pass

@Typed(float)
class Float(Descriptor):
    pass

@Unsinged
class UnsingedFloat(Float):
    pass

@Typed(str)
class String(Descriptor):
    pass

@MaxSized
class SizedString(String):
    pass


class Stock:
    name = SizedString('name',size=8)  #对输入的值进行验证
    shares = UnsingedInteger('shares')
    price = UnsingedFloat('price')
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

s = Stock('Lili',50,90.9)
print(s.name)  # Lili
s.shares = 70
print(s.shares)  # 70
s.shares = -20
# Traceback (most recent call last):
#   File "E:vippythonPython CookBook第八章 类与对象.py", line 680, in 
#     s.shares = -20
#   File "E:vippythonPython CookBook第八章 类与对象.py", line 621, in __set__
#     raise ValueError('Expected > = 0')
# ValueError: Expected > = 0

8.14 实现自定义的容器
# 自定义一个类,来模仿普通内建容器类型,比如列表 字典的行为,collections.abc库中有大量抽象基类。
# 比如sequence、MutableSequence,Mapping,MutableMapping,Set,MutableSet按照功能递进
# 创建一个能够排序后储存的类
import collections.abc
import bisect

class SortedItems(collections.abc.Sequence):
    def __init__(self,initial=None):
        self._items = sorted(initial) if initial is not None else []

    def __getitem__(self, index):
        return self._items[index]

    def __len__(self):
        return len(self._items)
    def add(self,item):
        bisect.insort(self._items,item)

item = SortedItems([12,34,1,39,53,32,9])
print(list(item))   #[1, 9, 12, 32, 34, 39, 53]
print(item[3])  #32
print(len(item))   #7
item.add(90)
print(list(item))  #[1, 9, 12, 32, 34, 39, 53, 90]

from collections.abc import MutableSequence

class Items(MutableSequence):
    def __init__(self,initial):
        self._items = list(initial) if initial is not None else []

    def __getitem__(self, index):
        print('Getting:',index)
        return self._items[index]

    def __setitem__(self, index, value):
        print('Setting:',index,value)
        self._items[index] = value

    def __delitem__(self, index):
        print('Deleting:',index)
        del self._items[index]

    def insert(self, index,value):
        print('Inserting:',index,value)
        self._items.insert(index,value)

    def __len__(self):
        print('Len')
        return len(self._items)
#这个类支持列表的所有功能
a = Items([1,2,3,4,5])
print(len(a))
a.append(7)
print(list(a))
a.count(3)
a.remove(2)
print(list(a))

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/887569.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号