抽象类是一种特殊的类,只能有抽象方法(没有实现功能),有两个典型特点:
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如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)
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
元类是python的中一个难点,在大部分场景下都不会用到。但是在编写框架方面却是必不可缺少的利器。要想理解元类,需要从以下三个层面逐渐深入学习。
class MyClassObject(object):
pass
def echo(o):
print(o)
echo(MyClassObject) # 将类做为参数传给函数
MyClassObjectMirror = MyClassObject # 将类赋值给一个变量
通常我们使用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)