Домашни > The Old Man from Scene #24


The Old Man from Scene #24
Краен срок: 29.04.2026 18:00
Точки: 4

Може би сте гледали филмът за Светият Граал, а може би не. При все, вдъхновен от [този](https://www.youtube.com/watch?v=0D7hFHfLEyk) скеч, анонимен преподавател реши да ни помогне с предизвикателствата и ни даде идеята за това, което ще последва. В процеса обаче, стана ясно, че това си е половин домашно, затова получавате половин домашно. Честито! :grin: ![Ако някой иска да благодари на главният идеолог на домашното - след 2 седмици](/media/resources/homework-4.png) ### `BridgeKeeper` Напишете контекстен мениджър `BridgeKeeper`, който приема име на модул и връща обект, даващ достъп само до обектите на този модул, които отговарят на определени правила. #### Правилата на пазителя на моста За да са достъпни обектите на модула, те трябва да изпълняват правилата на пазителя на моста, т.е. могат да отговорят на трите му въпроса. Трите "въпроса", които пазачът трябва да зададе на обектите, за да провери дали да ги направи достъпни, са следните: ##### What is your name? Обектът трябва да има атрибут `__name__` и името задължително трябва да започва с главна буква. ##### What is your quest? Обектът да е callable и при извикването му, той приема и работи с аргументите, които са описани в docstring-а на обекта. Аргументите биха присъствали в docstring-а на обекта под следната форма: ``` def some_function(x, names, additional): """Does something. Some other info. Parameters ---------- x : int names : list[str] additional : list[int] | tuple[int] Some other info. """ ``` "Блокът" от docstring-а, който дефинира параметрите се определя от: ``` Parameters ---------- ``` За начало и два нови реда или край на стринга за край. Всичко извън този блок, дори да отговаря на синтаксиса, който следва - **не е част от аргументите / параметрите на функцията**. Типовете на аргументите се дефинират както при анотациите, като ще се ограничим до прости конструкции: - `name : базов_тип` (`int`, `float`, `str`, `bool`, etc.) - `name : колекция[базов_тип]` (`list`, `tuple` или `set`) - колекция с неограничен брой елементи от тип `базов_тип` - `name : dict[базов_тип_1, базов_тип_2]` - речник с ключове `базов_тип_1` и стойности `базов_тип_2` - `name : тип_1 | тип_2` - може да бъде `тип_1` или `тип_2` Какво ще рече **"приема и работи с аргументите"** - ще рече, че callable обектът **не хвърля грешка**, когато е извикан с аргументи, съответстващи по брой, ред и тип на описаните в docstring-а параметри. За целите на задачата ни интересува **само това**. Не е необходимо да проверявате дали callable обектът ще хвърли грешка при извикване с други аргументи. Например, ако имаме: ``` x : int names : list[str] additional : list[int] | tuple[int] ``` То извиквания като `some_function(1, ['a', 'b'], (1, 2, 3))` и `some_function(1, ['a', 'b'], [1, 2, 3])` са валидни и callable обектът не трябва да хвърля грешка при нито едно от тях. Ако callable обектът е дефиниран така: ``` def some_function(x, names): ... ``` Но в docstring-а му са описани параметри `x`, `names` и `additional`, тогава при опит да бъде извикан с аргументите, описани в docstring-а, той **ще** хвърли грешка. В такъв случай приемаме, че callable обектът не отговаря на този въпрос и не трябва да бъде достъпен през `BridgeKeeper`. ###### Уговорки - При липсващ `Parameters` блок или docstring изцяло се приема, че функцията се вика без параметри. - Редът и имената на параметрите са от значение. - Няма да дефинираме ситуация, където има default стойности на дадени параметри. - Няма да дефинираме ситуации с `*args` и `**kwargs`. - Няма да даваме невалидни стойности за типовете, например речник с ключове списъци, което е невъзможно. ##### What is ...? Това е случаен въпрос, чийто отговор се крие **някъде** из атрибутите на обекта. Атрибутът трябва да отговаря на следните правила: - Може да е публичен, protected или private, но **не може** да е dunder. - Не може да съдържа повече от 3 последователни гласни (на латиница) - `a, e, i, o, u`. `y` е специален случай, така че за простота няма да я броим за гласна. - Последната **буква** _(буква от латинската азбука, а не символ, т.е. може след това да има други символи)_ задължително е главна. Примери: ``` # Валидни obj.baaB obj._BABA obj.BB7AA_123X2 # Невалидни obj.bioeabA obj.__BABA__ obj.baba ``` Ако обектът има такъв атрибут, приемаме, че той е отговорът на третия въпрос, ако няма атрибут, който да отговаря на условията - не. И сега да се върнем в началото, какво означава **"обект, даващ достъп само до обектите на този модул"**? Просто - ако в тялото на контекстния мениджър имаме опит за достъп до атрибут, който отговаря на условията - той връща стойност, ако имаме опит за достъп до атрибут, който не отговаря на тях - `AttributeError`. Например: ``` # imported_module.py def baba(x, y): """The baba function. Something. Parameters ---------- x : int y : str """ return y * x baba.__name__ = "Baba" # Защото не искаме да правим прекалено грозни неща спрямо PEP8 baba._capital_of_AssyriA = 'Assur' def dyado(): """The dyado function.""" return 5 def strinka(x): """The strinka function. Parameters ---------- x : int y : str """ return x strinka.__name__ = "Strinka" strinka.secretPassagE = "bridge" ``` ``` with BridgeKeeper("imported_module") as filtered_module: filtered_module.baba(5, 'banichka') filtered_module.dyado # Хвърля се AttributeError, защото dyado не отговаря на въпроси 1 и 3 filtered_module.strinka # Хвърля се AttributeError, защото strinka не може да бъде извикана успешно с аргументите, описани в docstring-а ```
 1import unittest
 2
 3import solution
 4
 5
 6class TestSanity(unittest.TestCase):
 7    """Check if all data is present."""
 8
 9    def test_lyutenitsa_data(self):
10        names = [
11            "BridgeKeeper",
12        ]
13        unimported = [name for name in names if name not in dir(solution)]
14        self.assertEqual(
15            unimported, [], "\n\nЕлементите по-горе липсват (проверете си имената)!"
16        )
17
18
19if __name__ == "__main__":
20    unittest.main()
  1import unittest
  2
  3from solution import BridgeKeeper
  4
  5
  6class TestBridgeKeeper(unittest.TestCase):
  7    def test_allows_valid_function(self):
  8        with BridgeKeeper("bridgekeeper_cases") as filtered:
  9            self.assertEqual(filtered.multiply_text(3, "na"), "nanana")
 10
 11    def test_allows_zero_arg_callable_when_parameters_block_is_missing(self):
 12        with BridgeKeeper("bridgekeeper_cases") as filtered:
 13            self.assertEqual(filtered.silent_oracle(), 42)
 14
 15    def test_allows_union_typed_parameter(self):
 16        with BridgeKeeper("bridgekeeper_cases") as filtered:
 17            self.assertEqual(filtered.sum_group([1, 2, 3]), 6)
 18            self.assertEqual(filtered.sum_group((4, 5)), 9)
 19
 20    def test_rejects_object_with_lowercase_name(self):
 21        with BridgeKeeper("bridgekeeper_cases") as filtered:
 22            with self.assertRaises(AttributeError):
 23                filtered.shadow_name
 24
 25    def test_rejects_non_callable_object(self):
 26        with BridgeKeeper("bridgekeeper_cases") as filtered:
 27            with self.assertRaises(AttributeError):
 28                filtered.artifact
 29
 30    def test_rejects_callable_without_valid_third_answer(self):
 31        with BridgeKeeper("bridgekeeper_cases") as filtered:
 32            with self.assertRaises(AttributeError):
 33                filtered.almost_there
 34
 35    def test_ignores_parameter_like_lines_outside_parameters_block(self):
 36        with BridgeKeeper("bridgekeeper_cases") as filtered:
 37            self.assertEqual(filtered.tricky_doc(2, "xo"), "xoxo")
 38
 39    def test_rejects_docstring_with_parameter_order_mismatch(self):
 40        with BridgeKeeper("bridgekeeper_cases") as filtered:
 41            with self.assertRaises(AttributeError):
 42                filtered.wrong_order
 43
 44    def test_rejects_docstring_with_arity_mismatch(self):
 45        with BridgeKeeper("bridgekeeper_cases") as filtered:
 46            with self.assertRaises(AttributeError):
 47                filtered.wrong_arity
 48
 49    def test_allows_zero_arg_callable_with_missing_docstring(self):
 50        with BridgeKeeper("bridgekeeper_cases") as filtered:
 51            self.assertEqual(filtered.ghost_zero(), "boo")
 52
 53    def test_allows_attribute_whose_last_letter_is_uppercase_but_name_continues(self):
 54        with BridgeKeeper("bridgekeeper_cases") as filtered:
 55            self.assertEqual(filtered.trailing_marker(7), 70)
 56
 57    def test_rejects_callable_when_parameter_names_do_not_match(self):
 58        with BridgeKeeper("bridgekeeper_cases") as filtered:
 59            with self.assertRaises(AttributeError):
 60                filtered.renamed_params
 61
 62    def test_allows_third_answer_name_with_trailing_double_underscores_if_not_dunder(self):
 63        with BridgeKeeper("bridgekeeper_cases") as filtered:
 64            self.assertEqual(filtered.trailing_underscores(4), 5)
 65
 66    def test_allows_dict_typed_parameter(self):
 67        with BridgeKeeper("bridgekeeper_cases") as filtered:
 68            self.assertEqual(filtered.score_map({"alice": 3, "bob": 7}), 10)
 69
 70    def test_rejects_union_when_callable_does_not_support_all_union_branches(self):
 71        with BridgeKeeper("bridgekeeper_cases") as filtered:
 72            with self.assertRaises(AttributeError):
 73                filtered.union_requires_both
 74
 75    def test_allows_set_typed_parameter(self):
 76        with BridgeKeeper("bridgekeeper_cases") as filtered:
 77            self.assertEqual(filtered.gather_tags({"bridge", "keeper"}), "bridge,keeper")
 78
 79    def test_rejects_missing_parameters_block_when_callable_requires_arguments(self):
 80        with BridgeKeeper("bridgekeeper_cases") as filtered:
 81            with self.assertRaises(AttributeError):
 82                filtered.no_params_but_requires_one
 83
 84    def test_allows_callable_instance(self):
 85        with BridgeKeeper("bridgekeeper_cases") as filtered:
 86            self.assertEqual(filtered.callable_box([1, 2, 3]), 6)
 87
 88    def test_rejects_dict_when_callable_does_not_work_with_documented_key_and_value_types(self):
 89        with BridgeKeeper("bridgekeeper_cases") as filtered:
 90            with self.assertRaises(AttributeError):
 91                filtered.wrong_dict_shape
 92
 93    def test_allows_multi_argument_union_bonus_case(self):
 94        with BridgeKeeper("bridgekeeper_cases") as filtered:
 95            self.assertEqual(filtered.triple_union_pair(3, [1, 2]), 6)
 96            self.assertEqual(filtered.triple_union_pair(3, (1, 2)), 6)
 97            self.assertEqual(filtered.triple_union_pair("abc", [1, 2]), 6)
 98            self.assertEqual(filtered.triple_union_pair("abc", (1, 2)), 6)
 99
100    def test_rejects_list_when_callable_does_not_work_with_documented_element_type(self):
101        with BridgeKeeper("bridgekeeper_cases") as filtered:
102            with self.assertRaises(AttributeError):
103                filtered.list_inner_type_mismatch
104
105    def test_rejects_tuple_when_callable_does_not_work_with_documented_element_type(self):
106        with BridgeKeeper("bridgekeeper_cases") as filtered:
107            with self.assertRaises(AttributeError):
108                filtered.tuple_inner_type_mismatch
109
110    def test_rejects_set_when_callable_does_not_work_with_documented_element_type(self):
111        with BridgeKeeper("bridgekeeper_cases") as filtered:
112            with self.assertRaises(AttributeError):
113                filtered.set_inner_type_mismatch
114
115    def test_allows_third_answer_with_single_uppercase_letter_name(self):
116        with BridgeKeeper("bridgekeeper_cases") as filtered:
117            self.assertEqual(filtered.single_letter_answer(5), 15)
118
119    def test_rejects_third_answer_with_four_consecutive_uppercase_vowels(self):
120        with BridgeKeeper("bridgekeeper_cases") as filtered:
121            with self.assertRaises(AttributeError):
122                filtered.uppercase_vowels_answer
123
124
125if __name__ == "__main__":
126    unittest.main()
Дискусия
Милица Лазарова
29.04.2026 04:52

Какво правим ако модулът не съществува? ModuleNotFoundError, AttributeError?
Нишка
Виктор Бечев
29.04.2026 10:20

Не сме го предвидили като опция, не е нужно да се застраховате.
Милан Георгиев
22.04.2026 22:58

,,Последната буква задължително е главна" - А ако няма никакви букви отговаря ли на правилата?