本文通过针对不同应用场景及其解决方案的方式,总结了Python中类的一些相关知识,具体如下。
一、概念
1.将代码及其数据集成在类中
Python的基本输入机制是基于行的,从文本文件向程序读入数据时,一次读取一个数据行。
2.使用class定义类
3.self的重要性
①如果没有这个赋值,Python解释器无法得出方法调用要应用到哪个对象实例
②每一个方法的第一个参数都是self
二、示例
1.改变对象的字符串显示
应用场景:想要改变对象实例的打印或输出,更具可读性
解决方案:重写字符串的str()和repr()方法
class Pair:
def __init__(self,x,y):
self.x=x
self.y=y
#返回一个实例的代码表示形式,通常用来重新构造这个实例
def __repr__(self):
return 'Pair({0.x!s},{0.y!s})'.format(self)
#将一个实例转换为一个字符串,使用str()和print()可以输出该字符串
def __str__(self):
return ‘({0.x!s},{0.y!s})’.format(self)#0实际上就是self本身
测试代码:
p=Pair(3,4)
p#Pair(3,4)
print(p)#(3,4)
2.自定义字符串的格式化
应用场景:想通过format()函数和字符串方法使得一个对象能够支持自定义格式化
解决方案:需要在类上面定义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)
#test code
d=Date(2017,03,20)
format(d)#2017-03-20
format(d,'mdy')#03/20/2017
3.让对象支持上下文管理协议
应用场景:想通过with语句让对象支持上下文管理协议
解决方案:为了让一个对象兼容with语句,需要实现enter()和exit()方法
from socket import socket,AF_INFT,SOCK_STREAM
class LazyConnection:
def __init__(self,address,family=AF_INFT,
type=SOCK_STREAM):
self.address=address
self.family=family
self.type=type
#self.sock=None
self.sock=[]
def __enter__(self):
sock=socket(self.family,self.type)
sock.connect(self.address)
self.connections.append(sock)
return sock
#异常类型、异常值、追溯信息
def __exit__(self,exc_ty,exc_val,tb):
self.connections.pop().close()
#test case
from functools import partial
conn=LazyConnection(('www.python.org',80))
#当出现with语句时,对象的__enter__()方法会被执行,其返回值赋值给as声明的变量
with conn as s1:
pass
with conn as s2:
pass
4.在类中封装属性名
应用场景:想要封装类的实例里的私有数据,但是python并没有访问权限
解决方案:不依赖语言特性去封装数据,通过遵循一定的属性和方法命名规约来实现
class A:
def __init__(self):
self._internal=0#内部属性
self.public=1#公用属性
def public_method(self):
pass
def _internal_method(self):
pass
'''
使用双下划线会导致访问名称发生变化,私有属性会被重命名为
_B__private和_B__private_method,这种属性通过继承无法被覆盖
'''
class B:
def __init__(self):
self.__private=0
def __private_method(self):
pass
def public_method(self):
pass
self.__private_method()
class C(B):
def __init__(self):
super().__init__()
self.__private=1 #并不会覆盖B.__private
#并不会覆盖B.__private_method()
def __private_method(self):
pass
5.创建可管理的属性
应用场景:想要给某个属性增加除访问和修改之外的其他逻辑处理,如类型检查或合法性验证
解决方案:将属性定义为一个property
#示例1
class Person:
def __init__(self,first_name):
self.first_name=first_name
#相当于一个getter函数,使得first_name成为一个属性
@property
def first_name():
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't delete attribute'
'''通过property将所有的访问接口形式统一,对直径、周长、面积的访问都是通过属性访问,就跟访问简单的属性一样'''
import math
class Circle:
def __init__(self,radius):
self.radius=radius
@property
def area(self):
return math.pi*self.radius**2
@property
def diameter(self):
return self.radius**2
@property
def perimeter(self):
return 2*math.pi*self.radius
6.super()函数的使用
应用场景:想要在子类中调用父类的某个已经被覆盖的方法
解决方案:使用super()函数
#示例1 在__init__()方法中确保父类被正确初始化
class A:
def __init__(self):
self.x=0
class B:
def __init__(self):
super().__init__()
self.y=1
#示例2 覆盖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.startwith('_'):
super().__setattr__(name,value)
else:
setattr(self.obj,name,value)
#示例3 多继承中可避免不必要的调用
class Base:
def __init__(self):
print('Base.__init__')
class A:
def __init__(self):
Base.__init__(self)
print('A.__init__')
class B:
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__')
#使用super()改进示例3中的代码
class Base:
def __init__(self):
print('Base.__init__')
class A:
def __init__(self):
super().__init__()
print('A.__init__')
class B:
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__()#只有这里会执行一次
print('C.__init__')
7.创建新的类或实例属性
应用场景:想创建一个新的拥有一些额外功能的实例属性类型,如类型检查
解决方案:可以通过一个描述器类的形式来定义其功能
#示例1 整型类型检测属性的描述器属性
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]
#所有对描述器的属性如x,y的访问都会被__get__()__set__()
#__delete__()方法捕获到
class Point:
x=Integer('x')
y=Integer('y')
def __init__(self,x,y):
self.x=x
self.y=y
#test case
p=Point(2,3)
p.x
p.y=5
p.x=2.5#会报整型异常
#类装饰器
class Typed:
def __init__(self,name,excepted_type):
self.name=name
self.excepted_type=excepted_type
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,self.excepted_type):
raise TypeError('Excepted'+str(self.excepted_tpye))
instance.__dict__[self.name]=value
def __delete__(self,instance):
del instance.__dict__[self.name]
#类装饰器应用于选中的属性
def typeassert(**kwargs):
def decorate(cls):
for name,excepted_type in kwargs.items():
#将类装饰器链接到当前类
setattr(cls,name,Typed(name,excepted_type))
return cls
return decotate
#test case
@typeassert(name=str,shares=int,price=float)
class Stock:
def __init__(self,name,shares,price):
self.name=name
self.shares.shares
self.price=price
8.使用延迟计算属性
应用场景:想将一个只读属性定义成一个property,只在访问的时候才计算结果,访问结束后希望结果被缓存起来以便下次调用
解决方案:可以通过使用一个描述器类定义一个延迟属性
#使用延迟属性
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('计算面积')
return math.pi*self.radius**2
@lazyproperty
def perimeter(self):
print('计算周长')
return math.pi*self.radius*2
#test case
c=Circle(4.0)
c.radius#4
c.area
c.area
c.perimeter
c.perimeter
9.接口和抽象类
应用场景:想定义一个接口或抽象类并通过执行类型检查来确保子类实现了某些特定方法
解决方案:使用abc模块
#示例1 定义接口或抽象类,注意抽象类不能直接被实例化
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('Excepted an IStream')
pass
#通过注册的方式来代替继承实现抽象类
import io
IStream.register(io.IOBase)
f=open('foo.txt')
isinstance(f,IStream)#true
10.实现数据模型的类型约束
应用场景:想定义某些在属性赋值上有限制的数据结构
解决方案:在对某些实例属性赋值时进行检查,可以使用描述器自定义属性赋值函数
#基类,使用描述器设置值
class Descriptor:
def __init__(self,name=None,**opts):
self.name=name
for key,value in opts.items():
setattr(self,name,value)
def __set__(self,instance,value):
instance.__dict__[self.name]=value
#强制类型描述器
class Typed(Descriptor):
excepted_type=type(None)
def __set__(self,instance,value):
if not isinstance(value,self.excepted_type):
raise TypeError('excepted'+str(self.excepted_type))
super().__set__(instance,value)
#强制值描述器
class Unsigned(Descriptor):
def __set__(self,instance,value):
if value<0:
raise ValueError('excepted>=0')
super().__set__(instance,value)
class MaxSized(Descriptor):
def __init__(self,name=None,**opts):
if 'size' not in opts:
raise TypeError('missing size option')
super().__set__(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):
excepted_type=int
class UnsignedInteger(Integer,Unsigned):
pass
class Float(Typed):
excepted_type=float
class UnsignedFloat(Float,Unsigned):
pass
class String(Typed):
excepted_type=str
class SizedString(String,MaxSized):
pass
class Stock:
name=SizedString('name',size=8)
shares=UnsignedInteger('shares')
price=UnsignedFloat('price')
def __init__(self,name,shares,price):
self.name=name
self.shares=shares
self.price=price
#test case
s.name
s.shares=70
s.shares=-10 #报错
s.price='a lot' #报错
s.name='aaaaaaaaaaaaaaaaaaaa'#报错
11.自定义容器
应用场景:想实现一个自定义容器的类来模拟内置的容器类功能,比如列表和字典,但是不确定到底要实现哪些方法
解决方案:使用clloections
##想让类支持迭代,需要实现collections.Iterator的所有抽象方法
#否则类似a=A()的操作会报错
import collections
class A(collections.Iterator):
pass
class SortedItems(collections.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模块是一个在排序列表中插入元素的高效方式
bisect.insort(self._items,item)
#test case
items=SortedItems([5,1,3])
print(list(items))
print(items[0],items[-1])
items.add(2)
print(list(items))
12.在类中定义多个构造器
应用场景:想实现一个类,除了使用init()方法,还想让其他方式可以初始化它
解决方案:使用类方法
import time
class Date:
''' 使用类方法'''
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
@classmethod
#接受一个class作为cls参数
def today(cls):
t=time.loacltime()
return cls(t.tm_year,t.tm_month,t.tm_day)
#test case
a=Date(2017,03,20)
b=Date.today()
13.创建不调用init()方法的实例
应用场景:想创建一个实例,但是希望绕过init()方法
解决方案:可以通过new()方法创建一个未初始化的实例
#示例
class Date:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
#test case
d=Date.__new__(Date)
d
d.year#未初始化,报错,需要手动初始化
data={'year':2017,'month':3,'day':20}
for key value in data.items():
setattr(d,key,value)
d.year
d.month
d.day
14.通过字符串调用对象方法
应用场景:已知一个字符串形式的方法名称,想通过它调用某个对象的对应方法
解决方案:使用getattr()
#示例1
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)
#test case
p=Point(2,3)
d=getattr(p,'distance')(0,0)#调用p.distance(0,0)
#示例2 当需要通过相同的参数多次调用某个方
#法时,使用operator.methodcaller
import operator
operator.methodcaller('distance',0,0)(p)
points=[
Point(1,2),
Point(3,0),
Point(10,-3),
Point(-5,-7),
Point(-1,8),
Point(3,2)
]
points.sort(key=operator.methodcaller('distance',0,0))