python基础——面向对象进阶下

时间:2022-02-18 01:14:05

python基础——面向对象进阶下

1 __setitem__,__getitem,__delitem__

把对象操作属性模拟成字典的格式

想对比__getattr__(), __setattr__() 和 __deltattr__()这三个通过属性的方式的三个方法

 还有__getitem__(), __setitem__() 和 __delitem__()这三个函数, 是通过字典形式来处理属性

 字典形式使用中括号的方式获取值

 在实现__setitem__()的时候仍然需要使用__dict__来实现增值的设置

—getitem— 查看并返回一个value的值

class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item): #需要一个返回值
print('getitem')
return self.__dict__[item] #返回一个值value的值
f=Foo('egon')
# print(f.name) #值是f.name="egon"
print(f['name']) #触发getitem运行 把name传给item
print(f.__dict__) #查看字典里面的值

输出结果为:

getitem
egon
{'name': 'egon'}

__setitem__设置字典里面的值

class Foo:
def __init__(self,name):
self.name=name def __setitem__(self, key, value):
print('setitem')
self.__dict__[key]=value f=Foo("egon")
f["age"]=18 #触发 setitem 并打印setitem 并没有执行下面的值
print(f.__dict__) #查看字典里面的值

输出结果为:

setitem
{'name': 'egon', 'age': 18}

  

 __delitem__ 删除字典里面的值

class Foo:
def __init__(self,name):
self.name=name
def __delitem__(self, key):
print('delitem')
self.__dict__.pop(key) f=Foo('egon')
# del f['age'] #del.f.age
print(f.__dict__) #删除后打印一下

输出结果为:

{'name': 'egon'}

2 _slots_

1.__slots__是什么:

是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

2.引子:

使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)

3.为何使用__slots__:

字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__

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

4.注意事项:

__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。

关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。

slots_不会产生一个名称空间,省内存,属性只能设置_slots_规定的属性

class People:
_slots_=["name","age"] #可以加值
p=People()
p.name="karina" #对象没有自己的字典也就意味着没有自己的名称空间
p.age="18"
print(p.name,p.age)

输出结果为:

karina 18

3 _iter_ _next_ 实现迭代器协议

 可迭代对象是有方法__iter__()

迭代器是有方法__next__()

因而可以自己构建一个类, 使得它的对象, 既是一个可迭代对象, 也是一个迭代器 

例1

from collections import Iterable,Iterator #导入模块 判断是不是可迭代对象
class Foo:
def __init__(self,start):
self.start=start def __iter__(self):
return self def __next__(self):
return 'asb' f=Foo(0)
print(next(f)) #触发_next_ f._next_()
for i in f: #res=f._iter_() #next(res)
print(i)
*目前这样的情况下会进入死循环
# print(isinstance(f,Iterable)) #判断f是不是可迭代对象
# print(isinstance(f,Iterator)) #判断f是不是迭代器

抛一个异常

# from collections import Iterable,Iterator #导入模块 判断是不是可迭代对象
# class Foo:
# def __init__(self,start):
# self.start=start
#
# def __iter__(self):
# return self
#
# def __next__(self):
# if self.start>10: #抛一个异常并 做一个判断
# raise StopIteration
# n=self.start #设定初始值 从0开始
# self.start+=1 #每次自加一次
# # return n
# f=Foo(0)
# print(next(f))
# print(next(f))
# print(next(f))
# print(next(f))
# for i in f:
# print(i)

  

列2:模拟range功能 

class Range:
def __init__(self,start,end):
self.start=start
self.end=end def __iter__(self):
return self
def __next__(self):
if self.start>self.end:
raise StopIteration
n=self.start
self.start+=1
return n
r=Range(0,2)
for i in r :
print(i)

输出结果为:

0
1
2

  

4 _doc_ 打印注释信息 

在类中最开始定义的光秃秃的字符串是类的文档信息

查看该文档信息可以使用__doc__来获取

在函数中的第一个光秃秃的字符串也是, 通过函数名来调用__doc__即可获得

另外__doc__无法被继承

class Foo:
"我是描述信息"
pass
class Bar(Foo):
pass
print(Bar.__doc__) #该属性无法继承给子类

5 __module__

  __module__和__class__是两个特殊属性

  可以通过对象点的方式获取

  __module__是获取对象所在模块的名字. __class__是获取对象所在的类

  具体代码如下

import time
print(time.time.__module__)
print(time.time.__class__)
# time
# <class 'builtin_function_or_method'="">
</class>

 

6 __del__ 析构函数

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

import time
class Open:
def __init__(self,filepath,mode='r',encode='utf8'):
self.f=open(filepath,mode=mode,encoding=encode) def write(self):
pass def __getattr__(self, item):
return getattr(self,item) def __del__(self): #析构函数del
print('---->del')
self.f.close() #已删除就关闭文件 做清理操作 f=Open('a.txt','r')
del f #删除时 去执行__del__
time.sleep(100)

输出结果为:

---->del

7 __enter__和__exit__

我们知道在操作文件对象的时候可以这么写

1 with open('a.txt') as f:
2   '代码块'

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

class Foo:
def __enter__(self):
print('enter')
return "111" def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
print('exc_type',exc_type) #打印类型
print('exc_val',exc_val) #打印值
print('exc_tb',exc_tb) #打印追踪信息
return True #返回一个不为假的布尔值,代表已经出来过异常,程序不会蹦掉 # with Foo(): #res=Foo()._enter_
# pass with Foo() as obj: #res=Foo()._enter_ #obj=res #with 触发_enter 然后执行打印拿回一个返回值
print('with foo 的自代码块',obj)#再执行with的自代码块,打印的值为None
#因为obj目前是没有值,如果有返回值,obj就会返回值
raise NameError("名字属性定义") #主动抛出异常 触发 exit方法 在with的自代码中抛出异常,
# 意味着with自代码块执行完,后面不管有没有代码都不执行

输入结果为:

enter
exit
exc_type None
exc_val None
exc_tb None
enter
with foo 的自代码块 111
exit
exc_type <class 'NameError'>
exc_val 名字属性定义
exc_tb <traceback object at 0x02558058>

用途或者说好处:

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。 

8 __call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

class People:
def __init__(self,name):
self.name=name def __call__(self, *args, **kwargs):
print("call")
p=People("egon")
p() #属性直接加括号运行

输出结果为:

call

9 元类

1、所有的对象都是由类产生的,类又是有type产生的

type--->类----->对象

type成为元类,是所有类的类,利用type模拟class关键字的创建类的过程

创建一个类两方面要素 一个是类的名字,一个是名称空间定制的属性
 在python3里,所有的类都是新式类,都有继承object
 type()手动创建一个类三点:#类名 字符串形式 #类继承关系
元祖形式 #类的属性名称空间 字典类型

def run(self):
print('%s is runing'%self.name) class_name='Bar' #类名
bases=(object,) #类继承关系
class_dic={
"x":1, #类的属性名称空间
"run":run
}
Bar=type(class_name,bases,class_dic)
print(Bar)

输出结果为:

<class '__main__.Bar'>
class_name='Spam'
bases=(object,)
class_dic={
"name":"karina",
"age":18
}
Spam=type(class_name,bases,class_dic)
print(Spam)
print(Spam.__dict__)

输出结果为:

<class '__main__.Spam'>
{'name': 'karina', 'age': 18, '__module__': '__main__', '__dict__':
<attribute '__dict__' of 'Spam' objects>, '__weakref__': <attribute '__weakref__' of 'Spam' objects>, '__doc__': None}

  

2、自定义元类

可以写一个元类Mateclass, 它需要继承自type类

  原来的类需要关联该元类, 也就是在继承中有 metaclass=元类名字

  此时执行元类就可以生成一个对象, 也就是创建的这个类

  基于此, 就是元类中的__init__()方法创建的 类对象, 所以新加的功能只需在__init__()方法中就行

  元类的__init__()有额外三个参数, 分别是类名, 类基类, 类属性字典

  实现检查__doc__的代码如下

class MyMetaclass(type):
def __init__(self, class_name, class_bases, class_dic):
for key, value in class_dic.items():
if callable(value) and not value.__doc__:
raise Exception("{}方法内必须写入注释..".format(key)) class Foo(metaclass=MyMetaclass):
x = 1
def __init__(self, name):
self.name = name
def run(self):
'run function'
print('running')
def go(self):
print('going') 

元类可以创建类的行为:

type
class Mymeta(type):
def __init__(self,class_name,class_bases,class_dic):
pass
def __call__(self, *args, **kwargs):
# print(self)
obj=self.__new__(self) #产生空对象
self.__init__(obj,*args,**kwargs) #obj.name='egon'
return obj class Foo(metaclass=Mymeta):#指定元类等于Mymeta
x=1
def __init__(self,name):
self.name=name #obj.name='egon 不能有返回值
def run(self):
print("running")
f=Foo('egon')
print(f)