From 21b38b3d640bccc75dc29dc00ef4981dcc984fb8 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Thu, 13 Mar 2025 17:37:00 +0100 Subject: [PATCH 1/5] Sample code for the article on `.__dict__` --- python-dict-attribute/README.md | 3 ++ python-dict-attribute/class_inheritance.py | 16 +++++++++++ python-dict-attribute/config_v1.py | 16 +++++++++++ python-dict-attribute/config_v2.py | 21 ++++++++++++++ python-dict-attribute/config_v3.py | 21 ++++++++++++++ python-dict-attribute/demo.py | 11 ++++++++ python-dict-attribute/fibonacci.py | 27 ++++++++++++++++++ python-dict-attribute/person_v1.py | 22 +++++++++++++++ python-dict-attribute/person_v2.py | 26 ++++++++++++++++++ python-dict-attribute/record.py | 32 ++++++++++++++++++++++ python-dict-attribute/track_calls.py | 10 +++++++ 11 files changed, 205 insertions(+) create mode 100644 python-dict-attribute/README.md create mode 100644 python-dict-attribute/class_inheritance.py create mode 100644 python-dict-attribute/config_v1.py create mode 100644 python-dict-attribute/config_v2.py create mode 100644 python-dict-attribute/config_v3.py create mode 100644 python-dict-attribute/demo.py create mode 100644 python-dict-attribute/fibonacci.py create mode 100644 python-dict-attribute/person_v1.py create mode 100644 python-dict-attribute/person_v2.py create mode 100644 python-dict-attribute/record.py create mode 100644 python-dict-attribute/track_calls.py diff --git a/python-dict-attribute/README.md b/python-dict-attribute/README.md new file mode 100644 index 0000000000..5f0914c2b4 --- /dev/null +++ b/python-dict-attribute/README.md @@ -0,0 +1,3 @@ +# Using Python's `.__dict__` to Work With Attributes + +This folder provides the code examples for the Real Python tutorial [Using Python's `.__dict__` to Work With Attributes](https://realpython.com/python-dict-attribute/). diff --git a/python-dict-attribute/class_inheritance.py b/python-dict-attribute/class_inheritance.py new file mode 100644 index 0000000000..a7282faa4d --- /dev/null +++ b/python-dict-attribute/class_inheritance.py @@ -0,0 +1,16 @@ +class Parent: + def __init__(self): + self.parent_attr = "parent" + + +class Child(Parent): + def __init__(self): + super().__init__() + self.child_attr = "child" + + +parent = Parent() +print(parent.__dict__) + +child = Child() +print(child.__dict__) diff --git a/python-dict-attribute/config_v1.py b/python-dict-attribute/config_v1.py new file mode 100644 index 0000000000..f5e06f1f86 --- /dev/null +++ b/python-dict-attribute/config_v1.py @@ -0,0 +1,16 @@ +class Config: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + update = __init__ + + def __str__(self): + return str(self.__dict__) + + +config = Config(theme="light", font_size=12, language="English") +print(config) + +user = {"theme": "dark", "font_size": 14, "language": "Spanish"} +config.update(**user) +print(config) diff --git a/python-dict-attribute/config_v2.py b/python-dict-attribute/config_v2.py new file mode 100644 index 0000000000..22405b7633 --- /dev/null +++ b/python-dict-attribute/config_v2.py @@ -0,0 +1,21 @@ +class Config: + def __init__(self, name): + self.name = name + + def set_option(self, key, value): + setattr(self, key, value) + + def get_option(self, key): + return getattr(self, key, None) + + def remove_option(self, key): + if hasattr(self, key): + delattr(self, key) + print(f"'{key}' removed!") + else: + print(f"'{key}' does not exist.") + + def clear(self): + for key in list(self.__dict__.keys()): + delattr(self, key) + print("All options removed!") diff --git a/python-dict-attribute/config_v3.py b/python-dict-attribute/config_v3.py new file mode 100644 index 0000000000..f09f2db32e --- /dev/null +++ b/python-dict-attribute/config_v3.py @@ -0,0 +1,21 @@ +class Config: + def __init__(self, name): + self.name = name + + def set_option(self, key, value): + setattr(self, key, value) + + def get_option(self, key): + return getattr(self, key, None) + + def remove_option(self, key): + if key in self.__dict__: + delattr(self, key) + print(f"'{key}' removed!") + else: + print(f"'{key}' does not exist.") + + def clear(self): + for key in self.__dict__: + delattr(self, key) + print("All options removed!") diff --git a/python-dict-attribute/demo.py b/python-dict-attribute/demo.py new file mode 100644 index 0000000000..db71bc9113 --- /dev/null +++ b/python-dict-attribute/demo.py @@ -0,0 +1,11 @@ +class DemoClass: + class_attr = "This is a class attribute" + + def __init__(self): + self.instance_attr = "This is an instance attribute" + + def method(self): + return "This is a method" + + +print(DemoClass.__dict__) diff --git a/python-dict-attribute/fibonacci.py b/python-dict-attribute/fibonacci.py new file mode 100644 index 0000000000..f194c2d157 --- /dev/null +++ b/python-dict-attribute/fibonacci.py @@ -0,0 +1,27 @@ +import time + + +def fibonacci_of_v1(n): + if n <= 1: + return n + return fibonacci_of_v1(n - 1) + fibonacci_of_v1(n - 2) + + +start = time.perf_counter() +fibonacci_of_v1(35) +end = time.perf_counter() +print(f"{end - start:.3f} seconds") + + +def fibonacci_of_v2(n): + cache = fibonacci_of_v2.__dict__.setdefault("cache", {}) + if n in cache: + return cache[n] + cache[n] = n if n <= 1 else fibonacci_of_v2(n - 1) + fibonacci_of_v2(n - 2) + return cache[n] + + +start = time.perf_counter() +fibonacci_of_v2(35) +end = time.perf_counter() +print(f"{end - start:.3f} seconds") diff --git a/python-dict-attribute/person_v1.py b/python-dict-attribute/person_v1.py new file mode 100644 index 0000000000..6f620d7fb2 --- /dev/null +++ b/python-dict-attribute/person_v1.py @@ -0,0 +1,22 @@ +class Person: + def __init__(self, first_name, last_name, age): + self.first_name = first_name + self.last_name = last_name + self.age = age + + def __str__(self): + return "{first_name} {last_name} is {age} years old".format( + **self.__dict__ + ) + + def __repr__(self): + return "{cls}('{first_name}', '{last_name}', {age})".format( + cls=type(self).__name__, + **self.__dict__, + ) + + def as_dict(self): + return self.__dict__ + + def as_tuple(self): + return tuple(self.__dict__.values()) diff --git a/python-dict-attribute/person_v2.py b/python-dict-attribute/person_v2.py new file mode 100644 index 0000000000..c9738e43be --- /dev/null +++ b/python-dict-attribute/person_v2.py @@ -0,0 +1,26 @@ +class Person: + def __init__(self, first_name, last_name, age): + self.first_name = first_name + self.last_name = last_name + self.age = age + + def __str__(self): + return "{first_name} {last_name} is {age} years old".format( + **self.__dict__ + ) + + def __repr__(self): + return "{cls}('{first_name}', '{last_name}', {age})".format( + cls=type(self).__name__, + **self.__dict__, + ) + + def as_dict(self): + return self.__dict__ + + def as_tuple(self): + return tuple(self.__dict__.values()) + + def from_dict(self, data): + self.__dict__.update(data) + return self diff --git a/python-dict-attribute/record.py b/python-dict-attribute/record.py new file mode 100644 index 0000000000..f5dfc82d06 --- /dev/null +++ b/python-dict-attribute/record.py @@ -0,0 +1,32 @@ +import logging + +logging.basicConfig(level=logging.INFO, format="%(message)s") + + +class Record: + def __init__(self, **fields): + logging.info(f"[INIT] Creating record with fields: {fields}") + self.__dict__.update(fields) + + def __getattribute__(self, key): + if key == "__dict__": + return super().__getattribute__(key) + value = super().__getattribute__(key) + logging.info(f"[GET] Accessing '{key}' → {value}") + return value + + def __setattr__(self, key, value): + if key in self.__dict__: + logging.info(f"[UPDATE] Modifying '{key}' → {value}") + else: + logging.info(f"[SET] Creating '{key}' → {value}") + self.__dict__[key] = value + + def __delattr__(self, key): + if key in self.__dict__: + logging.info(f"[DEL] Deleting '{key}'") + del self.__dict__[key] + else: + logging.warning( + f"[DEL] Attempted to delete non-existent field '{key}'" + ) diff --git a/python-dict-attribute/track_calls.py b/python-dict-attribute/track_calls.py new file mode 100644 index 0000000000..6dea659fb0 --- /dev/null +++ b/python-dict-attribute/track_calls.py @@ -0,0 +1,10 @@ +def track_calls(): + track_calls.__dict__["calls"] = track_calls.__dict__.get("calls", 0) + 1 + print(f"Calls {track_calls.calls}") + + +track_calls() +track_calls() +track_calls() + +print(track_calls.calls) From a2fdcaa3cc40a02cb149611d6135062e93cb0f90 Mon Sep 17 00:00:00 2001 From: martin-martin Date: Sun, 16 Mar 2025 16:39:54 +0100 Subject: [PATCH 2/5] Add prints and missing code files, apply edits --- python-dict-attribute/config_v3.py | 4 ++-- python-dict-attribute/person_v1.py | 7 +++++++ python-dict-attribute/plain_record.py | 26 ++++++++++++++++++++++++++ python-dict-attribute/salary.py | 14 ++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 python-dict-attribute/plain_record.py create mode 100644 python-dict-attribute/salary.py diff --git a/python-dict-attribute/config_v3.py b/python-dict-attribute/config_v3.py index f09f2db32e..22405b7633 100644 --- a/python-dict-attribute/config_v3.py +++ b/python-dict-attribute/config_v3.py @@ -9,13 +9,13 @@ def get_option(self, key): return getattr(self, key, None) def remove_option(self, key): - if key in self.__dict__: + if hasattr(self, key): delattr(self, key) print(f"'{key}' removed!") else: print(f"'{key}' does not exist.") def clear(self): - for key in self.__dict__: + for key in list(self.__dict__.keys()): delattr(self, key) print("All options removed!") diff --git a/python-dict-attribute/person_v1.py b/python-dict-attribute/person_v1.py index 6f620d7fb2..8c178779ff 100644 --- a/python-dict-attribute/person_v1.py +++ b/python-dict-attribute/person_v1.py @@ -20,3 +20,10 @@ def as_dict(self): def as_tuple(self): return tuple(self.__dict__.values()) + + +person = Person("John", "Doe", 30) +print(repr(person)) +print(person) +print(person.as_dict()) +print(person.as_tuple()) diff --git a/python-dict-attribute/plain_record.py b/python-dict-attribute/plain_record.py new file mode 100644 index 0000000000..e4e7a808c7 --- /dev/null +++ b/python-dict-attribute/plain_record.py @@ -0,0 +1,26 @@ +class Record: + """Hold a record of data.""" + + +john = { + "name": "John Doe", + "position": "Python Developer", + "department": "Engineering", + "salary": 80000, + "hire_date": "2020-01-01", + "is_manager": False, +} + + +def as_dict(self): + return self.__dict__ + + +Record.as_dict = as_dict +print(Record.__dict__) + +john_record = Record() +john_record.__dict__.update(john) +print(john_record.name) +print(john_record.department) +print(john_record.as_dict()) diff --git a/python-dict-attribute/salary.py b/python-dict-attribute/salary.py new file mode 100644 index 0000000000..9b503d423c --- /dev/null +++ b/python-dict-attribute/salary.py @@ -0,0 +1,14 @@ +class Employee: + def __init__(self, name, department, salary): + self.name = name + self.department = department + self.salary = salary + + def give_raise(self, amount): + self.salery = self.salary + amount # Typo here: self.salery + + +john = Employee("John", "Engineering", 70000) +john.give_raise(5000) +print(john.salary) +print(john.__dict__) From 8935e904b63cd5eae9cc315bb9d26161aa831a7b Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Mon, 17 Mar 2025 15:26:55 +0100 Subject: [PATCH 3/5] TR updates, first round --- python-dict-attribute/config_v2.py | 23 +++++++++++----- python-dict-attribute/demo.py | 2 ++ .../{person_v1.py => person.py} | 11 ++++---- python-dict-attribute/person_v2.py | 26 ------------------- python-dict-attribute/plain_record.py | 26 ------------------- 5 files changed, 25 insertions(+), 63 deletions(-) rename python-dict-attribute/{person_v1.py => person.py} (81%) delete mode 100644 python-dict-attribute/person_v2.py delete mode 100644 python-dict-attribute/plain_record.py diff --git a/python-dict-attribute/config_v2.py b/python-dict-attribute/config_v2.py index 22405b7633..954a8368d6 100644 --- a/python-dict-attribute/config_v2.py +++ b/python-dict-attribute/config_v2.py @@ -3,19 +3,30 @@ def __init__(self, name): self.name = name def set_option(self, key, value): - setattr(self, key, value) + self.__dict__[key] = value def get_option(self, key): - return getattr(self, key, None) + return self.__dict__.get(key, None) def remove_option(self, key): - if hasattr(self, key): - delattr(self, key) + if key in self.__dict__: + del self.__dict__[key] + # self.__dict__.pop(key) print(f"'{key}' removed!") else: print(f"'{key}' does not exist.") def clear(self): - for key in list(self.__dict__.keys()): - delattr(self, key) + self.__dict__.clear() print("All options removed!") + + +conf = Config("GUI App") +conf.set_option("theme", "dark") +conf.set_option("size", "200x400") +print(conf.__dict__) +conf.remove_option("size") +print(conf.__dict__) +# conf.remove_option("autosave") # Raises KeyError +conf.clear() +print(conf.__dict__) diff --git a/python-dict-attribute/demo.py b/python-dict-attribute/demo.py index db71bc9113..bf80310209 100644 --- a/python-dict-attribute/demo.py +++ b/python-dict-attribute/demo.py @@ -9,3 +9,5 @@ def method(self): print(DemoClass.__dict__) +demo_object = DemoClass() +print(demo_object.__dict__) diff --git a/python-dict-attribute/person_v1.py b/python-dict-attribute/person.py similarity index 81% rename from python-dict-attribute/person_v1.py rename to python-dict-attribute/person.py index 8c178779ff..53618d3df1 100644 --- a/python-dict-attribute/person_v1.py +++ b/python-dict-attribute/person.py @@ -22,8 +22,9 @@ def as_tuple(self): return tuple(self.__dict__.values()) -person = Person("John", "Doe", 30) -print(repr(person)) -print(person) -print(person.as_dict()) -print(person.as_tuple()) +john = Person("John", "Doe", 30) +print(repr(john)) +Person("John", "Doe", 30) +print(john) +print(john.as_dict()) +print(john.as_tuple()) diff --git a/python-dict-attribute/person_v2.py b/python-dict-attribute/person_v2.py deleted file mode 100644 index c9738e43be..0000000000 --- a/python-dict-attribute/person_v2.py +++ /dev/null @@ -1,26 +0,0 @@ -class Person: - def __init__(self, first_name, last_name, age): - self.first_name = first_name - self.last_name = last_name - self.age = age - - def __str__(self): - return "{first_name} {last_name} is {age} years old".format( - **self.__dict__ - ) - - def __repr__(self): - return "{cls}('{first_name}', '{last_name}', {age})".format( - cls=type(self).__name__, - **self.__dict__, - ) - - def as_dict(self): - return self.__dict__ - - def as_tuple(self): - return tuple(self.__dict__.values()) - - def from_dict(self, data): - self.__dict__.update(data) - return self diff --git a/python-dict-attribute/plain_record.py b/python-dict-attribute/plain_record.py deleted file mode 100644 index e4e7a808c7..0000000000 --- a/python-dict-attribute/plain_record.py +++ /dev/null @@ -1,26 +0,0 @@ -class Record: - """Hold a record of data.""" - - -john = { - "name": "John Doe", - "position": "Python Developer", - "department": "Engineering", - "salary": 80000, - "hire_date": "2020-01-01", - "is_manager": False, -} - - -def as_dict(self): - return self.__dict__ - - -Record.as_dict = as_dict -print(Record.__dict__) - -john_record = Record() -john_record.__dict__.update(john) -print(john_record.name) -print(john_record.department) -print(john_record.as_dict()) From 70c3858d80ea4ecb2d0e47ea303bc450107526b5 Mon Sep 17 00:00:00 2001 From: martin-martin Date: Wed, 19 Mar 2025 13:09:48 +0100 Subject: [PATCH 4/5] Add output, colons, and remove obsolete line --- python-dict-attribute/config_v3.py | 12 ++++++++++++ python-dict-attribute/person.py | 1 - python-dict-attribute/record.py | 16 ++++++++++++++++ python-dict-attribute/track_calls.py | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/python-dict-attribute/config_v3.py b/python-dict-attribute/config_v3.py index 22405b7633..5a86eb8b97 100644 --- a/python-dict-attribute/config_v3.py +++ b/python-dict-attribute/config_v3.py @@ -19,3 +19,15 @@ def clear(self): for key in list(self.__dict__.keys()): delattr(self, key) print("All options removed!") + + +conf = Config("GUI App") +conf.set_option("theme", "dark") +conf.set_option("size", "200x400") +print(conf.__dict__) +conf.remove_option("size") +print(conf.__dict__) +conf.remove_option("autosave") # Raises KeyError +print(conf.__dict__) +conf.clear() +print(conf.__dict__) diff --git a/python-dict-attribute/person.py b/python-dict-attribute/person.py index 53618d3df1..1640edb917 100644 --- a/python-dict-attribute/person.py +++ b/python-dict-attribute/person.py @@ -24,7 +24,6 @@ def as_tuple(self): john = Person("John", "Doe", 30) print(repr(john)) -Person("John", "Doe", 30) print(john) print(john.as_dict()) print(john.as_tuple()) diff --git a/python-dict-attribute/record.py b/python-dict-attribute/record.py index f5dfc82d06..6c770ab5fc 100644 --- a/python-dict-attribute/record.py +++ b/python-dict-attribute/record.py @@ -30,3 +30,19 @@ def __delattr__(self, key): logging.warning( f"[DEL] Attempted to delete non-existent field '{key}'" ) + + +jane = Record(first_name="Jane", last_name="Doe", age=25) + +# Access +print(jane.first_name) +print(jane.age) + +# Mutations +jane.age = 26 +jane.job = "Software Engineer" +print(jane.__dict__) + +# Deletion +del jane.age +print(jane.__dict__) diff --git a/python-dict-attribute/track_calls.py b/python-dict-attribute/track_calls.py index 6dea659fb0..a2e529f894 100644 --- a/python-dict-attribute/track_calls.py +++ b/python-dict-attribute/track_calls.py @@ -1,6 +1,6 @@ def track_calls(): track_calls.__dict__["calls"] = track_calls.__dict__.get("calls", 0) + 1 - print(f"Calls {track_calls.calls}") + print(f"Calls: {track_calls.calls}") track_calls() From c3075b2efe2013c819d2aedbf527d37c088c7e3c Mon Sep 17 00:00:00 2001 From: martin-martin Date: Wed, 19 Mar 2025 13:13:02 +0100 Subject: [PATCH 5/5] Add line, remove incorrect message --- python-dict-attribute/config_v2.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python-dict-attribute/config_v2.py b/python-dict-attribute/config_v2.py index 954a8368d6..aff8422edd 100644 --- a/python-dict-attribute/config_v2.py +++ b/python-dict-attribute/config_v2.py @@ -27,6 +27,7 @@ def clear(self): print(conf.__dict__) conf.remove_option("size") print(conf.__dict__) -# conf.remove_option("autosave") # Raises KeyError +conf.remove_option("autosave") +print(conf.__dict__) conf.clear() print(conf.__dict__)