Домашни > The Old Man from Scene #24 > Решения > Решението на Михаил Георгиев

Резултати
3 точки от тестове
0 точки от учител

3 точки общо

21 успешни теста
4 неуспешни теста
Код

  1import importlib
  2import re
  3
  4
  5class BridgeKeeper:
  6    def __init__(self, module_name):
  7        self.module_name = module_name
  8
  9    def __enter__(self):
 10        module = importlib.import_module(self.module_name)
 11        allowed = {}
 12
 13        for name in dir(module):
 14            obj = getattr(module, name)
 15
 16            if (
 17                self._passes_name(obj)
 18                and self._passes_callable(obj)
 19                and self._passes_third_question(obj)
 20            ):
 21                allowed[name] = obj
 22
 23        return FilteredModule(allowed)
 24
 25    def __exit__(self, exc_type, exc, tb):
 26        return False
 27
 28    def _passes_name(self, obj):
 29        if not hasattr(obj, "__name__"):
 30            return False
 31        name = obj.__name__
 32        return isinstance(name, str) and name[:1].isupper()
 33
 34    def _passes_callable(self, obj):
 35        if not callable(obj):
 36            return False
 37
 38        params = self._parse_docstring(obj)
 39
 40        try:
 41            args = [self._dummy_value(t) for t in params]
 42            obj(*args)
 43            return True
 44        except Exception:
 45            return False
 46
 47    def _parse_docstring(self, obj):
 48        doc = obj.__doc__
 49        if not doc:
 50            return []
 51
 52        match = re.search(r"Parameters\s*-+\s*((?:.+\n)+?)(?:\n\s*\n|$)", doc)
 53        if not match:
 54            return []
 55
 56        params_block = match.group(1)
 57        types = re.findall(r"\w+\s*:\s*([^\n]+)", params_block)
 58
 59        return [t.strip() for t in types]
 60
 61    def _dummy_value(self, type_str):
 62        # union
 63        if "|" in type_str:
 64            type_str = type_str.split("|")[0].strip()
 65
 66        if type_str.startswith("list["):
 67            inner = type_str[5:-1]
 68            return [self._dummy_value(inner)]
 69
 70        if type_str.startswith("tuple["):
 71            inner = type_str[6:-1]
 72            return (self._dummy_value(inner),)
 73
 74        if type_str.startswith("set["):
 75            inner = type_str[4:-1]
 76            return {self._dummy_value(inner)}
 77
 78        if type_str.startswith("dict["):
 79            inner = type_str[5:-1]
 80            k, v = map(str.strip, inner.split(","))
 81            return {self._dummy_value(k): self._dummy_value(v)}
 82
 83        mapping = {
 84            "int": 1,
 85            "float": 1.0,
 86            "str": "a",
 87            "bool": True,
 88        }
 89
 90        return mapping.get(type_str, None)
 91
 92    def _passes_third_question(self, obj):
 93        for attr in dir(obj):
 94            if attr.startswith("__") and attr.endswith("__"):
 95                continue
 96
 97            if self._valid_attribute_name(attr):
 98                return True
 99
100        return False
101
102    def _valid_attribute_name(self, name):
103        if re.search(r"[aeiou]{4,}", name):
104            return False
105
106        letters = [c for c in name if c.isalpha()]
107        if not letters:
108            return False
109
110        return letters[-1].isupper()
111
112
113class FilteredModule:
114    def __init__(self, allowed):
115        self._allowed = allowed
116
117    def __getattr__(self, name):
118        if name in self._allowed:
119            return self._allowed[name]
120        raise AttributeError(f"{name} is not allowed")
121
122    def __dir__(self):
123        return list(self._allowed.keys())

...........EF.........F.F
======================================================================
ERROR: test_ignores_parameter_like_lines_outside_parameters_block (test.TestBridgeKeeper.test_ignores_parameter_like_lines_outside_parameters_block)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 37, in test_ignores_parameter_like_lines_outside_parameters_block
self.assertEqual(filtered.tricky_doc(2, "xo"), "xoxo")
^^^^^^^^^^^^^^^^^^^
File "/tmp/solution.py", line 120, in __getattr__
raise AttributeError(f"{name} is not allowed")
AttributeError: tricky_doc is not allowed

======================================================================
FAIL: test_rejects_callable_when_parameter_names_do_not_match (test.TestBridgeKeeper.test_rejects_callable_when_parameter_names_do_not_match)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 59, in test_rejects_callable_when_parameter_names_do_not_match
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised

======================================================================
FAIL: test_rejects_third_answer_with_four_consecutive_uppercase_vowels (test.TestBridgeKeeper.test_rejects_third_answer_with_four_consecutive_uppercase_vowels)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 121, in test_rejects_third_answer_with_four_consecutive_uppercase_vowels
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised

======================================================================
FAIL: test_rejects_union_when_callable_does_not_support_all_union_branches (test.TestBridgeKeeper.test_rejects_union_when_callable_does_not_support_all_union_branches)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 72, in test_rejects_union_when_callable_does_not_support_all_union_branches
with self.assertRaises(AttributeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
AssertionError: AttributeError not raised

----------------------------------------------------------------------
Ran 25 tests in 0.008s

FAILED (failures=3, errors=1)

Дискусия
Виктор Бечев
28.04.2026 17:07

Отвъд забележките - хубава атомарност на функциите.
История

f1import importlibf1import importlib
n2import inspectn
3import re2import re
43
54
6class BridgeKeeper:5class BridgeKeeper:
7    def __init__(self, module_name):6    def __init__(self, module_name):
8        self.module_name = module_name7        self.module_name = module_name
98
10    def __enter__(self):9    def __enter__(self):
n11        self.module = importlib.import_module(self.module_name)n10        module = importlib.import_module(self.module_name)
12        self.allowed = {}11        allowed = {}
1312
n14        for name in dir(self.module):n13        for name in dir(module):
15            obj = getattr(self.module, name)14            obj = getattr(module, name)
1615
nn16            if (
17            if self._passes_name(obj) and \17                self._passes_name(obj)
18               self._passes_callable(obj) and \18                and self._passes_callable(obj)
19               self._passes_third_question(obj):19                and self._passes_third_question(obj)
20            ):
21                allowed[name] = obj
2022
n21                self.allowed[name] = objn
22 
23        return FilteredModule(self.allowed)23        return FilteredModule(allowed)
2424
25    def __exit__(self, exc_type, exc, tb):25    def __exit__(self, exc_type, exc, tb):
26        return False26        return False
n27 n
2827
29    def _passes_name(self, obj):28    def _passes_name(self, obj):
30        if not hasattr(obj, "__name__"):29        if not hasattr(obj, "__name__"):
31            return False30            return False
32        name = obj.__name__31        name = obj.__name__
33        return isinstance(name, str) and name[:1].isupper()32        return isinstance(name, str) and name[:1].isupper()
n34 n
3533
36    def _passes_callable(self, obj):34    def _passes_callable(self, obj):
37        if not callable(obj):35        if not callable(obj):
38            return False36            return False
3937
40        params = self._parse_docstring(obj)38        params = self._parse_docstring(obj)
4139
42        try:40        try:
43            args = [self._dummy_value(t) for t in params]41            args = [self._dummy_value(t) for t in params]
44            obj(*args)42            obj(*args)
45            return True43            return True
46        except Exception:44        except Exception:
47            return False45            return False
4846
49    def _parse_docstring(self, obj):47    def _parse_docstring(self, obj):
50        doc = obj.__doc__48        doc = obj.__doc__
51        if not doc:49        if not doc:
52            return []50            return []
5351
54        match = re.search(r"Parameters\s*-+\s*((?:.+\n)+?)(?:\n\s*\n|$)", doc)52        match = re.search(r"Parameters\s*-+\s*((?:.+\n)+?)(?:\n\s*\n|$)", doc)
55        if not match:53        if not match:
56            return []54            return []
5755
58        params_block = match.group(1)56        params_block = match.group(1)
n59 n
60        types = re.findall(r"\w+\s*:\s*([^\n]+)", params_block)57        types = re.findall(r"\w+\s*:\s*([^\n]+)", params_block)
6158
62        return [t.strip() for t in types]59        return [t.strip() for t in types]
n63 n
6460
65    def _dummy_value(self, type_str):61    def _dummy_value(self, type_str):
66        # union62        # union
67        if "|" in type_str:63        if "|" in type_str:
68            type_str = type_str.split("|")[0].strip()64            type_str = type_str.split("|")[0].strip()
6965
70        if type_str.startswith("list["):66        if type_str.startswith("list["):
71            inner = type_str[5:-1]67            inner = type_str[5:-1]
72            return [self._dummy_value(inner)]68            return [self._dummy_value(inner)]
7369
74        if type_str.startswith("tuple["):70        if type_str.startswith("tuple["):
75            inner = type_str[6:-1]71            inner = type_str[6:-1]
76            return (self._dummy_value(inner),)72            return (self._dummy_value(inner),)
7773
78        if type_str.startswith("set["):74        if type_str.startswith("set["):
79            inner = type_str[4:-1]75            inner = type_str[4:-1]
80            return {self._dummy_value(inner)}76            return {self._dummy_value(inner)}
8177
82        if type_str.startswith("dict["):78        if type_str.startswith("dict["):
83            inner = type_str[5:-1]79            inner = type_str[5:-1]
84            k, v = map(str.strip, inner.split(","))80            k, v = map(str.strip, inner.split(","))
85            return {self._dummy_value(k): self._dummy_value(v)}81            return {self._dummy_value(k): self._dummy_value(v)}
8682
87        mapping = {83        mapping = {
88            "int": 1,84            "int": 1,
89            "float": 1.0,85            "float": 1.0,
90            "str": "a",86            "str": "a",
91            "bool": True,87            "bool": True,
92        }88        }
9389
94        return mapping.get(type_str, None)90        return mapping.get(type_str, None)
9591
n96 n
97    def _passes_third_question(self, obj):92    def _passes_third_question(self, obj):
98        for attr in dir(obj):93        for attr in dir(obj):
99            if attr.startswith("__") and attr.endswith("__"):94            if attr.startswith("__") and attr.endswith("__"):
100                continue95                continue
10196
102            if self._valid_attribute_name(attr):97            if self._valid_attribute_name(attr):
103                return True98                return True
10499
105        return False100        return False
t106 t
107101
108    def _valid_attribute_name(self, name):102    def _valid_attribute_name(self, name):
109        if re.search(r"[aeiou]{4,}", name):103        if re.search(r"[aeiou]{4,}", name):
110            return False104            return False
111105
112        letters = [c for c in name if c.isalpha()]106        letters = [c for c in name if c.isalpha()]
113        if not letters:107        if not letters:
114            return False108            return False
115109
116        return letters[-1].isupper()110        return letters[-1].isupper()
117111
118112
119class FilteredModule:113class FilteredModule:
120    def __init__(self, allowed):114    def __init__(self, allowed):
121        self._allowed = allowed115        self._allowed = allowed
122116
123    def __getattr__(self, name):117    def __getattr__(self, name):
124        if name in self._allowed:118        if name in self._allowed:
125            return self._allowed[name]119            return self._allowed[name]
126        raise AttributeError(f"{name} is not allowed")120        raise AttributeError(f"{name} is not allowed")
127121
128    def __dir__(self):122    def __dir__(self):
129        return list(self._allowed.keys())123        return list(self._allowed.keys())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import importlibf1import importlib
2import inspect2import inspect
3import re3import re
44
55
6class BridgeKeeper:6class BridgeKeeper:
7    def __init__(self, module_name):7    def __init__(self, module_name):
8        self.module_name = module_name8        self.module_name = module_name
99
10    def __enter__(self):10    def __enter__(self):
11        self.module = importlib.import_module(self.module_name)11        self.module = importlib.import_module(self.module_name)
12        self.allowed = {}12        self.allowed = {}
1313
14        for name in dir(self.module):14        for name in dir(self.module):
15            obj = getattr(self.module, name)15            obj = getattr(self.module, name)
1616
17            if self._passes_name(obj) and \17            if self._passes_name(obj) and \
18               self._passes_callable(obj) and \18               self._passes_callable(obj) and \
19               self._passes_third_question(obj):19               self._passes_third_question(obj):
2020
21                self.allowed[name] = obj21                self.allowed[name] = obj
2222
23        return FilteredModule(self.allowed)23        return FilteredModule(self.allowed)
2424
25    def __exit__(self, exc_type, exc, tb):25    def __exit__(self, exc_type, exc, tb):
26        return False26        return False
2727
2828
29    def _passes_name(self, obj):29    def _passes_name(self, obj):
30        if not hasattr(obj, "__name__"):30        if not hasattr(obj, "__name__"):
31            return False31            return False
32        name = obj.__name__32        name = obj.__name__
33        return isinstance(name, str) and name[:1].isupper()33        return isinstance(name, str) and name[:1].isupper()
3434
3535
36    def _passes_callable(self, obj):36    def _passes_callable(self, obj):
37        if not callable(obj):37        if not callable(obj):
38            return False38            return False
3939
40        params = self._parse_docstring(obj)40        params = self._parse_docstring(obj)
4141
42        try:42        try:
43            args = [self._dummy_value(t) for t in params]43            args = [self._dummy_value(t) for t in params]
44            obj(*args)44            obj(*args)
45            return True45            return True
46        except Exception:46        except Exception:
47            return False47            return False
4848
n49 n
50    def _parse_docstring(self, obj):49    def _parse_docstring(self, obj):
51        doc = obj.__doc__50        doc = obj.__doc__
52        if not doc:51        if not doc:
53            return []52            return []
5453
n55        lines = doc.splitlines()n54        match = re.search(r"Parameters\s*-+\s*((?:.+\n)+?)(?:\n\s*\n|$)", doc)
56 55        if not match:
57        start = None
58        for i, line in enumerate(lines):
59            if line.strip() == "Parameters":
60                start = i
61                break
62 
63        if start is None:
64            return []56            return []
6557
n66        i = start + 2n58        params_block = match.group(1)
6759
n68        params = []n60        types = re.findall(r"\w+\s*:\s*([^\n]+)", params_block)
6961
t70        while i < len(lines):t62        return [t.strip() for t in types]
71            line = lines[i].strip()
72            if not line:
73                break
74 
75            match = re.match(r"(\w+)\s*:\s*(.+)", line)
76            if match:
77                _, type_str = match.groups()
78                params.append(type_str.strip())
79 
80            i += 1
81 
82        return params
8363
8464
85    def _dummy_value(self, type_str):65    def _dummy_value(self, type_str):
86        # union66        # union
87        if "|" in type_str:67        if "|" in type_str:
88            type_str = type_str.split("|")[0].strip()68            type_str = type_str.split("|")[0].strip()
8969
90        if type_str.startswith("list["):70        if type_str.startswith("list["):
91            inner = type_str[5:-1]71            inner = type_str[5:-1]
92            return [self._dummy_value(inner)]72            return [self._dummy_value(inner)]
9373
94        if type_str.startswith("tuple["):74        if type_str.startswith("tuple["):
95            inner = type_str[6:-1]75            inner = type_str[6:-1]
96            return (self._dummy_value(inner),)76            return (self._dummy_value(inner),)
9777
98        if type_str.startswith("set["):78        if type_str.startswith("set["):
99            inner = type_str[4:-1]79            inner = type_str[4:-1]
100            return {self._dummy_value(inner)}80            return {self._dummy_value(inner)}
10181
102        if type_str.startswith("dict["):82        if type_str.startswith("dict["):
103            inner = type_str[5:-1]83            inner = type_str[5:-1]
104            k, v = map(str.strip, inner.split(","))84            k, v = map(str.strip, inner.split(","))
105            return {self._dummy_value(k): self._dummy_value(v)}85            return {self._dummy_value(k): self._dummy_value(v)}
10686
107        mapping = {87        mapping = {
108            "int": 1,88            "int": 1,
109            "float": 1.0,89            "float": 1.0,
110            "str": "a",90            "str": "a",
111            "bool": True,91            "bool": True,
112        }92        }
11393
114        return mapping.get(type_str, None)94        return mapping.get(type_str, None)
11595
11696
117    def _passes_third_question(self, obj):97    def _passes_third_question(self, obj):
118        for attr in dir(obj):98        for attr in dir(obj):
119            if attr.startswith("__") and attr.endswith("__"):99            if attr.startswith("__") and attr.endswith("__"):
120                continue100                continue
121101
122            if self._valid_attribute_name(attr):102            if self._valid_attribute_name(attr):
123                return True103                return True
124104
125        return False105        return False
126106
127107
128    def _valid_attribute_name(self, name):108    def _valid_attribute_name(self, name):
129        if re.search(r"[aeiou]{4,}", name):109        if re.search(r"[aeiou]{4,}", name):
130            return False110            return False
131111
132        letters = [c for c in name if c.isalpha()]112        letters = [c for c in name if c.isalpha()]
133        if not letters:113        if not letters:
134            return False114            return False
135115
136        return letters[-1].isupper()116        return letters[-1].isupper()
137117
138118
139class FilteredModule:119class FilteredModule:
140    def __init__(self, allowed):120    def __init__(self, allowed):
141        self._allowed = allowed121        self._allowed = allowed
142122
143    def __getattr__(self, name):123    def __getattr__(self, name):
144        if name in self._allowed:124        if name in self._allowed:
145            return self._allowed[name]125            return self._allowed[name]
146        raise AttributeError(f"{name} is not allowed")126        raise AttributeError(f"{name} is not allowed")
147127
148    def __dir__(self):128    def __dir__(self):
149        return list(self._allowed.keys())129        return list(self._allowed.keys())
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op