29 反射 内置方法 元类

反射 内置方法 元类

1 反射

1.1 什么是反射机制

Python属于动态语言,即程序执行变量定义语句时才确定变量的类型。

反射机制指的是在程序的运行过程中能够动态地获取对象信息以及动态地调用对象功能的一种解决方案。

在程序的运行过程中,可以获取并调用这个对象的所有属性和方法。

1.2 为什么使用反射

当需要访问某个对象的属性,却不能确定这个对象是否拥有这个属性时,需要做判断。
反射可以理解为一种能力,在程序运行过程中能够临时分析出对象/类中具有的属性/方法。

def check_attr(obj, attr):
    if attr not in obj.__dict__:
        return False
    else:
        return True

class A:
    pass

a_obj = A()
a_obj.num = 10
if check_attr(a_obj, 'num'):
    print(a_obj.num)

但是上述方法存在局限性,对象调用__dict__只能获取对象自己(self)的属性和方法。

1.3 如何使用反射
1.3.1 dir和__dict__
class A:
    pass

a_obj = A()
a_obj.num = 10

num_str = dir(a_obj)[-1]
print(num_str, type(num_str))  # num <class 'str'>
print(a_obj.__dict__[num_str])  # 10

dir(obj) 返回由参数obj的属性名和方法名组成的列表,但其元素都是字符串类型,不方便直接调用。
另外,不建议通过__dict__直接操作对象。

1.3.2 四个内置函数

hasattr,getattr,setattr,delattr
这四个内置函数通过字符串操作对象的属性/方法,即通过字符串反射到对象的属性/方法上,Python通过这四个内置函数实现反射机制。

  1. hasattr
    hasattr(obj, attr_name)
    用于判断对象是否包含对应的属性。
  2. getattr
    getattr(obj, attr_name[, default])
    用于返回一个对象属性值。
    其中default — 默认返回值,如果不提供该参数,在没有对应属性时会抛出异常AttributeError。
  3. setattr
    setattr(obj, attr_name, attr_value)
    用于设置属性值,该属性不一定是存在的。
  4. delattr
    delattr(obj, attr_name)
    用于删除属性。
class A:
    name = 'A'

a_obj = A()

print(a_obj.name)  # A

if hasattr(a_obj, 'name'):
    setattr(a_obj, 'name', 'A_Name')
else:
    pass

print(a_obj.name)  # A_Name
class Ftp:
    def put(self):
        print('执行上传操作。')

    def get(self):
        print('执行下载操作')

    def interactive(self):
        method_input = input('>>>').strip()
        method = getattr(self, method_input, None)
        if method is None:
            print('指令不存在。')
        else:
            method()

ftp_obj = Ftp()
ftp_obj.interactive()

2 内置方法

2.1 介绍

定义在类的内部,以双下划线__开头并以双下划线__结尾的方法。

内置方法会在满足一定条件时自动触发执行,无需手动调用。

使用内置方法可以定制类或对象。

len('abc')  # 会自动触发 'abc'.__len__()

print('abc')  # 会自动触发 'abc'.__str__()
2.2 常用内置方法
  1. 内置方法__str__
    打印对象本身时自动触发,
    返回值必须是字符串类型。
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
    	# 这里会自动触发对象自己的方法__str__(),导致递归调用。
    	# print(self)  
        return f'姓名:{self.name},年龄:{self.age}'

p1 = Person('刘翠英', 25)  # 姓名:刘翠英,年龄:25
  1. 内置方法__del__
    删除(清理)对象时自动触发。
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __del__(self):
        print(f'删除对象{self.name}')

p1 = Person('刘翠英', 25)
# del p1

即使不执行语句 del p1,当程序结束运行时会清理对象,清理对象时会自动触发对象的方法__del__。
内置方法__del__一般用于发起系统调用,回收系统资源,例如对象中的一个属性对应了一个打开的文件,或者网络连接等。

3 元类

如果希望为函数添加功能,而且不修改函数的代码和调用方式,使用装饰器;
如果希望修改类的功能,而且不修改类的代码和调用方式,使用元类。

3.1 介绍

Python中一切皆为对象。
如何得到一个对象?通过类的实例化,即调用类后返回一个对象。

类本身也是对象,类对象是通过调用元类实例化得到的。

元类 —(实例化)—> 类 —(实例化)—> 对象

定义元类可以用于控制类的产生以及类的调用。

  1. 控制类的产生
    在元类中定义方法__new__和__init__;
  2. 控制类的调用
    在元类中定义方法__call__。
class Demo:
    pass

# 自定义类
print(type(Demo))  # <class 'type'>

# 内置类
print(type(int))  # <class 'type'>

print(type(type))  # <class 'type'>
3.2 关键字class产生类的流程
3.2.1 分析1 类的组成

一个类由三部分组成:类名,基类,类体

# 类名
class_name = 'People'  

# 基类
class_bases = (object,)  

# 类体 字符串
class_body = """  
def __init__(self, name, age):
	self.name = name
	self.age = age
"""

# 类的名称空间
class_dict = {}  
3.2.2 分析2 执行类体代码

函数exec

  1. exec(object[, globals[, locals]])
  2. object:待执行的Python代码,必须是字符串或code对象。
  3. globals:表示全局命名空间,用于存放全局变量,必须是一个字典对象。
  4. locals:表示当前局部命名空间,用于存放局部变量,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。
  5. 函数exec的返回值永远是None。

执行类体代码产生类的名称空间,并将执行过程中产生的名字放入类的名称空间中。

exec(class_body, {}, class_dict)

print(class_dict)  # {'__init__': <function __init__ at 0x地址>}
3.2.3 分析3 调用元类产生类
# 调用元类产生类
NewPeopleClass = type(class_name, class_bases, class_dict)

函数type的参数:类名,类的基类,类的名称空间。

NewPeopleClass只是个变量名。

print(NewPeopleClass)  # <class '__main__.People'>
print(NewPeopleClass.__name__)  # People
3.2.4 分析4 调用类产生对象

使用自定义类NewPeopleClass实例化产生对象。

people_obj = NewPeopleClass('刘翠英', 25)
3.2.5 总结

关键字class产生类的步骤
步骤1:获取类名;
步骤2:获取类的基类;
步骤3:执行类体代码,并将执行过程中产生的名字存入类的名称空间中;
步骤4:调用元类实例化产生类。

3.3 元类

元类是一种特殊的类,用于实例化产生类。
通过自定义元类可以对类的产生过程进行控制,即可以动态地创建类。

3.3.1 type动态地创建类

type可以用于动态地创建类,参数是类的描述信息。

type(类名,由父类名称组成的元组,包含属性/方法的字典)

def print_obj_num(self):
	print(self.num)

@classmethod
def print_cls_num(cls):
    print(cls.num)

@staticmethod
def print_none():
	pass

Demo = type(
    "Demo",
    (),
    {
    	'num': 10,
    	"print_obj_num": print_obj_num,
    	"print_cls_num": print_cls_num,
    	"print_none": print_none
    }
)

print(Demo.__base__.__name__)  # object

等价于

class Demo:
    num = 10
    def print_obj_num(self):
        print(self.num)
    
    @classmethod
    def print_cls_num(cls):
    	print(cls.num)
	
	@staticmethod
	def print_none():
		pass
3.3.2 指定元类产生类
class 类名(父类1, 父类2..., metaclass=type):
	pass

指定参数metaclass后,可以按照指定的方式创建新类,即调用指定的函数/元类后返回的对象为新产生的类。
指定函数,新产生的类是函数的返回值;
指定元类,新产生的类是元类中的方法__new__的返回值。

如果没有指定参数metaclass,默认使用type创建新类。

3.3.3 自定义元类

继承元类type的类是自定义元类。

class CustomMeta(type):
	pass
3.3.4 通过自定义元类产生类的过程

通过自定义元类产生类的过程

  1. 产生一个空对象,自动调用方法__new__;
  2. 自动调用类元类CustomMeta中的方法__init__,将新类(self)以及其类名,类的基类和类的名称空间传入方法__init__中,为新对象(新类)完成初始化操作;
  3. 返回完成初始化的对象,即新的类。
class CustomMeta(type):
	def __init__(self, class_name, class_bases, class_dict):
		super().__init__(class_name, class_bases, class_dict)
		
		print(self)  # <class '__main__.People'>
		print(class_name)  # People
		print(class_bases)  # ()
		print(class_dict)  # {'__module__': '__main__', '__qualname__': 'People', '__init__': <function People.__init__ at 0x地址>}

class People(metaclass=CustomMeta):
	def __init__(self):
		pass

通过元类type产生的类如果没有指定父类,会自动继承父类object。

class CustomMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)
        print(class_bases)  # ()

class People(metaclass=CustomMeta):
    def __init__(self):
        pass

print(People.__base__.__name__)  # object
3.3.5 示例
  1. 自定义的类的类名首字母必须大写。
class CustomMeta(type):
	def __init__(self, class_name, class_bases, class_dict):
		if class_name[0].islower():
			raise NameError('类名首字母必须大写。')
			
		super().__init__(class_name, class_bases, class_dict)

class People(object, metaclass=CustomMeta):
	pass

class people(object, metaclass=CustomMeta):  # NameError: 类名首字母必须大写。
	pass
  1. 类中必须有文档注释
class CustomMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip()) == 0:
            raise TypeError('类中必须有文档注释。')
            
        super().__init__(class_name, class_bases, class_dict)

class People(object, metaclass=CustomMeta):
    """People"""
    pass

class People1(object, metaclass=CustomMeta):  # TypeError: 类中必须有文档注释。
    """"""
    pass
3.4 方法__new__

产生一个空对象时会自动调用方法__new__。
调用方法__new__产生空对象,再调用方法__init__对空对象进行初始化。

class CustomMeta(type):
	# __init__的参数self是由__new__造的空对象
	# __init__的后三个参数来自__new__的参数*args和**kwargs
    def __init__(self, class_name, class_bases, class_dict):
        super().__init__(class_name, class_bases, class_dict)

    def __new__(cls, *args, **kwargs):
        print(cls)
        # <class '__main__.CustomMeta'> 即当前所在的类

        print(*args)
        # People (<class 'object'>,) {'__module__': '__main__', '__qualname__': 'People', '__doc__': 'People'}
        # 类名 基类组成的元组 类的名称空间组成的字典

        print(super().__name__)  # <attribute '__name__' of 'type' objects>
        return super().__new__(cls, *args, **kwargs)

class People(object, metaclass=CustomMeta):
    pass

3.4 方法__call__

如果一个对象需要是可调用的,则可以在对象的类中定义方法__call__。
调用对象就会自动触发其类的方法__call__。
类的方法__call__一般会依次调用其对象的方法__new__和__init__。

class A:
    def __call__(self, *args, **kwargs):
        print('call')
        return 123

a = A()
res = a()  # call
print(res)  # 123

对象实例化需要调用类,类能够被调用说明其元类中定义了方法__call__,
元类中的方法__call__做了三件事完成了对象的实例化过程:

  1. 调用类中的方法__new__产生一个空对象;
  2. 调用类中的方法__init__,为新对象完成初始化操作;
  3. 返回完成初始化的对象。

要点:

  1. 自定义元类可以被调用说明其元类type中定义了方法__call__。
  2. 自定义元类中的方法__call__控制了类的调用过程,即类的对象的产生过程。
class CustomMeta(type):
    def __init__(self, class_name, class_bases, class_dict):
        pass

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

    def __call__(self, *args, **kwargs):
        print(self)  # <class '__main__.People'>
        # self在CustomMeta元类中,因此是元类的对象。
        # 此时元类的对象是新类People

        print(args)  # ('喜大壮', '26')
        print(**kwargs)

        # 调用其对象People中的方法__new__和方法__init__。
        new_obj = self.__new__(self)
        print(new_obj.__dict__)  # {}
        
        self.__init__(new_obj, *args, **kwargs)
        print(new_obj.__dict__)  # {'name': '喜大壮', 'age': '26'}

        return new_obj

class People(object, metaclass=CustomMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age
	
	# People中如果没有定义方法__new__,则会去其父类中寻找,最后会找到object.__new__(cls)
    def __new__(cls, *args, **kwargs):
        # return object.__new__(cls)
        return super().__new__(cls)

p_obj = People('喜大壮', '26')
3.5 属性查找

原则:对象 => 类 => 父类
注意:父类不是元类
因此从对象开始查找时,不会找到元类中。

Python中一切皆为对象,包括类。
如果将类视为对象,产生类对象的类是元类。
因此从类开始查找时,
先作为类去访问父类,直到object,
然后作为对象去访问元类,直到type。

4 练习

4.1

通过元类将自定义类的非隐藏属性变成大写字母

class CustomMeta(type):
    def __new__(cls, class_name, class_bases, class_dict):
        new_class_dict = {}
        for each_key, each_value in class_dict.items():
            if not callable(each_value) and not each_key.startswith('__'):
                new_class_dict[each_key.upper()] = each_value
            else:
                new_class_dict[each_key] = each_value

        return super().__new__(cls, class_name, class_bases, new_class_dict)

class Demo(metaclass=CustomMeta):
    attr1 = None

    def func(self):
        pass

print(Demo.__dict__)

另一种方式

def upper_attr(class_name, class_bases, class_attr):
    new_attr = {}
    for each_name, each_value in class_attr.items():
        if not each_name.startswith('__'):
            new_attr[each_name.upper()] = each_value
        else:
            new_attr[each_name] = each_value

    return type(class_name, class_bases, new_attr)

class Demo(metaclass=upper_attr):
    attr1 = None

    def func(self):
        pass

print(Demo.__dict__)
4.2

类实例化时只能以关键字的形式传递参数,且参数将作为对象的属性,属性名均为大写字母。

class CustomMeta(type):
    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError('Only keyword arguments can used.')
            
        obj = self.__new__(self)
        for each_key, each_value in kwargs.items():
            obj.__dict__[each_key.upper()] = each_value

        return obj

class Demo(metaclass=CustomMeta):
    pass

d = Demo(attr1=123)
print(d.__dict__)
4.3

将类实例化产生对象时初始化的属性全部隐藏。

class CustomMeta(type):
    def __call__(self, *args, **kwargs):
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)

        obj.__dict__ = {f'_{self.__name__}__{each_key}': each_value for each_key, each_value in obj.__dict__.items()}
        return obj

class Demo(metaclass=CustomMeta):
    def __init__(self, attr1):
        self.attr1 = attr1

d = Demo(123)
print(d.__dict__)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值