Python属性函数
引言
Python中我们对于自己设置的类,尽量要使其属性私有化,获得更好的封装性。- 如果要访问和修改私有属性,要为其设置set和get方法。
Python中,可以使用特殊的装饰器将set和get方法属性化,这样就能够使用更简洁的语法去调用这些方法。使用案例
```python class Person: def init(self, name, age):
self.__name = nameself.__age = age
这里以 age 为例
@property def age(self):
return self.__age
@age.setter def age(self, age):
if age < 0:self.__age = ageelif age > 120:self.__age = 120else:self.__age = age
me = Person(“MetaTian”, 20)
print(me.age) # 直接作为属性进行调用,get方法 me.age += 1 # get 和 set 方法同时使用 print(me.age) # get 方法
result:
20
21
<a name="0a8d4341"></a># 魔法函数__getattr__()和__getattribute__()<a name="e4568847"></a>## 引言- 这两个函数是解释器在查找对象属性时要进行调用的- 如果没找到代码需要的属性,则会调用`__getattr__()`- 如果实现了`__getattribute__()`,则不管请求什么属性都会**先调用**这个魔法函数<a name="568d18bd"></a>## 使用案例```python"""如果没有实现 __getattr__(),调用未定义的属性后,会报错"""class Person:passme = Person()print(me.age)# result:# AttributeError: 'Person' object has no attribute 'age'
class Person:# attr 是代码请求的属性def __getattr__(self, attr):return "{0} dose not exist".format(attr)me = Person()print(me.age)print(me.name)# result:# age dose not exist# name dose not exist
"""实现了__getattribute__()魔法函数不论请求什么属性,都返回同样的值"""class Person:def __init__(self, name, age):self.name = nameself.age = agedef __getattribute__(self, attr):return "value"me = Person("MetaTian", 19)print(me.age, me.name, me.gender)# result:# value value value
属性描述符
引言
- 描述符是对多个属性运用相同的逻辑来进行存取的一种方式,它是实现了特定魔法函数的一个类。
- 只要实现了
__get__(),__set__(),__delete__()三个魔法函数中的任意一个,这个类就是描述符。 property最大的缺点就是它修饰属性的过程不能重复使用,如果要对多个属性进行非负检查(>=0),那必须对每个属性的set方法分别包装。描述符就是可以重用的属性。使用案例
```python “”” 定义了一个 “非负的” 描述符 “”” class NonNegative: def init(self, label):
self.label = label # 存储描述符在对象级别的名称
def get(self, instance, owner):
# 当进行属性调用时,obj.p# instance = obj# owner = type(obj)return instance.__dict__.get(self.label) # 给实例添加属性
def set(self, instance, value):
# 当进行属性调用时,obj.p = val# instance = obj# value = valif not isinstance(value, int):raise ValueError("Int value allowed")if value < 0:raise ValueError("Negative value not allowed: %s" % value)instance.__dict__[self.label] = value # 把通过检验的属性值反向设置给调用对象
def delete(self, instance):
pass
“”” 使用描述符的一个类 “”” class Person:
定义为类的属性,而不是实例属性,才能触发后面的属性检查
age = NonNegative(“age”) # label
def init(self, name, age):
self.name = nameself.age = age # 描述符会起作用,自动调用get和set魔法函数
me = Person(“MetaTian”, “21”)
result:
ValueError: Int value allowed
him = Person(“Rity”, -18)
result:
ValueError: Negative value not allowed: -21
me = Person(“MetaTian”, 21) him = Person(“Rity”, 21)
him.age += 1 me.age -= 1 print(him.age, me.age)
result:
22 20
<a name="5db9fd7c"></a>## 小结**描述符**其实是目标**类**的一个属性,也就是说目标类中有一个**描述符实例**。当目标类的实例准备操作自身属性时,会首先将它交给类的个**描述符实例**进行管理(`__get__(), __set__(), __delete__()`),然后由它把属性设置到实例中(`obj.__dict__[label]`)。<a name="81107be0"></a># 属性调用顺序<a name="e4568847"></a>## 引言- `Python`的描述符有两种类型:**数据描述符**和**非数据描述符**。- 数据描述符:实现了`__get__()`和`__set__()`魔法函数。- 非数据描述符:只实现了`__get__()`魔法函数。- 使用不同的描述符,属性查找的过程是不一样的。<a name="1f0a3a1c"></a>## 详细在使用`me.age`操作属性的时候,解释器对属性`age`的查找顺序是怎么样的呢?如果`me`是某个对象的实例,那么对于`me.age`或与其等价的`getattr(me, "age")`属性操作方式,会首先调用`__getattribute__()`,如果调用过程中抛出了**AttributeError**,这时就会调用`__getattr__()`如果`age`是一个**属性描述符**,则相关属性操作操作会委托给`__get__()`魔法函数,这个过程发生在`__getattribute__()`内部此时,`age`属性的调用顺序如下:- **如果**`age`出现在`Person`或其基类的`__dict__`中,而且`age`是一个**数据描述符**,那么直接调用`__get__()`方法- **如果**`age`出现在`me`的`__dict__`中,那么直接返回`me.__dict__['age']`- **如果**`age`出现在`Person`或其基类的`__dict__`中:- 如果`age`是**非数据描述符**,那么就调用`__get__`- 否则返回`Person.__dict__['age']`- **如果**定义了`__getattr__()`,则调用`__getattr__()`- **否则**,抛出**AttributeError**<a name="9e8945fc"></a># __new __和 __init __的区别<a name="e4568847"></a>## 引言- `__new__()`允许我们在**类**的生成过程中加入自己的逻辑,是一个静态方法。- `__init__()`可以在生成**对象**之后加入自己的逻辑,一般是初始化属性。- `__init__()`的调用在`__new__()`之后。<a name="568d18bd"></a>## 使用案例```python"""new 用来控制对象的生成过程,在对象生成之前起作用init 用来完善生成的对象,在对象生成之后调用如果 new 中没有返回生成的对象,则 init 方法不会调用"""def Person:def __new__(cls, *args, **kwargs):print("---in new---")return super.__new__(cls)def __init__(self, name):print("---in init---")self.name = nameme = Person("MetaTian")# result:# ---in new---# ---in init---
自定义元类
引言
- 前面介绍过,
Python中一切皆对象,类也是一个对象,在Java中想要动态生成一个类,似乎并不容易,但是对于动态语言Python,这是比较容易实现和理解的。 ```python def create_cls(name): if name = “User”: # 动态生成的 User 类
elif name = “Person”: # 动态生成的 Person 类class User:def __str__(self):return "user"return User
class Person:def __str__(self):return "person"return Person
ClsUser, ClsPson = create_cls(“User”), create_cls(“Person”) user, person = ClsUser(), ClsPson() print(user, person)
result:
user person
- 使用`type`动态地创建类会更加简洁。- 但是实际使用,往往很少直接使用`type`,而是用自己定义的**元类**。<a name="568d18bd"></a>## 使用案例```python"""class type(name, bases, dict)name: str, 要创建类的名称bases: tuple, 创建类要继承的基类dict: 创建类的属性集合"""# 必须有 self 作为参数def say(self):print("I am a person")Person = type("Person", (), {"name":"MetaTian", "say":say})me = Person()print(me.name)me.say()# result:# MetaTian# I am a person
Python中类的创建过程中,会首先寻找metaclass,通过metaclass去创建这个类,如果没有找到metaclass,则向上找基类,使用它们的meataclass,如果都没有,再直接调用type来创建这个类。
# 这是我们自己定义的一个元类class MetaClass(type):def __new__(cls, *args, **kwargs):# 这里不加入其它逻辑,只构建一个框架,委托给 type 进行类的构建return super().__new__(cls, *args, **kwargs) # 这里需要传递参数# 用我们自己定义的元类来控制 Person 类的构建class Person(metaclass=MetaClass):def __init__(self, name):self.name = namedef __str__(self):return "I am {name}".format(name=self.name)me = Person("MetaTian")print(me)# result:# I am MetaTian
通过元类实现ORM
引言
-
使用案例
```python “”” 需求 我们想构建出一个 Person 类,构造其一个实例后 通过obj.save()方法,就可以将它存储在数据库中 “”” class Person:
这里的属性对应数据库表中的每一项
在表中的哪一列,这一列数据的约束条件是什么
name = CharField(db_colunm=None, max_length=None)
Person 类中,定义了另外一个类,用来存放其他的一些信息
class Meta:
db_table = "person" # 这里存放了对象对应的数据库表名,用来后面拼凑 sql 语句
me = Person(“MetaTian”) me.save() # 在数据库中插入一条记录
首先,我们来构建**数据描述符**,数据的存储以及类型检查都委托给它来完成,把简单的调用方式暴露出来即可。这里不仅仅要处理需存储的数据,数据在数据库中的一些属性也要进行处理,比如所在的`column`和存储格式限制。```python"""分析类中的属性要采用描述符的方式,在存取数据的时候要进行类型检查不仅要保存属性值的信息,还要存储属性在数据库表中的信息(max_length, db_column)"""class CharField:def __init__(self, max_length=None, db_column=None):self.db_column = dbcolumnself.max_length = max_lengthself._value = None # 描述符初始化的时候不给默认值,一般由用户在后面赋值# 将这两个属性设置为必填项if max_length is None:raise ValueError("max_length info required")if db_column is None:raise ValueError("db_column info required")# 返回值def __get__(self, instance, owner):return self._value# 设置值def __set__(self, instance, value):if not isinstance(value, str):raise ValueError("String value required")if len(value) > self.max_length:raise ValueError("valuen length invalid")self._value = value # 通过检查后,赋值保存
"""现在我们有了如下结构:还需要自己定义的一个元类来控制 Person 类的生成"""class CharField:passclass Person:name = CharField(max_length=10, db_colunm="name") # name 属性的存取检查已经完成class Meta:db_table = "person" # 这个类对象和 person 这个数据库表对应
自定义的元类,要修改__new__()方法,在类的创建过程中加入一些我们自己的逻辑。
class ModelMetaClass(type):"""这里对参数元组进行了拆解,便于更好的观察name: 要构建类对象的名称bases: 继承的基类attrs: 构建类对象中的属性,我们要从中提取出和业务逻辑有关的属性进行另外存储"""def __new__(cls, name, bases, attrs, **kwargs):for k, v in attrs.items():print("{key}:{val}".format(key=k, val=v))return super().__new__(cls, name, bases, attrs, **kwargs)# result:# __qualname__:Person# __module__:__main__# Meta:<class '__main__.Person.Meta'> # 这是我们在 Person 中定义的类,存放信息# name:<__main__.CharField object at 0x00000226B18B4F28> # 这是我们需要的
class ModelMetaClass(type):def __new__(cls, name, bases, attrs, **kwargs):# 抽离出数据fields = {}for k, v in attrs.items():if isinstance(v, CharField): # 数据域fields[k] = vfor k in fields:del attrs[k] # 清空原来的数据# 抽离出表名称attrs_meta = attrs.get("Meta", None) # 关于字典 get 方法的使用,前面讲过db_table = name.lower() # 数据库表名默认是类的小写形式if attrs_meta: # 如果类存储了表信息,用类中定义的db_table = getattr(attrs_meta, "db_table")del attrs["Meta"] # 表名称信息已经获得,这里就不需要了# 重组 attrs 参数_meta = {"db_table":db_table}attrs["_meta"] = _metaattrs["fields"] = fieldsfor k, v in attrs.items():print("{key}:{val}".format(key=k, val=v))# 最后委托给 type ,完成类的创建return super().__new__(cls, name, bases, attrs, **kwargs)# result:# __module__:__main__# fields:{'name': <__main__.CharField object at 0x000001AFCD994F98>}# __qualname__:Person# _meta:{'db_table': 'person'}
"""现在我们有了如下结构:"""# 控制类的生成class ModelMetaClass(type):pass# 检查属性存取class CharField:passclass Person(metaclass=ModelMetaClass):name = CharField(max_length=10, db_colunm="name") # name 属性的存取检查已经完成class Meta:db_table = "person" # 这个类对象和 person 这个数据库表对应
最后,还要在Person类中添加我们需要的逻辑,比如初始化操作、写入数据库操作、查询操作等。因此要为每一个逻辑添加一个方法加入类中,但是会让这个类显得十分臃肿。我们构建这个类的目的就是希望它能和数据库的表结构尽量一一对应,简化我们的操作。同时,不同类的操作需求可能是一样的,都需要往数据库中插入一条新记录。因此,可以考虑将这些操作逻辑抽象成一个父类。
class BaseModel(metaclass=ModelMetaClass):# 便于通用,这里要约定初始化的一种方式def __init__(self, *args, **kwargs):for k, v in kwargs.items():setattr(self, k, v)super.__init__()def save(self):fields, values = [], []for k, v in self.fields.items():fields.append(v.db_column)values.append(str(getattr(self, k))) # 把所有的值都变成字符串,便于后面拼接sql = "insert {table}({fields}) value({values})".format(table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))print(sql)# todo 和数据库连接有关的逻辑class Person(BaseModel):name = CharField(max_length=10, db_colunm="name") # nameclass Meta:db_table = "person" # 这个类对象和 person 这个数据库表对应me = Person(name="MetaTian")me.save()
完整的代码
class CharField:def __init__(self, max_length=None, db_column=None):self.db_column = db_columnself.max_length = max_lengthself._value = Noneif max_length is None:raise ValueError("max_length info required")if db_column is None:raise ValueError("db_column info required")def __get__(self, instance, owner):return self._valuedef __set__(self, instance, value):if not isinstance(value, str):raise ValueError("String value required")if len(value) > self.max_length:raise ValueError("valuen length invalid")self._value = valueclass ModelMetaClass(type):def __new__(cls, name, bases, attrs, **kwargs):# BaseModel和其子类都要通过这个元类来进行创建# 子类才有相关的 Meta 信息,进行信息重组,这里进行过滤if name == "BaseModel":return super().__new__(cls, name, bases, attrs, **kwargs)# 抽离出数据fields = {}for k, v in attrs.items():if isinstance(v, CharField):fields[k] = vfor k in fields:del attrs[k]# 抽离出表名称attrs_meta = attrs.get("Meta", None) # 关于字典 get 方法的使用,前面讲过db_table = name.lower() # 数据库表名默认是类的小写形式if attrs_meta: # 如果类存储了表信息,用类中定义的db_table = getattr(attrs_meta, "db_table")# 重组 attrs 参数_meta = {"db_table":db_table}attrs["_meta"] = _metaattrs["fields"] = fieldsdel attrs["Meta"] # 表名称信息已经获得,这里就不需要了# 最后委托给 type ,完成类的创建return super().__new__(cls, name, bases, attrs, **kwargs)class BaseModel(metaclass=ModelMetaClass):# 便于通用,这里要约定初始化的一种方式def __init__(self, *args, **kwargs):for k, v in kwargs.items():setattr(self, k, v)super().__init__()def save(self):fields, values = [], []for k, v in self.fields.items():fields.append(v.db_column)values.append(str(getattr(self, k, None))) # 把所有的值都变成字符串,便于后面拼接sql = "insert {table}({fields}) value({values})".format(table=self._meta["db_table"], fields=",".join(fields), values=",".join(values))# todo 和数据库有关的逻辑print(sql)class Person(BaseModel):name = CharField(max_length=10, db_column="name") # name 属性的存取检查已经完成class Meta:db_table = "person" # 这个类对象和 person 这个数据库表对应me = Person(name="MetaTian")me.save()# result:# insert person(name) value(MetaTian)
