面向对象高级--反射、内置方法和元类,其实,我们也可以自定


1.反射

1.1什么是反射

python是一门动态语言,而反射机制被视为动态语言的关键!

反射机制指的是:在程序的运行过程中,动态的获取程序的信息和对象的功能!

‘动态’:指一开始不知道程序的信息和对象的功能,只有等到运行到那的时候才会动态获取!!!

比如:x=18
在程序运行的时候,python才会通过反射机制动态的获取到这个值是整型,并不需要一开始定义的时候,就规定这个18位整型!

1.2为什么要用反射机制

当我们获取一个对象时,很多场景下,我们是并不知道这个对象里面是有什么属性和方法的,我们需要通过反射机制,动态的获取到该对象的属性和方法!!

案例:当我们在接收用户输入指令的时候,我们接收的是一个用户输入的字符串类型的指令,我们需要通过反射机制判断,这个字符串是不是该对象的功能,如果是调用该功能,如果不是返回提示信息!

案例代码化:

class Func:
    def put(self):
        print('正在执行上传功能')

    def get(self):
        print('正在执行下载功能')

    # 该函数是用来接收用户的操作指令,并判断该对象是否有该功能,有则执行,没有则提示没有该功能
    def action(self):
        action = input('请输入操作指令:')
        if hasattr(self,action):
            getattr(self,action)()
        else:
            print('没有该功能')
obj = Func()
obj.action()

1.3如何实现反射机制

其实就是四个内置函数的使用!

class People:
    def __init__(self,name):
        self.name = name
    def say(self):
        pass

obj = People('zhang')

# 1.可以通过dir方法获取obj对象有哪些属性
print(dir(obj)) # 格式是一个列表套字符串的形式

# 2.通过字符串反射到真正的属性上,从而得到属性,操作属性
# 四个内置函数的使用
print(hasattr(obj,'name'))  # hasattr()判断obj这个对象有没有name这个属性,name是字符串格式
print(getattr(obj,'name'))  # 等同于obj.name
setattr(obj,'name','yang') # 等同于obj.name='yang'
delattr(obj,'name')  # 等同于 del obj.name
print(obj.__dict__)  # 结果为{}
# 上述四个方法也可以括号里放个类,判读类是否有该函数
res = getattr(People,'say')  # 等同于People.say
print(res)

2.内置方法

2.1什么是内置方法

定义在类的内部,以__开头__结尾的方法

特点是在满足某种情况下自动触发该方法!!!!

2.2为毛要用内置方法

为了自定义定制我们的类or对象

2.3如何使用内置方法

2.3.1 __str__方法

class Func:
    def put(self):
        print('正在执行上传功能')
    def __str__(self):
        return 'w1e' # 打印对象是返回的值,必须位字符串类型
obj = Func()
print(obj)  # 等同于print(obj.__str__())
# 不定义__str__方法,印出来为<__main__.Func object at 0x00000149DE206FA0>
# 定义__str__方法,可以在__str__内部函数指定返回的东西,return后面必须是字符串类型

2.3.2 __del__方法

该方法是在清理对象之前触发,会先执行该方法

class Func:
    def put(self):
        print('正在执行上传功能')
    def __del__(self):
        print('run...')
        # 在del内部更多的是进行清理该对象占用系统的资源,对象清理了需要发起系统调用,清理对象占据的系统资源!
obj = Func()
del obj # 清理对象了,清理完之后会直接调用__del__方法,然后在执行下面的代码;如果不手动清理,在执行代码全部运行完之后,程序也会清理,打印run
print('====')

2.3.3 __call__方法

如果想要让一个对象可以加括号调用,需要在该对象的类中添加一个__call__方法

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

    def __call__(self, *args, **kwargs):
        print(args,kwargs) #a()里传入的参数
        return 123  # __call__的返回值就是a()的返回值

a = Person('zhang')
res = a(1,2,3,a=4,b=5) # 如果想要让对象a可以加括号调用,就必须在该对象的类中定义__call__方法,不添加则报错
print(res)

同样,如果想要类可以加括号调用,需要在该类的元类里添加一个__call__方法

调用__call__方法完成了三件事:
1.调用该类中的__new__方法造出一个空对象
2.调用该类中的__init__方法造出一个初始化对象(给上面的空对象添加属性,穿衣服)
3.返回初始化好的对象

2.4 总结

了解:这些内置方法__str__等又称魔法方法!

# __init__:类实例化会触发
# __str__:打印对象会触发
# __call__:对象()触发,类也是对象  类(),类的实例化过程调用元类的__call__
# __new__:在类实例化会触发,它比__init__早(造出裸体的人,__init__穿衣服)
# __del__:del 对象,对象回收的时候触发
# __setattr__,__getattr__:(.拦截方法),当对象.属性--》赋值会调用setattr,如果是取值会调用getattr
# __getitem__,__setitem__:([]拦截)
# __enter__和__exit__ 上下文管理器

上下文管理器应用:

class Person:
    def __enter__(self):
        print("我在with管理的时候,会触发")
        print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')
        return 'oo'

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('退出with代码块时执行此方法')
        print('1', exc_type)
        print('2', exc_val)
        print('3', exc_tb)


with Person() as p:   # 这句话执行,会触发类的__enter__
    print(p)

小练习:类中可以通过对象.属性的方式获取值和设置值,但是不可以通过对象['键']=值设置值和获取值,思考如何可以实现呢?

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

    def __setitem__(self, key, value): # 重写类的__setitem__方法
        setattr(self,key,value) # 当对象['键']=值触发该方法,调用setattr是触发self.key=value

    def __getitem__(self, item): # 同上
        return getattr(self,item)

p = Person('zy')
print(p.name)
p['name']=10
print(p['name'])

3.元类

3.1什么是元类

元类就是用来实例化产生类的类

关系:元类--->实例化--->类--->实例化--->对象(obj)

3.2如何查看内置的元类

其实,我们使用class定义的各种类和内置的类都是由内置的元类type帮我们实例化产生的

我们可以使用type()函数查看内置的元类

例如:在python中int、dict内置元类都继承自object类,int和dict又都是type元类的对象

print(type(int))  # <class 'type'>
print(type(dict)) # <class 'type'>

那么type和object又是什么关系呢?我们来type一下object和type!

print(type(type))  #<class 'type'>
print(type(object)) #<class 'type'>

其实:

1.object的元类其实是type类,object是由type类构造出来的对象
2.type是自己的对象(指针指向了自己)
3.type类又继承了object类

3.3class机制(class如何造出类的)

calss其实底层执行了以下四个步骤,造出了类!

class Func:
    def put(self):
        print('正在执行上传功能')
    def __del__(self):
        print('run...')
# 1.得到类名
class_name = 'Func'

# 2.得到类的基类
class_bases = (object,)

# 3.执行类体代码拿到名称空间!
class_dict = {}
class_body = """
def put(self):
    print('正在执行上传功能')
def __del__(self):
    print('run...')
"""
# exec第一个参数是类体代码、第二个是类体代码中的全局变量、第三的是一个空字典容器
exec (class_body,{},class_dict)
print(class_dict)

# 4.调用元类,得到People类
Func = type(class_name,class_bases,class_dict)
print(Func)

# Func类就是type元类实例化产生出来的对象!!!!

3.4如何自定义元类来控制类的产生

在3.3中,我们是使用type元类控制Func类的产生。其实,我们也可以自定义元类来控制类产生

class MyMeta(type):   # 只有继承了type的类才是元类
    def __init__(self,x,y,z): # 注意调用MyMeta这个类其实传入了四个参数分别是self、class_name,class_bases,class_dict
        print('run...')
        print(x) # x对应class_name
        print(y) # y对应class_bases
        print(z) # z对应class_dict

class Func(metaclass=MyMeta):
    def put(self):
        print('正在执行上传功能')
    def __del__(self):
        print('run...')

# 在自定义类的时候,metaclass默认等于type,我们可以通过指定metaclass=MyMeta来自定义元类
# 指定了metaclass=MyMeta,其实就执行了第四步调用元类Func = MyMeta(class_name,class_bases,class_dict)
# 调用MyMeta(class_name,class_bases,class_dict)发生了三件事!
# 注意!!!调用它就等于调用了type的__call__方法!!!!
#     1.先造一个空对象---Func--这里其实先调用了MyMeta类里的__new__()方法
#     2.调用MyMeta这个类的__init__方法,完成初始化对象操作
#     3.返回初始化好的对象

"""
完成上述操作之后,我们就可以在自定义的MyMeta元类里面的init方法里面,规定一下类的产生必须满足那些条件!
"""

3.5 元类下的属性查找

首先,切记!!父类不是元类!!

对象.属性查找是先从自己那找,再到类中,再到该类的父类中,最后到object类

类.属性查找是先从该类的父类中找,再到父类的父类中找,再到object中找,最后还要到该类的元类中找

评论关闭