• 元类编程


    一. property动态属性
     
    1. 首先来个例子,需求是根据出生年月日,得到某人的年龄
    from datetime import date, datetime
     
     
    class User:
        def __init__(self, name, birthday):
            self.name = name
            self.birthday = birthday
     
        def get_age(self):
            return datetime.now().year - self.birthday.year
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21))
        print(user1.get_age())
    输出结果为36
     
    注意:这里学习一下取时间的方法
    >>> from datetime import date, datetime
     
    #使用datetime来取值
    >>> datetime.now().year
    2018
    >>> datetime.now().month
    10
     
    # 使用date来取值,所得的值为int
    >>> a=date(1982,3,21)
    >>> a.year
    1982
    >>> type(a.year)
    <class 'int'>
     
     
     
    2. 下面使用@property装饰器来优化代码,它会把下面定义的函数变成一个属性描述符,也就是把函数当作一个属性来用
    from datetime import date, datetime
     
     
    class User:
        def __init__(self, name, birthday):
            self.name = name
            self.birthday = birthday
     
        @property
        def age(self):
            return datetime.now().year - self.birthday.year
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21))
        print(user1.age)
    输出结果同样为36,注意最后我们用的是user1.age来得到年龄
     
     
     
    3. 上面的@propety相当于获取年龄,下面的例子中,装饰器@age.setter可用来设置年龄
    from datetime import date, datetime
     
     
    class User:
        def __init__(self, name, birthday):
            self.name = name
            self.birthday = birthday
     
        @property
        def myage(self):
            return datetime.now().year - self.birthday.year
     
        @myage.setter
        def myage(self, value):
            self._age_set = value
     
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21))
        # 给@age.setter下的myage函数设置value值
        user1.myage = 30
        # 得到设置的value值
        print(user1._age_set)
        # 下面代码得到的是@property下的age
        print(user1.myage)
    输出结果如下
    30
    36
     
    说明:
    1) _age_set中前面的单下划线只是一种编程规范,表示这个变量不想对外暴漏。但并不能真正做到隐藏,实际上还是可以通过user1._age_set访问到;只有双下划线__age才能隐藏
    2)@property以及@myage.setter下的函数名myage必须相同,@myage.setter中的myage也必须和函数名myage相同
     
     
     
     
     
    二. __getattr__和__getattribute
     
    1. __getattr__ :在查找不到属性的时候,python就会调用这个魔法函数,可避免出现报错信息
    from datetime import date
     
    class User:
        def __init__(self, name, birthday):
            self.name = name
            self.birthday = birthday
     
        def __getattr__(self, item):
            return "not find attr"
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21))
        print(user1.age)
    输出结果如下
    not find attr
    如果不加__getattr__函数,就会报找不到age属性的错误
     
     
    1. 2 我们也可以在__getattr__中添加自己的逻辑,改写上例,添加一个参数info, 类型为字典,使对象可访问字典中的键得到相应的值
    from datetime import date
     
    class User:
        def __init__(self, name, birthday, info={}):
            self.name = name
            self.birthday = birthday
            self.info = info
     
        def __getattr__(self, item):
            return self.info[item]
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21), info={"nickname": "monkey", "gender": "male"})
        print(user1.nickname)
    输出结果为monkey
     
     
    2. __getattribute__ :对象只要调用属性,无论能否找到这个属性,都会先调用这个魔法函数,比__getattr__的优先级高
    在上面代码不变的基础上添加一个__getattribute__函数
    from datetime import date
     
    class User:
        def __init__(self, name, birthday, info={}):
            self.name = name
            self.birthday = birthday
            self.info = info
     
        def __getattr__(self, item):
            return self.info[item]
     
        def __getattribute__(self, item):
            return "hong"
     
    if __name__ == "__main__":
        user1 = User("jack", date(1982, 3, 21), info={"nickname": "monkey", "gender": "male"})
        print(user1.nickname)
    输出结果为hong
    说明:这个魔法函数最好别随便重写,不然会造成整个属性系统崩溃,写框架的时候用比较好
     
     
     
     
     
    三. 属性描述符和属性查找过程
     
    例子1,需求:验证属性类型,如果类型正确,保存起来;否则,报自定义错误信息
    import numbers
     
    class IntField:
        # 数据描述符可按照自定义的逻辑来检查对象
        def __get__(self, instance, owner):
            return self.value
     
        def __set__(self, instance, value):
            if not isinstance(value, numbers.Integral):
                raise ValueError("int value need")
            if value < 0:
                raise ValueError("positive value need")
            self.value = value
     
        def __delete__(self, instance):
            pass
     
    class User:
        age = IntField() #这里的age是一个属性描述符的对象
     
    if __name__ == "__main__":
        user = User()
        user.age = 30  #调用上面的__set__函数
        print(user.age) #调用上面的__get__函数
     
    输出30
     
    说明:
    1) __get__函数中的对象属性value需要和__set__函数中的对象属性value相同
    2) 如果user.age = "abc",就会报"int value need"的错误信息
    3) numbers库中的Integral类可用来判断,对象是否为int类型
    4) 要把一个类变成属性描述符,只需要在其中加入上面的三个魔法函数的任一个就可以了
     
     
    1. 2 上面的IntField是数据非数据属性描述符如下,格式上的区别是只有一个__get__魔法函数,类的名字随意
    class NonDataIntField:
        #非数据属性描述符
        def __get__(self, instance, owner):
            return self.value
     
     
    2. 属性查找过程
    如果user是某个类的实例,那么user.age(以及等价的getattr(user,’age’))首先调用__getattribute__。
    如果类定义了__getattr__方法,那么在__getattribute__抛出 AttributeError 的时候就会调用到__getattr__,
    而对于描述符(__get__)的调用,则是发生在__getattribute__内部的。
    user = User(), 那么user.age 顺序如下:
    1)如果“age”是出现在User或其基类的__dict__中, 且age是data descriptor, 那么调用其__get__方法(类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类__dict__里的)
    2)如果“age”出现在user对象的__dict__中, 那么直接返回 obj.__dict__[‘age’]
    3)如果“age”出现在User或其基类的__dict__中,并且age是non-data descriptor,那么调用其__get__方法, 否则返回 __dict__[‘age’](例如age=1)
    4)如果User有__getattr__方法,调用__getattr__方法,
    5)  上面都不符合的话,抛出AttributeError
     
     
     
     
    四. __init__和__new__的区别
     
    1) __new__允许在生成类的对象之前添加逻辑,它传进来的参数cls表示类本身,可自定义类的生成过程
    2) __init__传进来的参数self表示类的对象本身,是用来完善对象的
    3) 调用__new__函数生成对象之后,并且__new__方法中要返回对象,才会调用__init__函数
    class User:
     
        def __init__(self, name):
            self.name = name
            print("in init")
     
        def __new__(cls, *args, **kwargs):
            print("in new")
            #return super().__new__(cls)
     
    if __name__ == "__main__":
        user = User(name="jack")
    输出结果如下
    in new
    return super().__new__(cls)会返回一个对象,取消前面的注释后,输出如下
    in new
    in init
    上例表明会先执行__new__魔法函数,并且没有返回对象时,不会调用__init__函数;
     
     
     
     
     
    五.自定义元类
     
    1. 一个简单的例子
    # 类也是对象,这里函数中返回一个类,type创建类的类
    def create_class(name):
        if name == "user":
            class User:
                def __str__(self):
                    return "user"
            return User
     
        elif name == "company":
            class Company:
                def __str__(self):
                    return "company"
            return Company
     
    if __name__ == "__main__":
        MyClass = create_class("user")
        my_obj = MyClass()
        print(my_obj)
    返回结果为user
     
     
    2. type也可以动态创建类,定义类里面的变量,函数和继承其他类的简单例子如下
    def say(self):
        # return self.name
        return "i am user"
     
    class BaseClass:
        def answer(self):
            return "i am baseclass"
     
    USER = type("User", (BaseClass, ), {"name": "jack", "age": "14", "say": say})
    my_obj = USER()
     
    print(type(my_obj))
    print(my_obj.age)
    print(my_obj.say())
    print(my_obj.answer())
    输出结果如下
    <class '__main__.User'>
    14
    i am user
    i am baseclass
    说明:
    1) 在type函数中,第一个参数User定义了要创建类的名字,第2个参数()表示要继承的类,如果为空则继承object类;第三个参数{}表示类的属性或方法
    2) 在参数中写入方法时,只能写方法名,后面不能加()
    3) type方法定义一个类继承另外一个类时,在父类名后面一定要加逗号
     
     
    3. 什么是元类? 
    元类是创建类的类,比如type,常见的用法是定义一个类来继承type,那么这个新定义的类就是元类
     
    python中类的实例化过程:
    首先找自定义的metaclass, 通过metaclass来创建类对象,如果没有metaclass, 则会使用type来创建类对象
    class MetaClass(type):
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls, *args, **kwargs)
     
     
    class User(metaclass=MetaClass):
        def __init__(self, name):
            self.name = name
     
        def __str__(self):
            return "user"
     
    if __name__ == "__main__":
        my_obj = User(name="jack")
        print(my_obj)
    输出为user
     
     
     
     
    六,通过元类实现orm,先了解下代码吧,以后在细看
    # 需求
    import numbers
     
    class Field:
        pass
     
    class IntField(Field):
        # 数据描述符
        def __init__(self, db_column, min_value=None, max_value=None):
            self._value = None
            self.min_value = min_value
            self.max_value = max_value
            self.db_column = db_column
            if min_value is not None:
                if not isinstance(min_value, numbers.Integral):
                    raise ValueError("min_value must be int")
                elif min_value < 0:
                    raise ValueError("min_value must be positive int")
            if max_value is not None:
                if not isinstance(max_value, numbers.Integral):
                    raise ValueError("max_value must be int")
                elif max_value < 0:
                    raise ValueError("max_value must be positive int")
            if min_value is not None and max_value is not None:
                if min_value > max_value:
                    raise ValueError("min_value must be smaller than max_value")
     
        def __get__(self, instance, owner):
            return self._value
     
        def __set__(self, instance, value):
            if not isinstance(value, numbers.Integral):
                raise ValueError("int value need")
            if value < self.min_value or value > self.max_value:
                raise ValueError("value must between min_value and max_value")
            self._value = value
     
     
     
    class CharField(Field):
        def __init__(self, db_column, max_length=None):
            self._value = None
            self.db_column = db_column
            if max_length is None:
                raise ValueError("you must spcify max_lenth for charfiled")
            self.max_length = max_length
     
        def __get__(self, instance, owner):
            return self._value
     
        def __set__(self, instance, value):
            if not isinstance(value, str):
                raise ValueError("string value need")
            if len(value) > self.max_length:
                raise ValueError("value len excess len of max_length")
            self._value = value
     
     
    # 定义元类
    class ModelMetaClass(type):
        def __new__(cls, name, bases, attrs, **kwargs):
            if name == "BaseModel":
                return super().__new__(cls, name, bases, attrs, **kwargs)
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    fields[key] = value
            attrs_meta = attrs.get("Meta", None)
            _meta = {}
            db_table = name.lower()
            if attrs_meta is not None:
                table = getattr(attrs_meta, "db_table", None)
                if table is not None:
                    db_table = table
            _meta["db_table"] = db_table
            attrs["_meta"] = _meta
            attrs["fields"] = fields
            del attrs["Meta"]
            return super().__new__(cls, name, bases, attrs, **kwargs)
     
     
    class BaseModel(metaclass=ModelMetaClass):
        def __init__(self, *args, **kwargs):
            for key, value in kwargs.items():
                setattr(self, key, value)
            return super().__init__()
     
        def save(self):
            fields = []
            values = []
            for key, value in self.fields.items():
                db_column = value.db_column
                if db_column is None:
                    db_column = key.lower()
                fields.append(db_column)
                value = getattr(self, key)
                values.append(str(value))
     
            sql = "insert {db_table}({fields}) value({values})".format(db_table=self._meta["db_table"],
                                                                       fields=",".join(fields), values=",".join(values))
            pass
     
     
     
     
    class User(BaseModel):
        name = CharField(db_column="name", max_length=10)
        age = IntField(db_column="age", min_value=1, max_value=100)
     
        class Meta:
            db_table = "user"
     
     
    if __name__ == "__main__":
        user = User(name="bobby", age=28)
        # user.name = "bobby"
        # user.age = 28
        user.save()
     
     
     
    1. 当参数中有可变参数和默认参数时,要把可变参数写在前面
    2. django中,db_column为数据表中的列名,如果不写的话,列名为前面定义的变量名
  • 相关阅读:
    C语言I博客作业05
    C语言I博客作业04
    C语言I博客作业03
    C语言I博客作业02
    C语言I博客作业01
    SQL学习
    2018-7-24 列表生成式+过滤器(filter)+映射(map)+lambda总结(转)
    2018-7-13 mysql 导入大文件并进行替换字符串
    2018-7-12python爬取历史天气数据
    Python语法.md
  • 原文地址:https://www.cnblogs.com/regit/p/9881187.html
Copyright © 2020-2023  润新知