Why does python print memory location rather than class.name in some cases but not others in my code?

Question:

My code has a mathematical function that iterates over a list of county class objects and returns the county with the max voter turnout along with that turnout in a tuple.

Code here:

class County:
  def __init__(self, init_name, init_population, init_voters):
    self.name = init_name
    self.population = init_population
    self.voters = init_voters
    
def highest_turnout(data) :
  max_turnout_county = data[0]
  max_turnout = (data[0].voters / data[0].population)
  for i in range(0,6):
    if (data[i].voters / data[i].population) > max_turnout:
      max_turnout_county = data[i].name
      max_turnout = (data[i].voters / data[i].population)

  return (max_turnout_county, max_turnout)


allegheny = County("allegheny", 1000490, 645469)
philadelphia = County("philadelphia", 1134081, 539069)
montgomery = County("montgomery", 568952, 399591)
lancaster = County("lancaster", 345367, 230278)
delaware = County("delaware", 414031, 284538)
chester = County("chester", 319919, 230823)
bucks = County("bucks", 444149, 319816)
data = [allegheny, philadelphia, montgomery, lancaster, delaware, chester, bucks]  

result = highest_turnout(data) 
print(result) # prints the output of the function

In its current state, it will return the desired output.
(‘chester’, 0.7215045058280377)

However, if I change the county with the highest output, for example, if I change Allegheny voters from 645469 to 1000480 so that Allegheny is now the max turnout county the output will no longer return the county name in the tuple as expected, but rather the memory location.

Output here:
(<submission.County object at 0x7f8e37d3cc18>, 0.9999900048976001)

Why is my code outputting memory location in the second case but not the first case and how would I fix this?

Asked By: Flann3l

||

Answers:

There is bug in your code.
In first line, you are assigning whole County object to the max_county_variable

max_turnout_county = data[0]

later, you are assigning only the attribute name:

 max_turnout_county = data[i].name

To fix, you can just change first line to:

max_turnout_county = data[0].name
Answered By: Blomex

You initialize max_turnout_county with an instance of County from the argument:

max_turnout_county = data[0]

That should be initialized to the name of the county:

max_turnout_county = data[0].name

Alternatively, you can add a turnout property to your County class:

class County :
  def __init__(self, init_name, init_population, init_voters):
    self.name = init_name
    self.population = init_population
    self.voters = init_voters

  @property
  def turnout(self):
     return self.voters / self.population

which simplifies your function greatly:

def highest_turnout(data) :
  return max(data, key=lambda c: c.turnout)
  # Or, if you really want to return a tuple instead of
  # an instance of County,
  # c = max(data, key=lambda c: c.turnout)
  # return (c.name, c.turnout)
  
Answered By: chepner

Add this

 def __str__(self):
        return self.name

str method is used to show name of objects rather than memory location and it must be a string.

Answered By: Sunderam Dubey

What controls how objects of a class will display themseleves when printed is the existense of a __repr__ (or, if you want to distinguish an internal representation for debugging from actually being printed-out, the __str__) method.

The default __repr__, howver, if you don’t write one in your class, is the class name and memory position.

In your snippet above, when certain conditions are met, max_turnout_county is assigned with data[i].name– i.e. the name of your instance, which is a tring. If that condition is not met, the same variable remains assigned with the object itself data[0].

Depending on what you want to do with these objetcts, the best thing is to write a proper __repr__ method for them, and do not use only the .name, but rather, the actual object in any assignments. The object repr can even take care of outputting any other attributes you care about as well, so no need to keep two state variables in your function,and returning a tuple:

class County :
  def __init__(self, init_name, init_population, init_voters):
    self.name = init_name
    self.population = init_population
    self.voters = init_voters
    
  # Bonus: yoru object can have a calculated property:
  @property
  def turnout(self):
      return self.voters/self.population
      
  def __repr__(self):
    return f"{self.name}, turnout: {(self.turnout * 100):.02f}"

def highest_turnout(data) :
  max_turnout_county = data[0]
  max_turnout = (data[0].voters / data[0].population)
  for i in range(0,6):
    if (data[i].turnout) > max_turnout:
      max_turnout_county = data[i]    # <- this would change
      max_turnout = (data[i].voters / data[i].population)

  return max_turnout_county


allegheny = County("allegheny", 1000490, 645469)
philadelphia = County("philadelphia", 1134081, 539069)
montgomery = County("montgomery", 568952, 399591)
lancaster = County("lancaster", 345367, 230278)
delaware = County("delaware", 414031, 284538)
chester = County("chester", 319919, 230823)
bucks = County("bucks", 444149, 319816)
data = [allegheny, philadelphia, montgomery, lancaster, delaware, chester, bucks]  

result = highest_turnout(data) 
print(result) # prints the output of the function

# as part of the bonus - you don't even need a function for that,
# as each object now "knows" its turnout, Python's built-in 
# "max" function can find your optimal county:
print(max(data, key=lambda c: c.turnout))
Answered By: jsbueno