欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解python虛擬機之多繼承與?mro

 更新時間:2023年05月16日 08:24:46   作者:一無是處的研究僧  
在本篇文章當中將主要給大家介紹?python?當中的多繼承和mro,通過介紹在多繼承當中存在的問題就能夠理解在cpython當中引入c3算法的原因了,從而能夠幫助大家更好的了理解mro,需要的朋友可以參考下

python 繼承的問題

繼承是一種面向對象編程的概念,它可以讓一個類(子類)繼承另一個類(父類)的屬性和方法。子類可以重寫父類的方法,或者添加自己的方法和屬性。這種機制使得代碼可以更加模塊化和易于維護。在 Python 中,繼承是通過在子類的定義中指定父類來實現(xiàn)的。例如:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
    def speak(self):
        return "woof"

在這個例子中,我們定義了一個 Animal 類和一個 Dog 類。Dog 類繼承了 Animal 類,并且重寫了 speak 方法。此時,如果我們創(chuàng)建一個 Dog 實例并調用 speak 方法,它將返回 "woof"

父類的修改會影響子類

當你修改父類的代碼時,可能會影響到繼承自它的子類。這是因為子類繼承了父類的所有屬性和方法,包括它們的實現(xiàn)。如果你修改了父類的實現(xiàn),可能會導致子類的行為發(fā)生變化。因此,在修改父類代碼時,你需要仔細考慮這個問題,并盡量避免對子類的影響。

多層繼承的復雜性

在面向對象編程中,有時需要多層繼承,即一個類繼承自另一個繼承自另一個類。這會導致代碼的復雜性增加,因為你需要考慮每個類之間的關系和可能出現(xiàn)的命名沖突。另外,多層繼承也會增加代碼的耦合性,使得代碼難以重構和維護。

多繼承當中一個非常經(jīng)典的問題就是棱形繼承,菱形繼承是指一個子類繼承了兩個父類,而這兩個父類又繼承自同一個基類的情況,如下圖所示:

   A
  / \
 B   C
  \ /
   D

在這種情況下,子類 D 會繼承兩份來自基類 A 的屬性和方法,這可能會導致一些不必要的問題。例如,如果基類 A 中有一個名為 foo() 的方法,而基類 BC 都分別重寫了這個方法,并在子類 D 中調用了這個方法,那么子類 D 就無法確定應該調用哪個版本的 foo() 方法。

另外一種情況就是在多繼承的時候不同的基類定義了同樣的方法,那么子類就無法確定應該使用哪個父類的實現(xiàn)。例如,考慮下面這個示例:

class A:
    def method(self):
        print('A')

class B:
    def method(self):
        print('B')

class C(A, B):
    pass

c = C()
c.method()  # 輸出什么?

在這個示例中,類 C 繼承了類 A 和類 Bmethod 方法,但是這兩個方法具有相同的方法名和參數(shù)列表。因此,當我們調用 c.method() 方法時,Python 將無法確定應該使用哪個父類的實現(xiàn)。

為了解決上面所提到的問題,Python 提供了方法解析順序(Method Resolution Order,MRO)算法,這個算法可以幫助 Python 確定方法的調用順序,也就是在調用的時候確定調用哪個基類的方法。

MRO

在 Python 中,多重繼承會引起 MRO(Method Resolution Order,方法解析順序)問題。當一個類繼承自多個父類時,Python 需要確定方法調用的順序,即優(yōu)先調用哪個父類的方法。為了解決這個問題,Python 實現(xiàn)了一種稱為 C3算法的 MRO 算法,它是一種確定方法解析順序的算法。

我們先來體驗使用一下 python 的 mro 的結果是什么樣的,下面是一個使用多重繼承的示例:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

在這個示例中,D 類繼承自 B 類和 C 類,而 B 和 C 又都繼承自 A 類。因此,D 類的 MRO 列表如下所示:

[D, B, C, A]

這個列表的解析順序為 D -> B -> C -> A,也就是說,當我們調用 D 類的方法時,Python 會首先查找 D 類的方法,然后查找 B 類的方法,再查找 C 類的方法,最后查找 A 類的方法。

你可能會有疑問,為什么 cpython 需要這樣去進行解析,為什么不在解析完類 B 之后直接解析 A,還要解析完類 C 之后再去解析 A,我們來看下面的代碼:

class A:

    def method_a(self):
        print("In class A")

class B(A):

    pass

class C(A):

    def method_a(self):
        print("In class C")

class D(B, C):
    pass

if __name__ == '__main__':
    obj = D()
    print(D.mro())
    obj.method_a()

上面的代碼輸出結果如下所示:

[<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
In class C

在上面的代碼當中繼承體系也是棱形繼承,在類 A 當中有一個方法 method_a 其中類 B 繼承了 A 但是沒有重寫這個方法,類 B 繼承自 A 但是重寫了這個方法,類 D 同時繼承了類 B 和類 C,當對象 obj 調用方法 method_a 的時候發(fā)現(xiàn)在類 B 當中沒有這個方法,這個時候如果直接在類 A 查找這個方法在很多情況下并不是我們想要的,因為 D 繼承了 C 如果 C 重寫了這個方法的話我們應該需要調用類 C 的 method_a 方法,而不是調用類 A 的 method_a 。

從上面的案例我們可以知道,一個子類不能夠跨過他的直接父類(D 的直接父類就是 B 和 C)調用更上層的方法,而是先需要在所有的直接父類查看是否有這個方法,在 cpython 的 mro 實現(xiàn)當中是能夠保證這一點的,這種性質叫做“單調性”(monotonicity)。

C3 算法

C3 算法是 Python 中使用的 MRO 算法,它可以用來確定一個類的方法解析順序。首先我們需要知道的就是,當一個類所繼承的多個類當中有相同的基類或者定義了名字相同的方法,會是一個問題。mro 就是我給他一個對象,他會給我們返回一個類的序列,當我們打算從對象當中獲取一個屬性或者方法的時候就會順著這個序列從左往右進行查找,若查找成功則返回,否則繼續(xù)查找后續(xù)的類。

現(xiàn)在我們來詳細介紹一下 C3 算法的實現(xiàn)細節(jié),這個算法的主要流程是一個遞歸求解 mro 的過程,假設 A 繼承自 [B, C, D, E, F],那么 C3 算法求 mro 的實現(xiàn)流程如下所示:

  • mro(A) = [A] + merge(mro(B), mro(C), mro(D), mro(E), mro(F), [B, C, D, E, F] )
  • merge 函數(shù)的原理是遍歷傳入的序列,找到一個這樣的序列,序列的第一個類型只能在其他序列的頭部,或者沒有在其他序列出現(xiàn),并且將這個序列加入到 merge 函數(shù)的返回序列當中,并且將這個類從所有序列當中刪除,重復這個步驟直到所有的序列都為空。
 class O
 class A extends O
 class B extends O
 class C extends O
 class D extends O
 class E extends O
 class K1 extends A, B, C
 class K2 extends D, B, E

對 K2 求 mro 序列的結果如下所示:

L(K2) := [K2] + merge(L(D), L(B), L(E), [D, B, E])
      = [K2] + merge([D, O], [B, O], [E, O], [D, B, E])    // 選擇D
      = [K2, D] + merge([O], [B, O], [E, O], [B, E])       // 不選O,選擇B 因為 O 在 [B, O] 和 [E, O] 當中出現(xiàn)了而且不是第一個
      = [K2, D, B] + merge([O], [O], [E, O], [E])          // 不選O,選擇E 因為 O 在 [E, O] 當中出現(xiàn)了而且不是第一個
      = [K2, D, B, E] + merge([O], [O], [O])               // 選擇O
      = [K2, D, B, E, O]

我們自己實現(xiàn)的 mro 算法如下所示:

from typing import Iterable


class A:
    pass


class B(A):

    pass


class C(A):
    pass


class D(B, C):
    pass


def mro(_type: type):
    bases = _type.__bases__
    lin_bases = []
    for base in bases:
        lin_bases.append(mro(base))
    lin_bases.append(list(bases))
    return [_type] + merge(lin_bases)


def merge(types: Iterable[Iterable[type]]):
    res = []
    seqs = types
    while True:
        seqs = [s for s in seqs if s]
        if not seqs:
            # if seqs is empty
            return res
        for seq in seqs:
            head = seq[0]
            if not [s for s in seqs if head in s[1:]]:
                break
        else:
            # 如果遍歷完所有的類還是找不到一個合法的類 則說明 mro 算法失敗 這個繼承關系不滿足 C3 算法的要求
            raise Exception('can not find mro sequence')
        res.append(head)
        for s in seqs:
            if s[0] == head:
                del s[0]


if __name__ == '__main__':
    print(D.mro())
    print(mro(D))
    assert D.mro() == mro(D)

上面的程序的輸出結果如下所示:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

只需要理解求解 mro 序列的過程,上面的代碼比較容易理解,首先就是遞歸求解各個父類的 mro 序列,然后將他們按照從左到右的順序放入到一個列表當中,最終將父類的 mro 序列進行 merge 操作,返回結果即可。

merge 函數(shù)的主要操作為,按照從左到右的順序遍歷各個父類的 mro 序列,如果第一個類沒有在其他父類的 mro 序列當中出現(xiàn),或者是其他父類 mro 序列當中的第一個類的話就可以將這個類加入到返回的 mro 列表當中,否則選擇下一個類的 mro 序列進行相同的操作,直到找到一個符合上面條件的類,如果遍歷完所有的父類還是沒有找到的話那么就報錯。

Mypy 針對 mro 實現(xiàn)

mypy 是一個 python 類型的靜態(tài)分析工具,它也實現(xiàn)了 C3 算法用于計算 mro ,下面是它的代碼實現(xiàn)。

class MroError(Exception):
    """Raised if a consistent mro cannot be determined for a class."""


def linearize_hierarchy(
    info: TypeInfo, obj_type: Callable[[], Instance] | None = None
) -> list[TypeInfo]:
    # TODO describe
    if info.mro:	
        return info.mro
    bases = info.direct_base_classes()
    if not bases and info.fullname != "builtins.object" and obj_type is not None:
        # Probably an error, add a dummy `object` base class,
        # otherwise MRO calculation may spuriously fail.
        bases = [obj_type().type]
    lin_bases = []
    for base in bases:
        assert base is not None, f"Cannot linearize bases for {info.fullname} {bases}"
        lin_bases.append(linearize_hierarchy(base, obj_type))
    lin_bases.append(bases)
    return [info] + merge(lin_bases)


def merge(seqs: list[list[TypeInfo]]) -> list[TypeInfo]:
    seqs = [s.copy() for s in seqs]
    result: list[TypeInfo] = []
    while True:
        seqs = [s for s in seqs if s]
        if not seqs:
            return result
        for seq in seqs:
            head = seq[0]
            if not [s for s in seqs if head in s[1:]]:
                break
        else:
            raise MroError()
        result.append(head)
        for s in seqs:
            if s[0] is head:
                del s[0]

上面的函數(shù) linearize_hierarchy 就是用于求解 mro 的函數(shù),上面的實現(xiàn)整體過程和我們自己實現(xiàn)的 C3 算法是一樣的,首先遞歸調用 linearize_hierarchy 計算得到父類的 mro 序列,最后將得到的 mro 進行 merge 操作。

cpython 虛擬機 mro 實現(xiàn)

在本小節(jié)當中主要給大家介紹一下在 cpython 當中 C 語言層面是如何實現(xiàn) mro 的。需要知道的 cpython 對于 mro 的實現(xiàn)也是使用我們在上面提到的算法,算法原理也是一樣的。

static PyObject *
mro_implementation(PyTypeObject *type)
{
    PyObject *result;
    PyObject *bases;
    PyObject **to_merge;
    Py_ssize_t i, n;

    if (type->tp_dict == NULL) {
        if (PyType_Ready(type) < 0)
            return NULL;
    }
    // 獲取類型 type 的所有父類
    bases = type->tp_bases;
    // bases 的數(shù)據(jù)類型為 tuple
    assert(PyTuple_Check(bases));
    n = PyTuple_GET_SIZE(bases);
    // 檢查基類的 mro 序列是否計算出來了
    for (i = 0; i < n; i++) {
        PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
        if (base->tp_mro == NULL) {
            PyErr_Format(PyExc_TypeError,
                         "Cannot extend an incomplete type '%.100s'",
                         base->tp_name);
            return NULL;
        }
        assert(PyTuple_Check(base->tp_mro));
    }
    // 如果是單繼承 也就是只繼承了一個類 那么就可以走 fast path
    if (n == 1) {
        /* Fast path: if there is a single base, constructing the MRO
         * is trivial.
         */
        PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, 0);
        Py_ssize_t k = PyTuple_GET_SIZE(base->tp_mro);
        result = PyTuple_New(k + 1);
        if (result == NULL) {
            return NULL;
        }
        // 直接將父類的 mro 序列加在 當前類的后面 即 mro = [當前類, 父類的 mro 序列]
        Py_INCREF(type);
        PyTuple_SET_ITEM(result, 0, (PyObject *) type);
        for (i = 0; i < k; i++) {
            PyObject *cls = PyTuple_GET_ITEM(base->tp_mro, i);
            Py_INCREF(cls);
            PyTuple_SET_ITEM(result, i + 1, cls);
        }
        return result;
    }

    /* This is just a basic sanity check. */
    if (check_duplicates(bases) < 0) {
        return NULL;
    }

    /* Find a superclass linearization that honors the constraints
       of the explicit tuples of bases and the constraints implied by
       each base class.

       to_merge is an array of tuples, where each tuple is a superclass
       linearization implied by a base class.  The last element of
       to_merge is the declared tuple of bases.
    */
  
    // 如果是多繼承就要按照 C3 算法進行實現(xiàn)了
    to_merge = PyMem_New(PyObject *, n + 1);
    if (to_merge == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    // 得到所有父類的 mro 序列,并將其保存到 to_merge 數(shù)組當中
    for (i = 0; i < n; i++) {
        PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
        to_merge[i] = base->tp_mro;
    }
    // 和前面我們自己實現(xiàn)的算法一樣 也要將所有基類放在數(shù)組的最后
    to_merge[n] = bases;

    result = PyList_New(1);
    if (result == NULL) {
        PyMem_Del(to_merge);
        return NULL;
    }

    Py_INCREF(type);
    PyList_SET_ITEM(result, 0, (PyObject *)type);
    // 合并數(shù)組
    if (pmerge(result, to_merge, n + 1) < 0) {
        Py_CLEAR(result);
    }

    PyMem_Del(to_merge);
   // 將得到的結果返回
    return result;
}

在上面函數(shù)當中我們可以分析出來整個代碼的流程和我們在前面提到的 C3 算法一樣,

static int
pmerge(PyObject *acc, PyObject **to_merge, Py_ssize_t to_merge_size)
{
    int res = 0;
    Py_ssize_t i, j, empty_cnt;
    int *remain;
    // remain[i] 表示 to_merge[i] 當中下一個可用的類 也就是說 0 - i-1 的類已經(jīng)被處理合并了
    /* remain stores an index into each sublist of to_merge.
       remain[i] is the index of the next base in to_merge[i]
       that is not included in acc.
    */
    remain = PyMem_New(int, to_merge_size);
    if (remain == NULL) {
        PyErr_NoMemory();
        return -1;
    }
    // 初始化的時候都是從第一個類開始的 所以下標初始化成 0
    for (i = 0; i < to_merge_size; i++)
        remain[i] = 0;

  again:
    empty_cnt = 0;
    for (i = 0; i < to_merge_size; i++) {
        PyObject *candidate;

        PyObject *cur_tuple = to_merge[i];

        if (remain[i] >= PyTuple_GET_SIZE(cur_tuple)) {
            empty_cnt++;
            continue;
        }

        /* Choose next candidate for MRO.

           The input sequences alone can determine the choice.
           If not, choose the class which appears in the MRO
           of the earliest direct superclass of the new class.
        */
        // 得到候選的類
        candidate = PyTuple_GET_ITEM(cur_tuple, remain[i]);
        // 查看各個基類的 mro 序列的尾部當中是否包含 condidate 尾部就是除去剩下的 mro 序列當中的第一個 剩下的類就是尾部當中含有的類 tail_contains 就是檢查尾部當中是否包含 condidate
        for (j = 0; j < to_merge_size; j++) {
            PyObject *j_lst = to_merge[j];
            if (tail_contains(j_lst, remain[j], candidate))
                // 如果尾部當中包含 condidate 則說明當前的 candidate 不符合要求需要查看下一個 mro 序列的第一個類 看看是否符合要求 如果還不符合就需要找下一個 再進行重復操作
                goto skip; /* continue outer loop */
        }
        // 找到了則將 condidate 加入的返回的結果當中
        res = PyList_Append(acc, candidate);
        if (res < 0)
            goto out;
        // 更新 remain 數(shù)組,在前面我們提到了當加入一個 candidate 到返回值當中的時候需要將這個類從所有的基類的 mro 序列當中刪除 (事實上只可能刪除各個 mro 序列當中的第一個類)因此需要更新 remain 數(shù)組
        for (j = 0; j < to_merge_size; j++) {
            PyObject *j_lst = to_merge[j];
            if (remain[j] < PyTuple_GET_SIZE(j_lst) &&
                PyTuple_GET_ITEM(j_lst, remain[j]) == candidate) {
                remain[j]++;
            }
        }
        goto again;
      skip: ;
    }

    if (empty_cnt != to_merge_size) {
        set_mro_error(to_merge, to_merge_size, remain);
        res = -1;
    }

  out:
    PyMem_Del(remain);

    return res;
}

static int
tail_contains(PyObject *tuple, int whence, PyObject *o)
{
    Py_ssize_t j, size;
    size = PyTuple_GET_SIZE(tuple);

    for (j = whence+1; j < size; j++) {
        if (PyTuple_GET_ITEM(tuple, j) == o)
            return 1;
    }
    return 0;
}

再談 MRO

在本篇文章當中主要給大家介紹了多繼承存在的問題,以及介紹了在 python 當中的解決方案 C3 算法。之所以被稱作 C3 算法,主要是因為這個算法有以下三點特性:

  • a consistent extended precedence graph
  • preservation of local precedence order
  • fitting a monotonicity criterion

我們使用上面的圖來分析一下上面的三個特性是在說明什么,在上面的繼承關系當中類 A 當中有一個方法 method,類 B 和類 C 繼承自類 A 并且類 C 重寫了 method 方法,類 D 繼承了 B 和 C 。

  • monotonicity 單調性,這個特性主要是說明子類不能夠跨過父類直接調用父類的父類的方法,比如在上面的類當中,當類 D 調用 method 方法的時候,調用的是類 C 的 method 方法而不是類 A 的 method 方法,雖然類 B 沒有 method 而且類 A 有 method 方法,但是子類 D 不能夠跨過父類 B 直接調用 類 A 的方法,必須檢查類 C 是否有這個方法,如果有就調用 C 的,如果 B C 都沒有才調用 A 的。
  • preservation of local precedence order(保留局部優(yōu)先順序),這一點表示 mro 要保證按照繼承先后的順序去查找,也就是說先繼承的先查找,比如 D(B, C) 那么如果同一個方法類 B 和類 C 都有,那么就會優(yōu)先使用 B 當中的方法。
  • a consistent extended precedence graph,這一點是相對來說比較復雜的,這個特性也是一個關于優(yōu)先級的特性,是之前局部優(yōu)先的擴展,他的意思是如果兩個類 A B 有相同的方法,如果 A 或者 A 的子類出現(xiàn)在 B 或者 B 的子類之前,那么 A 的優(yōu)先級比 B 高。比如說對于下圖當中的繼承關系 editable-scrollable-pane 繼承自 scrollable-pane 和 editable-pane,editable-pane繼承自 editing-mixin 和 pane,scrollable-pane 繼承自 scrolling-mixin 和 pane?,F(xiàn)在有一個 editable-scrollable-pane 對象調用一個方法,如果這個方法只在 scrolling-mixin 和 editing-mixin 當中出現(xiàn),那么會調用 scrolling-mixin 當中的方法,不會調用 editing-mixin 當中的方法。這是因為對于 editable-scrollable-pane 對象來說 scrollable-pane 在 editable-pane 前面,而前者是 scrolling-mixin 的子類,后者是 editing-mixin 的子類,這是符合前面我們所談到的規(guī)則。

上圖來自論文 A Monotonic Superclass Linearization for Dylan ,這篇論文便是 C3 算法的出處。如果你對這篇論文感興趣的話,論文下載地址為 c3-linearization.pdf (opendylan.org)。

總結

在本篇文章當中主要給大家詳細分析了 python 當中是如何解決多繼承存在的問題的,并且詳細分析了 C3 算法以及他在 python 和虛擬機層面的實現(xiàn),最后簡要介紹了 C3 算法的三個特性,通過仔細分析這三個特性可以幫助我們深入理解整個繼承樹的調用鏈,當然在實際編程當中最好使用更簡潔的繼承方式,這也可以避免很多問題。

以上就是深入理解python虛擬機之多繼承與 mro的詳細內容,更多關于python虛擬機 多繼承與 mro的資料請關注腳本之家其它相關文章!

相關文章

  • python多進程(加入進程池)操作常見案例

    python多進程(加入進程池)操作常見案例

    這篇文章主要介紹了python多進程(加入進程池)操作,結合常見案例形式分析了Python多進程復制文件、加入進程池及多進程聊天等相關操作技巧,需要的朋友可以參考下
    2019-10-10
  • 詳解如何在Matplotlib中繪制平滑曲線

    詳解如何在Matplotlib中繪制平滑曲線

    這篇文章主要為大家詳細介紹了如何在Matplotlib中繪制平滑曲線,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下
    2024-04-04
  • python 定時器,輪詢定時器的實例

    python 定時器,輪詢定時器的實例

    今天小編就為大家分享一篇python 定時器,輪詢定時器的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-02-02
  • pygame編寫音樂播放器的實現(xiàn)代碼示例

    pygame編寫音樂播放器的實現(xiàn)代碼示例

    這篇文章主要介紹了pygame編寫音樂播放器的實現(xiàn)代碼示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • python實現(xiàn)數(shù)據(jù)分析與建模

    python實現(xiàn)數(shù)據(jù)分析與建模

    這篇文章主要介紹了python實現(xiàn)數(shù)據(jù)分析與建模功能,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-07-07
  • python socket網(wǎng)絡編程之粘包問題詳解

    python socket網(wǎng)絡編程之粘包問題詳解

    這篇文章主要介紹了python socket網(wǎng)絡編程之粘包問題詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • Python語言異常處理測試過程解析

    Python語言異常處理測試過程解析

    這篇文章主要介紹了Python語言異常處理測試過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • 解析Python中的__getitem__專有方法

    解析Python中的__getitem__專有方法

    __getitem__是Python雙下劃線包圍的special method之一,這里我們就來解析Python中的__getitem__專有方法的使用,需要的朋友可以參考下:
    2016-06-06
  • Flask實現(xiàn)跨域請求的處理方法

    Flask實現(xiàn)跨域請求的處理方法

    這篇文章主要介紹了Flask實現(xiàn)跨域請求的處理方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • 基于python實現(xiàn)ROC曲線繪制廣場解析

    基于python實現(xiàn)ROC曲線繪制廣場解析

    這篇文章主要介紹了基于python實現(xiàn)ROC曲線繪制廣場解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06

最新評論