[Tutor] How to avoid "UnboundLocalError: local variable 'goal_year' referenced before assignment"?

boB Stepp robertvstepp at gmail.com
Sun Mar 24 02:54:11 EDT 2019


Ah!  After another long break from coding, I resume a pattern of one
step forward, who knows how many steps back, as I forget what I once
remembered and understood.

It is the wee morning hours where I am at, so I don't think I will
make it through everything, but I will make a start...

On Sun, Mar 24, 2019 at 12:22 AM Cameron Simpson <cs at cskk.id.au> wrote:
>
> Discussion below your post here, since I feel I should quote it all:
>
> On 23Mar2019 22:15, boB Stepp <robertvstepp at gmail.com> wrote:

> >Traceback (most recent call last):
> >  File "pages_per_day.py", line 250, in <module>
> >    start_pgm()
> >  File "pages_per_day.py", line 246, in start_pgm
> >    goal_date_obj, pages_read, total_pages_to_read = get_inputs()
> >  File "pages_per_day.py", line 63, in get_inputs
> >    'date_value_err_ck': (goal_year, 1, 1),
> >UnboundLocalError: local variable 'goal_year' referenced before assignment
> >
> >I understand this result, but cannot come up with a way to implement
> >my desired DRY strategy as I am stuck on how to get around this "local
> >variable ... referenced before assignment" issue.  On subsequent
> >passes "goal_year" will become "goal_month" and then "goal_day" as the
> >user needs to input all three of these numbers.  Is there a way to
> >accomplish my goal or am I attempting to be too clever?
>
> You're not attemnpting to be too clever, but you are making some basic
> errors. For example, you can't just run a string like 'goal_year <
> date.today).year' and you shouldn't be trying i.e. do _not_ reach for
> the eval() function.

Too late!  About the time your message hit my inbox, I just finished a
working replacement after pulling the the eval() trigger.  I found a
non-eval() workaround for dealing with date(*date_value_err_ck), but
could not come up with anything better than using
eval(condition.format(user_input)) to replace the "if condition:",
where the embedded "{0}" in "condition" from the calling code is being
used to pass in the actual user input value.  I imagine a clever user
(other than myself) could now wreak all sorts of havoc!  I totally got
rid of "identifier" as an argument.

> Let's look at your get_input() function. It basicly loops until you get
> a value you're happy with, and returns that value. Or it would if it
> were correct. Let's examine the main loop body:
>
>     try:
>         identifier = int(input(input_prompt))
>         if date_value_err_ck:
>             date(*date_value_err_ck)
>     except ValueError:
>         print(err_msg)
>         continue
>     for (condition, condition_msg) in conditions:
>         if condition:
>             print(condition_msg)
>             break
>     else:
>         return identifier
>
> To start with, you have confusion in the code bwteen the name you're
> intending to use for the input value (the "identifier" parameter) and
> the value you're reading from the user. You go:

It wasn't really confusion on my part.  What I *wanted* to do was to
substitute a more specific identifier from the calling code for the
generic "identifier" in the get_input() function, and use it both for
the actual user input and to fill in that value wherever I need it.
But I could not find a way to accomplish this and just left my
question at the last error state.  But I guess this is not a doable
thing.

>     identifier = int(input(input_prompt))
>
> That immediatey destroys the name you passed in as a parameter. Instead,
> use a distinct variable for the input value. Let's be imaginitive and
> call it "value":

Or "user_input", which I finally wound up with.

> Then you try to create a date from that value (though you don't save it
> anywhere). I presume you want to use the datetime.date() constructor.
> So:
>
>     # at the start of your programme
>     import datetime
>
> then in the function:
>
>     date = datetime.date(*date_value_err_ck)
>
> I think your plan is that datetime.date() will also raise a ValueError
> for a bad year number in "value". So you want, in fact, to call:

That is, in fact, the plan.  Since I only want to check for a bad
year, then a bad month, and finally, a bad day of the month, I saw no
point in binding a name to the date object.  In the part of the code I
did not include, I create a date object from date(goal_year,
goal_month, goal_day) to use in the rest of the program.

> Let me introduce you to the lambda.

Or, in my case, "reintroduce" me to the lambda.  It has been probably
at least a couple of years since I had need of this and I had totally
forgotten about it!  There are those backward steps again!

I guess I will have to head for bed now and look more closely at the
rest later today.  But I can see that this totally eliminates any need
for eval() -- which I did not want to use anyway -- and solves what I
was struggling so much with.

As for closures, I will have to read that portion *most* carefully
later.  I know I have asked some questions in the past where these
were brought up as answers, but this did not stick in my brain at all.

Thanks, Cameron, for the time you took for your in-depth help!!!

G'night,
boB


More information about the Tutor mailing list