1
Day 5 and 6 Homework Karen
Karenzz edited this page 2023-04-12 00:44:00 +02:00

The assignment for tonight is to write a class named Vault containing an attribute named "treasure" which returns "gold" under normal circumstances, but a warning message such as "attribute deleted!" if it detects any of its attributes have been changed. You can use the descriptor protocol (set, etc) to detect if its attributes have been changed, and it will have to check its own dict against a known good copy before accessing the treasure attribute to make sure that the attributes are what they're supposed to be.

class Vault:
    def __init__(self):
        self._treasure = "gold"
        self._original_dict = dict(self.__dict__)

    def __setattr__(self, name, value):
        if name != "_treasure" and name in self.__dict__ and self._original_dict.get(name) != value:
            print("attribute deleted!")
        self.__dict__[name] = value

    @property
    def treasure(self):
        if self.__dict__ != self._original_dict:
            print("attribute deleted!")
        return self._treasure
v = Vault()
print(v.treasure)

Here is another method using __getattribute__ and __setattr__.

class Vault:
    def __init__(self):
        self._treasure = "gold"
        self._known_good_copy = self.__dict__.copy()

    def __getattribute__(self, name):
        if name == "treasure":
            if self.__dict__ != self._known_good_copy:
                print("Attribute deleted!")
            return self._treasure
        else:
            return object.__getattribute__(self, name)

    def __setattr__(self, name, value):
        if name != "_treasure" and name != "_known_good_copy":
            print("Attribute deletion detected!")
            self._known_good_copy = self.__dict__.copy()
        object.__setattr__(self, name, value)
v = Vault()
print(v.treasure)

Also, we can define a descriptor by implementing the __get__, __set__, and __delete__ methods.

class Vault:
    def __init__(self):
        self._treasure = "gold"
        self._known_good_copy = self.__dict__.copy()

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            if instance.__dict__ != self._known_good_copy:
                print("Attribute deleted!")
            return self._treasure

    def __set__(self, instance, value):
        print("Attribute deletion detected!")
        self._known_good_copy = instance.__dict__.copy()

    def __delete__(self, instance):
        print("Attribute deletion detected!")
        self._known_good_copy = instance.__dict__.copy()

    def __set_name__(self, owner, name):
        self.name = name
v = Vault()
print(v._treasure)

Unfortunately, I didn't figure out what happened to the code that I used super().__setattr__ last week, so I create a question on stack overflow to see if someone can help me with this issue. https://stackoverflow.com/questions/75990535/attributeerror-and-recursionerror-happened-when-using-setattr-in-python

class Vault:
    def __init__(self):
        self._treasure = "gold"
        self._attributes = dict(self.__dict__)

    def __setattr__(self, name, value):
        if name != "_attributes" and name not in self._attributes:
            print("attribute deleted!")
        self._attributes[name] = value
        super().__setattr__("_attributes", dict(self.__dict__))

    @property
    def treasure(self):
        if self.__dict__ != self._attributes:
            print("attribute deleted!")
        return self._treasure

v = Vault()
print(v.treasure)

To assess other's Vault (although it may not be ethical), we can use __getattribute__ method like:

class EvilVault(Vault):
    def __getattribute__(self, name):
        if name == "treasure":
            return "gold"
        return super().__getattribute__(name)

Or I think we can use another easier way as accessing the attribute through the class's dictionary:

Vault.__dict__["_treasure"]