Five
Five

Reputation: 13

Overriding an attribute that is set using @property

I'm wondering how I can update an attribute that has been set using the @property decorator in this case. (the code below will tell you more than words...)

When I try to update the email without a setter, I get an AttributeError: can't set attribute. When I do use a setter, nothing changes. The new email doesn't use neither the firstname nor the lastname.

Could anybody help?

class Employee:
    def __init__(self, first, last):
        self.first = first
        self.last = last

    @property
    def email(self):
        return f"{self.first}.{self.last}@email.com".lower()

    # @email.setter
    # def email(self, new_email):
    #     return new_email

Upvotes: 1

Views: 123

Answers (2)

chris
chris

Reputation: 2063

I think the most straightforward path here would be to do away with the property, and instead make email an optional paramater that defaults to first.last:

class Employee:
    def __init__(self, first, last, email=None):
        self.first = first
        self.last = last
        self.email = email if email else f"{first}.{last}@email.com".lower()

Now you can modify the email address for an existing instance with the usual dot notation:

>>> e = Employee('John', 'Doe')
>>> e.email
'[email protected]'
>>>
>>> e.email = '[email protected]'
>>> e.email
'[email protected]'

If you're really intent on keeping the property, the setter needs to update an instance attribute:

class Employee:
    def __init__(self, first, last):
        self.first = first
        self.last = last
        self._email = f"{first}.{last}@email.com".lower()

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

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

You might choose this pattern if you need to do some validation on the way in - say, confirm the new email has an @ sign:

    @email.setter
    def email(self, addr):
        if '@' not in addr:
            raise ValueError('nope!')
        self._email = addr

but otherwise the first alternative is a bit simpler.

Upvotes: 2

Jab
Jab

Reputation: 27485

As I posted in my comment you need to determine if the new email is in [email protected] then just set the first and last attributes.

Although I wouldn't use email as a property if you're creating it based on the name you should change the name itself.

class Employee:
    def __init__(self, first, last):
        self.first = first
        self.last = last

    def email(self):
        return f"{self.first}.{self.last}@email.com".lower()

    def __repr__(self):
      return f"<Employee | {self.first} {self.last}>"

john = Employee('John', 'Smith')
print(john)
#<Employee | John Smith>
john.first = "Joe"
print(john)
#<Employee | Joe Smith>

Otherwise if you want a setter for email then I suggest using it to set the first and last attributes but you don't need a return value as you're just setting the email you'd already know it. I'd use the re library to check if the email is in correct format. This is a very rough example:

@email.setter
def email(self, new_email):
    try:
        self.first, self.last = re.search('(?P<first>\w+).(?P<last>\w+)@\S+', email).group('first', 'last'))
    except AttributeError:
        raise ValueError("Email must be in "[email protected]")

Upvotes: 0

Related Questions