Python 基础 四 面向对象杂谈

时间:2023-03-06 18:20:32

Python 基础  四  面向对象杂谈

一、isinstance(obj,cls) 与issubcalss(sub,super)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo:
pass f1=Foo()
print(isinstance(f1,Foo)) #True issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo:
pass class Xoo(Foo):
pass print(issubclass(Xoo,Foo)) #True

二、__getattribute__
    在介绍__getattribute__之前是否还记得之前学过一个叫__getattr__的方法,这两者之间是否存在某种关系呢?实际上呢,还是有关系的,是个什么情况呢,就是大哥和小弟的关系,有大哥在的时候大哥上,大哥不在小弟上,或者大哥不想干了,让给了小弟,那你小弟就必须上了是吧。

先看一下__getattr__的例子:

class Foo:
def __init__(self,x):
self.x=x def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item] f1=Foo(10)
print(f1.x) #d当访问的属性存在的时候是不会触发__getattr__
#f1.xxxxxx #不存在的属性访问,触发__getattr__,若自己没有重写此方法时,就会触发系统默认的此方法,若重写了就会按你自己写的东西执行

好了,再看一下两个都存在的触发方式及结果:

class Foo:
def __init__(self,x):
self.x=x def __getattr__(self, item):
print('执行的是getattr')
# return self.__dict__[item]
def __getattribute__(self, item):
print('执行的是getattribute')
raise AttributeError('抛出异常了')
# raise TabError('xxxxxx')
f1=Foo(10)
#f1.x
f1.xxxxxx #不存在的属性访问,触发__getattr__,若有__getattribute__就先触发此方法(不管属性是否存在),若后面有raise方法抛出异常的时候,就会再次交给__getattr__处理

三、item系列
  看到这里是否还记得之前说过一个系列叫做:attr,这两者又是什么关系?我们先看看item系列

class Foo:
def __getitem__(self, item):
print('getitem',item)
#return self.__dict__[item] def __setitem__(self, key, value):
print('setitem')
self.__dict__[key]=value def __delitem__(self, key):
print('delitem')
self.__dict__.pop(key) f1=Foo()
#print(f1.__dict__) #对象f1的属性字典是空的————》{}
#f1.name='egon'#---->setattr-------->f1.__dict__['name']='egon'
#print(f1.__dict__) #{'name': 'egon'}
f1['name']='egon'#--->setitem--------->f1.__dict__['name']='egon'
f1['age']=18 #会触发 内部的__setitem__方法
#
print('===>',f1.__dict__) # ===> {}查看属性字典却没有添加进去,只是触发了内部的__setitem__方法,若要添加上,就必须对底层进行操作: self.__dict__[key]=value
# 这样就把属性字典添加上了 ===> {'name': 'egon', 'age': 18}
del f1.name # 删除属性值
print(f1.__dict__) # {'age': 18}
# #
print(f1.age)
del f1['name'] #触发__delitem__,若要真实的删除属性字典中的值就必须对底层进行操作: self.__dict__.pop(key)
#print(f1.__dict__)

总结一下item系列的触发是通过字典类型的方式操作完成的,而attr方式的触发是通过对象加点(.)的方式触发的,这就是两者的最大区别,但是两者最终的效果是一样的。

四 、 _str_ ;_reper_;__format__

_str_ ;_reper_;改变对象的字符串显示方式换句话就是展示print()函数的 执行结果的:

class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '名字是%s 年龄是%s' %(self.name,self.age)
# #
f1=Foo('egon',18)
print(f1) #str(f1)--->f1.__str__() 若没有__str__函数,则执行print()就会执行提供的默认的打印内容
#比如本实例中打印的内容为:<__main__.Foo object at 0x006166D0>,若我们在定义的时候写了__str__函数就会按照我们自己定义好的方式执行打印操作:名字是egon 年龄是18
#
x=str(f1)
print(x)
#
y=f1.__str__()
print(y)
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '折是str'
def __repr__(self):
return '名字是%s 年龄是%s' %(self.name,self.age) f1=Foo('egon',19)
#repr(f1)---->f1.__repr__()
print(f1) #str(f1)---》f1.__str__()------>f1.__repr__()

总结:当 str 与repr同时存在的时候执行顺序是:先找str 若没有再去找repr,若都没有就去执行默认的print()打印结果。
__format__ 自定义方式来制定输出格式:

format_dic={
'ymd':'{0.year}{0.mon}{0.day}',
'm-d-y':'{0.mon}-{0.day}-{0.year}',
'y:m:d':'{0.year}:{0.mon}:{0.day}'
}
class Date:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def __format__(self, format_spec):
print('我执行啦')
print('--->',format_spec)
if not format_spec or format_spec not in format_dic:
format_spec='ymd'
fm=format_dic[format_spec]
return fm.format(self)
d1=Date(2016,12,26)
format(d1) #d1.__format__()
print(format(d1))
print(format(d1,'ymd'))
print(format(d1,'y:m:d'))
print(format(d1,'m-d-y'))
print(format(d1,'m-d:y'))
print('===========>',format(d1,'asdfasdfsadfasdfasdfasdfasdfasdfasdfasd

五 、__slots__ (省内存)
    1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

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

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

class Foo:
__slots__=['name','age'] #{'name':None,'age':None}
# __slots__='name' #{'name':None,'age':None} f1=Foo()
f1.name='egon'
#print(f1.name) f1.age=18 #--->setattr----->f1.__dict__['age']=18
#print(f1.__dict__) #AttributeError: 'Foo' object has no attribute '__dict__' 为何会出现这个结果,这就是不让各个实例对象自己封存自己的数据属性了。
print(Foo.__slots__) #['name', 'age']类中指存放了键的属性
print(f1.__slots__) # ['name', 'age']
f1.name='egon'
f1.age=17
print(f1.name)
print(f1.age)
f1.gender='male' #Foo' object has no attribute 'gender' 在定义数据属性的时候就没有定义性别的属性,so就加不进去

六、__doc__

class Foo:
'我是描述信息'
pass print(Foo.__doc__) # 我是描述信息

需要注意的是:此属性是不具有继承的:

class Foo:
'我是描述信息'
pass class Bar(Foo):
pass
print(Bar.__doc__) #该属性无法继承给子类 打印结果为:None

七、__module__和__class__
_module__ 表示当前操作的对象在那个模块

__class__     表示当前操作的对象的类是什么

八、__del__

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

class Foo:

    def __del__(self):
print('执行我啦') f1=Foo()
del f1
print('------->') #输出结果
执行我啦
------->

九 、__call__方法
    对象后面加括号,触发执行。

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

class Foo:

    def __init__(self):
print('你就是大傻逼') def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__
obj() # 执行 __call__

十、用 __next__和__iter__实现面向对象的迭代器协议

class Foo:
def __init__(self,n):
self.n=n
def __iter__(self):
return self def __next__(self):
if self.n == 13:
raise StopIteration('终止了')
self.n+=1
return self.n # l=list('hello')
# for i in l:
# print(i)
f1=Foo(10)
print(f1.__next__())
print(f1.__next__())
print(f1.__next__())
print(f1.__next__()) for i in f1: # obj=iter(f1)------------>f1.__iter__() #此时的f1的值为最后一次迭代额值
print(i) #obj.__next_()

十一、描述符(__get__,__set__,__delete__)(这个东西很高大上,不过现在的级别还是没啥卵用,就先简单的了解一下下)
    1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
    __get__():调用一个属性时,触发
    __set__():为一个属性赋值时,触发
    __delete__():采用del删除属性时,触发

一个小小的例子:

class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass

2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

#描述符Str
class Str:
def __get__(self, instance, owner):
print('Str调用')
def __set__(self, instance, value):
print('Str设置...')
def __delete__(self, instance):
print('Str删除...') #描述符Int
class Int:
def __get__(self, instance, owner):
print('Int调用')
def __set__(self, instance, value):
print('Int设置...')
def __delete__(self, instance):
print('Int删除...') class People:
name=Str()
age=Int()
def __init__(self,name,age): #name被Str类代理,age被Int类代理,
self.name=name
self.age=age #何地?:定义成另外一个类的类属性 #何时?:且看下列演示 p1=People('alex',18) #描述符Str的使用
p1.name
p1.name='egon'
del p1.name #描述符Int的使用
p1.age
p1.age=18
del p1.age #我们来瞅瞅到底发生了什么
print(p1.__dict__)
print(People.__dict__) #补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__) 描述符应用之何时?何地?

描述符

3 描述符分两种
    一 数据描述符:至少实现了__get__()和__set__()

1 class Foo:
2 def __set__(self, instance, value):
3 print('set')
4 def __get__(self, instance, owner):
5 print('get')

二 非数据描述符:没有实现__set__()

1 class Foo:
2 def __get__(self, instance, owner):
3 print('get')

4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()

今天的主要内容就这些,看起来还不错,实践,实践,还是实践!!!