V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
woniu127
V2EX  ›  Python

python append 为何如此奇怪?

  •  
  •   woniu127 · 2016-11-20 09:33:25 +08:00 · 4725 次点击
    这是一个创建于 2928 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是一段测试代码

    li1 = []
    li2 = []
    ki = [0]
    j = 0
    for i in range(10):
        ki[0] = ki[0]+1
        j = j+1
        li1.append(ki)
        li2.append(j)
    print(li1)
    print(li2)
    

    这是输出:

    [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10]]
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    

    为何 append()参数是列表类型时,每次循环都会改变 li1 中的值???

    第 1 条附言  ·  2016-11-20 16:13:14 +08:00
    谢谢大家,今天又学到了很多,是我基础太薄弱了,对对象和引用理解的都不够。
    18 条回复    2016-11-21 16:18:09 +08:00
    laoyur
        1
    laoyur  
       2016-11-20 09:37:55 +08:00   ❤️ 1
    li1 = []
    li2 = []
    ki = [0]
    j = 0
    for i in range(10):
    ki[0] = ki[0]+1
    j = j+1
    li1.append(list(ki))
    li2.append(j)
    print(li1)
    print(li2)
    kindjeff
        2
    kindjeff  
       2016-11-20 09:38:39 +08:00 via iPhone   ❤️ 1
    因为你只是 append 了 ki 对象的引用,而 list 是可变的,内容改变了,你 append 的进去的仍然是 ki 的引用。
    woniu127
        3
    woniu127  
    OP
       2016-11-20 09:48:26 +08:00
    @kindjeff 也就是说只要 append 的是不可变的对象就没问题了
    exoticknight
        4
    exoticknight  
       2016-11-20 10:06:41 +08:00   ❤️ 1
    明显是没搞清楚 python 是引用优先的
    估计楼主肯定还会在用默认参数是对象的时候出现问题
    woniu127
        5
    woniu127  
    OP
       2016-11-20 10:12:51 +08:00
    @exoticknight 学 python 还不够,认识还不够深,只能一个坑一个坑的踩了
    BiggerLonger
        6
    BiggerLonger  
       2016-11-20 10:19:18 +08:00 via Android
    應該是用 list.extend 方法
    wwqgtxx
        7
    wwqgtxx  
       2016-11-20 10:24:42 +08:00
    又一个被 C 的值传递坑的
    IanPeverell
        8
    IanPeverell  
       2016-11-20 10:53:46 +08:00   ❤️ 1
    因为 append(ki)的时候传递的是 ki 的地址,所以如果想不改变值可以这么些 li.append([x for x in ki])
    mornlight
        9
    mornlight  
       2016-11-20 10:58:09 +08:00 via iPhone   ❤️ 1
    第一个 list 里到最后是 10 个指向同一块内存的相同元素
    dtfm
        10
    dtfm  
       2016-11-20 11:15:46 +08:00   ❤️ 1
    ```
    li1 = []
    li2 = []
    ki = [0]
    j = 0
    for i in range(10):
    ki[0] = ki[0]+1
    j = j+1
    li1.append(ki[0])
    li2.append(j)
    print(li1)
    print(li2)
    ```
    在 ki 里面加一个 ki[0],这样每次都添加就好了
    lzhCoooder
        11
    lzhCoooder  
       2016-11-20 11:29:45 +08:00   ❤️ 1
    浅拷贝 你 li1 里的每个元素都是指向的或者说引用的同一个 ki
    li2 里之所以不这样 是因为 j 是不可变类型,没加一次创建一个新 j
    rebirth2
        12
    rebirth2  
       2016-11-20 12:40:51 +08:00 via Android   ❤️ 1
    可以用 list slice, ki[:]这样直接创建新 list ,不过比较黑科技。。
    onlyice
        13
    onlyice  
       2016-11-20 13:02:14 +08:00 via Android
    把代码粘贴到这里看看就懂了 http://www.pythontutor.com/visualize.html#py=2
    bravecarrot
        14
    bravecarrot  
       2016-11-20 13:13:10 +08:00
    @onlyice 这个有意思啊 哈哈哈哈 不用开 ide 看了
    bravecarrot
        15
    bravecarrot  
       2016-11-20 13:25:14 +08:00   ❤️ 1
    @woniu127 ls 的 @exoticknight 可能说的是这个

    ``` python
    def foo(x, l = []):
    l.append(x)
    return l
    y = foo(6)
    z = foo(8)

    p = foo(2, [3])
    q = foo(4, [5])
    print y
    print z
    print p
    print q
    ```

    output:
    [6, 8]
    [6, 8]
    [3, 2]
    [5, 4]
    *************
    同理,函数每次使用的默认参数的 list 都是同一个,每次都会操作它
    20015jjw
        16
    20015jjw  
       2016-11-20 15:34:28 +08:00
    lz 你显然没理解 reference 的概念..
    enenaaa
        17
    enenaaa  
       2016-11-21 09:10:23 +08:00
    我也觉得奇怪,其他语言标准库的 append 函数多半是值插入, python 非得是引用。
    然后用个不常见 extend 函数代替。 比较坑
    diydry
        18
    diydry  
       2016-11-21 16:18:09 +08:00   ❤️ 1
    对象变动(Mutation)
    Python 中可变(mutable)与不可变(immutable)的数据类型让新⼿很是头痛。 简单的说, 可
    变(mutable)意味着"可以被改动", ⽽不可变(immutable)的意思是“常量(constant)”。 想把脑
    筋转动起来吗? 考虑下这个例⼦:
    foo = ['hi']
    print(foo)
    # Output: ['hi']
    bar = foo
    bar += ['bye']
    print(foo)
    # Output: ['hi', 'bye']
    刚刚发⽣了什么? 我们预期的不是那样!我们期望看到是这样的:
    foo = ['hi']
    print(foo)
    # Output: ['hi']
    bar = foo
    bar += ['bye']
    print(foo)
    # Output: ['hi']
    print(bar)
    # Output: ['hi', 'bye']
    这不是⼀个 bug 。 这是对象可变性(mutability)在作怪。 每当你将⼀个变量赋值为另⼀个可
    变类型的变量时, 对这个数据的任意改动会同时反映到这两个变量上去。 新变量只不过是
    ⽼变量的⼀个别名⽽已。 这个情况只是针对可变数据类型。 下⾯的函数和可变数据类型让
    你⼀下就明⽩了:
    def add_to(num, target=[]):
    target.append(num)
    return target
    add_to(1)
    # Output: [1]
    add_to(2)
    # Output: [1, 2]
    add_to(3)
    # Output: [1, 2, 3]
    Python 进阶
    对象变动 Mutation 51 你可能预期它表现的不是这样⼦。 你可能希望, 当你调⽤add_to 时, 有⼀个新的列表被
    创建, 就像这样:
    def add_to(num, target=[]):
    target.append(num)
    return target
    add_to(1)
    # Output: [1]
    add_to(2)
    # Output: [2]
    add_to(3)
    # Output: [3]
    啊哈!这次又没有达到预期, 是列表的可变性在作怪。 在 Python 中当函数被定义时, 默认
    参数只会运算⼀次, ⽽不是每次被调⽤时都会重新运算。 你应该永远不要定义可变类型的
    默认参数, 除⾮你知道你正在做什么。 你应该像这样做:
    def add_to(element, target=None):
    if target is None:
    target = []
    target.append(element)
    return target
    现在每当你在调⽤这个函数不传⼊target 参数的时候, ⼀个新的列表会被创建。 举个例
    ⼦:
    add_to(42)
    # Output: [42]
    add_to(42)
    # Output: [42]
    add_to(42)
    # Output: [42]
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3269 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 00:39 · PVG 08:39 · LAX 16:39 · JFK 19:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.