Day 33(07/07)反射

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

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

1 class Foo(object):
2     pass
3
4 obj = Foo()
5
6 isinstance(obj, Foo)

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

1 class Foo(object):
2     pass
3
4 class Bar(Foo):
5     pass
6
7 issubclass(Bar, Foo)

回到顶部

二 反射

1 什么是反射

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

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

四个可以实现自省的函数

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

 hasattr(object,name)

 getattr(object, name, default=None)

 setattr(x, y, v)

 delattr(x, y)

 四个方法的使用演示

 类也是对象

 反射当前模块成员

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

 module_test.py

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3
 4 """
 5 程序目录:
 6     module_test.py
 7     index.py
 8
 9 当前文件:
10     index.py
11 """
12
13 import module_test as obj
14
15 #obj.test()
16
17 print(hasattr(obj,‘test‘))
18
19 getattr(obj,‘test‘)()

3 为什么用反射之反射的好处

好处一:实现可插拔机制

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

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

 egon还没有实现全部功能

 不影响lili的代码编写

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

回到顶部

三 __setattr__,__delattr__,__getattr__

 三者的用法演示

回到顶部

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

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

 二次加工标准类型(基于继承实现)

 练习(clear加权限限制)

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

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

 授权示范一

 授权示范二

 练习题(授权)

回到顶部

五 __getattribute__

 回顾__getattr__

 __getattribute__

 二者同时出现

回到顶部

六 描述符(__get__,__set__,__delete__)

1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

 定义一个描述符

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

 引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行

 描述符应用之何时?何地?

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__()

 类属性>数据描述符

 数据描述符>实例属性

 实例属性>非数据描述符

 再次验证:实例属性>非数据描述符

 非数据描述符>找不到

5 描述符使用

众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能

 牛刀小试

 拔刀相助

 磨刀霍霍

 大刀阔斧

大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,low,这时候我需要教你一招:独孤九剑

 类的装饰器:无参

 类的装饰器:有参

终极大招

 刀光剑影

6 描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

7 利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

 @property回顾

 自己做一个@property

 实现延迟计算功能

 一个小的改动,延迟计算的美梦就破碎了

8 利用描述符原理完成一个自定制@classmethod

 自己做一个@classmethod

9 利用描述符原理完成一个自定制的@staticmethod

 自己做一个@staticmethod

回到顶部

六 再看property

一个静态属性property本质就是实现了get,set,delete三种方法

 用法一

 用法二

怎么用?

 案例一

 案例二

回到顶部

七 __setitem__,__getitem,__delitem__

 

回到顶部

八 __str__,__repr__,__format__

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

自定制格式化字符串__format__

 

 自定义format练习

 issubclass和isinstance

回到顶部

九 __slots__

 __slots__使用

 刨根问底

回到顶部

十 __next__和__iter__实现迭代器协议

 简单示范

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):
    print(i) 

 练习:简单模拟range,加上步长

 斐波那契数列

回到顶部

十一 __doc__

 它类的描述信息

 该属性无法被继承

回到顶部

十二 __module__和__class__

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

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

 lib/aa.py

 index.py

回到顶部

十三  __del__

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

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

 简单示范

 挖坑埋了你

回到顶部

十四 __enter__和__exit__

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

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

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

 上下文管理协议

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

 

如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

 

 练习:模拟Open

用途或者说好处:

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

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

回到顶部

十五 __call__

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

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

 

回到顶部

十六 metaclass

exec:三个参数

参数一:字符串形式的命令

参数二:全局作用域

参数三:局部作用域

exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字

 知识储备exec命令

1 引子(类也是对象)

1 class Foo:
2     pass
3
4 f1=Foo() #f1是通过Foo类实例化的对象

python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:

  • 把类赋值给一个变量
  • 把类作为函数参数进行传递
  • 把类作为函数的返回值
  • 在运行时动态地创建类

上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

1 #type函数可以查看类型,也可以用来查看对象的类,二者是一样的
2 print(type(f1)) # 输出:<class ‘__main__.Foo‘>     表示,obj 对象由Foo类创建
3 print(type(Foo)) # 输出:<type ‘type‘>  

2 什么是元类?

元类是类的类,是类的模板

元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例Foo类是 type 类的一个实例)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

3 创建类的两种方式

方式一:使用class关键字

class Chinese(object):
    country=‘China‘
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print(‘%s is talking‘ %self.name)

方式二(就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

准备工作:

创建类主要分为三部分

  1 类名

  2 类的父类

  3 类体

#类名
class_name=‘Chinese‘
#类的父类
class_bases=(object,)
#类体
class_body="""
country=‘China‘
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print(‘%s is talking‘ %self.name)
"""

步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)

print(class_dic)
#{‘country‘: ‘China‘, ‘talk‘: <function talk at 0x101a560c8>, ‘__init__‘: <function __init__ at 0x101a56668>}

步骤二:调用元类type(也可以自定义)来产生类Chinense

Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo

print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
‘‘‘
<class ‘__main__.Chinese‘>
<class ‘type‘>
True
‘‘‘

我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Foo’,表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type(‘Foo‘,(Bar,),{})

4 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

所以类实例化的流程都一样,与三个方法有关:(大前提,任何名字后加括号,都是在调用一个功能,触发一个函数的执行,得到一个返回值)

1.obj=Foo(),会调用产生Foo的类内的__call__方法,Foo()的结果即__call__的结果
2.调用__call__方法的过程中,先调用Foo.__new__,得到obj,即实例化的对象,但是还没有初始化
3.调用__call__方法的过程中,如果Foo.__new__()返回了obj,再调用Foo.__init__,将obj传入,进行初始化(否则不调用Foo.__init__)
 
    总结:        __new__更像是其他语言中的构造函数,必须有返回值,返回值就实例化的对象        __init__只是初始化函数,必须没有返回值,仅仅只是初始化功能,并不能new创建对象

前提注意:

1. 在我们自定义的元类内,__new__方法在产生obj时用type.__new__(cls,*args,**kwargs),用object.__new__(cls)抛出异常:TypeError: object.__new__(Mymeta) is not safe, use type.__new__()

2. 在我们自定义的类内,__new__方法在产生obj时用object.__new__(self)

元类控制创建类:

class Mymeta(type):
    def __init__(self):
        print(‘__init__‘)

    def __new__(cls, *args, **kwargs):
        print(‘__new__‘)

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

class Foo(metaclass=Mymeta):
    pass

print(Foo)
‘‘‘
打印结果:
__new__
None
‘‘‘

‘‘‘
分析Foo的产生过程,即Foo=Mymeta(),会触发产生Mymeta的类内的__call__,即元类的__call__:
    Mymeta加括号,会触发父类的__call__,即type.__call__
    在type.__call__里会调用Foo.__new__
    而Foo.__new__内只是打印操作,没有返回值,因而Mymeta的结果为None,即Foo=None
‘‘‘

 定制元类,控制类的创建

 应用:限制类内的函数必须有文档注释

元类控制类创建对象

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(‘__call__‘)

class Foo(metaclass=Mymeta):
    pass

obj=Foo() #Foo加括号,触发Mymeta的__call__...

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        #self=<class ‘__main__.Foo‘>
        #args=(‘egon‘,)
        #kwargs={‘age‘:18}
        obj=self.__new__(self) #创建对象:Foo.__new__(Foo)
        self.__init__(obj,*args,**kwargs) #初始化对象:Foo.__init__(obj,‘egon‘,age=18)
        return obj #一定不要忘记return
class Foo(metaclass=Mymeta):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls,*args,**kwargs)

obj=Foo(‘egon‘,age=18) #触发Mymeta.__call__

print(obj.__dict__)

 单例模式

 应用:定制元类实现单例模式

 自定制元类

 自定制元类纯净版

 自定制元类精简版

 1 #元类总结
 2 class Mymeta(type):
 3     def __init__(self,name,bases,dic):
 4         print(‘===>Mymeta.__init__‘)
 5
 6
 7     def __new__(cls, *args, **kwargs):
 8         print(‘===>Mymeta.__new__‘)
 9         return type.__new__(cls,*args,**kwargs)
10
11     def __call__(self, *args, **kwargs):
12         print(‘aaa‘)
13         obj=self.__new__(self)
14         self.__init__(self,*args,**kwargs)
15         return obj
16
17 class Foo(object,metaclass=Mymeta):
18     def __init__(self,name):
19         self.name=name
20     def __new__(cls, *args, **kwargs):
21         return object.__new__(cls)
22
23 ‘‘‘
24 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__
25
26 而爹.__call__一般做两件事:
27 1.调用name.__new__方法并返回一个对象
28 2.进而调用name.__init__方法对儿子name进行初始化
29 ‘‘‘
30
31 ‘‘‘
32 class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
33 Foo=Mymeta(‘foo‘,(...),{...})
34 因此我们可以看到,只定义class就会有如下执行效果
35 ===>Mymeta.__new__
36 ===>Mymeta.__init__
37 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta(‘Foo‘,(...),{...})操作,
38 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
39 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
40 ‘‘‘
41
42 ‘‘‘
43 obj=Foo(‘egon‘)
44 的原理同上
45 ‘‘‘
46
47 ‘‘‘
48 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
49 1.谁后面跟括号,就从谁的爹中找__call__方法执行
50 type->Mymeta->Foo->obj
51 Mymeta()触发type.__call__
52 Foo()触发Mymeta.__call__
53 obj()触发Foo.__call__
54 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
55 ‘‘‘

练习一:在元类中控制把自定义类的数据属性都变成大写

 

练习二:在元类中控制自定义的类无需__init__方法

  1.元类帮其完成创建对象,以及初始化操作;

  2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument for key function;

  3.key作为用户自定义类产生对象的属性,且所有属性变成大写

ps:http://www.cnblogs.com/linhaifeng/articles/6204014.html  (参考老师博客)

  http://www.cnblogs.com/linhaifeng/articles/6182264.html#_label31  (参考老师博客)

时间: 2024-10-10 16:26:28

Day 33(07/07)反射的相关文章

【谜客帝国】第79届阳钧天歌主擂谜会(2016.07.07)

[谜客帝国]第79届阳钧天歌主擂谜会(2016.07.07) 主持:瓷   计分:手手 1.主动携手,昂首去西川(形容词)压抑 2.门客先到,必须蛙跳(称谓)闺蜜 3.不死的摇钱树(3字鲁迅小说人名)金永生 4.高手移居西城,她也去比试(称谓)老尼姑 5.遗容依旧(7字俗语)面不改色心不跳 6.缺少的缘故(名词,徐妃格)炊烟 7.未有好处(形容词,徐妃格)洋溢 8.山里飞花,坟前寒烛(人体特征)老茧 9.灾后工作模式,重新合作来依靠(旧物品)烟袋 10.守备送出关,吕布驰去也(象棋术语)过宫马

【BZOJ做题记录】07.07~?

在NOI一周前重开一个坑 最后更新时间:7.07 11:26 7.06 下午做的几道CQOI题: BZOJ1257: [CQOI2007]余数之和sum:把k mod i写成k-k/i*i然后分段求后面的部分就好了 BZOJ1258: [CQOI2007]三角形tri:在草稿纸上按照位置和边找一下规律就好了 BZOJ1260: [CQOI2007]涂色paint:简单的区间DP BZOJ1303: [CQOI2009]中位数图:小于中位数的改为-1大于的改为1,算一算前缀和然后哈希一下乘一乘就好

2016/07/07 wamp中apache2.4.9允许外部访问的配置 重点是版本 版本不同配置效果不同

wamp安装包中安装好服务器之后只能使用127.0.0.1来访问了,如果我们要设置多个站点或其它像192.168.1.1这种就需要进行一下修改,具体步骤如下. wamp-apache2.4.9允许外部访问的配置,apache2.2配置外网访问的方法跟apache2.4.9的有点不同 apache2.2配置方法: 打开apache目录下的httpd.conf配置文件,找到关键字:”deny from”,会发现一处 “deny from”下有一行”Allow from 127.0.0.1″ ,然后将

2016/07/07 apmserv5.2.6 Apache启动失败,请检查相关配置。MySQL5.1已启动。

因为要用PHP做一个程序,在本机上配PHP环境,下了个APMServ5.26,安装很简单,不再多说,装好后,启动,提示错误,具体是:“Apache启动失败,请检查相关配置.√MySQL5.1已启动”,然后就在网上找解决办法,倒是找到不少,但都没有解决问题,差点就想换一个集成环境了.不过知难而进一向是我的原则,最后终于解决了,现在把所有解决步骤整理出来,希望能对碰到同样情况的朋友有所帮助,如果有有朋友碰到新的情况,欢迎同我交流.另外如果大家有自已各方面经验,欢迎在阳关道网站上发布出来跟大家共享一下

2016/07/07 PHP的线程安全与非线程安全版本的区别

Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍. 从2000年 10月20日发布的第一个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用多进程的工作方式不 同的是Windows系统是采用多线程的工作方式.如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程的基础之上的

2016/07/07 mymps(蚂蚁分类信息/地方门户系统)

mymps(蚂蚁分类信息/地方门户系统)是一款基于php mysql的建站系统.为在各种服务器上架设分类信息以及地方门户网站提供完美的解决方案. mymps,整站生成静态,拥有世界一流的用户体验,卓越的访问速度和负载能力. 产品十大亮点功能简表 编辑 (1)在线支付:整合在线支付接口如支付宝,财付通,网银支付接口. (2)整合论坛:整合ucenter,同步ucenter应用如discuz论坛,discuzX,ucenterhome. (3)自定义信息字段模型:各栏目类别分类信息模型选项字段完全自

陈嘉 2015/07/07 个人文档

姓名 陈嘉 日期 2015/7/7 主要工作及心得 今天,我开始了客户端数据处理.传输部分的工作.要解决的问题就是将接收用户输入到界面的数据,然后发送给服务器. 第一个问题就是从界面接收数据.经过很多种情况的考虑之后,我决定把这部分代码安排在提交按钮的监听里.在提交数据的时候,获取界面上的数据. 第二个问题是将数据从客户端发送到服务器.我们查阅了很多种数据传输方式,最终决定采用socket通信实现  遇到的问题 客户端如何与服务器进行通讯 解决方法 上网查阅相关资料,并且结合学过网络通讯的同学的

2017.07.07 Python网络编程之打印设备名称和IPv4地址

1.简单易懂直接上代码: # -*- coding=gb2312 -*-#! /usr/bin/env python# Python Network Programming Cookbook --Chapter -1# This program is optimized for python 2.7. It may run on any# other Python version with/without modifications# -*- coding=gb2312 -*-import so

2017.07.07【NOIP提高组】模拟赛B组

Summary 因为某种无法抗拒的原因,今天没有打比赛,所以也就没有那种心态.今天的题目有状压DP和二分,这套题不难也不简单,适中,适合我这种渣渣来做.在改题时,发现了许多问题.我连欧拉函数的计算都记错了,二分也忘记了.也总结出了一些经验,例如,在二分小数时,一定要比题目要求多加两位小数,这样也就避免了许多因为浮点数而造成的错误.长话短说,先看题. Problem T1 原根(math) 题目大意 给出两个定义,对于不超过m的正整数a,Gcd(m,a)=1,定义Ordm(a)为ad≡1(mod