【问题标题】:Python 3.5.2: a class unexpectedly changes a global variable [duplicate]Python 3.5.2:一个类意外更改了全局变量 [重复]
【发布时间】:2017-08-20 20:36:41
【问题描述】:

我对编程、Python 和一般的面向对象编程很陌生。对于学校作业,我必须编写一个定义“多项式”类的代码,并以此找到多项式的根。由于代码没有按预期运行,我开始分析它并意识到一个全局变量,即表示多项式的系数列表,它是多项式类的“输入”,正在被修改。问题是我似乎无法弄清楚是什么(部分代码)导致了这种操作。以下是我使用的代码(相关部分):

#set input list
il=[1,1,-1]

#define B!/(O-1)!, note that O is an "oh":
def pr(O,B):
  p=1
  for i in range(O,B+1):
    p*=i
  return p

#polynomial
class pol:

  #init:
  def __init__(self,L=[0]):
    self.l=L
    self.d=len(L)
    self.n=self.d-1

  #evaluate:
  def ev(self,X=0):
    if X==0:
      return self.l[0]
    else:
      s=self.l[0]
      for i in range(1,self.d):
        s+=self.l[i]*X**i
      return s

  #N-th derivative:
  def der(self,N=1):
    if self.n < N:
      return pol([0])
    else:
      lwork=self.l
      for i in range(N,self.d):
        lwork[i]*=pr(i-N+1,i)
      return pol(lwork[N:])

#define specific polynomial and take derivative:
#---here I have put some extra prints to make clear what the problem is---
f=pol(il)
print(il)
fd=f.der()
print(il)
fd2=f.der(2)
print(il)

现在这应该评估为(至少在我的机器上是这样)

[1,1,-1]
[1,1,-2]
[1,1,-4]

虽然我希望它只是第一个列表三次,因为在方法“der”的定义中,我不操纵输入列表,或者在我看来是这样。

有人可以解释发生了什么吗?我错过了一个(简单的)细节,还是我在这里滥用了(某些方面的)类?

为了执行我的代码,我使用了在 Python 3.5.2 上运行的在线编译器 (repl.it)。

【问题讨论】:

  • 您的pol 课程不会复制您传递给它的列表。它与 il 全局变量所引用的列表相同。

标签: python python-3.x class object


【解决方案1】:

一:永远不要使用mutable default argument for a function/method of any kind

二:从一个名字到另一个名字,如:

lwork=self.l

只是别名,lwork 成为对与self.l 相同的list 的引用。如果您不想更改self.l,(浅)复制它,例如:对于像list 这样的简单序列:

lwork = self.l[:]

这将创建一个新的list,其值与self.l 相同。由于这些值都是不可变的,因此浅拷贝就足够了;如果这些值可能是可变的,您需要使用copy 模块的copy.deepcopy 来确保副本与原始list 没有任何联系。

同样,如果您不想保留传递给 pol 初始化程序的 list 和存储在实例上的 list 之间的联系,请复制它,例如:

self.l = list(L)

在这种情况下,我使用了list(L) 而不是L[:],因为它从任何输入可迭代类型中为我们提供了保证类型(list)。这实际上使可变默认参数安全(因为你总是浅拷贝它,所以没有人真正改变它),但即便如此,可变默认值通常被认为是代码异味,所以最好避免它们。

修复整个 __init__ 方法,你最终会得到:

# Mostly to avoid code smell, use immutable default (list constructor converts to list)
def __init__(self, L=(0,)):
    self.l = list(L)      # Create list from arbitrary input iterable
    self.d = len(self.l)  # Get length of now guaranteed list (so iterator inputs work)
    self.n = self.d-1

【讨论】:

  • Never 是一个非常强烈的词——尽管不多,但绝对有你希望使用可变默认参数的情况。这是一个案例:stackoverflow.com/questions/9158294/….
  • @NoticeMeSenpai:是的,它出现了。通常,它的合理用例是它根本不打算作为参数的情况,它只是用于模拟 C 将使用函数范围的static 变量的情况(例如该链接上的cache 示例,它可以只使用全局状态,但使用默认参数更有效并且涉及更少的全局范围污染)或作为将重复使用的内置缓存缓存到本地范围的微优化。除此之外,这通常是不值得的,因为维护人员的代码气味如果没有别的。
  • 正确,我并不反对一般观点,只是需要澄清它是“何时使用”,而不是“如果曾经使用”给新程序员
猜你喜欢
  • 2014-11-23
  • 2020-02-16
  • 2015-08-19
  • 1970-01-01
  • 2018-05-19
  • 2020-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多