python入门系列:元类编程

Python属性函数

引言

Python中我们对于自己设置的类,尽量要使其属性私有化,获得更好的封装性。
如果要访问和修改私有属性,要为其设置set和get方法。
Python中,可以使用特殊的装饰器将set和get方法属性化,这样就能够使用更简洁的语法去调用这些方法。
使用案例

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

这里以 age 为例

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

@age.setter
def age(self, age):
if age < 0:
self.age = age
elif age > 120:
self.
age = 120
else:
self.__age = age
me = Person("MetaTian", 20)
print(me.age) # 直接作为属性进行调用,get方法
me.age += 1 # get 和 set 方法同时使用
print(me.age) # get 方法

result:

20

21

魔法函数getattr()和getattribute()

引言

这两个函数是解释器在查找对象属性时要进行调用的
如果没找到代码需要的属性,则会调用getattr()
如果实现了getattribute(),则不管请求什么属性都会先调用这个魔法函数
使用案例

"""
如果没有实现 getattr(),调用未定义的属性后,会报错
"""
class Person:
pass

me = 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 = name
self.age = age

def 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方法分别包装。描述符就是可以重用的属性。
使用案例

"""
定义了一个 “非负的” 描述符
"""
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 = val

if 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 = name
self.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

小结

描述符其实是目标类的一个属性,也就是说目标类中有一个描述符实例。当目标类的实例准备操作自身属性时,会首先将它交给类的个描述符实例进行管理(get(), set(), delete()),然后由它把属性设置到实例中(obj.dict[label])。

属性调用顺序

引言

Python的描述符有两种类型:数据描述符和非数据描述符。
数据描述符:实现了get()和set()魔法函数。
非数据描述符:只实现了get()魔法函数。
使用不同的描述符,属性查找的过程是不一样的。
详细

在使用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
new init 的区别

引言

new()允许我们在类的生成过程中加入自己的逻辑,是一个静态方法。
init()可以在生成对象之后加入自己的逻辑,一般是初始化属性。
init()的调用在new()之后。
使用案例

"""
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 = name
me = Person("MetaTian")

result:

---in new---

---in init---

自定义元类

引言

前面介绍过,Python中一切皆对象,类也是一个对象,在Java中想要动态生成一个类,似乎并不容易,但是对于动态语言Python,这是比较容易实现和理解的。
def create_cls(name):
if name = "User": # 动态生成的 User 类
class User:
def str(self):
return "user"
return User
elif name = "Person": # 动态生成的 Person 类
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,而是用自己定义的元类。
使用案例

"""
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 = name
def str(self):
return "I am {name}".format(name=self.name)
me = Person("MetaTian")
print(me)

result:

I am MetaTian

通过元类实现ORM

引言

什么是ORM
使用案例

"""
需求
我们想构建出一个 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和存储格式限制。

"""
分析
类中的属性要采用描述符的方式,在存取数据的时候要进行类型检查
不仅要保存属性值的信息,还要存储属性在数据库表中的信息(max_length, db_column)
"""
class CharField:
def init(self, max_length=None, db_column=None):
self.db_column = dbcolumn
self.max_length = max_length
self._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:
pass

class 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] = v

for 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"] = _meta
attrs["fields"] = fields

for 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:
pass

class 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") # name

class 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_column
self.max_length = max_length
self._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

class 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] = v
for 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"] = _meta
attrs["fields"] = fields
del 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)

喜欢python + qun:839383765 可以获取Python各类免费最新入门学习资料!

转载于:https://blog.51cto.com/14186420/2349826

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值