反射方法、魔法方法、元类

反射方法

1.反射的含义

通过字符串来操作对象的数据方法

2.反射的四个主要方法

hasattr():判断对象是否含有某个字符串对应的属性
getattr():获取对象字符串对应的属性
setattr():根据字符串给对象设置属性
delattr():根据字符串给对象删除属性

3.反射使用

class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()

target_name = input('请输入你想要核查的名字>>>:')
print(hasattr(stu, target_name))
# hasattr,帮我们实现了变量名与字符串的转换

image-20220730124558699

class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()
target_name = input('请输入你想要核查的名字>>>:')
print(getattr(stu, target_name))
'''
1.拿所给的字符串去所给的对象里面查找
2.查看是否存在一个变量名
3.如果存在的话就直接将它所绑定的值打印出来,如果是一个函数名那么加括号就可以使用了
4.如果输入一个不存在的名字,就会报错告诉我们,当前这个对象里面没有这个名字
'''

image-20220730125357524

# 需求:判断用户提供的名字在不在对象可以使用的范围内
# 方式一:利用异常处理
# try:
#     if stu.school:
#         print(f"True{stu.school}")
# except Exception:
#     print("没有属性")
# 这么写有缺陷,过于繁琐
'''
变量名school 与字符串school 区别大不大
    stu.school
    stu.'school'
两者虽然只差了引号 但是本质是完全不一样的 
'''
# 方式二:获取用户输入的名字 然后判断该名字对象有没有 上面的异常处理没有办法实现了 需要反射
while True:
    target_name = input('请输入你想要核查的名字>>>:')
    # print(hasattr(stu, target_name))
    # print(getattr(stu, target_name))
    if hasattr(stu, target_name):
        # print(getattr(stu, target_name))
        res = getattr(stu, target_name)
        if callable(res):
            print('拿到的名字是一个函数', res())
        else:
            print('拿到的名字是一个数据', res)
    else:
        print('不好意思 您想要查找的名字 对象没有')

4.使用反射的小技巧

"""
以后只要在需求中看到了关键字
	....对象....字符串 
那么肯定需要使用反射
"""

5.反射实战案例

class FtpServer:
    def serve_forever(self):
        while True:
            inp = input('input your cmd>>: ').strip()
            # 通过空格进行分割
            cmd, file = inp.split()
            if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性
                func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性
                func(file)
    def get(self, file):
        print('Downloading %s...' % file)

    def put(self, file):
        print('Uploading %s...' % file)
# 产生一个对象
obj = FtpServer()
# 用产生的对象去调用serve_forever()
obj.serve_forever()

image-20220730132000944

1.加载配置文件纯大写的配置
	# 配置文件加载:获取配置文件中所有大写的配置 小写的直接忽略  组织成字典
    import settings
    new_dict = {}
    # print(dir(settings))  # dir获取括号中对象可以调用的名字
    # ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
    for i in dir(settings):
        if i.isupper():  # 如果名字是纯大写 那么获取该大写名字对应的值   'AGE'   'INFO'
            v = getattr(settings, i)
            new_dict[i] = v
    print(new_dict)
2.模拟操作系统cmd终端执行用户命令
	class WinCmd(object):
        def dir(self):
            print('dir获取当前目录下所有的文件名称')

        def ls(self):
            print('ls获取当前路径下所有的文件名称')

        def ipconfig(self):
            print('ipconfig获取当前计算机的网卡信息')
    obj = WinCmd()
    while True:
        cmd = input('请输入您的命令>>>:')
        if hasattr(obj, cmd):
            cmd_name = getattr(obj, cmd)
            cmd_name()
        else:
            print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)

常见魔法方法

1.魔法方法的介绍

1.其实就是类中定义的对双下方法的一个别名
2.之所以会叫魔法方法是因为这些方法都是到达某个条件自动触发,无需调用
    例如:__init__方法,在实例化对象的时候,类加括号,在括号里加一些数据的时候,它会自动调用这个方法

2.魔法方法的学习

1.代码演示
class MyClass(object):
    def __init__(self, name):
        # print('__init__方法')
        self.name = name
    def __str__(self):
        """
        对象被执行打印操作的时候会自动触发
            该方法必须返回一个字符串
            返回什么字符串打印对象之后就展示什么字符串
        """
        # print('__str__方法')
        return ''
    def __call__(self, *args, **kwargs):
        # print('__call__方法')
        # print(args)
        # print(kwargs)
        pass
    def __getattr__(self, item):
        '''
        当对象获取一个不存字的属性名时,自动触发
        该方法返回什么,对象获取不存在的属性名就会得到什么
        形参item就是对象想要获取的不存在的属性名
        '''
        print('__getatter__方法')
        return 123
    def __setattr__(self, key, value):
        '''
        对象操作属性值的时候自动触发
        有  对象.属性=属性值
        __setattr__
        name
        tom
        '''
        print('__setattr__')
        print(key)
        print(value)
    def __del__(self):
        """对象在被删除(主动 被动)的时候自动触发"""
        # print('__del__')
        pass
    def __getattribute__(self, item):
        """对象获取属性的时候自动触发 无论这个属性存不存在
            当类中既有__getattr__又有__getattribute__的时候 只会走后者
        """
        # print('__getattribute__')
        # return super(MyClass, self).__getattribute__(item)  复杂写法
        return super().__getattribute__(item)  # 简便写法
    def __enter__(self):
        """对象被with语法执行的时候自动触发 该方法返回什么 as关键字后面的变量名就能得到什么"""
        print('__enter__')
    def __exit__(self, exc_type, exc_val, exc_tb):
        """对象被with语法执行并运行完with子代码之后 自动触发"""
        print('__exit__')
obj = MyClass('kerry')
# obj(1, 2, 3, name='kerry')
print(obj)
# print(obj.age)
obj.name = 'tom'
print(obj.__dict__)
with obj as f:
    print(f)
2.文字追加说明解释
1.双下init方法的结果:<__main__.MyClass object at 0x000001E4FD544B80>
2.双下str方法的结果:TypeError: __str__ returned non-string (type NoneType)
    双下str在对象执行打印操作的时候就会自动触发,并且必须要返回一个字符串数据,否则不行
    这个方法的目的是为了在我们操作打印的时候,可以做一些提示信息,做一些更改
3.双下call方法,在类里面定义一个双下call,这个类在将来产生的对象,就会有加括号调用的功能
    如果不写的话,就不能使用加括号调用
    然后触发双下call的运行,并且还可以加参数
    那么这个对象既然拥有的对象的功能还拥有了函数的功能
4.双下getatter方法,当对象获取一个不存字的属性名时,自动触发
    如果返回一个123的时候。那么在这个属性名不存在的时候,它会除非返回123,就是在不存在的时候返回什么,这个对象.属性名就会得到什么
5.双下setattr方法,对象操作属性值的时候自动触发
6.双下del方法,对象在被删除(主动 被动)的时候自动触发
    在对象执行的时候就会会自动触发
    在整个运行结束的时候,整个空间所有的东西会被删除清空,那么这个时候也会被触发
7.双下getattribute,对象获取属性的候时自动触发 无论这个属性存不存在
    当类中既有双下getattr又有双下getattribute的时候 只会走后者
8.双下enter方法,对象被with语法执行的时候自动触发,该方法return返回什么,as关键字后面的变量名就能得到什么,return什么都不返回的时候就是None
9.双下exit方法,对象被with语法执行并运行完with子代码之后 自动触发
3.额外补充知识
额外补充:
print(obj.__dict__)  # {}
obj.name(当对象获取一个不存字的属性名时,自动触发)的时候不存在,但是我们明明在双下init中存在name,这是为什么呢??
    首先,我们可以使用的双下的那些方法是因为继承的父类里面的双下方法
    然后我们在自己的类里面写了双下方法,那么就不会执行父类里面的双下方法了,现在我们知识打印了以下,并没有讲数据放入对象的独有空间里面,所以就是没有
    那么现在就是,我们把原来的方法重新写了,不能再使用到原来的方法了,子类继承了父类,又把里面的方法重新写了,覆盖了父类的方法
    如果想再次使用父类里面的原来的那个方法那么就使用:super().__setattr__(key, value)  将该有的东西传入,self可以不传
4.魔法方法笔试题
'''补全以下代码,执行之后不会报错'''
class Context:
    # 补充的代码
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass
    def do_something(self):
        pass
with Context() as f:  # Context()是一个对象,对象放到类里面,那么就靠考虑它会干什么事情
    f.do_something()

元类

元类的简介

 s1 = '哈哈哈 今天下午终于可以敲代码了!!!'
 l2 = [60, 80, 100, 120, 150, 200]
 d = {'name': '死给我看', 'age': 18}
 print(type(s1))  # <class 'str'>
 print(type(l2))  # <class 'list'>
 print(type(d))  # <class 'dict'>
"""
基础阶段我们使用type来查找数据的数据类型
但是学了面向对象之后 发现查看的不是数据类型 而是数据所属的类
我们定义的数据类型 其实本质还是通过各个类产生了对象
    class str:
        pass
    h = 'hello' 等于 str('hello')
我们也可以理解为type用于查看产生当前对象的类是谁
"""
class MyClass:
    pass
obj = MyClass()
print(type(obj))  # 查看产生对象obj的类:<class '__main__.MyClass'>
print(type(MyClass))  # 查看产生对象MyClass的类:<class 'type'>
"""
通过上述推导 得出结论 自定义的类都是由type类产生的
我们将产生类的类称之为 '元类'
"""

产生类的俩种方式

1.class关键字
	class MyClass:
        pass

2.利用元类type
    type(name, bases, dict) -> a new type
 	type(类名,类的父类,类的名称空间)
    
"""
学习元类其实就是掌握了类的产生过程 我们就可以在类的产生过程中高度定制化类的行为
	eg:
		类名必须首字母大写
	上述需求就需要使用元类来控制类的产生过程 在过程中校验
"""

元类的基本使用

class MyMetaClass(type):
    pass
"""只有继承了type的类才可以称之为是元类"""
class MyClass(metaclass=MyMetaClass):
    pass
'''
metaclass是使用元类的关键字
如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass去声明
MyClass是由MyMetaClass负责产生的
类中的__init__用于实例化对象
元类中__init__用于实例化类
'''

class MyMetaClass(type):
    # def __init__(self):  # 报错,现在继承的是type,type里面有__init__,这个时候其实已经把元类中的__init__给替换掉了,元类里的__init__就不能在使用了,只要重写了父类,在最后都得调用回去,super().__init__(what, bases, dict)
    def __init__(self,what, bases=None, dict=None):
        # print('别磕睡')
        # print('what', what)  类名 MyClass
        # print('bases', bases) 类的父类
        # print('dict', dict) 类的名称空间
        if not what.istitle():
            # print('首字母必须大写')
            raise Exception('首字母必须大写')
        super().__init__(what, bases, dict)
"""只有继承了type的类才可以称之为是元类"""

# class Myclass(metaclass=MyMetaClass):
#     pass
class aaa(metaclass=MyMetaClass):
    pass

元类的进阶操作

元类不仅可以控制类的产生过程,还可以控制对象的产生
1.对象加括号执行产生该对象类里面的双下call
2.类加括号执行产生该类的元类里面的双下call

class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        print('__call__')
        if args:
            raise Exception('必须用关键字参数传参')
        super().__call__(*args, **kwargs)

class MyClass(metaclass=MyMetaClass):
    def __init__(self, name, age):
        self.name = name
        self.age = age

obj = MyClass('jason', 18)
obj = MyClass(name='jason', age=38)
'''
参数是怎么传入进去的?
    参数给予双下call,然后在双下call中会调用双下init
'''
"""
总结:
如果我们想高度定制对象的产生过程
	可以操作元类里面的__call__
如果我们想高度定制类的产生过程
	可以操作元类里面的__init__
"""

魔法方法之双下new方法

"""
类产生对象的步骤
	1.产生一个空对象
	2.自动触发__init__方法实例化对象
	3.返回实例化好的对象
"""
__new__方法专门用于产生空对象				骨架
__init__方法专门用于给对象添加属性		  血肉
posted @ 2022-07-31 18:12  小张不爱吃泡面  阅读(45)  评论(0编辑  收藏  举报