06 反射 冒泡排序 元类 __call__ exec

一、冒泡排序

详见算法部分

ls = [2,1,3,5,100,24,12,12,1,2,1,1,4,32]
for i in range(len(ls)-1):
    for j in range(len(ls)-1-i):
        # 如果前面的小于后面的则交换位置
        if ls[j] > ls[j+1]:
            ls[j],ls[j+1] = ls[j+1],ls[j]
print(ls)

二、反射(反省、自省) 框架的基石

1.反射

反射指的是一个对象应该具备,可以检测,修改,增加,删除自身属性的能力

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

 

涉及的四个函数,这四个函数就是普通的内置函数,与print等等没有区别, 没有双下划线,和类里面的双下的那几个不一样

hasattr    getattr   setattr   delattr 

class Stu:
    pass
s1 = Stu()
#查询属性
print(hasattr(s1,'name'))  #False
#输出属性
if hasattr(s1,'name'):
    print(getattr(s1,'name'))
#增加属性
setattr(s1,'name','123')
print(hasattr(s1,'name'))  #True
if hasattr(s1,'name'):
    print(getattr(s1,'name'))  #123
#删除属性
delattr(s1,'name')
print(hasattr(s1,'name')) #False

使用场景:

反射其实就是对属性的增删改查,但是如果直接使用内置的dict来操作,语法繁琐,不好理解

另外一个最主要的问题是,如果对象不是我们自己写的是另一方提供的,我们就必须判断这个对象是否满足要求,也就是是否具我们需要的属性和方法

2.框架了解

框架就是已经实现了最基础的所有项目都一样的部分

框架的设计者,不可能提前知道你的对象到底是怎么设计的,以你提供给框架的对象 ,必须通过判断验证之后才能正常使用

判断验证就是反射要做的事情, 当然通过__dict__也是可以实现的只是比较麻烦不容易看, 其实这些方法也就是对__dict__的操作进行了封装


需求:要实现一个用于处理用户的终端指令的小框架

补充导入模块的模块:

 

#导入用来导入模块的模块
import importlib

# 根据模块的路径,拿到模块 (libs文件夹下plugins模块,里面包含WinCMD和LinuxCMD两个类)
pl = importlib.import_module("libs.plugins")

# 从模块中取出类WinCMD
cls = getattr(pl,"WinCMD")

# 实例化产生对象
obj = cls()

#调用对象的cd方法
obj.cd()
View Code

 

框架代码:

import plugins #也就是导入插件的文件夹

# 框架已经实现的部分
def run(plugin):
    while True:
        cmd = input("请输入指令:")
        if cmd == "exit":
            break
        # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测
        # 判断对象是否具备处理这个指令的方法
        if hasattr(plugin,cmd):
            # 取出对应方法方法
            func = getattr(plugin,cmd)
            func() # 执行方法处理指令
        else:
            print("该指令不受支持...")
    print("see you la la!")

# 创建一个插件对象 调用框架来使用它
linux = plugins.LinuxCMD() #实例化LinuxCMD的对象
run(linux)
View Code

 

插件代码:

class WinCMD:
    def cd(self):
        print("wincmd 切换目录....")
    def delete(self):
        print("wincmd 要不要删库跑路?")
    def dir(self):
        print("wincmd 列出所有文件....")

class LinuxCMD:
    def cd(self):
        print("Linuxcmd 切换目录....")
    def rm(self):
        print("Linuxcmd 要不要删库跑路?")
    def ls(self):
        print("Linuxcmd 列出所有文件....")
View Code

上述框架代码中 写死了必须使用某个类,并且我们无法提前知道对方的类的位置(plugins), 以及类名(LinuxCMD)

所以我们应该为框架的使用者提供一个配置文件,要求对方将类的信息写入配置文件,想运行哪个类就在配置文件中修改类的路径

然后框架自己去加载需要的模块,如此一来,框架就与实现代码彻底解耦了,只剩下配置文件来链接框架和插件

配置文件:

# 作为框架使用者 在配置文件中指定你配合框架的类是哪个
CLASS_PATH = "libs.plugins.LinuxCMD"

最终框架代码:

import importlib
import settings

# 框架已经实现的部分
def run(plugin):
    while True:
        cmd = input("请输入指令:")
        if cmd == "exit":
            break
        # 因为无法确定框架使用者是否传入正确的对象所以需要使用反射来检测
        # 判断对象是否具备处理这个指令的方法
        if hasattr(plugin,cmd):
            # 取出对应方法方法
            func = getattr(plugin,cmd)
            func() # 执行方法处理指令
        else:
            print("该指令不受支持...")
    print("see you la la!")

# 框架 得根据配置文件拿到需要的类
path = settings.CLASS_PATH
# 从配置中单独拿出来 模块路径和 类名称 (类的路径=模块路经.类名称)
module_path,class_name = path.rsplit(".",1)
#拿到模块
mk = importlib.import_module(module_path)
# 拿到类
cls = getattr(mk,class_name)
# 实例化对象
obj = cls()
#调用框架
run(obj)

 

三、元类

1.元类概念和类的组成

元类:  用于创建类的类

  万物皆对象,类当然也是对象

    对象是通过类实例化产生的,如果类也是对象的话,必然类对象也是有另一个类实例化产生的

      默认情况下所有类的元类都是type(可以使用type直接产生类)

class Person(object):
    name = "123"
    pass
p = Person()
print(type(p))  #<class '__main__.Person'>
print(type(Person))  #<class 'type'>

 

我们可以通过调用type而不依赖于class关键字创建类

调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类由三大组成部分,分别是

1、类名class_name  (字符串)

2、基类们class_bases  (元组或者列表)

3、类的名称空间class_dic  (字典类型)

调用type时会依次传入以上三个参数

 

创建自定义类的步骤:
"""
1.获取类名(OldboyTeacher)

2.获取类的父类(object,)

3.执行类体代码获取产生的名称空间(class_dict)

4.调用元类得到自定义类OldboyTeacher = type(class_name,class_bases,class_dict)
"""

 

#知识点补充:

如何执行一段字符串内部的代码并将产生的名称空间交给对应的参数?  >>>   exec()

class_body = """
school = 'oldboy'
def __init__(self,name):
      self.name = name
def run(self):
      print('%s is running'%self.name)
"""
class_dic = {}
class_global = {}

exec(class_body,class_global,class_dic)
# class_global一般情况下都为空,除非在字符串代码内部用global关键字声明,才会将产生的名字丢到class_global全局名称空间中
print(class_dic)

{'school': 'oldboy', '__init__': <function __init__ at 0x000000B5D2771EA0>, 'run': <function run at 0x000000B5DB5B7400>}

 

有了这个exec方法后,我们就可以不依赖于calss关键字创建自定义类:(优酷中会用到下面的方法)

创建过程:

# 类名
class_name = 'OldgirlTeacher'
# 类的父类
class_bases = (object,)  # 注意必须是元祖,逗号不能忘
# 名称空间
class_body = """
school = 'oldgirl'

def __init__(self,name):
    self.name = name

def run(self):
    print(self.name)
"""
class_dic = {}
exec(class_body,{},class_dic)

#类的三要素齐全了,调用元类创建自定义类
OldgirlTeacher = type(class_name,class_bases,class_dic)
print(OldgirlTeacher)
# 结果为:<class '__main__.OldgirlTeacher'>  是一个类

# 并且它可以访问自身的属性和方法,并实例化产生对象
print(OldgirlTeacher.school) 
print(OldgirlTeacher.run)
# 结果为:
"""
oldgirl
<function run at 0x000000229B157378>  #函数名,加括号后就会执行
"""

obj
= OldgirlTeacher('jason') print(obj.school) #oldgirl obj.run() #jason

 

2.自定义元类控制类的创建

我们可以通过元类自定义一个类,对创建的类做一些限制,也就是说你的这个类一生成就带有某种特点

 

创建类是由type完成的,  type中必然包含了创建了的具体代码,  现在需要对这些代码进行修改才能实现一创建出来就带有特殊功能

两种方式:

1.修改type源代码  不可取

2.创建新的元类 使用自己的元类来创建类 从而实现定制类

 

一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type

来自定义元类

 

练习: 强制要求类名称必须大写开头,否则报错

"""
只要继承了type 那么这个类就变成了一个元类
"""
# 定义了一个元类
class MyType(type):
    def __init__(self,clss_name,bases,dict):
        super().__init__(clss_name,bases,dict)  #三个参数或者一个self
        print(clss_name,bases,dict)
        if not clss_name.istitle():  #判断首字母大写
            raise Exception("你丫的 类名不会写...")  #抛出异常

# 为pig类指定了元类为MyType
class Pig(metaclass=MyType): #指定自己的元类,指定元类这句话就可以触发MyType中的init,如果你类名首字母没有大写会抛出异常的
    pass

class Duck(metaclass=MyType):
    pass

#结果

# Pig () {'__module__': '__main__', '__qualname__': 'Pig'}
# Duck () {'__module__': '__main__', '__qualname__': 'Duck'}

 3.自定义元类控制类的调用即类对象的创建

3.1元类中的__call__

当你调用类创建对象时会自动触发元类中的__call__方法 ,并将这个类本身作为第一个参数传入,以及后面的一堆参数

覆盖元类中的call之后,这个类就无法产生对象,必须调用父类方法生成对象,并将其返回出去

使用场景:

当你想要控制对象的创建过程时,就覆盖call方法

当你想要控制类的创建过程时,就覆盖init方法

 

3.2 __call__函数的执行时机

时机1:该方法会在调用对象是自动触发执行 (对象加括号)

            通常调用一个普通对象是没有意义的

#对象加括号——执行时机
class
Foo: def __call__(self, *args, **kwargs): print("run") f = Foo() #调用Foo得到f对象 f()#调用对象时 触发__call__的执行

 

时机2: 万物皆对象,类也是一个对象,Foo()也会执行Foo的元类中的__call__函数

#类加括号——执行时机
class M(type):
    def __call__(self, *args, **kwargs):
        print("run mateclass __call__")
        pass
    
class Foo(metaclass=M):
    pass
print(Foo())
#输出 run mateclass __call__  #看到没 类加括号触发元类的双下call了吧
#输出 None 元类中的双下call无返回值,这里就是None

3.3 覆盖__call__函数时的注意事项

第一行输出表明了,调用类Foo时,的确自动执行了__call__函数,

第二行输出一个空,这是为什么呢? 因为你覆盖了type的call方法,将__call__注释起来不覆盖父类,再次测试,会发打印结果就变成了一个对象! 

 

思考:类加括号实例化对象的时候,有哪几个步骤?

  1.创建一个该类的空对象

  2.实例化该空对象

  3.将实例化完成的空对象返回给调用者


所以在__call__函数中必须完成这三步操作 ,一旦覆盖了父类的__call__函数,就必须自己来完成上述的几个步骤:

 

class MyMate(type):
    def __call__(self, *args, **kwargs):
        # 创建空对象
        # 初始化对象(我感觉这里不能调用init初始化,因为你传入的是关键字参数,还怎么指定self.name=name???)
        # 返回初始化后的对象
        obj = object.__new__(self)
# self.__init__(obj,*args,**kwargs) #这里这样写有什么意思??不懂鸭 for k,v in kwargs.items(): obj.__dict__[k]=v #这一步往空对象的内存空间中添加属性,相当于初始化,等效于init的初始化 return obj
class Foo(metaclass=MyMate): pass print(Foo()) p=Foo(name='tank',age=18) print(p.__dict__) print(p.name) ''' <__main__.Foo object at 0x0000000002828470> #这样就可以生成对象,就不是None了 {'name': 'tank', 'age': 18} tank '''

#完整案例

#需求:
#1.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
#2.key作为用户自定义类产生对象的属性,且所有属性变成大写
class Mymetaclass(type):
def __call__(self, *args, **kwargs): if args: raise TypeError('must use keyword argument for key function')
obj
= object.__new__(self) #调用父类方法创建对象,self为类Chinese     

#kwargs为字典 for k,v in kwargs.items(): obj.__dict__[k.upper()]=v #这一步往空的内存空间中添加属性,相当于初始化,等效于init的初始化
return obj class Chinese(metaclass=Mymetaclass): country='China' tag='Legend of the Dragon' #龙的传人 def walk(self): print('%s is walking' %self.name) p=Chinese(name='egon',age=18,sex='male') print(p.__dict__) # {'NAME': 'egon', 'AGE': 18, 'SEX': 'male'} print(p.country) #China 对象公用类的属性,但是在对象名称空间里面没有该属性

 

元类练习:

'''
定义u一个元类,功能是获取类中所有属性包括方法,将名字存储到一个列表中,再将列表作为类的属性
类可以调用attrs来获取所有属性的名字
'''

class Meta(type):
    def __init__(self,name,bases,dict):
        super().__init__(name,bases,dict)
        #dict中有好多内置的属性,__**__形式存在,去除后列表生成式生成
        attrs = [i for i in list(dict.keys()) if not i.startswith("__")]
        #生成列表后在这里进行声明,在下面就可以调用了
        self.attrs = attrs


class A(metaclass=Meta):
    name = "jack"
    age = 18

    def info(self):
        print("info")

    def say(self):
        print("say")

print(A.attrs)  
#['name', 'age', 'info', 'say']

 

我们就可以通过自定义元类,并重写__new__方法或者__init__来拦截类的创建过程,在类被创建出来之前进行一系列其他操作,实现类一创建出来就自带buff

new方法和init 都可以实现控制类的创建过程,init更简单,但是new功能强大

补充__new__ : https://www.cnblogs.com/xp1315458571/p/11308045.html

5. 元类实现单例

什么是单例

单例是指的是单个实例,指一个类只能有一个实例对象

为什么要用单例

当一个类的所有对象属性全部相同时,则没有必要创建多个对象

例如开发一个音乐播放器程序,音乐播放器可以封装为一个对象,那你考虑一下,当你切歌的时候,是重新创建一个播放器,还是使用已有的播放器?

因为播放器中的数据和业务逻辑都是相同的没有必要创建新的,所以最好使用单例模式,以节省资源

当两个对象的数据完全相同时 则没有必要占用两份资源

# 单例n元类
class Single(type):
    def __call__(self, *args, **kwargs):
        if hasattr(self,"obj"): #判断是否存在已经有的对象
            return getattr(self,"obj") # 有就返回
        obj = super().__call__(*args,**kwargs) # 没有则创建
        print("new 了")
        self.obj = obj # 并存入类中
        return obj

class Student(metaclass=Single):
    def __init__(self,name):
        self.name = name

# 只会创建一个对象
a=Student('tank')
b=Student('tank666')
print(id(a))
print(id(b))
print(a.name)
print(b.name)

'''
new 了
31952400
31952400
tank
tank
'''

 

看不懂.....................................
#
在类定义时 自动执行init 在init中创建实例 call中直接返回已有实例 class MyMeta(type): __instance = None def __init__(self,name,bases,dic): if not self.__instance: #判断是否已经有了,没有才会创建, self.__instance = object.__new__(self) self.__init__(self.__instance) super().__init__(name, bases, dic) def __call__(cls): return cls.__instance class Player(metaclass=MyMeta): def __init__(self): print("创建播放器了") Player() Player() # 仅执行一次创建播放器

 

 

posted @ 2019-07-30 21:17  www.pu  Views(257)  Comments(0Edit  收藏  举报