3

How can I reference variables defined within if/elif statements further on in the parent Python (3.6) function?

The below is a mock-up of my code - I am trying to 'print' or 'work with' variables defined in each 'if' and 'elif' block below, at the end of the function which contains the if/elif statements that the variables are defined within, however I get an unresolved reference error in Pycharm and the following Python error when I run the code:

    UnboundLocalError: local variable 'a1_summary' referenced before assignment

I am relatively new to Python and coding and don't understand where I am going wrong as I expect that as the variables are defined within the same function then they should be 'visible' to the print statement at the end of the function...

Is this a case where global variables should be used?

def Process_Data(incoming_data):

      if incoming_data.count(',') == 2:
         data_summary = incoming_data.split(',')
         a1_summary, b1_summary = data_summary[0], data_summary[1]

      elif incoming_body.count(',') == 3:
           data_summary = incoming_data.split(',')
           a2_summary, b2_summary, c2_summary = data_summary[0], data_summary[1], data_summary[2]

      print(a1_summary, b1_summary, a2_summary, b2_summary, c2_summary )

      else:
           pass

Update

I was trying to keep the question simple but may have confused things as I am trying to use the if/elif statements within a function that process messages from a RabbitMQ message queue - I need to process messages based on the number of commas in the respective message and assign the parts of the message to variables and then aggregate the parts from each message received and processed by the if/elif statements into one array so that I can then pass this to another function later on in my program.

I may be going wrong in thinking that each time the function receives incoming_data which matches either the if or elif statement - any previous or new data that matches the other condition will still be held as a variable within the function e.g. I am thinking that the function holds variables from both the if/elif blocks and only updates any new data processed by the corresponding logic statement...

halfer
  • 19,824
  • 17
  • 99
  • 186
Mark Smith
  • 757
  • 2
  • 16
  • 27
  • Define them outside of the if with a default value, then reassign them inside the if. – Carcigenicate Jul 12 '17 at 18:11
  • Thanks but i have tried defining a1_summary = '' and b1_summary = '' at the start of the function but the values don't seem to get updated when the if/elif statement does its processing - the print statement just 'prints' two spaces then the values of a2_summary, b2_summary, c2_summary. Sorry if I am being thick here but can you give any pointers? – Mark Smith Jul 12 '17 at 18:14
  • Only one of those statements will execute, so you'll have either a1 and b1 or a2, b2, and c2. And based on the indentation, your "else" statement should be throwing an error too. – mauve Jul 12 '17 at 18:17
  • @mauve - thanks - I think I see where I am going wrong - I was thinking the function would hold the values of the variables defined within each of the logic blocks and then I could aggregate them at the end of the function... should I use global variables to hold these variables as a work around - from what I've read people advise strongly against GVs... – Mark Smith Jul 12 '17 at 18:23

5 Answers5

1

Looking at your code, what I think you are trying to do can be done far more efficiently, and fix your error. It looks like you are trying to split the input by commas then print each element. Instead of doing what you are doing now, you could put them in a list, and then print the list. I think this will do what you want

def Process_Data(incoming_data):
    print(*incoming_data.split(","))

I am not really sure why you would do this, because it won't actually do anything except replace commas with spaces. If you want to return a tuple with the result, replace the print with

    return(tuple(incoming_data.split(",")))

You can also make this into a lambda:

Process_data = lambda incoming_data: print(*incoming_data.split(","))

or

Process_data = lambda incoming_data: return(tuple(incoming_data.split(",")))

If you want to change the types of the variables based on the number, that can definitely be done.

def Process_Data(incoming_data):
    data_input = incoming_data.split(",")
    if len(data_input) == 3:
         dataA, dataB, dataC = int(data_input[0]), str(data_input[1]), int(data_table[2])
    elif len(data_input) == 2:
         dataA, dataB, dataC = str(data_input[0]), int(data_input[1]), None
    return(dataA, dataB, dataC)

You can change the types as needed for the desired output. This is pretty similar to what you did, except a little shorter, and defining the remaining data point as None.

Sam Craig
  • 875
  • 5
  • 16
  • Thanks Sam, I'm sorry I didn't make this clearer but I'm trying to receive two messages from a queue, split the content of the messages based on the number of commas, into variables - some of which are strings and some of which are numbers - I then want to aggregate these variables so that I can pass them to another function to do calculations further on - sorry I didn't make this clearer - I was trying to simplify the question but may have confused things.. – Mark Smith Jul 12 '17 at 18:34
1

The problem with your code is that a1_summary and b1_summary only get defined if your incoming data contains two commas, and in this case, a2_summary, b2_summary, and c2_summary will never get defined.

The reversed problem appears with 3 commas in the input - and both if there is less than 2 or more than 3 commas.

So, whatever your input is, some of your variables never get defined.

There is absolutely no problem of scope here. If you want more information about scopes in Python 3, you can have a look at this answer.

To solve your problem, you could give these variables default values at the beginning of the function. This could be an empty string, for example

a1_summary = b1_summary = a2_summary = b2_summary = c2_summary = ''

Then, whatever path your code takes, all variables will have been defined at some point.

Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50
1

If this code:

def Process_Data(incoming_data):

      if incoming_data.count(',') == 2:
         data_summary = incoming_data.split(',')
         a1_summary, b1_summary = data_summary[0], data_summary[1]

      elif incoming_body.count(',') == 3:
           data_summary = incoming_data.split(',')
           a2_summary, b2_summary, c2_summary = data_summary[0], data_summary[1], data_summary[2]

      print(a1_summary, b1_summary, a2_summary, b2_summary, c2_summary )

causes this error:

UnboundLocalError: local variable 'a1_summary' referenced before assignment

it can only be because a1_summary has not been assigned a value when you try to use it in the print statement.

The only place you assign to a1_summary is in this block:

if incoming_data.count(',') == 2:
     data_summary = incoming_data.split(',')
     a1_summary, b1_summary = data_summary[0], data_summary[1]
     ^^^^^^^^^^

ergo this code hasn't been executed, which means that incoming_data.count(',') is not equal to 2.

This is also the reason why "the values don't seem to get updated" when you initialize a1_summary = "" etc. at the top of your function.

If you want the function to remember previous values you should use a class instead, e.g.

class DataProcessor(object):
    def __init__(self):
        self.a1 = self.a2 = self.b1 = self.b2 = self.c2 = ""

    def process(incoming_data):
        comma_count = incoming_data.count(',')
        if comma_count == 2:
            self.a1, self.b1 = data_summary[0], data_summary[1]
        elif comma_count == 3:
            self.a2, self.b2, self.c2 = ... you get the point...
        else:
            pass  # or log if it's an error condition
        self.print_summary()

    def print_summary(self):
        print(self.a1, self.b1, self.a2, self.b2, self.c2)

usage:

dp = DataProcessor()
...
dp.process(data)

If you don't want to change your API, you can call the process method __call__ instead, and then use it as:

Process_Data = DataProcessor()  # keep original function name
...
Process_Data(data)  # calls DataProcessor.__call_
thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • Thank you thebjorn - I will test what you have written and come back to you. – Mark Smith Jul 12 '17 at 21:09
  • Thanks for taking the time to post thebjorn - I am new to coding/python and tried to use your suggestion in my project but I lack understanding so was unable to get it to work as needed - I found a work around in as much as I defined variables in a config.py file and then imported and referenced these variables in my functions as needed - this seems to have worked as I can now do what I need to - I don't doubt that what you suggested is a better/more advanced way of doing this but at my current level of understanding it didn't work for me... I will work to change that! – Mark Smith Jul 13 '17 at 10:56
1

Thanks to all those who commented on my question, you have given me food for thought however I seem to have found a work around/solution to the problem as follows:

I created a config.py file and defined variables in it, e.g.

config.py

a1_summary = ""
b1_summary = ""

a2_summary = ""
b2_summary = ""
c2_summary = ""

Then in my 'main' python script file, I imported 'config' and referenced the variables it contains using the prefix 'config.' in my function - the updated code from my original example is shown below:

import config
...

def Process_Data(incoming_data):

  if incoming_data.count(',') == 2:
     data_summary = incoming_data.split(',')
     config.a1_summary, config.b1_summary = config.data_summary[0], config.data_summary[1]

  elif incoming_body.count(',') == 3:
       data_summary = incoming_data.split(',')
       config.a2_summary, config.b2_summary, config.c2_summary = config.data_summary[0], config.data_summary[1], config.data_summary[2]

  print(config.a1_summary, config.b1_summary, config.a2_summary, config.b2_summary, config.c2_summary )

  else:
       pass

This now allows me to aggregate the values from each of the if/elif statements into the print statement at the bottom of the code.

I think using a class as suggested by thebjorn above may be the prefered way of doing this however as I am new to python/coding this is a little beyond my understanding at present and the solution above allows me to do what I need to for the moment.

I realise I need to learn more about classes and intend to do so as I think using these would be more elegant and remove the need for an external config.py file.

I hope this may be useful to those who are currently in a similar position to myself.

Mark Smith
  • 757
  • 2
  • 16
  • 27
  • Thanks, after searching for a bit this is the solution that required the fewest code changes for me. – DaReal Nov 17 '18 at 22:34
0

Due to scoping, you will have to define them outside of the if/elif block, and then redefine them inside.