Reportlab生成PDF文件

在reportlab的官方网站(https://www.reportlab.com/chartgallery/pie/)上给出生成各种图表的例子,对于我这种不经常跟图表打交道的人来说还是挺开眼界的,我们拿出几个做中文注释。在即将推出的reportlab的教学视频里会详细介绍每个程序,先来看都生成了哪些图表。

直方图(Bar Chart):

折线图(Line Plot):

饼形图(Pie Chart):

雷达图(Radar Chart或蛛网图Spider Chart):

散点图(Scatter Plot):

 

分区图(Area Chart):

生成每个图表的程序都放在独立的代码文件里,用自定义的类来完成,程序runall.py把当前文件夹下的所有生成图表的.py的程序逐个运行,生成指定类型的图表文件(可以是pdf、pict或png)。runall.py和注释如下:

# 运行代码所在目录下所有的生成图表的py程序

import sys
import glob     # 查找符合特定规则的文件路径名
import inspect  # inspect.getmembers()可以获取模块、类或对象的成员或参数信息

def moduleClasses(mod):
    """
    :参数mod: 引进的模块
    :返回值: 返回模块里定义的类
    """

    def P(obj, m=mod.__name__, CT=type):
        """
        :参数obj:    可以是模块自己定义的类成员,比如2-2lineChart_withMarkers.py里定义的LineChart_WithMarkers
                    也可是引用的模块里的成员,比如PCMYKColor(0,0,0,24,alpha=100)
        :参数m:      mod.__name__得到的模块名,比如'2-2lineChart_withMarkers'
        :参数CT:     type是元类,type(3)是int类型,type(int)是type类型
        :返回值:     真或假
        """
        return (type(obj)==CT and obj.__module__==m)
        # obj.__module__得到obj所在的模块名
        # obj.__module__== m,m是mod.__name__,mod是调用函数给的模块
        # 所以这个判断条件确保只提取给出模块的类成员
        # type(obj)是obj的类型,obj的类型是元类type

    try:
        return inspect.getmembers(mod, P)[0][1]
        # inspect.getmembers()检查第一个参数mod(module的前三个字母)的成员或参数,
        # 这里的mod接收的是模块,比如<module '2-2lineChart_withMarkers' from '/Users/……/2-2lineChart_withMarkers.py'>
        # 如果有第二个参数(P是函数名),则只返回使P为真的成员或参数
        # inspect.getmembers(mod, P))返回:
        # [('LineChart_WithMarkers', <class '2-2lineChart_withMarkers.LineChart_WithMarkers'>)]

    except:
        return None

def getclass(f):
    """
    :参数f:  引入的模块
    :返回值: 返回模块里定义的类
    """
    return moduleClasses(__import__(f))
    # import导入Python模块时,调用的是__import__()函数,只有动态加载模块时才直接使用该函数
    # __import__(f)引入f, 如__import__('2-2lineChart_withMarkers')
    # 将2-2lineChart_withMarkers.py的内容引入程序
    # 这里为了讲课方便,引进的模块名称用数字序号开头标识顺序
    # 如果直接在程序中用import 2 - 2lineChart_withMarkers是会报错的
    # 但像__import__('2-2lineChart_withMarkers')这样动态引入就可以
    # __import__(f)返回引进模块,比如<module '2-2lineChart_withMarkers' from '/Users/……/2-2lineChart_withMarkers.py'>

def run(format, VERBOSE=0):
    """
    :参数format:   生成文件的类型,比如pdf,png……
    :参数VERBOSE:  VERBOSE是冗长、啰嗦,默认为0;如果调用run()时指定为1,则会输出文件的类型
    """
    formats = format.split(',')
    # 假如接收到的format是' PDF, png',则format被用','分开后得到的formats为[' PDF', 'png']
    for i in range(0, len(formats)):
        formats[i] == formats[i].strip().lower()
        # 剥掉列表formats的每个字符串元素左右的空格,并变成小写,formats变成['pdf', 'png']
    allfiles = glob.glob('*.py')
    # glob.glob('*.py')将当前文件夹下的py文件名字符串取出来,放进allfiles
    # 比如allfiles的内容可能为['2-2lineChart_withMarkers.py', '5-1areaChart_withDynamicLabel.py', ……]
    allfiles.sort()
    # 对列表allfiles的元素进行排序
    for fn in allfiles:     # 进入循环一个一个文件处理
        f = fn.split('.')[0]
        # fn.split('.')把文件名与扩展名分开,比如['2-2lineChart_withMarkers', 'py']
        # 索引为0取出文件名放入变量f,即'2-2lineChart_withMarkers'
        c = getclass(f)
        # 调用自己定义的函数getclass('2-2lineChart_withMarkers')
        # 这个函数的行参是名字为f的模块,返回名字为f内的类,比如<class '2-2lineChart_withMarkers.LineChart_WithMarkers'>
        if c != None:       # 如果fn里确实有定义的类和模块,则存放返回值的变量c就不为None
            print(c.__name__)   # 显示fn里定义的类名称,比如LineChart_WithMarkers
            try:
                for fmt in formats:
                # 理论上formats是存放着文件格式字串的列表,比如['pdf', 'png']
                    if fmt:     # 如果列表元素不为空
                        c().save(formats=[fmt],outDir='.',fnRoot=c.__name__)
                        # c是引入的fn里定义的一个类,c()是类c生成的一个对象
                        # 调用c类对象的方法函数save()将对象内容生成指定格式的文件
                        # formats=格式参数,outDir=指定输出文件的路径,.是指当前路径,fnRoot=文件名
                        if VERBOSE:     # 如果调用函数时给的VERBOSE为1
                            print("  %s.%s" % (c.__name__, fmt))
                            # 则输出文件名及格式
            except:
                print("  COULDN'T CREATE '%s.%s'!" % (c.__name__, format))
                # 出错输出提示无法生成该格式的文件,继续处理下一个

if __name__ == "__main__":
    if len(sys.argv) == 1:
    # 以下情况if语句的条件为真:
    #   1、在pycharm(或其他集成开发环境)
    #   2、在终端/命令窗口进入runall.py所在目录,敲入:python3 runall.py
        run('pdf,pict,png')
        # 依次声称PDF、PICT和PNG文件类型的图表
    else:
        try:
            if sys.argv[1] == "-h":
            # if语句条件为真的情况:
            #   在命令行或终端进入runall.py所在目录,运行: python3 runall.py -h
            #   sys.argv[0]为'runall.py',sys.argv[1]为'-h'
                print('usage: runall.py [FORMAT] [-h]')
                print('   if format is supplied is should be one or more of pdf,gif,eps,png etc')
                print('   if format is missing the following formats are assumed: pdf,pict,png')
                print('   -h prints this message')
            else:
                t = sys.argv[1:]
                # 比如命令:python3 runall.py PDF PNG
                # sys.argv[1]为'PDF',sys.argv[2]为'PNG',t的值为['PDF', 'PNG']
                for f in t:
                    run(f)
                # 进入循环,依次运行run('PDF')和run('PNG'),指定生成的文件类型
        except:     # 给出出错提示,输出跟python3 runall.py -h一样
            print('usage: python3 runall.py [FORMAT][-h]')
            print('   if format is supplied is should be one or more of pdf,gif,eps,png etc')
            print('   if format is missing the following formats are assumed: pdf,pict,png')
            print('   -h prints this message')
            raise