Search code examples
pythonfunctionshellexecutableprogram-entry-point

How to create a program which can be executed directly from the shell (harmonic sum)?


I have done some programming in MatLab but I am completely new to Python. For our first project, our teacher wants us to program a harmonic sum (1/1+1/2+1/3...) and a "complete program which can be executed directly from the shell like this:

$ python3 hsum.py 10
0 0
1 1.0
2 1.5
3 1.8333333333333333
4 2.083333333333333
5 2.283333333333333
6 2.4499999999999997
7 2.5928571428571425
8 2.7178571428571425
9 2.8289682539682537

which I have managed like this:

import sys

def hSum(n):
    s = 0
    for i in range(n + 1):
        if i == 0:
            s = 0
        else:
            s += 1 / i
    return s


def main():
    hSum(int(sys.argv[1])) # a command he has specified for us to use

    main()

The main problem is I do not actually understand what he means by a "a complete program which can be executed directly from the shell". Obviously the second problem is that when I run the "test"-file that he has provided, which tests to see if the program is correct, it tells me:

function main should generate exactly the same output as in the assignment
Got:
[blank]
Instead of:
[the list in the previous quote]

Here is the test.py code:

import sys
import importlib.util


def testEq(res, ref, msg):
    global pass_tests, fail_tests
    if res == ref:
        pass_tests = pass_tests + 1
    else:
        print(msg)
        print("Got:")
        print(res)
        print("Instead of:")
        print(ref)
        fail_tests = fail_tests + 1


def test(fun, x, y):
    global pass_tests, fail_tests
    if type(x) == tuple:
        z = fun(*x)
    else:
        z = fun(x)
    if y == z:
        pass_tests = pass_tests + 1
    else:
        if type(x) == tuple:
            s = repr(x)
        else:
            s = "(" + repr(x) + ")"
        print("Condition failed:")
        print("   " + fun.__name__ + s + " == " + repr(y))
        print(fun.__name__ + " returned/printed:")
        print(str(z))
        fail_tests = fail_tests + 1


def run(src_path=None):
    global pass_tests, fail_tests

    saved_stdout = sys.stdout
    sys.stdout = io.StringIO()

    saved_argv = sys.argv
    sys.argv = ["hsum.py", "10"]

    if src_path == None:
        import hsum
    else:
        spec = importlib.util.spec_from_file_location("hsum", src_path + "/hsum.py")
        hsum = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(hsum)

    sys.argv = saved_argv

    out = sys.stdout.getvalue()
    sys.stdout = saved_stdout

    pass_tests = 0
    fail_tests = 0
    fun_count = 0

    if hasattr(hsum, "hSum"):
        fun_count = fun_count + 1
        test(hsum.hSum, 5, 2.283333333333333)
        test(hsum.hSum, 7, 2.5928571428571425)
    else:
        print("hSum is not implemented yet!")

    if hasattr(hsum, "main"):
        fun_count = fun_count + 1
        testEq(out,
               "0 0\n1 1.0\n2 1.5\n3 1.8333333333333333\n4 2.083333333333333\n5 2.283333333333333\n6 "
               "2.4499999999999997\n7 2.5928571428571425\n8 2.7178571428571425\n9 2.8289682539682537\n",
               "function main should generate exactly the same output as in the assignment")
    else:
        print("main is not implemented yet!")

    print(str(pass_tests) + " out of " + str(pass_tests + fail_tests) + " passed.")

    return fun_count == 2 and fail_tests == 0


if __name__ == "__main__":
    run()





Solution

  • Right now your code doesn't print or return anything. This may be a mistake in copy/pasting - if you unindent the final line main() then it runs and hSum() returns a single value of s as the code is written to. Right now this final line is executing as a part of the main() function itself (causing it to infinitely loop), but the function itself is never actually being called so nothing happens.

    Could you upload a copy of the test.py script too? It is unclear how it expects the result to be returned.

    Judging by the first quote it just wants you to print all of the values. You can do this easily by adding print(str(i) + " " + str(s)) in the for loop (after calculating s of course). You don't need return s as this will cause hSum() to "be equal" to s (for example value = hSum() would cause value to take the value returned by the function), but you aren't doing anything with that information.

    Using the following code I get 2/3 passes on the test. I will explain why later.

    import sys
    
    def hSum(n):
        s = 0
        for i in range(n + 1):
            if i == 0:
                s = 0
            else:
                s += 1 / i
            print(str(i) + " " + str(s))
        return s
    
    
    def main():
        hSum(int(sys.argv[1])) # a command he has specified for us to use
    
    main()
    

    Using return s was the correct thing to do, as it allows the test script to call the hSum(n) function and get the final value of s. This works correctly for test 1 and 2, returning the correct values for 5 and 7.

    The 3rd test I believe has an error, or a core difference in how the test code was implemented that I can't see. This issue causes a conflict - in test 1 and 2 it expects the function to return the values for 5 and 7 including 5 and 7. For the final test it expects it to return the values for 10 including 10 but print the values not including 10. It is possible to write a workaround for this in order to pass the test but while yes - you pass the arbitrary test - the code won't be as functional as it would be if you left it as is. This is a common issue with test driven development. Your tests need to be perfect to result in good code.

    Unless someone else has an explanation that I am missing I suggest messaging your tutor/lecturer and asking about this conflict.

    FWIW here is a workaround that simply doesn't print the final result. I don't recommend coding like this, but it passes all the tests.

    import sys
    
    def hSum(n):
        s = 0
        for i in range(n+1):
            
            if i == 0:
                s = 0
            else:
                s += 1 / i
            if(i != n):
                print(str(i) + " " + str(s))
        return s
    
    
    def main():
        hSum(int(sys.argv[1])) # a command he has specified for us to use
    
    main()