victory的博客

长安一片月,万户捣衣声

0%

pytest使用规则及示例

Pytest是一个功能强大且易于使用的python测试框架,支持编写单元测试集成测试功能测试

Pytest使用规则:

  1. 测试文件命名应符合test_*.py*_test.py
  2. 若使用class对测试用例进行分组,测试类名应符合TestXxx
  3. 测试用例函数/方法名称应符合test_*()

另外,值得注意的是,pytest支持在测试类中定义setupteardown方法,这写方法会在每个测试方法运行前后分别调用,用于设置测试环境和清理资源。setup_classteardown_class方法在测试类中的所有测试方法之前和之后分别只运行一次

下面编写一个计算器类Calculator,并使用pytest进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# test_calculator.py 测试文件名称符合test_*.py
import pytest

# 定义计算器类
class Calculator:
@staticmethod
def add(a, b):
return a + b

@staticmethod
def sub(a, b):
return a - b

@staticmethod
def mul(a, b):
return a * b

@staticmethod
def div(a, b):
try:
return a / b
except ZeroDivisionError:
print("zero Division Error Exception")

# 定义测试类
class TestCalc: # 测试类名符合TestXxx
def test_calc_add(self): # 测试用例方法名符合test_*()
assert Calculator.add(1, 2) == 3

def test_calc_sub(self):
assert Calculator.sub(3, 1) == 2

def test_calc_mul(self):
assert Calculator.mul(2, 4) == 8

def test_calc_div(self):
assert Calculator.div(8, 4) == 2

def test_calc_div_zero_div_exp(self):
with pytest.raises(ZeroDivisionError):
Calculator.div(3, 0)

CI/CD

CI/CD:持续集成和持续交付/持续部署,是DevOps实践中的关键组成部分,包括代码的自动构建、测试和部署,以确保软件的快速和健壮开发。

DevOps是软件开发中的一种文化和实践,强调开发(Development),测试,运维(Operations)三个领域的合并,通过自动化软件交付和架构变更的流程,实现构建、测试、发布软件的快捷、频繁和可靠。

参考资料1:什么是 CI/CD?

参考资料2:使用gitlab实现CI/CD

迭代器与生成器的区别

  1. python迭代器

    Python中的迭代器是一个可以记住遍历位置的对象,用于访问集合元素的一种方式。迭代器是Python中处理数据集合的一种高效机制,它们允许开发者逐个访问集合中的元素,而不需要一次性将所有数据加载到内存中

    迭代器通过实现__iter__()__next__()方法来定义其行为。__iter__()方法返回迭代器对象本身,而__next__()方法负责返回容器中的下一个值。当没有更多元素可以返回时,__next__()方法会抛出StopIteration异常,通知调用者所有元素已经被遍历完毕。

阅读全文 »

GCC/Clang的头文件搜索路径

  • GCC/Clang编译器在对C++源代码文件进行预处理时,会对C++源代码头部包含的头文件进行搜索,搜索时根据环境变量CPLUS_INCLUDE_PATH中的路径进行搜索,如果搜索不到某头文件会报错。若CPLUS_INCLUDE_PATH中没有包含所使用头文件所在路径,也可以通过-I参数制定使用到的头文件的路径,例如,gcc -I /your/header/file/ test.cpp。

  • 添加GCC头文件搜索路径

    1
    export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/xxx/include
  • 查看C++编译器(GCC)的头文件搜索路径命令

    1
    2
    3
    4
    5
    6
    gcc -x c++ -v -E -
    # gcc:表示使用GCC编译器进行编译。
    # -x c++:表示指定编译的语言为C++。
    # -v:表示显示详细的编译过程信息。
    # -E:表示仅执行预处理阶段,不进行编译和链接。
    # -:表示从标准输入读取源代码。
  • 查看C++编译器(Clang)的头文件搜索路径命令

    1
    2
    3
    4
    5
    6
    clang -x c++ -v -E -
    # clang:表示使用GCC编译器进行编译。
    # -x c++:表示指定编译的语言为C++。
    # -v:表示显示详细的编译过程信息。
    # -E:表示仅执行预处理阶段,不进行编译和链接。
    # -:表示从标准输入读取源代码。

Python是否支持多线程

  1. 多线程

    线程在CPU上运行,是操作系统进行调度和执行的基本单位

​ 首先,要明确进程与线程的关系。一个进程可以包含多个线程,而每个线程都是进程中的一个执行流。可以把进程比作一个工厂,而线程就像是工厂里的工人,它们共同协作完成工作。

​ 其次,线程的创建和运行是在一个进程中进行的。当启动一个线程时(例如,通过调用Thread.start()方法),操作系统会为这个线程分配必要的资源,包括寄存器、栈等,并在CPU上执行该线程的代码。

​ 再者,在多核CPU系统中,不同的线程可以被分配到不同的核心上并行运行。而在单核CPU中,线程的运行会通过时间片轮转或优先级调度等方式进行切换,从而实现并发执行。

​ 最后,线程之间共享所属进程的内存空间,包括代码段、数据段、堆和栈。这意味着线程可以访问相同的变量和数据结构,但同时需要注意同步机制来避免数据竞争和不一致的问题。

综上所述,线程是在CPU上运行的,它们利用进程中的资源和内存空间,按照操作系统的调度策略执行任务。

  1. python是否支持多线程?

Python支持多线程,但由于全局解释器锁(GIL)的存在,在任意时刻只能有一个线程执行Python字节码。Python中的多线程是通过在一个进程中创建多个线程来实现的,每个线程可以执行不同的任务。然而,由于Python解释器(cpython,由c语言编写的python解释器库)的GIL限制,这些线程并不是真正意义上的并行执行。GIL确保了同一时间只有一个线程能够访问Python的对象和内存空间,这意味着即使在多核处理器上,Python的多线程也无法实现真正的并行计算。

​ 尽管如此,Python的多线程对于I/O密集型任务仍然非常有用,因为它们可以在等待I/O操作完成时释放GIL,从而允许其他线程执行。在这种情况下,多线程可以提高程序的效率,因为它们可以减少因等待I/O而浪费的时间。

​ 此外,如果需要执行CPU密集型任务,或者希望充分利用多核处理器的能力,可以考虑使用Python的多进程模块multiprocessing。多进程可以绕过GIL的限制,因为每个进程都有自己的Python解释器和内存空间,从而实现真正的并行执行。

​ multiprocessing实现多进程并行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import multiprocessing 

def print_cube(num):
print("Cube: {}".format(num * num * num))

def print_square(num):
print("Square: {}".format(num * num))

if __name__ == "__main__":
# 创建进程
p1 = multiprocessing.Process(target=print_square, args=(10, ))
p2 = multiprocessing.Process(target=print_cube, args=(10, ))

# 开启进程
p1.start()
p2.start()

# 等待p1、p2线程都执行完
p1.join()
p2.join()

print("Done!")

C++字符串分割

字符串分割是编程中经常遇到的一个问题,采用C++语言实现字符串分割与其他语言有所不同,下面将采用C++语言实现字符串分割并对用到的字符串操作函数进行详细的描述。

  • 字符串分割Java实现

    Java通过调用String.split(String delimiter)方法可直接完成对字符串的分割。

    1
    2
    3
    4
    5
    6
    7
    public static void  main(String[] args){
    String str = "I am a good programmer!";
    String[] splited_string = str.split(" ");
    for(String s : splited_string){
    System.out.println(s);
    }
    }
  • 字符串分割Python实现

    python实现字符串分割代码更为间接,很多功能使用python实现仅用一行代码,江湖上称之为“一行流”。

    1
    print("I am a good programmer!".split(" "))
  • 字符串分割C++实现

    比起Java和python实现,采用C++实现字符串分割的过程则略为繁琐。需要用到c_str()、strcpy()、strtok()等方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <cstring>
    #include <string>
    #include <vector>
    #include <iostream>

    using namespace std;

    void splitString(const string &str, const string &split, vector<string> &res)
    {
    // str:要处理的字符串
    // split:分隔符
    // res:存放分割后的结果
    char *strc = new char[str.size() + 1];

    strcpy(strc, str.c_str());

    char *temp = strtok(strc, split.c_str());
    //strtok()的源码为char *__cdecl strtok(char * __restrict__ _Str,const char * __restrict__ _Delim)
    //strtok接收的参数时char*类型,因此进行字符串分割前需要将string类型的字符串转为char*类型

    while (temp != NULL)
    {
    res.push_back(string(temp));
    temp = strtok(NULL, split.c_str()); //strtok第一个参数传入NULL,使用之前保存的SAVE_PTR定位下一个待处理的字符的位置
    }
    delete[] strc;
    }

    int main(){
    vector<string> result;
    splitString("I am a good programmer!", " ", result);

    cout << "分割的子字符串的数量为:" << result.size() << endl;
    cout << "分割后的字符串为:" << endl;
    for(int i = 0; i < result.size(); ++i) {
    std::cout << result[i] << endl;
    }

    return 0;
    }
  • 字符串分割所使用方法详解

    • string.c_str()

      c_str()方法的原型如下:

      1
      const _CharT* c_str()

      该方法返回一个指向常量的指针,指针所指向的内容不能被修改,在字符串分割中主要使用该方法将string类型转为char*类型,与字符串分割函数strtok()方法的接收参数类型保持一致。注意:一定要使用strcpy()函数等来操作方法c_str()返回的指针。

    • string.strcpy()

      strcpy()方法的原型如下:

      1
      char * __cdecl strcpy(char * __restrict__ _Dest,const char * __restrict__ _Source);

      该方法将字符串Source复制到Dest。该方法线程不安全,c++提供了线程安全的字符串拷贝方法strcpy_s()。

    • string.strtok()

      strtok()方法的原型如下:

      1
      char *__cdecl strtok(char * __restrict__ _Str,const char * __restrict__ _Delim)

      其中,Str表示要进行分割的字符串,Delim为字符串分隔符,该方法将Delim中的字符作为分隔符对字符串Str进行分割。如果Str为空,则函数内部的SAVE_PTR指针在下一次调用中将作为下一个分割字符串的起始位置。其实这就说明在函数strtok()内部使用了一个静态变量SAVE_PTR指针,用以记录分割一次之后_String中下一个字符串的位置。这种方法导致了一个问题,就是strtok()函数线程不安全的(因为其函数内部使用到了静态存储区)。

      除此之外,从函数的定义,第一个传入参数_String定义为char*而不是const char*,就说明strtok()函数不保证不修改传入数据的内容。实际上,第一个参数_String传进来的字符串,是会被strtok()函数所修改的,因此调用strtok()函数的时候应当注意。

      c++中也提供了线程安全的字符串分割方法strtok_s,其原型如下:

      1
      char *__cdecl strtok_s(char *_Str,const char *_Delim,char **_Context);

      strtok_s()函数增加了一个参数_Context,这个参数就是相当于strtok()函数中内部定义的静态SAVE_PTR指针,用来传递对字符串_String的处理进行到了哪里。

参考文章:C/C++——字符串分割(strtok, strtok_s)

C++ OOP方法实现链式队列

  • 队列

    队列(Queue)是一个先进先出(First In First Out, FIFO)的数据结构,即先入队的元素先出队。队列有两种存储形式:顺序存储和链式存储,采用顺序存储方式的队列称为顺序队列,采用链式存储的队列称为链式队列。顺序队列采用数组存储队列中的元素,由队头指针head和队尾指针tail表示队列的头尾。链式队列采用链表实现,由头结点和若干个队列元素节点组成,头结点包括队头指针head、队尾指针和队列大小size三个域,head指针指向队头,tail指针指向队尾,队列元素节点由值域val和指针域next组成,val代表队列元素的值,next代表指向下一个队列结点的指针。其中,较为常用的是链式队列,下面将详细介绍链式队列的属性、方法以及C++代码实现。

  • 队列属性和方法介绍

    对队列进行抽象,可将其表示为包括队头指针、队尾指针和队列大小三个属性的数据结构。队列的基本操作包括入队、出队、队列判空、队列销毁、队列元素输出等,其伪代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    //队列定义
    queue:
    head, tail, size//head:队头指针、tail:队尾指针、size:队列大小
    enqueue(elem)//入队
    dequeue()//出队
    is_empty()//队列判空
    destory()//队列销毁
    traverse()//队列元素输出
  • 链式队列C++实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    #include<iostream>

    using namespace std;

    //队列中的元素结点
    class QueueNode{
    public:
    int val;//值域
    QueueNode *next;//指针域,指向下一个队列元素
    };

    //队列
    class Queue{
    private:
    int size;//队列大小

    public:
    QueueNode *head;//队头指针
    QueueNode *tail;//队尾指针

    Queue();//构造函数,完成对象初始化工作
    ~Queue();//析构函数
    void enqueue(int val);//入队
    int dequeue();//出队
    bool is_empty();//队列判空
    void destory();//销毁队列
    void traverse();//输出队列中的所有元素
    int get_size();//获取队列大小
    };

    //获取队列大小
    int Queue::get_size(){
    return this->size;
    }

    //构造函数
    Queue::Queue(){
    this->head = this->tail = NULL;
    this->size = 0;
    }

    //析构函数
    Queue::~Queue(){

    }

    //入队
    void Queue::enqueue(int val){
    QueueNode *node = (QueueNode *)malloc(sizeof(QueueNode));

    node->val = val;
    node->next = NULL;

    if(this->head == NULL){
    this->head = node;
    this->tail = node;
    }
    else{
    this->tail->next = node;
    this->tail = node;
    }

    this->size += 1;
    };

    //出队
    int Queue::dequeue(){
    if(this->is_empty() == 1){
    cout << "队列为空,没有元素可以出队,请先向队列中添加元素!" << endl;
    return -1;
    }

    int val = this->head->val;
    this->head = this->head->next;

    this->size -= 1;

    return val;
    }

    //队列判空
    bool Queue::is_empty(){
    if(this->size == 0){
    return true;
    }
    else
    {
    return false;
    }
    }

    //输出所有队列元素
    void Queue::traverse(){
    if(this->is_empty() == 1){
    cout << "队列为空!" << endl;
    return;
    }

    cout << "队列所有元素如下:" << endl;

    QueueNode *p_node = this->head;

    while(p_node != NULL)
    {
    cout << p_node->val << endl;
    p_node = p_node->next;
    }
    }

    //销毁队列
    void Queue::destory(){
    if(this->is_empty() == 1){
    cout << "队列中没有元素!" << endl;
    return;
    }
    int val = this->dequeue();
    while(val != -1){
    val = this->dequeue();
    }
    cout << "queue is destroyed!" << endl;

    }

    //主函数
    int main(){
    //队列定义
    Queue queue;

    //操作队列
    queue.enqueue(1);//元素入队列
    queue.enqueue(2);
    queue.enqueue(3);
    queue.enqueue(4);
    queue.enqueue(5);
    int val = queue.dequeue();//元素出队列
    cout << "出队元素为:" << val << endl;
    queue.traverse();//输出所有队列元素
    queue.destory();//销毁队列
    queue.traverse();
    cout << "queue.size:" << queue.get_size() << endl;//输出队列大小
    cout << queue.is_empty() << endl;//队列判空

    return 0;
    }

参考资料:队列的基本操作(顺序队列、循环队列、链式队列)

lambda表达式

  1. lambda表达式是什么?

    Lambda 表达式(通常称为 Lambda)是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。

  2. lambda表达式的语法

    1
    [capture list] (parameters) mutable throw() -> return-type {statement}
阅读全文 »