Домашни > Работилница за отвари! > Решения > Решението на Филип Филчев

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

4 точки общо

7 успешни теста
13 неуспешни теста
Код
Скрий всички коментари

  1def times_decorator(func, times):
  2    def wrapper(target):
  3        for _ in range(times):
  4            func(target)
  5
  6    return wrapper
  7
  8
  9def is_merged_decorator(func):
 10    def wrapper(*args, **kwargs):
 11        self = args[0]
 12        other = args[1]
 13        if self.is_merged or (other.is_merged if isinstance(other, Potion) else False):
 14            raise TypeError('Potion is now part of something bigger than itself.')
 15        try:
 16            return func(*args, **kwargs)
 17        finally:
 18            if func.__name__ != '__getattr__':
 19                """Set both both potions as merged and 'part of something bigger than itself'
 20                    if the function is not __getattr__.
 21                    If the potion is merged, __getattr__ will also raise an exception.
 22                """
 23                self.set_is_merged(True)
 24                other.set_is_merged(True)
 25
 26    return wrapper
 27
 28
 29class Potion:
 30    def __init__(self, effects, duration, intensities=None):
 31        self._effects = effects
 32        self._intensities = intensities or {effect: 1 for effect in effects}
 33        self._depleted = set()
 34        self._duration = duration
 35        self._is_merged = False
 36        self._is_depleted = False
 37
 38    @property
 39    def duration(self):
 40        return self._duration
 41
 42    @property
 43    def is_merged(self):
 44        return self._is_merged
 45
 46    @property
 47    def effects(self):
 48        return list(self._effects.keys())
 49
 50    @property
 51    def depleted(self):
 52        return self._depleted
 53
 54    def __getitem__(self, item):
 55        return self.__getattr__(item)
 56
 57    def set_is_depleted(self):
 58        self._is_depleted = True
 59
 60    def set_is_merged(self, value):
 61        self._is_merged = value
 62
 63    @is_merged_decorator
 64    def __getattr__(self, effect_name):
 65        if effect_name not in self._effects:
 66            raise AttributeError
 67
 68        if effect_name in self._depleted:
 69            raise TypeError('Effect is depleted')
 70
 71        if self._is_depleted:
 72            raise TypeError('Potion is depleted.')
 73
 74        self._depleted.add(effect_name)
 75        return times_decorator(self._effects[effect_name], self._intensities[effect_name])
 76
 77    @is_merged_decorator
 78    def __add__(self, other):
 79        if not isinstance(other, Potion):
 80            raise TypeError('Potions can only be combined with other potions')
 81
 82        effects = {**self._effects, **other._effects}
 83        duration = max(self._duration, other._duration)
 84        intensities = {
 85            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
 86            for effect in effects
 87        }
 88        return Potion(effects, duration, intensities)
 89
 90    @is_merged_decorator
 91    def __sub__(self, other):
 92        if not isinstance(other, Potion):
 93            raise TypeError('Potions can only be combined with other potions')
 94
 95        if other._effects.keys() - self._effects.keys() != set():
 96            raise TypeError('All effects of the right potion must be present in the left potion')
 97
 98        duration = self._duration
 99        effects = {}
100        intensities = {}
101        for effect in self._effects:
102            if effect in other._effects:
103                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
104                if new_effect_intensity > 0:
105                    effects[effect] = self._effects[effect]
106                    intensities[effect] = new_effect_intensity
107            else:
108                effects[effect] = self._effects[effect]
109                intensities[effect] = self._intensities[effect]
110
111        return Potion(effects, duration, intensities)
112
113    @is_merged_decorator
114    def __mul__(self, intensity_growth):
115        try:
116            float(intensity_growth)
117        except ValueError:
118            raise TypeError('Potions can only be multiplied by integers')
119
120        effects = {**self._effects}
121        duration = self._duration
122        intensities = {
123            effect: round(self._intensities.get(effect, 0) * intensity_growth)
124            for effect in effects
125        }
126        return Potion(effects, duration, intensities)
127
128    @is_merged_decorator
129    def __truediv__(self, new_potions_count):
130        try:
131            int(new_potions_count)
132        except ValueError:
133            raise TypeError('Potions can only be divided by integers')
134
135        return tuple([Potion({**self._effects},
136                             self._duration,
137                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)
138                              for effect in self._intensities}
139                             ) for _ in range(new_potions_count)])
140
141    def __eq__(self, other):
142        if not isinstance(other, Potion):
143            return False
144
145        return self._effects.keys() - other._effects.keys() == set() and all(
146            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
147        )
148
149    def __lt__(self, other):
150        if not isinstance(other, Potion):
151            return False
152
153        return sum(self._intensities.values()) < sum(other._intensities.values())
154
155    def __gt__(self, other):
156        if not isinstance(other, Potion):
157            return False
158
159        return not self == other and not self < other
160
161
162class ГоспожатаПоХимия:
163    def __init__(self):
164        self._target = None
165        self._target_public_attributes = None
166        self._current_tick_duration = None
167
168    def _sort_effects_by_molecule_mass(self, effects):
169        new_effects = list(effects)[:]
170        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
171
172    def _get_public_attributes(self, target):
173        all_attributes = vars(target)
174        return {key: value for key, value in all_attributes.items() if
175                not key.startswith('_')}
176
177    def apply(self, target, potion):
178        self._target_public_attributes = self._get_public_attributes(target)
179        self._target = target
180        self._current_tick_duration = potion.duration
181
182        for effect in self._sort_effects_by_molecule_mass(potion.effects):
183            potion[effect](target)
184
185        if set(potion.depleted) == set(potion.effects):
186            potion.set_is_depleted()
187
188    def tick(self):
189        if not self._current_tick_duration:
190            raise Exception('No potion applied')
191
192        self._current_tick_duration -= 1
193
194        if self._current_tick_duration != 0:
195            return
196
197        current_public_attributes = self._get_public_attributes(self._target)
198        for key in current_public_attributes:
199            if key in self._target_public_attributes:
200                setattr(self._target, key, self._target_public_attributes[key])
201            else:
202                del self._target.__dict__[key]

.F.EE..EEEEEF..E.FEF
======================================================================
ERROR: test_equal (test.TestPotionComparison)
Test equality of potions.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 296, in test_equal
potion3 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_superbness (test.TestPotionComparison)
Test superbness of potions.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 316, in test_superbness
potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_deprecation (test.TestPotionOperations)
Test deprecation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 263, in test_deprecation
potion = potion1 * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_dilution (test.TestPotionOperations)
Test dilution of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 118, in test_dilution
half_potion = base_potion * 0.5
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'float' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_potentiation (test.TestPotionOperations)
Test potentiation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 110, in test_potentiation
potion = potion * 3
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_purification (test.TestPotionOperations)
Test purification of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 183, in test_purification
potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_separation (test.TestPotionOperations)
Test separation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 212, in test_separation
potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 9
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
ERROR: test_applying_part_of_potion (test.TestГоспожатаПоХимия)
Test applying only a part of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 365, in test_applying_part_of_potion
self._dimitrichka.apply(self._target, potion)
File "/tmp/solution.py", line 183, in apply
potion[effect](target)
File "/tmp/solution.py", line 55, in __getitem__
return self.__getattr__(item)
File "/tmp/solution.py", line 16, in wrapper
return func(*args, **kwargs)
File "/tmp/solution.py", line 69, in __getattr__
raise TypeError('Effect is depleted')
TypeError: Effect is depleted

======================================================================
ERROR: test_ticking_multiple_targets (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 479, in test_ticking_multiple_targets
potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'

======================================================================
FAIL: test_depletion (test.TestBasicPotion)
Test depletion of a potion effect.
----------------------------------------------------------------------
TypeError: Effect is depleted

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/tmp/test.py", line 78, in test_depletion
with self.assertRaisesRegex(TypeError, 'Effect is depleted\.'):
AssertionError: "Effect is depleted\." does not match "Effect is depleted"

======================================================================
FAIL: test_applying_depleted_potion (test.TestГоспожатаПоХимия)
Test applying a depleted potion or a potion that was used in a reaction.
----------------------------------------------------------------------
TypeError: Effect is depleted

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/tmp/test.py", line 380, in test_applying_depleted_potion
with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
AssertionError: "Potion is depleted\." does not match "Effect is depleted"

======================================================================
FAIL: test_ticking_multiple_potions (test.TestГоспожатаПоХимия)
Test ticking after applying multiple potions which affect the same attribute.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 458, in test_ticking_multiple_potions
self.assertEqual(self._target.int_attr, 50)
AssertionError: 500 != 50

======================================================================
FAIL: test_ticking_mutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 446, in test_ticking_mutable
self.assertEqual(self._target.list_attr, [1, 2, 3])
AssertionError: Lists differ: [1, 2, 3, 4] != [1, 2, 3]

First list contains 1 additional elements.
First extra element 3:
4

- [1, 2, 3, 4]
? ---

+ [1, 2, 3]

----------------------------------------------------------------------
Ran 20 tests in 0.002s

FAILED (failures=4, errors=9)

Дискусия
Виктор Бечев
03.12.2023 23:51

Да, ако имаш дефинирана `__eq__` и едно от `__gt__` или `__lt__` - Python ще се оправи с останалите. **В общия случай** е така, пак казвам - тук логиката може да се окаже шано, не мога да го сметна наум. :)
Филип Филчев
03.12.2023 15:09

Относно коментара за \_\_gt__, не е нужно да се предефинира ли даже, ако е в такъв вид? Тоест == и < са достатъчни? Може да е споменавано на лекция, но не съм запомнил.
Филип Филчев
03.12.2023 15:08

Ами да осъзнавам го това, но според мен е логично подобен проблем да се обособи по някакъв такъв начин, в случая с декоратор. Пък и тъкмо да се упражня с декоратори, понеже ме кефят. Уж го изтествах и работи. Ако ми остане време преди крайния срок ще се опитам да го счупя.
Виктор Бечев
03.12.2023 13:44

Не са драма декораторите, проблем е, че се опитваш да генерализираш декориране на неща, които имат различни сигнатури (i.e. в единия случай приемат `Potion` и `Potion`, в другия `Potion` и `int` и т.н.), така че дано си се подсигурил, че това не обърква нещо.
Филип Филчев
03.12.2023 00:46

Реших да си поиграя с декоратори, получи се доста грозно, че даже и може да не работи много като хората, ама кво такова :)
История

f1def times_decorator(func, times):f1def times_decorator(func, times):
2    def wrapper(target):2    def wrapper(target):
3        for _ in range(times):3        for _ in range(times):
4            func(target)4            func(target)
55
6    return wrapper6    return wrapper
77
88
9def is_merged_decorator(func):9def is_merged_decorator(func):
10    def wrapper(*args, **kwargs):10    def wrapper(*args, **kwargs):
11        self = args[0]11        self = args[0]
12        other = args[1]12        other = args[1]
n13        if self.is_merged or other.is_merged:n13        if self.is_merged or (other.is_merged if isinstance(other, Potion) else False):
14            raise TypeError('Potion is now part of something bigger than itself.')14            raise TypeError('Potion is now part of something bigger than itself.')
15        try:15        try:
16            return func(*args, **kwargs)16            return func(*args, **kwargs)
17        finally:17        finally:
18            if func.__name__ != '__getattr__':18            if func.__name__ != '__getattr__':
19                """Set both both potions as merged and 'part of something bigger than itself'19                """Set both both potions as merged and 'part of something bigger than itself'
20                    if the function is not __getattr__.20                    if the function is not __getattr__.
21                    If the potion is merged, __getattr__ will also raise an exception.21                    If the potion is merged, __getattr__ will also raise an exception.
22                """22                """
23                self.set_is_merged(True)23                self.set_is_merged(True)
24                other.set_is_merged(True)24                other.set_is_merged(True)
2525
26    return wrapper26    return wrapper
2727
2828
29class Potion:29class Potion:
30    def __init__(self, effects, duration, intensities=None):30    def __init__(self, effects, duration, intensities=None):
31        self._effects = effects31        self._effects = effects
32        self._intensities = intensities or {effect: 1 for effect in effects}32        self._intensities = intensities or {effect: 1 for effect in effects}
33        self._depleted = set()33        self._depleted = set()
34        self._duration = duration34        self._duration = duration
35        self._is_merged = False35        self._is_merged = False
36        self._is_depleted = False36        self._is_depleted = False
3737
38    @property38    @property
39    def duration(self):39    def duration(self):
40        return self._duration40        return self._duration
4141
42    @property42    @property
43    def is_merged(self):43    def is_merged(self):
44        return self._is_merged44        return self._is_merged
4545
46    @property46    @property
47    def effects(self):47    def effects(self):
48        return list(self._effects.keys())48        return list(self._effects.keys())
4949
50    @property50    @property
51    def depleted(self):51    def depleted(self):
52        return self._depleted52        return self._depleted
5353
54    def __getitem__(self, item):54    def __getitem__(self, item):
55        return self.__getattr__(item)55        return self.__getattr__(item)
5656
57    def set_is_depleted(self):57    def set_is_depleted(self):
58        self._is_depleted = True58        self._is_depleted = True
5959
60    def set_is_merged(self, value):60    def set_is_merged(self, value):
61        self._is_merged = value61        self._is_merged = value
6262
63    @is_merged_decorator63    @is_merged_decorator
64    def __getattr__(self, effect_name):64    def __getattr__(self, effect_name):
65        if effect_name not in self._effects:65        if effect_name not in self._effects:
66            raise AttributeError66            raise AttributeError
6767
68        if effect_name in self._depleted:68        if effect_name in self._depleted:
69            raise TypeError('Effect is depleted')69            raise TypeError('Effect is depleted')
7070
71        if self._is_depleted:71        if self._is_depleted:
72            raise TypeError('Potion is depleted.')72            raise TypeError('Potion is depleted.')
7373
74        self._depleted.add(effect_name)74        self._depleted.add(effect_name)
75        return times_decorator(self._effects[effect_name], self._intensities[effect_name])75        return times_decorator(self._effects[effect_name], self._intensities[effect_name])
7676
77    @is_merged_decorator77    @is_merged_decorator
78    def __add__(self, other):78    def __add__(self, other):
79        if not isinstance(other, Potion):79        if not isinstance(other, Potion):
80            raise TypeError('Potions can only be combined with other potions')80            raise TypeError('Potions can only be combined with other potions')
8181
82        effects = {**self._effects, **other._effects}82        effects = {**self._effects, **other._effects}
83        duration = max(self._duration, other._duration)83        duration = max(self._duration, other._duration)
84        intensities = {84        intensities = {
85            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))85            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
86            for effect in effects86            for effect in effects
87        }87        }
88        return Potion(effects, duration, intensities)88        return Potion(effects, duration, intensities)
8989
90    @is_merged_decorator90    @is_merged_decorator
91    def __sub__(self, other):91    def __sub__(self, other):
92        if not isinstance(other, Potion):92        if not isinstance(other, Potion):
93            raise TypeError('Potions can only be combined with other potions')93            raise TypeError('Potions can only be combined with other potions')
9494
95        if other._effects.keys() - self._effects.keys() != set():95        if other._effects.keys() - self._effects.keys() != set():
96            raise TypeError('All effects of the right potion must be present in the left potion')96            raise TypeError('All effects of the right potion must be present in the left potion')
9797
98        duration = self._duration98        duration = self._duration
99        effects = {}99        effects = {}
100        intensities = {}100        intensities = {}
101        for effect in self._effects:101        for effect in self._effects:
102            if effect in other._effects:102            if effect in other._effects:
103                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)103                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
104                if new_effect_intensity > 0:104                if new_effect_intensity > 0:
105                    effects[effect] = self._effects[effect]105                    effects[effect] = self._effects[effect]
106                    intensities[effect] = new_effect_intensity106                    intensities[effect] = new_effect_intensity
107            else:107            else:
108                effects[effect] = self._effects[effect]108                effects[effect] = self._effects[effect]
109                intensities[effect] = self._intensities[effect]109                intensities[effect] = self._intensities[effect]
110110
111        return Potion(effects, duration, intensities)111        return Potion(effects, duration, intensities)
112112
113    @is_merged_decorator113    @is_merged_decorator
114    def __mul__(self, intensity_growth):114    def __mul__(self, intensity_growth):
115        try:115        try:
116            float(intensity_growth)116            float(intensity_growth)
117        except ValueError:117        except ValueError:
118            raise TypeError('Potions can only be multiplied by integers')118            raise TypeError('Potions can only be multiplied by integers')
119119
120        effects = {**self._effects}120        effects = {**self._effects}
121        duration = self._duration121        duration = self._duration
122        intensities = {122        intensities = {
123            effect: round(self._intensities.get(effect, 0) * intensity_growth)123            effect: round(self._intensities.get(effect, 0) * intensity_growth)
124            for effect in effects124            for effect in effects
125        }125        }
126        return Potion(effects, duration, intensities)126        return Potion(effects, duration, intensities)
127127
128    @is_merged_decorator128    @is_merged_decorator
129    def __truediv__(self, new_potions_count):129    def __truediv__(self, new_potions_count):
130        try:130        try:
131            int(new_potions_count)131            int(new_potions_count)
132        except ValueError:132        except ValueError:
133            raise TypeError('Potions can only be divided by integers')133            raise TypeError('Potions can only be divided by integers')
134134
135        return tuple([Potion({**self._effects},135        return tuple([Potion({**self._effects},
136                             self._duration,136                             self._duration,
137                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)137                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)
138                              for effect in self._intensities}138                              for effect in self._intensities}
139                             ) for _ in range(new_potions_count)])139                             ) for _ in range(new_potions_count)])
140140
141    def __eq__(self, other):141    def __eq__(self, other):
142        if not isinstance(other, Potion):142        if not isinstance(other, Potion):
143            return False143            return False
144144
145        return self._effects.keys() - other._effects.keys() == set() and all(145        return self._effects.keys() - other._effects.keys() == set() and all(
146            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]146            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
147        )147        )
148148
149    def __lt__(self, other):149    def __lt__(self, other):
150        if not isinstance(other, Potion):150        if not isinstance(other, Potion):
151            return False151            return False
152152
153        return sum(self._intensities.values()) < sum(other._intensities.values())153        return sum(self._intensities.values()) < sum(other._intensities.values())
154154
155    def __gt__(self, other):155    def __gt__(self, other):
156        if not isinstance(other, Potion):156        if not isinstance(other, Potion):
157            return False157            return False
158158
159        return not self == other and not self < other159        return not self == other and not self < other
160160
161161
162class ГоспожатаПоХимия:162class ГоспожатаПоХимия:
163    def __init__(self):163    def __init__(self):
164        self._target = None164        self._target = None
165        self._target_public_attributes = None165        self._target_public_attributes = None
166        self._current_tick_duration = None166        self._current_tick_duration = None
167167
168    def _sort_effects_by_molecule_mass(self, effects):168    def _sort_effects_by_molecule_mass(self, effects):
169        new_effects = list(effects)[:]169        new_effects = list(effects)[:]
170        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)170        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
171171
172    def _get_public_attributes(self, target):172    def _get_public_attributes(self, target):
173        all_attributes = vars(target)173        all_attributes = vars(target)
174        return {key: value for key, value in all_attributes.items() if174        return {key: value for key, value in all_attributes.items() if
175                not key.startswith('_')}175                not key.startswith('_')}
176176
177    def apply(self, target, potion):177    def apply(self, target, potion):
178        self._target_public_attributes = self._get_public_attributes(target)178        self._target_public_attributes = self._get_public_attributes(target)
179        self._target = target179        self._target = target
180        self._current_tick_duration = potion.duration180        self._current_tick_duration = potion.duration
181181
182        for effect in self._sort_effects_by_molecule_mass(potion.effects):182        for effect in self._sort_effects_by_molecule_mass(potion.effects):
183            potion[effect](target)183            potion[effect](target)
184184
185        if set(potion.depleted) == set(potion.effects):185        if set(potion.depleted) == set(potion.effects):
186            potion.set_is_depleted()186            potion.set_is_depleted()
187187
188    def tick(self):188    def tick(self):
189        if not self._current_tick_duration:189        if not self._current_tick_duration:
190            raise Exception('No potion applied')190            raise Exception('No potion applied')
191191
192        self._current_tick_duration -= 1192        self._current_tick_duration -= 1
193193
194        if self._current_tick_duration != 0:194        if self._current_tick_duration != 0:
195            return195            return
196196
197        current_public_attributes = self._get_public_attributes(self._target)197        current_public_attributes = self._get_public_attributes(self._target)
198        for key in current_public_attributes:198        for key in current_public_attributes:
199            if key in self._target_public_attributes:199            if key in self._target_public_attributes:
200                setattr(self._target, key, self._target_public_attributes[key])200                setattr(self._target, key, self._target_public_attributes[key])
201            else:201            else:
202                del self._target.__dict__[key]202                del self._target.__dict__[key]
tt203 
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1def times_decorator(func, times):f1def times_decorator(func, times):
2    def wrapper(target):2    def wrapper(target):
3        for _ in range(times):3        for _ in range(times):
4            func(target)4            func(target)
55
6    return wrapper6    return wrapper
77
88
9def is_merged_decorator(func):9def is_merged_decorator(func):
10    def wrapper(*args, **kwargs):10    def wrapper(*args, **kwargs):
11        self = args[0]11        self = args[0]
nn12        other = args[1]
12        if self._is_merged:13        if self.is_merged or other.is_merged:
13            raise TypeError('Potion is now part of something bigger than itself.')14            raise TypeError('Potion is now part of something bigger than itself.')
14        try:15        try:
15            return func(*args, **kwargs)16            return func(*args, **kwargs)
16        finally:17        finally:
17            if func.__name__ != '__getattr__':18            if func.__name__ != '__getattr__':
nn19                """Set both both potions as merged and 'part of something bigger than itself'
20                    if the function is not __getattr__.
21                    If the potion is merged, __getattr__ will also raise an exception.
22                """
18                self._is_merged = True23                self.set_is_merged(True)
24                other.set_is_merged(True)
1925
20    return wrapper26    return wrapper
2127
2228
23class Potion:29class Potion:
24    def __init__(self, effects, duration, intensities=None):30    def __init__(self, effects, duration, intensities=None):
n25        self._is_merged = Falsen
26        self._effects = effects31        self._effects = effects
27        self._intensities = intensities or {effect: 1 for effect in effects}32        self._intensities = intensities or {effect: 1 for effect in effects}
28        self._depleted = set()33        self._depleted = set()
29        self._duration = duration34        self._duration = duration
nn35        self._is_merged = False
30        self._is_depleted = False36        self._is_depleted = False
3137
32    @property38    @property
33    def duration(self):39    def duration(self):
34        return self._duration40        return self._duration
3541
36    @property42    @property
nn43    def is_merged(self):
44        return self._is_merged
45 
46    @property
37    def effects(self):47    def effects(self):
38        return list(self._effects.keys())48        return list(self._effects.keys())
3949
40    @property50    @property
41    def depleted(self):51    def depleted(self):
42        return self._depleted52        return self._depleted
4353
44    def __getitem__(self, item):54    def __getitem__(self, item):
45        return self.__getattr__(item)55        return self.__getattr__(item)
4656
n47    def deplete(self):n57    def set_is_depleted(self):
48        self._is_depleted = True58        self._is_depleted = True
4959
n50    def __repr__(self):n60    def set_is_merged(self, value):
51        return f'Potion({self._effects}, {self._duration})'61        self._is_merged = value
5262
53    @is_merged_decorator63    @is_merged_decorator
54    def __getattr__(self, effect_name):64    def __getattr__(self, effect_name):
55        if effect_name not in self._effects:65        if effect_name not in self._effects:
56            raise AttributeError66            raise AttributeError
5767
58        if effect_name in self._depleted:68        if effect_name in self._depleted:
59            raise TypeError('Effect is depleted')69            raise TypeError('Effect is depleted')
6070
61        if self._is_depleted:71        if self._is_depleted:
62            raise TypeError('Potion is depleted.')72            raise TypeError('Potion is depleted.')
6373
64        self._depleted.add(effect_name)74        self._depleted.add(effect_name)
65        return times_decorator(self._effects[effect_name], self._intensities[effect_name])75        return times_decorator(self._effects[effect_name], self._intensities[effect_name])
6676
67    @is_merged_decorator77    @is_merged_decorator
68    def __add__(self, other):78    def __add__(self, other):
69        if not isinstance(other, Potion):79        if not isinstance(other, Potion):
70            raise TypeError('Potions can only be combined with other potions')80            raise TypeError('Potions can only be combined with other potions')
7181
72        effects = {**self._effects, **other._effects}82        effects = {**self._effects, **other._effects}
73        duration = max(self._duration, other._duration)83        duration = max(self._duration, other._duration)
74        intensities = {84        intensities = {
75            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))85            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
76            for effect in effects86            for effect in effects
77        }87        }
78        return Potion(effects, duration, intensities)88        return Potion(effects, duration, intensities)
7989
80    @is_merged_decorator90    @is_merged_decorator
81    def __sub__(self, other):91    def __sub__(self, other):
82        if not isinstance(other, Potion):92        if not isinstance(other, Potion):
83            raise TypeError('Potions can only be combined with other potions')93            raise TypeError('Potions can only be combined with other potions')
8494
85        if other._effects.keys() - self._effects.keys() != set():95        if other._effects.keys() - self._effects.keys() != set():
86            raise TypeError('All effects of the right potion must be present in the left potion')96            raise TypeError('All effects of the right potion must be present in the left potion')
8797
88        duration = self._duration98        duration = self._duration
89        effects = {}99        effects = {}
90        intensities = {}100        intensities = {}
91        for effect in self._effects:101        for effect in self._effects:
92            if effect in other._effects:102            if effect in other._effects:
93                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)103                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
94                if new_effect_intensity > 0:104                if new_effect_intensity > 0:
95                    effects[effect] = self._effects[effect]105                    effects[effect] = self._effects[effect]
96                    intensities[effect] = new_effect_intensity106                    intensities[effect] = new_effect_intensity
97            else:107            else:
98                effects[effect] = self._effects[effect]108                effects[effect] = self._effects[effect]
99                intensities[effect] = self._intensities[effect]109                intensities[effect] = self._intensities[effect]
100110
101        return Potion(effects, duration, intensities)111        return Potion(effects, duration, intensities)
102112
103    @is_merged_decorator113    @is_merged_decorator
104    def __mul__(self, intensity_growth):114    def __mul__(self, intensity_growth):
105        try:115        try:
106            float(intensity_growth)116            float(intensity_growth)
107        except ValueError:117        except ValueError:
108            raise TypeError('Potions can only be multiplied by integers')118            raise TypeError('Potions can only be multiplied by integers')
109119
110        effects = {**self._effects}120        effects = {**self._effects}
111        duration = self._duration121        duration = self._duration
112        intensities = {122        intensities = {
113            effect: round(self._intensities.get(effect, 0) * intensity_growth)123            effect: round(self._intensities.get(effect, 0) * intensity_growth)
114            for effect in effects124            for effect in effects
115        }125        }
116        return Potion(effects, duration, intensities)126        return Potion(effects, duration, intensities)
117127
118    @is_merged_decorator128    @is_merged_decorator
119    def __truediv__(self, new_potions_count):129    def __truediv__(self, new_potions_count):
120        try:130        try:
121            int(new_potions_count)131            int(new_potions_count)
122        except ValueError:132        except ValueError:
123            raise TypeError('Potions can only be divided by integers')133            raise TypeError('Potions can only be divided by integers')
124134
125        return tuple([Potion({**self._effects},135        return tuple([Potion({**self._effects},
126                             self._duration,136                             self._duration,
127                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)137                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)
128                              for effect in self._intensities}138                              for effect in self._intensities}
129                             ) for _ in range(new_potions_count)])139                             ) for _ in range(new_potions_count)])
130140
131    def __eq__(self, other):141    def __eq__(self, other):
132        if not isinstance(other, Potion):142        if not isinstance(other, Potion):
133            return False143            return False
134144
135        return self._effects.keys() - other._effects.keys() == set() and all(145        return self._effects.keys() - other._effects.keys() == set() and all(
136            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]146            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
137        )147        )
138148
139    def __lt__(self, other):149    def __lt__(self, other):
140        if not isinstance(other, Potion):150        if not isinstance(other, Potion):
141            return False151            return False
142152
143        return sum(self._intensities.values()) < sum(other._intensities.values())153        return sum(self._intensities.values()) < sum(other._intensities.values())
144154
145    def __gt__(self, other):155    def __gt__(self, other):
146        if not isinstance(other, Potion):156        if not isinstance(other, Potion):
147            return False157            return False
148158
149        return not self == other and not self < other159        return not self == other and not self < other
150160
151161
152class ГоспожатаПоХимия:162class ГоспожатаПоХимия:
153    def __init__(self):163    def __init__(self):
154        self._target = None164        self._target = None
155        self._target_public_attributes = None165        self._target_public_attributes = None
156        self._current_tick_duration = None166        self._current_tick_duration = None
157167
158    def _sort_effects_by_molecule_mass(self, effects):168    def _sort_effects_by_molecule_mass(self, effects):
159        new_effects = list(effects)[:]169        new_effects = list(effects)[:]
160        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)170        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
161171
162    def _get_public_attributes(self, target):172    def _get_public_attributes(self, target):
163        all_attributes = vars(target)173        all_attributes = vars(target)
164        return {key: value for key, value in all_attributes.items() if174        return {key: value for key, value in all_attributes.items() if
165                not key.startswith('_')}175                not key.startswith('_')}
166176
167    def apply(self, target, potion):177    def apply(self, target, potion):
168        self._target_public_attributes = self._get_public_attributes(target)178        self._target_public_attributes = self._get_public_attributes(target)
169        self._target = target179        self._target = target
170        self._current_tick_duration = potion.duration180        self._current_tick_duration = potion.duration
171181
172        for effect in self._sort_effects_by_molecule_mass(potion.effects):182        for effect in self._sort_effects_by_molecule_mass(potion.effects):
173            potion[effect](target)183            potion[effect](target)
174184
175        if set(potion.depleted) == set(potion.effects):185        if set(potion.depleted) == set(potion.effects):
t176            potion.deplete()t186            potion.set_is_depleted()
177187
178    def tick(self):188    def tick(self):
179        if not self._current_tick_duration:189        if not self._current_tick_duration:
180            raise Exception('No potion applied')190            raise Exception('No potion applied')
181191
182        self._current_tick_duration -= 1192        self._current_tick_duration -= 1
183193
184        if self._current_tick_duration != 0:194        if self._current_tick_duration != 0:
185            return195            return
186196
187        current_public_attributes = self._get_public_attributes(self._target)197        current_public_attributes = self._get_public_attributes(self._target)
188        for key in current_public_attributes:198        for key in current_public_attributes:
189            if key in self._target_public_attributes:199            if key in self._target_public_attributes:
190                setattr(self._target, key, self._target_public_attributes[key])200                setattr(self._target, key, self._target_public_attributes[key])
191            else:201            else:
192                del self._target.__dict__[key]202                del self._target.__dict__[key]
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1def times_decorator(func, times):f1def times_decorator(func, times):
2    def wrapper(target):2    def wrapper(target):
3        for _ in range(times):3        for _ in range(times):
4            func(target)4            func(target)
55
6    return wrapper6    return wrapper
77
88
9def is_merged_decorator(func):9def is_merged_decorator(func):
10    def wrapper(*args, **kwargs):10    def wrapper(*args, **kwargs):
11        self = args[0]11        self = args[0]
12        if self._is_merged:12        if self._is_merged:
13            raise TypeError('Potion is now part of something bigger than itself.')13            raise TypeError('Potion is now part of something bigger than itself.')
14        try:14        try:
15            return func(*args, **kwargs)15            return func(*args, **kwargs)
16        finally:16        finally:
17            if func.__name__ != '__getattr__':17            if func.__name__ != '__getattr__':
18                self._is_merged = True18                self._is_merged = True
1919
20    return wrapper20    return wrapper
2121
2222
23class Potion:23class Potion:
24    def __init__(self, effects, duration, intensities=None):24    def __init__(self, effects, duration, intensities=None):
25        self._is_merged = False25        self._is_merged = False
26        self._effects = effects26        self._effects = effects
27        self._intensities = intensities or {effect: 1 for effect in effects}27        self._intensities = intensities or {effect: 1 for effect in effects}
28        self._depleted = set()28        self._depleted = set()
29        self._duration = duration29        self._duration = duration
n30        self._depleted_message = 'Effect is depleted'n
31        self._is_depleted = False30        self._is_depleted = False
3231
33    @property32    @property
34    def duration(self):33    def duration(self):
35        return self._duration34        return self._duration
3635
37    @property36    @property
38    def effects(self):37    def effects(self):
39        return list(self._effects.keys())38        return list(self._effects.keys())
4039
41    @property40    @property
42    def depleted(self):41    def depleted(self):
43        return self._depleted42        return self._depleted
4443
45    def __getitem__(self, item):44    def __getitem__(self, item):
46        return self.__getattr__(item)45        return self.__getattr__(item)
4746
n48    def deplete(self, depleted_message):n47    def deplete(self):
49        self._depleted_message = depleted_message
50        self._is_depleted = True48        self._is_depleted = True
5149
52    def __repr__(self):50    def __repr__(self):
53        return f'Potion({self._effects}, {self._duration})'51        return f'Potion({self._effects}, {self._duration})'
5452
55    @is_merged_decorator53    @is_merged_decorator
56    def __getattr__(self, effect_name):54    def __getattr__(self, effect_name):
57        if effect_name not in self._effects:55        if effect_name not in self._effects:
58            raise AttributeError56            raise AttributeError
5957
n60        if effect_name in self._depleted or self._is_depleted:n58        if effect_name in self._depleted:
61            raise TypeError(self._depleted_message)59            raise TypeError('Effect is depleted')
60 
61        if self._is_depleted:
62            raise TypeError('Potion is depleted.')
6263
63        self._depleted.add(effect_name)64        self._depleted.add(effect_name)
64        return times_decorator(self._effects[effect_name], self._intensities[effect_name])65        return times_decorator(self._effects[effect_name], self._intensities[effect_name])
6566
66    @is_merged_decorator67    @is_merged_decorator
67    def __add__(self, other):68    def __add__(self, other):
68        if not isinstance(other, Potion):69        if not isinstance(other, Potion):
69            raise TypeError('Potions can only be combined with other potions')70            raise TypeError('Potions can only be combined with other potions')
7071
71        effects = {**self._effects, **other._effects}72        effects = {**self._effects, **other._effects}
72        duration = max(self._duration, other._duration)73        duration = max(self._duration, other._duration)
73        intensities = {74        intensities = {
74            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))75            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
75            for effect in effects76            for effect in effects
76        }77        }
77        return Potion(effects, duration, intensities)78        return Potion(effects, duration, intensities)
7879
79    @is_merged_decorator80    @is_merged_decorator
80    def __sub__(self, other):81    def __sub__(self, other):
81        if not isinstance(other, Potion):82        if not isinstance(other, Potion):
82            raise TypeError('Potions can only be combined with other potions')83            raise TypeError('Potions can only be combined with other potions')
8384
84        if other._effects.keys() - self._effects.keys() != set():85        if other._effects.keys() - self._effects.keys() != set():
85            raise TypeError('All effects of the right potion must be present in the left potion')86            raise TypeError('All effects of the right potion must be present in the left potion')
8687
87        duration = self._duration88        duration = self._duration
88        effects = {}89        effects = {}
89        intensities = {}90        intensities = {}
90        for effect in self._effects:91        for effect in self._effects:
91            if effect in other._effects:92            if effect in other._effects:
92                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)93                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
93                if new_effect_intensity > 0:94                if new_effect_intensity > 0:
94                    effects[effect] = self._effects[effect]95                    effects[effect] = self._effects[effect]
95                    intensities[effect] = new_effect_intensity96                    intensities[effect] = new_effect_intensity
96            else:97            else:
97                effects[effect] = self._effects[effect]98                effects[effect] = self._effects[effect]
98                intensities[effect] = self._intensities[effect]99                intensities[effect] = self._intensities[effect]
99100
100        return Potion(effects, duration, intensities)101        return Potion(effects, duration, intensities)
101102
102    @is_merged_decorator103    @is_merged_decorator
103    def __mul__(self, intensity_growth):104    def __mul__(self, intensity_growth):
104        try:105        try:
105            float(intensity_growth)106            float(intensity_growth)
106        except ValueError:107        except ValueError:
107            raise TypeError('Potions can only be multiplied by integers')108            raise TypeError('Potions can only be multiplied by integers')
108109
109        effects = {**self._effects}110        effects = {**self._effects}
110        duration = self._duration111        duration = self._duration
111        intensities = {112        intensities = {
112            effect: round(self._intensities.get(effect, 0) * intensity_growth)113            effect: round(self._intensities.get(effect, 0) * intensity_growth)
113            for effect in effects114            for effect in effects
114        }115        }
115        return Potion(effects, duration, intensities)116        return Potion(effects, duration, intensities)
116117
117    @is_merged_decorator118    @is_merged_decorator
118    def __truediv__(self, new_potions_count):119    def __truediv__(self, new_potions_count):
119        try:120        try:
120            int(new_potions_count)121            int(new_potions_count)
121        except ValueError:122        except ValueError:
122            raise TypeError('Potions can only be divided by integers')123            raise TypeError('Potions can only be divided by integers')
123124
124        return tuple([Potion({**self._effects},125        return tuple([Potion({**self._effects},
125                             self._duration,126                             self._duration,
126                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)127                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)
127                              for effect in self._intensities}128                              for effect in self._intensities}
128                             ) for _ in range(new_potions_count)])129                             ) for _ in range(new_potions_count)])
129130
130    def __eq__(self, other):131    def __eq__(self, other):
131        if not isinstance(other, Potion):132        if not isinstance(other, Potion):
132            return False133            return False
133134
134        return self._effects.keys() - other._effects.keys() == set() and all(135        return self._effects.keys() - other._effects.keys() == set() and all(
135            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]136            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
136        )137        )
137138
138    def __lt__(self, other):139    def __lt__(self, other):
139        if not isinstance(other, Potion):140        if not isinstance(other, Potion):
140            return False141            return False
141142
142        return sum(self._intensities.values()) < sum(other._intensities.values())143        return sum(self._intensities.values()) < sum(other._intensities.values())
143144
144    def __gt__(self, other):145    def __gt__(self, other):
145        if not isinstance(other, Potion):146        if not isinstance(other, Potion):
146            return False147            return False
147148
148        return not self == other and not self < other149        return not self == other and not self < other
149150
150151
151class ГоспожатаПоХимия:152class ГоспожатаПоХимия:
152    def __init__(self):153    def __init__(self):
153        self._target = None154        self._target = None
154        self._target_public_attributes = None155        self._target_public_attributes = None
155        self._current_tick_duration = None156        self._current_tick_duration = None
156157
157    def _sort_effects_by_molecule_mass(self, effects):158    def _sort_effects_by_molecule_mass(self, effects):
158        new_effects = list(effects)[:]159        new_effects = list(effects)[:]
159        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)160        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
160161
161    def _get_public_attributes(self, target):162    def _get_public_attributes(self, target):
162        all_attributes = vars(target)163        all_attributes = vars(target)
163        return {key: value for key, value in all_attributes.items() if164        return {key: value for key, value in all_attributes.items() if
164                not key.startswith('_')}165                not key.startswith('_')}
165166
166    def apply(self, target, potion):167    def apply(self, target, potion):
167        self._target_public_attributes = self._get_public_attributes(target)168        self._target_public_attributes = self._get_public_attributes(target)
168        self._target = target169        self._target = target
169        self._current_tick_duration = potion.duration170        self._current_tick_duration = potion.duration
170171
171        for effect in self._sort_effects_by_molecule_mass(potion.effects):172        for effect in self._sort_effects_by_molecule_mass(potion.effects):
172            potion[effect](target)173            potion[effect](target)
173174
174        if set(potion.depleted) == set(potion.effects):175        if set(potion.depleted) == set(potion.effects):
n175            potion.deplete('Potion is depleted.')n176            potion.deplete()
176177
177    def tick(self):178    def tick(self):
178        if not self._current_tick_duration:179        if not self._current_tick_duration:
179            raise Exception('No potion applied')180            raise Exception('No potion applied')
180181
181        self._current_tick_duration -= 1182        self._current_tick_duration -= 1
182183
183        if self._current_tick_duration != 0:184        if self._current_tick_duration != 0:
184            return185            return
185186
186        current_public_attributes = self._get_public_attributes(self._target)187        current_public_attributes = self._get_public_attributes(self._target)
187        for key in current_public_attributes:188        for key in current_public_attributes:
188            if key in self._target_public_attributes:189            if key in self._target_public_attributes:
189                setattr(self._target, key, self._target_public_attributes[key])190                setattr(self._target, key, self._target_public_attributes[key])
190            else:191            else:
191                del self._target.__dict__[key]192                del self._target.__dict__[key]
t192 t
193 
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1def times_decorator(func, times):f1def times_decorator(func, times):
2    def wrapper(target):2    def wrapper(target):
3        for _ in range(times):3        for _ in range(times):
4            func(target)4            func(target)
55
6    return wrapper6    return wrapper
77
88
9def is_merged_decorator(func):9def is_merged_decorator(func):
10    def wrapper(*args, **kwargs):10    def wrapper(*args, **kwargs):
11        self = args[0]11        self = args[0]
12        if self._is_merged:12        if self._is_merged:
13            raise TypeError('Potion is now part of something bigger than itself.')13            raise TypeError('Potion is now part of something bigger than itself.')
14        try:14        try:
15            return func(*args, **kwargs)15            return func(*args, **kwargs)
16        finally:16        finally:
17            if func.__name__ != '__getattr__':17            if func.__name__ != '__getattr__':
18                self._is_merged = True18                self._is_merged = True
1919
20    return wrapper20    return wrapper
2121
2222
23class Potion:23class Potion:
24    def __init__(self, effects, duration, intensities=None):24    def __init__(self, effects, duration, intensities=None):
25        self._is_merged = False25        self._is_merged = False
26        self._effects = effects26        self._effects = effects
27        self._intensities = intensities or {effect: 1 for effect in effects}27        self._intensities = intensities or {effect: 1 for effect in effects}
28        self._depleted = set()28        self._depleted = set()
29        self._duration = duration29        self._duration = duration
30        self._depleted_message = 'Effect is depleted'30        self._depleted_message = 'Effect is depleted'
31        self._is_depleted = False31        self._is_depleted = False
3232
33    @property33    @property
34    def duration(self):34    def duration(self):
35        return self._duration35        return self._duration
3636
37    @property37    @property
38    def effects(self):38    def effects(self):
39        return list(self._effects.keys())39        return list(self._effects.keys())
4040
41    @property41    @property
42    def depleted(self):42    def depleted(self):
43        return self._depleted43        return self._depleted
4444
45    def __getitem__(self, item):45    def __getitem__(self, item):
46        return self.__getattr__(item)46        return self.__getattr__(item)
4747
48    def deplete(self, depleted_message):48    def deplete(self, depleted_message):
49        self._depleted_message = depleted_message49        self._depleted_message = depleted_message
50        self._is_depleted = True50        self._is_depleted = True
5151
52    def __repr__(self):52    def __repr__(self):
53        return f'Potion({self._effects}, {self._duration})'53        return f'Potion({self._effects}, {self._duration})'
5454
55    @is_merged_decorator55    @is_merged_decorator
56    def __getattr__(self, effect_name):56    def __getattr__(self, effect_name):
57        if effect_name not in self._effects:57        if effect_name not in self._effects:
58            raise AttributeError58            raise AttributeError
5959
60        if effect_name in self._depleted or self._is_depleted:60        if effect_name in self._depleted or self._is_depleted:
61            raise TypeError(self._depleted_message)61            raise TypeError(self._depleted_message)
6262
63        self._depleted.add(effect_name)63        self._depleted.add(effect_name)
64        return times_decorator(self._effects[effect_name], self._intensities[effect_name])64        return times_decorator(self._effects[effect_name], self._intensities[effect_name])
6565
66    @is_merged_decorator66    @is_merged_decorator
67    def __add__(self, other):67    def __add__(self, other):
68        if not isinstance(other, Potion):68        if not isinstance(other, Potion):
69            raise TypeError('Potions can only be combined with other potions')69            raise TypeError('Potions can only be combined with other potions')
7070
71        effects = {**self._effects, **other._effects}71        effects = {**self._effects, **other._effects}
72        duration = max(self._duration, other._duration)72        duration = max(self._duration, other._duration)
73        intensities = {73        intensities = {
74            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))74            effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
75            for effect in effects75            for effect in effects
76        }76        }
77        return Potion(effects, duration, intensities)77        return Potion(effects, duration, intensities)
7878
79    @is_merged_decorator79    @is_merged_decorator
80    def __sub__(self, other):80    def __sub__(self, other):
81        if not isinstance(other, Potion):81        if not isinstance(other, Potion):
82            raise TypeError('Potions can only be combined with other potions')82            raise TypeError('Potions can only be combined with other potions')
8383
t84        if len(other._effects.keys() - self._effects.keys()):t84        if other._effects.keys() - self._effects.keys() != set():
85            raise TypeError('All effects of the right potion must be present in the left potion')85            raise TypeError('All effects of the right potion must be present in the left potion')
8686
87        duration = self._duration87        duration = self._duration
88        effects = {}88        effects = {}
89        intensities = {}89        intensities = {}
90        for effect in self._effects:90        for effect in self._effects:
91            if effect in other._effects:91            if effect in other._effects:
92                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)92                new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
93                if new_effect_intensity > 0:93                if new_effect_intensity > 0:
94                    effects[effect] = self._effects[effect]94                    effects[effect] = self._effects[effect]
95                    intensities[effect] = new_effect_intensity95                    intensities[effect] = new_effect_intensity
96            else:96            else:
97                effects[effect] = self._effects[effect]97                effects[effect] = self._effects[effect]
98                intensities[effect] = self._intensities[effect]98                intensities[effect] = self._intensities[effect]
9999
100        return Potion(effects, duration, intensities)100        return Potion(effects, duration, intensities)
101101
102    @is_merged_decorator102    @is_merged_decorator
103    def __mul__(self, intensity_growth):103    def __mul__(self, intensity_growth):
104        try:104        try:
105            float(intensity_growth)105            float(intensity_growth)
106        except ValueError:106        except ValueError:
107            raise TypeError('Potions can only be multiplied by integers')107            raise TypeError('Potions can only be multiplied by integers')
108108
109        effects = {**self._effects}109        effects = {**self._effects}
110        duration = self._duration110        duration = self._duration
111        intensities = {111        intensities = {
112            effect: round(self._intensities.get(effect, 0) * intensity_growth)112            effect: round(self._intensities.get(effect, 0) * intensity_growth)
113            for effect in effects113            for effect in effects
114        }114        }
115        return Potion(effects, duration, intensities)115        return Potion(effects, duration, intensities)
116116
117    @is_merged_decorator117    @is_merged_decorator
118    def __truediv__(self, new_potions_count):118    def __truediv__(self, new_potions_count):
119        try:119        try:
120            int(new_potions_count)120            int(new_potions_count)
121        except ValueError:121        except ValueError:
122            raise TypeError('Potions can only be divided by integers')122            raise TypeError('Potions can only be divided by integers')
123123
124        return tuple([Potion({**self._effects},124        return tuple([Potion({**self._effects},
125                             self._duration,125                             self._duration,
126                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)126                             {effect: round(self._intensities.get(effect, 0) / new_potions_count)
127                              for effect in self._intensities}127                              for effect in self._intensities}
128                             ) for _ in range(new_potions_count)])128                             ) for _ in range(new_potions_count)])
129129
130    def __eq__(self, other):130    def __eq__(self, other):
131        if not isinstance(other, Potion):131        if not isinstance(other, Potion):
132            return False132            return False
133133
134        return self._effects.keys() - other._effects.keys() == set() and all(134        return self._effects.keys() - other._effects.keys() == set() and all(
135            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]135            [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
136        )136        )
137137
138    def __lt__(self, other):138    def __lt__(self, other):
139        if not isinstance(other, Potion):139        if not isinstance(other, Potion):
140            return False140            return False
141141
142        return sum(self._intensities.values()) < sum(other._intensities.values())142        return sum(self._intensities.values()) < sum(other._intensities.values())
143143
144    def __gt__(self, other):144    def __gt__(self, other):
145        if not isinstance(other, Potion):145        if not isinstance(other, Potion):
146            return False146            return False
147147
148        return not self == other and not self < other148        return not self == other and not self < other
149149
150150
151class ГоспожатаПоХимия:151class ГоспожатаПоХимия:
152    def __init__(self):152    def __init__(self):
153        self._target = None153        self._target = None
154        self._target_public_attributes = None154        self._target_public_attributes = None
155        self._current_tick_duration = None155        self._current_tick_duration = None
156156
157    def _sort_effects_by_molecule_mass(self, effects):157    def _sort_effects_by_molecule_mass(self, effects):
158        new_effects = list(effects)[:]158        new_effects = list(effects)[:]
159        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)159        return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
160160
161    def _get_public_attributes(self, target):161    def _get_public_attributes(self, target):
162        all_attributes = vars(target)162        all_attributes = vars(target)
163        return {key: value for key, value in all_attributes.items() if163        return {key: value for key, value in all_attributes.items() if
164                not key.startswith('_')}164                not key.startswith('_')}
165165
166    def apply(self, target, potion):166    def apply(self, target, potion):
167        self._target_public_attributes = self._get_public_attributes(target)167        self._target_public_attributes = self._get_public_attributes(target)
168        self._target = target168        self._target = target
169        self._current_tick_duration = potion.duration169        self._current_tick_duration = potion.duration
170170
171        for effect in self._sort_effects_by_molecule_mass(potion.effects):171        for effect in self._sort_effects_by_molecule_mass(potion.effects):
172            potion[effect](target)172            potion[effect](target)
173173
174        if set(potion.depleted) == set(potion.effects):174        if set(potion.depleted) == set(potion.effects):
175            potion.deplete('Potion is depleted.')175            potion.deplete('Potion is depleted.')
176176
177    def tick(self):177    def tick(self):
178        if not self._current_tick_duration:178        if not self._current_tick_duration:
179            raise Exception('No potion applied')179            raise Exception('No potion applied')
180180
181        self._current_tick_duration -= 1181        self._current_tick_duration -= 1
182182
183        if self._current_tick_duration != 0:183        if self._current_tick_duration != 0:
184            return184            return
185185
186        current_public_attributes = self._get_public_attributes(self._target)186        current_public_attributes = self._get_public_attributes(self._target)
187        for key in current_public_attributes:187        for key in current_public_attributes:
188            if key in self._target_public_attributes:188            if key in self._target_public_attributes:
189                setattr(self._target, key, self._target_public_attributes[key])189                setattr(self._target, key, self._target_public_attributes[key])
190            else:190            else:
191                del self._target.__dict__[key]191                del self._target.__dict__[key]
192192
193193
Legends
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op