8000 Merge branch 'master' into python-uv · realpython/materials@0e848fd · GitHub
[go: up one dir, main page]

Skip 8000 to content

Commit 0e848fd

Browse files
authored
Merge branch 'master' into python-uv
2 parents 99ea39d + 2a88b23 commit 0e848fd

File tree

11 files changed

+360
-1
lines changed

11 files changed

+360
-1
lines changed

python-copy/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Shallow vs Deep Copying of Python Objects
2+
3+
This folder contains code associated with the Real Python tutorial [Shallow vs Deep Copying of Python Objects](https://realpython.com/python-copy/).

python-copy/benchmark.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from copy import copy
2+
from random import choices, randint
3+
from string import ascii_letters
4+
from timeit import timeit
5+
6+
EXECUTIONS = 10_000
7+
SIZE = 1_000
8+
9+
10+
def main():
11+
print(f" Size={SIZE:,} ({EXECUTIONS:,}x) ".center(50, "="))
12+
for container_type in random_dict, random_set, random_list:
13+
container = container_type(size=SIZE)
14+
for method, seconds in benchmark(container, EXECUTIONS).items():
15+
print(f"{seconds:.5f} {method}")
16+
print()
17+
18+
19+
def benchmark(container, executions):
20+
type_ = type(container)
21+
name = type_.__name__
22+
results = {
23+
f"{name}.copy()": timeit(container.copy, number=executions),
24+
f"{name}()": timeit(lambda: type_(container), number=executions),
25+
f"copy({name})": timeit(lambda: copy(container), number=executions),
26+
}
27+
if sliceable(container):
28+
results[f"{name}[:]"] = timeit(lambda: container[:], number=executions)
29+
return results
30+
31+
32+
def sliceable(instance):
33+
try:
34+
instance[0:1]
35+
except (TypeError, KeyError):
36+
return False
37+
else:
38+
return True
39+
40+
41+
def random_dict(size):
42+
keys = random_set(size)
43+
values = random_set(size)
44+
return dict(zip(keys, values))
45+
46+
47+
def random_set(size):
48+
return set(random_list(size))
49+
50+
51+
def random_list(size, shortest=3, longest=15):
52+
return [
53+
"".join(choices(ascii_letters, k=randint(shortest, longest)))
54+
for _ in range(size)
55+
]
56+
57+
58+
if __name__ == "__main__":
59+
main()

python-copy/console.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import copy
2+
from datetime import UTC, datetime
3+
from pprint import pp
4+
5+
6+
class ConsoleWindow:
7+
def __init__(self, tabs):
8+
self.tabs = tabs
9+
self.history = []
10+
self.created_at = datetime.now(UTC)
11+
12+
def __copy__(self):
13+
instance = type(self)(self.tabs)
14+
instance.history = self.history
15+
self.tabs.add(instance)
16+
return instance
17+
18+
def __deepcopy__(self, memo):
19+
instance = type(self)(self.tabs)
20+
instance.history = copy.deepcopy(self.history, memo)
21+
self.tabs.add(instance)
22+
return instance
23+
24+
def run_command(self, command):
25+
self.history.append(command)
26+
27+
28+
if __name__ == "__main__":
29+
shared_registry = set()
30+
window = ConsoleWindow(shared_registry)
31+
window.run_command("cd ~/Projects")
32+
tab1 = copy.deepcopy(window)
33+
tab1.run_command("git clone git@github.com:python/cpython.git")
34+
tab2 = copy.copy(tab1)
35+
tab2.run_command("cd python/")
36+
window.run_command("ls -l")
37+
tab1.run_command("git checkout 3.13")
38+
pp(vars(window))
39+
print()
40+
pp(vars(tab1))
41+
print()
42+
pp(vars(tab2))

python-copy/datafile.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import copy
2+
import json
3+
from pprint import pp
4+
5+
6+
class DataFile:
7+
def __init__(self, path):
8+
self.file = open(path, mode="r", encoding="utf-8")
9+
10+
def __copy__(self):
11+
return type(self)(self.file.name)
12+
13+
def __deepcopy__(self, memo):
14+
return self.__copy__()
15+
16+
def __enter__(self):
17+
return self
18+
19+
def __exit__(self, exc_type, exc_val, exc_tb):
20+
self.file.close()
21+
22+
def read_json(self):
23+
self.file.seek(0)
24+
return json.load(self.file)
25+
26+
27+
if __name__ == "__main__":
28+
with DataFile("person.json") as data_file:
29+
shallow_copy = copy.copy(data_file)
30+
deep_copy = copy.deepcopy(data_file)
31+
pp(data_file.read_json())
32+
print()
33+
pp(shallow_copy.read_json())
34+
shallow_copy.file.close()
35+
print()
36+
pp(deep_copy.read_json())
37+
deep_copy.file.close()

python-copy/emoji.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import unicodedata
2+
3+
4+
class Emoji:
5+
def __init__(self, name):
6+
self.name = name
7+
8+
def __repr__(self):
9+
return self._glyph
10+
11+
@property
12+
def name(self):
13+
return unicodedata.name(self._glyph).title()
14+
15+
@name.setter
16+
def name(self, value):
17+
self._glyph = unicodedata.lookup(value)
18+
19+
20+
if __name__ == "__main__":
21+
emoji = Emoji("tangerine")
22+
print(emoji)
23+
emoji.name = "clown face"
24+
print(emoji)

python-copy/golden_ratio.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from fractions import Fraction
2+
3+
cf = [1]
4+
cf.append(cf)
5+
6+
7+
def evaluate(depth):
8+
if depth > 0:
9+
return cf[0] + Fraction(1, evaluate(depth - 1))
10+
return cf[0]
11+
12+
13+
golden_ratio = (1 + 5**0.5) / 2
14+
for n in range(21):
15+
fraction = evaluate(n)
16+
approximation = float(fraction)
17+
error = abs(golden_ratio - approximation)
18+
print(
19+
f"n={n:<3}",
20+
f"{fraction:>11}",
21+
"\N{ALMOST EQUAL TO}",
22+
f"{approximation:.15f}",
23+
f"(\N{GREEK CAPITAL LETTER DELTA} = {error:<11.10f})",
24+
)

python-copy/mutable_int.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class MutableInt:
2+
def __init__(self, value):
3+
self.value = value
4+
5+
def __iadd__(self, other):
6+
self.value += other
7+
return self
8+
9+
def __str__(self):
10+
return str(self.value)
11+
12+
13+
if __name__ == "__main__":
14+
x = MutableInt(40)
15+
x += 2
16+
print(x)

python-copy/person.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "John Doe",
3+
"age": 42,
4+
"email": "johndoe@example.com",
5+
"is_subscribed": true,
6+
"hobbies": ["reading", "cycling", "traveling"],
7+
"address": {
8+
"street": "123 Main St",
9+
"city": "New York",
10+
"zip": "10001"
11+
}
12+
}
13+

python-copy/person.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import copy
2+
from datetime import date
3+
4+
5+
def in_place():
6+
class Person:
7+
def __init__(self, name, age):
8+
self.name = name
9+
self.age = age
10+
11+
def __replace__(self, **changes):
12+
if unknown := changes.keys() - self.__dict__.keys():
13+
raise AttributeError(", ".join(unknown))
14+
self.__dict__.update(**changes)
15+
16+
person = Person("John Doe", 42)
17+
copy.replace(person, age=24, name="Alice Smith")
18+
print(vars(person))
19+
# This raises an error:
20+
# print(copy.replace(person, name="Bob Brown", email="bob.brown@example.com"))
21+
22+
23+
def shallow():
24+
class Person:
25+
def __init__(self, name, age):
26+
self.name = name
27+
self.age = age
28+
29+
def __replace__(self, **changes):
30+
return type(self)(**self.__dict__ | changes)
31+
32+
person = Person("John Doe", 42)
33+
person_copy = copy.replace(person, age=24, name="Alice Smith")
34+
print(vars(person))
35+
print(vars(person_copy))
36+
# This raises an error:
37+
# print(copy.replace(person, email="bob.brown@example.com"))
38+
39+
40+
def slots():
41+
class Person:
42+
__slots__ = ("name", "age")
43+
44+
def __init__(self, name, age):
45+
self.name = name
46+
self.age = age
47+
48+
def __replace__(self, **changes):
49+
instance = type(self)(self.name, self.age)
50+
for name, value in changes.items():
51+
if hasattr(self, name):
52+
setattr(instance, name, value)
53+
return instance
54+
55+
person = Person("John Doe", 42)
56+
person_copy = copy.replace(person, age=24, name="Alice Smith")
57+
58+
def vars_slots(obj):
59+
return {name: getattr(obj, name) for name in obj.__slots__}
60+
61+
print(vars_slots(person))
62+
print(vars_slots(person_copy))
63+
64+
65+
def derived():
66+
class Person:
67+
def __init__(self, name, date_of_birth):
68+
self.name = name
69+
self.date_of_birth = date_of_birth
70+
71+
@property
72+
def age(self):
73+
return (date.today() - self.date_of_birth).days // 365
74+
75+
def __replace__(self, **changes):
76+
age = changes.pop("age", None)
77+
dob = changes.pop("date_of_birth", None)
78+
79+
instance = copy.copy(self)
80+
for name, value in changes.items():
81+
if hasattr(self, name):
82+
setattr(instance, name, value)
83+
84+
if age and dob:
85+
raise AttributeError(
86+
"can't set both 'age' and 'date_of_birth'"
87+
)
88+
elif age:
89+
dob = copy.replace(date.today(), year=date.today().year - age)
90+
instance.date_of_birth = dob
91+
elif dob:
92+
instance.date_of_birth = dob
93+
94+
return instance
95+
96+
person = Person("John Doe", date(1983, 3, 14))
97+
print(vars(copy.replace(person, age=24)))
98+
print(vars(copy.replace(person, date_of_birth=date(1999, 6, 15))))
99+
# This raises an error:
100+
# print(vars(copy.replace(person, date_of_birth=date(1999, 6, 15), age=12)))
101+
102+
103+
if __name__ == "__main__":
104+
in_place()
105+
shallow()
106+
slots()
107+
derived()

python-copy/rectangle.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import copy
2+
3+
4+
class Rectangle:
5+
def __init__(self, top_left, bottom_right):
6+
self.top_left = top_left
7+
self.bottom_right = bottom_right
8+
9+
def __repr__(self):
10+
return f"Rectangle({self.top_left}, {self.bottom_right})"
11+
12+
13+
class Point:
14+
def __init__(self, x, y):
15+
self.x = x
16+
self.y = y
17+
18+
def __repr__(self):
19+
return f"Point(x={self.x}, y={self.y})"
20+
21+
22+
if __name__ == "__main__":
23+
bounding_box = Rectangle(
24+
top_left := Point(10, 20), bottom_right := Point(30, 40)
25+
)
26+
shallow_copy = copy.copy(bounding_box)
27+
deep_copy = copy.deepcopy(bounding_box)
28+
29+
bounding_box.bottom_right = Point(500, 700)
30+
bottom_right.x += 100
31+
32+
print(f"{bounding_box = }")
33+
print(f"{shallow_copy = }")
34+
print(f"{deep_copy = }")

0 commit comments

Comments
 (0)
0