我是R语言的Python新手。最近,我花了很多时间阅读Python中的所有内容如何成为对象,对象可以自己调用方法,方法是类中的函数的方式,yada yada yada。
这是我不明白的地方。使用以下简单代码:
mylist = [3, 1, 7]
如果我想知道数字7出现了多少次,我可以这样做:
mylist.count(7)
那当然会返回1。如果我想将计数保存到另一个变量中:
seven_counts = mylist.count(7)
到目前为止,太好了。除了语法以外,其行为类似于R。但是,假设我正在考虑将数字添加到列表中:
mylist.append(9)
等等,该方法实际上更改了变量本身! (即“ mylist”已更改,现在将数字9作为列表的第四位。)将代码分配给新变量(就像我对seven_counts所做的那样)会产生垃圾:
newlist = mylist.append(9)
我发现这种行为的不一致有点奇怪,坦率地说是不可取的。 (比方说,我想先看看追加结果如何,然后可以选择是否将其分配给新变量。)
我的问题很简单:
是否可以提前知道调用特定方法是否会真正改变您的变量(对象)?
答案 0 :(得分:3)
除了阅读文档(对于某些方法,该方法将包括指定返回值的类型注释)或在交互式解释器中使用该方法(包括使用help()
检查类型注释的文档字符串)之外,否,仅通过查看方法就无法知道。
也就是说,您看到的行为是故意的。 Python方法 返回对象的 new 修改副本或修改对象;至少在内置程序中,它们既不会做任何事情(某些方法会变异对象并返回非None
值,但绝不会仅仅是对象被变异; {pop
{1}}和dict
是这种情况的示例。
此或/或行为是故意的;如果他们不遵守该规则,那么您将面临一个更加混乱且难以识别的问题,即确定list
是变异了它所调用的值还是返回了一个新对象。您肯定回来了append
,但这是新的list
还是相同的list
?如果它更改了它调用的值,那么
list
有点奇怪; newlist = mylist.append(9)
和newlist
将是同一个mylist
的别名(所以为什么要同时使用两个名称?)。您可能有一阵子都没有注意到;您会继续使用list
,以为它独立于newlist
,而只是看着mylist
并发现它们被弄乱了。通过使所有此类“就地修改”方法返回mylist
(或至少不是原始对象),可以更快/更轻松地发现错误;如果您尝试使用None
,而错误地认为它是newlist
,则会立即得到list
或TypeError
s。
基本上,提前知道的唯一方法是阅读文档。对于名称表示修改操作的方法,您可以检查返回值,并经常了解它们是否正在变异。首先,它有助于了解哪些类型是可变的。 AttributeError
,list
,dict
和set
都是可变的,并且它们的方法具有不可变的对应物(除了bytearray
之外,它们没有不可变的对应物)的缺乏往往会使对象在原处发生变异。
默认情况是倾向于简单地更改对象,因为这样做更有效;如果您有100,000个元素dict
,则list
的默认行为会产生一个新的100,001个元素append
并返回它,这将是非常低效的(并且没有明显的方法来避免它) )。对于不可变类型(例如list
,str
,tuple
),这是不可避免的,如果希望保证对象永远不会发生突变,则可以使用这些类型,以不必要的对象创建和销毁为代价,这在大多数情况下会降低代码的速度。
答案 1 :(得分:2)
只需签出文档:
>>> list.count.__doc__
'L.count(value) -> integer -- return number of occurrences of value'
>>> list.append.__doc__
'L.append(object) -> None -- append object to end'
实际上并不是很容易分辨,但是:
不可变对象->无法通过方法调用进行更改
例如,tuple
没有影响元组的方法,因为它是不可更改的,因此方法只能返回新实例。
如果您“想先看一下追加结果,然后可以选择是否将其分配给新变量”,则可以使用一个元素。
即
>>> l = [1,2,3]
>>> k = l + [4]
>>> l
[1, 2, 3]
>>> k
[1, 2, 3, 4]
答案 2 :(得分:0)
不仅仅是您的调用(您的方法调用)。您可以保证,如果仅传递不可变的对象,则该方法不会更改该对象,但是某些定义了 的方法可以更改该对象-并且不会为您使用的方法定义任何方法,否则会执行失败。
在《真实生活》中,您可以查看该方法的文档:这将告诉您确切的情况。
[我正要包括Joe Iddon的答案所涵盖的内容...]