元编程#

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)

4. 类元编程#