Python全栈开发【面向对象进阶】

时间:2022-06-01 18:10:08
Python全栈开发【面向对象进阶】

本节内容:

  • isinstance(obj,cls)和issubclass(sub,super)
  • 反射
  • __setattr__,__delattr__,__getattr__
  • 二次加工标准类型(包装)
  • __getattribute__
  • 描述符(__get__,__set__,__delete__)
  • 再看property
  • __setitem__,__getitem,__delitem__
  • __str__,__repr__,__format__
  • __slots__
  • __next__和__iter__实现迭代器协议
  • __doc__
  • __module__和__class__
  • __del__
  • __call__
  • metaclass

isinstance(obj,cls)和issubclass(sub,super)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象

 class Foo(object):
     pass
 obj = Foo()
 print(isinstance(obj, Foo)) #True

issubclass(sub, super)检查sub类是否是 super 类的派生类

 class Foo(object):
     pass

 class Bar(Foo):
     pass

 print(issubclass(Bar, Foo)) #True

反射★

1、什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)

2、python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

 class Car:
     power ='oil'
     def __init__(self,name,type):
         self.name=name
         self.type=type

     def start(self):
         print('%s 汽车发动了' %self.name)
     def stop(self):
         print('%s 刹车停止了' %self.type)

 c1=Car('奔驰','SUV')
 #判断是否有属性
 print(hasattr(c1,'start')) # True
 #获取属性值,相当于实例.属性
 print(getattr(c1,'name')) #奔驰
 #添加属性,设置属性
 setattr(c1,'color','黑')
 print(getattr(c1,'color'))# 黑
 print(getattr(c1,'addraa'))   #没有该属性则报错,AttributeError: 'Car' object has no attribute 'addraa'
 print(getattr(c1,'addraa',True))  #没有则返回后面默认值 True
 #删除属性
 delattr(c1,'power')
 delattr(c1,'start')
 delattr(c1,'fly')#不存在,则报错

3、用反射的好处

好处一:实现可插拔机制

 #项目由不同的程序员来写可实现程序可插拔
 #程序员一,module---->FtpClient
 class FtpClient:
     'ftp客户端,但是还么有实现具体的功能'
     def __init__(self,addr):
         print('正在连接服务器[%s]' %addr)
         self.addr=addr

 #程序员二来调用程序员一写的逻辑(类)
 #from module import FtpClient
 f1=FtpClient('192.168.1.1')
 if hasattr(f1,'get'):
     func_get=getattr(f1,'get')
     func_get()
 else:
     print('---->不存在此方法')
     print('处理其他的逻辑')

好处二:动态导入模块(基于反射当前模块成员)

 # 在m1文件夹下建hello.py
 def test():
     print('---->test()')
 def _test():
     print('---->_test()')

 ########################################################
 import importlib

 m = importlib.import_module('m1.hello') #直接找到hello模块
 print(m)
 m.test()
 ----------------------------------------------------
 module_t = __import__('m1.hello')  #导入的是最顶层模块m1
 print(module_t)
 module_t.hello.test()  #运行需要.hello.test()
 ----------------------------------------------------
 from m1.hello import *
 _test()   #报错,找不到_test()  私有属性在import *时是找不到的

__setattr__,__delattr__,__getattr__★

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('----> from getattr:你找的属性不存在')

    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #这就无限递归了
        self.__dict__[key]=value #应该使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx  #找不到的时候触发__getattr__执行getattr的逻辑

二次加工标准类型(包装)★

包装:python提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

 #继承/派生方法实现包装
 class List(list):  #继承list类
     def append(self, p_object):
         if type(p_object) is str:
             super().append(p_object)
         else:
             print('只能添加字符串')
 #基本的继承list的属性
 l1 = List('hello')
 print(l1)  #['h', 'e', 'l', 'l', 'o']
 #修改后的append方法
 l1.append('car')
 print(l1)  #['h', 'e', 'l', 'l', 'o', 'car']
 # 修改后的append方法无法添加非字符串数据
 l1.append(12)  #运行显示:只能添加字符串

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

★实现授权的关键点就是覆盖__getattr__方法

 import time
 class FileHandle:
     def __init__(self,filename,mode='r',encoding='utf-8'):
         self.file=open(filename,mode,encoding=encoding) #调用open()方法
     def write(self,line):
         t=time.strftime('%Y-%m-%d %X')
         self.file.write('%s %s' %(t,line))

     def __getattr__(self, item):
         return getattr(self.file,item)

 f1=FileHandle('b.txt','w+')  #实例化
 f1.write('内存不足')  #类中有write方法故而调用自己类中的write方法(带时间戳)
 f1.seek(0)   #f1从类中找不到seek()方法,触发__getattr__方法,从而获取open()类中的方法,从而能调用seek()方法
 print(f1.read()) #同理
 f1.close() #同理
 #运行结果:
 # 2016-12-23 17:38:29 内存不足

授权模拟实现打字机显示效果

AR实景红包基于“LBS+AR+红包”的方式,用户在发、抢红包时,都需要满足地理位置和AR实景扫描两个条件。相比于既有的红包形式,互动性和趣味性都强了很多。
据介绍,除了用户之间可以借此加强线下交流,商家也可以利用AR实景红包在春节这个特殊的时间点与用户深度互动。
支付宝AR实景红包首批接入的商家包括可口可乐、宝洁、优衣库、饿了么等。
以可口可乐为例,用户打开支付宝“扫一扫“右下角的AR功能,扫描可口可乐农历新年包装广告上的福娃,就能查看过往一年中,属于自己的生活数据,
包括旅行、购物、观影等各个维度。回顾完之后,用户更有机会获得由可口可乐发送的现金红包(这段话是支付宝说的)。

a.txt

import time,sys

class Printer:
    def __init__(self,filename,mode = 'r',encofing = 'utf-8'):
        self.file = open(filename,mode,encoding=encofing)
    def __getattr__(self, item):
        return getattr(self.file,item)

    def printer(self):
        data = self.file.readlines()
        for i in data:
            for v in i:
                sys.stdout.write(v)
                sys.stdout.flush()
                time.sleep(0.3)
f = Printer('a.txt','r')
f.printer()

授权实现打字机显示效果

__getattribute__

 class Foo:
     def __init__(self,x):
         self.x=x

     def __getattr__(self, item):
         print('执行的是我')
         # return self.__dict__[item]

 f1=Foo(10)
 print(f1.x)
 f1.xxxxxx #不存在的属性访问,触发__getattr__

__getattr__

 class Foo:
     def __init__(self,x):
         self.x=x

     def __getattribute__(self, item):
         print('不管是否存在,我都会执行')

 f1=Foo(10)
 f1.x
 f1.xxxxxx #不管能不能找到属性都会触发__getattribute__方法

__getattribute__

 class Foo:
     def __init__(self,x):
         self.x=x

     def __getattr__(self, item):
         print('执行的是我')
         # return self.__dict__[item]
     def __getattribute__(self, item):
         print('不管是否存在,我都会执行')
         raise AttributeError('哈哈')

 f1=Foo(10)
 f1.x
 f1.xxxxxx

 #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

二者同时出现

描述符(__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 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

 class Foo:
     def __get__(self, instance, owner):
         print('触发get方法')
     def __set__(self, instance, value):
         print('触发set方法')
     def __delete__(self, instance):
         print('触发delete方法')
 class Bar:
     x = Foo()  #必须把描述符定义成这个类的类属性,不能定义到构造函数中
     def __init__(self,n):
         self.x = n
 b1 = Bar(10)   #触发set方法
 print(b1.__dict__)  #{}  空字典
 print(Bar.__dict__)
 b1.x  #触发get方法
 b1.x = 1 #触发set方法
 del b1.x  #触发delete方法

3 描述符分两种

  • 数据描述符:至少实现了__get__()和__set__()
 class Foo:
     def __set__(self, instance, value):
         print('set')
     def __get__(self, instance, owner):
         print('get')
  • 非数据描述符:没有实现__set__()
 class Foo:
     def __get__(self, instance, owner):
         print('get')

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

再看property

__setitem__,__getitem,__delitem__

 #字典形式操作属性的时候才是触发的item系列的函数
 #点的方式调用属性的时候是触发的attr系列的内置函数
 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]时,我执行')
         #字典形式操作属性的时候才是触发的item系列的函数
         self.__dict__.pop(key)
     def __delattr__(self, item):
         print('del obj.key时,我执行')
         #点的方式调用属性的时候是触发的attr系列的内置函数
         self.__dict__.pop(item)

 f1=Foo('ocean')
 f1['age']=18
 f1['age1']=19
 del f1.age1 #del obj.key时,我执行
 del f1['age'] #del obj[key]时,我执行
 f1['name']='jack'
 print(f1.__dict__) #{'name': 'jack'}

__str__,__repr__,__format__

改变对象的字符串显示__str__,__repr__

 # str函数或者print函数--->obj.__str__()
 # repr或者交互式解释器--->obj.__repr__()
 # 如果__str__没有被定义,那么就会使用__repr__来代替输出
 # 注意:这俩方法的返回值必须是字符串,否则抛出异常

 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('ocean',18)
 print(f1)  #名字是ocean 年龄是18
 #如果没有自定义str方法的情况下打印输出的是f1对象的内存地址
 # x = str(f1)
 # print(x)  #名字是ocean 年龄是18
 # y = f1.__str__()
 # print(y)  #名字是ocean 年龄是18

 class Foo:
     def __init__(self,name,age):
         self.name = name
         self.age = age
     def __repr__(self):
         return '名字是%s 年龄是%s'%(self.name,self.age)
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('清华','北京','公立')
print('from repr: ',repr(s1))
print('from str: ',str(s1))
print(s1)

'''
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
'''
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'asfdasdffd'))

自定制格式化字符串__format__

 date_dic={
     'ymd':'{0.year}:{0.month}:{0.day}',
     'dmy':'{0.day}/{0.month}/{0.year}',
     'mdy':'{0.month}-{0.day}-{0.year}',
 }
 class Date:
     def __init__(self,year,month,day):
         self.year=year
         self.month=month
         self.day=day

     def __format__(self, format_spec):
         if not format_spec or format_spec not in date_dic:
             format_spec='ymd'
         fmt=date_dic[format_spec]
         return fmt.format(self)

 d1=Date(2016,12,12)
 print(format(d1))  #2016:12:12
 print('{:mdy}'.format(d1)) #12-12-2016

__slots__

1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。           更多的是用来作为一个内存优化工具。

__slots__

class Foo:
    __slots__ = 'x'

f1 = Foo()
f1.x = 1
# f1.y = 2  # 报错
print(f1.__slots__)  # f1不再有__dict__
# -----------------------------------------------------------
class Bar:
    __slots__ = ['x', 'y']

n = Bar()
n.x, n.y = 1, 2
# n.z = 3  # 报错
# ------------------------------------------------------------
class Foo:
    __slots__=['name','age']

f1=Foo()
f1.name='tom'
f1.age=18
print(f1.__slots__)

f2=Foo()
f2.name='jack'
f2.age=19
print(f2.__slots__)

print(Foo.__dict__)
#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存,只有类Foo有__dict__

__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
 f1 = Foo(10)
 print(f1.__next__())
 print(f1.__next__())
 print(f1.__next__())
 print(f1.__next__()) #StopIteration: 终止  抛出异常终止程序

 #######################
 for i in f1:
     print(i)

 #for循环会捕捉到StopIteration异常自动终止而不会报错
 class Fib:
     def __init__(self):
         self._a=0
         self._b=1

     def __iter__(self):
         return self

     def __next__(self):
         self._a,self._b=self._b,self._a + self._b
         return self._a

 f1=Fib()

 print(f1.__next__())
 print(next(f1))
 print(next(f1))

 for i in f1:
     if i > 100:
         break
     print('%s ' %i,end='')

斐波那契数列

__doc__

 class Foo:
     '我是描述信息'
     pass

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

 class Bar(Foo):
     pass
 print(Bar.__doc__) #该属性无法继承给子类

__module__和__class__

 __module__ 表示当前操作的对象在那个模块

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

#m1中的hello.py内容:
class C:

    def __init__(self):
        self.name = 'ocean'
##################################################

from m1.hello import C

obj = C()
print(obj.__module__)  # 输出 m1.hello,即:输出模块
print(obj.__class__)      # 输出 <class 'm1.hello.C'>,即:输出类

__del__

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

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

class Foo:
    def __del__(self):
        print('执行我啦')
f1 = Foo()
del f1
print('------>')
#结果:
# 执行我啦
# ------>
#################################################
class Foo:
    def __del__(self):
        print('执行我啦')
f1 = Foo()
print('------>')
#结果:
# ------>
# 执行我啦
# 析构函数的调用是由解释器在进行垃圾回收时自动触发执行的,程序执行完成自动回收垃圾时触发了析构函数

__call__ 

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

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

class Foo:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('__call__')

obj = Foo()  # 执行 __init__
obj()  # 执行 __call__

metaclass