• 面向对象进阶(一)


    一、反射

    1 什么是反射

    反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

    2 python面向对象中的反射

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

     

    二、四个可以实现自省的函数

    1. 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)

    hasattr(object,name)
    
    判断object中有没有一个name的方法或属性
    getattr(obj,name,default=none) 
    
    获取object中的name方法或属性
    setattr(object, key, value)
    
    在obj中设置名为key内容为value的方法或者属性
    delattr(object, name)
    
    在object中删除name方法或者属性

     

    2. 四个方法的使用演示

    class Player:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def game(self):
            print('%s率领湖人取得总冠军' % self.name)
    
    p1 = Player('lebron_james', '35')
    print(p1.__dict__)  #{'name': 'lebron_james', 'age': '35'}
    
    #检测是否含有某属性
    print(hasattr(p1, 'name'))  #True
    print(hasattr(p1, 'game'))  #True
    
    #获取属性
    print(getattr(p1, 'name', '没有这个属性'))  #相当于print(p1.name)
    print(getattr(p1, 'game', '没有这个属性'))  #相当于prin(p1.game)
    print(getattr(p1, 'gagaf', '没有这个属性'))
    
    #设置数据属性
    setattr(p1, 'height', '2.03m')  #相当于:bi.height = '2.03m'
    print(p1.__dict__)   #{'name': 'lebron_james', 'age': '35', 'height': '2.03m'}
    
    #设置函数属性
    setattr(p1, 'func', lambda self: self.name + '牛逼')  #添加函数属性
    print(p1.__dict__)  #{'name': 'lebron_james', 'age': '35', 'height': '2.03m', 'func': <function <lambda> at 0x0000012A94FBC1E0>}
    print(p1.func(p1))  #lebron_james牛逼
    
    #删除属性
    delattr(p1, 'height')   #相当于 del p1.height
    print(p1.__dict__)   #{'name': 'lebron_james', 'age': '35'}

     

    三、自省函数与模块导入

    1.反射当前模块成员

    import sys
    
    
    def s1():
        print('s1')
    
    
    def s2():
        print('s2')
    
    
    this_module = sys.modules[__name__]  # sys.modules[__name__]返回当前文件的模块名
    
    print(this_module)
    print(hasattr(this_module, 's1'))
    print(getattr(this_module, 's2'))

    >>>

      <module '__main__' from 'C:/Users/chen/PycharmProjects/untitled1/day26/动态导入模块.py'>
      True
      <function s2 at 0x0000016D265CAA60>

     

     2.导入其他模块,利用反射查找该模块是否存在某个方法

    #需导入模块所在位置
    '''
        day26m	.py
        
        def test1():
            print('from test1')
    
        def _test2():
            print('from test2')
    '''
    
    #测试函数所在位置
    '''
        day26/动态导入模块.py
    '''
    
    import m.t as module
    
    print(hasattr(module, 'test2'))
    print(getattr(module, 'test2'))
    
    >>>
    True
    <function test2 at 0x00000189ED052C80>

    注意点:

    #模块导入方法不同所导致的差异
    
    方法一:
    module_t = __import__('m.t') 
    #module_t.test1()  #报错,这种模块导入方式只能把模块所在的最顶层导入过来
    print(module_t)    #<module 'm' from 'C:\Users\chen\PycharmProjects\untitled1\day26\m\__init__.py'>
    
    module_t.t.test1()    #from test1
    
    方法二:
    import importlib
    
    module_t = importlib.import_module('m.t')
    
    print(module_t)  #<module 'm.t' from 'C:\Users\chen\PycharmProjects\untitled1\day26\m\t.py'>
    module_t.test1()   #不会报错,这种模块导入方法直接按所指定路径把模块导入过来
    
    #所导入模块中的方法前面加了‘_’则为私有模块,如_module(),这时要注意
    
    from m.t import *    #'*'代表导入所有模块
    test1()   #from test1
    #_test2()  #报错,_test2是t的私有模块
    
    #当所导入模块为私有方法时,应该以以下方法导入:
    from m.t import test1, _test2
    test1()
    _test2()
    
    

    四、反射实现可插拔机制

    有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

    总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。

    egon还没实现全部功能

    class FtpClient:
        'ftp客户端,但是还没有实现具体的功能'
        def __init__(self, addr):
            print('正在连接服务器[%s]' % addr)
            self.addr = addr

    不影响lili的代码编写

    from m.t import FtpClient
    f1 = FtpClient('192.168.1.1')
    if hasattr(f1, 'get'):
        func_get = getattr(f1, 'get')
        func_get()
    else:
        print('---->不存在此方法')
        print('处理其他的逻辑')
    
    >>>
    正在连接服务器[192.168.1.1]
    ---->不存在此方法
    处理其他的逻辑

    五、带双下划线的attr方法__setattr__, __delattr__, __getattr__

    1. 三种方法的用法演示(和类无关,类的实例化对象才能触发)

    class Foo:
        x = 23
        y = 6
        def __init__(self, info):
            self.info = info
    
        def __getattr__(self, item):  #类内置的方法,这里是重定义了,类的实例找不到调用方法时触发,默认的方法是找不到属性就会报错
            print('因为该属性不存在,触发了__getatter__方法')
    
        def __delattr__(self, item):   #类内置的方法,这里是重定义了,类的实例删除属性时触发
            print('正在删除属性,触发了__delatter__方法')
            #del self.item   #这就会陷入无限递归了
            self.__dict__.pop(item)
    
        def __setattr__(self, key, value):   #类内置的方法,这里是重定义了,类的实例设置属性时触发
            print('正在设置属性,触发了__setattr__方法')
            #self.key = value  #这也会陷入无限递归
            self.__dict__[key] = value
    
    p = Foo('test')
    print(p.x)  #23
    print(getattr(p, 'x'))  #23
    
    p.dfasf  #因为找不到该属性,执行__getatter__方法
    
    del Foo.x  #正在删除属性,执行__delatter__方法
    # delattr(Foo, 'x')  #正在删除属性,执行__delatter__方法
    
    p.z = 23
    print(p.__dict__)

    2. 三种方法的初级作用

    class Foo:
        x = 23
        y = 6
        def __getattr__(self, item):
            print('找不到该属性,触发__getattr__方法')
    
    p = Foo()
    p.xxx
    
    #默认的内置方法下,调用不存在的属性会报错,而重定义这个方法后,不会报错
    
    class Foo:
        x = 23
        y = 6
        def __setattr__(self, key, value):
            if type(value) ==str:
                self.__dict__[key] = value
            else:
                print('只能添加字符串属性')
    
    p = Foo()
    p.z = 18  # 只能添加字符串属性
    p.z = 'lebron'
    print(p.__dict__)  #{'z': 'lebron'}
    #可指定添加属性的类型
    
    class Foo:
        x = 23
        y = 6
        def __delattr__(self, item):
            print('不能删除%s属性' % item)
    p = Foo()
    
    del p.x  #不能删除x属性
    #可禁止删除属性

    六、二次加工标准型(包装)

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

    class List(list):
        def append(self, object):
            ' 派生自己的append:加上类型检查'
            if not isinstance(object, int):
                raise TypeError('must be int')
            super().append(object)
    
        @property
        def mid(self):
            '新增自己的属性'
            m = len(self)//2
            return self[m]
    li = List([1,2,3,4,5,6])
    #li.append('c')  #报错,必须为int类型
    
    print(li.mid)
    
    #其余的方法都继承list的
    li.insert(0, -123)
    print(li)
    li.clear()
    print(li)
    
    >>>
    4
    [-123, 1, 2, 3, 4, 5, 6]
    []

    练习(clear加权限限制)

    class List(list):
        def __init__(self,item,tag=False):
            super().__init__(item)
            self.tag=tag
        def append(self, p_object):
            if not isinstance(p_object,str):
                raise TypeError
            super().append(p_object)
        def clear(self):
            if not self.tag:
                raise PermissionError
            super().clear()
    
    l=List([1,2,3],False)
    print(l)
    print(l.tag)
    
    l.append('saf')
    print(l)
    
    # l.clear() #异常
    
    l.tag=True
    l.clear()

    七、授权

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

    实现授权的关键点就是覆盖__getattr__方法(当对象调用的方法,类中不存在时,会自动触发该方法)

    授权示范1:

    import time
    class Open:
        def __init__(self, filename, mode='r', encoding='utf-8'):
            #self.filename = filename
            #self.mode = mode
            #self.encoding = encoding
            self.file = open(filename, mode, encoding=encoding)
    
        def write(self, line):
            t = time.strftime('%Y-%m-%d %X')
            self.file.write('%s %s' % (t, line))
    
        def __getattr__(self, item):
            print(item)
            #self.file.read
            return getattr(self.file, item)
    
    f1 = Open('a.txt', 'w+')
    
    print(f1.__dict__)
    print(f1.read)
    
    f1.write('今天天气晴朗
    ')
    f1.write('我又来到铁道
    ')
    f1.write('学python
    ')
    
    f1.seek(0)
    
    print(f1.read())
    
    >>>
    {'file': <_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'>}
    <_io.TextIOWrapper name='a.txt' mode='w+' encoding='utf-8'>
    read
    <built-in method read of _io.TextIOWrapper object at 0x00000185D65EAC18>
    seek
    read
    2019-03-31 16:32:26 今天天气晴朗
    2019-03-31 16:32:26 我又来到铁道
    2019-03-31 16:32:26 学python

     授权示范2:

    #我们来加上b模式支持
    import time
    class FileHandle:
        def __init__(self, filename, mode='r', encoding='utf-8'):
            if 'b' in mode:
                self.file = open(filename, mode)
            else:
                self.file = open(filename, mode, encoding=encoding)
            self.filename = filename
            self.mode = mode
            self.encoding = encoding
    
        def write(self, line):
            if 'b' in self.mode:
                if not isinstance(line, bytes):
                    raise TypeError('must be bytes')
            self.file.write(line)
    
        def __getattr__(self, item):
            return getattr(self.file, item)
    
        def __str__(self):
            if 'b' in self.mode:
                res = "<_io.BufferedReader name='%s'>" % self.filename
            else:
                res = "<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" % (self.filename,self.mode,self.encoding)
            return res
    f1 = FileHandle('b.txt','wb')
    #f1.write('你好啊啊啊啊啊')  # 自定制的write,不用在进行encode转成二进制去写了,简单,大气
    f1.write('你好啊'.encode('utf-8'))
    print(f1)
    f1.close()
  • 相关阅读:
    微信小程序tab(swiper)切换
    微信小程序如何动态增删class类名
    Vi (Unix及Linux系统下标准的编辑器)VIM (Unix及类Unix系统文本编辑器)
    js 阻止事件冒泡和默认行为 preventDefault、stopPropagation、return false
    H5中的touch事件
    CSS3 Gradient 渐变
    CSS3动画属性Transform解读
    你所不知的 CSS ::before 和 ::after 伪元素用法
    javascript移动设备Web开发中对touch事件的封装实例
    那些过目不忘的H5页面
  • 原文地址:https://www.cnblogs.com/cjsword/p/10632443.html
Copyright © 2020-2023  润新知