【问题标题】:Why does my function overwrite a list passed as a parameter?为什么我的函数会覆盖作为参数传递的列表?
【发布时间】:2016-05-27 16:32:00
【问题描述】:

我创建了一个将列表作为参数的函数。它打乱列表,替换第一个元素并返回新列表。

import random
firstList=["a","b","c","d","e","f","g","h","i"]

def substitution(importedList):
    random.shuffle(importedList)
    importedList[0]="WORD"
    return importedList

洗牌对我的问题没有影响。然而,令我惊讶的是,返回的 importList 覆盖了原来的 firstList。

>>> firstList
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

>>> substitution(firstList)
['WORD', 'a', 'b', 'd', 'i', 'c', 'g', 'e', 'h']

>>> firstList
['WORD', 'a', 'b', 'd', 'i', 'c', 'g', 'e', 'h']

我通过在函数中复制列表找到了一种解决方法,但它似乎效率低下。

import random
firstList=["a","b","c","d","e","f","g","h","i"]
string="a"

def substitutionandcopy(importedList):
    copiedList=importedList[:]
    random.shuffle(copiedList)
    copiedList[0]="WORD"
    return copiedList

我的问题是为什么函数会替换 firstList?例如,如果它是一个字符串,则不会发生这种情况。

string="a"

def substituteString(foo):
    foo='b'
    return foo

>>> string
'a'

>>> substituteString(string)
'b'

>>> string
'a'

【问题讨论】:

  • 1. shuffle 改变了原来的可变列表。 2.传递列表的copy而不是原来的。见How to clone or copy a list in Python?
  • 字符串是不可变的,列表是可变的。按照@BhargavRao 的建议传递一份副本。
  • 这可能是一种品味,但我宁愿让函数复制列表并处理副本,而不是我必须传递副本。可以通过您可以传入的类型使函数更加灵活。(例如,您可以传入元组而不是列表)。
  • 来自 Bhargav 的链接:“当您说 new_list = my_list 时,您不是在复制,您只是在添加另一个指向内存中原始列表的名称”。作为函数参数传递的列表也是如此。
  • 如果你想要的是复制,那么明确地复制显然不是低效——可以这么说,你不能不做就做:-)

标签: python arrays list


【解决方案1】:

字符串、整数、元组是不可变的 Python 类型,因此当您执行更改这些类型之一的操作时,每次都会在内存中有效地创建新的对应对象。 (或者,如果尝试就地更改这些内容,则会出现错误。)

列表和字典是可变的 Python 类型,因此当您执行更改其中一种类型的操作时,对象保持不变,但其部分(即列表元素)会发生变化。

因此,如果您想更改列表,但又想保持原件不变,您必须自己复制它。重要的是,有两种复制类型 - 浅复制深复制

浅拷贝可以这样完成:

list_b = list_a[:] #using slice syntax

#or

list_b = list(list_a) #instantiating a new list from iterating over the old one

#or

import copy
list_b = copy.copy(list_a) #using copy module

深拷贝的方式如下:

import copy
list_b = copy.deepcopy(list_a)

深拷贝和浅拷贝的区别是……

在做浅拷贝时,如果可变对象包含其他可变对象,则只复制最上面的那个。 IE。如果一个列表包含其他列表,如果顶部列表被复制,然后内部列表在副本中发生更改,则实际上内部列表在副本和原始列表中都将被更改,因为它是内存中引用的同一个对象两个不同的列表。基本上,浅拷贝会创建一个新对象,该对象具有存储在原始对象中的相同引用。

当做深拷贝时,如果可变对象包含其他可变对象,那么内部可变对象也会被复制。 IE。和前面的例子一样,如果你在副本中更改内部列表,它只会在副本中更改,而原始列表不受影响。所以深拷贝复制所有内容,在内存中为被复制对象中的所有内容创建新结构,而不仅仅是引用。

【讨论】:

  • @DaveOB 没问题,有时 Python 并不那么简单。 :) 如果您发现我的答案适合您的问题,您可能想要标记它,或其他适合的答案 - 请参阅 stackoverflow.com/help/someone-answers
【解决方案2】:

它不会替换第一个列表。第一个列表是通过引用传递的,这意味着您在作为参数传递的列表上执行的任何突变也将在函数外部的列表上执行,因为它是同一个列表。

但是,字符串和其他基本类型不是通过引用传递的,因此您在函数范围内所做的任何更改都只是变量的本地副本。

【讨论】:

  • “通过引用传递”在讨论 Python 的数据模型时可能会产生误导。请参阅Facts and myths about Python names and values,由 SO 资深人士 Ned Batchelder 撰写。
  • 我觉得说Python中的所有变量都是引用比较正确。只有一些对象可以“就地”变异,所以对同一地址的所有引用都将反映更改。 Python 中的字符串并非如此,它们是不可变的。
  • 字符串通过引用传递。问题是,如果他尝试对带有字符串的列表(即random.shuffle(foo)foo[0] = "w")做同样的事情,他会得到一个错误,因为字符串是不可变的。如果字符串是可变的,就可以对字符串做同样的事情。
【解决方案3】:

如您所见,random.shuffle 会在原地改变列表:

random.shuffle(x[, random])

将序列 x 打乱。可选参数 random 是一个 0 参数函数,返回 [0.0, 1.0) 中的随机浮点数;默认情况下,这是函数 random()。

请注意,即使是相当小的 len(x),x 的排列总数也大于大多数随机数生成器的周期;这意味着永远无法生成长序列的大多数排列。

字符串在 Python 中是不可变的,所有字符串操作都返回一个新字符串。这是您问题中的“字符串”示例:

string="a"

def substitute_string(foo):
    foo = 'b'
    return foo

它与问题第一个代码块中substitution 列表中的代码并不真正相似。使用列表的等效代码如下:

alist = [1, 2, 3]

def substitute_list(foo):
    foo = [4, 5, 6]
    return foo

它的工作原理是一样的:

>>> alist
[1, 2, 3]

>>> substitute_list(alist)
[4, 5, 6]

>>> alist
[1, 2, 3]

回到你的解决方案,它可能是:

def substitution_and_copy(imported_list):
    imported_list = imported_list[:]
    random.shuffle(imported_list)
    imported_list[0]="WORD"
    return imported_list

不,给参数分配一个新值不会改变原始列表,就像你给foo分配一个新值时不会改变原始字符串一样(我也将camelCase更改为snake_case,我'我对PEP8 有点不耐烦。

[更新]

然而,你现在拥有的是他已经尝试过的。 “我通过在函数中复制列表找到了解决方法,但似乎效率低下”

列表副本并不像您想象的那么低效,但这不是重点:正如其他人指出的那样,您要么在适当的位置改变列表并且不返回任何内容,要么返回一个新列表 - 你不能拥有你的吃蛋糕。

【讨论】:

  • random.shuffle() 返回无。如果他这样定义some_list,他会发现一些意想不到的行为。
  • 确实,刚刚发现。
  • 然而,你现在拥有的是他已经尝试过的。 我通过在函数中复制列表找到了解决方法,但似乎效率低下。
  • 它并没有你想象的那么低效,因为在 Python 中每个列表元素只是一个引用,它不会为所有字符串复制空间。它更像是 C 中的 void 指针数组。除非它处于紧密循环中,否则即使对于大型列表,您也可以在大多数情况下负担得起。
  • 并没有说效率低下。 OP 是一个想要不同的人。
【解决方案4】:

来自docs 上的random.shuffle()shuffle list x in place; return None. 如果你不想这样,你可以使用random.sample()

def substitutionandcopy(importedList):
    shuffledList = random.sample(importedList, len(importedList))
    shuffledList[0]="WORD"
    return shuffledList

【讨论】:

    猜你喜欢
    • 2015-12-27
    • 2020-09-04
    • 1970-01-01
    • 2020-07-16
    • 1970-01-01
    • 2012-04-03
    • 2018-06-09
    • 2020-01-15
    • 2020-01-16
    相关资源
    最近更新 更多