{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Перегрузка специальных методов и операторов\n", "\n", "Перегрузка специальных методов позволяет достичь многих аспектов полиморфизма." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Приведение к строке. `__str__` и `__repr__`\n", "\n", "Если попробовать распечатать экземпляр пользовательского класса, то вы получите нечто не очень содержательное. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Quaternion object at 0x00000228CEFE1FD0>\n" ] } ], "source": [ "from numbers import Number\n", "\n", "class Quaternion(Number):\n", " def __init__(self, a, b, c, d):\n", " self.a = a\n", " self.b = b\n", " self.c = c\n", " self.d = d\n", "\n", "q = Quaternion(1, 2, 3, 4)\n", "print(q)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При попытке распечатать экземпляр класса `Quaternion` мы получили сообщение, из которого никак нельзя узнать значение атрибутов объекта. \n", "\n", "Этот класс в примере реализует тип [кватернионов](https://ru.wikipedia.org/wiki/%D0%9A%D0%B2%D0%B0%D1%82%D0%B5%D1%80%D0%BD%D0%B8%D0%BE%D0%BD). Кватернион $q$ можно задать четырьмя действительными числами $a$, $b$, $c$ и $d$. Тогда он определяется как сумма\n", "\n", "$$\n", " q = a + b i + c j + d k.\n", "$$\n", "\n", "Сделаем так, чтобы функцией `print` на экран выводилось представление кватерниона в такой форме. Для это необходимо объявить специальный метод [\\_\\_str\\_\\_](https://docs.python.org/3/reference/datamodel.html#object.__str__), который отвечает за приведение объекта к строке (встроенная функция [str](https://docs.python.org/3/library/stdtypes.html#str) сначала пытается вызвать этот метод)." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 + 2i + 3j + 4k\n" ] } ], "source": [ "class Quaternion(Number):\n", " def __init__(self, a, b, c, d):\n", " self.a = a\n", " self.b = b\n", " self.c = c\n", " self.d = d\n", "\n", " def __str__(self):\n", " return f\"{self.a} + {self.b}i + {self.c}j + {self.d}k\"\n", "\n", "q = Quaternion(1, 2, 3, 4)\n", "print(q)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Есть также похожий метод [\\_\\_repr\\_\\_](https://docs.python.org/3/reference/datamodel.html#object.__repr__) (и соответствующая встроенная функция [repr](https://docs.python.org/3/library/functions.html#repr)), который тоже должен возвращать строку. Разница между `__str__` и `__repr__` примерное следующая. \n", "\n", "- Метод `__str__` должен возвращать читабельную для человека строку. Такой метод вызывается функцией `print`. \n", "- Метод `__repr__` должен возвращать строку, из которой можно было бы восстановить объект. Такой метод может быть полезен для отладки.\n", "\n", "Для примера реализуем метод `__repr__`, вычисление результата которого воссоздаст исходный объект." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Quaternion(a=1, b=2, c=3, d=4)\n", "1 + 2i + 3j + 4k\n" ] } ], "source": [ "class Quaternion(Number):\n", " def __init__(self, a, b, c, d):\n", " self.a = a\n", " self.b = b\n", " self.c = c\n", " self.d = d\n", "\n", " def __str__(self):\n", " return f\"{self.a} + {self.b}i + {self.c}j + {self.d}k\"\n", " \n", " def __repr__(self):\n", " return f\"Quaternion(a={self.a}, b={self.b}, c={self.c}, d={self.d})\"\n", "\n", "q = Quaternion(1, 2, 3, 4)\n", "representation = repr(q)\n", "print(representation)\n", "\n", "q_copy = eval(representation)\n", "print(q_copy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(call_obj)=\n", "## Вызов объекта. `__call__`\n", "\n", "Определив специальный метод `__call__`, можно имитировать функции. \n", "\n", "В качестве примера реализуем класс, который позволит вычислять композицию функций одной переменной. Напомним, что композицией двух функций $f:\\mathbb{R} \\to \\mathbb{R}$ и $g:\\mathbb{r} \\to \\mathbb{R}$ называется функция $h = f \\circ g:\\mathbb{R} \\to \\mathbb{R}$, значение которой $h(x) = f(g(x))$ для $\\forall x \\in \\mathbb{R}$. \n", "\n", "Реализация такого класса может выглядеть как-нибудь вот так." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.0\n" ] } ], "source": [ "from collections.abc import Callable\n", "import math\n", "\n", "class Composition(Callable):\n", " def __init__(self, f, g):\n", " self.f = f\n", " self.g = g\n", "\n", " def __call__(self, x):\n", " return self.f(self.g(x))\n", "\n", "f = abs\n", "g = math.sin\n", "h = Composition(f, g)\n", "print(h(-math.pi / 2.))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Конструктор класса `Composition` принимает на вход две функции (или вызываемых объекта), запоминает их в атрибуты `f` и `g`. При вызове объекта (пара круглых скобок после имени объекта), вызывается метод `__call__`, который в данном случае возвращает результат вычисления `f(g(x))`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAGDCAYAAABN1ObNAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAABqF0lEQVR4nO3dd3yV5f3/8dd1skMWmZAwwshggywDhITpKCpqadWqVWu1WltHtda2P6vV2rpt1davtlpr1VrcAxEJZABhyl4hQBgJZBCyx0lyrt8fJ6EIARJykuuMz/PxyCNw3/c5552TnHM+930tpbVGCCGEEEK4NovpAEIIIYQQouukqBNCCCGEcANS1AkhhBBCuAEp6oQQQggh3IAUdUIIIYQQbkCKOiGEEEIIN+BtOoAziIyM1PHx8aZjCCGEEEKc04YNG8q01lGnbpeiDoiPj2f9+vWmYwghhBBCnJNS6kB726X5VQghhBDCDUhRJ4QQQgjhBqSoE0IIIYRwA1LUCSGEEEK4ASnqhBBCCCHcgBR1QgghhBBuQIo6IYQQQgg3YHSeOqXUQ8AFwHhgEHBAax1/HvdzI3AvkAxUAZ8BD2mtSx2XVgjRVQ0NDZSWltLQ0EBzc7PpOMJBfHx8iI6OJiQkxHQUITya6cmHnwDKgW+AsPO5A6XUvcBzQBZwN9APuA9IUUpN0lrXOiaqEKIrKisrKS4uJioqij59+uDt7Y1SynQs0UVaa+rr6yksLASQwk4Ig0wXdUO01vsAlFLbgKDO3FgpFQk8DqwDZmmtW1q3rwM+xV7kPeHQxEKI81JWVka/fv0IDAw0HUU4kFKKwMBA4uLiKCoqkqJOCIOM9qlrK+i6YD4QCLzYVtC13u9nwD7g+i7evxCdl5sLf/yj/bs4wWq1EhAQYDqG6CYBAQE0NTWZjuFc5L1A9DBXHygxsfV7e6+Y1UCyUqpTV/+E6JLcXMq/M59Vr/6XvVffQH3OKtOJnIo0t7ov+d3+T4tNc3TZSjb84A52/vnv6FmzpLATPcJ082tXxbZ+L2xnXyGgWo/JO3WnUuo24DaAAQMGdFc+4SHqrM18vaOYT78oIOtHr9Ls1frS+uI44ZlfExcWQGyYP7FhAa3/tn/127udyNxsSE+HlBSjP4MQomMaVqziUOZqCkeOp6jvIIoq6imqqKew9etoZQPNNg3f+wMAQ44d4orFO7gicTQDI3oZTi/cmasXdW2dcxrb2ddwyjHforV+FXgVYMKECdrx0YS7a2qxsWJPGR9vKmTJ9mLqm1ro6x/Gj9Z8RMr+jRwPDqfw7gcpDImiqKKefaW15Owpo87a8q37mZ1fz8N/vpYBH70rhZ0QTszabOOf72byl43HqPFNgtU1wFa8LIo+If7EhvkzfmBv+4lb+RHiHvstRQFhfDo8jecaR/Dc05mM7R/GFWNjmTc6lqhgP9M/knAzrl7U1bV+9wPqT9nnf8oxQnRNbi625Zl8M3oqnzSH88XWI5TXWgkN8GH+uDjmj41lYnw4ljVhkDmo3atvWmuq6psprKin6O//YsvSNfx9whXMvv55blu8kzvHTyTQ19VfluKf//wnN998M8uXLyc9Pf2cx2utmTJlCoMHD+btt9/u9ON98sknLFiwgO3bt5OQkHAeicW5ZO4u4fef7WBfWT0zD23niu3Lias5RuztNxH9q/vw9jq1N1MyDHoGMjP5QXoqRcPG8tnmIj7ZVMSjn+3gsc93MHVoJFeMjeOi2oMEr8ySK/aiy1z906Oo9XsckH/KvjhAn3SMEOfNtmoV/3jgBf45+mIKq6rx96pmzshYrhgTy/TEKHy9T3pDT0k54xuzUorQQB9CA30YPmc8s//0S67b8hV/Sr+Zl5Kn88GzWTx06TAuG91X+ih5kHfffZf169fzr3/967xuf8UVVzBq1CgefPBBPvzwQwen82wFZbU8/sUOlu4sYVBkL964MIgZf3kKrFbw9YWZU+G0gq7VSe8FscDtaUO4PW0Ie4qr+WRTEZ9sLuT+hZv5TbOVOfmV/Pa5BfT5dKEUduK8uXpRtw57v7gUTi/qLgR2a61rejyVcCu1jc38YlEBi1N/yJSCzdy/4m3mXDuXoGu/07U7TkmBjAz6ZGbyQno61/dN4nefbufn727k37kHeOTyEQyPlekhPMHvf/975s2b16WrbHfffTc//OEP2b59OyNGjHBgOs9U29jMy8vz+XvOfny8FA9dkszNUwfZT+AyMiAz87yvrCXEBHP/RUn8Ym4iGx//C5/k7GLhyFms7TeCV5au5QIp6sR5cpnRr0qpAUqpZKWUz0mbP8He7HqXUsrrpGMvAwYDnW/HEOIkh8rruPpvq1jSHMpvs//J2+8/zJV7cwmaMd0xD5CSAg89BCkpTIgP59O7pvHHq0aRX1rDvBdz+H8fb+N4rdUxjyWcUkZGBrt37+bGG2/s0v1cddVVBAYG8sorrzgomWfSWvPJpkJmPpvJXzP3Mm9MX5bfn87taUP+d0X+pNdtVyiluGD2JB5d8SYfvfNL/FqauKZ+KO9vOOyAn0R4IqNFnVLqBqXUb5VSvwWigNC2/yulbjjl8H8BO7E3qwLQugzY/wMmAUuVUrcppR4F3gV2AS/0xM8h3FPu3mNc/tIKiirqeeOWSdz67H2oxx6zn6V305m0l0Vx7aQBLP9FOjemxPPO2oPMeDaTt95ZTssTMt+Vq7HZbDzzzDMMGTIEPz8/EhMTefPNN791zMKFC/Hy8mLu3Lnf2v7Xv/4VpRSPPfbYt7YXFRURFRXFsGHDqK3934I5QUFBpKam8v7773ffD+TOcnPZ/vjzfO/pJdz9n01EB/vzwR1TeO57Y4kO8T/37c9X6xX7pLt/zKfXJDFhUAT3L9zM45/voLnF1n2PK9yT1trYF5CJvd9be1+ZZzg2vp37uQnYjH3EawnwOhDd0Rzjx4/XQrSx2Wz6X6v26yEPfaFnPrNc7yutMZZl55FKfc3Ti/XABz/X37vuj7o2OEzrVauM5emKHTt2mI7QY9544w0N6MmTJ+vRo0frJ598Ur/wwgs6KSlJA3rFihUnjk1OTtajR49u936uuuoq7eXlpXNycrTWWre0tOj09HTt5+enN23adNrxjzzyiAb0zp07u+cHOweX/R2vWqX/PfEyPeiBT/QFP3tb/+fd5bqlxWYkirW5Rf/uk2164IOf6+v/vlpX1FqN5BDODViv26lnTK8oka61Vmf4Sj/DsQXt3M8/tdZjtNb+WutorfUtWuuSnvo5hPuwNtv49Ufb+H+fbGd6YhQf/XQqgyLNzSuV3CeEd6zf8NTiv7Aubji3X/oLGpZnGcvj1Jxw9v7GxkbWrVvHL3/5S+6++24yMjLw9fXlpZdeAqClpYW8vDyGDBnS7u3//ve/069fP6677jqOHz/OH/7wBzIzM3nmmWcYM2bMace33c/27du774dyQx8s2cRvZt5O2v5vWPaPO/j+/lwsFjMDlXy8LDxy+Qj+dNUoVu87xhUvryC/pNpIFuF6XH2ghBAOU1bTyJ3//oa1BeXckT6E++cm4WXojf1kakY633v8MZTWPHDJ3dwV6MPfWmz4nGnEnSfKzYVZs/43IrEbm8g7484778TX1/fE/+Pi4khMTGTPnj0AHDt2DJvNRnh4eLu37927N++88w5paWlccsklrF+/nssvv5y77rqr3eMjIiIAKCmRc9qO+mLLER6oH8DUg1v426dP4u+l7AMgDLtm0gCGRAdxx783MP/lVfzl2rHMTI4xHUs4OflUEALYXlTJFS+tZPPhCv58zVgevDjZKQo64ESfmwXXzOCx0YEsPdrEve9tosUmc2afkJlpL+haWuzfMzNNJwJg8ODBp22LiIjg2LFjwP+W1rK3prRvypQpPPjgg6xZs4aoqChef/31Mx7bdj8yHU7HZOws5u7/bOSCgb157Y40/B952GlOCAAmxofzyV3TGBgRyI/eXM9fM/PP+rcihFypEx7vi/ezuH99FaH+Xiz8yRRG9wszHel0rfNd3QDU99vLE4t24e/jxVNXjzbWTORU0tPtV+jartQ5wZUWAC8vr3a3t30wR0REYLFYKC8vP+N9WK1WvvrqKwDKy8s5ePDgiStyp2q7n6ioqK7E9ggr9pRxx9vfMDw2hNdvnkigvw+kTjEd6zRxYQG8/5MpPPD+Zp5avJtdW/fzZPUGAmakOU3xKZyHXKkTHm3xB1n8dH0Nwwrz+PSlWxl9aKfpSOd02/Qh3DM7gfc3HOZ3n26XM3c4cTWTbh6d7GgWi4Vhw4adaI5tz0MPPcT69et56qmnCAkJ4ZprrvnWqNeT5efbp+scOXJkt+R1F+sKyvnxv9YzKKIXb948iRB/n3PfyKAAXy9evHYcDwwL4LPDDdy1sR7brNlO1X9UOAcp6oTHyi+p4f51lYw5ksc77z5EdEWJ0zTbncvdsxK4ffpg3lp9gD9+uUsKO3DY3GE9LT09nZ07d1JVVXXavi+//JLnn3+eH/7whzzwwAO88cYb5OXlnbFP3erVq4mJiSEpKam7Y7usLYcruPmNdfQN9efft06mdy/fc9/ICSil+GnRGh5Z9hoZQybx4vj5LvN+JXqOFHXCI9U0NnP7W+vx8/Xmb18+hz82p2q2OxelFL+6JJkbUwbyavY+Xlh65is9wrktWLAAm83G4sWLv7X9yJEj/PCHPyQhIeHEaNl58+Zx9913889//pP//Oc/3zq+pqaGnJwcFixY0GPZXc2uo1Xc+PpawgJ9ePvHk4kK9jMdqXPS07lx+1Ku2r6MF6Zcw7Jk52suFmZJUSc8jtaa+/+7mYJjdbz4w0nEfvJfl2u2A3th98hlI/ju+H78OWMP/5e113QkcR7S0tIYPnw4b7311oltNpuNG264gaqqKt577z2CgoJO7HvqqacYN24ct99+O/v37z+x/YMPPqCuro7bb7+9R/O7ir2lNVz/9zX4e3vxzq0X0jc0wHSkzktJQWVk8ERKFMPCfLhncwMFZe03xQvPpKTZBiZMmKDXr19vOoboIX/L3MuTi3fx2+8M49bU00cnupoWm+bu/2zk8y1H+P0VI7gxJd50pHbt3LmTYcOGmY7hlP7zn/9w/fXXs3379vNuOr3ggguIj4/nww8/dHC6jnPW3/Gh8joWvJJLU4uN925PYWh00Llv5OQOlddx2Usr6BPiz4d3TiHQV8Y9ehKl1Aat9YRTt8uVOuFRVuwp4+mvdjFvdF9+NG2Q6TgO4WVRPP/9scweFsPDn2znv4++Ih2oXcw111zDxIkTefTRR8/r9h9//DHbtm3jySefdHAy13dk2Uque3ox9fWN/PvWyW5R0AH0Dw/kL9eMY3dxNb/6YKv0qxWAFHXCgxw+XsfP3v2GodFBPHn1aLeay8vHy8JLQ6ykHtzMr2pj2fCDO6SwczG5ubm8884753Xb+fPnY7VaSUhIcHAq19a0chW3vbWB4w0t/OutBxlW4F4rbUxPjOL+uUl8urmI11cWmI4jnIAUdcIjNDS18JN/b6C5RfN/N0ygl5/7NVX452Txt4/+SN/qMh6YfacsJyY83itLdrI1ZghPfflnxhze6ZajRe9IG8Lc4TE8sWgnq/cdMx1HGCZFnXB7Wmt++/E2thVW8fz3xxpdy7VbpacTpFp46quX2Bcex9NRp3W3EMJj7Ciq4i/WPly2ewWX5q92qdHtnWGxKJ793hgGhgdy1zvfcKSy3nQkYZAUdcLtvb3mIO9vOMzPZyUwe7gbr53YOgHv1B8v4IZ4P17f18ja/WdeqUAId2VttvGLhZsJ7eXH7++Z55Kj2zsj2N+H/7thPPXWFu749zc0NreYjiQMkaJOuLUNB47z6GfbmZEUxT2zPKC/UesEvL+6OZ3+vQN54P3N1FmbTacSoke9tDyfnUeqeOLKkfROm+qSk1J3VkJMMM8sGMOmQxU8+tkO03GEIVLUCbdVUt3AnW9voG9oAC98f5xHrZHay8+bp787mgPH6nhq8W7TcYToMdsKK3l5eT5XjYtj7og+puP0qEtG9eUnaUN4Z81B/rvukOk4wgAp6oRbalq5irv+9AmVtY383w3jCQ107rUdu8PkwRHcPDWef64qYNXeMtNxhOh2jc0t3PffTUT08uV3l40wHceI++cmMm1oJL/9aAtbHntBRsF7GCnqhPvJzeXF377G2pYgnvziz243jUFn/PKiZOIjAvnl+1uoaZRmWOHe/rx0D3nFNTx59WiPPJED8Pay8JehzURVlHJnUQj1F10ihZ0HkaJOuJ0DGat4ZfwVzN++nCu2LXPLaQw6KsDXi2cWjKGwop4/LtppOo4Q3WbToQpeydrL9yb0Y0ZytOk4RoXnZvPsouc5HBrDK2PnefR7oKeRok64nceCRuFja+ah7H+57TQGnTEhPpxbpw3i7TUHydlTajqOEA7X0NTCL/67iZgQf347b7jpOOalp3NhyR4u25nNK5Ou5tCkVNOJRA+Rok64leW7S1h6tImfjQ0n5lf3uvU0Bp3xi7lJDInqxYPvb6G6ocl0HCEc6vmv89hbWsuTV48mxN8zm12/pXV6o19PisLi58vjRf6mE4keIkWdcBvWZhu//2wHgyN7cct16R4xjUFH+fvYm2GPVjXw+OfSDNsT9u/fz/z584mKikIpxU033QRAQ0MD8fHx/OY3v+nyYzz44IMMGjQIq9Xa6dsePXqUwMBA3nzzzU7tczYbDpTzas4+rps8gOmJUabjOI+UFPr+5n7ump3EV9uL5Sq9h5CiTriN11fuZ39ZLQ9fNhxfb/nTPtW4Ab25PW0I760/xPLdJabjuL2bbrqJrKwsHnzwQd566y1uv/12AJ577jkqKiq4//77u3T/+/fv589//jMPP/wwvr6+nb59nz59+MlPfsJvfvMb6urqOrzPmdRbW7h/4RZiQwP49aXDTMdxSremDmJgRCCPfraDphab6Tiim8knn3ALxVUNvJixh9nDYkhP8uxO0mdzz+wEEmOC+NUHW6isk2bY7tLY2EhOTg433HAD999/P9dffz0pKSnU19fz9NNPc/PNN9O7d+8uPcaf/vQnQkJCuP7668/7Pn7+859TVFTEG2+80al9zuLpr3azv6yWp787miA3XM/ZEfy8vXh43nDyS2p4c1WB6Tiim0lRJ9zCHxftpMmm+X/z5Gz9bPy8vXh2wVjKaqz8/o1M+OMfZbqDblBcXIzWmvDw8G9tf+edd6ioqODGG2/s0v1XVVXx9ttvc+211+Ljc/59yOLj40lNTeX//u//OrXPGaz5LJs3Vu7jxkF+TBkaaTqOU5uZHE16UhQvLN1DSXWD6TiiG0lRJ1zeuoJyPt5UxG2pgxkY0ct0HKc3ql8oPx3qxweHrCx//WOYNUsKOwe66aabGDhwIACPPvooSimUUmRmZrJw4UL69OnDuHHjTrtdXV0d9913H3379iUgIIALL7yQjIwMbrrpJpT69mooixYtora2lksvvfRb2zdu3Ii/vz9z5sxBa31ie0tLC9OnTycwMJBt27Z96zaXXHIJW7duZdeuXadlOts+kxpWrOKBRXvoX3GUXz2wQP5+z0EpxcPzhtPY3MKTX8oKM+5Mijrh0lpsmt99sp2+of7cOWOI6Tgu466j6xhcXsgfp/+QlqZmmcfKgW6//Xaef/55AK688kreeust3nrrLYYNG8bKlSuZNGlSu7dbsGABzz//PBdeeCHPPPMM06ZN48orr2Tjxo2nHZuVlQXAxIkTv7V93LhxPPXUUyxdupQ//elPJ7Y/+uij5OTk8PzzzzNy5Mhv3SaldTBRZjt/A2fbZ9K/l27nYGgfnlj8EoF11fL32wGDo4L40bTBfPDNYb45eNx0HNFNpBOCcGnvrj3IjiNVvHjtOAJ95c+5o3xnpHHfT37LXZfcxyejZnKVh8/l50gpKSn07duXe++9l9GjR5/o87Z//35qamoYMuT0k49FixaxaNEibr31Vl577bUT22fOnMl3vvOd047fsWMHvXv3Pq15F+x94TIyMnj44YeZMWMGDQ0N/OEPf+Dqq68+MVjjZG15tm8/feWVs+0zpaaxmb/a4ph2cDPTDm+TuSg74a6ZQ/nwm8M88ul2Pr5zqketh+0p5FNQuKzjtVaeWbKbCweHM290X9NxXEtKCpf+7TFGfF7E8/PvZt7EyXR+/KRjPPrZdnYUVRl69PYNjw1x+NqhpaX2KSXaK8Q+++wzAO67775vbb/00ksZNmwYO3d+exqa0tLSdu+nzRtvvMGYMWO49tpraWpqon///vz9739v99iIiAgASkpOHxF9tn2m/CNnP+VWzQPXT4NRj9kLOpm6qEOC/Lz59aXDuOe9Tfx3/SGumTTAdCThYNL8KlzWs1/vpqq+iUcuH3FanyNxbpYpU7j/h9M5VGfjvXUHTcdxe21/oyf3dWuzf/9+LBYLQ4cOPW1fUlJSu/fV3v20CQ8P54033qCgoIDCwkLefvttwsLC2j227X7aew2dbZ8Jx2utvJazj4tGxDDm0lSZi/I8XDE2lgkDe/PUV7tlBLwbkit1wiVtL6rknTUHueHCgST3CTEdx2WlJ0YxKT6cvyzL57vj+xPg69XjGRx9RcxZRUXZJ8YtLy8/4zEdLZ6ioqLYvHnzWY/59NNPT/x706ZNTJ06td3j2vK05evoPhP+lrWXWmsz9889vdAVHaOU4pHLR3DZSyt4fmkej1zuGa8/TyFX6oTL0Vrz6Kc7CAv05b458ubeFUopHrg4idLqRv4pc1h1q/79+xMSEsKePXtO2xcfH4/NZmt33+7dp49WHDlyJBUVFZSVlbX7WJ9++ikvvvgiN998M6mpqdx///1s3bq13WPz8/NP3Gdn9vW0o5UNvLmqgCvHxZEQE2w6jksbGRfKdZMG8NbqA+w+Wm06jnAgKeqEy/l0cxFrC8p54KIkQgNlnceumhgfzoykKF7J2ktlvTTHdBcvLy9SU1NZs2bNafsuu+wygBOjZtssWrTotP50AOmtAwNWr1592r7CwkJuueUWhg0bxksvvcTbb79NQEAA11xzDfX19acd33YfaWlpndrX0/6ybA82rbl3dqLpKG7h/rlJBPt787tPt521KV+4FinqhEupzVnFE++tY1SoF9+b0N90HLfxi7lJVNY38Vr2PtNR3NqCBQsoKytj7dq139p+6aWXctFFF/Haa69x1VVX8de//pUHHniAa6+9ltGjR592PxdffDHBwcEsWrToW9ttNhs/+MEPqK2t5b333iMwMJD+/fvzj3/8gx07dnDPPfecdl+LFi1i1KhRJCcnd2pfTyooq+W/6w5x7aQB9A8PNJrFXfTu5csv5iaxel85X3yQJRORuwkp6oTryM3lpUdfp1j78Mhrv8JrzelXKcT5GRkXyrzRfXl95X5KqxtNx3Fb3//+9wkPD+ett9761nalFB988AF33303K1eu5Be/+AXZ2dl89NFHJCQkEBAQ8K3jg4KCuP7663nvvfewWq0ntj/++ONkZWXx3HPPMWrUqBPbr7zySu68805effVVPvjggxPbCwoKWLFiRbtTnZxtX097fmke3l6Ku2aePpBEnL/rJg1gWIgXf1h+gLrfPy4TkbsBKeqEyyhdtoLXx1zKlduWMf7gNplw1MHum5NIY7ONl5fnm47i8uLj49Fa88gjj3xru7+/Pw888AD/+te/Thsw0atXL1544QWKi4upr69nzZo1zJw5k927dzNgwOlTT/zyl7+ktraWf/3rXye2Pfzww2itueOOO047/uWXX0ZrzdVXX31i25///GdiY2O5+eabTzv+bPt60s4jVXy6uYibpw4iOtjfaBZ342VRPNKSx5HgSN4edRFYrfK+6uKkqBMu4/U+F2D19uFnaxbKhKPdYHBUEAvG9+OdNQc5fLzOdBy3dc8999C7d2+eeeaZb21vr7/bF198wbZt25gzZ85p++Lj47nnnnt4/PHHv3W1rqOOHDnCK6+8wh/+8AcCAwM7vK+nPbtkN0F+3vxkuqwY0x0mz55IyqFtvDbpShoDAuV91cUp6SAJEyZM0OvXrzcdQ5xFZX0TU/+0jLQICy/XrpcJR7tJUUU96c9kcsWYWJ5eMMah971z506GDRvm0Pt0Jw899BAbN25kxowZhIaGsmnTJl5//XVCQkLYtGkT/fr1Mx3xnBz9O95w4DhX/20VD1yUxE9nSNNrd1nxSTbX51bzxJhArrt2huk4ogOUUhu01hNO3S7z1AmX8FZuATWNzdz53WkQe/pVC+EYsWEB3HDhQN5YuZ/b04YwNDrIdCSPkZqaysqVK3n66aeprKwkPDycq6++mscee8wlCjpH01rz9Fe7iAzy5eap8abjuLWpl6cy5tBKXjnUxPdabHh7SSOeq5LfnHB6ddZmXl9ZwIykKEbEhpqO4/buTB9CgI8Xz319+vxoovtceumlZGdnU1ZWRlNTE8XFxbz77rvtrjLhCVbkl7F6Xzl3zRgq6zp3M6UUd84YysHyOr7YesR0HNEFUtQJp/eftYcor7VK80sPiQjy40epg1m09ShbD1eajiM8kP0q3W7iwgK4drKsT9oT5gyLISE6iL8u34vNJt2yXJUUdcKpWZttvJazj0mDwpkQf+YFzIVj3Zo6iLBAH55eIlfrRM/7avtRthyu5J7ZCfh59/zSdZ7IYlHcOWMIu4urydhVYjqOOE9S1Amn9vHGQo5UNnBnuox860kh/j7cmT6E7LxSVu875rD7lYFZ7stRv9sWm+aZJXkMjQ7iqgs8ry+hSZeNjqVf7wBeXp4vr1UXJUWdcFotNs3fsvYyIjaEtETnWFDck9yYEk9MiB9Pf7XbIW/wXl5eNDXJMmTuqrm5GW/vrvd9+2hjIfklNfxiTiJeFuWAZKKjvL0s/CRtCJsOVZDrwJM50XOkqBNO68ttR9hfVstPZwxFKXlz72n+Pl78fFYCGw4cZ/ljL3d5pvng4GCqqqoclE44m+rqavz9uzY5cOOKVTz/vn0ZwItH9nFQMtEZ3x3fj6hgP/66fK/pKOI8SFEnnJLWmpeX72VwVC8uGiFv7qZ8r7mQgRVHeW5fC7qLSwiFh4dz/PhxysrKsFqt0rzjJrTW1NXVUVZWRlRUF66o5+by/n1/olD78sCbj6BWyzKAJvj7eHHrtEGsyC9j06EK03FEJxkdJ66UsgB3A7cD8UAp8F/gYa11bQduHwT8HLi29faNQB7wKvCmlk8Nl5W5u5SdR6p4+rujpQnGIJ/sLG5fm8Ov5/6UtdFDmZyZed6TPvv5+TFgwADKy8spKCigpaXFsWGFMX5+fsTExHTpSp1teSavj7mU0UfySM1fb1+uSiYYN+IHFw7k5eX5/HV5Pq/eeNr8tsKJmZ7853nsRdlHwLPAsNb/j1NKzdZa2850w9aC8EtgCvAm8CIQiL3Ae6P1vh7s1vSi27y8PJ+4sADmj4szHcWzpadz5R+f5KnUG3h9wnwmd3EJIT8/P/r27Uvfvn0dk0+4jezhU9hbVcMLXzyHkmUAjQry8+amqYP4S8Ye8oqrSYwJNh1JdJCx5lel1AjgZ8CHWuurtNavaa3vA+4DZgDXnOMuJgPTgL9orW/RWr+qtX4BSAX2Y7/6J1zQ2v3lrD9wnNumD8ZHZjY3KyWFgK++5LqwBr4eOplDSY5dOkyINm+UBxDtp7j02jmQkSFX6Qy7eUo8gb5evJIpfetciclPzGsBBbxwyvbXgDrg+nPcPqT1e9HJG7XWVqAMOGfzrXBOLy/PJ6KXL9+b0N90FAGQksIN912DxaJ4c1WB6TTCDeWXVJOVV8oN0xPw/fWvpKBzAr17+XLdpAF8srmIQ+V1puOIDjJZ1E0EbMDakzdqrRuATa37z2YtUAH8Uim1QCk1QCmVrJT6IzAeeMTRgUX321ZYSVZeKbdMG0SAr0w66iz6hgZwyai+vLfuEDWNzabjCDfzxsoCfL0tXCerRziVW1MH46UU/5ctV+tchcmiLhYo01o3trOvEIhUSvme6cZa6+PA5UA59sEVB4CdwE+Bq7XWrzk+suhuf83MJ9jPmxtSBpqOIk5xy9R4qhubeX/9IdNRhBupqLPywTeHuXJsHBFBfqbjiJP0CfXn6vH9+O/6w5RUNZiOIzrAZFEXiH20ansaTjrmbGqAbcAzwFXArUA+8I5Sas7ZbqiUuk0ptV4ptb60tLTjqUW3yS+p4cttR7lxykBC/H1MxxGnGDegN+MGhPHPVQWyNqRwmHfXHqKhycbN0+JNRxHt+EnaYJpbbPxjxX7TUUQHmCzq6oAznZb5n3RMu5RSo4BVwNda6we01h9prf+BffDEUeA1pdQZ2+9aB1ZM0FpP6NLcSsJhXsnai5+3hZunDjIdRZzBLVMHUXCsjuW7ZW1I0XVNLTb+lVvA1KERJPcJOfcNRI8bGNGLy8bE8u/VB6iskxVhnJ3Joq4IexNre4VdHPamWetZbn8v9uJv4ckbtdZ1wBfAQOxz1wkXUFhRz8cbC7lm4gAipQnGaV08sg99Q/15faWctYuu+2r7UY5UNnDzFDmRc2Z3pA+h1trCm7kFpqOIczBZ1K1rffxJJ29USvkDY4H157h92wRm7V2N8z7lu3Byr723Emw2bgs6bjqKOAsfLws3psSzMv8Yu47Kkl+ia15fsZ+BEYHMTI42HUWcRXKfEGYPi+H1zD3UPvFkl5cMFN3HZFH3HqCBe07Z/mPsfenebtuglBqilEo+5bgdrd9vOnmjUioMuAI4jr1/nXByxzJX8u6eaq7aupTYeXPlDcPJXTupP/4+Ft5YUWA6inBhGw8e55uDFdw8JR6LrBrj9O6MrKeiSfPuJ2ugi0sGiu5jrKjTWm8FXgauUkp9qJS6VSn1LPAckAW8c9LhGdhHtp7sBewjX/+klHpLKfUTpdSvgY1AX+C3WmtZh8gFvLd8J43evty25gOwWu3LAwmnFRboy1UX9OOjTYUcqznTWCchzu6NlQUE+3nzXZmP0iVcsGUFkw5t562xl2CzNsn7tJMyPV3/PcD9wAjsBd412Jf7mne2JcIAtNYHsDfdvoV9BYoXgV8Bh7BPafLX7ostHMVm07zrFceFh7cxtOIIyPJALuHmKfFYm228u/ag6SjCBR2prGfR1iN8f2J/gvykl4xLSE/nB9u/5kDvWFYOGS/v007K6Kup9Uras61fZzsu/gzb9wI/dHwy0VNy8ss4VGfjl/Mnw4jH7G8UMpu800uICWZ6YhT/yj3AbdOH4Ott+vxQuJK3cg9g05ofTok3HUV0VEoKF7/0KOFflPLOnb8nVd6nnZK8Ewuj3l59gIhevlw0fzo89JAUdC7klqnxlFQ3smjrEdNRhAupt7bw7tqDzBkeQ//wc01FKpyJ37QpLEhNZElxM8UyGbFTkqJOGHOksp6MXSUsmNBfrvS4oOkJUQyJ6sXrK/ejtUxGLDrm402FHK9r4haZj9IlXTtpAC02zX/Xycoyzkg+SYUx7607RItNc90kWe/RFVksipumDmLL4Uq+OShT0Yhz01rz+or9jIgNYdKgcNNxxHmIj+xFakIk7649SIusLON0pKgTRjS32Hhv3SGmJ0YxIEKaYFzV1RfEEeLvzesyvYnogBX5ZewpqeHmqYNQSqYxcVXXTRpAUWUDWXmysoyzkaJOGLF8dylHKhvkKp2LC/T15trJA/hy2xEOHz/jqn5CAPbJhiODfLlsTF/TUUQXzB4eQ1SwH2+vltHvzkaKOmHE22sOEBPix6xhMpO8q7sxJR6lFG/lHjAdRTixfaU1LN9dyvUXDsTP+4zLcgsX4ONl4ZqJ/Vm+u4TCinrTccRJpKgTPe5QeR1ZeaV8f+IAfLzkT9DVxYUFcPHIPry79iB11mbTcYST+ueqAny9LPxg8kDTUYQDfH9ifzTwnsxV6VTkE1X0uP+sO4gCrpkoM8m7i1umDqKqoZkPnviHLB8kTlOZtZKFufu5PNabqGA/03GEA/TrHciMpGj+s+4QTS1nXStA9CAp6kSPsjbbeG/dYWYmRxMbFmA6jnCQCwp3MuZoPm8Ugm3WbCnsxP/k5vLer/9CPRZufvJu+dtwIz+YPICS6kYydhabjiJaSVEnetTXO4opq2mUJhg3o7KyuHnDp+yL6EdunyRZF1KcYFueyb9HzWHSwa2MKMqTvw03kp4UTWyoP2+vkSZYZyFFnehR76w9QFxYANMTo0xHEY6Uns7FBesJaahh4eg5si6kOGHtqKkcDOvLNduWytrObsbLorhm0gBy9pRx4Fit6TgCKepED9pXWsPK/GNcO6k/XhaZo8qtpKTgv2Qxl4c08OXw6VSNm2A6kXASC+tDCfKGS66ZDRkZshSgm/n+RPv7+TsyYMIpSFEnesy7aw/ibVF8b4IMkHBLKSks+PEVNNrg882yHqyAmsZmFm09wrxx/Qn49a+koHNDMSH+zB4WzcL1h2lsbjEdx+NJUSd6RENTC+9vOMzcETFEh/ibjiO6yeh+oSTGBLFwg6wLKWDRliPUN7WwYEI/01FEN/rB5IGU11r5arsMmDBNijrRIxZvO8rxuiaumyQDJNyZUooF4/uz8WAF+SXVpuMIwxZuOMTgqF5cMKC36SiiG00bGsmA8EDeXi0TkJsmRZ3oEW+vOUB8RCBThkSYjiK62fxxcXhZFAs3HDYdRRi0v6yWdQXH+e74frLOq5uzWBTXThrAmv3lcjJnmBR1otvlFVezruA4100egEUGSLi9qGA/ZiRF8+E3hTTLpKQe6/0Nh7AouPoCaXr1BAsm9MPHS8n0JoZJUSe63TtrDuLrZeG742WAhKdYMKEfpdWNZOWVmo4iDGixaT7YUEhaYhQx0ofWI0QG+XHxyL58sOEwDU0yYMIUKepEt6qzNvPBN4e5ZFQfwnv5mo4jesjM5GgievmycL00wXqiFfllHK1qYIGMdPco100aQFVDM59vkdHvpkhRJ7rV55uPUN3QLCtIeBgfLwvzx8WRsauY8lqr6Tiihy1cf4iwQB9mDYs2HUX0oAsHhzM4qhdvr5EBE6ZIUSe61dtrD5IQHcTEeBn95mkWTOhHU4vm442FpqOIHlRZ18SSHcXMHxuHn7eX6TiiByml+MHkgWw8WMGOoirTcTySFHWi22z7MofNhyq4Ltomo988UHKfEEbFhcooWA/z6eZCrM02vjteBkh4oqsviMPXAu+88hHk5pqO43GkqBPdIzeX/7y4EL+mRq762ffkxe2hvjehHzuPVLGtsNJ0FNFDFm44zLC+IYyMCzUdRRgQtnkD87Zn8nF1IPUXXSLv/T1MijrRLazLs/g8IYWL9uQSWn0cMjNNRxIGXD4mDl9vC+/L1TqPsPtoNVsOV7JArtJ5rsxMvrtlKTV+gSztP1be+3uYFHWiW2Qlp1AREML8Xdng6wvp6aYjCQNCA32YOzyGjzcVyrqQHmDh+kP4eCnmj4szHUWYkp7OhcV59Kku4+ORM+S9v4dJUSe6xccNwYT7KlJvmg8ZGbKQtwdbMKE/FXVNZOwsMR1FdKOmFhsfbSxkVnKMTF/kyVJSsGQs5YoIG1mDJ1A+erzpRB5FijrhcNUNTSzdUcxl4wfg8+uHpKDzcNOGRtI31J+F6w+ZjiK60bJdJRyrtbJggjS9eryUFObfcTXNGr7YUmQ6jUeRok443OJtR2lstnGFNMEIwMuiuOqCOLLySimuajAdR3SThesPExXsR1pilOkowgkM6xtCUkwwH8mURj1KijrhcB9vKmRgRCDj+oeZjiKcxHfH98em4cNv5A3eHZVWN7J8dwlXjYvD20s+VoTd/HFxfHOwgoPH6kxH8Rjy6hMOdbSygVV7j3HF2DiZm06cMCiyFxPje7NwwyG01qbjCAf7eGMhLTYtTa/iWy4fGwvYT/RFz5CiTjjUZ5uL0Brmt76YhWizYHx/9pXW8s3BCtNRhANprVm44RDjBoQxNDrYdBzhROLCApg8KJyPNxXKyVwPkaJOONRHGwsZ0y+UwVFBpqMIJ3Pp6L4E+HjJgAk3s+VwJXnFNSwY3990FOGE5o+LY19pLVtlAvIeIUWdcJi84mp2HKmSOapEu4L8vLl0VF8+33KEOmuz6TjCQRZuOIS/j4V5Y/qajiKc0KUj++LrZZEBEz1EijrhMB9vLMTLopg3WppeRfsWTOhHTWMzi7cdNR1FOEBDUwufbiri4hF9CPH3MR1HOKHQQB9mJkfz2eYjNLfYTMdxe1LUCYew2TSfbCpi2tBIooL9TMcRTmryoHAGhAeycL0sG+YOluwopqqhmQUTpOlVnNn8cbGU1TSycu8x01HcnhR1wiHWHzhOYUU9V0rTqzgLpRTfHd+P3H3HOPSHZ2Sxbxe3cOlW4pSVlJI801GEE0tPiibE35tPpAm220lRJxzi402FBPh4MWd4jOkowsldTTFK2/hw8UaYNUsKOxd1dNlKVpRYuXrlh1hmz5bfozgjfx8vLh3Vl8Xbj0p/2m4mRZ3oMmuzjS+2HOGiETH08vM2HUc4ubi1OUw8vIPPkqahrVbIzDQdSZyHL7J3oJWF+duWgfwexTnMHxdHnbWFr3cUm47i1qSoE12WubuEyvomWRZMdEx6Opfl55IfOYDdfYdCerrpROI8fB7Qn+Gl+xlcVQy+vvJ7FGc1KT6c2FB/PpYm2G4lRZ3oso83FRLRy5fUoZGmowhXkJLCxX96AAuazx99GVJSTCcSnXT4eB0bj7cwb/pweOwxyMiQ36M4K4tFcfnYOLL3lHGsptF0HLclRZ3okqqGJpbuLOGyMbGy5qPosKgZU0kZGsnnxywy07wL+mLLEQDmzbsQHnpICjrRIfPHxdJi03ze+vcjHE8+hUWXLN56FGuzTSYcFp02b3QsBcfq2F5UZTqK6KTPtxxhTL9QBkQEmo4iXEhynxCS+wTLRMTdSIo60SUfbSxkUGQvxvQLNR1FuJiLR/TB26L4bEuR6SiiEwrK7Es+ySTj4nxcOS6OTYcqKCirNR3FLUlRJ87bkcp6Vu8/xhVjY1FKmY4jXEzvXr5MHRrJF1uOSBOsC/liq73p7DujZVkw0XmXj41FKXtfbOF4UtSJ8/bppiK0hvljpelVnJ95o/ty+Hg9mw5VmI4iOuizzUWMH9ib2LAA01GEC+obGsCFgyL4ZFORnMx1AynqxHn7eFMRY/uHER/Zy3QU4aLmjuiDr5dFOk67iPySGnYdrWaeXKUTXTB/XCz7y2rZfLjSdBS3Y7SoU0pZlFL3KqV2KaUalFKHlFLPKqU6XCUopcKVUs8opfJb76NUKbVcKZXandk93e6j1ew8UiXLgokuCQ3wYXqivQnWZpOzdmf3+ZYilIJLR0lRJ87fxSP74uttkTnruoHpK3XPA88BO4CfAQuBnwOfKaXOmU0pNRDYAPwQeB+4E3gCKACk2uhGH28qxMuipF+N6LJ5o2M5WtXAhoPHTUcRZ6G1fSqKSfHhxIT4m44jXFhogA+zkqP5bHMRTS0203HcirE1nZRSI7AXch9qra8+aft+4C/ANcA757ibf2P/GUZrraX9pofYbJpPNhYyPSGSyCA/03GEi5s9PAY/bwufby5iYny46TjiDHYXV5NfUsMP5480HUW4gfnj4vhy21FW5JcxIynadBy3YfJK3bWAAl44ZftrQB1w/dlurJSaDkwDntJaH1FK+SilZNKkHrCuoJyiygaZm044RJCfNzOSolm07Sgt0gTrtD7ffASLgktG9jEdRbiB9KQoQvy9+USaYB3KZFE3EbABa0/eqLVuADa17j+bS1u/H1RKfQbUA7VKqTyl1FkLQtE1H3+1kUBamFN9wHQU4SbmjelLaXUja/YfMx1FtMPe9FrElCFydV44hp+3F98ZHctXW4uofeJJyM01HcktmCzqYoEyrXV7i8AVApFKKd+z3D6p9ftrQDj2fnW3AFbgLaXUzWd7cKXUbUqp9Uqp9aWlpZ1P76GsK1exaPcxLtqeTeBFs+WFKBxiZnI0AT5eMgrWSW0vqqLgWJ2MehUONd/nOPUtsPSdr2DWLPk8cQCTRV0gcKZVfRtOOuZMglu/VwMztNZva63fAFKBCuCJsw220Fq/qrWeoLWeEBUV1bnkHiw3YwOV/kF8Z2cOWK2QmWk6knADgb7ezBoWzeJtR2mWjtNO57MtRXhbFBdL06twoIlbVxJdU86XCRfK54mDmCzq6oAzXcf3P+mYM6lv/f6u1tratlFrfRz4FOjD/67mCQdZHDWMXtZ6ph3aAr6+kJ5uOpJwE/NGx1Jea2XVXmmCdSZaa77YcoRpCZGEBZ6t8USIzrHMSOeifWvJHDyBusBg+TxxAJNFXRH2Jtb2Crs47E2z1nb2tTnc+v1oO/va2nB6dyGfOEVzi40lZZqZg0Lxf+RhyMiAlBTTsYSbSE+KIsjPm89lLVinsulQBYeP18tar8LxUlK45CcLaPDxI+uNj+XzxAFMFnXrWh9/0skblVL+wFhg/Tlu3zbAol87+9q2lXQhnzjF2oJyjtVauSR9JDz0kLwAhUP5+3gxZ3gMi7cdxdosTbDO4vMtR/D1sjB3RIzpKMINTfrONHoH+vClNcR0FLdgsqh7D9DAPads/zH2vnRvt21QSg1RSiWfctzH2PvTXa+UCjrp2L7AfCBPa53v8NQebPG2o/j7WEhPkj6IonvMG92XqoZmVuTL4CVnYLPZm16nJ0YR4u9jOo5wQ95eFi4a0YeMncU0NLWYjuPyjBV1WuutwMvAVUqpD5VStyqlnsW+wkQW3554OAPYecrtjwP3Y2+qXa2Uuk8p9StgNeCLfWJj4SA2m2bxtqOkJ0YT6Gtszmrh5lIT7HNXfb5ZRsE6gw0Hj3O0qoHLxsioV9F9Lh7Zh1prCyv2lJmO4vJMLxN2D/bCbAT2Au8a4EVgntb6nO0vWutXgauBGuAx4DfAbuyjYZd0U2aPtPHQcUqqG7lklIx+E93H19t+1r5kh5y1O4PPNxfh521h1jBpehXdZ8qQSIL9vflyW3td5EVnGC3qtNYtWutntdZJWms/rXWc1vo+rXXNKcfFa63VGe7jQ631hVrrXlrrYK31XK31yp75CTzHl1uP4utlYWayLOciute8MbHUNDaTlSdNsCa12DSLth1lZnI0QX5ydV50H19vC3OGxbB0Z7GsBdtFpq/UCRegtebLbUeZlhBJsPSrEd1sypAIwnv5ykTEhq3Zf4zS6kYZ9Sp6xMUj+1BZ30SuTGnUJVLUiXPaWlhJYUW9rPkoeoSPl4WLR9o7TtdbpQnWlM+3HCHQ10uuzoseMT0xil6+XtIE20VS1Ilz+nLbUbwtijnDpV+N6BnzRvelztrCsl0yK5EJzS02Fm87yqxhMQT4epmOIzyAv48XM5KjWbL9KC02bTqOy5KiTpyV1vZRrylDImQ2edFjJg+KICrYTyYiNmTV3mOU11plrVfRoy4Z2ZdjtVbW7i83HcVlSVEnzmp3cTX7y2plzUfRo7wsiktH9mHZrhJqGptNx/E4n28pItjPm7REmZNS9Jz0pCj8vC0s3ib9ac+XFHXirBZtPYpSMHe4FHWiZ80bE0tjs42MP70Gubmm43gM68pVLF5fwJwoC/4+0vQqek6v1hOJxduPYpMm2PMiRZ04q8XbjjApPpyo4PaW6BWi+4wv2kWfmmN8tvUIzJolhV1PyM1lxY9/SRXezPvro/Kcix536ai+FFc1svFQhekoLkmKOnFGe0tryCuukVGvwghLVhaX7F5Jdvw4arUFMjNNR3J/mZksHjSB4IYapuWvl+dc9LiZw6Lx8VJ8uVWaYM+HFHXijBa3Di2/eKR0lhYGpKdzUcEGrN6+ZA+dCOnpphO5vZa0NJYOnczMfRvw9bbIcy56XIi/D9OGRvLltqNoLU2wnSVFnTijL7cdYdyAMPqE+puOIjxRSgoT/vkXeqtmltz+a0hJMZ3I7W3om0x5QAhzJ8RDRoY858KIS0b2pbCinm2FVaajuBwp6kS7Dh6rY1thlTS9CqO8p05h1gXxZJTZZPmgHrBku305wLQHb5OCThgzZ3gMXhbFlzIKttOkqBPtWrzd/mK6RJpehWFzh8dQ1dAsc1d1M601S3YUM3VohKz1Kozq3cuXlMERLJYm2E6Tok6068ttRxkZF0L/8EDTUYSHS02Iwt/HwpLtsnxQd9p1tJqD5XXMHSFX54V5F4/sw76yWvKKa0xHcSlS1InTHKmsZ+PBCrlKJ5xCgK8X0xOiWLKjWM7au9GS7cUoBbOGyVqvwry5I2JQChbJKNhOkaJOnOarE6Ne5YxdOIe5I/pwpLJBOk53oyU7jnLBgN5EB8vAKGFedLA/EweGn5iFQXSMFHXiNIu2HSUxJoghUUGmowgBwKzkaCzKXngIxzt8vI7tRVXMHR5jOooQJ1w8sg+7i6vZVypNsB0lRZ34ltLqRtYVlEvTq3AqvXv5MmlQOEu2F5uO4pa+3mF/XqU/nXAmba1FX8rVug6Tok58y5IdR9EaLhklb+7Cucwdbj9rLyirNR3F7SzZXkxCdBCDInuZjiLECbFhAYztHyZNsJ3QqaJOKZWnlHpQKSWf+G5q8bajDIrsRVJMsOkoQnzLnNamwbarSsIxjtdaWVtQztwR0vQqnM8lI/uwtbCSQ+V1pqO4hM5eqWsC/ggcVEp9rJSap5SSq31u4nitlVV7j3HxyD4opUzHEeJb+ocHMrxviPSrc7Blu0posWnmDpdzdeF82roCydW6julUQaa1HgFMAd4EZgCfAIeUUn9QSg3phnyiB329s5gWm5ZVJITTmjsihvUHjlNW02g6ittYsuMofUL8GRUXajqKEKcZEGE/mZPVJTqm01fZtNartdY/BvoCtwL7gYeAPKXUMqXUdUopPwfnFN0tN5fFH68gLsAib+7Cac0d3getIWOnNME6Qr21hay8UuYMj8FikavzwjldOqoP3xys4OgfnobcXNNxnNp5N51qreu01m9oracBycB/gHTgLaBIKfWCUmqAY2KKbpWbS/Ul81jREMAlKz9BrV5tOpEQ7RrWN5h+vQNkFKyDrMgvo6HJJv3phFO7uNn+ev/q4xyYNUsKu7PoUn84pZSXUupK4Dng+4AGlgOrgbuAnUqpK7qcUnSvzEyWx43G6u3DxbtWQGam6URCtEspxdzhfcjJL6O2sdl0HJe3ZPtRgv29mTwownQUIc5o6IYcEsoOsnjohWC1ymfUWZxXUaeUSlZKPQ0UAh8AE4BngESt9Wyt9XewX73bDTzlqLCim6SnszTxQiLqKhh3rADS000nEuKM5o6IwdpsIzuv1HQUl9bcYmPpzmJmJkfj6y3j3YQTS09ndsEG1vUbQWVQmHxGnUVnpzT5kVJqJbAduA/YDCwA+mmtf6W13tt2rNY6H/gLIAMonFzTpMlkjkxlZqgNr6VfQ0qK6UhCnNGEgb3pHejDEpnapEs2HDjO8bomGfUqnF9KCrPvvZFmL2+yXntfPqPOorOnZ68Bg4A/AUO01hdprT/QWp+pHWQH9j52womtLzhOVZNm1vfnyItFOD1vLwuzhsWQsbOYphab6Tgua8mOYny9LKQlRZmOIsQ5jb0klYhevmS0yEC+s+lsUXcV0F9r/RutdcG5DtZar9Va33xeyUSPWbrT/uaemhBpOooQHTJ3eAxVDc2s3V9uOopL0lqzZMdRpg6NIMjP23QcIc7Jy6KYkRzN8l0lcjJ3Fp2dp+5jrXVLd4URPU9rTcbOYqYMjaCXvLkLF5GaEIW/j4Ul22VC0vOx62g1h8rrZa1X4VJmD7OfzK0vOG46itOS3rEebm9pLQXH6pg1TKY0EK4jwNeL6QlRLNlRjNbadByXs2R7MUrBrGHRpqMI0WGpCZH4ellYKvNUnpEUdR6u7cUxK1ne3IVrmTuiD0cqG9hWWGU6istZsuMoFwzoTXSwv+koQnRYLz9vUoZEkLFTTubORIo6D5exs5gRsSHEhgWYjiJEp8xKjsaikLVgO+nw8Tq2F1Uxd7hcnReuZ/bwGAqO1bG3tNZ0FKckRZ0HK6+1suHAcWl6FS6pdy9fJg0Kl9UlOunr1qlgpD+dcEVtrUrSBNs+Keo82PJdJdg0zJGiTrioucP7sLu4moIyOWvvqCXbi0mIDmJQZC/TUYTotNiwAEbEhsj6z2cgRZ0Hy9hVTEyIHyPjQkxHEeK8zGltQvxaJiLukOO1VtYWlMtar8KlzRoWw4YDxymvtZqO4nSkqPNQjc0tZO0uZWZyDEop03GEOC/9wwMZ3jdE+tV10LJdJbTYtKwiIVza7GHR2LS9tUl8mxR1HmrNvnJqrS3MGS6jXoVrmzsihvUHjlNW02g6itNbsuMofUL8GRUns/IL1zUyNpSYED8ydskV+lNJUeehMnYW4+9jYcoQWUVCuLa5w/ugNdLH5hzqrS1k5ZUyZ3gMFotcnReuy2JRzEyOIWt3KY3Nsh7CyaSo80Baa5buLGHa0Cj8fbxMxxGiS4b1DaZfoIUln6yE3FzTcZzWis9X0NBkY66SpdWE65s9LJpaawtr9snf88mkqPNAu45WU1hRL02vwi2o1auZu/JTchr8qb34O1LYtSc3lyVvfEpwQw2TfzBPniPh8qYOjcTfR1aXOJUUdR5oaetIwRmyioRwB5mZzMnLxertS07scMjMNJ3I6bQsz2TZoAuYsW89vg118hwJl+fv48W0oVFk7CyR1SVOIkWdB1q6q4Qx/cNkiSDhHtLTmVC6l+CGGpYNnQzp6aYTOZ3NY6ZxLDCMWfs2gK+vPEfCLcweFk1hRT27jlabjuI0pKjzMCXVDWw+VMEcWchbuIuUFHy+XsL04GaWXzAL2+QLTSdyOst9orEAaddeDBkZkJJiOpIQXTaz9XNsqcxTeYIUdR5m2U77vD6yNJhwKykpzJyfRmmjZntRlek0TmfZrhLGx/cm7De/lIJOuI3oYH/G9A9jqcxXd4IUdR5m6c4S4sICSO4TbDqKEA6VnhSFUvYCRvzP0coGthdVSR9a4ZbmDItm86EKSqoaTEdxCkaLOqWURSl1r1Jql1KqQSl1SCn1rFKq04sSKqUClVL7lFJaKfVSd+R1dQ1NLazIL2X2sGhZRUK4nYggP8b2D2OZTEj6Lct324vcmVLUCTfU1uokJ3N2pq/UPQ88B+wAfgYsBH4OfKaU6my23wNRjo3nXlbml9HQZJOmV+G2ZiZFs/lwJaXVsrpEm2W7SogN9ScpRq7OC/eT3CeYuLAAlu6Uog4MFnVKqRHYC7kPtdZXaa1f01rfB9wHzACu6cR9XQDcA/yuO7K6i6U7Swjy82by4HDTUYToFm1NjJm75Q0e7Gs8r8wvY0ayXJ0X7kkpxexh0azIL6WhSVaXMHml7lpAAS+csv01oA64viN3opTyar3NYuBDB+ZzKzabJmNnMdMTI/HzllUkhHsaERtCTIjfiSZHT7dmXzl11hZmyWh34cZmDYuhocnGyvwy01GMM1nUTQRswNqTN2qtG4BNrfs74l4gGbjLkeHczbaiSkqqG5mVLE2vwn0ppZiZHE1OXhnWZpvpOMYt21WCn7eFlMGyxrNwX5MHh9PL10tWl8BsURcLlGmt2+v8UghEKqV8z3YHSqlBwKPA77XWBZ15cKXUbUqp9Uqp9aWlpZ25qUtaurMEi5JVJIT7m5EUTXVjM+sLPHtNSK01y3aVMGVIBAG+cnVeuC8/by/SkuyrS9hsnr26hMmiLhA4U2/mhpOOOZtXgH3YB1t0itb6Va31BK31hKgo9x9fsXRHMeMH9ia811nrZCFc3tShkfh6WTx+NNze0loOltfJqFfhEWYlx1BS3cjWwkrTUYwyWdTVAX5n2Od/0jHtUkpdD8wB7tBaNzk4m1spqqhnx5EqZsuoV+EBerUOBlrm4f3qlrcWtXJ1XniCGcnRWBRkeHgTrMmirgh7E2t7hV0c9qZZa3s3bL3Nc8Ai4KhSaqhSaigwsPWQ0NZtYd2Q2+Vk7JJVJIRnmZkczb7SWgrKak1HMWbZrhKSYoLp1/tcDR5CuL7wXr6MH9jb46c2MVnUrWt9/Eknb1RK+QNjgfVnuW0A9jnpvgPsOekrs3X/9a3/v9WRgV3V0h3FxEcEMiSq03M6C+GS2pocPbUJtqqhiXUF5XKVTniUWcNi2HGkisKKetNRjDFZ1L0HaOzzy53sx9j70r3dtkEpNUQplXzSMbXAgna+7mzdv7j1/592R3BXUtvYTO7eY8weFiPzVAmPMTCiF0Oienns1CY5eWU027T0pxMepa2L0TIPboL1NvXAWuutSqmXgbuUUh9ib0odhn1FiSzgnZMOz8DetKpab9sEvH/qfSql4lv/uVdrfdp+T5Tz+UqsLTZmaZm/R3iWmcnRvLnqALWNzfTyM/ZWZ8SyXSWEBvhwwYAw01GE6DFDonoR38vC11+s5gaOQEqK6Ug9zvQyYfcA9wMjgJexryLxIjBPay2TTHVVbi4Z//yUkIYaJlz7HcjNNZ1IiB4zIzkaa4uNFR42IanNpsnKKyEtMQpvL9Nv8UL0HLV6NbNWfMrqBn9qL/bMzzyjr3itdYvW+lmtdZLW2k9rHae1vk9rXXPKcfFa63O2HWqtC7TWSmstExEDtuWZZA4cy/T93+DTUA+ZmaYjCdFjJsaHE+znfWIUqKfYUlhJWY1Vml6F58nMZOaeNVi9fVjVJ9kjP/PkNM6N7Rg3jdKgcGbs/wZ8fSE93XQkIXqMj5eF6YlRLNtVgtaeMyHpsp3FWBSkJbr//JtCfEt6OhNK8+llrWf50Ike+ZknRZ0by/TrA8D078+FjAyP7F8gPNuM5GhKqhvZXlRlOkqPWba7hHEDetNbJhoXniYlBb8lXzEl0ErWpIvRF15oOlGPk6LOjS3fXcrofqFE/eYBKeiER0pPikIpz5napKSqgW2FVdL0KjxXSgozLptGYb2N/JKacx/vZqSoc1MVdVY2HjxOujTBCA8WGeTH6H5hHlPUtU3hIkWd8GTpSfbPPU+c0kiKOjeVs6cMm4a0JHlzF55tZlI0mw9XUFZzpqWm3ceyXSX0DfUnuU+w6ShCGBMbFkBSTDCZu0tNR+lxUtS5qeW7SwgL9GFs/zDTUYQwatawaLTG7d/gG5tbyNlTxozkaJloXHi89KQo1hWUU9PYbDpKj5Kizg3ZbJrsvFKmJ0ThZZE3d+HZRsSGEB3s5/ZTm6zdX06dtYVZ0vQqBOlJ0TS1aFZ62DyVUtS5oW1F9nmqZiRLfzohlFLMSIomO6+Uphb3ndN82a4S/LwtTBkSaTqKEMZNiO9NkJ83mR7Wr06KOjeUubsUpWB6ghR1QoB9apPqxmbWFxw3HaVbaK1ZtquElCERBPh6mY4jhHE+XhamDY0kc3epR81TKUWdG1q+u4TR/cKICPIzHUUIpzAtIRIfL8WyXe650Pe+sloOHKuTUa9CnCQ9KYojlQ3kFXvO1CZS1LmZ8lormw5VyFQmQpwkyM+bCwdHuO3UJm39BWfIaHchTkjzwKlNpKhzMzl7StHa3twkhPifGUnR7C2t5eCxOtNRHG7ZrhISY4LoHx5oOooQTqNvaADJfYI9ql+dFHVuJnN3KeG9fBkdF2o6ihBOpa1p0t2aYKsbmli7v1xO5IRoR3pSNOsLjlPd0GQ6So+Qos6N2GyarLxSpidEYpGpTIT4lvjIXgyO7MUyN5uvbsWeMpptmpnS9CrEaWYkRdFs85ypTaSocyNbCispr7XKGbsQZzAjOZrV+aXUPvEk5OaajuMQGdnbCKGZ8Ud2m44ihNO5YGBvgv28Wb7LvU7mzkSKOjeyfFcJSkGqTGUiRLtm2cqw2mDlm5/ArFkuX9jZVq0ic3cJaTtX4T1ntsv/PEI4mo+XhdTESDLzSjxiahMp6txIZl4pY/uHEd7L13QUIZzShG2rCGqsY/mg8WC1Qmam6Uhdsi1jLWWBYczYu84tfh4hukN6YjTFVY3sPFJtOkq3k6LOTRyraWTL4QrSE6XpVYgz8Z2RxtRDW8kefAHa1xfS001H6pKs/qNR2kbagU3gBj+PEN2hbWqTzDz3HwUrRZ2byD4xlYk0vQpxRikppF09g8KQaPZ+vARSUkwn6pKsen9G9fYh4tcPQEaGy/88QnSHmBB/hvcNIdPNBkm1R4o6N5G5u5TIIF9GxspUJkKczfSLJwOQGRBrOEnXVNY18c3B46RdMAgeekgKOiHOYkZyFBsOHKey3r2nNpGizg20tE1lkhglU5kIcQ79egcyNDqIrDzXPmtfubcMm4Y0WT1GiHNKT4qmxQOmNpGizg1sPlxBRV0T6TJPlRAdkpYYxZr95dRbW0xHOW9Zu0sJ9vdmbP8w01GEcHrj+ocR4u99Ykk9dyVFnRvI3FWCRcH0hEjTUYRwCWmJUVibbazed8x0lPOitf3qfGpCJN5e8jYuxLl4e1lITYwiM6/Urac2kXcDN5CZV8q4Ab0JC5SpTIToiEmDwvH3sbhsE2xecQ1Hqxqk6VWITpiRFE1pdSPbi6pMR+k2UtS5uNLqRrYcriRd3tyF6DB/Hy9SBke4bFGX1To1w3R53QvRYW0nQa76uu8IKepcXHbrH6csDSZE56QlRrG/rJYDx2pNR+m0zN2lJMUE0zc0wHQUIVxGVLAfI+NCyNztvv3qpKhzcct3lxAZ5MfwviGmowjhUtJaBxZlu9hZe21jM+sKyk9MqCqE6LgZSdH2qU3q3HNqEynqXFhzi42cPWWkJ8lUJkJ0VnxEIAPCA12uKSZ37zGaWrT0pxPiPKQnRWHTkJPvWq/7jpKizoVtOlRBZX0T6XLGLkSnKaVIS4xi1d5jNDa7ztQmWXmlBPp6MSG+t+koQricsf17Exbow/JdUtQJJ5O5uxSLgtShUtQJcT7SEqOos7awoeC46SgdorUmM6+EKUMi8PP2Mh1HCJfjZVGkJkSRlVeKzeZ+U5tIUefCMvNKGD+wN6GBPqajCOGSUoZE4OOlXKYJtuBYHYfK66XpVYgumJEURVmNe05tIkWdiyqpbmBbYZWsIiFEF/Ty82ZifLjLFHVZraP20hLldS/E+WqbCsgdR8FKUeeisnbbP4SkP50QXZOWGMWuo9UcrWwwHeWcsvJKGRTZiwERgaajCOGyIoP8GNMvlEwXOZnrDCnqXFTmql1EqyaGF2w3HUUIl9Y2NYizT23S0NRC7r5j0vQqhAOkJUWz8cBxKv7wFOTmmo7jMFLUuaDmlavI2VdO2pYs1OzZbvUHKURPS4oJJibEj8w8526KWVdQTkOTTYo6IRxghrUYG5D17pcwa5bbfI5KUeeCNi1bR5V/EOl714HVCpmZpiMJ4bLapjbJ2VNGc4vNdJwzytpdiq+3hcmDw01HEcLljd68grD6KrIHjnWrz1Ep6lxQdtxILDYb0w5tBV9fSE83HUkIl5aeFE11QzObDlWYjnJGWXmlTB4UTqCvt+koQrg8rxnpTDu0lZxBF6Dd6HNUijoXlFXvz5gIH0J/+yvIyICUFNORhHBpU4dG4mVx3qlNCivq2VNSI02vQjhKSgrTvzeXkqBwdn34ldt8jkpR52KO11rZcriC6RcMhocecps/RCFMCg3wYVz/MKct6toGcUhRJ4TjTL/kQgCyA2INJ3EcKepczIr8MrT+3zw7QgjHSEuMYsvhSspqGk1HOU3W7lJiQ/0ZGh1kOooQbqNPqD9JMcFk73HOk7nzIUWdi8nOKyXE35sx/UJNRxHCrbRNbbJiT5nhJN/W1GJjZX4ZaUlRKKVMxxHCrUxPjGTd/uPUWZtNR3EIKepciNaa7D2lTEuIxNtLfnVCONLI2FDCe/k6XRPsxoMVVDc2S9OrEN1gemIU1hYba/aVm47iEFIZuJC84hqKqxqZniBv7kI4msWimJ4QSbaTLfSdlVeCl0UxZWik6ShCuJ2J8eH4+1ic7mTufElR50LaOktLfzohukdaUhTHaq1OtdB3Vl4p4wf0JsTfx3QUIdyOv48XkwdFuE2/OinqXEj2nlKGRgcRGxZgOooQbim19Sp4lpOsLlFS3cC2wqoT/f2EEI43PTGKfaW1HD5eZzpKlxkt6pRSFqXUvUqpXUqpBqXUIaXUs0qpXh24baJS6vdKqdVKqVKlVLVSapNS6jcdub2rqbe2sGZ/uTS9CtGNIoP8GBUX6jRNMTl59kEb0p9OiO6Tlmjv2pCd51yDpM6H6St1zwPPATuAnwELgZ8DnymlzpXtFuBeYC/we+ABYDfwOLBKKeVWl7PW7D+GtdnG9ETpVyNEd0pLjOKbgxVU1jeZjkJWXimRQX4M7xtiOooQbmtIVBCxof4nuji5MmNFnVJqBPZC7kOt9VVa69e01vcB9wEzgGvOcRfvA/201j/QWr+otX5Fa/194A/AaOBH3Zm/p2XnldnXfRwUYTqKEG4tLSmKFptmZb7Zs/YWmyZnTynTEyOxWGQqEyG6i1KK6YlRrNzr3Os/d4TJK3XXAgp44ZTtrwF1wPVnu7HWer3WurKdXe+1fh/Z1YDOJHuPfd3HAF8v01GEcGvj+ocR7O9N1m6zZ+1bCys5XtckTa9C9IDpiVFOv/5zR5gs6iYCNmDtyRu11g3Aptb956Nf6/fi807mZIoq6skvqZH+dEL0AG8vC6kJkWTllaK1ualNsnaXotT/Bm8IIbrP1CGRWBQu3wRrsqiLBcq01u2tyVMIRCqlfDtzh0opL+D/Ac3AO12P6BxkKhMhelZaYhRHqxrIK64xliErr4TR/cII79Wpt0EhxHkIDfRhbP8wspxsRZnOMlnUBQJnWmSx4aRjOuMFIAV4WGu9+2wHKqVuU0qtV0qtLy117so8e08pfUL8SYyRdR+F6AltJ1BZr/wHcnN7/PErslay6eBx0gIbzn2wEMIhpidGseVwBcdrraajnDeTRV0d4HeGff4nHdMhSqnHgLuAV7XWfzzX8VrrV7XWE7TWE6KinPcKWHOLjRV7ykhNiJR1H4XoIX13bCKp7KC9X92sWT1b2OXmsuLnD2NDkfbYL4wUlUJ4oumJUWgNKwwPkuoKk0VdEfYm1vYKuzjsTbMdKpeVUo8AvwXeAH7isIROYPPhSqoamqXpVYielJnJ9P0bWBc3nDqtIDOzRx87u98oQhpqGHNoR88+thAebEy/MEIDfFy6X53Jom5d6+NPOnmjUsofGAus78idtBZ0vwPeBG7VJns2d4PsPHtn6Wmy7qMQPSc9nemHt2H19mFN/FhIT++xh9ZpaWQPuoBpBzbj7ePdo48thCfzsiimDY0ke4/ZQVJdYbKoew/QwD2nbP8x9r50b7dtUEoNUUoln3oHSqmHsRd0bwG3aK1de4KZdmTvKWV0vzB6S2dpIXpOSgoT//EcftjIvvdRSEnpsYfOHzKKo0ERpI4dCBkZPfrYQni66YmRFFc1Gh0k1RXeph5Ya71VKfUycJdS6kNgETAM+4oSWXx79GoGMBD7vHYAKKV+CjwKHASWAted0uesWGv9dbf+EN2ssq6JzYcquGvGUNNRhPA4/tOmMDlvLTkV9T36uNmto+9Sf34D9O7sWDEhRFe0dXXKzislqU+w4TSdZ6yoa3UPUADcBnwHKANexD569VxX3drmsRuAven1VFmASxd1K/LLsGmZykQIU6YnRPL4FzspqqgnNqxnVh7MzitlcFQv+klBJ0SP6xsaQEJ0ENl7Svnx9MGm43Sa0bVftdYtWutntdZJWms/rXWc1vo+rXXNKcfFa63VKdtu0lqrs3yl9+gP0w2y80oJ9vdmbP8w01GE8EhtE//m7OmZjtMNTS2s2X9MJhoXwqDpiVGs2V9OvbXFdJROM1rUiTPTWpO9p5SpQyLx9pJfkxAmJMYEERPid6JJtLttOHCchiYb0xNlYJQQpkxPjMLabGPN/mOmo3SaVAtOKr+khiOVDdL0KoRBSilSE6JYmV9Gi637R8Nl7ynFx0sxeVBEtz+WEKJ9kweF4+dtITvP9eark6LOSWWdWBpMztiFMCk1IZKKuia2FlZ2+2Nl55UxfmBvevmZ7u4shOfy9/Fi0qBwsvJKTEfpNCnqnFSWdJYWwilMGxqJUpDTzROSllQ3sPNIlVydF8IJpCVGsbe0lsIeHv3eVVLUOaGGphbW7i+XztJCOIGIID9GxoaS08396la2Lk0kr3shzDt5ahNXIkWdE1qzv5zGZhtpcsYuhFNITYjkm4PHqW5o6rbHyM4rI6KXL8P7hnTbYwghOiYhOog+If5S1Imuy84rxdfLwuTB4aajCCGwT23SbNPk7u2e0XA2myZnTxnTEiKxWNS5byCE6FZKKaYnRrIiv4zmFtdZrEqKOieUnVfKxEG9CfSVztJCOIPxA3sT6OvVbU2wu45WU1bTeGJePCGEedMTo6huaGbz4QrTUTpMijonU1RRz56SGulXI4QT8fW2kDI4otsmIc5uvd/UBBntLoSzmDY0EouCLBea2kSKOifT9qEhI+CEcC6pCZEUHKvj4LE6h993zp5SkvsEExPi7/D7FkKcn7BAX0b3C3OpfnVS1DmZ7LwyooP9SHbBhYSFcGcnRsM5+GpdvbWFdfuPy1U6IZxQWmIUWw5XUFFnNR2lQ6SocyItq1axYtshpodplJLO0kI4k0GRvYgLC3B4E+ya/cewttikP50QTmh6YhQ2DSue/jvk5pqOc05S1DmL3Fw2X38Hldqb6a8+6RJ/PEJ4krbRcKvyjzl0NFx2Xhl+3hYmDZLR7kI4mzGHdxLSUEP22jyYNcvpP5ulqHMWmZnkxI1EaRvT9m2AzEzTiYQQp0hNiKK6sZlNhyocdp85e0qZNCgcfx8vh92nEMIxvLOzmHZgMzkDx6KtVqf/bJaizlmkp5Mz6AJGFe8lvKUR0tNNJxJCnGLqEPtouGwHTW1ypFJGuwvh1NLTST28lSMhUeyNGeT0n81S1DmJ6nET2BiXzLSBoZCRASkppiMJIU4RGujDmP5hDutXl9M6VYKMdhfCSaWkMO1PDwKQ8+SrTv/ZLEWdk8jde4wWDak3Xu70fzRCeLLUhCg2H6qgsq7rS4Zl7yklOtiPxJggByQTQnSH/rOmMSiyFznNzv86laLOSeTsKSPQ14sLBoaZjiKEOIu0xEhsGlbu7VoTbItNsyK/jNSEKBntLoSTS02IJHfvMRqbW0xHOSsp6pxEzp5SLhwcgZ+3dJYWwpmN6RdGsJ93l5tgtxVWUlHXxPREmZ9OCGeXmhBFfVML3xyoMB3lrKSocwIHj9VRcKxOJh8VwgV4e1mYMjSC7LwytNbnfT9tReG0ofK6F8LZXTg4HG+L6ralAh1FijonkJPftu6jdJYWwhWkJkRRWFHP/rLa876P7D1ljIwLISLIz4HJhBDdIdjfhwsG9CbHQSPfu4sUdU5gxZ4yYkP9GRLVy3QUIUQHpLUtGXaea0LWNDbzzYHjciInhAuZlhDJtqJKymudd8kwKeoMa26xsVI6SwvhUvqHBxIfEXjeZ+25e4/RbNMyP50QLiQ1IRKtYWW+816tk6LOsC2FlVQ1NDNN+tMJ4VJSE6LI3XcMa3PnlwzL2VMqo92FcDGj+4UR4t/1QVLdSYo6w3LyylAKpkpnaSFcyvTEKOqsLWw4cLzTt83ZUyaj3YVwMV4WxbSESHL2dG2QVHeSos6wnD2ljIoLJbyXr+koQohOON/RcIfK69hfVst0uTovhMtJTYjiSGUDe0trTEdplxR1BlU1NLHxUIVMZSKECzrf0XDZrUVgqiwNJoTLaZuCKDvPOfvVSVFn0Oq9x2ixaRkBJ4SLmp5oHw13rKaxw7fJySsjLiyAwZEy2l0IV9M/PJDBkb2ctl+dFHUGnVgabEBv01GEEOchNSEKrWFFB0fDNbfYWLm3jOmJkTLaXQgXNS0hktX7yp1yyTAp6gzK2VNKyuAIfL3l1yCEKxoZF0pYoE+Hm2A3H66guqFZrs4L4cLalgw7n0FS3U2qCUNkaTAhXJ+XRTF1aCQ52wvRT/wRcnPPenz2so1Y0Ewt39dDCYUQjtY2SGqFE64u4W06gKdqWxpsmpyxC+HS0ixVfNGgubwgEK+81ZBdCUHBpx9YU83+I5WMKT9M6CX/DzIyICWl5wMLIbrk5EFSv7zYdJpvkyt1huTkydJgQriDi/at5ZK8VfSuqySkvoaQ6gpCAnxO/6quYMyRPO7IXQhWK2Rmmo4uhDhPqQmdHyTVE+RKnQHNLTZW7S3jkpF9pbO0EC4udEYqf3t8lr1Q8/VtvQI36fQDc1tg1i3/Oy49vcezCiEcIzUxime/zmPl3mNcPibWdJwTpKgzoG1psNRE6U8nhMtLSbEXcpmZ9kLtTE2qHT1OCOH0RsWFEhrgQ05eqRR1nu7E0mBDpKgTwi2kpHSsSOvocUIIp2YfJBVxYskwZ2l1kz51BuTsKWV0XCi9ZWkwIYQQwiWlJkRxtKqB/BLnWTJMiroe9r+lwWTUqxBCCOGq2pYM6+xSgd1JiroelntiaTBpehVCCCFclTMuGSZFXQ/L2VNKoK8X42RpMCGEEMKlpTrZkmFS1PWwFXvKZGkwIYQQwg0425JhUln0IFkaTAghhHAfFw6JwNuinKZfnRR1PahtabDURBkkIYQQQri6ID/v1iXDnKNfnRR1PSgnr4y4sAAGR8rSYEIIIYQ7SE2IZFthlVMsGSZFXQ9pbrGxcm8ZqQmRTjNJoRBCCCG6pq31bUW++SZYKep6yObDlVQ3NMv8dEIIIYQbaVsybIUT9KszXtQppSxKqXuVUruUUg1KqUNKqWeVUh1qo+zq7XvKij2tS4MNjTAdRQghhBAO4mVRTBsaeWLJMJOMF3XA88BzwA7gZ8BC4OfAZ0qpjuTr6u17RNvSYGGBsjSYEEII4U6mJUQ6xZJh3iYfXCk1Ansh9qHW+uqTtu8H/gJcA7zTXbfvKW1Lg92RNsR0FCGEEEI4WNuSYdl7ykiICTaWw/SVrGsBBbxwyvbXgDrg+m6+fY/IXbTKvjSYtdh0FCGEEEI4WP/wQAb3spCzeDXk5hrLYbqomwjYgLUnb9RaNwCbWvd35+27X24uOX//gF7WesZdO8/oL1sIIYQQ3SA3l9RVX7C63o/GuRcZ+6w3XdTFAmVa6/YmdykEIpVSZ+uEdt63V0rdppRar5RaX1rajZMGZmYSWX2M+duX49tQB5mZ3fdYQgghhOh5mZnM3LOGKQc2c9zL39hnvdE+dUAgcKbZ+hpOOsbq6NtrrV8FXgWYMGFC9w1XSU/nnsceA6sVfH0hPb3bHkoIIYQQBqSnk/bYY6QVbDT6WW+6qKsDos+wz/+kY7rr9t0vJQUyMuxVe3q6/f9CCCGEcB9O8llvuqgrAoYrpfzaaUKNw960eqardI64fc9ISZFiTgghhHBnTvBZb7pP3brWDJNO3qiU8gfGAuu7+fZCCCGEEG7BdFH3HqCBe07Z/mPsfeHebtuglBqilEo+39sLIYQQQrgzo82vWuutSqmXgbuUUh8Ci4Bh2FeEyOLbEwdnAAOxz0t3PrcXQgghhHBbpvvUgf0qWwFwG/AdoAx4EXhYa23rgdsLIYQQQrg8ZXrxWWcwYcIEvX69dL8TQgghhPNTSm3QWk84dbvpPnVCCCGEEMIBpKgTQgghhHADUtQJIYQQQrgBKeqEEEIIIdyAFHVCCCGEEG5AijohhBBCCDcgU5oASqlS4EA3P0wk9jn0xPmT57Dr5DnsGnn+uk6ew66T57Br3OH5G6i1jjp1oxR1PUQptb69OWVEx8lz2HXyHHaNPH9dJ89h18lz2DXu/PxJ86sQQgghhBuQok4IIYQQwg1IUddzXjUdwA3Ic9h18hx2jTx/XSfPYdfJc9g1bvv8SZ86IYQQQgg3IFfqhBBCCCHcgBR1QgghhBBuQIq6bqSUsiil7lVK7VJKNSilDimlnlVK9TKdzRUopR5SSi1USu1TSmmlVIHpTK5EKZWolPq9Umq1UqpUKVWtlNqklPqN/A12jFIqSSn1tlJqp1KqUilV1/p6fk4p1dd0PleklAo86TX9kuk8rqD1uWrvq8Z0NleilApXSj2jlMpv/UwuVUotV0qlms7mKN6mA7i554GfAx8BzwLDWv8/Tik1W2ttMxnOBTwBlAPfAGFmo7ikW4CfAp8CbwNNwAzgceB7SqkLtdb1BvO5gn5AX+yv4cNAMzAKuA24Rik1VmtdYjCfK/o9cNqkqeKccji9g3+TiSCuSCk1EMgEgoB/AHlAKDAaiDOXzLGkqOsmSqkRwM+AD7XWV5+0fT/wF+Aa4B1D8VzFEK31PgCl1DbsL0bRce8Df9RaV5607RWl1B7gN8CPALlSchZa6wwg49TtSqls4L/ATcBTPRzLZSmlLgDuAX6J/URXdNw+rfW/TYdwYf/GXvOM1lofMR2mu0jza/e5FlDAC6dsfw2oA67v6UCupq2gE+dHa73+lIKuzXut30f2ZB4307asYG+jKVyIUsoL+/vfYuBDw3FcklLKVyklJ7edpJSaDkwDntJaH1FK+SilAk3n6g5S1HWfiYANWHvyRq11A7Cpdb8QJvRr/V5sNIULUUr5K6UilVL9lFJzgf9r3bXIZC4Xcy+QDNxlOoiL+i72CwLVSqkSpdSLSqlQ06FcxKWt3w8qpT4D6oFapVSeUsqtLrBIUdd9YoEyrXVjO/sKgUillG8PZxIervVqyf/D3jdMmv877lagFDgEfIW9j+f1Wusck6FchVJqEPAo8HutdYHhOK5oLfAI9sLuh8Ay7MVxjly565Ck1u+vAeHYn8NbACvwllLqZlPBHE361HWfQKC9gg6g4aRjrD0TRwjA3h0gBfi11nq34Syu5GNgF/Z+neOAy4FIk4FczCvAPuA500FckdZ68imb/qWU2gL8Abi79bs4s+DW79XADK21FUAp9TH2v8snlFJvusPgRblS133qAL8z7PM/6RgheoRS6jHsZ/evaq3/aDqPK9FaH9ZaL9Vaf6y1/h32M/2nlFIPmc7m7Fqbt+YAd2itZbSm4zyN/aLAd0wHcQFto/zfbSvoALTWx7HPDtCH/13Nc2lS1HWfIuxNrO0VdnHYm2blKp3oEUqpR4DfAm8APzGbxvVprbcAG4E7TWdxZq3vf89h73t4VCk1VCk1FBjYekho67YwUxldVWuBXIRcMe6Iw63fj7azr20krFsMepKirvusw/78Tjp5o1LKHxgLrDeQSXig1oLud8CbwK1aFnx2lADs/XPEmQVgn5PuO8Cek74yW/df3/r/W02Ec2WtnyX9kAFPHdE2YLFfO/vatrnFfJNS1HWf9wCNfU6mk/0Ye1+6t3s6kPA8SqmHsRd0bwG3uEOfkZ6klOpzhu0zsE8Js7pnE7mcWmBBO19tVzgXt/7/UyPpXIBSKuIMux7D3i/+sx6M46o+xt6f7vqTB5a0rgozH8jTWuebieZYSk7au49S6kXsfZg+wt780LaixEpgpnzAnp1S6gb+10zzM8CX/01YekBr/ZaRYC5CKfVT7JMLH8Q+4vXUv7dirfXXPR7MhSilPsK+osQy7HPT+QPjsU8eXgeka603GQvoopRS8cB+4GWttUxxchZKqeeBC4Hl2F/LQdin6JgBrMHe8V9WhjkHpdRt2Kci2g68jv3z5A7sr+95WuslBuM5jBR13ah1+oh7sC8pFA+UYb+C97DWWtbsOwelVCaQdobdWVrr9J5L43qUUv/E3qH/TOQ5PAel1PeAG4Ex2JsRNfbi7mvgaa31QYPxXJYUdR2nlLoC+5XNkUAE0IK9yfq/wHOtc5+KDlBKXYV9NZNR2E9yc4FHtdYrjQZzICnqhBBCCCHcgPSpE0IIIYRwA1LUCSGEEEK4ASnqhBBCCCHcgBR1QgghhBBuQIo6IYQQQgg3IEWdEEIIIYQbkKJOCCGEEMINSFEnhBBCCOEGpKgTQgghhHADUtQJIYQQQrgBKeqEEEIIIdyAFHVCCOEASilvpdRKpVStUir5lH23KaW0Uur3pvIJIdyf0lqbziCEEG5BKTUQ2AQcACZrrRuVUiOAdcAGIF1r3WIwohDCjcmVOiGEcBCt9QHgR8AY4FmlVADwHtAA/EAKOiFEd5IrdUII4WBKqb8CdwCrgCnA1VrrD82mEkK4OynqhBDCwZRS/sA2YAjwmtb6NsORhBAeQJpfhRDC8cYAA1r/PVIp5W0yjBDCM0hRJ4QQDqSUCgHeBcqA3wApwKNGQwkhPIKcPQohhGO9CgwE5mitlymlxgG/Ukot1VovN5xNCOHGpE+dEEI4iFLqR8DfgSe01r9p3RaGfZoTH2C01vqYsYBCCLcmRZ0QQjhA64TDG7AXcGla6+aT9qUA2cCXWuvLzSQUQrg7KeqEEEIIIdyADJQQQgghhHADUtQJIYQQQrgBKeqEEEIIIdyAFHVCCCGEEG5AijohhBBCCDcgRZ0QQgghhBuQok4IIYQQwg1IUSeEEEII4QakqBNCCCGEcANS1AkhhBBCuIH/DzrnzHtOuaRwAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "from matplotlib import pyplot as plt\n", "plt.rc('font', size=18)\n", "\n", "\n", "X = np.linspace(0, 2*np.pi)\n", "Y1 = [h(x) for x in X]\n", "Y2 = [f(g(x)) for x in X]\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "ax.plot(X, Y1, \"r.\", label=\"h(x)\")\n", "ax.plot(X, Y2, label=\"f(g(x))\")\n", "ax.set_xlabel(\"x\")\n", "ax.set_ylabel(\"y\")\n", "ax.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Итератор по объекту. `__iter__`\n", "\n", "Если ваш элемент класс является контейнером, то может потребоваться возможность пробежаться по его элементам. Для этого необходимо, чтобы встроенная функция [iter](https://docs.python.org/3/library/functions.html#iter) возвращала итератор по этому объекту. Чтобы это работало у пользовательского класса, необходимо или реализовать оператор извлечения элемента по индексу (см. [](container_operators)) или реализовать метод `__iter__`, чтобы он возвращал итератор по нему.\n", "\n", "[Итератор](https://docs.python.org/3/glossary.html#term-iterator) в свою очередь должен поддерживать метод `__next__` и метод `__iter__`. Последний необходим для того, чтобы итератор сам по себе можно было использовать в цикле `for` (выражение `for x in iterable` первым шагом вызывает `iter(iterable)`) и в схожих контекстах. \n", "\n", "В ряде случаев проще всего реализовать метод `__iter__` в виде генератора, т.к. они автоматически поддерживают и метод `__next__` и метод `__iter__`. В качестве примера рассмотрим класс векторов трехмерного пространства и предположим, что мы хотим уметь итерироваться по его координатам. " ] }, { "cell_type": "code", "execution_count": 126, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "42 3 13 " ] } ], "source": [ "from collections import abc\n", "\n", "class Vector3D(abc.Iterable):\n", " def __init__(self, x, y, z):\n", " self.x = x\n", " self.y = y\n", " self.z = z\n", " \n", " def __iter__(self):\n", " yield self.x\n", " yield self.y\n", " yield self.z\n", "\n", "v = Vector3D(42, 3, 13)\n", "\n", "for coordinate in v:\n", " print(coordinate, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видим, метод `__iter__` является функцией-генератором. Объект генератора результата вызова `__init__()` по очереди возвращает координаты `x`, `y` и `z`.\n", "\n", "Теперь, в качестве альтернативы реализуем класс итератора `Iterator` по объектам `Vector3D` в явном виде. Теперь метод `Vector3D.__iter__` возвращает экземпляр класса `Iterator`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Создан итератор.\n", "42 3 13 " ] } ], "source": [ "from collections import abc \n", "\n", "class Vector3D(abc.Iterable):\n", " def __init__(self, x, y, z):\n", " self.x = x\n", " self.y = y\n", " self.z = z\n", " \n", " def __iter__(self):\n", " return Vector3D.Iterator(self)\n", "\n", " class Iterator(abc.Iterator):\n", " attributes = \"xyz\"\n", " def __init__(self, vector):\n", " print(\"Создан итератор.\")\n", " self.vector = vector\n", " self.current = 0\n", "\n", " def __iter__(self):\n", " return self\n", " \n", " def __next__(self):\n", " try:\n", " current_attribute = self.attributes[self.current]\n", " except IndexError:\n", " raise StopIteration\n", " \n", " self.current += 1\n", " return getattr(self.vector, current_attribute)\n", "\n", "v = Vector3D(42, 3, 13)\n", " \n", "for coordinate in v:\n", " print(coordinate, end=\" \") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- В целях инкапсуляции класс итератора объявлен прямо в теле исходного класса;\n", "- Метод `__init__` этого класса принимает на вход экземпляр класса, запоминает значения атрибутов класса и устанавливает значение счетчик равным нулю. Этот счетчик затем используется, чтобы запомнить атрибут, на котором мы остановились в методе `next`; \n", "- Метод `__next__` возвращает значение следующего атрибута, имя которого получается индексацией из строки `attributes`. Когда символы этой строки заканчиваются, то происходит обращение к символу строки за её пределами, что приводит к возбуждению исключения `IndexError`. Это исключение перехватывается и вместо него возбуждается `StopIteration`, что сигнализирует вызывающему коду, что итератор исчерпался;\n", "- Метод `__iter__` итератора возвращает ссылку на себя, что позволяет использовать его самого в цикле `for`, как в примере ниже." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Создан итератор.\n", "<__main__.Vector3D.Iterator object at 0x00000228CE202730>\n", "42 3 13 " ] } ], "source": [ "it = iter(v)\n", "print(it)\n", "\n", "for coordinate in it:\n", " print(coordinate, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Перегрузка операторов\n", "\n", "### Доступные операторы\n", "\n", "Как и во многих других языках программирования, в `python` можно перегружать действие операторов на пользовательские объекты. Все операторы в `python` могут быть представлены в форме специальных методов. Например, метод `__add__` из этого модуля соответствует оператору `+`." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "49\n" ] } ], "source": [ "x = 42\n", "y = 7\n", "\n", "print(x.__add__(y))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для всех остальных арифметических операторов, есть свои функции и, забегая вперед, специальные методы с такими же именами, но окруженные двойными нижними подчеркиваниями.\n", "\n", "#### Арифметические операторы\n", "\n", "| Арифметическое действие | Операторная форма | Метод |\n", "| ------------------------ | --------------------- | --------------------- |\n", "| Сложение | `a + b` | `a.__add__(b)` |\n", "| Вычитание | `a - b` | `a.__sub__(b)` |\n", "| Умножение | `a * b` | `a.__mul__(b)` |\n", "| Обычное деление | `a / b` | `a.__truediv__(a, b)` |\n", "| Целочисленное деление | `a // b` | `a.__floordiv__(b)` |\n", "| Остаток от деления | `a % b` | `a.__mod__(b)` |\n", "| Возведение в степень | `a ** b` | `a.__pow__(b)` |\n", "| Матричное умножение | `a @ b` | `a.__matmul__(b)` |\n", "| Положительный | `+a` | `a.__pos__()` |\n", "| Арифметическое отрицание | `-a` | `a.__neg__()` |\n", "\n", "```{note}\n", "Для каждого бинарного оператора существует аналог с префиксом `r` (например, `__radd__`) и аналог с префиксом `i` (например, `__iadd__`).\n", "```\n", "\n", "##### Операнд справа\n", "\n", "Если встречается выражение `a + b`, то сначала `python` пробует вызвать `a.__add__(b)`. Если такой метод у объекта `a` не определен или этот метод возвращает [NotImplemented](https://docs.python.org/3/library/constants.html#NotImplemented), то `python` пробует вызвать `b.__radd__(a)`. Если и он не определен или возвращает `NotImplemented`, то `python` возбуждает ошибку `TypeError`.\n", "\n", "##### Версия оператора на месте\n", "\n", "Если встречается выражение `a += b`, то сначала `python` попытается вызвать `a = a.__iadd__(b)`. Если такой метод у объекта `a` не определен, то `python` пробует свести выражение `a += b` к выражению `a = a + b`, т.е. `a = a.__add__(b)`. Далее процедура следует алгоритму, описанному в предыдущем абзаце.\n", "\n", "```{warning}\n", "Методы на месте должны возвращать ссылку на какой-то объект. Для неизменяемых типов это обычно ссылка на новый объект, для изменяемых --- ссылка на исходный объект (`self`, если вы следуете соглашениям). \n", "```\n", "\n", "##### Пример\n", "\n", "В качестве самого простого примера реализуем свой тип \"изменяемых\" целых чисел, который будет поддерживать все формы оператора умножения." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Умножение 3 на 7. Объект слева.\n", "21\n", "Умножение 42 на 13. Объект слева.\n", "546\n", "Умножение 42 на 13. Объект справа.\n", "Умножение 13 на 42. Объект слева.\n", "546\n" ] } ], "source": [ "from numbers import Number\n", "\n", "class MyMutableInt(Number):\n", " def __init__(self, value):\n", " if not isinstance(value, int):\n", " raise TypeError(\"Допускаются только целые числа\")\n", " self.value = value\n", "\n", " def __str__(self):\n", " return str(self.value)\n", "\n", " def __repr__(self):\n", " return f\"MyMutableInt(value={self.value})\"\n", "\n", " def __mul__(self, other):\n", " print(f\"Умножение {self} на {other}. Объект слева.\")\n", " if(isinstance(other, int)):\n", " return MyMutableInt(self.value * other)\n", " if(isinstance(other, MyMutableInt)):\n", " return MyMutableInt(self.value * other.value) \n", " return NotImplemented\n", " \n", " def __rmul__(self, other):\n", " print(f\"Умножение {other} на {self}. Объект справа.\")\n", " return self * other\n", "\n", " def __imul__(self, other):\n", " print(f\"Умножение {self} на {other} на месте\")\n", " if(isinstance(other, int)):\n", " self.value *= other\n", " return self\n", " if(isinstance(other, MyMutableInt)):\n", " self.value *= other.value\n", " return self \n", " return NotImplemented \n", "\n", "\n", "\n", "print(MyMutableInt(3) * MyMutableInt(7))\n", "print(MyMutableInt(42) * 13)\n", "print(42 * MyMutableInt(13))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Умножать экземпляры этого класса на встроенные целые числа можно в любом порядке за счет реализации метода `__rmul__`.\n", "\n", "```{note}\n", "Важно обратить внимание, что не все арифметические операции коммутативны, а значит в некоторых из них нельзя в методе с префиксом `r` просто поменять операнды местами.\n", "```" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Значение 3 по адресу 2017574962800\n", "Умножение 3 на 7 на месте\n", "Значение 21 по адресу 2017574962800\n" ] } ], "source": [ "x = MyMutableInt(3)\n", "print(f\"Значение {x} по адресу {id(x)}\")\n", "x *= 7\n", "print(f\"Значение {x} по адресу {id(x)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Умножать на месте можно за счет реализации метода `__imul__`. \n", "\n", "Все остальные операторы перегружаются аналогично. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Операторы сравнения\n", "\n", "| Сравнение на | Операторная форма | Метод |\n", "| ------------------------ | --------------------- | --------------------- |\n", "| Равенство | `a == b` | `a.__eq__(b)` |\n", "| Неравенство | `a != b` | `a.__ne__(b)` |\n", "| Меньше | `a < b` | `a.__lt__(b)` |\n", "| Меньше или равно | `a <= b` | `a.__le__(b)` |\n", "| Больше или равно | `a >= b` | `a.__ge__(b)` |\n", "| Больше | `a > b` | `a.__gt__(b)` |\n", "\n", "Все эти методы должны возвращать, `True`, `False` (или приводимое к ним значение), если сравнение произошло успешно, и `NotImplemented`, если сравнение с объектом такого типа не предусмотренно. В отличие от арифметических операций, не существует отраженных версий операторов сравнения с префиксом `r`. Вместо этого `a.__gt__(b)` (`a > b`) является отражением `b.__lt__(a)` (`b < a`) и т.п. Возвращенное значение `NotImplemented` приведет к вызову отраженного оператора. \n", "\n", "Метод `__eq__` по умолчанию эквивалентен `is`, т.е. сравнение двух экземпляров всегда даёт `False`, если только это не один и тот же объект.\n", "\n", "```{tip}\n", "Если множество всех экземпляров класса является [линейно упорядоченным](https://ru.wikipedia.org/wiki/%D0%9B%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D0%BE_%D1%83%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D1%87%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE), то можно воспользоваться декоратором [total_ordering](https://docs.python.org/3/library/functools.html#functools.total_ordering) из модуля [functools](https://docs.python.org/3/library/functools.html): необходимо определить лишь метод для `==` и один из операторов `<`, `<=`, `>=` и `>`, а остальные сгенерируются декоратором. \n", "```\n", "\n", "Множество целых чисел является линейно упорядоченным. Дополним пример с `MyMutableInt` операторами сравнения `<` и `==`, а остальные позволим сгенерировать декоратору `total_ordering`." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True True\n", "True True\n", "False\n" ] } ], "source": [ "from numbers import Number\n", "from functools import total_ordering\n", "\n", "@total_ordering\n", "class MyMutableInt(Number):\n", " def __init__(self, value):\n", " if not isinstance(value, int):\n", " raise TypeError(\"Допускаются только целые числа\")\n", " self.value = value\n", "\n", " def __str__(self):\n", " return str(self.value)\n", "\n", " def __repr__(self):\n", " return f\"MyMutableInt(value={self.value})\"\n", "\n", " def __lt__(self, other):\n", " if isinstance(other, int):\n", " return self.value < other\n", " if isinstance(other, MyMutableInt):\n", " return self.value < other.value\n", " return NotImplemented\n", "\n", " def __eq__(self, other):\n", " if isinstance(other, int):\n", " return self.value == other\n", " if isinstance(other, MyMutableInt):\n", " return self.value == other.value\n", " return NotImplemented\n", "\n", "print(MyMutableInt(3) < MyMutableInt(7), MyMutableInt(3) <= 7)\n", "print(MyMutableInt(42) > 13, MyMutableInt(42) >= 13)\n", "print(42 == MyMutableInt(13))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Все операторы сравнения работают. В качестве бонуса мы теперь можем сортировать списки чисел типа `MyMutableInt`." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[MyMutableInt(value=6), MyMutableInt(value=-1), MyMutableInt(value=-4), MyMutableInt(value=5), MyMutableInt(value=10)]\n", "[MyMutableInt(value=-4), MyMutableInt(value=-1), MyMutableInt(value=5), MyMutableInt(value=6), MyMutableInt(value=10)]\n" ] } ], "source": [ "from random import randint\n", "\n", "L = [MyMutableInt(randint(-10, 10)) for _ in range(5)]\n", "\n", "print(L)\n", "print(sorted(L))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Побитовые операторы\n", "\n", "| Операция | Операторная форма | Метод |\n", "| --------------------------- | --------------------- | ------------------------------ |\n", "| Побитовое \"и\" | `a & b` | `a.__and__(b)` |\n", "| Побитовое \"или\" | `a \\| b` | `a.__or__(b)` |\n", "| Побитовое \"исключающее или\" | `a ^ b` | `a.__xor__(b)` |\n", "| Побитовое \"не\" | `~ a` | `a.__invert__()` |\n", "| Битовый сдвиг влево | `a << b` | `a.__lshift__(b)` |\n", "| Битовый сдвиг вправо | `a >> b` | `a.__rshift__(b)` |\n", "\n", "```{note}\n", "Для всех бинарных побитовых операторов справедливы все те же замечания, что и для арифметических операторов, т.е. есть аналогичные методы с префиксами `r` и `i` для выполнения операций, когда объект стоит справа от оператора, и для выполнения операций на месте.\n", "```\n", "\n", "(container_operators)=\n", "#### Операторы контейнеров\n", "\n", "| Операция | Операторная форма | Метод |\n", "| ------------------------ | --------------------- | ------------------------------ |\n", "| Проверка принадлежности | `obj in collection` | `collection.__contains__(obj)` |\n", "| Обращение по индексу | `obj[k]` | `obj.__getitem__(k)` |\n", "| Присваивание по индексу | `obj[k] = v` | `obj.__setitem__(k, v)` |\n", "| Удаление по индексу | `del obj[k]` | `obj.__delitem__(k)` |\n", "\n", "Метод `__getitem__` мы неявно вызывали уже много раз. У списков и кортежей этот метод позволяет получить элемент по индексу, у строк получить символ. У словарей он позволяет получить значение по ключу. Также этот метод реализован у массивов `NumPy` и у таблиц `pandas`.\n", "\n", "В качестве примера рассмотрим ещё одну возможную реализацию диапазона действительных чисел. Пусть конструктор принимает параметры `start`, `stop` и `step`, а метод `__getitem__` возвращает `k`-й элемент последовательности, который вычисляется по формуле \n", "\n", "$$\n", " \\text{frange}[k] = \\text{start} + \\text{step} * k,\n", "$$\n", "\n", "где $k$ в диапазоне от $0$ до длинны последовательности $\\text{length}$, которая вычисляется по формуле\n", "\n", "$$\n", " \\text{length} = \\left\\lfloor\\dfrac{\\text{stop} - \\text{start}}{\\text{start}}\\right\\rfloor + 1.\n", "$$\n", "\n", "Вынесем вычисление $k$-го элемента последовательности в отдельный не публичный метод и предусмотрим индексацию отрицательными индексами." ] }, { "cell_type": "code", "execution_count": 106, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.0 0.5 1.0 1.5 2.0 2.5 3.0 0.0 0.5 1.0 1.5 2.0 2.5 3.0 " ] } ], "source": [ "from collections.abc import Sequence\n", "from math import floor\n", "\n", "class FloatRange(Sequence):\n", " def __init__(self, start, stop, step=1.0):\n", " self.start = start\n", " self.stop = stop\n", " if step == 0:\n", " raise ValueError(\"Параметр step не может быть равен нулю\")\n", " self.step = step\n", " \n", " self.length = floor((self.stop - self.start) / self.step) + 1\n", " \n", " def __str__(self):\n", " return f\"range({self.start}, {self.stop}, {self.step})\"\n", "\n", " def __repr__(self):\n", " return f\"FloatRange(start={self.start}, stop={self.stop}, step={self.step})\" \n", "\n", " def __len__(self):\n", " return self.length\n", "\n", " def _kth_value(self, k):\n", " return self.start + k * self.step\n", " \n", " def __getitem__(self, k):\n", " if isinstance(k, int):\n", " if k < -self.length or k >= self.length:\n", " raise IndexError(\"Индекс за пределами диапазона\")\n", " k = k + self.length if k < 0 else k\n", " return self._kth_value(k)\n", " raise TypeError(\"Индексация возможна только целым числом\")\n", "\n", "r = FloatRange(0., 3, 0.5)\n", "for i in range(-r.length, r.length):\n", " print(r[i], end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Класс [collections.abc.Sequence](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence) --- абстрактный базовый класс с абстрактными методами `__len__` и `__getitem__`, т.е. то, что мы наследовали от него, принуждает нас реализовать не только метод `__getitem__`, но и метод [\\_\\_len\\_\\_](https://docs.python.org/3/reference/datamodel.html#object.__len__). Зато теперь для объектов этого класса автоматически генерируется ряд методов, такие как `__contains__` и `__iter__`. Такие сгенерированные методы называют [примесями](https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%81%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)) ([mixin](https://en.wikipedia.org/wiki/Mixin)).\n", "\n", "\n", "Это значит, что мы можем итерировать по объектам `FloatRange`, не реализуя специальный метод `__iter__`. " ] }, { "cell_type": "code", "execution_count": 102, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.0 2.5 2.0 1.5 1.0 0.5 0.0 " ] } ], "source": [ "for x in FloatRange(3.0, 0.0, -0.5):\n", " print(x, end=\" \")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А так же проверять элемент на принадлежность диапазону." ] }, { "cell_type": "code", "execution_count": 103, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "r = FloatRange(0., 3, 0.5)\n", "\n", "print(1.5 in r)\n", "print(2.71 in r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Но если попробовать получить срезу, то получиться ошибка, т.к. мы это не предусмотрели в методе `__getitem__`." ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [], "source": [ "try:\n", " r[1:3]\n", "except TypeError as msg:\n", " print(msg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Операторы последовательностей\n", "\n", "| Операция | Операторная форма | Метод |\n", "| ------------------------ | --------------------- | --------------------------------------- |\n", "| Обращение по срезу | `seq[i:j]` | `seq.__getitem__(slice(i, j))` |\n", "| Присваивание по срезу | `seq[i:j] = values` | `seq.__setitem__(slice(i, j), values)` |\n", "| Удаление по срезу | `del seq[i:j]` | `seq.__detitem__(slice(i, j)` |\n", "\n", "Чтобы можно было получать срез, необходимо предусмотреть ситуацию, когда в функцию `__getitem__` передано не целое число, а объект [slice](https://docs.python.org/3/library/functions.html#slice).\n", "\n", "```{note}\n", "Выражение `a[start:stop:step]` эквивалентно `a[slice(start, stop, step)]`. \n", "```\n", "\n", "Расширим предыдущий пример функциональностью среза. " ] }, { "cell_type": "code", "execution_count": 105, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "range(0, 3, 0.5)[1:5:2]->range(0.5, 2.5, 1.0)\n", "range(0, 3, 0.5)[5:1:-1]->range(2.5, 0.5, -0.5)\n" ] } ], "source": [ "from collections.abc import Sequence\n", "from math import floor\n", "\n", "class FloatRange(Sequence):\n", " def __init__(self, start, stop, step=1.0):\n", " self.start = start\n", " self.stop = stop\n", " if step == 0:\n", " raise ValueError(\"Параметр step не может быть равен нулю.\")\n", " self.step = step\n", " \n", " self.length = floor((self.stop - self.start) / self.step) + 1\n", " \n", " def __str__(self):\n", " return f\"range({self.start}, {self.stop}, {self.step})\"\n", "\n", " def __repr__(self):\n", " return f\"FloatRange(start={self.start}, stop={self.stop}, step={self.step})\" \n", "\n", " def __len__(self):\n", " return self.length\n", "\n", " def _kth_value(self, k):\n", " return self.start + k * self.step\n", " \n", " def __getitem__(self, k):\n", " if isinstance(k, int):\n", " if k < -self.length or k >= self.length:\n", " raise IndexError(\"Индекс за пределами диапазона\")\n", " k = k + self.length if k < 0 else k\n", " return self._kth_value(k)\n", " \n", " if isinstance(k, slice):\n", " slice_start = k.start if k.start else 0\n", " slice_stop = k.stop\n", " slice_step = k.step if k.step else 1\n", " \n", " new_start = self._kth_value(slice_start)\n", " new_stop = self._kth_value(slice_stop)\n", " new_step = self.step * slice_step\n", " \n", " return FloatRange(new_start, new_stop, new_step)\n", " \n", " raise TypeError(\"Индексация возможна только целым числом или срезом\")\n", "\n", "r = FloatRange(0, 3, 0.5)\n", "\n", "print(f\"{r}[1:5:2]->{r[1:5:2]}\")\n", "print(f\"{r}[5:1:-1]->{r[5:1:-1]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Метод `__getitem__` дополнился новым блоком кода.\n", "```python\n", "if isinstance(k, slice):\n", " slice_start = k.start if k.start else 0\n", " slice_stop = k.stop\n", " slice_step = k.step if k.step else 1\n", " \n", " new_start = self._kth_value(slice_start)\n", " new_stop = self._kth_value(slice_stop)\n", " new_step = self.step * slice_step\n", " \n", " return FloatRange(new_start, new_stop, new_step)\n", "```\n", "\n", "Параметры `start` и `step` среза по умолчанию приравниваются к `None`. Первые три строки извлекают эти параметры и обрабатывают возможный `None`. Следующие три строки вычисляют границы и шаг нового диапазона, который и возвращается в качестве результата." ] } ], "metadata": { "interpreter": { "hash": "cd49b4596bae9c980ff74fdf93e8fe80e447435ae307c062fad6c4f9ef2eb47f" }, "kernelspec": { "display_name": "Python 3.8.10 ('venv': venv)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }