Python说文解字_详解元类

1、深入理解一切接对象:

  1.1 什么是类和对象?

    首先明白元类之前要明白什么叫做类。类是面向对象object oriented programming的重要概念。在面向对象中类和对象是最基本的两个概念。正如中国的道家所言,一生二,二生三,三生万物。类和对象正如这个阴阳的二元世界观,相辅相成存在的。

    类英文class、对象英文instance。类是描述如何创建一个对象的代码段,用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。

    因此我们有这样的概念。

    类是对象的载体,对象又分属性和方法两个方面。

    属性是对象的名词部分、方法是对象的动词部分。

  1.2 一切接对象?

    类是来自元初的,也就是在Py中类也是对象,也可以具有属性和方法两个方面。

  1.3 这是一个哲学概念。

    面向对象的最基本的问题是建立在哲学的思考上面的(当然我们用道家的思想来解释,老外可能也不是这么想的 ),因此我们有这样一个图示:  

       

     是不是面向对象的底层是哲学的观点。元类是不可再分的类,也就是类的加工厂,我们平时所建立的类其实是人家已经给你建立好的“工厂”、“模板”拿来可以直接用。当然我们也可以自定义一个“工厂”、“模板”,属于定制性的类。当然自定义一个类也好,使用人家Python给你现成的模板也罢。只要一个类一旦下生,就具备了属性和方法两个功能(人生下来就会吃饭和喘气),而且生下来的这个类本身也被大自然或者上帝看做是一个对象。

    我们知道类的继承,在Python中type函数可以表示一个对象的属性,而且还可以创建一个类,也叫做类的加工厂。我们自定义一个类,让他们分别继承在父类(object)和元类(type),用dir命令来看看他们所具有的的属性和方法。

class Users1(object):
    pass

class Users2(type):
    pass

print(dir(Users1))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
print(dir(Users2))
# ['__abstractmethods__', '__base__', '__bases__', '__basicsize__', '__call__', '__class__', '__delattr__', '__dict__', '__dictoffset__', '__dir__', '__doc__', '__eq__', '__flags__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__instancecheck__', '__itemsize__', '__le__', '__lt__', '__module__', '__mro__', '__name__', '__ne__', '__new__', '__prepare__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasscheck__', '__subclasses__', '__subclasshook__', '__text_signature__', '__weakrefoffset__', 'mro']

 

  我们发现从元类继承过来的这个类,不光具有父类继承的一些属性和方法,而且还增加了好多方法。在我们实际使用的过程中,我们大多数是在父类这个层面上面来使用的。其中关于元类的很多属性和方法都被隐藏了。

print(Users1.__base__)
print(Users2.__base__)
# <class 'object'>
# <class 'type'>
print(Users1.__bases__)
print(Users2.__bases__)
# (<class 'object'>,)
# (<class 'type'>,)

 

  我们分别打印里面的属性,就可以看到其实User1也有这属性和方法,但是都被隐藏了。

 

2.property动态属性:

   第一个问题:为什么要有动态属性?

  提出动态属性,肯定就有静态属性这么一说。静态属性是在写类的时候通过构造函数或者类属性都之前定义好的。比如说。

  例子1:构造函数定义好一些属性,平时就不太用更改了。

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

if __name__ == '__main__':
    p = Person('thomas',18)
    print(p.age)

 

   例子2:类属性定义。

class Person:
    human = 'white human'
    def __init__(self,name,age):
        self.name = name
        self.age = age

if __name__ == '__main__':
    p = Person('thomas',18)
    print(p.human)

  这两个都叫做静态属性。

  第二个问题:为什么要分动态属性和静态属性?

  其实这是一种规范而已。举个例子:比如说我们有一个人的类的属性。大类来说分:“白种人”、“黄种人”、“黑人”等等,这些属性都是显而易见基本上都不会变化的属性,他们大多数都是用于分类而用的,在编程中类似于这类的属性归入静态属性的范畴(类的特性就是归类,要学会类必须要学会归类);另外,我们在人的这个类中,姓名、年龄、身高等等,每个人的个体变化都不一样,都是纷繁复杂的变化,我们把这类属性归入动态属性。

  第三:写一个比较规范的,用构造函数改写的人的属性(动态属性和静态属性,用property装饰器的方式)

class Person:
    def __init__(self,race,national):
        self.race = race
        self.national = national

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self,value):
        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self,value):
        self._age = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self,value):
        self._height = value

if __name__ == '__main__':
    p = Person('yellow','China')
    # 打印静态属性
    print(p.race,p.national)

    # 赋值动态属性
    p.name = "Thomas"
    p.age = 38
    p.height = 183

    # 打印动态属性
    print(p.name)
    print(p.height)
    print(p.age)

# yellow China
# Thomas
# 183
# 38

 

  一个重要的注意:!

    @property
    def name(self):
        return self.name

    @name.setter
    def name(self,value):
        self.name = value
RecursionError: maximum recursion depth exceeded

  ·发现没有,如果我们将装饰器函数中的下划线去掉了。会出现了最大递归深度的报错。

  重点:在用动态属性property的时候(其实大多数时候也带注意),方法名和里面属性名一致的时候,类属性的下划线不能少,否则会报错。原因在于,如果不加下划线出现self.属性名时会调用类的getattr方法不断调用会陷入死循环。

 

3.__getattr__,__getattribute__

  #getattr是在查找不到属性的时候调用

  #getattribute是查没查到着都会调用这个属性

  第一个问题:为什么要有__getattr__和__getattribute__?

    在实际的工作中,我们如果选择一些属性的时候,误选了或者本身这个属性没有的话会报错(当然大家都是),为了完善整个类中属性的调用,给这些属性加上一些特别的提示,不用报错停止,而是返回一些提示。这就用到了这两种方法。

  第一个:关于getattr的方法

  例子1:打印一个不存在的属性

class Person:
    def __init__(self,race,national):
        self.race = race
        self.national = national

if __name__ == '__main__':
    p = Person('yellow','China')
    # 打印不存在的属性
    print(p.company)
# AttributeError: 'Person' object has no attribute 'company'

  发现会报一个属性的错误。

  例子2:我们完善一下这个属性的调用

class Person:
    def __init__(self,race,national):
        self.race = race
        self.national = national

    def __getattr__(self, item):
        print("no this attribution")

if __name__ == '__main__':
    p = Person('yellow','China')
    # 打印不存在的属性
    print(p.company)
# no this attribution
# None

  发现是打印这个属性不存在的内容,并返回一个空值。

  第二个:发现__getattr__后面的参数存在一个item的传参?这是什么?

  这是一个关于字典的传参。因此我们在__getattr__后还可以加入一些限定条件,我们就用这个传参item为例子。

  例子1:

class Person:
    def __init__(self,race,national,info={}):
        self.race = race
        self.national = national
        self.info = info

    def __getattr__(self, item):
        return self.info[item]


if __name__ == '__main__':
    p = Person('yellow','China',{"company":"CCTV"})
    # 打印不存在的属性
    print(p.company)

  我们限定了字典的键内容,可以实现直接访问值的方式。

  第三:关于__getattribute__这个是比较全的,

class Person:
    def __init__(self,race,national,info={}):
        self.race = race
        self.national = national
        self.info = info

    def __getattr__(self, item):
        return self.info[item]

    def __getattribute__(self, item):
        print("yes")

if __name__ == '__main__':
    p = Person('yellow','China',{"company":"CCTV"})
    # 打印不存在的属性
    print(p.company)
yes
None

  也就是找没找的到都会打印。

 

4. __get__、__set__、__delete__属性查找过程:

  我们通过get,set来所属于的属性是否和我们预先设计的一样。这两个函数其实就是起到了静态语言的一种作用。

  例子1:我们判断给类的属性输入30这个值是否是一个int类型的。如果不是返回是错误一句话;

import numbers


class IsInt:
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        if not isinstance(value,numbers.Integral):
            print("value is not Int")
        else:
            self.value = value
    def __delete__(self, instance):
        pass


class Person:
    age = IsInt()

if __name__ == '__main__':
    p = Person()
    p.age = 30
    print(p.age)
# 30
    p.age = 'abc'
    print(p.age)
# value is not Int

   一个重要的问题:属性描述符!

  属性描述符:描述属性的,我们知道Python语言还是一种弱类型语言,所谓弱类型语言就是变量前面不许“强调”数据类型。但是问题是,如果我们还想描述数据的话怎么实现呢?Python通过属性描述符来实现的。

  属性描述符分:数据型描述符和非数据描述符。

  补充一点儿知识:我们知道Python常用的数据类型分:数值型、数组型、字符串、字典型等。我们再把这些数据类型再进行分类:像数值类型,列表类型(Python没有),布尔数据类型,这样的叫做基本数据类型,其他数据类型叫非数据类型。

  因此属性描述符分:描述数据描述符、非数据描述符。用来判断一个数据类型是不是数据类型和非数据类型。

  另一个重要问题:非数据类型和非数据类型如何使用?

  例子1:数据描述符。

class IsInt:
    # 数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        # if not isinstance(value,numbers.Integral):
        #     print("value is not Int")
        # else:
            self.value = value
    def __delete__(self, instance):
        pass

  例子2:非数据描述符。

class NotIsInt:
    # 非数据描述符
    def __get__(self, instance, owner):
        return self.value
  def __set__(self,instance,value)
    pass

  我们发现这两者有啥不同。不同点就是get单独存在的时候和get、set、delete同时存在的时候。

  如果三个人同时存在,是描述数据类型的一种表示方式。如果get存在是描述非数据类型的表示方式。

  最后一个重要问题:优先级。

  数据描述符----优先于----非数据描述符。

  情况1:如果当前是一个数据——首先落入数据描述符——不能落入__dict__字典属性中;如果当前是一个非数据——首先落入数据描述符——不能落入__dict__字典属性中。

  情况2:如果当前是一个非数据——首先落入非数据描述符——能落入__dict__字典属性中;如果当前是一个非数据——首先落入非数据描述符——能落入__dict__字典属性中。

  情况1:

import numbers


class IsInt:
    # 数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        # if not isinstance(value,numbers.Integral):
        #     print("value is not Int")
        # else:
            self.value = value
    def __delete__(self, instance):
        pass

class NotIsInt:
    # 非数据描述符
    def __get__(self, instance, owner):
        return self.value
  def __set__(self,instance,value)
    pass
class Person: age = IsInt() if __name__ == '__main__': p = Person() p.age = 30 print(p.age) print(p.__dict__)

30
{}

 

 

  当前=30,数据描述符组合——不会落入__dict__

import numbers


class IsInt:
    # 数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        # if not isinstance(value,numbers.Integral):
        #     print("value is not Int")
        # else:
            self.value = value
    def __delete__(self, instance):
        pass

class NotIsInt:
    # 非数据描述符
    def __get__(self, instance, owner):
        return self.value
  def __set__(self,instance,value)
    pass
class Person: age = IsInt() if __name__ == '__main__': p = Person() p.age = '30' print(p.age) print(p.__dict__) 30 {}

  当前=‘30’,数据描述符组合——也不会不会落入__dict__

 

  情况2:

import numbers


class IsInt:
    # 数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        # if not isinstance(value,numbers.Integral):
        #     print("value is not Int")
        # else:
            self.value = value
    def __delete__(self, instance):
        pass

class NotIsInt:
    # 非数据描述符
    def __get__(self, instance, owner):
        return self.value
  def __set__(self,instance,value)
    pass
class Person: age = NotIsInt() if __name__ == '__main__': p = Person() p.age = 30 print(p.age) print(p.__dict__) 30 {'age': 30}

  当前=30,非数据描述符组合——会落入__dict__

 

import numbers


class IsInt:
    # 数据描述符
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        # if not isinstance(value,numbers.Integral):
        #     print("value is not Int")
        # else:
            self.value = value
    def __delete__(self, instance):
        pass

class NotIsInt:
    # 非数据描述符
    def __get__(self, instance, owner):
        return self.value
  def __set__(self,instance,value)
    pass
class Person: age = NotIsInt() if __name__ == '__main__': p = Person() p.age = '30' print(p.age) print(p.__dict__) 30 {'age': '30'}

  当前=‘30’,非数据描述符组合——会落入__dict__

 

4. __new__和__init__的区别

  第一个问题:参照开篇所说的内容,在这个“模板”类之前,还有一个元类,如果我们定义一个这样的内容:

class User:
    def __new__(cls, *args, **kwargs):
        print("in new")
    def __init__(self,name):
        print("in init")
        self.name = name


if __name__ == '__main__':
    user = User('thomas')
    print(user.name)

in new
Traceback (most recent call last):
  File "F:/QUANT/练习/house1.py", line 11, in <module>
    print(user.name)
AttributeError: 'NoneType' object has no attribute 'name'

 

  我们发现打印了new中的内容,但是抛出一个属性异常。

  这就说明了第一个问题,如果__new__不设置返回值的情况,给类进行传参,会直接进入到new这里面来而终止。

 

  第二个问题:终止之后我们想把他和当前的类进行连接如何处理?

  必须return一个返回值就可以连接了。

class User:
    def __new__(cls, *args, **kwargs):
        print("in new")
        return super().__new__(cls)
    def __init__(self,name):
        print("in init")
        self.name = name


if __name__ == '__main__':
    user = User('thomas')
    print(user.name)

    # in new
    # in init
    # thomas

  我们看到就可以正常返回了。

 

  这个时候进行总结一下:

  第一:

  元类——————????——————“模板类”:这样一个关系之间。我们通过一个“类工厂”的东西进行自定义类,因此可以写成:

  元类——————类工厂———————“模板类”。

  

  第二:类工厂是通过__new__的返回来进行连接的。

  类工厂————__new__ + return super().__new__(cls)————“模板类”。

 

5. “类工厂”——自定义类:

  类也是对象,type是创建类的类,也叫做类工厂。

  第一:我们用一个复杂的方式动态创建一个了:

def creat_class(name):
    if name == "user":
        class User:
            def __str__(self):
                return "user"
        return User
    if name == "company":
        class Company:
            def __str__(self):
                return "company"
        return Company

if __name__ == '__main__':
    myClass = creat_class("user")
    my_obj = myClass
    print(my_obj)

# <class '__main__.creat_class.<locals>.User'>

  我们发现可以用这样一种方式创建一个动态的类。

 

  第二:我们是不是可以用一种灵活的方式创建一个类

  答案是用type:type是用来获取某一个对象的类型的;另外type还是可以动态创建一个类的。我们现在用type的第二种方式。

  原型:type(object_or_name,bases,dict)

  bases是我们的基类,dict是我们的属性。

user = type("User",(),{})

 

if __name__ == '__main__':
    user = type("User", (), {})
    my_obj = user
    print(my_obj)

 

if __name__ == '__main__':
    user = type("User", (), {"name":"User"})
    my_obj = user
    print(my_obj.name)
    # User

 

  我们可以这样创建我们的属性。

  

  第二:我们还可以在这个动态类的创建方式中,创建一个方法。

def say(self):
    return "i am user"

if __name__ == '__main__':
    user = type("User", (), {"name":"User","say":say})
    my_obj = user()
    print(my_obj.say())
    # i am user

 

  

  第三:我们还可以继承

def say(self):
    return "i am user"

class BaseClass:
    def answer(self):
        return "i am answer"

if __name__ == '__main__':
    user = type("User", (BaseClass,), {"name":"User","say":say})
    my_obj = user()
    print(my_obj.answer())
    # i am answer

 

 

6. 可以回答什么是元类了:

  元类是创建类的类,对象<-class(对象)<-type。

  metaclass创建元类的连接。

  Python中的类的实例化过程,首先回去寻找metaclass这个属性,通过metaclass去创建user类,type去创建类对象,实例

 

  第一:我们从上面的例子可以看到__new__这个魔法方法是放到类当中去实现的,其实经常用的方式,我们可以把这不元类的创建单独剥离出来通过metaclass的方式去继承这个类工厂加工出来的东西。

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("i am metaclass")
        return super().__new__(cls,*args, **kwargs)


class User(metaclass=MetaClass):
    def __init__(self,name):
        self.name = name


if __name__ == '__main__':
    my_obj = User("thomas")
    print(my_obj)

i am metaclass
<__main__.User object at 0x0000026399C40898>

  这样我们就剥离出来这个创建类的类的这个过程了。注意,在这里我们返回的__new__的父类(这里的父类是元类type),要加上参数,这是和放到类中直接创建不同的。这样剥离出来也是模式中的低耦合、高聚合的方式。

  另外,我们可以不通过继承,也可以也可以这样创建。

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("i am metaclass")
        return super().__new__(cls,*args, **kwargs)


class User:
    __metaclass__ = MetaClass

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


if __name__ == '__main__':
    my_obj = User("thomas")
    print(my_obj)

 

  

  第二:我们这样有两个疑惑了

  疑惑1:优先级:

  如果一个类的创建过程中,标注出metaclass=这样的方式优先级是最高的,如果在子类当中步去直接继承,会首先在子类当中出查找是否具有metaclass,如果没有去父类查找,父类没有去元类查找,如果写明来自元类的继承,这样这个子类具有了很高的优先级。

  疑惑2:捣鼓这些元类、类工厂是做什么用的?

  其实我们观察很多的模块当中用的这样的方式,不用线程的“模板”类,而且找“类工厂”去加工一个自己的类,目的就是为了在调用这个类的时候把一些首先需要判断的因素考虑在内,先去检测,不合格去抛异常,或者给这个类首先具备一些功能。

 

7. 用 ORM来做应用:

  ORM是啥?

  ORM是object relation mapping,对象关系映射。是一种程序技术,用于面向对象编程语言里不同系统的数据之间的转换。从效果上来说,它其实就是创建了一个可以在编程语言里使用的“虚拟对象数据库”。

  ORM三个核心原则:

  简单:以最基本的形式建模数据。

  传达性:数据库结构被任何人都能理解的语言文档化。

  精确性:基于数据模型创建正确标准化的结构。

import numbers

class Field:
    pass

class IntField:
    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("mix_value must be smaller than max_value")
    def __get__(self, instance, owner):
        return self._value
    def __set__(self, instance, value):
        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
        self.max_length = max_length
        if max_length is None:
            raise ValueError("you must specify max_length for charField")
        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("string 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 = db_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(value,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_length=0,max_length=100)

    class Meta:
        db_table = "user"

if __name__ == '__main__':
    user = User(name="bobby",age =28)
    # user.name = "bobby"
    # user.age = 28
    user.save()

 

posted @ 2019-05-30 20:34  时海涛|Thomas  阅读(395)  评论(0编辑  收藏  举报