opencv之adaboost中的cvCreateMTStumpClassifier函数详解~

cvCreateMTStumpClassifier函数出自opencv中的haartraining程序,在adaboost(cvCreateTreeCascadeClassifier)的强分类器(icvCreateCARTStageClassifier)中被两次调用,该函数用于寻找最优弱分类器,或者说成计算最优haar特征。功能很明确,但是大家都知道的,opencv的代码绝大部分写的让人真心看不懂,这个函数算是haartraining中比较难以看懂的函数,局部变量达到20个之多,童鞋我也是不甘心,不甘心被这小小的函数所击溃,于是擦干泪水,仔细研读,终于恍然大悟,大彻大悟的同时,不忘回报CSDN博客,与朋友们分享。

最优弱分类器的计算过程,网上到处都有介绍,其实就是个穷举的过程,对每个特征多对应的训练样本的特征值进行排序,然后遍历每个特征值作为阈值,根据一定的公式(1.misclass 2.gini 3.entropy 4.least sum of squares)确定最优阈值,进一步确定最优特征,也就是最优弱分类器了。

但是opencv写的比较通用,所以有点让人摸不清头脑,它是这么干的:我先预计算一些haar特征(基本上为600个)的训练样本升序矩阵(trainData),然后先寻找这600个特征中的最优特征,但是总共的特征可能有1万多个,对于新特征那就只能在重新计算训练样本升序矩阵(mat),继续寻找最优特征。可能是程序一直跃跃欲试,想用并行的方法处理,导致了程序的局部变量增多(例如portion的引用)。这是值得大家注意的地方。

另外,上面说到cvCreateMTStumpClassifier函数被两次调用,一次是在cvCreateCARTClassifier中,一次是在icvCreateCARTStageClassifier中,其中,cvCreateCARTClassifier中trainData对应的是一个矩阵,而后者对应的是一个行向量。

注意上面两处,应该就不会被弄晕了,我直接上代码,并且做了比较详细的注释,这样子更加实在一些,希望能够对童鞋们有所帮助!

// 函数功能:计算最优弱分类器
CV_BOOST_IMPL
CvClassifier* cvCreateMTStumpClassifier( CvMat* trainData,      // 训练样本特征值
                      int flags,                                // 1.按行排列,0.按列排列
                      CvMat* trainClasses,                      // weakTrainVals(行向量)
                      CvMat* /*typeMask*/,                      // 搞不懂这么写有什么意义
                      CvMat* missedMeasurementsMask,            // 未知,很少用到
                      CvMat* compIdx,                           // 特征序列(必须为NULL)(行向量)
                      CvMat* sampleIdx,                         // 实际训练样本序列(行向量)
                      CvMat* weights,                           // 实际训练样本样本权重(行向量)
                      CvClassifierTrainParams* trainParams )    // 其它数据&参数
{
    CvStumpClassifier* stump = NULL;        // 弱分类器(桩)
    int m = 0;                              // 样本总数
    int n = 0;                              // 所有特征个数
    uchar* data = NULL;                     // trainData数据指针
    size_t cstep   = 0;                     // trainData一行字节数
    size_t sstep   = 0;                     // trainData元素字节数
    int    datan   = 0;                     // 预计算特征个数
    uchar* ydata = NULL;                    // trainClasses数据指针
    size_t ystep = 0;                       // trainClasses元素字节数
    uchar* idxdata = NULL;                  // sampleIdx数据指针
    size_t idxstep = 0;                     // sampleIdx单个元素字节数
    int    l = 0;                           // 实际训练样本个数
    uchar* wdata = NULL;                    // weights数据指针
    size_t wstep = 0;                       // weights元素字节数

    /* sortedIdx为事先计算好的特征值-样本矩阵,包含有预计算的所有HAAR特征对应于所有样本的特征值(按大小排列) */
    uchar* sorteddata = NULL;               // sortedIdx数据指针
    int    sortedtype    = 0;               // sortedIdx元素类型
    size_t sortedcstep   = 0;               // sortedIdx一行字节数
    size_t sortedsstep   = 0;               // sortedIdx元素字节数
    int    sortedn       = 0;               // sortedIdx行数(预计算特征个数)
    int    sortedm       = 0;               // sortedIdx列数(实际训练样本个数)

    char* filter = NULL;                    // 样本存在标示(行向量),如果样本存在则为1,否则为0
    int i = 0;

    int compidx = 0;                        // 每组特征的起始序号
    int stumperror;                         // 计算阈值方法:1.misclass 2.gini 3.entropy 4.least sum of squares
    int portion;                            // 每组特征个数,对所有特征n进行分组处理,每组portion个

    /* private variables */
    CvMat mat;                              // 新特征-样本矩阵
    CvValArray va;
    float lerror;                           // 阈值左侧误差
    float rerror;                           // 阈值右侧误差
    float left;
    float right;
    float threshold;                        // 阈值
    int optcompidx;                         // 最优特征

    float sumw;
    float sumwy;
    float sumwyy;

    /*临时变量,循环用*/
    int t_compidx;
    int t_n;

    int ti;
    int tj;
    int tk;

    uchar* t_data;                          // 指向data
    size_t t_cstep;                         // cstep
    size_t t_sstep;                         // sstep

    size_t matcstep;                        // mat一行字节数
    size_t matsstep;                        // mat元素字节数

    int* t_idx;                             // 样本序列
    /* end private variables */

    CV_Assert( trainParams != NULL );
    CV_Assert( trainClasses != NULL );
    CV_Assert( CV_MAT_TYPE( trainClasses->type ) == CV_32FC1 );
    CV_Assert( missedMeasurementsMask == NULL );
    CV_Assert( compIdx == NULL );

    // 计算阈值方法:1.misclass 2.gini 3.entropy 4.least sum of squares
    stumperror = (int) ((CvMTStumpTrainParams*) trainParams)->error;

    // 样本类别
    ydata = trainClasses->data.ptr;
    if( trainClasses->rows == 1 )
    {
        m = trainClasses->cols;
        ystep = CV_ELEM_SIZE( trainClasses->type );
    }
    else
    {
        m = trainClasses->rows;
        ystep = trainClasses->step;
    }

    // 样本权重
    wdata = weights->data.ptr;
    if( weights->rows == 1 )
    {
        CV_Assert( weights->cols == m );
        wstep = CV_ELEM_SIZE( weights->type );
    }
    else
    {
        CV_Assert( weights->rows == m );
        wstep = weights->step;
    }

    // sortedIdx为空,trainData为行向量(1*m);sortedIdx不为空,trainData为矩阵(m*datan);
    if( ((CvMTStumpTrainParams*) trainParams)->sortedIdx != NULL )
    {
        sortedtype =
            CV_MAT_TYPE( ((CvMTStumpTrainParams*) trainParams)->sortedIdx->type );
        assert( sortedtype == CV_16SC1 || sortedtype == CV_32SC1
                || sortedtype == CV_32FC1 );
        sorteddata = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->data.ptr;
        sortedsstep = CV_ELEM_SIZE( sortedtype );
        sortedcstep = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->step;
        sortedn = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->rows;
        sortedm = ((CvMTStumpTrainParams*) trainParams)->sortedIdx->cols;
    }

    if( trainData == NULL )                         // 为空的情况没有遇到
    {
        assert( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL );
        n = ((CvMTStumpTrainParams*) trainParams)->numcomp;
        assert( n > 0 );
    }
    else
    {
        assert( CV_MAT_TYPE( trainData->type ) == CV_32FC1 );
        data = trainData->data.ptr;
        if( CV_IS_ROW_SAMPLE( flags ) )             // trainData为矩阵
        {
            cstep = CV_ELEM_SIZE( trainData->type );
            sstep = trainData->step;
            assert( m == trainData->rows );
            datan = n = trainData->cols;
        }
        else                                        // trainData为向量
        {
            sstep = CV_ELEM_SIZE( trainData->type );
            cstep = trainData->step;
            assert( m == trainData->cols );
            datan = n = trainData->rows;
        }

        // trainData为矩阵,当trainData为向量时,datan = n = 1
        if( ((CvMTStumpTrainParams*) trainParams)->getTrainData != NULL )
        {
            n = ((CvMTStumpTrainParams*) trainParams)->numcomp;     // 总特征个数
        }
    }

    // 预计算特征个数一定要小于特征总数
    assert( datan <= n );

    if( sampleIdx != NULL )     // 已经剔除小权值样本
    {
        assert( CV_MAT_TYPE( sampleIdx->type ) == CV_32FC1 );
        idxdata = sampleIdx->data.ptr;
        idxstep = ( sampleIdx->rows == 1 )
            ? CV_ELEM_SIZE( sampleIdx->type ) : sampleIdx->step;
        l = ( sampleIdx->rows == 1 ) ? sampleIdx->cols : sampleIdx->rows;

        // sorteddata中存放的是所有训练样本,需要筛选出实际训练样本
        if( sorteddata != NULL )
        {
            filter = (char*) cvAlloc( sizeof( char ) * m );
            memset( (void*) filter, 0, sizeof( char ) * m );
            for( i = 0; i < l; i++ )
            {
                filter[(int) *((float*) (idxdata + i * idxstep))] = (char) 1;   // 存在则为1,不存在则为0
            }
        }
    }
    else                        // 未剔除小权值样本
    {
        l = m;
    }

    // 桩
    stump = (CvStumpClassifier*) cvAlloc( sizeof( CvStumpClassifier) );
    memset( (void*) stump, 0, sizeof( CvStumpClassifier ) );

    // 每组特征个数
    portion = ((CvMTStumpTrainParams*)trainParams)->portion;

    if( portion < 1 )
    {
        /* auto portion */
        portion = n;
        #ifdef _OPENMP
        portion /= omp_get_max_threads();
        #endif /* _OPENMP */
    }

    stump->eval = cvEvalStumpClassifier;
    stump->tune = NULL;
    stump->save = NULL;
    stump->release = cvReleaseStumpClassifier;

    stump->lerror = FLT_MAX;
    stump->rerror = FLT_MAX;
    stump->left  = 0.0F;
    stump->right = 0.0F;

    compidx = 0;

    // 并行计算,默认为关闭的
    #ifdef _OPENMP
    #pragma omp parallel private(mat, va, lerror, rerror, left, right, threshold,                                  optcompidx, sumw, sumwy, sumwyy, t_compidx, t_n,                                  ti, tj, tk, t_data, t_cstep, t_sstep, matcstep,                                   matsstep, t_idx)
    #endif /* _OPENMP */
    {
        lerror = FLT_MAX;
        rerror = FLT_MAX;
        left  = 0.0F;
        right = 0.0F;
        threshold = 0.0F;
        optcompidx = 0;

        sumw   = FLT_MAX;
        sumwy  = FLT_MAX;
        sumwyy = FLT_MAX;

        t_compidx = 0;
        t_n = 0;

        ti = 0;
        tj = 0;
        tk = 0;

        t_data = NULL;
        t_cstep = 0;
        t_sstep = 0;

        matcstep = 0;
        matsstep = 0;

        t_idx = NULL;

        mat.data.ptr = NULL;

        // 预计算特征个数小于特征总数,则说明存在新特征,用于计算样本的新特征,存放在mat中
        if( datan < n )
        {
            if( CV_IS_ROW_SAMPLE( flags ) )
            {
                mat = cvMat( m, portion, CV_32FC1, 0 );
                matcstep = CV_ELEM_SIZE( mat.type );
                matsstep = mat.step;
            }
            else
            {
                mat = cvMat( portion, m, CV_32FC1, 0 );
                matcstep = mat.step;
                matsstep = CV_ELEM_SIZE( mat.type );
            }
            mat.data.ptr = (uchar*) cvAlloc( sizeof( float ) * mat.rows * mat.cols );
        }

        // 将实际训练样本序列存放进t_idx
        if( filter != NULL || sortedn < n )
        {
            t_idx = (int*) cvAlloc( sizeof( int ) * m );
            if( sortedn == 0 || filter == NULL )
            {
                if( idxdata != NULL )
                {
                    for( ti = 0; ti < l; ti++ )
                    {
                        t_idx[ti] = (int) *((float*) (idxdata + ti * idxstep));
                    }
                }
                else
                {
                    for( ti = 0; ti < l; ti++ )
                    {
                        t_idx[ti] = ti;
                    }
                }
            }
        }

        #ifdef _OPENMP
        #pragma omp critical(c_compidx)
        #endif /* _OPENMP */

        // 初始化计算特征范围
        {
            t_compidx = compidx;
            compidx += portion;
        }

        // 寻找最优弱分类器
        while( t_compidx < n )
        {
            t_n = portion;                      // 每组特征个数
            if( t_compidx < datan )             // 已经计算过的特征
            {
                t_n = ( t_n < (datan - t_compidx) ) ? t_n : (datan - t_compidx);
                t_data = data;
                t_cstep = cstep;
                t_sstep = sstep;
            }
            else                                // 新特征
            {
                t_n = ( t_n < (n - t_compidx) ) ? t_n : (n - t_compidx);
                t_cstep = matcstep;
                t_sstep = matsstep;
                t_data = mat.data.ptr - t_compidx * ((size_t) t_cstep );

                // 计算每个新特征对应于每个训练样本的特征值
                ((CvMTStumpTrainParams*)trainParams)->getTrainData( &mat,
                        sampleIdx, compIdx, t_compidx, t_n,
                        ((CvMTStumpTrainParams*)trainParams)->userdata );
            }

            /* 预计算特征部分,直接寻找最优特征,也就是传说中的最优弱分类器 */
            if( sorteddata != NULL )
            {
                if( filter != NULL )    // 需要提取实际训练样本
                {
                    switch( sortedtype )
                    {
                        case CV_16SC1:<span style="white-space:pre">	</span>// 这里重复度很高,只注释一个分支,剩下的都一个道理

                            // 从一组特征(datan个预计算特征)中寻找最优特征
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                tk = 0;

                                // 提取实际训练样本
                                for( tj = 0; tj < sortedm; tj++ )
                                {
                                    int curidx = (int) ( *((short*) (sorteddata
                                            + ti * sortedcstep + tj * sortedsstep)) );
                                    if( filter[curidx] != 0 )
                                    {
                                        t_idx[tk++] = curidx;
                                    }
                                }

                                // 如果findStumpThreshold_32s返回值为1, 则更新最优特征
                                if( findStumpThreshold_32s[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        (uchar*) t_idx, sizeof( int ), tk,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        case CV_32SC1:
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                tk = 0;
                                for( tj = 0; tj < sortedm; tj++ )
                                {
                                    int curidx = (int) ( *((int*) (sorteddata
                                            + ti * sortedcstep + tj * sortedsstep)) );
                                    if( filter[curidx] != 0 )
                                    {
                                        t_idx[tk++] = curidx;
                                    }
                                }
                                if( findStumpThreshold_32s[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        (uchar*) t_idx, sizeof( int ), tk,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        case CV_32FC1:
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                tk = 0;
                                for( tj = 0; tj < sortedm; tj++ )
                                {
                                    int curidx = (int) ( *((float*) (sorteddata
                                            + ti * sortedcstep + tj * sortedsstep)) );
                                    if( filter[curidx] != 0 )
                                    {
                                        t_idx[tk++] = curidx;
                                    }
                                }
                                if( findStumpThreshold_32s[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        (uchar*) t_idx, sizeof( int ), tk,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        default:
                            assert( 0 );
                            break;
                    }
                }
                else            // 所有训练样本均参与计算
                {
                    switch( sortedtype )
                    {
                        case CV_16SC1:
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                if( findStumpThreshold_16s[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        sorteddata + ti * sortedcstep, sortedsstep, sortedm,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        case CV_32SC1:
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                if( findStumpThreshold_32s[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        sorteddata + ti * sortedcstep, sortedsstep, sortedm,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        case CV_32FC1:
                            for( ti = t_compidx; ti < MIN( sortedn, t_compidx + t_n ); ti++ )
                            {
                                if( findStumpThreshold_32f[stumperror](
                                        t_data + ti * t_cstep, t_sstep,
                                        wdata, wstep, ydata, ystep,
                                        sorteddata + ti * sortedcstep, sortedsstep, sortedm,
                                        &lerror, &rerror,
                                        &threshold, &left, &right,
                                        &sumw, &sumwy, &sumwyy ) )
                                {
                                    optcompidx = ti;
                                }
                            }
                            break;
                        default:
                            assert( 0 );
                            break;
                    }
                }
            }

            /* 新特征部分,要对样本特征值进行排序,然后再寻找最优特征 */
            ti = MAX( t_compidx, MIN( sortedn, t_compidx + t_n ) );
            for( ; ti < t_compidx + t_n; ti++ )
            {
                va.data = t_data + ti * t_cstep;
                va.step = t_sstep;

                // 对样本特征值进行排序
                icvSortIndexedValArray_32s( t_idx, l, &va );

                // 继续寻找最优特征
                if( findStumpThreshold_32s[stumperror](
                        t_data + ti * t_cstep, t_sstep,
                        wdata, wstep, ydata, ystep,
                        (uchar*)t_idx, sizeof( int ), l,
                        &lerror, &rerror,
                        &threshold, &left, &right,
                        &sumw, &sumwy, &sumwyy ) )
                {
                    optcompidx = ti;
                }
            }
            #ifdef _OPENMP
            #pragma omp critical(c_compidx)
            #endif /* _OPENMP */

            // 更新特征计算范围
            {
                t_compidx = compidx;
                compidx += portion;
            }
        }

        #ifdef _OPENMP
        #pragma omp critical(c_beststump)
        #endif /* _OPENMP */

        // 设置最优弱分类器
        {
            if( lerror + rerror < stump->lerror + stump->rerror )
            {
                stump->lerror    = lerror;
                stump->rerror    = rerror;
                stump->compidx   = optcompidx;
                stump->threshold = threshold;
                stump->left      = left;
                stump->right     = right;
            }
        }

        /* free allocated memory */
        if( mat.data.ptr != NULL )
        {
            cvFree( &(mat.data.ptr) );
        }
        if( t_idx != NULL )
        {
            cvFree( &t_idx );
        }
    } /* end of parallel region */

    /* END */

    /* free allocated memory */
    if( filter != NULL )
    {
        cvFree( &filter );
    }

    if( ((CvMTStumpTrainParams*) trainParams)->type == CV_CLASSIFICATION_CLASS )
    {
        stump->left = 2.0F * (stump->left >= 0.5F) - 1.0F;
        stump->right = 2.0F * (stump->right >= 0.5F) - 1.0F;
    }

    return (CvClassifier*) stump;
}

其实,我现在一直认为,寻找弱分类器是一个很easy的过程,根本不需要这么多行代码,这么多局部变量,但是仔细阅读之后发现,opencv还是很牛的,这段代码的通用性比较强大,兼顾了并行操作可能性。可以应对多特征弱分类器,代码结构也是比较爽快的,尤其是其在条件宏、函数指针方面的应用,令人羡慕异常。今后还要继续研读opencv代码,对编程素养的提高,绝对有很大帮助。

如果有啥问题,还请不吝赐教,私聊,评论,哪怕直接骂都可以!!!!

时间: 2024-10-12 23:43:05

opencv之adaboost中的cvCreateMTStumpClassifier函数详解~的相关文章

delphi中的Format函数详解

首先看它的声明:[[email protected]][@21ki!] function Format(const Format: string; const Args: array of const): string; overload;[[email protected]][@21ki!] 事实上Format方法有两种形式,另外一种是三个参数的,主要区别在于它是线程安全的,[[email protected]][@21ki!]但并不多用,所以这里只对第一个介绍:[[email protect

Mysql中关于 group_concat函数详解

group_concat()主要功能:能将相同的行组合起来 完整的语法如下: group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator '分隔符']) 基本查询 Sql代码   select * from aa; +------+------+| id| name |+------+------+|1 | 10||1 | 20||1 | 20||2 | 20||3 | 200 ||3 | 500 |+------+---

Python中的getattr()函数详解:

Python中的getattr()函数详解: getattr(object, name[, default]) -> value Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception i

Oracle中的substr()函数 详解及应用

注:本文来源于<Oracle中的substr()函数 详解及应用> 1)substr函数格式   (俗称:字符截取函数) 格式1: substr(string string, int a, int b); 格式2:substr(string string, int a) ; 解释: 格式1:        1.string 需要截取的字符串         2.a 截取字符串的开始位置(注:当a等于0或1时,都是从第一位开始截取)        3.b 要截取的字符串的长度 格式2:     

linux内核中的hook函数详解

在编写linux内核中的网络模块时,用到了钩子函数也就是hook函数.现在来看看linux是如何实现hook函数的.     先介绍一个结构体: struct nf_hook_ops,这个结构体是实现钩子函数必须要用到的结构体,其实际的定义为: 其中的成员信息为: hook  :是一个函数指针,可以将自定义的函数赋值给它,来实现当有数据包到达是调用你自定义的函数.自定义函数的返回值为: owner:是模块的所有者,一般owner = THIS_MODULE ;     pf   :是protoc

pandas中的isin函数详解

原文链接:http://www.datastudy.cc/to/69 今天有个同学问到,not in 的逻辑,想用 SQL 的select c_xxx_s from t1 left join t2 on t1.key=t2.key where t2.key is NULL 在 Python 中的逻辑来实现,实现了 left join 了(直接用join方法),但是不知道怎么实现where key is NULL. 其实,实现not in的逻辑,不用那么复杂,直接用isin函数再取反即可,下面就是

SQL中的ISNULL函数详解及用途

SQL中有多种多样的函数,下面将为您介绍SQL中的ISNULL函数,包括其语法.注释.返回类型等,供您参考,希望对您学习SQL能够有所帮助 ISNULL 使用指定的替换值替换 NULL. 语法 ISNULL ( check_expression , replacement_value ) 参数 check_expression 将被检查是否为 NULL的表达式.check_expression 可以是任何类型的. replacement_value 在 check_expression 为 NU

oracle中的trim()函数详解

1.先看一下Oracle TRIM函数的完整语法描述 TRIM([ { { LEADING | TRAILING | BOTH }[ trim_character ]| trim_character}FROM]trim_source) 以上语法引自于Oracle 10gR2官方文档:http://download.oracle.com/docs/ ... 0/img_text/trim.htm单从这个语法定义上我们就可以看出,小小的Oracle TRIM函数蕴含了更多可定制的功能.一一展示,供参

Python3中的super()函数详解

关于Python3中的super()函数 我们都知道,在Python3中子类在继承父类的时候,当子类中的方法与父类中的方法重名时,子类中的方法会覆盖父类中的方法, 那么,如果我们想实现同时调用父类和子类中的同名方法,就需要使用到super()这个函数,用法为super().函数名() 下面是一个例子: class A1(): def go(self): print("go A1 go") class A2(): def go(self): print("go A2 go&qu