Python-元类

exec模块

什么是exec模块?

exec 模块 是Python内置的一个模块

exec模块的作用?

exec 模块可以把 “字符串形式的” Python代码 添加到全局名称空间或局部名称空间中

exec模块怎么用?

直接调用 exec()

需要传三个参数:

  • 参数1:字符串形式的Python代码
  • 参数2:全局名称空间 字典
  • 参数3:局部名称空间 字典
# 全局名称空间
'''
文本形式的python代码:
    如下:
'''
code = '''
global x
global y

x = 10
y = 20
def func():
    pass
'''

# 全局名称空间
global_dict = {"x":200}

# 局部名称空间
local_dict = {}

# 传三个参数:
# 参数1:字符串形式的Python代码
# 参数2:全局名称空间
# 参数3:局部名称空间
exec(code,global_dict,local_dict)

print(global_dict)      # {'x': 10,....}
print(local_dict)       # {'func': <function func at 0x0000000001D01E18>}

创建类的两种方式

1、 用 class 关键字创建

# 创建类的第一种方式:
class Test:
    country = "china"

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

    def speak(self):
        print("speak chinese")

p1 = Test("qinyj",18)
print(Test)

2、 手动调用 type() 实例化出来得到一个自定义的类

# 创建类的第二种方式:
class_name = "Test"
class_base = (object,)
class_dict = {}
code = '''
name = "Test"
def __init__(self,name,age):
    self.name = name
    self.age = age
def test(self):
    print("from Test.test...")
'''

# 使用exec 模块,目的:将字符串形式的Python代码执行封装到类的名称空间中
exec(code,{},class_dict)
'''
type源码:
    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
    参数说明:
        what --> 类名
        bases --> 基类/父类
        dict --> 类的名称空间
'''
Test = type(class_name,class_base,class_dict)
print(Test)

元类介绍

什么是元类?

元类就是类的类,我们自定义的类的类是type,type就是所有类的类,type就是一个元类

元类的作用?

元类可以帮我们控制类的创建

元类可以帮我们控制类的调用

怎么用元类?

1、 自定义一个元类,继承type,派生出自己的属性和方法

2、 给需要使用的类,通过metaclass 指定自定义的元类

自定义一个元类

首先我们自定义一个元类必须要继承type类,然后重写里面的方法。

# 自定义一个元类
class MyMeta(type):
    # 子类的方法与父类的方法一样,优先用子类的,子类覆盖父类的__init__方法
    # 控制了子类的定义方式
    def __init__(self,class_name,class_base,class_dict):
        if not class_name.istitle():
            raise TypeError("类的首字母必须大写")

        if not class_dict.get("__doc__"):
            raise TypeError("类的内部必须要写注释")
        super().__init__(class_name, class_base, class_dict)

    # 模拟type元类内部做的事情
    # 元类触发的__call__可以控制类的调用,调用__call__会触发以下两点:
    # 1、会调用__new__产生一个空对象
    # 2、会执行__init__(),把参数传过去,再将实例化出来的对象返回给自定义的类
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        obj.__init__(*args, **kwargs)
        return obj

    # 可以通过元类内部的__new__控制对象的创建
    # def __new__(cls, *args, **kwargs):
    #     pass

# 首先自定义一个类,
# 因为Foo类继承了元类,必须手动继承object
class Foo(object,metaclass=MyMeta):
    '''
    如果定义的类名首字母没有大写则会报错:
        TypeError: 类的首字母必须大写
    如果不写注释则会报错:
        TypeError: 类的内部必须要写注释
    注释:这是一个Foo类
    '''
    x = 10
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def f1(self):
        print("from Foo.f1")

foo = Foo("qinyj",18)       # 调用Foo对象,会触发type的__call__方法
# print(foo.f1())

ORM

什么是ORM?

ORM:对象关系映射 --> 映射到数据库MySQL中的数据表

Python中 MySQL数据库中
类名 表名
对象 一条记录
对象.属性 字段

这里模拟Django的ORM,为了将数据库的增、删、改、查,全部封装成一个个方法,比如:save,delete,update,select

在优酷项目中实现元类

'''
ORM:对象关系映射:----》映射到数据MySQL中的数据表
类名--》表名
对象--》一条记录
对象.属性--》字段

模拟Django的ORM,为了将数据库的增、删、改、查全部封装成一个个的方法:
    比如:save、delete、uptate、select
'''
class Field:
    def __init__(self,name,column_type,primary_key,default):
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default

# int类型
class IntegerField(Field):
    def __init__(self,name,column_type="int",primary_key=False,default=0):
        super().__init__(name,column_type,primary_key,default)

# str类型
class StringField(Field):
    def __init__(self,name,column_type="varchar(64)",primary_key=False,default=None):
        super().__init__(name,column_type,primary_key,default)


'''
问题1:解决代码冗余问题:比如有100张表,需要写100个__init__
解决1:使用继承,继承父类,继承一个dict

问题2:无法预测每一张表的字段是什么,无法通过父类的__init__解决问题
解决2:通过继承字典,内部的__init__,可以接受任意个数的关键字参数

问题3:继承字典的类实例化的对象,无法通过对象.属性的方式存值
解决3:对象和字典的属性时两个不同的名称空间,通过在类内部实现魔法方法
        __getattr__、__setattr__ 实现字典与对象的名称空间的属性相通,一模一样,
        并且具备字典原有的特性,取值方式和字典一样
'''


'''
创建元类,元类需要做的事情:
    1、一张表必须有一个表名
    2、一张数据表必须有一个主键,并且主键必须是唯一的
    3、将数据表中所有的字段对象,都存放在一个独立的字典中
        存不是目的,取才是目的
'''
class OrmMetaClass(type):
    # 元类  实现__new__方法
    def __new__(cls, class_name,class_base,class_dict):

        # 过滤 Models 类
        if class_name == "Models":
            # 什么事情都不做,原路返回
            return type.__new__(cls, class_name,class_base,class_dict)

        # 获取数据表的表名
        table_name = class_dict.get("table_name",class_name)

        # 定义主键的中间变量
        primary_key = None

        # 定义字典,存放数据表的字段对象
        mappings = {}

        # 遍历类名称空间中的所有属性
        for key,value in class_dict.items():
            # 过滤不想要的属性
            if isinstance(value,Field):
                mappings[key] = value
                # 判断是否是添加了主键
                if value.primary_key:

                    # 判断主键的中间变量是否存在
                    # 如果已经有了就抛异常,只能有一个主键
                    if primary_key:
                        raise TypeError("只能有一个主键")

                    # 若主键中间的变量没有值,则给中间变量赋值
                    primary_key = value.name
        # 如果上述遍历 发现没有定义主键,则抛异常必须有一个主键
        if not primary_key:
            raise TypeError("必须有一个主键")

        # 循环遍历 把类的名称空间中多余重复的属性删除掉,节省内存资源
        for key in mappings.keys():
            class_dict.pop(key)

        # 给类的名称空间添加表名、主键、存放字段对象 属性。
        class_dict["table_name"] = table_name
        class_dict["primary_key"] = primary_key
        class_dict["mappings"] = mappings
        return type.__new__(cls, class_name,class_base,class_dict)


class Models(dict,metaclass=OrmMetaClass):
    def __getattr__(self, item):
        # print(item,"在调用对象.属性没有属性值得时候触发")
        return self.get(item)

    def __setattr__(self, key, value):
        # print(key,value)
        self[key] = value

# 创建用户表类
class User(Models):
    user_id = IntegerField(name="user_id",primary_key=True)
    user_name = StringField(name="name")
    pwd = StringField(name="pwd")


# 创建电影类
class Movies(Models):
    movie_id = IntegerField(name="movie_id",primary_key=True)
    movie_name = StringField(name="movie_name")


user = User(id="001",name="qinyj",pwd="123")
print(user)
# 通过在类内部实现魔法方法,让对象.属性得到的值和用字典得到的值名称空间相通,取到的值一模一样,
# print(user.get("id"))
# user.age = 18
# print(user.age)

movie = Movies(id="002",movie_name="真实写真")
print(movie)
posted @ 2019-11-04 18:12  GeminiMp  阅读(184)  评论(0编辑  收藏  举报