{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Metaklasy\n", "\n", "Metaklasą nazywamy obiekt (najczęściej klasę) generujący inne klasy.\n", "\n", "*\"Metaclasses are deeper magic than 99% of users should ever worry about.\n", "If you wonder whether you need them, you don't\"*\n", "\n", "*-- Python Guru Tim Peters*\n", "\n", "## Klasy jako obiekty\n", "\n", "Podobnie jak w przypadku funkcji, klasy są obiektami.\n", "Służą do tworzenia nowych obiektów (instancji)." ] }, { "cell_type": "code", "execution_count": 145, "metadata": {}, "outputs": [], "source": [ "class MyClass:\n", " pass" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Nowy obiekt jest tworzony przy pomocy operatora ``()``. Jego typ to nazwa klasy." ] }, { "cell_type": "code", "execution_count": 146, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.MyClass" ] }, "execution_count": 146, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mc = MyClass()\n", "type(mc)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Jakiego typu jest obiekt klasy?" ] }, { "cell_type": "code", "execution_count": 147, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "type" ] }, "execution_count": 147, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(MyClass)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Dynamiczne tworzenie klas\n", "\n", "Skoro są obiektami typu *type*, to możemy je dynamicznie tworzyć:" ] }, { "cell_type": "code", "execution_count": 148, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.choose_class..WindowsSpecificClass" ] }, "execution_count": 148, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def choose_class(system: str):\n", " if system == \"windows\":\n", " class WindowsSpecificClass:\n", " pass\n", " return WindowsSpecificClass\n", " else:\n", " class DefaultClass:\n", " pass\n", " return DefaultClass\n", " \n", "os_class = choose_class(\"windows\")\n", "\n", "os_class" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Skoro klasa jest typu *type*, to może można użyć go tak jak klasy?\n", "\n", "``type`` przyjmuje trzy argumenty:\n", "* nazwa klasy\n", "* krotka z rodzicami klasy\n", "* słownik zawierający nazwy atrybutów i ich wartości" ] }, { "cell_type": "code", "execution_count": 159, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "123" ] }, "execution_count": 159, "metadata": {}, "output_type": "execute_result" } ], "source": [ "attrs = {'foo': \"foo_value\", 'bar': 123}\n", "\n", "DynamicClass = type(\"DynamicClass\", (), attrs)\n", "\n", "dc = DynamicClass()\n", "dc.bar" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Dziedziczenie i metaklasy\n", "\n", "Używając instrukcji ``class`` można zdefiniować klasę dziedziczącą po innej klasie w następujący sposób:" ] }, { "cell_type": "code", "execution_count": 160, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "665" ] }, "execution_count": 160, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class MyClassChild(DynamicClass):\n", " bar = 665\n", "\n", "child_mc = MyClassChild()\n", "child_mc.bar" ] }, { "cell_type": "code", "execution_count": 152, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'foo_value'" ] }, "execution_count": 152, "metadata": {}, "output_type": "execute_result" } ], "source": [ "child_mc.foo" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Jeżeli chcemy stworzyć klasę dynamicznie (z użyciem *type*), to jako drugi argument możemy przekazać krotkę rodziców:" ] }, { "cell_type": "code", "execution_count": 153, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "65" ] }, "execution_count": 153, "metadata": {}, "output_type": "execute_result" } ], "source": [ "DynamicClassChild = type(\"DynamicClassChild\", (DynamicClass,), {'bar': 65})\n", "child_dc = DynamicClassChild()\n", "child_dc.bar" ] }, { "cell_type": "code", "execution_count": 155, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'foo_value'" ] }, "execution_count": 155, "metadata": {}, "output_type": "execute_result" } ], "source": [ "child_dc.foo" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Metody klasy\n", "\n", "Metody też mogą stanowić część słownika przekazywaną do `type`:" ] }, { "cell_type": "code", "execution_count": 158, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Echo: foo_value\n" ] } ], "source": [ "def echo(self):\n", " print(f\"Echo: {self.foo}\")\n", "\n", "AnotherDynamicClass = type(\"AnotherDynamicClass\", (DynamicClass,), {'bar': 42, 'echo' : echo})\n", "\n", "acd = AnotherDynamicClass()\n", "acd.echo()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Metaklasy\n", "\n", "*type* jest więc wbudowaną w Pythona metaklasą.\n", "\n", "Jednakże istnieje możliwość stworzenia własnych metaklas.\n", "\n", "Pod Pythonem 3, składnia jest następująca:\n", "\n", "```python\n", "class MyClass(object, metaclass=class_creator):\n", " ...\n", "```\n", "\n", "gdzie ``class_creator`` to specjalny obiekt, którego należy użyć zamiast `type` do utworzenia obiektu klasy.\n", "\n", "### Funkcja jako metaklasa\n", "\n", "W szczególności, metaklasą może być funkcja.\n", "Poniżej przedstawiono metaklasę, która konwertuje nazwy wszystkich atrybutów tak, aby używały wielkich liter." ] }, { "cell_type": "code", "execution_count": 161, "metadata": {}, "outputs": [], "source": [ "def upper_attr(cls, parents, attrs):\n", " _attrs = ((name.upper(), value)\n", " for name, value in attrs.items())\n", " attrs_upper = dict(_attrs)\n", " return type(cls, parents, attrs_upper)\n", "\n", "class Foo(metaclass=upper_attr):\n", " bar = 'foo'" ] }, { "cell_type": "code", "execution_count": 162, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'foo'" ] }, "execution_count": 162, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo = Foo()\n", "foo.BAR" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Przykład metaklasy\n", "\n", "Zazwyczaj jednak metaklasa jest klasą." ] }, { "cell_type": "code", "execution_count": 163, "metadata": {}, "outputs": [], "source": [ "class UpperAttr(type):\n", " def __new__(cls, name, parents, attrs):\n", " _attrs = ((name.upper(), value)\n", " for name, value in attrs.items())\n", " attrs_upper = dict(_attrs)\n", " return type(name, parents, attrs_upper)\n", "\n", "class Boo(object, metaclass=UpperAttr):\n", " bar = 'boo'" ] }, { "cell_type": "code", "execution_count": 164, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'foo'" ] }, "execution_count": 164, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo = Foo()\n", "foo.BAR" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Metoda specjalna `__new__`\n", "\n", "``__new__(cls, ...)`` jest metodą wywoływaną, aby utworzyć nową instancję obiektu.\n", "Przekazywany jest do niej *obiekt klasy* oraz argumenty konstruktora.\n", "\n", "Dla porównania, ``__init__(self, ...)`` jest konstruktorem i jest wykonywany, gdy inicjalizowany jest obiekt (instancja).\n", "Przekazywany jest do niego *obiekt instancji*.\n", "\n", "## Szablon metaklasy\n", "\n", "Ponieważ metaklasy są tak naprawdę zwykłymi klasami, to mogą podlegać dziedziczeniu.\n", "Aby zapewnić bezproblemowe dziedziczenie, musimy użyć ``super()``:" ] }, { "cell_type": "code", "execution_count": 166, "metadata": {}, "outputs": [], "source": [ "class UpperAttrChild(type):\n", " def __new__(cls, name, parents, attrs):\n", " _attrs = ((name.upper(), value) for name, value in attrs.items())\n", " attrs_upper = dict(_attrs)\n", " return super(UpperAttrChild, cls).__new__(cls, name, parents, attrs_upper)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## `__call__` metaklasy\n", "\n", "Ciekawym aspektem jest użycie metody `__call__`.\n", "Jest ona wywoływana dla gotowego obiektu klasy wtedy, gdy używamy \"wywołania\" (operatora ``()``)" ] }, { "cell_type": "code", "execution_count": 167, "metadata": {}, "outputs": [], "source": [ "class Meta(type):\n", " def __call__(cls, *args, **kwargs):\n", " print('__call__ of ', str(cls))\n", " print('__call__ *args=', str(args))\n", " return type.__call__(cls, *args, **kwargs)" ] }, { "cell_type": "code", "execution_count": 168, "metadata": {}, "outputs": [], "source": [ "class Gadget(metaclass=Meta):\n", " def __init__(self, name, price):\n", " self.name = name\n", " self.price = price\n", " print(f\"Initializing Gadget({self.name}, {self.price})\")" ] }, { "cell_type": "code", "execution_count": 169, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "__call__ of \n", "__call__ *args= ('ipad', 1500.0)\n", "Initializing Gadget(ipad, 1500.0)\n" ] } ], "source": [ "g = Gadget(\"ipad\", 1500.0)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Zastosowanie metaklas\n", "\n", "Metaklasy są nazywane \"rozwiązaniem szukającym problemu\".\n", "\n", "W zdecydowanej większości sytuacji ich użycie nie jest konieczne.\n", "\n", "W praktyce, metaklasy są stosowane tam, gdzie API klasy musi być tworzone dynamicznie (np. ORM w Django) oraz do implementacji niektórych wzorców projektowych (np. singleton)." ] } ], "metadata": { "kernelspec": { "display_name": "jb", "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.8" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }