• [技术干货] 利用Python和C语言分别实现哈夫曼编码【转】
    目录1.C语言实现1.1代码说明1.2运行结果2.Python实现2.1代码说明2.2运行结果1.C语言实现1.1代码说明a  创建双向链表:在创建哈夫曼树的过程中,需要不断对结点进行更改和删除,所以选用双向链表的结构更容易123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102'''C#include <stdlib.h>#include <stdio.h>#include <windows.h>    //哈夫曼树结构体,数据域存储字符及其权重typedef struct node{    char c;    int weight;    struct node *lchild, *rchild;}Huffman, *Tree;    //双向链表结构体,数据域存储哈夫曼树结点typedef struct list{    Tree root;    struct list *pre;    struct list *next;}List, *pList;    //创建双向链表,返回头结点指针pList creatList(){    pList head = (pList)malloc(sizeof(List));      pList temp1 = head;    pList temp2 = (pList)malloc(sizeof(List));    temp1->pre = NULL;    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'a';    temp1->root->weight = 22;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;           temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'b';    temp1->root->weight = 5;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;           temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'c';    temp1->root->weight = 38;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'd';    temp1->root->weight = 9;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'e';    temp1->root->weight = 44;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp2 = (pList)malloc(sizeof(List));    temp1->next = temp2;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'f';    temp1->root->weight = 12;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      temp2->pre = temp1;    temp1 = temp2;    temp1->next = NULL;    temp1->root = (Tree)malloc(sizeof(Huffman));    temp1->root->c = 'g';    temp1->root->weight = 65;    temp1->root->lchild = NULL;    temp1->root->rchild = NULL;      return head;                          }b创建栈结构:解码过程需要用到两个栈,一个用来存放树结点,一个用来存放码0和1123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115'''C#define STACK_INIT_SIZE 100   //栈初始开辟空间大小#define STACK_INCREMENT 10    //栈追加空间大小  //字符栈结构体,存放编码'0'和'1'typedef struct {    char *base;    char *top;    int size;}charStack;    //栈初始化charStack charStackInit(){    charStack s;    s.base = (char *)malloc(sizeof(char)*STACK_INIT_SIZE);    s.top = s.base;    s.size = STACK_INIT_SIZE;    return s;}  //入栈void charPush(charStack *s, char e){    if(s->top - s->base >= s->size)    {        s->size += STACK_INCREMENT;        s->base = realloc(s->base, sizeof(char)*s->size);    }    *s->top = e;    s->top++;}  //出栈char charPop(charStack *s){    if(s->top != s->base)    {        s->top--;        return *s->top;    }    return -1;}  //得到栈顶元素,但不出栈char charGetTop(charStack *s){    s->top--;    char temp = *s->top;    s->top++;    return temp;}  //栈结构体,存放哈夫曼树结点typedef struct{    Huffman *base;    Huffman *top;    int size;}BiStack;  //栈初始化BiStack stackInit(){    BiStack s;    s.base = (Huffman *)malloc(sizeof(Huffman)*STACK_INIT_SIZE);    s.top = s.base;    s.size =STACK_INIT_SIZE;    return s;}  //入栈void push(BiStack *s, Huffman e){    if(s->top - s->base >= s->size)    {        s->size += STACK_INCREMENT;        s->base = (Huffman *)realloc(s->base, sizeof(Huffman)*s->size);    }    *s->top = e;    s->top++;}  //出栈Huffman pop(BiStack *s){    Huffman temp;    s->top--;    temp = *s->top;    return temp;}  //得到栈顶元素,但不出栈Huffman getTop(BiStack s){    Huffman temp;    s.top--;    temp = *s.top;    return temp;}  char stack[7][10];             //记录a~g的编码//遍历栈,得到字符c的编码void traverseStack(charStack s, char c){    int index = c - 'a';     int i = 0;    while(s.base != s.top)    {        stack[index][i] = *s.base;        i++;        s.base++;    }}c 创建哈夫曼树:123456789101112131415161718192021222324252627282930313233343536373839404142434445'''C//通过双向链表创建哈夫曼树,返回根结点指针Tree creatHuffman(pList head){    pList list1 = NULL;    pList list2 = NULL;    pList index = NULL;    Tree root = NULL;    while(head->next != NULL)   //链表只剩一个结点时循环结束,此结点数据域即为哈夫曼树的根结点    {        list1 = head;        list2 = head->next;        index = list2->next;        root = (Tree)malloc(sizeof(Huffman));        while(index != NULL)    //找到链表中权重最小的两个结点list1,list2        {            if(list1->root->weight > index->root->weight || list2->root->weight > index->root->weight)            {                if(list1->root->weight > list2->root->weight) list1 = index;                else list2 = index;            }            index = index->next;        }        //list1和list2设为新结点的左右孩子        if(list2->root->weight > list1->root->weight)        {            root->lchild = list1->root;            root->rchild = list2->root;        }        else        {            root->lchild = list2->root;            root->rchild = list1->root;        }        //新结点字符统一设为空格,权重设为list1与list2权重之和        root->c = ' ';        root->weight = list1->root->weight + list2->root->weight;        //list1数据域替换成新结点,并删除list2        list1->root = root;        list2->pre->next = list2->next;        if(list2->next != NULL)            list2->next->pre = list2->pre;        }    return head->root;}d编码:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869'''Cchar stack[7][10];             //记录a~g的编码//遍历栈,得到字符c的编码void traverseStack(charStack s, char c){    int index = c - 'a';     int i = 0;    while(s.base != s.top)    {        stack[index][i] = *s.base;        i++;        s.base++;    }}    //通过哈夫曼树编码void encodeHuffman(Tree T){      BiStack bs = stackInit();    charStack cs = charStackInit();    Huffman root = *T;      Tree temp = NULL;    push(&bs, root);      //根结点入栈    while(bs.top != bs.base)      //栈空表示遍历结束    {        root = getTop(bs);        temp = root.lchild;       //先访问左孩子        while(temp != NULL)       //左孩子不为空        {            //将结点左孩子设为空,代表已访问其左孩子            root.lchild = NULL;            pop(&bs);                        push(&bs, root);            //左孩子入栈            root = *temp;            temp = root.lchild;            push(&bs, root);            //'0'入字符栈            charPush(&cs, '0');        }        temp = root.rchild;     //后访问右孩子             while(temp == NULL)     //右孩子为空,代表左右孩子均已访问,结点可以出栈         {            //结点出栈            root = pop(&bs);            //寻到叶子结点,可以得到结点中字符的编码            if(root.c != ' ')                traverseStack(cs, root.c);            charPop(&cs);       //字符栈出栈            if(bs.top == bs.base) break;    //根结点出栈,遍历结束            //查看上一级结点是否访问完左右孩子              root = getTop(bs);            temp = root.rchild;                   }        if(bs.top != bs.base)        {            //将结点右孩子设为空,代表已访问其右孩子            root.rchild = NULL;                   pop(&bs);            push(&bs, root);            //右孩子入栈            root = *temp;                  push(&bs, root);            //'1'入字符栈            charPush(&cs, '1');        }        }}e解码:1234567891011121314151617181920212223242526'''Cchar decode[100];   //记录解码得到的字符串//通过哈夫曼树解码void decodeHuffman(Tree T, char *code){    int cnt = 0;    Tree root;    while(*code != '\0')                  //01编码字符串读完,解码结束    {        root = T;        while(root->lchild != NULL)       //找到叶子结点        {            if(*code != '\0')            {                if(*code == '0')                    root = root->lchild;                else                    root = root->rchild;                code++;            }            else break;        }        decode[cnt] = root->c;             //叶子结点存放的字符即为解码得到的字符        cnt++;    }}f主函数:123456789101112131415161718192021222324'''Cvoid main(){    pList pl = creatList();    printf("字符的权重如下\n");    for(pList l = pl; l->next != NULL; l = l->next)        printf("字符%c的权重是 %d\n", l->root->c, l->root->weight);    Tree T = creatHuffman(pl);    encodeHuffman(T);    printf("\n\n字符编码结果如下\n");    for(int i = 0; i < 7; i++)        printf("%c : %s\n", i+'a', stack[i]);    char code[100];    printf("\n\n请输入编码:\n");    scanf("%s", code);    printf("解码结果如下:\n");    decodeHuffman(T, code);    printf("%s\n", decode);    printf("\n\n");    system("date /T");    system("TIME /T");    system("pause");    exit(0); }2.Python实现2.1代码说明a创建哈夫曼树:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#coding=gbk  import datetimeimport timefrom pip._vendor.distlib.compat import raw_input  #哈夫曼树结点类class Huffman:    def __init__(self, c, weight):        self.c = c        self.weight = weight        self.lchild = None        self.rchild = None         #创建结点左右孩子        def creat(self, lchild, rchild):        self.lchild = lchild        self.rchild = rchild  #创建列表        def creatList():    list = []    list.append(Huffman('a', 22))    list.append(Huffman('b', 5))    list.append(Huffman('c', 38))    list.append(Huffman('d', 9))    list.append(Huffman('e', 44))    list.append(Huffman('f', 12))    list.append(Huffman('g', 65))    return list  #通过列表创建哈夫曼树,返回树的根结点def creatHuffman(list):    while len(list) > 1:               #列表只剩一个结点时循环结束,此结点即为哈夫曼树的根结点        i = 0        j = 1        k = 2        while k < len(list):           #找到列表中权重最小的两个结点list1,list2                      if list[i].weight > list[k].weight or list[j].weight > list[k].weight:                if list[i].weight > list[j].weight:                    i = k                else:                    j = k            k += 1              root = Huffman(' ', list[i].weight + list[j].weight) #新结点字符统一设为空格,权重设为list1与list2权重之和           if list[i].weight < list[j].weight:                  #list1和list2设为新结点的左右孩子            root.creat(list[i], list[j])        else:            root.creat(list[j], list[i])        #list1数据域替换成新结点,并删除list2        list[i] = root        list.remove(list[j])    return list[0]b编码:12345678910111213141516171819202122232425262728293031323334353637#通过哈夫曼树编码def encodeHuffman(T):    code = [[], [], [], [], [], [], []]    #列表实现栈结构    treeStack = []    codeStack = []    treeStack.append(T)    while treeStack != []:        #栈空代表遍历结束        root = treeStack[-1]        temp = root.lchild        while temp != None:            #将结点左孩子设为空,代表已访问其左孩子            root.lchild = None                   #左孩子入栈                      treeStack.append(temp)                     root = temp            temp = root.lchild            #0入编码栈            codeStack.append(0)        temp = root.rchild            #后访问右孩子        while temp == None:           #右孩子为空,代表左右孩子均已访问,结点可以出栈            root = treeStack.pop()           #结点出栈            #寻到叶子结点,可以得到结点中字符的编码            if root.c != ' ':                codeTemp = codeStack.copy()                code[ord(root.c) - 97] = codeTemp                 if treeStack == []:    #根结点出栈,遍历结束                break            codeStack.pop()        #编码栈出栈            #查看上一级结点是否访问完左右孩子            root = treeStack[-1]            temp = root.rchild        if treeStack != []:            treeStack.append(temp)     #右孩子入栈            root.rchild = None         #将结点右孩子设为空,代表已访问其右孩子            codeStack.append(1)        #1入编码栈    return code c解码:1234567891011121314151617#通过哈夫曼树解码def decodeHuffman(T, strCode):    decode = []    index = 0    while index < len(strCode):        #01编码字符串读完,解码结束        root = T        while root.lchild != None:     #找到叶子结点            if index < len(strCode):                if strCode[index] == '0':                    root = root.lchild                else:                    root = root.rchild                index += 1            else:                break        decode.append(root.c)           #叶子结点存放的字符即为解码得到的字符    return decoded主函数:12345678910111213141516171819202122232425if __name__ == '__main__':    list = creatList()    print("字符的权重如下")    for i in range(len(list)):        print("字符{}的权重为: {}".format(chr(i+97), list[i].weight))    T = creatHuffman(list)    code = encodeHuffman(T)    print("\n字符编码结果如下")    for i in range(len(code)):        print(chr(i+97), end=' : ')        for j in range(len(code[i])):            print(code[i][j], end='')        print("")    strCode = input("\n请输入编码:\n")    #哈夫曼树在编码时被破坏,必须重建哈夫曼树    list = creatList()    T = creatHuffman(list)    decode = decodeHuffman(T, strCode)    print("解码结果如下:")    for i in range(len(decode)):        print(decode[i], end='')    print("\n\n")    datetime = datetime.datetime.now()    print(datetime.strftime("%Y-%m-%d\n%H:%M:%S"))    input("Press Enter to exit…")
  • [技术干货] Python中闭包与lambda的作用域解析【转】
    目录Python闭包与lambda的作用域python当中的坑(闭包与lambda)先来看一个栗子Python闭包与lambda的作用域lambda写法123456def fun():    for i in range(3):        yield lambda x : x * i f0, f1, f2 = fun()print(f0(1), f1(2), f2(3))闭包的写法123456789def fun():    result = []    for i in range(3):        def demo(x):            return x * i        result.append(demo)    return resultf0, f1, f2 = fun()print(f0(1), f1(2), f2(3))上面两种写法的结果都是2,4,6,按最初的想法结果应该是0,2,6。问题原因:问题的本事在python的变量查找规则,LEGB(local,enclousing,global,bulitin),上面的例子中,i就是在闭包作用域(enclousing),而Python的闭包是迟绑定,闭包中用到的变量i的值,是在内部函数被调用时查找到的。解决办法将闭包作用域变为局部作用域lambda写法123456def fun():    for i in range(3):        yield lambda x, i = i: x * i f0, f1, f2 = fun()print(f0(1), f1(2), f2(3))闭包写法123456789def fun():    result = []    for i in range(3):        def demo(x, i=i):            return x * i        result.append(demo)    return resultf0, f1, f2 = fun()print(f0(1), f1(2), f2(3))以上输出结果0,2,6另一种情况:12345def fun():    for i in range(3):        yield lambda x : x * if0, f1, f2 = fun()print(f0(1), f1(2), f2(3))输出结果还是2,4,6问题原因fun()方法返回的生成器(或迭代器),并没有真正的执行,而是在每次调用的时候进行执行。在遍历后执行打印时,i这个变量使用的是最后调用的值。将lambda看作闭包方法的话,变量i值还是闭包作用域(no local)python当中的坑(闭包与lambda)先来看一个栗子12345def create():    return [lambda x:i*x for i in range(5)]  for i in create():    print(i(2))结果:88888create函数的返回值时一个列表,列表的每一个元素都是一个函数 -- 将输入参数x乘以一个倍数i的函数。预期的结果时0,2,4,6,8. 但结果是5个8,意外不意外。由于出现这个陷阱的时候经常使用了lambda,所以可能会认为是lambda的问题,但lambda表示不愿意背这个锅。问题的本质在与python中的属性查找规则,LEGB(local,enclousing,global,bulitin),在上面的例子中,i就是在闭包作用域(enclousing),而Python的闭包是 迟绑定 , 这意味着闭包中用到的变量的值,是在内部函数被调用时查询得到的解决办法也很简单,那就是变闭包作用域为局部作用域。12345def create():    return [lambda x, i=i:i*x for i in range(5)]  for i in create():    print(i(2))换种写法:12345678910def create():    a = []    for i in range(5):        def demo(x, i=i):            return x*i        a.append(demo)    return a  for i in create():    print(i(2))以上两种写法是一样的结果:02468再来一个问题相似的栗子代码很简单:(声明:python3问题)1234nums = range(2,20)for i in nums:    nums = filter(lambda x: x==i or x%i, nums)print(list(nums))结果:[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]同样按照正常的逻辑结果应该为:[2, 3, 5, 7, 11, 13, 17, 19]问题产生的原因:在python3当中filter()函数返回的是一个迭代器,因此并没有做真正的执行,而是在每次调用的时候执行(python2中filter()返回的值列表,无此现象)在遍历后执行打印时,现在执行循环当中的函数,同上面一个栗子的问题,i这个变量使用的是最后调用时的值,与以上栗子不同的是以上栗子用的是内嵌作用域的值,而这个栗子用的是全局i的值修改代码:1234nums = range(2,20)for i in nums:    nums = filter(lambda x,i=i: x==i or x%i, nums)print(list(nums))结果:[2, 3, 5, 7, 11, 13, 17, 19]
  • [技术干货] Python实现读取HTML表格 pd.read_html()【转】
    目录Python读取HTML表格pd.read_html读取数据不完整问题解决办法Python读取HTML表格数据部门提供的数据是xls格式的文件,但是执行读取xls文件的脚本报错。xlrd报错:xlrd.biffh.XLRDError: Unsupported format, or corrupt file: Expected BOF record; found b'<html xm'读取xlrd的脚本123456789101112131415161718data_lines = read_excel_file(self.file_path) def read_excel_file(file_path):    """    读取excel文件    """    import xlrd    print('[Info] excel file: {}'.format(file_path))    book = xlrd.open_workbook(file_path)    sheet = book.sheet_by_index(0)    data_lines = []    for row in range(0, sheet.nrows):        line_data = []        for column in range(0, sheet.ncols):            val = sheet.cell(row, column).value            line_data.append(val)        data_lines.append(line_data)    return data_lines  # 二维数组原因是文件格式是HTML表格,参考python xlrd unsupported format, or corrupt file.使用pandas的read_html读取文件,同时替换nan为空字符,数据格式保持一致。1234567891011121314151617def read_html_table(file_path):    """    读取html表格    """    import pandas as pd    pd_table = pd.read_html(file_path)    df = pd_table[0]    # num_col = df.shape[1]    # num_row = df.shape[0]    df_data = df.values.tolist()    df_data = df_data[1:]    for r_idx, row in enumerate(df_data):        for c_idx, value in enumerate(row):            # 判断nan,参考https://stackoverflow.com/questions/944700/how-can-i-check-for-nan-values            if value != value:                df_data[r_idx][c_idx] = ""    return df_data读取问题解决。pd.read_html读取数据不完整问题问题:有一个较大的表格数据存在了html中,打算用read_html直接取出来这部分数据,但后来发现read_html读取的数据不完整,后来检查html的table都没有任何问题解决办法pd.read_html的默认解析器为 'lxml' ,添加参数flavor='bs4'便可解决
  • [技术干货] Python轻量级搜索工具Whoosh的使用教程【转】
    目录Whoosh简介Index & query示例代码数据字段创建索引文件查询本文将简单介绍Python中的一个轻量级搜索工具Whoosh,并给出相应的使用示例代码。Whoosh简介Whoosh由Matt Chaput创建,它一开始是一个为Houdini 3D动画软件包的在线文档提供简单、快速的搜索服务工具,之后便慢慢成为一个成熟的搜索解决工具并已开源。Whoosh纯由Python编写而成,是一个灵活的,方便的,轻量级的搜索引擎工具,现在同时支持Python2、3,其优点如下:Whoosh纯由Python编写而成,但很快,只需要Python环境即可,不需要编译器;默认使用 Okapi BM25F排序算法,也支持其他排序算法;相比于其他搜索引擎,Whoosh会创建更小的index文件;Whoosh中的index文件编码必须是unicode;Whoosh可以储存任意的Python对象。Whoosh的官方介绍网站为:https://whoosh.readthedocs.io/en/latest/intro.html。相比于ElasticSearch或者Solr等成熟的搜索引擎工具,Whoosh显得更轻便,操作更简单,可以考虑在小型的搜索项目中使用。Index & query对于熟悉ES的人来说,搜索的两个重要的方面为mapping和query,也就是索引的构建以及查询,背后是复杂的索引储存、query解析以及排序算法等。如果你有ES方面的经验,那么,对于Whoosh是十分容易上手的。按照笔者的理解以及Whoosh的官方文档,Whoosh的入门使用主要是index以及query。搜索引擎的强大功能之一在于它能够提供全文检索,这依赖于排序算法,比如BM25,也依赖于我们怎样储存字段。因此,index作为名词时,是指字段的索引,index作为动词时,是指建立字段的索引。而query会将我们需要查询的语句,通过排序算法,给出合理的搜索结果。关于Whoosh的使用,在官文文档中已经给出了详细的说明,笔者在这里只给出一个简单的例子,来说明Whoosh如何能方便地提升我们的搜索体验。示例代码数据本项目的示例数据为poem.csv,下图为该数据集的前十行:poem.csv字段根据数据集的特征,我们创建四个字段(fields):title, dynasty, poet, content。创建的代码如下:12345678910111213# -*- coding: utf-8 -*-import osfrom whoosh.index import create_infrom whoosh.fields import *from jieba.analyse import ChineseAnalyzerimport json # 创建schema, stored为True表示能够被检索schema = Schema(title=TEXT(stored=True, analyzer=ChineseAnalyzer()),                dynasty=ID(stored=True),                poet=ID(stored=True),                content=TEXT(stored=True, analyzer=ChineseAnalyzer())                )其中,ID只能为一个单元值,不能分割为若干个词,常用于文件路径、URL、日期、分类;TEXT文件的文本内容,建立文本的索引并存储,支持词汇搜索;Analyzer选择结巴中文分词器。创建索引文件接着,我们需要创建索引文件。我们利用程序先解析poem.csv文件,并将它转化为index,写入到indexdir目录下。Python代码如下:12345678910111213141516# 解析poem.csv文件with open('poem.csv', 'r', encoding='utf-8') as f:    texts = [_.strip().split(',') for _ in f.readlines() if len(_.strip().split(',')) == 4] # 存储schema信息至indexdir目录indexdir = 'indexdir/'if not os.path.exists(indexdir):    os.mkdir(indexdir)ix = create_in(indexdir, schema) # 按照schema定义信息,增加需要建立索引的文档writer = ix.writer()for i in range(1, len(texts)):    title, dynasty, poet, content = texts[i]    writer.add_document(title=title, dynasty=dynasty, poet=poet, content=content)writer.commit()index创建成功后,会生成indexdir目录,里面含有上述poem.csv数据的各个字段的索引文件。查询index创建成功后,我们就利用进行查询。比如我们想要查询content中含有明月的诗句,可以输入以下代码:12345678# 创建一个检索器searcher = ix.searcher() # 检索content中出现'明月'的文档results = searcher.find("content", "明月")print('一共发现%d份文档。' % len(results))for i in range(min(10, len(results))):    print(json.dumps(results[i].fields(), ensure_ascii=False))输出结果如下:一共发现44份文档。前10份文档如下:{"content": "床前明月光,疑是地上霜。举头望明月,低头思故乡。", "dynasty": "唐代", "poet": "李白 ", "title": "静夜思"}{"content": "边草,边草,边草尽来兵老。山南山北雪晴,千里万里月明。明月,明月,胡笳一声愁绝。", "dynasty": "唐代", "poet": "戴叔伦 ", "title": "调笑令·边草"}{"content": "独坐幽篁里,弹琴复长啸。深林人不知,明月来相照。", "dynasty": "唐代", "poet": "王维 ", "title": "竹里馆"}{"content": "汉江明月照归人,万里秋风一叶身。休把客衣轻浣濯,此中犹有帝京尘。", "dynasty": "明代", "poet": "边贡 ", "title": "重赠吴国宾"}{"content": "秦时明月汉时关,万里长征人未还。但使龙城飞将在,不教胡马度阴山。", "dynasty": "唐代", "poet": "王昌龄 ", "title": "出塞二首·其一"}{"content": "京口瓜洲一水间,钟山只隔数重山。春风又绿江南岸,明月何时照我还?", "dynasty": "宋代", "poet": "王安石 ", "title": "泊船瓜洲"}{"content": "四顾山光接水光,凭栏十里芰荷香。清风明月无人管,并作南楼一味凉。", "dynasty": "宋代", "poet": "黄庭坚 ", "title": "鄂州南楼书事"}{"content": "青山隐隐水迢迢,秋尽江南草未凋。二十四桥明月夜,玉人何处教吹箫?", "dynasty": "唐代", "poet": "杜牧 ", "title": "寄扬州韩绰判官"}{"content": "露气寒光集,微阳下楚丘。猿啼洞庭树,人在木兰舟。广泽生明月,苍山夹乱流。云中君不见,竟夕自悲秋。", "dynasty": "唐代", "poet": "马戴 ", "title": "楚江怀古三首·其一"}{"content": "海上生明月,天涯共此时。情人怨遥夜,竟夕起相思。灭烛怜光满,披衣觉露滋。不堪盈手赠,还寝梦佳期。", "dynasty": "唐代", "poet": "张九龄 ", "title": "望月怀远 / 望月怀古"}
  • [技术干货] Python实现邮件自动下载的示例详解【转】
    开始码代码之前,我们先来了解一下三种邮件服务协议:1、SMTP协议SMTP(Simple Mail Transfer Protocol),即简单邮件传输协议。相当于中转站,将邮件发送到客户端。2、POP3协议POP3(Post Office Protocol 3),即邮局协议的第3个版本,是电子邮件的第一个离线协议标准。该协议把邮件下载到本地计算机,不与服务器同步,缺点是更易丢失邮件或多次下载相同的邮件。3、IMAP协议IMAP(Internet Mail Access Protocol),即交互式邮件存取协议。该协议连接远程邮箱直接操作,与服务器内容同步。然后介绍一下email包这个包的中心组件是代表电子邮件消息的“对象模型”。 应用程序主要通过在 message 子模块中定义的对象模型接口与这个包进行交互。 应用程序可以使用此 API 来询问有关现有电子邮件的问题、构造新的电子邮件,或者添加或移除自身也使用相同对象模型接口的电子邮件子组件。 也就是说,遵循电子邮件消息及其 MIME 子组件的性质,电子邮件对象模型是所有提供 EmailMessage API 的对象所构成的树状结构。接下来我们通过具体的代码实现一个登录邮箱客户端,下载邮件,解析邮件附件内容的功能。首先我们需要定义一个邮件解析的类,该类需要三个变量:1、邮箱所属的imap服务地址;2、邮箱账号;3、邮箱密码【注:不同邮箱需要不同的安全策略,例如qq邮箱需要短信验证,获取登录授权码,而不是明文密码去登录远程客户端】123456789class Email_parse:     def __init__(self,remote_server_url,email_url,password):        # imap服务地址        self.remote_server_url = remote_server_url        # 邮箱账号        self.email_url = email_url        # 邮箱密码        self.password = password然后定义类中入口函数,登录远程,默认获取第一页所有的邮件。我们获取邮件的主题,并打印出来【不同邮件主题的编码可能不同,二进制需要转码才能正确显示】12345678910111213141516171819202122def main_parse_Email(self):    """入口函数,登录imap服务"""    server = imaplib.IMAP4_SSL(self.remote_server_url, 993)    server.login(self.email_url, self.password)    server.select('INBOX')    status,data = server.search(None,"ALL")    if status != 'OK':        raise Exception('read email error')    emailids = data[0].split()    mail_counts = len(emailids)    print("count:",mail_counts)    # 邮件的遍历是按时间从后往前,这里我们选择最新的一封邮件    for i in range(mail_counts - 1, mail_counts - 2, -1):        status, edata = server.fetch(emailids[i], '(RFC822)')        msg = email.message_from_bytes(edata[0][1])        #获取邮件主题title        subject = email.header.decode_header(msg.get('subject'))        if type(subject[-1][0]) == bytes:            title = subject[-1][0].decode(str(subject[-1][1]))        elif type(subject[-1][0]) == str:            title = subject[-1][0]        print("title:", title)其中,msg变量保存的就是邮件的主体,接下来因为会重复用到msg和tilte,我们将构造一个类函数返回msg和title。12345678def get_email_title(msg):    subject = email.header.decode_header(msg.get('subject'))    if type(subject[-1][0]) == bytes:        title = subject[-1][0].decode(str(subject[-1][1]))    elif type(subject[-1][0]) == str:        title = subject[-1][0]    print("title:", title)    return title解析邮件,我们分为两部分,邮件正文【HTML】和附件【xlsx等】,判断有附件,我们就保存到固定的路径下。表格的解析不再赘述了,pandas之类的包足以搞定。12345678910111213def get_att(msg):    """获取附件并下载"""    filename = Email_parse.get_email_name(msg)    for part in msg.walk():        file_name = part.get_param("name")        if file_name:            data = part.get_payload(decode=True)            if data != None:                att_file = open('./src/' + filename, 'wb')                att_file.write(data)                att_file.close()            else:                pass邮件正文内容,我们直接解析html,将文本内容直接保存到.txt文件中,方便读取。12345678910111213def get_text_from_HTML(msg):    """获取邮件中的html"""    filename = Email_parse.get_email_name(msg)    current_title = Email_parse.get_email_title(msg)    print("filename:",filename,type(filename))    for part in msg.walk():        if not part.is_multipart():            result = part.get_payload(decode=True)            result = result.decode('gbk')            f = open(f'./src/{current_title}.txt','w')            f.write(result)            f.close()            return result完整代码如下:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293import emailimport imaplibfrom email.header import decode_headerimport pandas as pdimport datetime  class Email_parse:    def __init__(self,remote_server_url,email_url,password):        self.remote_server_url = remote_server_url        self.email_url = email_url        self.password = password     def get_att(msg):        filename = Email_parse.get_email_name(msg)        for part in msg.walk():            file_name = part.get_param("name")            if file_name:                data = part.get_payload(decode=True)                if data != None:                    att_file = open('./src/' + filename, 'wb')                    att_file.write(data)                    att_file.close()                else:                    pass     def get_email_title(msg):        subject = email.header.decode_header(msg.get('subject'))        if type(subject[-1][0]) == bytes:            title = subject[-1][0].decode(str(subject[-1][1]))        elif type(subject[-1][0]) == str:            title = subject[-1][0]        print("title:", title)        return title      def get_email_name(msg):        for part in msg.walk():            file_name = part.get_param("name")            if file_name:                h = email.header.Header(file_name)                dh = email.header.decode_header(h)                filename = dh[0][0]                if dh[0][1]:                    value, charset = decode_header(str(filename, dh[0][1]))[0]                    if charset:                        filename = value.decode(charset)                        print("附件名称:", filename)                        return filename      def main_parse_Email(self):        server = imaplib.IMAP4_SSL(self.remote_server_url, 993)        server.login(self.email_url, self.password)        server.select('INBOX')        status,data = server.search(None,"ALL")        if status != 'OK':            raise Exception('read email error')        emailids = data[0].split()        mail_counts = len(emailids)        print("count:",mail_counts)        for i in range(mail_counts - 1, mail_counts - 2, -1):            status, edata = server.fetch(emailids[i], '(RFC822)')            msg = email.message_from_bytes(edata[0][1])            subject = email.header.decode_header(msg.get('subject'))            if type(subject[-1][0]) == bytes:                title = subject[-1][0].decode(str(subject[-1][1]))            elif type(subject[-1][0]) == str:                title = subject[-1][0]            print("title:", title)            Email_parse.get_att(msg)            Email_parse.get_text_from_HTML(msg)      def get_text_from_HTML(msg):        filename = Email_parse.get_email_name(msg)        current_title = Email_parse.get_email_title(msg)        print("filename:",filename,type(filename))        for part in msg.walk():            if not part.is_multipart():                result = part.get_payload(decode=True)                result = result.decode('gbk')                f = open(f'./src/{current_title}.txt','w')                f.write(result)                f.close()                return result if __name__ == "__main__":    remote_server_url = 'imap.qq.com'    email_url = "*********@qq.com"    password = "**********"    demo = Email_parse(remote_server_url,email_url,password)    demo.main_parse_Email()
  • [技术干货] python+selenium 实现扫码免密登录示例代码【转】
    目录一、需求描述二、首先扫码登录获取cookies保存到本地未后面免密登录做准备三、初始化浏览器四、获取本地保存的cookies实现登录一、需求描述web 自动化测试/python爬虫往往会遇到扫码登录的情况,不是所有的网站都支持用户密码登录,遇到这种扫码登录的情况会阻碍我们自动化测试/爬虫的进行,所有为了可以顺畅的进行我们的需求,在进行自动化或者爬虫前我们首先要解决扫码登录的问题,下面讲解如何实现扫码免密登录。二、首先扫码登录获取cookies保存到本地未后面免密登录做准备具体实现代码如下:12345678910111213141516def get_cookies():    # 首先获取cookies保存至本地    driver = webdriver.Chrome('/Users/xxx/Documents/python/pack/chromedriver/chromedriver')    page_url = 'https:/xxx/38268919/bugtrace/buglists/query/1/created/desc?query_token=20220706291334df28c613a85aa589f04376d034'    driver.get(page_url)    driver.maximize_window()    # 进行扫码登录    time.sleep(20)    # 获取列表形式的cookies    cookies = driver.get_cookies()    # 转换成字符串保存    jsonCookie = json.dumps(cookies)    # 保存到txt文件    with open('cookies.txt','w') as f:        f.write(jsonCookie)    print('cookies保存成功!')三、初始化浏览器123456def browser_initial():    driver = webdriver.Chrome('/Users/xxx/Documents/python/pack/chromedriver/chromedriver')    page_url = 'https://xxx/38268919/bugtrace/buglists/query/1/created/desc?query_token=20220706291334df28c613a85aa589f04376d034'    driver.get(page_url)    driver.maximize_window()    return driver四、获取本地保存的cookies实现登录代码实现如下123456789101112131415161718def login(driver):    with open('cookies.txt','r',encoding='utf8') as f:        cookies = json.loads(f.read())    # 给浏览器添加cookies    for cookie in cookies:        cookie_dict = {            'domain': '.tapd.cn',            'name': cookie.get('name'),            'value': cookie.get('value'),            "expiry": 1658386136,            'path': '/',            'httpOnly': True,            'Secure': True        }        # print(cookie_dict)        driver.add_cookie(cookie_dict)    # 刷新网页,cookies才会成功    driver.refresh()12345if __name__ =='__main__': driver=browser_initial() login(driver)
  • [技术干货] 利用Python自动生成PPT的示例详解【转】
    在日常工作中,PPT制作是常见的工作,如果制作创意类PPT,则无法通过自动化的形式生成,因为创意本身具有随机性,而自动化解决的是重复性工作,两者有所冲突。python-pptx是python处理PPT的一个库,注重的是读和写,无法导出,没有渲染功能。废话不多说,第一步,安装python-pptx库:1pip3 install -i https://pypi.doubanio.com/simple/ python-pptxppt里面处理的主要对象一般为文本框,表格,图片。每一页的ppt为一个slide123456from pptx import Presentation, utilfrom pptx.util import Pt,Cmfrom pptx.shapes.picture import Picture#实例化一个ppt对象ppt = Presentation("./test.pptx")slide = ppt.slides[0] #第几页然后遍历查看这一页ppt中都包含哪些对象:123456789101112131415161718192021222324252627282930313233def rander_template(slide):    for shape in slide.shapes:        if shape.has_text_frame == True:            print("==========================文本框=============================")            print("段落长度:",len(shape.text_frame.paragraphs))            for paragraph in shape.text_frame.paragraphs:                # 拼接文字                print("段落包含字段:",len(paragraph.runs))                print(''.join(run.text for run in paragraph.runs))                for i in range(len(paragraph.runs)):                    print("run"+str(i)+":"+paragraph.runs[i].text)            print(shape.text_frame.paragraphs[0].runs[0].text)            shape.text_frame.paragraphs[0].runs[0].text = "规则是自由的第一要义"        elif shape.has_table == True:            print("==========================表格==============================")            one_table_data = []            for row in shape.table.rows:  # 读每行                row_data = []                for cell in row.cells:  # 读一行中的所有单元格                    cell.text = cell.text if cell.text != "" else "未填写"                    c = cell.text                    row_data.append(c)                one_table_data.append(row_data)  # 把每一行存入表            # 用二维列表输出表格行和列的数据            print(one_table_data)            print("第一个单元格内容:",shape.table.rows[0].cells[0].text)         elif isinstance(shape,Picture):            print("==========================图片==============================")            index = 0            with open(f'{index}.jpg','wb') as f:                f.write(shape.image.blob)                index += 1文本框对象【text_frame】:shape.has_text_frame查看是否有文本框对象,有的话查看具体有几个段落【len(shape.text_frame.paragraphs)】,每个段落又有多少个run对象【len(paragraph.runs)】注意:修改run对象的时候,修改run[0],后面的值都会被覆盖。表格对象【table】:table对象还是按照行列值来定位划分的,eg:table.rows[2]cells[3].text代表第三行第四列的值图片对象【Picture】:插入图片需要固定图片的位置,比如:def insert_pic(slide):    #需要用到pptx库的util方法    img_path = './blue.png'  # 图片路径    # 设置图片的位置和大小    left = util.Cm(8.04)    top = util.Cm(9.93)    width = util.Cm(15.07)    height = util.Cm(4.06)    # 在页面中插入图片    slide.shapes.add_picture(img_path, left, top, width, height)全部代码:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354from pptx import Presentation, utilfrom pptx.util import Pt,Cmfrom pptx.shapes.picture import Pictureppt = Presentation("./test.pptx") def rander_template(slide):    for shape in slide.shapes:        if shape.has_text_frame == True:            print("==========================文本框=============================")            print("段落长度:",len(shape.text_frame.paragraphs))            for paragraph in shape.text_frame.paragraphs:                # 拼接文字                print("段落包含字段:",len(paragraph.runs))                print(''.join(run.text for run in paragraph.runs))                for i in range(len(paragraph.runs)):                    print("run"+str(i)+":"+paragraph.runs[i].text)            print(shape.text_frame.paragraphs[0].runs[0].text)            shape.text_frame.paragraphs[0].runs[0].text = "规则是自由的第一要义"        elif shape.has_table == True:            print("==========================表格==============================")            one_table_data = []            for row in shape.table.rows:  # 读每行                row_data = []                for cell in row.cells:  # 读一行中的所有单元格                    cell.text = cell.text if cell.text != "" else "未填写"                    c = cell.text                    row_data.append(c)                one_table_data.append(row_data)  # 把每一行存入表            # 用二维列表输出表格行和列的数据            print(one_table_data)            print("第一个单元格内容:",shape.table.rows[0].cells[0].text)         elif isinstance(shape,Picture):            print("==========================图片==============================")            index = 0            with open(f'{index}.jpg','wb') as f:                f.write(shape.image.blob)                index += 1def insert_pic(slide):    img_path = './blue.png'  # 图片路径    # 设置图片的位置和大小    left = util.Cm(8.04)    top = util.Cm(9.93)    width = util.Cm(15.07)    height = util.Cm(4.06)    # 在页面中插入图片    slide.shapes.add_picture(img_path, left, top, width, height)  if __name__ == "__main__":    slide = ppt.slides[0] #第几页    rander_template(slide)    insert_pic(slide)    ppt.save('new.pptx')  # 保存为文件
  • [技术干货] Python的flask常用函数route()【转】
    目录一、route()路由概述二、静态路由和动态路径方式1:静态路由方式2:动态路由三、route()其它参数1.methods=[‘GET’,‘POST’]一、route()路由概述功能:将URL绑定到函数路由函数route()的调用有两种方式:静态路由和动态路由二、静态路由和动态路径方式1:静态路由@app.route(“/xxx”) xxx为静态路径 如::/index / /base等,可以返回一个值、字符串、页面等12345678910111213from flask import Flaskapp = Flask(__name__) @app.route('/hello')def hello_world():    return 'Hello World!!!'     @app.route('/pro')def index():    return render_template('login.html') if __name__ == '__main__':    app.run(debug = True)方式2:动态路由采用<>进行动态url的传递@app.route(“/”),这里xxx为不确定的路径。123456789from flask import Flaskapp = Flask(__name__) @app.route('/hello/<name>')def hello_name(name):   return 'Hello %s!' % name if __name__ == '__main__':   app.run(debug = True)如果浏览器地址栏输入:http:// localhost:5000/hello/w3cschool则会在页面显示:Hello w3cschool!三、route()其它参数1.methods=[‘GET’,‘POST’]当前视图函数支持的请求方式,不设置默认为GET请求方式不区分大小写methods=[‘GET’] 支持的请求方法为GETmethods=[‘POST’] 支持的请求方法为POSTmethods=[‘GET’,‘POST’] 支持的请求方法为POST GET123456789101112@app.route('/login', methods=['GET', 'POST'])  # 请求参数设置不区分大小写,源码中自动进行了upperdef login():    if request.method == 'GET':        return render_template('login.html')    elif request.method == 'POST':        username = request.form.get('username')        pwd = request.form.get('pwd')        if username == 'yang' and pwd == '123456':            session['username'] = username            return 'login successed 200  ok!'        else:            return 'login failed!!!'
  • [技术干货] Python实现不写硬盘上传文件【转】
    目录引言文本类型二进制类型使用 requests 把字符串按照文件上传引言你写了一个 api,接受 client 上传的文件,然后在上传到 oss,你会怎么做?先写硬盘,然后在上传到 oss?太笨了!你写了一个截图服务,截到的图要上传到 oss,你会怎么做?先写硬盘,在上传到 oss?太笨了!这篇文章教你重新做人!文本类型使用 io.StringIO1234567import iofrom loguru import loggerfile_like_obj = io.StringIO("hahaha")logger.debug(file_like_obj)logger.debug(type(file_like_obj))logger.debug(getattr(file_like_obj,'read'))logger.debug(file_like_obj.read())输出结果如下:2022-07-11 21:23:51.206 | DEBUG    | __main__:<module>:8 - <_io.StringIO object at 0x100323eb0>2022-07-11 21:23:51.206 | DEBUG    | __main__:<module>:9 - <class '_io.StringIO'>2022-07-11 21:23:51.206 | DEBUG    | __main__:<module>:10 - <built-in method read of _io.StringIO object at 0x100323eb0>2022-07-11 21:23:51.206 | DEBUG    | __main__:<module>:11 - hahaha再看一个熟悉的:12345import iofrom loguru import loggerfile_like_obj = io.StringIO("hahaha")with file_like_obj as f:    logger.debug(f.read())输出结果如下:2022-07-11 21:35:04.620 | DEBUG    | __main__:<module>:9 - hahaha二进制类型使用 io.BytesIO使用 requests 把字符串按照文件上传下面的代码是标准的上传文件的代码:123456import requestsimport ioresponse = requests.post('http://localhost:5000/', files={    'file': open('纵观人类文明史.txt', 'r', encoding='utf-8')})print(response.text)但是我想把 open('纵观人类文明史.txt', 'r', encoding='utf-8') 替换为字符串。为此我想到的办法是把字符串先写到硬盘上,在按照上面的代码上传,可是这太低效率(硬盘太低效率),平白无故多了两次硬盘读写操作,我想直接把内存中的字符串按照文件上传有什么办法吗? 参考文档https://docs.python.org/zh-cn/3/library/io.html#io.StringIO
  • [技术干货] Python使用Flask Migrate模块迁移数据库【转】
    前言安装初始化项目结构三大命令前言本篇文章,阐述一下Flask中数据库的迁移为什么要说数据库迁移呢?比如我们以前有一个数据库,里面的信息有 id, name现在我想再加一个gender属性,应该怎么办呢?不可能直接把数据库删除掉吧。然后重新创建一个,因此本文介绍一种,通过数据库迁移的方法,可以保留原始的数据,并完成添加新的一列的方法。安装直接使用pip install flask-migrate即可初始化项目结构123# exts.pyfrom flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()老样子,我们还是在exts.py中,初始我们的数据库,这里时放Flask扩展的地方。1234567# models.pyfrom exts import dbclass User(db.Model):    id = db.Column(db.Integer,primary_key=True)    username = db.Column(db.String(80),unique=True)    def __repr__(self):        return '<User %s>' % self.usernamemodels.py是放模型的地方。1234567891011# app.pyfrom flask import Flaskfrom exts import dbfrom flask_migrate import Migrateapp = Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///foo.db"# !!!绑定app和数据库db.init_app(app)migrate = Migrate(app,db)if __name__ == '__main__':    app.run()主程序,我们在!!!进行初始化数据库迁移的migrate 对象。初始化之后我们可以执行下面的三大命令去创建我们的数据库。就不需要db.create_all()命令啦。三大命令# (1)初始化flask db init# 把当前的模型添加到迁移文件flask db migrate# 将映射文件真正的映射到数据库中flask db upgrade(1)新建一个名字为migrations的文件夹,并且记录一个数据库版本号其他问题:如果报错[flask_migrate] Error: Can‘t locate revision identified by '409392ed6301'答:db revision --rev-id 409392ed6301 409392ed6301是问题的版本号Error: While importing ‘app’, an ImportError was raised.答:请先去运行一下app.py,确保app.py可以运行Error: Could not locate a Flask application. You did not provide the “FLASK_APP” environment variable, and a “wsgi.py” or “app.py” module was not found in the current directory. 答:请确保执行命令时的文件夹中有app.py文件
  • [技术干货] Python处理时间戳和时间计算等的脚本分享【转】
    由于实际需要,简要写了个小脚本,并打包生成exe,供无网络环境下使用脚本1:显示当前时间与时间戳,以及10分钟后的时间与时间戳123456789# -*- coding: utf-8 -*- """Project: pyWorkspaceCreator: Administrator -haochuangCreate time: 2021-05-12 09:24IDE: PyCharmIntroduction: """12345678910111213141516171819202122232425262728293031323334353637import timeimport datetime  t=datetime.datetime.now() #当前日期t1 =t.strftime('%Y-%m-%d %H:%M:%S')#转为秒级时间戳ts1=time.mktime(time.strptime(t1, '%Y-%m-%d %H:%M:%S'))#转为毫秒级end_time=int(str(ts1*1000).split(".")[0])  #10分钟后t2 = (t+datetime.timedelta(minutes=10)).strftime("%Y-%m-%d %H:%M:%S")# t2=(t-datetime.timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S")#转为秒级时间戳ts2=time.mktime(time.strptime(t2, '%Y-%m-%d %H:%M:%S'))#转为毫秒级start_time=int(str(ts2*1000).split(".")[0]) #print("\n","*"*30)print("\n")print("*"*30)print("当前时间戳:")print(start_time)print("当前时间:")print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts2)))print("*"*30,"\n") print("10分钟后的时间戳:")print(end_time)print("10分钟后的时间:")print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts1))) print("*"*30,"\n")脚本2:显示当前时间与时间戳,以及10分钟后的时间与时间戳,允许根据输入的指定时间,生成多久之后的时间戳123456789# -*- coding: utf-8 -*- """Project: pyWorkspaceCreator: Administrator -haochuangCreate time: 2021-05-12 09:24IDE: PyCharmIntroduction: """1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950import timeimport datetime  t=datetime.datetime.now() #当前日期t1 =t.strftime('%Y-%m-%d %H:%M:%S')#转为秒级时间戳ts1=time.mktime(time.strptime(t1, '%Y-%m-%d %H:%M:%S'))#转为毫秒级end_time=int(str(ts1*1000).split(".")[0])  #10分钟后t2 = (t+datetime.timedelta(minutes=10)).strftime("%Y-%m-%d %H:%M:%S")# t2=(t-datetime.timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S")#转为秒级时间戳ts2=time.mktime(time.strptime(t2, '%Y-%m-%d %H:%M:%S'))#转为毫秒级start_time=int(str(ts2*1000).split(".")[0]) #print("\n","*"*30)print("\n")print("*"*30)print("当前时间戳:")print(start_time)print("当前时间:")print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts2)))print("*"*30,"\n") # 10分钟后的时间戳print("10 分钟后的时间戳:")print(end_time)print("10 分钟后的时间:")print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts1)))print("*"*30,"\n") # 用户自定义时间time_user = input("需要多少分钟后的时间戳(请输入正确int类型数值):")t3 = (t+datetime.timedelta(minutes=int(time_user))).strftime("%Y-%m-%d %H:%M:%S")ts3=time.mktime(time.strptime(t3, '%Y-%m-%d %H:%M:%S'))#转为毫秒级start_time=int(str(ts3*1000).split(".")[0]) print(time_user + " 分钟后的时间戳:")print(end_time)print(time_user + " 分钟后的时间:")print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts3)))print("*"*30,"\n")脚本3:显示部分时间与时间戳等1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556# -*- coding: utf-8 -*- """Project: pyWorkspaceCreator: Administrator -haochuangCreate time: 2021-05-12 09:24IDE: PyCharmIntroduction: """import timeimport datetimefrom datetime import timezonefrom datetime import timedelta # 显示当前秒级时间戳与毫秒级时间戳、微秒级时间戳t = time.time()#print(t)  # 原始时间数据#print(int(t))  # 秒级时间戳#print(int(round(t * 1000)))  # 毫秒级时间戳#print(int(round(t * 1000000)))  # 微秒级时间戳  # 显示当前日期:dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')dt_ms = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') # 含微秒的日期时间,来源 比特量化print("当前日期(s):     " + dt)print("当前日期(ms):    " + dt_ms)  # 将日期转为秒级时间戳#dtt = '2018-01-01 10:40:30'#dtts = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))#ts_ms = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))t=datetime.datetime.now()print("当前时间戳(s):    " + t)print("当前时间戳(ms):   " + (int(round(t * 1000))))  # 国际标准时间print("国际标准时间:    "+time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()))# 本地时间print("本地当前时间:    "+time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) # 将当前日期转为秒级时间戳dt = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())dt_ts = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))print("当前时间:        " + dt)print("当前时间戳:      " + dt_ts) # 将获取十分钟后的秒级时间戳#dt_10 = int((datetime.datetime.now()+datetime.timedelta(minutes=10)).strftime("%Y-%m-%d %H:%M:%S"))#ts_10 = int(time.mktime(time.strptime(dt_10, "%Y-%m-%d %H:%M:%S")))after10 = (datetime.datetime.now()+datetime.timedelta(minutes=10)).strftime("%Y-%m-%d %H:%M:%S")after10_ts = int(time.mktime(time.strptime(t1,after10)))print("10分钟后的时间:   " + after10)print("10分钟后的时间戳: "脚本4:显示部分时间与时间戳等123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133# -*- coding: utf-8 -*- """Project: pyWorkspaceCreator: Administrator -haochuangCreate time: 2021-05-12 09:08IDE: PyCharmIntroduction: """ import datetimeimport time print('*'*30 +"获取时间方式")#获取当前时间:Thu Nov 03 16:40:00 2016print(time.strftime("%a %b %d %H:%M:%S %Y", time.localtime())) #获取当前时间:2016-11-03 16:40:00print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) #获取年,月,日:2016-11-03print(datetime.date.today()) #获取当前时间:2016-11-03 16:43:14.550000print(datetime.datetime.now()) #不加参数是00:00,参数days=1表示一天:1 day, 0:00:00print(datetime.timedelta(days=1)) #获取昨天日期:2016-11-02nowtime=datetime.date.today()oldtime=datetime.timedelta(days=1)print(nowtime-oldtime) #获取昨天的精确日期oldtime=datetime.timedelta(days=1)print (datetime.datetime.now() - oldtime) print ('*'*30 + 'python时间处理之time模块') import time# 返回时间戳# print(time.time()) # 返回当前时间print(time.ctime()) # 返回一天前的时间print(time.ctime(time.time()-86400)) # 函数返回time.struct_time类型的对象time_obj = time.gmtime()print(time_obj)#结果:time.struct_time(tm_year=2016, tm_mon=7, tm_mday=27, tm_hour=8, tm_min=52, tm_sec=26, tm_wday=2, tm_yday=209, tm_isdst=0)# 格式化输出:print(time_obj.tm_year,time_obj.tm_mon,time_obj.tm_mday) print("{year}-{month}".format(year=time_obj.tm_year,month=time_obj.tm_mon)) # 以time.struct_time类型,打印本地时间print(time.localtime()) # 转换成时间戳time_obj = time.gmtime()print(time.mktime(time_obj)) # 延时2秒time.sleep(2) # 打印UTC,世界标准时间,北京时区是东八区,领先UTC八个小时print(time.strftime("%Y-%m-%d %H:%M:%S",time.gmtime())) # 本地时间print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())) # 把time.struct_time类型时间,转换成时间戳tm = time.strptime("2016-05-6 15:06:33","%Y-%m-%d %H:%M:%S")print(tm)print(time.mktime(tm))  print ('*'*30 + '3-python时间处理之datetime模块') import datetime # 打印当前,年,月,日print(datetime.date.today()) # 打印当前时间,精确到微秒current_time = datetime.datetime.now()print(current_time) # 转成time.struct_time格式时间current_time = datetime.datetime.now()print(current_time.timetuple()) # 加十天print(datetime.datetime.now() +datetime.timedelta(days=10))# 减十天print(datetime.datetime.now() +datetime.timedelta(days=-10))# 减十个小时print(datetime.datetime.now() +datetime.timedelta(hours=-10))# 加120sprint(datetime.datetime.now() +datetime.timedelta(seconds=120)) # 替换成指定的时间cr_time = datetime.datetime.now()print(cr_time.replace(2014,9,12))# 结果:2014-09-12 17:28:17.522893 # 格式化输出print(datetime.datetime.strptime("21/11/06 16:30","%d/%m/%y %H:%M")) # 替换成指定时间后,类型是<class 'datetime.datetime'>current_time = datetime.datetime.now()time_obj = current_time.replace(2015,5)print(time_obj,type(time_obj))# 结果:2015-05-27 17:34:13.350245 <class 'datetime.datetime'> # 对比时间大小,取指定时间范围使用current_time = datetime.datetime.now()time_obj = current_time.replace(2015,5)print(current_time>time_obj) import datetimedef getYesterday():    today=datetime.date.today()    oneday=datetime.timedelta(days=1)    yesterday=today-oneday    return yesterday # 输出print(getYesterday())脚本5:关于时间戳处理123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354# -*- coding: utf-8 -*- """Project: pyWorkspaceCreator: Administrator -haochuangCreate time: 2021-05-12 09:24IDE: PyCharmIntroduction: """import timeimport datetimefrom datetime import timezonefrom datetime import timedelta # 显示当前秒级时间戳与毫秒级时间戳、微秒级时间戳t = time.time()print(t)  # 原始时间数据print(int(t))  # 秒级时间戳print(int(round(t * 1000)))  # 毫秒级时间戳print(int(round(t * 1000000)))  # 微秒级时间戳  # 显示当前日期:dt = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')dt_ms = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') # 含微秒的日期时间,来源 比特量化print(dt)print(dt_ms)  # 将日期转为秒级时间戳dt = '2018-01-01 10:40:30'ts = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))print(ts)  # 将秒级时间戳转为日期ts = 1515774430dt = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))print(dt) # 时区转换# 显示UTC时间utc_now = datetime.datetime.utcnow()print(utc_now)# 世界标准时间# utc_time = datetime(2019, 7, 30, 7, 50, 0)print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))# 北京时间UTC+8# cst_time =utc_time.astimezone(timezone(timedelta(hours=-8))).strftime("%Y-%m-%d %H:%M:%S") # 国际标准时间print("国际标准时间:"+time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()))# 本地时间print("本地时间:"+time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
  • [技术干货] Python制作简易版2048小游戏【转】
    目录目标效果设计开始步骤一步骤二步骤三步骤四步骤五今天我们来动手实现一款2048小游戏。这款游戏的精髓就玩家能够在于通过滑动屏幕合并相同数字,直到不能再合并为止。玩法可以说是非常的简单,但挑战性也是十足的。话不多说,让我们从0开始实现!目标效果大致要实现的效果如下:设计开始首先简单分析一下游戏的逻辑:输入移动方向,游戏内所有方块都朝指定方向移动同方向移动的方块,数字相同则合并,然后生成一个合并的方块合并后生成新的方块,无法生成新方块时游戏结束用一系列的颜色来区分不同分数的方块(可有可无,纯粹是为了美观)ok,游戏内再逻辑已经很清晰了。现在开始实现:步骤一新建一个文件夹用来放需要的游戏素材步骤二新建一个python程序,可以命名为2048,放在素材目录的同级文件夹下步骤三导入需要的依赖库:123import pygame as pyimport sys, random, time, redis, os,mathimport numpy as np依赖库中的redis是一个额外的数据库,用来存取游戏历史数据,需要的可以考虑安装,不需要的用excel表代替也可以。首先需要思考的是,游戏内的方块的移动本质上是坐标的变换,并且方块的坐标是固定的,也就是说,每次输入一个方向就按照一个移动函数将所有方块的坐标进行对应的转换。那么,如此以来,就需要建立一个坐标系用以标记方块的坐标。因为是4x4的游戏,那么就按照(1,1),(1,2),(1,3),...,(4,4)建立游戏坐标,然而相比直接移动坐标还是比较麻烦,一个简单的想法是,每个方块给一个唯一的标记,如我们需要实现4x4的游戏,就需要16个记号。而每一个标记就对应了唯一且固定的坐标。给出如下代码:123456789# 预加载移动逻辑def pre_move():    numberPos = {}    for num in range(1, 17):        row1, row2 = divmod(num, 4)        row = row1 + np.sign(row2)        column = [row2 if row2 != 0 else 4][0]        numberPos['{}'.format([row, column])] = num    return numberPos这里的numberPos实际上就是{‘{1,1}’:1,’{1,2}‘:2......}。当然如果想设计5x5或者6x6的只需要把循环里面的17和4改成25和5或36和6就行。ok,有了坐标接下来的问题好解决了。步骤四在新建的素材文件夹内放入一些图片方块(正方形)用来表示每个不同分数的方块。如下图所示:当然,不使用图片加载游戏也是可以的,如使用py.draw.rect()也能绘制图像,不过每次加载都绘制图像会占用游戏大量运算内存,并且使用图片可以自定义自己的游戏风格,修改上也非常便利。设置完成之后,定义一个游戏的初始化模块:12345678910111213141516171819# 主程序def game_start():    global screen, rate    py.init()    clock = py.time.Clock()    screen_x = 500  # 请调到合适的大小    screen_y = math.ceil(screen_x * rate / rate2)    screen = py.display.set_mode((screen_x, screen_y), depth=32)    py.display.set_caption("终极2048")    BackGround = [251, 248, 239]  # 灰色    Icon = py.image.load('./素材/icon.png').convert_alpha()    py.display.set_icon(Icon)    screen.fill(color=BackGround)    # 主界面下设计    width = math.floor(screen_x * rate)    bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()    bgSecond = py.transform.smoothscale(bgSecond, (width, width))    bgSecondRect = bgSecond.get_rect()    bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))游戏界面的大小请调节到合适的尺寸。接下来加载分数图片,以便游戏循环时随时可以调用。123456789101112131415161718192021222324252627282930313233343536373839404142# 预加载分数图def pre_load_image(background):    imageList = {}    imagePath = './素材/分数/'    image_filenames = [i for i in os.listdir(imagePath)]    width = math.floor(background.width * (1 - internalWidth) / 4)    for name in image_filenames:        image = py.transform.smoothscale(py.image.load(imagePath + name).convert_alpha(), (width, width))        imageList[name.replace('.png', '')] = image    return imageList# 加载分数图像def draw_image(score_list, image_list, pos_list):    for pos_num in score_list:        score = score_list[pos_num]        scoreSurf = BasicFont01.render('{}'.format(score), True, (0, 0, 0))        scoreRect = scoreSurf.get_rect()        if score <= 4096:            image = image_list['{}'.format(score)]        else:            image = image_list['4096']        imageRect = image.get_rect()        imageRect.topleft = pos_list['{}'.format(pos_num)]        scoreRect.center = imageRect.center        screen.blit(image, imageRect)        if score > 0:            screen.blit(scoreSurf, scoreRect)# 图像位置列表,表示为(x,y)# 用于确定加载的分数图像的显示点位def image_pos_list(background):    pre_x = background.topleft[0]    pre_y = background.topleft[-1]    internalLong = math.ceil(internalWidth / 5 * background.width)    imageLong = math.floor((1 - internalWidth) / 4 * background.width)    posList = dict(zip(list(range(1, 17)), [''] * 16))    for num in range(1, 17):        row1, row2 = divmod(num, 4)        row = row1 + np.sign(row2)        column = [row2 if row2 != 0 else 4][0]        image_x = pre_x + internalLong * column + imageLong * (column - 1)        image_y = pre_y + internalLong * row + imageLong * (row - 1)        posList['{}'.format(num)] = (image_x, image_y)    return posList这里用了三个函数来加载游戏图片,分表表示:提取图片名保存到列表中,绘制游戏中的2,4,8等等数字在分数图片上。最后一个函数用于确定每个坐标在游戏界面的显示位置,并将其一一绑定。加载完成图像之后,就需要完成关键的移动逻辑,先上代码:12345678910111213141516171819202122232425262728293031323334353637383940414243# 移动逻辑def number_move(number_pos, move_input, score_list):    values = list(number_pos.values())    keys = list(number_pos.keys())    numberPosReverse = dict(zip(values, keys))    newScoreList = score_list.copy()    oldScoreList = {}    while newScoreList != oldScoreList:        oldScoreList = newScoreList.copy()        for num in range(1, 17):            pos = eval(numberPosReverse[num])            x, y = pos[0] + move_input[0], pos[1] + move_input[1]            pos[0] = [x if 1 <= x <= 4 else pos[0]][0]            pos[1] = [y if 1 <= y <= 4 else pos[1]][0]            number = number_pos['{}'.format(pos)]            oldNumberScore = newScoreList[num]            nextNumberScore = newScoreList[number]            syn = list(map(lambda x, y: abs(x) * abs(y), move_input, pos))            # 0值移动            if nextNumberScore == 0:                newScoreList[number] = oldNumberScore                newScoreList[num] = 0            # 无法移动            elif num == number:                pass            # 合并移动            elif oldNumberScore == nextNumberScore and num != number:                newScoreList[number] = 2 * oldNumberScore                newScoreList[num] = 0            # 边界移动            elif oldNumberScore != nextNumberScore and 1 in syn or 4 not in syn:                pass            # 非边界移动            elif oldNumberScore != nextNumberScore and 1 not in syn and 4 not in syn:                x, y = pos[0] + move_input[0], pos[1] + move_input[1]                next2NumberScore = newScoreList[number_pos['{}'.format([x, y])]]                if next2NumberScore != nextNumberScore:                    pass                elif next2NumberScore == nextNumberScore:                    newScoreList[number_pos['{}'.format([x, y])]] = 2 * next2NumberScore                    newScoreList[number] = oldNumberScore                    newScoreList[num] = 0    return newScoreList首先导入预先确定好的坐标,移动变量。根据前面分析的游戏逻辑,每次输入移动向量后游戏内的所有方块都需要移动,相同分数的方块需要一次性合并到一起,并且不能留空。详细分析一下就是:输入一个移动向量(x,y),如(+1,0)表示方块向右移动一格。对所有的原坐标进行计算并保留为移动后坐标,提取前后两次坐标对应的分数从1号标记开始循环判断:0值移动:如果移动后的分数为0,用旧坐标分数替代新坐标的分数,并删除旧坐标的分数无法移动:移动后的坐标与移动前的坐标相同,那么不做改变合并移动:新旧坐标对应的分数相同,那么新坐标分数x2,旧坐标分数删除边界移动:方块已经处于移动的边界,无法移动,不做修改非边界移动:新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数也不同,不做修改;新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数相同,修改循环整个逻辑,直到所有坐标对应的分数不再发生改变通过上述分析,移动逻辑函数实现了输入一个方向游戏内的分数动态发生变化。最后我们还需要一个游戏结束的函数:12345678910111213141516171819# 游戏结束def game_over(score,bg):    ip = '127.0.0.1'    password = None    r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)    r.hset('2048','{}'.format(time.localtime()),score)    py.draw.rect(screen,bg,[0,0,screen.get_width(),screen.get_height()],0)    BasicFont02 = py.font.SysFont('/素材/simkai.ttf', 40)    overSurf = BasicFont01.render('Game Over', True, (0, 0, 0))    overRect = overSurf.get_rect()    overRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() / 2))    scoreSurf = BasicFont02.render('最终得分:', True, (0, 0, 0))    scoreRect = scoreSurf.get_rect()    scoreRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.6))    numberSurf = BasicFont02.render('{}'.format(score), True, (0, 0, 0))    numberRect = numberSurf.get_rect()    numberRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.7))    time.sleep(3)    sys.exit()一个键盘控制代码,实现键盘控制游戏:123456789101112# 键盘控制函数def keyboard_ctrl(event):    move_output = [0, 0]    if event.key == py.K_UP:        move_output = [-1, 0]    elif event.key == py.K_DOWN:        move_output = [1, 0]    elif event.key == py.K_RIGHT:        move_output = [0, 1]    elif event.key == py.K_LEFT:        move_output = [0, -1]    return move_output一个新方块生成器,实现每次合并之后能在空白方块处随机生成2或4中的一个新分数,生成概率按照当前游戏中的2和4的数量为基础。123456789101112# 随机得分生成def random_score(score_list):    values = list(score_list.values())    pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4))  # 以当前分数图中2或4出现的频率为概率    blank = [[i if score_list[i] == 0 else 0][0] for i in range(1, 17)]    blank = list(set(blank))    blank.remove(0)    if not blank:        return 'GameOver'  # 游戏结束    else:        score_list[random.choice(blank)] = random.choice(pro)        return score_list一个得分统计器,每次游戏运行是统计当前得分和历史最高得分:123456789101112131415161718192021222324252627# 统计并记录当前得分def record_score(score_list, background):    totalScore = 0    values = list(score_list.values())    for i in values: totalScore += i    scoreSurf = BasicFont01.render('得分:{}'.format(totalScore), True, (0, 0, 0))    scoreRect = scoreSurf.get_rect()    scoreRect.topleft = (math.floor(0.1 * screen.get_width()), math.floor(0.05 * screen.get_height()))    scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())    scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())    py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)    screen.blit(scoreSurf, scoreRect)    return totalScore# 绘制历史最高得分def draw_best(background):    ip = '127.0.0.1'    password = None    r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)    scores=[eval(i) for i in list(r.hgetall('2048').values())]    best_scores=max(scores)    scoreSurf=BasicFont01.render('最高得分:{}'.format(best_scores),True,(0,0,0))    scoreRect=scoreSurf.get_rect()    scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())    scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())    scoreRect.topright = (math.floor(0.9 * screen.get_width()), math.floor(0.05 * screen.get_height()))    py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)    screen.blit(scoreSurf, scoreRect)最后补充完整的游戏启动器:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455# 主程序def game_start():    global screen, rate    py.init()    clock = py.time.Clock()    screen_x = 500  # 请调到合适的大小    screen_y = math.ceil(screen_x * rate / rate2)    screen = py.display.set_mode((screen_x, screen_y), depth=32)    py.display.set_caption("终极2048")    BackGround = [251, 248, 239]  # 灰色    Icon = py.image.load('./素材/icon.png').convert_alpha()    py.display.set_icon(Icon)    screen.fill(color=BackGround)    # 主界面下设计    width = math.floor(screen_x * rate)    bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()    bgSecond = py.transform.smoothscale(bgSecond, (width, width))    bgSecondRect = bgSecond.get_rect()    bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))    # 主界面上部分设计    # 预加载数据    draw_best(BackGround)    posList = image_pos_list(bgSecondRect)    imageList = pre_load_image(bgSecondRect)    scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2]))  # 分数表    numberPos = pre_move()    scoreList = random_score(scoreList)    totalScore=0    # 主循环    while True:        screen.blit(bgSecond, bgSecondRect)  # 刷新屏幕        if scoreList == 'GameOver':            game_over(totalScore,BackGround)        draw_image(scoreList, imageList, posList)  # 绘制得分        totalScore = record_score(scoreList, BackGround)        key = py.key.get_pressed()        if key[py.K_ESCAPE]: exit()        for event in py.event.get():            if event.type == py.QUIT:                sys.exit()            elif event.type == py.KEYDOWN:                move_input = keyboard_ctrl(event)  # 按下按键                scoreList = number_move(numberPos, move_input, scoreList)  # 移动数字                scoreList = random_score(scoreList)  # 在按下按键后生成新的数字        py.display.update()        clock.tick(FPS)if __name__ == '__main__':    py.font.init()    BasicFont01 = py.font.Font('./素材/simkai.ttf', 30)    screen = py.display.set_mode((500, 500))    rate = 0.95  # 游戏主界面下的宽度占整个游戏界面宽度的比例    rate2 = 0.7  # 游戏主界面下的高度占整个游戏界面高度的比例    internalWidth = 0.1  # 间隙比例    FPS = 50  # 游戏帧率    game_start()步骤五启动游戏运行之前别忘了启动redis服务器。运行效果图:(游戏界面设计的不够好。。。。,本来打算再加入一些小道具比如说:撤销,全屏合并等功能)
  • [技术干货] Python制作简易版2048小游戏【转】
    目录目标效果设计开始步骤一步骤二步骤三步骤四步骤五今天我们来动手实现一款2048小游戏。这款游戏的精髓就玩家能够在于通过滑动屏幕合并相同数字,直到不能再合并为止。玩法可以说是非常的简单,但挑战性也是十足的。话不多说,让我们从0开始实现!目标效果大致要实现的效果如下:设计开始首先简单分析一下游戏的逻辑:输入移动方向,游戏内所有方块都朝指定方向移动同方向移动的方块,数字相同则合并,然后生成一个合并的方块合并后生成新的方块,无法生成新方块时游戏结束用一系列的颜色来区分不同分数的方块(可有可无,纯粹是为了美观)ok,游戏内再逻辑已经很清晰了。现在开始实现:步骤一新建一个文件夹用来放需要的游戏素材步骤二新建一个python程序,可以命名为2048,放在素材目录的同级文件夹下步骤三导入需要的依赖库:123import pygame as pyimport sys, random, time, redis, os,mathimport numpy as np依赖库中的redis是一个额外的数据库,用来存取游戏历史数据,需要的可以考虑安装,不需要的用excel表代替也可以。首先需要思考的是,游戏内的方块的移动本质上是坐标的变换,并且方块的坐标是固定的,也就是说,每次输入一个方向就按照一个移动函数将所有方块的坐标进行对应的转换。那么,如此以来,就需要建立一个坐标系用以标记方块的坐标。因为是4x4的游戏,那么就按照(1,1),(1,2),(1,3),...,(4,4)建立游戏坐标,然而相比直接移动坐标还是比较麻烦,一个简单的想法是,每个方块给一个唯一的标记,如我们需要实现4x4的游戏,就需要16个记号。而每一个标记就对应了唯一且固定的坐标。给出如下代码:123456789# 预加载移动逻辑def pre_move():    numberPos = {}    for num in range(1, 17):        row1, row2 = divmod(num, 4)        row = row1 + np.sign(row2)        column = [row2 if row2 != 0 else 4][0]        numberPos['{}'.format([row, column])] = num    return numberPos这里的numberPos实际上就是{‘{1,1}’:1,’{1,2}‘:2......}。当然如果想设计5x5或者6x6的只需要把循环里面的17和4改成25和5或36和6就行。ok,有了坐标接下来的问题好解决了。步骤四在新建的素材文件夹内放入一些图片方块(正方形)用来表示每个不同分数的方块。如下图所示:当然,不使用图片加载游戏也是可以的,如使用py.draw.rect()也能绘制图像,不过每次加载都绘制图像会占用游戏大量运算内存,并且使用图片可以自定义自己的游戏风格,修改上也非常便利。设置完成之后,定义一个游戏的初始化模块:12345678910111213141516171819# 主程序def game_start():    global screen, rate    py.init()    clock = py.time.Clock()    screen_x = 500  # 请调到合适的大小    screen_y = math.ceil(screen_x * rate / rate2)    screen = py.display.set_mode((screen_x, screen_y), depth=32)    py.display.set_caption("终极2048")    BackGround = [251, 248, 239]  # 灰色    Icon = py.image.load('./素材/icon.png').convert_alpha()    py.display.set_icon(Icon)    screen.fill(color=BackGround)    # 主界面下设计    width = math.floor(screen_x * rate)    bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()    bgSecond = py.transform.smoothscale(bgSecond, (width, width))    bgSecondRect = bgSecond.get_rect()    bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))游戏界面的大小请调节到合适的尺寸。接下来加载分数图片,以便游戏循环时随时可以调用。123456789101112131415161718192021222324252627282930313233343536373839404142# 预加载分数图def pre_load_image(background):    imageList = {}    imagePath = './素材/分数/'    image_filenames = [i for i in os.listdir(imagePath)]    width = math.floor(background.width * (1 - internalWidth) / 4)    for name in image_filenames:        image = py.transform.smoothscale(py.image.load(imagePath + name).convert_alpha(), (width, width))        imageList[name.replace('.png', '')] = image    return imageList# 加载分数图像def draw_image(score_list, image_list, pos_list):    for pos_num in score_list:        score = score_list[pos_num]        scoreSurf = BasicFont01.render('{}'.format(score), True, (0, 0, 0))        scoreRect = scoreSurf.get_rect()        if score <= 4096:            image = image_list['{}'.format(score)]        else:            image = image_list['4096']        imageRect = image.get_rect()        imageRect.topleft = pos_list['{}'.format(pos_num)]        scoreRect.center = imageRect.center        screen.blit(image, imageRect)        if score > 0:            screen.blit(scoreSurf, scoreRect)# 图像位置列表,表示为(x,y)# 用于确定加载的分数图像的显示点位def image_pos_list(background):    pre_x = background.topleft[0]    pre_y = background.topleft[-1]    internalLong = math.ceil(internalWidth / 5 * background.width)    imageLong = math.floor((1 - internalWidth) / 4 * background.width)    posList = dict(zip(list(range(1, 17)), [''] * 16))    for num in range(1, 17):        row1, row2 = divmod(num, 4)        row = row1 + np.sign(row2)        column = [row2 if row2 != 0 else 4][0]        image_x = pre_x + internalLong * column + imageLong * (column - 1)        image_y = pre_y + internalLong * row + imageLong * (row - 1)        posList['{}'.format(num)] = (image_x, image_y)    return posList这里用了三个函数来加载游戏图片,分表表示:提取图片名保存到列表中,绘制游戏中的2,4,8等等数字在分数图片上。最后一个函数用于确定每个坐标在游戏界面的显示位置,并将其一一绑定。加载完成图像之后,就需要完成关键的移动逻辑,先上代码:12345678910111213141516171819202122232425262728293031323334353637383940414243# 移动逻辑def number_move(number_pos, move_input, score_list):    values = list(number_pos.values())    keys = list(number_pos.keys())    numberPosReverse = dict(zip(values, keys))    newScoreList = score_list.copy()    oldScoreList = {}    while newScoreList != oldScoreList:        oldScoreList = newScoreList.copy()        for num in range(1, 17):            pos = eval(numberPosReverse[num])            x, y = pos[0] + move_input[0], pos[1] + move_input[1]            pos[0] = [x if 1 <= x <= 4 else pos[0]][0]            pos[1] = [y if 1 <= y <= 4 else pos[1]][0]            number = number_pos['{}'.format(pos)]            oldNumberScore = newScoreList[num]            nextNumberScore = newScoreList[number]            syn = list(map(lambda x, y: abs(x) * abs(y), move_input, pos))            # 0值移动            if nextNumberScore == 0:                newScoreList[number] = oldNumberScore                newScoreList[num] = 0            # 无法移动            elif num == number:                pass            # 合并移动            elif oldNumberScore == nextNumberScore and num != number:                newScoreList[number] = 2 * oldNumberScore                newScoreList[num] = 0            # 边界移动            elif oldNumberScore != nextNumberScore and 1 in syn or 4 not in syn:                pass            # 非边界移动            elif oldNumberScore != nextNumberScore and 1 not in syn and 4 not in syn:                x, y = pos[0] + move_input[0], pos[1] + move_input[1]                next2NumberScore = newScoreList[number_pos['{}'.format([x, y])]]                if next2NumberScore != nextNumberScore:                    pass                elif next2NumberScore == nextNumberScore:                    newScoreList[number_pos['{}'.format([x, y])]] = 2 * next2NumberScore                    newScoreList[number] = oldNumberScore                    newScoreList[num] = 0    return newScoreList首先导入预先确定好的坐标,移动变量。根据前面分析的游戏逻辑,每次输入移动向量后游戏内的所有方块都需要移动,相同分数的方块需要一次性合并到一起,并且不能留空。详细分析一下就是:输入一个移动向量(x,y),如(+1,0)表示方块向右移动一格。对所有的原坐标进行计算并保留为移动后坐标,提取前后两次坐标对应的分数从1号标记开始循环判断:0值移动:如果移动后的分数为0,用旧坐标分数替代新坐标的分数,并删除旧坐标的分数无法移动:移动后的坐标与移动前的坐标相同,那么不做改变合并移动:新旧坐标对应的分数相同,那么新坐标分数x2,旧坐标分数删除边界移动:方块已经处于移动的边界,无法移动,不做修改非边界移动:新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数也不同,不做修改;新旧坐标对应的分数不同,且新坐标的下一个坐标对应的分数相同,修改循环整个逻辑,直到所有坐标对应的分数不再发生改变通过上述分析,移动逻辑函数实现了输入一个方向游戏内的分数动态发生变化。最后我们还需要一个游戏结束的函数:12345678910111213141516171819# 游戏结束def game_over(score,bg):    ip = '127.0.0.1'    password = None    r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)    r.hset('2048','{}'.format(time.localtime()),score)    py.draw.rect(screen,bg,[0,0,screen.get_width(),screen.get_height()],0)    BasicFont02 = py.font.SysFont('/素材/simkai.ttf', 40)    overSurf = BasicFont01.render('Game Over', True, (0, 0, 0))    overRect = overSurf.get_rect()    overRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() / 2))    scoreSurf = BasicFont02.render('最终得分:', True, (0, 0, 0))    scoreRect = scoreSurf.get_rect()    scoreRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.6))    numberSurf = BasicFont02.render('{}'.format(score), True, (0, 0, 0))    numberRect = numberSurf.get_rect()    numberRect.center = (math.floor(screen.get_width() / 2), math.floor(screen.get_height() * 0.7))    time.sleep(3)    sys.exit()一个键盘控制代码,实现键盘控制游戏:123456789101112# 键盘控制函数def keyboard_ctrl(event):    move_output = [0, 0]    if event.key == py.K_UP:        move_output = [-1, 0]    elif event.key == py.K_DOWN:        move_output = [1, 0]    elif event.key == py.K_RIGHT:        move_output = [0, 1]    elif event.key == py.K_LEFT:        move_output = [0, -1]    return move_output一个新方块生成器,实现每次合并之后能在空白方块处随机生成2或4中的一个新分数,生成概率按照当前游戏中的2和4的数量为基础。123456789101112# 随机得分生成def random_score(score_list):    values = list(score_list.values())    pro = [2] * (2 + values.count(2)) + [4] * (1 + values.count(4))  # 以当前分数图中2或4出现的频率为概率    blank = [[i if score_list[i] == 0 else 0][0] for i in range(1, 17)]    blank = list(set(blank))    blank.remove(0)    if not blank:        return 'GameOver'  # 游戏结束    else:        score_list[random.choice(blank)] = random.choice(pro)        return score_list一个得分统计器,每次游戏运行是统计当前得分和历史最高得分:123456789101112131415161718192021222324252627# 统计并记录当前得分def record_score(score_list, background):    totalScore = 0    values = list(score_list.values())    for i in values: totalScore += i    scoreSurf = BasicFont01.render('得分:{}'.format(totalScore), True, (0, 0, 0))    scoreRect = scoreSurf.get_rect()    scoreRect.topleft = (math.floor(0.1 * screen.get_width()), math.floor(0.05 * screen.get_height()))    scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())    scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())    py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)    screen.blit(scoreSurf, scoreRect)    return totalScore# 绘制历史最高得分def draw_best(background):    ip = '127.0.0.1'    password = None    r = redis.Redis(host=ip, password=password, port=6379, db=2, decode_responses=True)    scores=[eval(i) for i in list(r.hgetall('2048').values())]    best_scores=max(scores)    scoreSurf=BasicFont01.render('最高得分:{}'.format(best_scores),True,(0,0,0))    scoreRect=scoreSurf.get_rect()    scoreRect.width = math.floor((rate - 0.15) / 2 * screen.get_width())    scoreRect.height = math.floor((1 - rate2) / 3 * 2 * screen.get_height())    scoreRect.topright = (math.floor(0.9 * screen.get_width()), math.floor(0.05 * screen.get_height()))    py.draw.rect(screen, background, [scoreRect.topleft[0], scoreRect.topleft[1], scoreRect.width, scoreRect.height], 0)    screen.blit(scoreSurf, scoreRect)最后补充完整的游戏启动器:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455# 主程序def game_start():    global screen, rate    py.init()    clock = py.time.Clock()    screen_x = 500  # 请调到合适的大小    screen_y = math.ceil(screen_x * rate / rate2)    screen = py.display.set_mode((screen_x, screen_y), depth=32)    py.display.set_caption("终极2048")    BackGround = [251, 248, 239]  # 灰色    Icon = py.image.load('./素材/icon.png').convert_alpha()    py.display.set_icon(Icon)    screen.fill(color=BackGround)    # 主界面下设计    width = math.floor(screen_x * rate)    bgSecond = py.image.load('./素材/BG_02.png').convert_alpha()    bgSecond = py.transform.smoothscale(bgSecond, (width, width))    bgSecondRect = bgSecond.get_rect()    bgSecondRect.topleft = math.floor(screen_x * (1 - rate) / 2), math.floor(screen_y * (1 - rate2))    # 主界面上部分设计    # 预加载数据    draw_best(BackGround)    posList = image_pos_list(bgSecondRect)    imageList = pre_load_image(bgSecondRect)    scoreList = dict(zip(list(range(1, 17)), [0] * 15 + [2]))  # 分数表    numberPos = pre_move()    scoreList = random_score(scoreList)    totalScore=0    # 主循环    while True:        screen.blit(bgSecond, bgSecondRect)  # 刷新屏幕        if scoreList == 'GameOver':            game_over(totalScore,BackGround)        draw_image(scoreList, imageList, posList)  # 绘制得分        totalScore = record_score(scoreList, BackGround)        key = py.key.get_pressed()        if key[py.K_ESCAPE]: exit()        for event in py.event.get():            if event.type == py.QUIT:                sys.exit()            elif event.type == py.KEYDOWN:                move_input = keyboard_ctrl(event)  # 按下按键                scoreList = number_move(numberPos, move_input, scoreList)  # 移动数字                scoreList = random_score(scoreList)  # 在按下按键后生成新的数字        py.display.update()        clock.tick(FPS)if __name__ == '__main__':    py.font.init()    BasicFont01 = py.font.Font('./素材/simkai.ttf', 30)    screen = py.display.set_mode((500, 500))    rate = 0.95  # 游戏主界面下的宽度占整个游戏界面宽度的比例    rate2 = 0.7  # 游戏主界面下的高度占整个游戏界面高度的比例    internalWidth = 0.1  # 间隙比例    FPS = 50  # 游戏帧率    game_start()步骤五启动游戏运行之前别忘了启动redis服务器。运行效果图:(游戏界面设计的不够好。。。。,本来打算再加入一些小道具比如说:撤销,全屏合并等功能)
  • [技术干货] Python Flask中Cookie和Session区别详解【转】
    目录前言安装创建虚拟环境进入虚拟环境安装 flaskCookie的使用Session的使用前言本篇文章,阐述一下Flask中Cookie和Session为什么要说Cookie和Session呢?答:因为http请求是无状态的,怎么理解呢?当你访问B站时,如果你没有Cookie或者Session,B站就认为你是一个没有登录的用户。如果你有Cookie或Session,那么B站就知道你登录了,并且知道你是谁。所以可以把跟你相关的资料返回给你两者的区别:答:Cookie是明文,Session是加密的Cookie。安装创建虚拟环境123mkdir myprojectcd myprojectpython3 -m venv venv进入虚拟环境1. venv/bin/activate安装 flask1pip install FlaskCookie的使用12345678910111213141516from flask import Flask, Response, requestapp = Flask(__name__)@app.route("/set_cookie")def set_cookie():    response = Response("set cookie success")    response.set_cookie("user_id","123")    return response@app.route("/get_cookie")def get_cookie():    cookies = request.cookies    if cookies.get("user_id") == "123":        return "尊敬的用户你好,你的cookies是" + str(cookies)    else:        return "你不是VIP,请先获取身份"if __name__ == "__main__":    app.run(debug=True,port=5000)第一次首先访问/home地址,会发现你没有Cookie给你返回一个你不是VIP,请先获取身份但是当我们首先访问/set_cookie,我们就会获得一个cookie,然后我们在访问/home地址,就可以成功的显示尊敬的用户您好了Session的使用12345678910111213141516from flask import Flask, sessionapp = Flask(__name__)# !!!你需要设置一个SECRET_KEY用来加密Cookieapp.config["SECRET_KEY"] = "123"@app.route("/set_session")def set_session():    session["user_name"] = "zhuxiaoxiao"    return "设置session成功" + session["user_name"]@app.route("/home")def home():    if session.get("user_name") == "zhuxiaoxiao":        return "尊敬的用户,登录成功"    else:        return "请先登录好吗?"if __name__ == "__main__":    app.run(debug=True,port=5000)Session的使用方式整体和Cookie是一样的。需要注意的是使用Session前,必须要进行设置SECRET_KEY,用来加密Cookie
  • [技术干货] Python实现yaml与json文件批量互转【转】
    目录1. 安装yaml库2. yaml转json3. json转yaml4. 批量将yaml与json文件互相转换1. 安装yaml库想要使用python实现yaml与json格式互相转换,需要先下载pip,再通过pip安装yaml库。如何下载以及使用pip,可参考:pip的安装与使用,解决pip下载速度慢的问题安装yaml库:1pip install pyyaml2. yaml转json新建一个test.yaml文件,添加以下内容:123456789A:     hello:          name: Michael              address: Beijing            B:     hello:          name: jack           address: Shanghai代码如下:1234567891011121314import yamlimport json # yaml文件内容转换成json格式def yaml_to_json(yamlPath):    with open(yamlPath, encoding="utf-8") as f:        datas = yaml.load(f,Loader=yaml.FullLoader)  # 将文件的内容转换为字典形式    jsonDatas = json.dumps(datas, indent=5) # 将字典的内容转换为json格式的字符串    print(jsonDatas) if __name__ == "__main__":    jsonPath = 'E:/Code/Python/test/test.yaml'    yaml_to_json(jsonPath)    执行结果如下:{     "A": {          "hello": {               "name": "Michael",                  "address": "Beijing"           }     },     "B": {          "hello": {               "name": "jack",                     "address": "Shanghai"          }     }}3. json转yaml新建一个test.json文件,添加以下内容:1234567891011121314{    "A": {         "hello": {            "name": "Michael",            "address": "Beijing"                   }    },    "B": {         "hello": {            "name": "jack",            "address": "Shanghai"            }    }}代码如下:12345678910111213import yamlimport json # json文件内容转换成yaml格式def json_to_yaml(jsonPath):    with open(jsonPath, encoding="utf-8") as f:        datas = json.load(f) # 将文件的内容转换为字典形式    yamlDatas = yaml.dump(datas, indent=5, sort_keys=False) # 将字典的内容转换为yaml格式的字符串    print(yamlDatas) if __name__ == "__main__":    jsonPath = 'E:/Code/Python/test/test.json'    json_to_yaml(jsonPath)执行结果如下:A:     hello:          name: Michael          address: BeijingB:     hello:          name: jack          address: Shanghai注意,如果不加sort_keys=False,那么默认是排序的,则执行结果如下:A:     hello:          address: Beijing          name: MichaelB:     hello:          address: Shanghai          name: jack4. 批量将yaml与json文件互相转换yaml与json文件互相转换:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132import yamlimport jsonimport osfrom pathlib import Pathfrom fnmatch import fnmatchcase class Yaml_Interconversion_Json:    def __init__(self):        self.filePathList = []         # yaml文件内容转换成json格式    def yaml_to_json(self, yamlPath):        with open(yamlPath, encoding="utf-8") as f:            datas = yaml.load(f,Loader=yaml.FullLoader)          jsonDatas = json.dumps(datas, indent=5)        # print(jsonDatas)        return jsonDatas     # json文件内容转换成yaml格式    def json_to_yaml(self, jsonPath):        with open(jsonPath, encoding="utf-8") as f:            datas = json.load(f)        yamlDatas = yaml.dump(datas, indent=5)        # print(yamlDatas)        return yamlDatas     # 生成文件    def generate_file(self, filePath, datas):        if os.path.exists(filePath):            os.remove(filePath)         with open(filePath,'w') as f:            f.write(datas)     # 清空列表    def clear_list(self):        self.filePathList.clear()     # 修改文件后缀    def modify_file_suffix(self, filePath, suffix):        dirPath = os.path.dirname(filePath)        fileName = Path(filePath).stem + suffix        newPath = dirPath + '/' + fileName        # print('{}_path:{}'.format(suffix, newPath))        return newPath     # 原yaml文件同级目录下,生成json文件    def generate_json_file(self, yamlPath, suffix ='.json'):        jsonDatas = self.yaml_to_json(yamlPath)        jsonPath = self.modify_file_suffix(yamlPath, suffix)        # print('jsonPath:{}'.format(jsonPath))        self.generate_file(jsonPath, jsonDatas)     # 原json文件同级目录下,生成yaml文件    def generate_yaml_file(self, jsonPath, suffix ='.yaml'):        yamlDatas = self.json_to_yaml(jsonPath)        yamlPath = self.modify_file_suffix(jsonPath, suffix)        # print('yamlPath:{}'.format(yamlPath))        self.generate_file(yamlPath, yamlDatas)     # 查找指定文件夹下所有相同名称的文件    def search_file(self, dirPath, fileName):        dirs = os.listdir(dirPath)         for currentFile in dirs:             absPath = dirPath + '/' + currentFile             if os.path.isdir(absPath):                 self.search_file(absPath, fileName)            elif currentFile == fileName:                self.filePathList.append(absPath)     # 查找指定文件夹下所有相同后缀名的文件    def search_file_suffix(self, dirPath, suffix):        dirs = os.listdir(dirPath)         for currentFile in dirs:             absPath = dirPath + '/' + currentFile             if os.path.isdir(absPath):                if fnmatchcase(currentFile,'.*'):                     pass                else:                    self.search_file_suffix(absPath, suffix)            elif currentFile.split('.')[-1] == suffix:                 self.filePathList.append(absPath)     # 批量删除指定文件夹下所有相同名称的文件    def batch_remove_file(self, dirPath, fileName):        self.search_file(dirPath, fileName)        print('The following files are deleted:{}'.format(self.filePathList))        for filePath in self.filePathList:            if os.path.exists(filePath):                os.remove(filePath)         self.clear_list()     # 批量删除指定文件夹下所有相同后缀名的文件    def batch_remove_file_suffix(self, dirPath, suffix):        self.search_file_suffix(dirPath, suffix)        print('The following files are deleted:{}'.format(self.filePathList))        for filePath in self.filePathList:            if os.path.exists(filePath):                os.remove(filePath)         self.clear_list()     # 批量将目录下的yaml文件转换成json文件    def batch_yaml_to_json(self, dirPath):        self.search_file_suffix(dirPath, 'yaml')        print('The converted yaml file is as follows:{}'.format(self.filePathList))        for yamPath in self.filePathList:            try:                self.generate_json_file(yamPath)            except Exception as e:                print('YAML parsing error:{}'.format(e))                 self.clear_list()     # 批量将目录下的json文件转换成yaml文件    def batch_json_to_yaml(self, dirPath):        self.search_file_suffix(dirPath, 'json')        print('The converted json file is as follows:{}'.format(self.filePathList))        for jsonPath in self.filePathList:            try:                self.generate_yaml_file(jsonPath)            except Exception as e:                print('JSON parsing error:{}'.format(jsonPath))                print(e)        self.clear_list() if __name__ == "__main__":    dirPath = 'C:/Users/hwx1109527/Desktop/yaml_to_json'    fileName = 'os_deploy_config.yaml'    suffix = 'yaml'    filePath = dirPath + '/' + fileName    yaml_interconversion_json = Yaml_Interconversion_Json()    yaml_interconversion_json.batch_yaml_to_json(dirPath)    # yaml_interconversion_json.batch_json_to_yaml(dirPath)    # yaml_interconversion_json.batch_remove_file_suffix(dirPath, suffix)
总条数:2474 到第 页
上滑加载中