1

I am quite new to programming, Python and object-oriented programming in general. For a school assignment, I had to write a code that defines a "polynomial" class and, with this, find the root of a polynomial. As the code did not behave as expected, I started analysing it and realised that a global variable, namely the list of coefficients representing the polynomial, which is the "input" for the polynomial class, was being modified. The problem is that I just can't seem to figure out what (part of the code) is causing this manipulation. Below is (the relevant part of) the code I use:

#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)

Now this should evaluate to (at least it does so on my machine)

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

while I expect it to be just the first list three times, since in the definition of the method "der", I do not manipulate the input list, or so it seems to me.

Can someone explain what's going on? Am I missing a (simple) detail, or am I misusing (some aspect of) classes here?

To execute my code, I use an online compiler (repl.it), running on Python 3.5.2.

Tyron
  • 113
  • 5
  • Your `pol` class doesn't make a copy of the list you pass it. It's working with the same list that the `il` global variable refers to. – user2357112 Mar 27 '17 at 23:51

1 Answers1

2

One: Never use a mutable default argument for a function/method of any kind.

Two: Assigning from one name to another, as in:

lwork=self.l

is just aliasing, lwork becomes a reference to the same list as self.l. If you don't want to change self.l, (shallow) copy it, e.g.: for simple sequences like list:

lwork = self.l[:]

That will make a new list with the same values as self.l. Since the values are all immutable, the shallow copy was enough; if the values might be mutable, you'd want to use the copy module's copy.deepcopy to ensure the copy has no ties to the original list.

Similarly, if you don't want to preserve a tie between the list passed to the pol initializer and the list stored on the instance, make a copy of it, e.g.:

self.l = list(L)

In this case, I used list(L) instead of L[:] because it gets us a guaranteed type (list), from any input iterable type. This actually makes the mutable default argument safe (because you always shallow copy it, so no one is ever actually mutating it), but even so, mutable defaults are usually considered code smell, so it's best to avoid them.

Fixing up the whole __init__ method, you'd end up with:

# 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
Community
  • 1
  • 1
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • *Never* is a very strong word to use -- there's absolutely, albeit not many, cases where you'd wish to use a mutable default argument. Here's one case: http://stackoverflow.com/questions/9158294/good-uses-for-mutable-function-argument-default-values. – the_constant Mar 27 '17 at 23:56
  • @NoticeMeSenpai: Yar, it comes up. Typically, the reasonable use cases for it are cases where it's not really intended as an argument at all, it's just being used to simulate what C would use function-scoped `static` variables for (e.g. the `cache` example at that link, which could just use global state, but it's more efficient and involves less global scope pollution to use the defaulted argument) or as a micro-optimization for caching repeatedly used builtins to local scope. Aside from that, it's usually not worth it, due to code smell for maintainers if nothing else. – ShadowRanger Mar 28 '17 at 00:00
  • Correct, and I'm not disagreeing with the general sentiment, just needed to clarify that it is "when to ever use", not "if to ever use" to a new programmer – the_constant Mar 28 '17 at 00:02