一、类方法
1. __getitem__、__setitem__、__delitem__方法:以中括号(类似于字典的方式)来调用属性时候才执行
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1 # 调用__delattr__
del f1['age']
f1['name']='alex'
print(f1.__dict__)
# 输出
del obj.key时,我执行
del obj[key]时,我执行
{'name': 'alex'}
2. 改变对象的字符串显示__str__、__repr__、自定制格式化字符串__format__
- 当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据
- __str__方法需要返回一个字符串,当做这个对象的描写
format_dict={
'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
}
class School:
def __init__(self,name,addr,type):
self.name=name
self.addr=addr
self.type=type
def __repr__(self):
return 'School(%s,%s)' %(self.name,self.addr)
def __str__(self):
return '(%s,%s)' %(self.name,self.addr)
def __format__(self, format_spec):
# if format_spec
if not format_spec or format_spec not in format_dict:
format_spec='nat'
fmt=format_dict[format_spec]
return fmt.format(obj=self)
s1=School('oldboy1','北京','私立')
print('from repr: ',repr(s1))
print('from str: ',str(s1))
print(s1)
'''
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
【往往可以使用__repr__ = __str__】
注意:这俩方法的返回值必须是字符串,否则抛出异常
'''
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'asfdasdffd'))
# 输出
from repr: School(oldboy1,北京)
from str: (oldboy1,北京)
(oldboy1,北京)
oldboy1-北京-私立
私立:oldboy1:北京
私立/北京/oldboy1
oldboy1-北京-私立
__repr__和__str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员。
- 打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。
- __repr__用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者详细的显示。
3. __slots__:类变量,变量值可以是列表、元组或者可迭代对象
给一个实例绑定方法
from types import MethodType
实例.函数属性 = MethodType(方法,实例)
给一个实例绑定方法,对另一个实例是不起作用的。为了给所有实例都绑定方法,可以给class绑定方法
当想要限制实例的属性时,通过定义__slots__变量来实现
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非子类也定义了__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
1.字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
2.当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上
3.关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具
class Foo:
__slots__=['name','age']
f1=Foo()
f1.name='alex'
f1.age=18
print(f1.__slots__)
f2=Foo()
f2.name='egon'
f2.age=19
f2.gender='male' #报错
print(f2.__slots__)
print(Foo.__dict__)
# 输出
['name', 'age']
['name', 'age']
{'__module__': '__main__', '__slots__': ['name', 'age'], 'age': <member 'age' of 'Foo' objects>, 'name': <member 'name' of 'Foo' objects>, '__doc__': None}
4. __doc__:默认为None,该属性无法继承给子类。
5. __module__:表示当前操作的对象在哪个模块
6. __class__:表示当前操作的对象的类是什么
7. __del__:析构方法,当对象在内存中被释放时,自动触发执行
如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源。如同文件读写完后必须加上f.close()
8. __call__:对象后面加括号,触发执行
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
9. 迭代器协议
__iter__: 把一个对象改为可迭代对象
__next__ : 可迭代对象中获取下一个数据,注意方法中为了不进行无限迭代, 需要有抛出StopIteration异常,在符合条件时停止迭代;
class Foo:
def __init__(self,start,stop):
self.num=start
self.stop=stop
def __iter__(self):
return self
def __next__(self):
if self.num >= self.stop:
raise StopIteration
n=self.num
self.num+=1
return n
f=Foo(1,5)
from collections import Iterable,Iterator
print(isinstance(f,Iterator))
for i in Foo(1,5): #for循环会自动捕获StopIteration异常
print(i)
10. 描述符:一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符
【对象属性的访问顺序:实例属性→类属性→父类属性→__getattr__()】
① __get__(self, instance, owner)
② __set__(self, instance, value)
③ __del__(self, instance)
一个类,如果只定义了 __get__() 方法,而没有定义 __set__(), __delete__() 方法,则认为是非数据描述符; 反之,则成为数据描述符
# 代码 1
class Desc(object):
def __get__(self, instance, owner):
print("__get__...")
print("self : \t\t", self)
print("instance : \t", instance)
print("owner : \t", owner)
print('='*40, "\n")
def __set__(self, instance, value):
print('__set__...')
print("self : \t\t", self)
print("instance : \t", instance)
print("value : \t", value)
print('='*40, "\n")
class TestDesc(object):
x = Desc() #定义在另一个被描述的对象中,以类的属性出现
#以下为测试代码
t = TestDesc()
t.x
#以下为输出信息:
__get__...
self : <__main__.Desc object at 0x0000000002B0B828>
instance : <__main__.TestDesc object at 0x0000000002B0BA20>
owner : <class '__main__.TestDesc'>
========================================
t为实例,访问t.x时,根据常规顺序,
首先:访问Owner的__getattribute__()方法(其实就是 TestDesc.__getattribute__()),访问实例属性,发现没有,然后去访问父类TestDesc,找到了!
其次:判断属性 x 为一个描述符,此时,它就会做一些变动了,将 TestDesc.x(这里的x是TestDesc的类属性,实例属性则不会转换)转化为TestDesc.__dict__['x'].__get__(None, TestDesc) 来访问
然后:进入类Desc的 __get__()方法,进行相应的操作
属性查询优先级
① __getattribute__(), 无条件调用
② 数据描述符:由 ① 触发调用 (若人为的重载了该 __getattribute__() 方法,可能会导致无法调用描述符)
③ 实例对象的字典(若与描述符对象同名,会被覆盖哦,描述符优先)
④ 类的字典
⑤ 非数据描述符
⑥ 父类的字典
⑦ __getattr__() 方法(都找不到的时候)
二、在内存中读写数据
StringIO,在内存中读写str
>>> from io import StringIO
# 在内存中写数据
>>> f = StringIO()
>>> f.write('jjjj')
4
>>> f.write('qqqq')
4
>>> print(f.getvalue())
jjjjqqqq
# 在内存中读数据
>>> f = StringIO('lubenwei!\nready!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
lubenwei!
ready!
BytesIO实现在内存中读写bytes
>>> from io import BytesIO
# 写入UTF-8编码后的Bytes
>>> f = BytesIO()
>>> f.write('写入'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe5\x86\x99\xe5\x85\xa5'
# 读取数据
>>> f = BytesIO(b'\xe5\x86\x99\xe5\x85\xa5')
>>> f.read()
b'\xe5\x86\x99\xe5\x85\xa5'