测试开发之Python核心笔记(23):抽象类、枚举类与元类

23.1 抽象类

抽象类是一种特殊的类,只能有抽象方法(没有实现功能),有两个典型特点:

  • 不能被实例化,只能被继承。
  • 强制子类必须实现抽象方法,使用相同的方法和方法名称。

抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),通常用作类似JAVA中接口的作用,用来规范子类的行为。

让每个人可以关注抽象类的方法和描述,而不需要考虑过多的实现细节,通过抽象类操作具体类的实例。

看一个例子:

from abc import ABCMeta, abstractmethod


class AllFile(metaclass=ABCMeta):  # 抽象类继承自ABCMeta
    all_type = 'file'  # 可以包含数据属性

    @abstractmethod  # 定义抽象方法,无需实现功能
    def read(self):
        pass

    @abstractmethod
    def write(self):
        pass


class Txt(AllFile):  # 子类继承抽象类,必须实现read和write方法,快捷方法Pycharm-->右键generate-->implements Methods
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的写入方法')


class Disk(AllFile):
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的写入方法')


class Network(AllFile):
    def read(self):
        print('网络数据的读取方法')

    def write(self):
        print('网络数据的写入方法')


if __name__ == '__main__':
    text = Txt()
    disk = Disk()
    network = Network()

    # 价值所在:调用者无需关心具体实现细节,使用抽象类中统一的方法来处理
    text.read()
    disk.write()
    network.read()
    
    print(text.all_type)
    print(disk.all_type)
    print(network.all_type)

23.2 枚举类

  • Python3.4增加的,可以看作是一种标签或是一系列常量的集合。
  • 通常用于表示某些特定的有限集合,例如星期、月份、状态等。
  • 代码可读性会好很多了。
from enum import unique, Enum


@unique  # 保证没有重复值。
class Weekday(Enum):
    Sun = 0  # Sun的value被设定为0,Sun叫做Key
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6


if __name__ == '__main__':
    print(Weekday.Sat)  # 输出Weekday.Sat
    print(Weekday.Sat.name)  # 输出Sat
    print(Weekday.Sat.value)  # 输出6
    day = 6
    if day == Weekday.Sat.value:
        print("今天是周六")
    elif day == Weekday.Mon.value:
        print("今天是周一")

不允许在类外直接修改枚举项的值。比如Weekday.Sat = 9则会报错AttributeError: Cannot reassign members。

另外,在枚举类中不能存在相同的 key 值。

在单元测试框架pytest的源码中,有一个叫做ExitCode的枚举类。定义了测试执行的结果枚举值,一共有六种不同的取值。

ExitCode的枚举类源码如下:

class ExitCode(enum.IntEnum):
    """
    .. versionadded:: 5.0

    Encodes the valid exit codes by pytest.

    Currently users and plugins may supply other exit codes as well.
    """

    #: tests passed,所有测试用例都成功了
    OK = 0
    #: tests failed,有部分测试用例失败了
    TESTS_FAILED = 1
    #: pytest was interrupted,测试过程被中断了
    INTERRUPTED = 2
    #: an internal error got in the way 测试过程发生了内部错误
    INTERNAL_ERROR = 3
    #: pytest was misused # 命令行出错
    USAGE_ERROR = 4
    #: pytest couldn't find tests  # 没有收集到测试用例
    NO_TESTS_COLLECTED = 5

23.3 元类

元类是python的中一个难点,在大部分场景下都不会用到。但是在编写框架方面却是必不可缺少的利器。要想理解元类,需要从以下三个层面逐渐深入学习。

  1. 理解类也是对象
class MyClassObject(object):
    pass


def echo(o):
    print(o)


echo(MyClassObject)  # 将类做为参数传给函数

MyClassObjectMirror = MyClassObject  # 将类赋值给一个变量

  1. type能动态的创建类

通常我们使用class关键字创建类。

class Foo:
    bar = True

其实还可以通过type创建Foo类,type可以接受一个类的描述作为参数,然后返回一个类。

在type类中,初始化方法如下:

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    # (copied from class doc)
    """
    pass

可以通过type(name, bases, dict)产生一个新类。

例如:

Foo = type('Foo', (), {'bar':True})

type的第一参数是类名,第二个参数是父类的元组(可以为空),第三个参数是属性的字典(名称和值))。这个例子中,Foo没有父类,只有一个bar属性,下面看一个完整的具有继承关系,并且具有方法的类如何通过type生成。

Foo = type('Foo', (), {'bar': True})


def echo_bar(self):
    print(self.bar)


FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})

my_foo = FooChild()
print(dir(my_foo))

上面这些代码,就是当我们使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

  • 使用自定义元类创建类

元类用来对类的创建过程做一些手脚。函数type实际上是Python的内建元类,type就是创建类对象的类。除了type这个Python内置的元类,也可以创建自定义的元类,再使用自定义的元类创建类。

自定义一个元类,元类对类的创建和修改都在__new__ 中完成,使用元类的类通过metaclass=MyMetaClass引用元类。整个模板如下:

class MyMetaClass(type):
    # mcs代表元类本身,name表示使用这个元类的类名,bases表示使用这个元类的类的父类,attrs表示属性
    def __new__(mcs, name, bases, attrs):
        # 定制类,对name、bases、attrs做点手脚
        pass
        return super().__new__(mcs, name, bases, attrs)  # 做完手脚之后,返回type类的实例
    
class MyClass(metaclass=MyMetaClass):  # 使用元类MyMetaClass定制MyClass
    pass

根据这个模板,介绍几个例子来学习学习。

  • 例子1,通过元类为每一个类自动添加一个方法

    下面的代码为每一个使用MyMetaClass元类的类都自动添加一个方法,这个方法以say开头后面接类名。这个方法可以输出类名和参数值。

class MyMetaClass(type):
    def __new__(mcs, name, bases, attrs):
        attrs['say' + name] = lambda self, value: print(name + ',' + value + '!')
        return super().__new__(mcs, name, bases, attrs)


class Hello(metaclass=MyMetaClass):
    pass


class Nihao(metaclass=MyMetaClass):
    pass


if __name__ == '__main__':
    hello = Hello()
    nihao = Nihao()

    hello.sayHello('world')
    nihao.sayNihao('你好')
  • 例子2,通过元类将类的属性名自动改为大写

    下面的代码将每一个使用MyMetaClass元类的类的属性名都改成大写。以双下划线开头的属性除外。

class MyMetaClass(type):
    def __new__(mcs, name, bases, attrs):
        attrs = ((key, value) for key, value in attrs.items() if not key.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super().__new__(mcs, name, bases, uppercase_attr)


class Foo(metaclass=MyMetaClass):
    bar = 'bip'


if __name__ == '__main__':
    f = Foo()
    print(dir(f))  # 输出中可以看到'BAR',
    print(f.BAR)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值