2

I have written a function derivative(w1, w2, pt) that evaluates the derivative of the function f(x) = w1 * x**3 + w2 * x - 1 at point pt. Strangely, I have found that I get different results depending on whether def f(x) is positioned inside or outside of derivative(w1, w2, pt). Why does the positioning of def f(x) matter / which is correct?

Example 1:

def derivative(w1, w2, pt):
    x = sy.Symbol('x')

    def f(x):
        return w1 * x**3 + w2 * x - 1

    # Get derivative of f(x)
    def df(x):
        return sy.diff(f(x),x)

    # Evaluate at point x
    return df(x).subs(x,pt)   

From which derivative(5, 8, 2) returns 68.

Example 2:

def f(x):
    return w1 * x**3 + w2 * x - 1

def derivative(w1, w2, pt):
    x = sy.Symbol('x')

    # Get derivative of f(x)
    def df(x):
        return sy.diff(f(x),x)

    # Evaluate at point x
    return df(x).subs(x,pt)

From which derivative(5, 8, 2) returns 53.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
j45612
  • 362
  • 1
  • 5
  • 17
  • 1
    I appreciate the link - but I do think this question is different because I understand why people nest functions for readability but I don't understand here why the nesting order affects the outcome of the function – j45612 Jan 16 '19 at 15:46
  • 2
    I'm surprised example 2 works at all - in example 1 `w1` and `w2` are available inside `f` because they're closed over from `derivative`'s parameters, but they don't exist in example 2. I guess `sy.diff` is doing some magic that's being interfered with. – jonrsharpe Jan 16 '19 at 15:49
  • 1
    Can you add import statements to your code so that it's a minimal working example and can be tested by others? – Harpe Jan 16 '19 at 15:52
  • @johnrsharpe That's what had me confused - if the difference in output was due to scope example 2 shouldn't work at all – j45612 Jan 16 '19 at 16:01
  • 1
    @c0deBr0 your second example only work by accident - you tested it in a context where `w1` and `w2` where defined as global variables. Copy it to a .py file (without other definitions) and try to execute it and you'll get a NameError. – bruno desthuilliers Jan 16 '19 at 16:04
  • 1
    @c0deBr0 also the answers to the dup question DO answer your own - nested functions are not that much about readability but about scope and closures. – bruno desthuilliers Jan 16 '19 at 16:06
  • ask yourself: which ‘w2’ is f(x) seeing in your global? in your nested? practical effects got nothing to do w readability. – JL Peyret Jan 16 '19 at 16:06
  • @Harpe: `import sympy as sy` – Martijn Pieters Jan 16 '19 at 16:07
  • @brunodesthuilliers Yes you're right, thank you for your help – j45612 Jan 16 '19 at 17:47

2 Answers2

2

I think it's your global scope that is polluted. Look at this example :

import sympy as sy

def f(x, w1, w2):
    return w1 * x**3 + w2 * x - 1

def derivative(w1, w2, pt):
    x = sy.Symbol('x')

    # Get derivative of f(x)
    def df(x, w1, w2):
        return sy.diff(f(x, w1, w2),x)

    # Evaluate at point x
    return df(x, w1, w2).subs(x,pt)

print(derivative(5, 8, 2))

This is just a modified version of your example 2 and it returns the same answer.

Hans Daigle
  • 364
  • 2
  • 14
1

A nested function has access to the local names in the parent function. When you define f outside, it can't access the locals w1 and w2, so it'll have to assume those are globals instead.

And if you don't define w1 and w2 at the global level, your second version actually raises a NameError:

>>> import sympy as sy
>>> def f(x):
...     return w1 * x**3 + w2 * x - 1
...
>>> def derivative(w1, w2, pt):
...     x = sy.Symbol('x')
...     def df(x):
...         return sy.diff(f(x),x)
...     return df(x).subs(x,pt)
...
>>> derivative(5, 8, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in derivative
  File "<stdin>", line 4, in df
  File "<stdin>", line 2, in f
NameError: name 'w1' is not defined

That you didn't get an exception means you already defined w1 and w2 before, and it is those values that are being used to provide your incorrect answer.

You can 'fix' your second example by setting w1 and w2 as globals instead. It doesn't actually matter what you pass in as the first and second arguments to the derivative() call, because those w1 and w2 argument values are entirely ignored:

>>> w1 = 5
>>> w2 = 8
>>> derivative('This value is ignored', 'And so is this one', 2)
68

In your local setup, you probably set w1 and w2 to 4 and 5, respectively, because it is those values for which x is 53:

>>> w1 = 4
>>> w2 = 5
>>> derivative('This value is ignored', 'And so is this one', 2)
53

For your first example, w1 and w2 are provided by the locals in derivative(); it doesn't matter what global names you may have defined, those are not going to be used instead.

If you want to define f outside of derivative(), and still pass in w1 and w2 to derivative() first, then you also need to pass those same values on to the f() function:

def f(x, w1, w2):
    return w1 * x**3 + w2 * x - 1

def derivative(w1, w2, pt):
    x = sy.Symbol('x')

    # Get derivative of f(x, w1, w2)
    def df(x):
        return sy.diff(f(x, w1, w2), x)

    # Evaluate at point x
    return df(x).subs(x,pt)

Now f() explicitly receives w1 and w2 from the nested df() function, and not from globals.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343