python小白,問一個關於可變類型和不可變類型底層的問題
给我你的怀抱
给我你的怀抱 2017-06-12 09:21:12
0
4
710

第一段程式碼:

#
a = "hello"   #定义一个字符串的变量
print(id(a))  #第一次的地址
print(a)      #a = hello
a = a.upper() # 单纯的a.upper() 执行过后,无法存储到a本身,必须得重新赋值给a  换句话说,a在被upper之后,重新指向了一个新的地址
print(id(a))  #第二次的地址
print(a)

第一段程式碼執行結果:

#第二段程式碼:

#
b = [11,22,33,44,55]  #定义一个列表的变量
print(id(b))          #第一次的地址
print(b)              #b = [11,22,33,44,55]
b.append(99)          #单纯的b.append()执行过后,不需要存储到b,因为b已经被更改
print(id(b))          #检查第一次的地址
print(b)              #发现在第一次地址当中,b已经改变
#b = b.append(99)     #如果将修改b这个行为赋值到b
#print(id(b))         #检查地址,发现已经变更
#print(b)             #检查b的值,发现已经变更。b的值为none   因为b.append(99)本身的返回值为none
#[***列表为可修改变量,因此修改完之后,地址跟原来的一样。反而是如果像修改字符串那样重新赋值,变得不可行。原因在于append语句本身并不返回值。***]
#字符串赋值之后放在内存一个地址,这个地址上面的字符串是无法更改的,只能重新做一个新的字符串,然后改变变量的指向位置。
#而列表赋值之后存在一个内存的地址,这个列表里面的值是可以直接修改的。不需要重新做一个新的列表,然后改变变量的指向位置。

第二段程式碼執行結果:

#在學python的過程當中被告知,字串是屬於不可變類型,列表屬於可變類型。也就是說,如果我要改字串,我其實是重新做了一個新的字串,放在記憶體的新的位址中,原來的地方那個字串還是原來的老樣子。如第一段代碼所示。
而列表不一樣,列表可以在原來的記憶體位址上直接修改。如第二段程式碼所示。
我的問題:
可變類型和不可變類型的根本差異在哪裡?為什麼會出現這種差異?為什麼第一段程式碼裡,a要改變,必須改變位址,第二段程式碼裡b可以不變位址的情況下直接修改清單的值。這裡面的底層邏輯是什麼?我猜想,是不是意味著列表這個東西本身,也其實是某一個一堆值得集合體,它僅僅只是反映了一個集合體本身,把一堆值指向了這一個地方而已,所以才是可以修改的?我不知道我表達有沒有清楚。
我只是對這個東西很好奇,也就是說,追根究底清單到底是個什麼東西,為什麼他是可以直接改的?而字串沒辦法改。往再底層深入之後,他們兩個到底是啥?

给我你的怀抱
给我你的怀抱

全部回覆(4)
大家讲道理

其實物件可變不可變, 對py, 都是內部實現的問題, 如果我修改相應的方法, 將其寫回到本身, 這樣也能模仿出可變的現象, 就小小類似tuple list的關係,
既然想了解底層, 那就直接看源碼吧:
這是字符串的upper()

static PyObject *
string_upper(PyStringObject *self)
{
    char *s;
    Py_ssize_t i, n = PyString_GET_SIZE(self); # 取出字符串对象中字符串的长度
    PyObject *newobj;

    newobj = PyString_FromStringAndSize(NULL, n); # 可以理解成申请内存空间
    if (!newobj)
        return NULL;

    s = PyString_AS_STRING(newobj);  # 从newobj对象取出具体字符串指针

    Py_MEMCPY(s, PyString_AS_STRING(self), n); # 拷贝旧的字符串

    for (i = 0; i < n; i++) {
        int c = Py_CHARMASK(s[i]);
        if (islower(c))
            s[i] = _toupper(c);   # 修改对应指针位置上的内容
    }

    return newobj;  # 返回新字符串对象 (区分字符串对象和里面字符串的指针)
}

這是清單的append

int
PyList_Append(PyObject *op, PyObject *newitem)
{
    if (PyList_Check(op) && (newitem != NULL))
        return app1((PyListObject *)op, newitem);
    PyErr_BadInternalCall();
    return -1;
}

static int
app1(PyListObject *self, PyObject *v)
{
    Py_ssize_t n = PyList_GET_SIZE(self);

    assert (v != NULL);
    if (n == PY_SSIZE_T_MAX) {
        PyErr_SetString(PyExc_OverflowError,
            "cannot add more objects to list");
        return -1;
    }

    if (list_resize(self, n+1) == -1)
        return -1;

    Py_INCREF(v);
    PyList_SET_ITEM(self, n, v);   # 因为列表是头和和成员分开的, 所以直接将新成员追加在原来的成员数组后面, 长度变化通过resize实现
    return 0;
}
巴扎黑

python字串有cache的,如果兩個相同的字串在不同的變數a,b,他們的id(a), id(b)是一樣的.
但如果當a, b的引用為0是,就會自動銷毀物件.

樓主的例子: 

a = a.upper()

a的變數內容已經變化,不一樣了,舊的內容沒有了引用,垃圾回收銷毀物件.
b是列表,是可變的,可以再申請內存.同時,b有內容引用,不會被銷毀.

为情所困

往再底層深入,就去看python的C源碼唄~

可不可變,是python語言規定的。

不可變類型 沒有提供修改物件本身的方法,而 可變類型 提供了這些方法。就這些差別,沒啥神秘的。

仅有的幸福

從硬體角度說,提供給使用者的介面是按照規定設定好的,操作記憶體就是固定的方式,不存在可變和不可變。
往上,就是作業系統層,對硬體api進行了大量的封裝,使用戶操作變得豐富,對於python解釋器是使用c語言編寫的,使用python時只是使用了python的語用,編寫程式碼,然後交給解釋器去執行.在上面的前提下,來解釋當前問題,python的可變和不可變是python創建者規定的,實現這些規定的方式可能就是調用了不同的底層api,或者是不同底層api相互組合來實現的。將這些規定以python語用的形式提供給使用者使用,最後還是編譯成0,1去讓電腦執行。對使用者來說,可變和不可變物件是語言提供的特性,可以完成一些功能,但是對於電腦其實是沒區別的。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板