OOP 反射 & 元类

 面向对象

反射:reflect,可以理解为自省的意思

  反射是指一个对象应该具有自我检测、修改、增加自身属性的能力

  反射就是通过字符串操作属性

涉及到的函数:hasattr & getattr & setattr & delattr

hasattr(对象,'属性名'):判断某个对象是否存在某个属性

getattr(对象,'属性名',None):从对象中取出属性     第三个值位默认值,当属性不存在是返回默认值

setattr(对象,'属性名','属性对应的值'):为对象设置添加新的属性

delattr(对象,'属性名'):从对象中删除属性

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


p = Person('rose')
if hasattr(p, 'name'):  # 判断是否具有某个属性
    print(getattr(p, 'name'))  # 获取属性  rose

setattr(p, 'age', 18)  # 设置添加属性
print(getattr(p, 'age'))  # 18

delattr(p, 'name')  # 删除属性
print(p.__dict__)  # {'age': 18}  可以看到name属性被成功删除

 使用场景:

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

  另外一个最主要的问题是:如果对象不是我们自己写的而是另一方提供的,我们就必须判断这个对象是否满足要求,也就是是否具有我们需要的属性和方法 
# 以框架设计方式为例:
# 需求:要实现一个用于处理用户的终端指令的小框架

import kkkkk


def run(a):
    while True:
        cmd = input('请输入指令>>>  ')
        if cmd == 'exit':
            break

        if hasattr(a, cmd):
            res = getattr(a, cmd)
            res()
        else:
            print('不支持该指令!!!')

    print('拜拜了您嘞!')


# 生成模块的实例化对象
wincmd = kkkkk.Windows()
linuxcmd = kkkkk.Linux()
# 调用框架来使用
run(wincmd)  # 括号里传入模块的实例化对象,这样就可以使用到模块实例化对象中的方法
# kkkkk模块内容

class Windows:
    def cd(self):
        print('Windows 切换目录...')

    def dir(self):
        print('Windows 列出所有目录')

    def delete(self):
        print('Windows 删除文件')


class Linux:
    def cd(self):
        print('Linux 切换目录')

    def rm(self):
        print('Linux 删除文件')

    def ls(self):
        print('Linux 列出所有目录')

  上述框架代码中,写死了必须使用某个类,这是不合理的;因为无法提前知道对方的类在什么地方以及类叫什么

  所以我们应该为框架的使用者提供一个配置文件,要求对方将类的信息写入配置文件;如将模块路径和类名写到settings中

  然后框架自己去加载需要的模块(从settings中导入模块路径及类名,拿到类后实例化对象,最后调用框架)

import kkkkk


def run(a):
    while True:
        cmd = input('请输入指令>>>  ')
        if cmd == 'exit':
            break

        if hasattr(a, cmd):
            res = getattr(a, cmd)
            res()
        else:
            print('不支持该指令!!!')

    print('拜拜了您嘞!')

# 在settings拿到你需要的类
path = settings.CLASS_PATH
# 从settings单独拿出模块路径和类名
module_path, class_name = path.rsplit('.', 1)
# 拿到模块
get_class = importlib.import_module(module_path)
# 拿到类
cls = getattr(get_class, class_name)
# 实例化对象
obj = cls()
# 调用框架
run(obj)
# kkkkk

class Windows:
    def cd(self):
        print('Windows 切换目录...')

    def dir(self):
        print('Windows 列出所有目录')

    def delete(self):
        print('Windows 删除文件')


class Linux:
    def cd(self):
        print('Linux 切换目录')

    def rm(self):
        print('Linux 删除文件')

    def ls(self):
        

print('Linux 列出所有目录')
# settings

# 作为框架使用者 在配置文件中指定你配合框架的类是哪个  这里面要求格式书写————  模块名与类

CLASS_PATH = 'kkkkk.Windows'  # ---模块名与类----字符串形式

元类metaclass 创建类的类

  作用:用于创建类

  解释:对象是通过类实例化产生的,万物皆对象,类也是对象,所以也有产生类对象的类,这个类就叫元类

  PS:通常默认所有的类的元类都是type

  扩展:如果只是为了创建类就不需要使用元类,直接定义就可以了。

     所以我们使用元类,一般是为了在类创建的过程时候进行一些限制

如何创建??

  1.定义一个类,使其继承type这样这个类就变成了一个元类,并且这个元类就有了type所有的属性方法

    type(类名,基类,类的名称空间)

    type(class_name, class_base, class_dict)   

    type(字符串,元祖,字典)  type('', (), {})

  2.对定义的元类进行初始化,覆盖type中的init方法

# 定义一个元类,使其限制新创建的类的类名必须使用大驼峰体

class MyType(type):
    def __init__(self, name, base, dict):
        super().__init__(name, base, dict)
        if name.istitle():
            pass
        else:
            raise Exception('每个单词首字母要大写!')


class person(metaclass=MyType):  # 报错,因为没有使用大驼峰体
    pass

(补充)元类中  __new__  的使用:

  当你要创建类对象时,会首先执行元类中的__new__方法,拿到一个空对象,然后会自动调用__init__来对这个类进行初始化操作 

  如果你覆盖了该方法则必须保证,new方法必须有返回值且必须是对应的类对象

  例:

class Meta(type):

    def __new__(cls, *args, **kwargs):
        '''
        这中间可以增加你想要的定义操作。增加以后会先执行__new__方法,有了一个返回值以后才会执行__init__方法
        不增加的话可以不写,默认返回对应的类对象
        '''
        # return super().__new__(cls,*args,**kwargs)  # 这里面的super就像相当于type
        # 可以写成下面的形式,两者等价
        obj = type.__new__(cls,*args,**kwargs)
        return obj

    def __init__(self,a,b,c):
        super().__init__(a,b,c)

  所以__new__和__init__都可以控制类对象的创建,但是__init__更为简单

元类中  __call__  的使用:

  我们知道,类实例化的过程就是调用__call__的过程,所以如果我们想要控制对象创建过程(实例化过程),据需要覆盖call方法;但是,覆盖元类中的call之后,这个类就无法产生对象,必须调用super().__call__来完成对象的创建,并返回其返回值。如下例:

# 将对象的所有属性名称转为大写

class M(type):
    def __call__(self, *args, **kwargs):
        new_args = []
        for i in args:
            new_args.append(i.upper())
        return super().__call__(*new_args,**kwargs)  # 注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象


class K(metaclass=M):
    def __init__(self,name):
        self.name = name

k = K('www')
print(k.name)  # WWW 成功实现

 例子:


# 要求创建对象时必须以关键字参数形式来传参
# 覆盖元类的__call__
# 判断你有没有传非关键字参数 == 不能有位置参数
# 有就炸
# 第一步,创建一个元类: class K(type): # 第二步,定义实例化时候的格式 def __call__(self, *args, **kwargs): if args: raise Exception('不能使用位置传参...') # 如果是位置参数,则报错 return super().__call__(*args, **kwargs) # 注意:一旦覆盖了call必须调用父类的call方法来产生对象并返回这个对象 # 第三步,创建一个类,并制定其元类 class O(metaclass=K): def __init__(self, name): self.name = name # 第四步,实例化,创建对象的过程 # o = O('位置参数') # Exception: 不能使用位置传参... 表明位置传参不行 o = O(name = '关键字传参') print(o.name) # 关键字传参 表明关键字传参可行
原文地址:https://www.cnblogs.com/pupy/p/11271671.html