Python 属性描述符和属性查找

起因于 @property 装饰器,它可以将对函数的调用伪装成对属性的调用,就是不用加括号了。
假设有这么一个类:

class Test(object):
    def __init__(self, age=0):
        self.age = age
        
        ---------分割线------------
        if age < 0:
            raise ValueError("Negative value not allowed:{}".format(age))

假设我们想要将 age 做一些限制,例如非负,那么我们只能添加上面的代码。
但是如果我们使用 . 来赋值呢,这不起作用啊。所以可以这样做:

class Test(object):
    def __init__(self, age=0):
        self._age = age
    @propery
    def age(self):
        return self._age
    @propery.setter
    def age(self, age):
        if age < 0:
            raise ValueError("Negative value not allowed:{}".format(age))
    @age.deleter
    def age(self):
        raise AttributeError("Can not delete age")

但是如果有很多属性要做这样的检查呢?那么不得不写一大堆 @propery.setter ,这样代码分离性就做的不好。
所以出现了描述符?
我现在的理解就是实现了__get__(), __set__()__delete__() 三者任意一个即可。只不过只实现 __get__() 的话称为非数据描述符,意味着只可读,同时实现 __get__(), 和 __set__() 称为数据描述符,意味着可读写。
那么上面的代码就可以这样写了

class Integer(object):
    def __init__(self, name):
        # 这里也可以做一些检查,防止直接实例化
        self.name = name

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError("Negative value not allowed")
        instance.__dict__[self.name] = value

class Test(object):
    age = Integer('test')
    
    -----------分割线------------
    def __init__(self, age):
        self.age = age

如果对 django 熟悉的话,发现这很像它的 orm。但是这样不能直接实例化了,所以可以添加上面分割线下代码。
这样可以实现一个简单的 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()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,099评论 19 139
  • 生活就像拉扯开的一页宣纸 以为平铺直叙细看却发现劣迹斑斑 我想要留住的一丝平淡风景早已烟消云散 只好拂一拂桌布上的...
    凛凛蝶阅读 250评论 0 1
  • 昨天是我爸的生辰,我居然差点就忘了,如果老爸看见我这样子的话,肯定会骂我这个不孝的女儿了吧。跟嫂子的聊天中,我觉得...
    简单_c12b阅读 166评论 1 1