[Tutor] cases with single if and an else clause

dn PyTutor at DancesWithMice.info
Tue Oct 5 15:08:56 EDT 2021


On 06/10/2021 07.39, Dennis Lee Bieber wrote:
> On Tue, 5 Oct 2021 07:22:24 +0530, Manprit Singh
> <manpritsinghece at gmail.com> declaimed the following:
> 
>> Dear sir,
>>
>> Now there is one more problem ,  Kindly loom at below written functions :
>>
>> def grade(percent):
>>    if percent < 0 or percent > 100:
>>        ans = "Invalid Input"
>>    elif percent >= 90:
>>        ans = "A"
>>    elif percent >= 70:
>>        ans = "B"
>>    elif percent >= 60:
>>        ans = "C"
>>    else:
>>        ans =  "Fail"
>>    return ans
>>
> 
> 	My biggest concern is that you are using the same "ans" return to
> indicate invalid input, letter grades (you have a big gap between A and B
> -- at least in US you'd commonly have breakpoints A:90, B:80, C:70, D:60,
> and E (or F) at 50). 
> 
> 	Presuming the numeric grade is being entered by the user of some
> program (say a teacher's electronic grade book) the input should have been
> validated at the place it was entered, NOT in a function that just
> translates number ranges into a letter grade.
> 
> 	If you really need to validate /in/ this function, you should be
> raising an exception... and the caller needs to catch this exception and do
> whatever is needed to correct the data before retrying the operation. As
> is, the caller is forced to test every return from the function for a
> SPECIFIC string...
> 
> aGrade = grade(score)
> if aGrade == "Invalid Input":
> 	#do something to correct the input and retry the call to grade()
> else:
> 	#record the returned letter grade
> 
> 
> 
> 	There are multiple ways to avoid a chain of if/elif/else comparisons...
> But for that you need to study /data structures/ and the algorithms using
> them rather than just playing around with simple examples that would seldom
> be used in actual applications. Especially examples that are mostly
> concerned with /coding style/ than on flexibility (algorithm reuse). If the
> code runs, it is technically correct -- but may look ugly and locked in to
> just the one application. Coding style, these days, tends to be defined by
> the company one works at, and one has to follow some guide book that
> already exists at that company. If you want to follow the One-In/One-Out of
> Structured Programming -- try to lay out your code using
> https://en.wikipedia.org/wiki/Nassi%E2%80%93Shneiderman_diagram
> (even in OOP, once one gets down to coding class methods, one should be
> back to structured programming)
> 
> -=-=-=-
> C:\Users\Wulfraed\Documents\_Hg-Repositories\Python Progs>letterGrade.py
> Enter percentage grade for Abigail: -5
> Invalid numeric grade
> Please try again
> Enter percentage grade for Abigail: 45
> 
> Letter grade for Abigail is F
> 
> 
> Enter percentage grade for Bertram: 110
> Invalid numeric grade
> Please try again
> Enter percentage grade for Bertram: 99
> 
> Letter grade for Bertram is A
> 
> 
> Enter percentage grade for Charlie: xyz
> invalid literal for int() with base 10: 'xyz'
> Please try again
> Enter percentage grade for Charlie: 68.5
> invalid literal for int() with base 10: '68.5'
> Please try again
> Enter percentage grade for Charlie: 68
> 
> Letter grade for Charlie is D
> 
> 
> Enter percentage grade for Edith: 77
> 
> Letter grade for Edith is C
> 
> 
> Enter percentage grade for Francisca: 81
> 
> Letter grade for Francisca is B
> 
> 
> 
> C:\Users\Wulfraed\Documents\_Hg-Repositories\Python Progs>
> -=-=-=-
> STUDENTS = [    "Abigail",
>                 "Bertram",
>                 "Charlie",
>                 "Edith",
>                 "Francisca" ]
> 
> GRADES = [ (60, "F"),
>            (70, "D"),
>            (80, "C"),
>            (90, "B"),
>            (100, "A") ]	#ordered list of break points
> 
> def grade(percent):
>     if percent < 0 or percent > 100:
>         raise ValueError("Invalid numeric grade")
>     for pc, ltr in GRADES:
>         if percent <= pc: return ltr
> 
> for st in STUDENTS:
>     while True:
>         try:
>             pcent = input("Enter percentage grade for %s: " % st)
>             pcent = int(pcent)
>             ltrGrade = grade(pcent)
>             print("\nLetter grade for %s is %s\n\n"
>                   % (st, ltrGrade))
>             break
>         except ValueError as ve:
>             print(str(ve))
>             print("Please try again")
>     
> -=-=-=-
> 
> 	For the example, I've left GRADES as a read-only "global". For reuse
> purposes, it should be passed as an argument to the grade() function.
> 
> 	This is easily modifiable for use when grading "by the curve".
> Simplified version: "C" is the mean of the scores, and one then works
> outwards; all that needs to be changed is the GRADES list; so if the mean
> came in at 60 then GRADES would look like:
> 
> GRADES = [ (45, "F"),
>            (55, "D"),
>            (65, "C"),		#allow 5 below to 5 above
>            (75, "B"),
>            (100, "A") ]	#don't change to catch outliers
> 
> If you input the scores per student first, you can have the program
> calculate the mean and modify GRADES as needed.. Granted, this simplified
> version will have problems if the scores are near the extremes... mean of
> 85 makes GRADES look like
> 
> GRADES = [ (70, "F"),
>            (80, "D"),
>            (90, "C"),		#allow 5 below to 5 above
>            (100, "B"),
>            (100, "A") ]	#no one gets a "A"
> 
> The more complex version would compute %iles using mean&std.dev. and use
> those to set the break points for letter grades.


+1

Those of us who learned "Structured Programming" when it was 'new'
probably still think of it as a 'thing'. (I'm not sure how often such is
mentioned in 'today's training', and haven't seen anyone under thirty
using a Nassi-Schneiderman chart for ages) We also take quite-naturally
to the SRP (Single Responsibility Principle)!

The OP named the function "grade" - in a conversation about 'style'.
Choosing names is important. It's also a matter of style - a weak name
requires a strong comment/docstring! In this case, the function lacks a
descriptive name.

As soon as one describes the (original) functionality as 'check validity
of numeric grade input AND convert to a letter-grade, if possible' the
problem @wulfraed illustrates becomes starkly-evident.

Secondly, we refer to successive mutually-exclusive if-statements as a
"ladder". It is logical that the conditions proceed systematically from
low-to-high, or vice-versa. Certainly "method" is better than "madness",
and testing becomes more methodical. (easier?) Accordingly, the first
if-statement is discordant because it can't make up its mind - is it the
'top' of the ladder, or the bottom? Such is NOT as readable as it
(easily) could/should be!

If the function includes an if-statement (to trap invalid data), won't
the calling code also have to cope with the same possibility? Does this
mean that there will be two if-statements which ask essentially the same
question, in two different scopes? Is that a good idea?

Instead of over-loading a data-object (see earlier post), is it time to
look at using Exceptions to convey meaning and handle unwanted
'edge-cases'?
(however, I'd prefer the 'solution' mooted above)
-- 
Regards,
=dn


More information about the Tutor mailing list