元编程#
1. 描述符#
使用描述符(descriptor)是一种特殊的类,可以类的形式定义实例的属性。@property
负责把一个方法变成属性调用,可对属性赋值时做必要的检查,并保证代码的清晰短⼩,同时将⽅法变换为只读,并重新实现⼀个属性的设置和读取⽅法,可做边界判定。
虽然内置的 property
经常用作装饰器,但它其实是一个类。property
都是类属性,但是 property
管理的其实是实例属性的存取。
实例属性遮盖类的数据属性
实例属性不会遮盖类
property
新添的类
property
遮盖现有的实例属性
内置的 property 类创建的其实是覆盖型描述符。
只读描述符必须有
__set__
方法用于验证的描述符可以只有
__set__
方法仅有
__get__
方法的描述符可以实现高效缓存
1.1. 属性验证#
被装饰的读值方法有个 .setter
属性,这个属性也是装饰器;这个装饰器把读值方法和设值方法绑定在一起。
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')
1.2. 属性删除#
定义特性时,可以使用 @my_propety.deleter
装饰器包装一个方法,负责删除特性管理的
属性。
class BlackKnight:
def __init__(self):
self.members = ['an arm', 'another arm', 'a leg', 'another leg']
self.phrases = [
"'Tis but a scratch.", "It's just a flesh wound.",
"I'm invincible!", "All right, we'll call it a draw."
]
@property
def member(self):
print('next member is:')
return self.members[0]
@member.deleter
def member(self):
print(
f'BLACK KNIGHT (loses {self.members.pop(0)})\n-- {self.phrases.pop(0)}'
)
knight = BlackKnight()
knight.member
# next member is:
# 'an arm'
del knight.member
# BLACK KNIGHT (loses an arm)
# -- 'Tis but a scratch.
del knight.member
# BLACK KNIGHT (loses another arm)
# -- It's just a flesh wound.
del knight.member
# BLACK KNIGHT (loses a leg)
# -- I'm invincible!
del knight.member
# BLACK KNIGHT (loses another leg)
# -- All right, we'll call it a draw.
2. 手动实现#
2.1. 基本结构#
class Quantity:
def __init__(self, storage_name):
self.storage_name = storage_name
def __set__(self, instance, value):
if value > 0:
instance.__dict__[self.storage_name] = value
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity('weight')
price = Quantity('price')
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
nutmeg = LineItem('Moluccan nutmeg', 8, 13.95)
print(nutmeg.weight, nutmeg.price) # (8, 13.95)
print(sorted(vars(nutmeg).items()))
# [('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)]
2.2. 自动获取属性名称#
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = f'_{prefix}#{index}'
cls.__counter += 1
def __get__(self, instance, owner):
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
coconuts = LineItem('Brazilian coconut', 20, 17.95)
print(coconuts.weight, coconuts.price) # (20, 17.95)
print(getattr(coconuts, '_Quantity#0')) # 20
print(getattr(coconuts, '_Quantity#1')) # 17.95
3. 元类#
Python 中,一切皆是对象,类也不例外,可对其赋值、拷贝、添加属性,甚至作为参数传递(如打印)。
3.1. type
#
type()
可被用于创建类,type('className', (parentClassName1, parentClassName2), {'attr': value})
,生成的类被称为元类(metaclass)。
Python 在创建类时,会自动创建 __metaclass__
属性。类中有 __metaclass__
时,Python 会通过 XXX 创建类(对象),若没有,则继续向上寻找。按照默认习惯,元类的类名总是以 Metaclass
结尾,以便清楚地表示这是一个元类。
className.__class__
可返回父类名称
当传入关键字参数 metaclass
时,魔术就生效了,它指示 Python 解释器在创建 MyList
时,要通过 ListMetaclass.__new__()
来创建,在此,我们可修改类的定义,如,加上新的方法,然后,返回修改后的定义。
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class MyList(list, metaclass=ListMetaclass):
pass
L = MyList()
L.add(1)
print(L)
L2 = list()
L2.add(1)