# python元编程

***

## 动态属性

在 Python 中，数据的属性和处理数据的方法统称**属性**（attribute）。其实，方法只是**可调用的**属性。

## 属性描述符(get/set/delete)

> [python 使用特性管理实例属性](https://halfclock.github.io/2019/06/01/python-property/)
>
> [(转)Python描述符（descriptor）解密](https://lingxiankong.github.io/2014-03-28-python-descriptor.html)
>
> [python理解描述符(descriptor)](https://www.lagou.com/lgeduarticle/9290.html)
>
> [python 描述符总结](https://www.jianshu.com/p/fe66aebc02ec)
>
> [Python描述符 (descriptor) 详解](https://www.cnblogs.com/Jimmy1988/p/6808237.html)
>
> [在不可散列的类中使用描述符-python](https://www.coder.work/article/1262710)
>
> [python高级编程——描述符Descriptor详解（下篇）](https://blog.csdn.net/qq_27825451/article/details/84848341?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase\&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase)

### 基本概念

描述符是对多个属性运用相同存取逻辑的一种方式。例如，Django ORM 和 SQL Alchemy 等 ORM 中的字段类型是描述符，把数据库记录中字段里的数据与 Python 对象的属性对应起来。

> **为什么需要描述符**：对property来说，最大的缺点就是它们不能重复使用。虽然property可以让类从外部看起来接口整洁漂亮，但是却做不到内部同样整洁漂亮。
>
> 描述符是property的升级版，允许你为重复的property逻辑编写单独的类来处理。
>
> **基本要求**：描述符是实现了特定协议的类，这个协议包括 `__get__`、`__set__` 和 `__delete__` 方法。`property` 类实现了完整的描述符协议。通常，可以只实现部分协议。其实，我们在真实的代码中见到的大多数描述符只实现了 `__get__` 和 `__set__` 方法，还有很多只实现了其中的一个。
>
> 实现了 `__get__`、`__set__` 或 `__delete__` 方法的类是描述符。
>
> **用法**：描述符的用法是，创建一个描述符类，它的实例对象作为另一个类的属性。
>
> 为了让描述符能够正常工作，它们必须定义在类的层次上。如果你不这么做，那么Python无法自动为你调用`__get__`和`__set__`方法。
>
> **大致流程**：
>
> 1. 定义一个描述符类D，其内包含一个或多个`__get__()`、`__set__()`、`__delete__()`方法
> 2. 将描述符类D的实例对象d赋值给另一个要代理的类中某个属性attr，即attr=D()
> 3. 之后访问、赋值、删除attr属性，将会自动触发描述符类中的`__get__()`、`__set__()`、`__delete__()`方法
>
> **实现**：
>
> 要定义描述符类很简单，只要某个类中包含了下面一个或多个方法，就算是满足描述符协议，就是描述符类，就可以作为属性操作的代理器。
>
> ```python
> class Descriptor():
>     def __get__(self, instance, owner):...
>     def __set__(self, instance, value):...
>     def __delete__(self, instance):...
> ```
>
> 需要注意的是，`__get__`的返回值需要是属性值或抛异常，另外两个方法要返回None。
>
> **类属性描述符对象和实例属性同名时**：描述符针对的是类属性，但是当一个类中，如果类属性是描述符对象，而实例属性由于这个描述符属性同名
>
> ```python
> class Person:
>     character = CharacterDescriptor('乐观的')
>     weight = WeightDescriptor(150)
>     def __init__(self, character,weight):
>         self.character = character
>         self.weight = weight
>        
> p = Person('悲观的', 200)
> print(p.character)  #属性的访问
> print(p.weight)     #
> ```
>
> 从上面的运行结果可以看出，首先是访问了描述符的\_\_set\_\_方法，这是因为在构建对象的时候，相当于为character和weight赋值，然后再调用\_\_get\_\_方法，这是因为访问了类属性character和weight，但是最终打印出来值却并不是类属性的值，这是因为，实例属性实际上是在“描述符类属性”后面访问的，所以覆盖掉了。

### 专有名词

> **描述符类**: **实现了描述符协议的类**，描述符类的一些协议(`__get__`、`__set__`或`__delete__` )。
>
> 实现了`__get__`、`__set__`、`__delete__` 方法的类是描述符，只要实现了其中一个就是。
>
> **托管类**： **将描述符实例作为类属性的类**，比如Fruits 类，他有 weight、price 两个类属性，且都被赋予了描述符类的实例。
>
> **描述符实例**: 描述符类创建出描述符实例，**通常来讲，描述符类的实例会被赋给托管类的类属性。**
>
> **托管实例**: 托管类创建出来的实例
>
> **托管属性**: 托管类中由描述符实例处理的公开属性
>
> **存储属性**: 可以粗略的理解为、托管实例的属性、在上例中使用 `vars(apple)` 得到的结果中 price 和 weight **实例属性**就是存储属性，它们实际**存储着***实例的***属性值**
>
> **非数据描述符**：一个类，如果只定义了\_\_get\_\_() 或者是\_\_delete\_\_()方法，而没有定义\_\_set\_\_()方法，则认为是非数据描述符(即没有定义\_\_set\_\_)
>
> **数据描述符**：一个类，不仅定义了\_\_get\_\_() 方法，还定义\_\_set\_\_(), \_\_delete\_\_() 方法，则认为是数据描述符(即定义了\_\_get\_\_和\_\_set\_\_)

> ps： **托管属性**是类(Fruits)属性、存储属性是实例(apple)的属性。

![img](https://pic.hycbook.com/i/hexo/bk_resources/python/7.python%E5%85%83%E7%BC%96%E7%A8%8B/image-20200625193223844.webp)>

> `Quantity` 实例是描述符，因此有个放大镜，用于获取值（`__get__`），以及一个手抓，用于设置值（`__set__`）。

### 例子

#### 要点

> **定义位置**：为了让描述符能够正常工作，它们必须定义在类的层次上。如果你不这么做，那么Python无法自动为你调用\_\_get\_\_和\_\_set\_\_方法。
>
> **独立实例**：类使用了一个字典来单独保存专属于实例的数据。这个一般来说是没问题的，除非你用到了不可哈希（unhashable）的对象
>
> **不可哈希处理**：list的子类是不可哈希的，因此它们不能为描述符类用做数据字典的key。有一些方法可以规避这个问题，但是都不完美。最好的方法可能就是给你的描述符加标签了。描述符可以安全的在这里存储数据。只是要记住，不要在别的地方也给这个描述符添加标签。这样的代码很脆弱也有很多微妙之处。但这个方法的确很普遍，可以用在不可哈希的所有者类上。
>
> ```python
> class Descriptor(object):
>     def __init__(self, label):
>         self.label = label
>
>     def __get__(self, instance, owner):
>         print('__get__', instance, owner)
>         return instance.__dict__.get(self.label)
>
>     def __set__(self, instance, value):
>         print('__set__')
>         instance.__dict__[self.label] = value
>
> class Foo(list):
>     x = Descriptor('x')
>     y = Descriptor('y')
> ```
>
> **泄漏内存问题**：WeakKeyDictionary可以保证描述符类不会泄漏内存：WeakKeyDictionary的特殊之处在于：如果运行期系统发现这种字典所持有的引用，是整个程序里面指向Exam实例的最后一份引用，那么，系统就会自动将该实例从字典的键中移除。

#### 模拟ORM

代码-描述符类

```python
#!/usr/bin/env Python
# -- coding: utf-8 --
import weakref
import numbers

class Field:
    pass

class IntField(Field):
    # 数据描述符
    def __init__(self, db_column, min_value=None, max_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")
        self._value = weakref.WeakKeyDictionary()

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self._value.get(instance, 0)

    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[instance] = 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
        self._value = weakref.WeakKeyDictionary()

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self._value.get(instance, '0')

    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[instance] = value
```

代码-元类

```python
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(BaseModel, self).__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))
        print(sql)
```

代码-测试

```
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__':
    first_user = User(name="bobby", age=28)
    first_user.name = "bobby"
    first_user.age = 28
    second_user = User(name="bobby", age=23)
    print(first_user.name is second_user.name)
    second_user.name = 'okay'
    print(first_user.name is second_user.name)

    second_user.name = 'sec_boddy'
    print(first_user.name)
    print(second_user.name)
    print(first_user.name is second_user.name)
```

输出

```cmd
True
False
bobby
sec_boddy
False
```

#### 成绩管理

代码

```python
#!/usr/bin/env Python
# -- coding: utf-8 --
import weakref

class Grade:
    def __init__(self):
        self._values = weakref.WeakKeyDictionary()

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self._values.get(instance, 0)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        self._values[instance] = value

class Exam:
    # https://lingxiankong.github.io/2014-03-28-python-descriptor.html
    # 为了让描述符能够正常工作，它们必须定义在类的层次上。如果你不这么做，那么Python无法自动为你调用__get__和__set__方法。
    # 确保实例的数据只属于实例本身
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()
    
if __name__ == '__main__':
    first_exam = Exam()
    first_exam.writing_grade = 82
    second_exam = Exam()
    second_exam.writing_grade = 75
    logger.info(f'first {first_exam.writing_grade}')
    logger.info(f'second {second_exam.writing_grade}')    
```

输出

```cmd
2020-06-25 20:18:23 lazy_db INFO:  first 82
2020-06-25 20:18:23 lazy_db INFO:  second 75
```

#### 成绩管理2.0

代码

```python
class Grade:
    def __init__(self, name):
        self.name = name
        self.internal_name = '_' + self.name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.internal_name, '')

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        setattr(instance, self.internal_name, value)

class Exam:
    # https://lingxiankong.github.io/2014-03-28-python-descriptor.html
    # 为了让描述符能够正常工作，它们必须定义在类的层次上。如果你不这么做，那么Python无法自动为你调用__get__和__set__方法。
    # 确保实例的数据只属于实例本身
    math_grade = Grade('math_grade')
    writing_grade = Grade('writing_grade')
    science_grade = Grade('science_grade')

if __name__ == '__main__':
    first_exam = Exam()
    first_exam.writing_grade = 82
    second_exam = Exam()
    second_exam.writing_grade = 75
    logger.info(f'first {first_exam.writing_grade}')
    logger.info(f'second {second_exam.writing_grade}')
```

输出

```cmd
2020-06-25 20:18:23 lazy_db INFO:  first 82
2020-06-25 20:18:23 lazy_db INFO:  second 75
```

#### 成绩管理2.1

代码

```python
class Grade:
    def __init__(self):
        self.name = None
        self.internal_name = None

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.internal_name, '')

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError('Grade must be between 0 and 100')
        setattr(instance, self.internal_name, value)

class Meta(type):
    def __new__(cls, name, bases, class_dict):
        for key, value in class_dict.items():
            if isinstance(value, Grade):
                value.name = key
                value.internal_name = '_' + key
        cls = type.__new__(cls, name, bases, class_dict)
        return cls

class Exam(object,metaclass=Meta):
    # https://lingxiankong.github.io/2014-03-28-python-descriptor.html
    # 为了让描述符能够正常工作，它们必须定义在类的层次上。如果你不这么做，那么Python无法自动为你调用__get__和__set__方法。
    # 确保实例的数据只属于实例本身
    math_grade = Grade()
    writing_grade = Grade()
    science_grade = Grade()

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

if __name__ == '__main__':
    first_exam = Exam(85)
    first_exam.writing_grade = 82
    second_exam = Exam(13)
    second_exam.writing_grade = 75
    logger.info(f'first {first_exam.writing_grade}')
    logger.info(f'second {second_exam.writing_grade}')
```

### 进阶

#### 添加回调

> 描述符仅仅是类，也许你想要为它们增加一些方法。举个例子，描述符是一个用来回调property的很好的手段。比如我们想要一个类的某个部分的状态发生变化时就立刻通知我们。下面的大部分代码是用来做这个的：

```python
class CallbackProperty(object):
    """A property that will alert observers when upon updates"""
    def __init__(self, default=None):
        self.data = WeakKeyDictionary()
        self.default = default
        self.callbacks = WeakKeyDictionary()

    def __get__(self, instance, owner):
        return self.data.get(instance, self.default)

    def __set__(self, instance, value):        
        for callback in self.callbacks.get(instance, []):
            # alert callback function of new value
            callback(value)
        self.data[instance] = value

    def add_callback(self, instance, callback):
        """Add a new function to call everytime the descriptor updates"""
        #but how do we get here?!?!
        if instance not in self.callbacks:
            self.callbacks[instance] = []
        self.callbacks[instance].append(callback)

class BankAccount(object):
    balance = CallbackProperty(0)

def low_balance_warning(value):
    if value < 100:
        print "You are poor"

ba = BankAccount()

# will not work -- try it
#ba.balance.add_callback(ba, low_balance_warning)
```

> 这是一个很有吸引力的模式——我们可以自定义回调函数用来响应一个类中的状态变化，而且完全无需修改这个类的代码。这样做可真是替人分忧解难呀。现在，我们所要做的就是调用ba.balance.add\_callback(ba, low\_balance\_warning)，以使得每次balance变化时low\_balance\_warning都会被调用。
>
> 但是我们是如何做到的呢？当我们试图访问它们时，描述符总是会调用`__get__`。就好像add\_callback方法是无法触及的一样！其实关键在于利用了一种特殊的情况，即，当从类的层次访问时，`__get__`方法的第一个参数是None。

```python
class CallbackProperty(object):
    """A property that will alert observers when upon updates"""
    def __init__(self, default=None):
        self.data = WeakKeyDictionary()
        self.default = default
        self.callbacks = WeakKeyDictionary()

    def __get__(self, instance, owner):
        if instance is None:
            return self        
        return self.data.get(instance, self.default)

    def __set__(self, instance, value):
        for callback in self.callbacks.get(instance, []):
            # alert callback function of new value
            callback(value)
        self.data[instance] = value

    def add_callback(self, instance, callback):
        """Add a new function to call everytime the descriptor within instance updates"""
        if instance not in self.callbacks:
            self.callbacks[instance] = []
        self.callbacks[instance].append(callback)

class BankAccount(object):
    balance = CallbackProperty(0)

def low_balance_warning(value):
    if value < 100:
        print "You are now poor"

ba = BankAccount()
BankAccount.balance.add_callback(ba, low_balance_warning)

ba.balance = 5000
print "Balance is %s" % ba.balance
ba.balance = 99
print "Balance is %s" % ba.balance
Balance is 5000
You are now poor
Balance is 99
```

#### 实现底层 @classmethod

```python

class NewDefine_classmethod:
    """
    使用“描述符”和“装饰器”结合起来，模拟@classmethod
    """
    def __init__(self, function):
        self.function = function
 
    def __get__(self, instance, owner):
        #对传进函数进行加工,最后返回该函数
        def wrapper(*args, **kwargs):   #使用不定参数是为了匹配需要修饰的函数参数
            print("给函数添加额外功能")
            self.function(owner, *args, **kwargs)
        return wrapper
 
class Person:
    name='我有姓名'
    def __init__(self):
        pass
 
    @NewDefine_classmethod
    def study_1(cls):
        print(f'我的名字是：{cls.name},我会搞学习！')
 
    @NewDefine_classmethod
    def study_2(cls,score):
        print(f'我的名字是：{cls.name},我会搞学习！,而且这次考试考了 {score} 分')
    
print(Person.study_1())
print(Person.study_2(99))
```

可以分这样几步分析：

> 第一步：@NewDefine\_classmethod本质上是一个“类装饰器”，从它的定义可知，它的定义为
>
> class NewDefine\_classmethod(function).我们发现，python系统定义的@classmethod其实它的定义也是一样的，如下，
>
> class classmethod(function) .怎么样？它们二者的定义是不是一样？
>
> 第二步：NewDefine\_classmethod本质上又是一个描述符，因为在它的内部实现了\_\_get\_\_协议，由此可见，NewDefine\_classmethod是“集装饰器-描述符”于一身的。
>
> 第三步：运行过程分析，因为study\_1=NewDefine\_classmethod（study\_1）,所以，study\_1本质上是一个NewDefine\_classmethod的对象，又因为NewDefine\_classmethod本质上是实现了描述符的，所以，study\_1本质上是一个定义在类中的描述符属性。
>
> 第四步：因为study\_1本质上是一个定义在类中的描述符属性。所以在执行Person.study\_1的时候，相当于是访问类的描述符属性，所以会进入到描述符的\_\_get\_\_方法。
>
> 现在是不是觉得原来python描述符还有这样神奇的使用呢？
>
> 注意：如果修饰的函数本身是具有返回值的，在\_\_get\_\_里面所定义的wrapper里面一定要返回，即return self.function(owner, \*args, \*\*kwargs)。
>
> 还有一个地方需要注意的是，因为这是自定义的底层实现，所以一些集成IDE可能会显示有语法错误，但是这没有关系，这正是python灵活多变的地方，运行并不会出现错误。

#### 实现底层 @staticmethod

staticmethod方法与classmethod方法的区别在于classmethod方法在使用需要传进一个类的引用作为参数。而staticmethod则不用。

```python
class NewDefine_staticmethod:
    """
    使用“描述符”和“装饰器”结合起来，模拟@classmethod
    """
    def __init__(self, function):
        self.function = function
 
    def __get__(self, instance, owner):
        #对传进函数进行加工,最后返回该函数
        def wrapper(*args, **kwargs):   #使用不定参数是为了匹配需要修饰的函数参数
            print("给函数添加额外功能")
            self.function(*args, **kwargs)
        return wrapper
 
class Person:
    name='我有姓名'
    def __init__(self):
        pass
 
    @NewDefine_staticmethod
    def study_1(math,english):
        print(f'我数学考了 {math} 分,英语考了 {english} 分,我会搞学习！')
 
    @NewDefine_staticmethod
    def study_2(history,science):
        print(f'我历史考了 {history} 分,科学考了 {science} 分,我会搞学习！')
    
print(Person.study_1(99,98))
print(Person.study_2(88,89))
```

类方法classmethod必须第一个参数是cls，这个实际上就是判断所属的那个类，因此在\_\_get\_\_里面的function在调用的时候，第一个参数需要传递为owner，因为所属的“类cls等价于Person等价于owner”，但是因为静态方法不需要任何参数cls或者是self都不需要，因此在\_\_get\_\_实现的时候不能再传递owner参数，否则会显示参数错误。

#### 实现底层 @property

```python
class NewDefine_property:
    """
    使用“描述符”和“装饰器”结合起来，模拟@classmethod
    """
    def __init__(self, function):
        self.function = function
 
    def __get__(self, instance, owner):
        print("给函数添加额外功能")
        return self.function(instance)
 
class Person:
    name='我有姓名'
    def __init__(self):
        self.__study=100
 
    @NewDefine_property
    def study_1(self):  #使用property装饰的函数一般不要用“参数”，因为它的主要功能是对属性的封装
        return self.__study
 
p=Person()
print(p.study_1)
```

基本思想和前面分析的还是一样的，但是有几个地方有所区别，需要注意：

> 第一：@property的目的是封装一个方法，是这个方法可以被当做属性访问
>
> 第二：调用的方式与前面有所不同，\_\_get\_\_里面不能再定义wrapper了，否则不会调用wrapper。得不到想要的结果，为什么呢？
>
> 因为调用的方式不一样，根据前面的分析，study\_1的本质是描述符属性，但是前面的调用均是使用的
>
> Person.study\_1()或者是p.study\_1()的形式，还是当成方法去使用的。但是此处不一样了，直接就是当成属性去使用，
>
> p.study\_1 ，不再是方法调用，因此wrapper函数得不到调用。所以\_\_get\_\_方法得到了进一步简化。

## 按需生成属性

> [Python 魔法方法（三） \_\_getattr\_\_，\_\_setattr\_\_， \_\_delattr\_\_](https://blog.csdn.net/yusuiyu/article/details/87945149)
>
> [Python高级用法之动态属性](https://blog.csdn.net/biheyu828/article/details/82794156)

> 使用\_\_getattr\_\_、\_\_setattr\_\_和\_\_getattribute\_\_来动态生成属性
>
> Python 语言提供了一些挂钩，使得开发者很容易就能编写出通用的代码，以便将多个系统黏合起来。例如，我们要把数据库的行（row）表示为 Python 对象。由于数据库有自己的一套结构（schema），也称架构、模式、纲要、概要、大纲，所以在操作与行相对应的对象时，我们必须知道这个数据库的结构。然而，把 Python 对象与数据库相连接的这些代码，却不需要知道行的结构，所以，这部分代码应该写得通用一些。
>
> 那么，如何实现这种通用的代码呢？普通的实例属性、@property 方法和描述符，都不能完成此功能，因为它们都必须预先定义好，而像这样的动态行为，则可以通过 Python 的\_\_getattr\_\_特殊方法来做。如果某个类定义了\_\_getattr\_\_，同时系统在该类对象的实例字典中又找不到待查询的属性，那么，系统就会调用这个方法。

### 实例属性查找

> [python属性查找（attribute lookup）](https://www.shuzhiduo.com/A/gVdn4oWlJW/)
>
> 首先需要明白的是实例属性查找的过程：

如果obj是某个类的实例，那么obj.name（以及等价的getattr(obj,'name')）首先调用\_\_getattribute\_\_。如果类定义了\_\_getattr\_\_方法，那么在\_\_getattribute\_\_抛出 AttributeError 的时候就会调用到\_\_getattr\_\_，而对于描述符(\_\_get\_\_）的调用，则是发生在\_\_getattribute\_\_内部的。官网文档是这么描述的

> The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to [`__getattr__()`](https://docs.python.org/2/reference/datamodel.html#object.__getattr__) if provided.

obj = Clz(), 那么obj.attr 顺序如下：

> （1）如果“attr”是出现在Clz或其基类的\_\_dict\_\_中， 且attr是data descriptor， 那么调用其\_\_get\_\_方法, 否则
>
> （2）如果“attr”出现在obj的\_\_dict\_\_中， 那么直接返回 obj.\_\_dict\_\_\['attr']， 否则
>
> （3）如果“attr”出现在Clz或其基类的\_\_dict\_\_中
>
> （3.1）如果attr是non-data descriptor，那么调用其\_\_get\_\_方法， 否则
>
> （3.2）返回 \_\_dict\_\_\['attr']
>
> （4）如果Clz有\_\_getattr\_\_方法，调用\_\_getattr\_\_方法，否则
>
> （5）抛出AttributeError

> 程序每次访问对象的属性时，Python 系统都会调用这个特殊方法，即使属性字典里面已经有了该属性，也依然会触发 \_\_getattribute\_\_ 方法。这样就可以在程序每次访问属性时，检查全局事务状态。

> 按照 Python 处理缺失属性的标准流程，如果程序动态地访问了一个不应该有的属性，那么可以在 \_\_getattr\_\_ 和 \_\_getattribute\_\_ 里面抛出 AttributeError 异常。

> 实现通用的功能时，我们经常会在 Python 代码里使用内置的 hasattr 函数来判断对象是否已经拥有了相关的属性，并用内置的 \_\_getattr\_\_ 函数来获取属性值。这些函数会先在实例字典中搜索待查询的属性，然后再调用 \_\_getattr\_\_。

### 四个魔法函数

> **访问时机**
>
> 如果某个类定义了\_\_getattr\_\_ ，同时系统在该类对象的实例字典中又找不到待查询的属性，那么，系统就会调用这个方法。
>
> 程序每次访问对象的属性时，Python 系统都会调用这个特殊方法，即使属性字典里面已经有了该属性，也依然会触发\_\_getattribute\_\_方法。这样就可以在程序每次访问属性
>
> 按照Python处理缺失属性的标准流程，如果程序动态地访问了一个不应该有的属性，那么可以在\_\_getattr\_\_ 和\_\_getattribute\_\_ 里面抛出AttributeError异常。
>
> 只要对实例的属性赋值，无论是直接赋值，还是通过内置的setattr函数赋值，都会触发\_\_setattr\_\_方法 。

#### \_\_getattr\_\_

当我们访问一个不存在的属性的时候，会抛出异常，提示我们不存在这个属性。而这个异常就是\_\_getattr\_\_方法抛出的，其原因在于他是访问一个不存在的属性的最后落脚点，作为异常抛出的地方提示出错再适合不过了。

看例子，我们找一个存在的属性和不存在的属性。

```python
class A(object):
    def __init__(self, value):
        self.value = value
 
    def __getattr__(self, item):
        print "into __getattr__"
        return  "can not find"
 
a = A(10)
print a.value
# 10
print a.name
# into __getattr__
# can not find
```

可以看出，访问存在的属性时，会正常返回值，若该值不存在，则会进入最后的兜底函数\_\_getattr\_\_。

#### \_\_setattr\_\_

在对一个属性设置值的时候，会调用到这个函数，每个设置值的方式都会进入这个方法。

```PYTHON
class A(object):
    def __init__(self, value):
        print "into __init__"
        self.value = value
 
    def __setattr__(self, name, value):
        print "into __setattr__"
        if value == 10:
            print "from __init__"
        object.__setattr__(self, name, value)
 
 
a = A(10)
# into __init__
# into __setattr__
# from __init__
print a.value
# 10
a.value = 100
# into __setattr__
print a.value
# 100
```

在实例化的时候，会进行初始化，在\_\_init\_\_里，对value的属性值进行了设置，这时候会调用\_\_setattr\_\_方法。

在对a.value重新设置值100的时候，会再次进入\_\_setattr\_\_方法。

需要注意的地方是，在重写\_\_setattr\_\_方法的时候千万不要重复调用造成死循环。

```python
class A(object):
    def __init__(self, value):
        self.value = value
 
    def __setattr__(self, name, value):
        self.name = value
```

这是个死循环。当我们实例化这个类的时候，会进入\_\_init\_\_，然后对value进行设置值，设置值会进入\_\_setattr\_\_方法，而\_\_setattr\_\_方法里面又有一个self.name=value设置值的操作，会再次调用自身\_\_setattr\_\_，造成死循环。

除了上面调用object类的\_\_setattr\_\_避开死循环，还可以如下重写\_\_setattr\_\_避开循环。

```PYTHON
class A(object):
    def __init__(self, value):
        self.value = value
 
    def __setattr__(self, name, value):
        self.__dict__[name] = value
 
 
a = A(10)
print a.value
# 10
```

#### \_\_delattr\_\_

\_\_delattr\_\_是个删除属性的方法

```PYTHON
class A(object):
    def __init__(self, value):
        self.value = value
 
    def __delattr__(self, item):
        object.__delattr__(self, item)
 
    def __getattr__(self, item):
        return "when can not find attribute into __getattr__"
 
 
 
a = A(10)
print a.value
# 10
del a.value
print a.value
# when can not find attribute into __getattr__
```

\_\_delattr\_\_也要避免死循环的问题，就如\_\_setattr\_\_一样，在重写\_\_delattr\_\_，避免重复调用。

#### \_\_getattribute\_\_

> 使用\_\_getattribute\_\_对属性的访问做额外处理
>
> 假设我们需要在数据库中实现事物（transaction）处理，即每次在访问属性时，需要额外调用特殊方法检查数据库中对应的行是否有效，以及相关的事务是否依然开放。此时使用\_\_getattr\_\_无法实现这种功能，因为第二次访问属性时，Python会直接返回上首次调用时存储在\_\_dict\_\_中的属性值，而不会再次调用\_\_getattr\_\_插寻属性的状态。此种情况下我们需要使用\_\_getattribute\_\_，该方法在用户每次访问属性是都会被调用。

```python
class LazyDB(object):
    def __init__(self):
        self.exist = 1
 
    def __getattribute__(self, item):
        print('__getattribute__ (%s) called' % item)
        try:
            return super().__getattribute__(item)
        except AttributeError:
            value = ' '.join(['default value: ', item])
            setattr(self, item, value)
            return value
 
data = LazyDB()
print(data.foo) ##每次访问类属性时都会被调用，此处是第1次调用
print(data.foo) ##每次访问类属性时都会被调用，此处是第2次调用
print(data.__dict__)  ##每次访问类属性时都会被调用，此处是第3次待用
 
###输出如下：
__getattribute__ (foo) called
default value:  foo
__getattribute__ (foo) called
default value:  foo
__getattribute__ (__dict__) called
{'exist': 1, 'foo': 'default value:  foo'}
```

### 要点

> * \_\_getattr\_\_ 和 \_\_setatr\_\_，我们可以用惰性的方式来加载并保存对象的属性。
> * 要理解 \_\_getattr\_\_ 与 \_\_getattribute\_\_ 的区别：前者只会在待访问的属性缺失时触发，而后者则会在每次访问属性时触发。
> * 如果要在 \_\_getattribute\_\_ 和 \_\_setattr\_\_ 方法中访问实例属性，那么应该直接通过super()（也就是object类的同名方法）来做，以避免无限递归。
>
> 总结：
>
> （1）对于类装饰器属性，只要出现属性访问（不管是通过对象访问还是类名访问），都会优先调用装饰器的\_\_get\_\_方法;
>
> （2）对于类装饰器属性，若出现属性修改（不管是通过对象访问还是类名访问），都会优先调用装饰器的\_\_set\_\_方法;
>
> （3）对于类装饰器属性，若出现属性删除（不管是通过对象访问还是类名访问），都会优先调用装饰器的\_\_delete\_\_方法

### 例子

## 元类

> [第33/34条用元类核实和登记子类,3334,验证,注册](https://www.pythonf.cn/read/35687)

### 基本概念

> 类元编程是指在运行时创建或定制类的技艺。在 Python 中，类是一等对象，因此任何时候都可以使用函数新建类，而无需使用 `class` 关键字。类装饰器也是函数，不过能够审查、修改，甚至把被装饰的类替换成其他类。最后，元类是类元编程最高级的工具：使用元类可以创建具有某种特质的全新类种，例如我们见过的抽象基类。

#### 元类的定义

> Python定义元类时，需要从 `type` 类中继承，然后重写 `__new__` 方法，便可以实现意想不到的功能。
>
> ```python
> class Meta(type):
>     def __new__(meta,name,bases,class_dict):
>         #...各种逻辑实现1
>         cls = type.__new__(meta,name,bases,class_dict)
>         print('当前类名',name)
>         print('父类',bases)
>         print('全部类属性',class_dict)
>         #...各种逻辑实现2
>         return cls
>
> class MyClass(object,metaclass=Meta):
>     stuff = 33
>     def foo(self):
>         pass
> ```
>
> ```cmd
> 当前类名 MyClass
> 父类 (<class 'object'>,)
> 全部类属性 {'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 33, 'foo': <function MyClass.foo at 0x0000019E028315E8>}
> ```
>
> 元类可以获知那个类的名称、其所继承的父类，以及定义在class语句体中的全部类属性

#### 元类的本质

在Python当中万物皆对象，我们用 `class` 关键字定义的类本身也是一个对象， **负责产生该对象的类称之为元类** ，元类可以简称为类的类， **元类的主要目的是为了控制类的创建行为** 。

* `type` 是Python的一个内建元类，用来直接控制生成类，在Python中任何 `class` 定义的类其实都是 `type` 类实例化的结果。
* 只有继承了 `type` 类才能称之为一个元类，否则就是一个普通的自定义类，自定义元类可以控制类的产生过程，类的产生过程其实就是元类的调用过程。

#### 小结

元类的各种操作可以实现类的验证和注册逻辑，均可以在元类的 `__new__` 方法中实现，主要原因是当子类对象构建时，会先调用元类的 `__new__` 方法，产生一个空对象，然后再调用子类的 `__init__` 方法给对象属性进行赋值。

### 验证子类

> 元类是python比较高级的用法，简而言之，元类就是创建类的类。
>
> 而type就是一个元类，是用来创建类对象的类。
>
> 因此，要定义元类就要使其继承type类。
>
> 通常情况下，开发者在使用OOP的方式编程时，往往会用到\_\_init\_\_方法，即构造函数。
>
> 该方法会在类初始化时运行。但是我们可以将验证的时机提前，以至于提前到类创建之时，因此就会用到\_\_new\_\_方法。

```python
class Base(type):
    def __new__(cls, name, param, dicts):
        print(cls)
        print(name)
        print(param)
        print(dicts)
        return super().__new__(cls, name, param, dicts)
 
class Meta(metaclass=Base):
    name = 'yang'
 
    def person(self):
        pass
Meta()
 
<class '__main__.Base'>
Meta
()
{'__module__': '__main__', '__qualname__': 'Meta', 'name': 'yang', 'person': <function Meta.person at 0x10c6492f0>}
```

**元类中所编写的验证逻辑，针对的是该基类的子类，而不是基类本身**。

> `__new__()`方法接收到的参数依次是：
>
> 1. 当前准备创建的类的对象；
> 2. 类的名字；
> 3. 类继承的父类集合；
> 4. 类的方法集合。

> **案例1** ：编写一个多边形类，当边数小于 `3` 时，其类报错，实现其验证逻辑。

```python
class ValidatePolygon(type):
    ## __new__当中放入验证逻辑
    def __new__(meta,name,bases,class_dict):
        if bases!=(object,): ##针对子类而不对基类
            if class_dict['sides'] < 3:
                raise ValueError('Polygons need 3+ sides')
        return type.__new__(meta,name,bases,class_dict)
        
class Polygons(object,metaclass=ValidatePolygon):
    sides = None
    
    @classmethod
    def interior_angles(cls):
        return (cls.sides - 2) * 180
        
class Triangle(Polygons):
    sides = 3
    
### 类设计报错。
class Line(Polygons):
    sides = 1
```

### 注册子类

元类还有一个用途，就是在程序中 `自动注册类型` 。开发者每次从基类中继承子类时，基类的元类都可以自动运行注册代码。

> **案例2** ：实现对象的序列化与反序列化

```python
###建立类名与该类对象的映射关系，维护registry字典。
registry = {}
def register_class(target_class):
    registry[target_class.__name__] = target_class

def deserialize(data):
    params = json.loads(data)
    name = params['class']
    target_class = registry[name]
    return target_class

class Meta(type):
    def __new__(meta,name,bases,class_dict):
        cls = type.__new__(meta,name,bases,class_dict)
        register_class(cls) ##注册子类
        return cls

class BetterSerializable(object):
    def __init__(self,*args):
        self.args = args
    
    def serialize(self):
        return json.dumps({'class':self.__class__.__name__,
				            'args':self.args,})
    def __repr__(self):
        pass

class RegisterSerializabel(BetterSerializable,metaclass=Meta):
    pass
```

通过元类来实现类的注册，可以确保所有的子类都不会遗漏，从而避免后续的错误。

### 获取\_\_init\_\_的默认参数

> 获取\_\_init\_\_的默认参数，并在classmethod方法中为没有给定的属性赋默认值，提升代码的健壮性
>
> 元类定义：
>
> ```python
> #!/usr/bin/env Python
> # -- coding: utf-8 --
>
> """
> @version: v0.1
> @author: narutohyc
> @file: meta_interface.py
> @Description: 
> @time: 2020/6/15 20:29
> """
> import collections
> from abc import (ABC,
>                  abstractmethod,
>                  ABCMeta)
> import inspect
>
> class DicMetaClass(ABCMeta):
>     def __new__(cls, name, bases, attrs, **kwargs):
>         if name == 'DicMeta':
>             return super().__new__(cls, name, bases, attrs, **kwargs)
>         # 获取__init__函数的 默认值
>         argspec = inspect.getfullargspec(attrs["__init__"])
>         init_defaults = dict(zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
>         cls.__init_defaults = init_defaults
>         attrs['__init_defaults__'] = init_defaults
>         return super().__new__(cls, name, bases, attrs, **kwargs)
> ```
>
> 抽象父类：
>
> ```python
> #!/usr/bin/env Python
> # -- coding: utf-8 --
>
> """
> @version: v0.1
> @author: narutohyc
> @file: meta_interface.py
> @Description: 
> @time: 2020/6/15 20:29
> """
>
> from abc import (ABC,
>                  abstractmethod,
>                  ABCMeta)
>
> class DicMeta(ABC, metaclass=DicMetaClass):
>     def __init__(self):
>         pass
>
>     @abstractmethod
>     def to_dict(self):
>         '''
>         返回字典
>         '''
>         pass
>
>     @classmethod
>     def load_from_mapping(cls, mapping_datas):
>         '''
>         用字典来构建实例对象
>         '''
>         assert isinstance(mapping_datas, collections.abc.Mapping)
>         obj = cls.__new__(cls)
>         [setattr(obj, k, v) for k, v in mapping_datas.items()]
>         return obj
> ```
>
> 子类实现：
>
> ```python
> #!/usr/bin/env Python
> # -- coding: utf-8 --
>
> """
> @version: v0.1
> @author: narutohyc
> @file: text_meta.py
> @Description: 
> @time: 2020/5/22 14:55
> """
> from augmentation.meta_class.meta_interface import DicMeta
> from utils.utils_func import gen_md5, str2bool
> import re
>
> class TaskMeta(DicMeta):
>     '''
>     数据包装类的bean结构
>     '''
>     def __init__(self, text, doc_id, sentence_id, reg_lst,
>                  has_reg=True,
>                  flag=None,
>                  dataset='train',
>                  text_source="primitive"):
>         super(TaskMeta, self).__init__()
>         self.text = text
>         self.doc_id = doc_id
>         self.sentence_id = sentence_id
>         if reg_lst and isinstance(reg_lst[0], list):
>             reg_lst = ['%s %s %s' % (tag, start_idx, value) for tag, start_idx, value in reg_lst]
>         self.reg_lst = sorted(reg_lst, key=lambda reg: int(re.sub(' +', ' ', reg).split(" ", 2)[1])) if reg_lst else []
>         self.flag = list(set(i.split(' ', 2)[0] for i in self.reg_lst)) if flag is None else flag
>         self.has_reg = str2bool(has_reg)
>         self.dataset = dataset
>         self.text_source = text_source
>         self._id = gen_md5(self.text)
>
>     @classmethod
>     def load_from_mapping(cls, mapping_datas):
>         '''
>         用字典来构建 TaskMeta实例
>         '''
>         obj = super(TaskMeta, cls).load_from_mapping(mapping_datas)
>         obj._id = gen_md5(obj.text)
>         [setattr(obj, k, v) for k, v in obj.__init_defaults__.items() if not hasattr(obj, k)]
>         if obj.flag is None:
>             obj.flag = list(set(i.split(' ', 2)[0] for i in obj.reg_lst))
>         obj.has_reg = str2bool(obj.has_reg)
>         return obj
>     
>     @property
>     def to_dict(self):
>         '''
>         当该类没有其他多余属性时  可以直接返回self.__dict__的副本
>         '''
>         return {"text": self.text,
>                 "doc_id": self.doc_id,
>                 "sentence_id": self.sentence_id,
>                 "reg_lst": self.reg_lst,
>                 "flag": list(self.flag),
>                 "has_reg": self.has_reg,
>                 "dataset": self.dataset,
>                 "text_source": self.text_source,
>                 "_id": self._id}    
> ```
>
> 测试类：
>
> ```python
> task_meta_0 = TaskMeta.load_from_mapping({'text': '斯坦福大学开发的基于条件随机场的命名实体识别系统，该系统参数是基于CoNLL、MUC-6、MUC-7和ACE命名实体语料训练出来的。', 
>                                           'doc_id': 'id1', 'sentence_id': 'id1', 
>                                           'reg_lst': ['学校 0 斯坦福大学', '标注 33 CoNLL', '标注 39 MUC-6', '标注 45 MUC-7', '标注 51 ACE']})
> task_meta_1 = TaskMeta.load_from_mapping({'text': '斯坦福大学开发的基于条件随机场的命名实体识别系统，该系统参数是基于CoNLL、MUC-6、MUC-7和ACE命名实体语料训练出来的。', 
>                                           'doc_id': 'id1', 'sentence_id': 'id1', 
>                                           'reg_lst': ['学校 0 斯坦福大学', '标注 33 CoNLL', '标注 39 MUC-6', '标注 45 MUC-7', '标注 51 ACE'], 
>                                           'flag': ['学校', '标注'], 'has_reg': True, 
>                                           'dataset': 'train', 'text_source': 'primitive', '_id': '3b895befc659345be8686bd7de4d7693'})
> task_meta_0.to_dict == task_meta_1.to_dict
> Out[33]: True
> ```
>
> 可以看出，task\_meta\_0和task\_meta\_1两者的 值是完全相同的，这里就可以做到共享\_\_init\_\_默认参数的效果

### 注解类的属性

> 元类还有一个更有用处的功能，那就是可以在某个类刚定义好但是尚未使用的时候，提前修改或注解该类的属性。这种写法通常会与描述符(descriptor) 搭配起来(参见本书第31条)，令这些属性可以更加详细地了解自己在外围类中的使用方式。 例如，要定义新的类，用来表示客户数据库里的某- -行。同时，我们还希望在该类的相关属性与数据库表的每一列之间， 建立对应关系。于是，用下面这个描述符类，把属性与列名联系起来。
>
> ```python
> class Field:
>        def __init__(self, name):
>            self.name = name
>            self.internal_name = '_' + self.name
>
>        def __get__(self, instance, owner):
>            if instance is None:
>                return self
>            return getattr(instance, self.internal_name, '')
>
>        def __set__(self, instance, value):
>            setattr(instance, self.internal_name, value)
> ```

> 由于列的名称已经保存到了Field描述符中，所以我们可以通过内置的setattr和getattr函数，把每个实例的所有状态都作为protected字段，存放在该实例的字典里面。 在本书前面的例子中，为了避免内存泄漏，我们曾经用weakref字典来构建描述符，而刚才的那段代码，目前看来，似乎要比weakref方案便捷得多。 接下来定义表示数据行的Customer类，定义该类的时候，我们要为每个类属性指定对应的列名。
>
> ```python
> class Customer:
>        first_name = Field('first_name')
>        last_name = Field('last_name')
>        prefix = Field('prefix')
>        suffix = Field('suffix')
> ```
>
> 问题在于，上面这种写法显得有些重复。在Customer类的class语句体中，我们既然要将构建好的Field对象赋给Customer.first\_ name, 那为什么还要把这个字段名(本例中是'first\_ name') 再传给Field的构造器呢? 之所以还要把字段名传给Field构造器，是因为定义Customer类的时候，Python 会以从右向左的顺序解读赋值语句，这与从左至右的阅读顺序恰好相反。首先，Python 会以Field(first\_ name') 的形式来调用Field 构造器，然后，它把调用构造器所得的返回值，赋给Customer.field\_ name。 从这个顺序来看，Field 对象没有办法提前知道自己会赋给Customer类里的哪一个属性。 为了消除这种重复代码，我们现在用元类来改写它。使用元类，就相当于直接在class语句上面放置挂钩，只要class语句体处理完毕，这个挂钩就会立刻触发。于是，我们可以借助元类，为Field描述符自动设置其Field.name和Field.internal\_ name, 而不用再像刚才那样，把列的名称手工传给Field 构造器。
>
> ```python
> class Meta(type):
>        def __new__(meta, name, bases, class_dict):
>            for key, value in class_dict.items():
>                if isinstance(value, Field):
>                    value.name = key
>                    value.internal_name = '_' + key
>            cls = type.__new__(meta, name, bases, class_dict)
>            return cls
> ```

> 下面定义一一个基类，该基类使用刚才定义好的Meta作为其元类。凡是代表数据库里面某一行的类，都应该从这个基类中继承，以确保它们能够利用元类所提供的功能:
>
> ```python
> class DatabaseRow(object, metaclass=Meta):
> 	pass
> ```
>
> 采用元类来实现这套方案时，Field 描述符类基本上是无需修改的。唯一 要调整的地方就在于:现在不需要再给构造器传人参数了，因为刚才编写的Meta.\_\_new\_\_ 方法会自动把相关的属性设置好。
>
> ```python
> class Field:
>        def __init__(self):
>            self.name = None
>            self.internal_name = None
> ```
>
> 有了元类、新的DatabaseRow基类以及新的Field描述符之后，我们在为数据行定义DatabaseRow子类时，就不用再像原来那样，编写重复的代码了。
>
> ```python
> class BetterCustomer(DatabaseRow):
>        first_name = Field()
>        last_name = Field()
>        prefix = Field()
>        suffix = Field()
> ```

#### ORM例子

> ORM 是 python编程语言后端web框架 Django的核心思想，“Object Relational Mapping”，即对象-关系映射，简称ORM。 一个句话理解就是：创建一个实例对象，用创建它的类名当做数据表名，用创建它的类属性对应数据表的字段，当对这个实例对象操作时，能够对应MySQL语句

代码

```python
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, tuple):
                print('Found mapping :%s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings
        attrs['__table__'] = name
        return type.__new__(cls, name, bases, attrs)


class Model(object, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        for name, value in kwargs.items():
            setattr(self, name, value)

    def save(self):
        fields, args = [], []
        for k, v in self.__mappings__.items():
            fields.append(v[0])
            args.append(getattr(self, k, None))
        args_temp = list()
        for temp in args:
            if isinstance(temp, int):
                args_temp.append(str(temp))
            elif isinstance(temp, str):
                args_temp.append("""'%s'""" % temp)

        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args_temp))
        print(sql)

class User(Model):
    uid = ('uid', 'int unsigned')
    name = ('username', 'varchar(30)')
    email = ('email', 'varchar(30)')
    password = ('password', 'varchar(30)')

user = User(uid=1234, name='naruto', email='1832044042@qq.mail', password='hycpass')
user.save()
```

输出

```python
Found mapping :uid ==> ('uid', 'int unsigned')
Found mapping :name ==> ('username', 'varchar(30)')
Found mapping :email ==> ('email', 'varchar(30)')
Found mapping :password ==> ('password', 'varchar(30)')
insert into User (uid,username,email,password) values (1234,'naruto','1832044042@qq.mail','hycpass')
```

#### 要点

> 借助元类，我们可以在某个类完全定义好之前，率先修改该类的属性。
>
> 描述符与元类能够有效地组合起来，以便对某种行为做出修饰，或在程序运行时探查相关信息。
>
> 如果把元类与描述符相结合，那就可以在不使用weakref模块的前提下避免内存泄漏。
