Method attribute is not updating itself even thoug

2020-03-07 07:29发布

问题:

Upon updating a value, the __init__ method still uses the old attribute value.

class Email:
    def __init__(self, name):
        self.name = name
        self.email = self.name + "@hotmail.com"

    def details(self):
        return f'{self.name} | {self.email}'

person = Email("James")
print(person.details())

person.name = "Michael"
print(person.details())

Output gotten:

James | James@hotmail.com
Michael | James@hotmail.com

Output expected:

James | James@hotmail.com
Michael | Michael@hotmail.com

What am I doing wrong?

回答1:

What went wrong in the OP?

If we open up the person object right after you instantiated it with the line person = Email("James"), it would be something like this:

person {
"name"  : "James", 
"email" : "James@hotmail.com"
}

If we update the name variable in this person object as you did with person.name = "Michael", and open up the person object again, it would be:

person {
"name"  : "Michael", 
"email" : "James@hotmail.com"
}

Please note that at this stage, it is still the same person object we instantiated earlier, and the email variable is not changed from its previous state, because we didn't do anything about it, yet.


The answer posted by @chepner is very nice and clean, by setting email field as a property, and by setting it dynamically using the name variable. Below is a copy of @chepner 's code:

class Email:

    def __init__(self, name):
        self.name = name
        # self.email = self.name + "@hotmail.com"

    @property
    def email(self):
        return f'{self.name}@hotmail.com'

    def details(self):
        return f'{self.name} | {self.email}'

What is property and how is it used?

property is a Python Built-in Function, a property object has these methods:

  • getter: used to get an attribute value
  • setter: used to set an attribute value
  • deleter: used to delete an attribute value

Now, what exactly is happening in @chepner 's answer? With the lines below, we are setting the email as a property:

    @property
    def email(self):
        return f'{self.name}@hotmail.com'

And this decorated function can also be used as a getter, e.g. person.email.

Note that in here we didn't link it to a variable (as shown in the Python documentation example), if we want / need, we can do so by replacing the return statement and set an _email variable in __init__:

    def __init__(self, input_name):
        self._name = input_name
        self._email = f'{self._name}@hotmail.com'

    @property
    def email(self):
        return self._email

And then, as for the setter and deleter, we can create them as:

    @email.setter
    def email(self, input_email):
        self._email = input_email

    @email.deleter
    def email(self):
        del self._email

Note that the getter, setter, and deleter methods all have the same name as the property, just different decorators.

From the question description it is not needed to support updating the email address separately, but just following this example here, if we run person.email = "Michael@hotmail.com", because email is a property, it is triggering the setter to set the value to the _email variable.


Going back to the details method in @chepner 's answer:

    def details(self):
        return f'{self.name} | {self.email}'

By doing self.email we are triggering the getter to return the email address, which is dynamically generated in that return statement return f'{self.name}@hotmail.com'.



回答2:

In this example, self.email is being assigned in the __init__ method of the class, which is only called when an instance of Email is created. As such, when self.name is reassigned, self.email is not changed. To work around this, you can use a property decorator:

class Email:
  def __init__(self, name):
    self._name = name
    self._email = f'{name}@hotmail.com'
  def details(self):
    return f'{self._name} | {self._email}'
  @property
  def name(self):
    return self._name
  @name.setter
  def name(self, _new_name):
    self._name = _new_name
    self._email = f'{_new_name}@hotmail.com'

person = Email("James")
print(person.details())
person.name = "Michael"
print(person.details())

Output:

James | James@hotmail.com
Michael | Michael@hotmail.com


回答3:

The simplest fix would be to make email a property, rather than an attribute you set in __init__.

class Email:

    def __init__(self, name):
        self.name = name
        # self.email = self.name + "@hotmail.com"

    @property
    def email(self):
        return f'{self.name}@hotmail.com'

    def details(self):
        return f'{self.name} | {self.email}'


回答4:

As @Ajax1234 saying, the init function is calling when you create a new instance. Think it like a constructor if you are familiar with an object oriented language.

So, if you just change the person.name, you can change the name field but not the email variable which assigned with older value of name.

You have a few options about that case:

  • You can create a new Email object with name of Michael.

  • You can re-assign the e-mail directly, like you do in init function.

  • You can create a "setter" method for name like setName(name) and you can update the email in this function. So, when you call the setter, it automatically updates the email.



标签: python class