{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#default_exp meta" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from fastcore.imports import *\n", "from contextlib import contextmanager\n", "from copy import copy\n", "import inspect" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fastcore.test import *\n", "from fastcore.foundation import *\n", "from nbdev.showdoc import *\n", "from fastcore.nb_imports import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Meta\n", "\n", "> Metaclasses" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "See this [blog post](https://realpython.com/python-metaclasses/) for more information about metaclasses. \n", "\n", "- `FixSigMeta` preserves information that enables [intropsection of signatures](https://www.python.org/dev/peps/pep-0362/#:~:text=Python%20has%20always%20supported%20powerful,fully%20reconstruct%20the%20function's%20signature.) (i.e. tab completion in IDEs) when certain types of inheritence would otherwise obfuscate this introspection.\n", "- `PrePostInitMeta` ensures that the classes defined with it run `__pre_init__` and `__post_init__` (without having to write `self.__pre_init__()` and `self.__post_init__()` in the actual `init`\n", "- `NewChkMeta` gives the `PrePostInitMeta` functionality and ensures classes defined with it don't re-create an object of their type whenever it's passed to the constructor\n", "- `BypassNewMeta` ensures classes defined with it can easily be casted form objects they subclass." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def test_sig(f, b): \n", " \"Test the signature of an object\"\n", " test_eq(str(inspect.signature(f)), b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def func_1(h,i,j): pass\n", "def func_2(h,i=3, j=[5,6]): pass\n", "\n", "class T:\n", " def __init__(self, a, b): pass\n", "\n", "test_sig(func_1, '(h, i, j)')\n", "test_sig(func_2, '(h, i=3, j=[5, 6])')\n", "test_sig(T, '(a, b)')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "def _rm_self(sig):\n", " sigd = dict(sig.parameters)\n", " sigd.pop('self')\n", " return sig.replace(parameters=sigd.values())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class FixSigMeta(type):\n", " \"A metaclass that fixes the signature on classes that override `__new__`\"\n", " def __new__(cls, name, bases, dict):\n", " res = super().__new__(cls, name, bases, dict)\n", " if res.__init__ is not object.__init__: res.__signature__ = _rm_self(inspect.signature(res.__init__))\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "
class
FixSigMeta
[source]FixSigMeta
(**`name`**, **`bases`**, **`dict`**) :: `type`\n",
"\n",
"A metaclass that fixes the signature on classes that override `__new__`"
],
"text/plain": [
"class
PrePostInitMeta
[source]PrePostInitMeta
(**`name`**, **`bases`**, **`dict`**) :: [`FixSigMeta`](/meta.html#FixSigMeta)\n",
"\n",
"A metaclass that calls optional `__pre_init__` and `__post_init__` methods"
],
"text/plain": [
"class
NewChkMeta
[source]NewChkMeta
(**`name`**, **`bases`**, **`dict`**) :: [`FixSigMeta`](/meta.html#FixSigMeta)\n",
"\n",
"Metaclass to avoid recreating object passed to constructor"
],
"text/plain": [
"class
BypassNewMeta
[source]BypassNewMeta
(**`name`**, **`bases`**, **`dict`**) :: [`FixSigMeta`](/meta.html#FixSigMeta)\n",
"\n",
"Metaclass: casts `x` to this class if it's of type `cls._bypass_type`"
],
"text/plain": [
"