Python Classes Instance Variable as Method Keyword Argument

I am working on the Python Classes: Medical Insurance Project.

In the extra steps you can do for practice, it says to try writing more methods to update the various parameters (bmi, smoker, etc.). I tried writing one method that would take keyword arguments and use control flow to update any of the parameters. The problem is it seems you can’t use an instance variable as a default value such as new_age = self.age - I get NameError: name 'self' is not defined. See def update() in my code below.
I guess my questions are:

  1. Why does it give this error? Have I not defined self? I am just learning and the logic behind classes is not crystal clear in my head.
  2. Can you somehow reference the current age (self.age) as a default for a keyword argument?
  3. Is there a better way to go about this, while retaining one method (.update()) to update all parameters, without needing to update all of them at once?
class Patient:
  def __init__(self, name, age, sex, bmi, children, smoker):
    self.name = name
    self.age = age
    self.sex = sex
    self.bmi = bmi
    self.children = children
    self.smoker = smoker

  def pt_profile(self):
    pt_info = {'name': self.name, 'age': self.age, 'sex': self.sex, 'bmi': self.bmi, 'children': self.children, 'smoker': self.smoker}
    return pt_info

  def est_insurance(self):
    est_cost = 250 * self.age - 128 * self.sex + 370 * self.bmi + 425 * self.children + 24000 * self.smoker - 12500
    print(self.name + "'s estimated insurance cost is $" + str(est_cost) + '.')

  def update(self, new_name = self.name, new_age = self.age, new_sex = self.sex, new_bmi = self.bmi, new_children = self.bmi, new_smoker = self.smoker):
    if new_name != self.name:
      print(self.name + ' has now changed their name to ' + new_name + '.')
      self.name = new_name
    elif new_age != self.age:
      self.age = new_age
      print(self.name + ' is now ' + str(self.age) + ' years old.')
    elif new_sex != self.sex:
      self.sex = new_sex
      if self.sex == 1:
        print(self.name + ' is now a male.')
      elif self.sex == 0:
        print(self.name + ' is now a female.')
    elif new_bmi != self.bmi:
      self.bmi = new_bmi
      print(self.name + ' now has a BMI of ' + str(self.bmi))
    elif new_children != self.children:
      self.children = new_children
      if self.children == 1:
        print(self.name + ' now has ' + str(self.children) + ' child.')
      else:
        print(self.name + ' now has ' + str(self.children) + ' children.')
    elif new_smoker != self.smoker:
      self.smoker = new_smoker
      if self.smoker == 1:
        print(self.name + ' has started smoking.')
      elif self.smoker == 0:
        print(self.name + ' has quit smoking.')
    self.est_insurance()

pt1 = Patient('John Doe', 25, 1, 22.2, 0, 0)
pt1.est_insurance()
pt1.update(new_bmi = 20)

Let’s simplify your example a little bit

class Patient:
  def __init__(self, name):
    self.name = name

  def update(self, new_name = self.name):
    pass

This will throw the same NameError: name 'self' is not defined.
My suspicion is that because the class isn’t initialized, self.name cannot be referenced yet.

This also fails to run

class Patient:
  def __init__(self, name):
    self.name = name

  def update(self, self.name):
    pass

But let’s consider, what does putting self.name as an argument really do?

  • self.name is automatically set at initialization
  • which means within the scope of update() we can always call self.name.

Consider:

#within Patient class
def comparison(self, new_name):
    if self.name != new_name:
       print("These values are different")
1 Like

Thanks a lot!
I changed my code to:

  def update(self, new_name = None):
    if new_name != None:
      self.name = new_name

That enables using keyword arguments so I can update one or two variables without needing to input all six. I guess I didn’t think it through enough, I could really replace None with anything - there was no need for it to be self.name.

I’m kind of guessing here, but am I correct in assuming that the whole line (all arguments) is read before it assigns the first argument self to the object, and hence why it says it isn’t defined?

That’s a decent guess. Function definitions are run just once when they’re first created (the first line, not the function code block). In this case it’d be well before any instances are created, hence the issue with self.

On a side note this relates to another issue that can catch people out when they use mutable types as default arguments to a function (e.g. an empty list). Since the definition is only ever run once if that list is mutated e.g. with .append then each new call to the function uses the same list… but with the .append, it’s no longer empty!

A couple of extra options you could explore. You could check out the setatrr function if you wanted a potentially more generalised solution which can be quite neat for setting multiple simple attributes. It’s probably best to ignore it for now but for more robust attribute setting consider the @property decorator.

2 Likes