{ "cells": [ { "cell_type": "markdown", "id": "668e21b4", "metadata": {}, "source": [ "# Quickstart\n", "\n", "In real world projects, maintaining a large amount of parameters for user inputs/options, function input variables and program runtime status is almost inevitable. A clear, robust and extensible design of parameter structures, rules and restrictions greatly contributes to the quality of project codes. Paramdict provides a Pythonic way to achieve this goal: parameters are stored in a dictionary, wrapped simply with a class and several decorators.\n", "\n", "## What is a Paramdict\n", "\n", "A paramdict defines a specific organization of a set of parameters: what is the type of the parameter, whether to strictly check the type, can this parameter be left blank, is there any limit about the parameter's range, etc. Following is an exmaple:" ] }, { "cell_type": "code", "execution_count": 1, "id": "67fa7f90", "metadata": {}, "outputs": [], "source": [ "from paramdict import *\n", "\n", "class pdTest(baseParamDict):\n", "\n", " def __init__(self, id:str='', source=None):\n", " super().__init__(id, source)\n", " def _setcls(self):\n", " return 'pdtest'\n", " def _pdinit(self):\n", " pass\n", " def _check(self):\n", " return super()._check()\n", "\n", " @pSimple\n", " def a(self) -> int: ...\n", "\n", " @pSimple\n", " def b(self) -> float: ...\n", "\n", " @pStrict\n", " def c(self) -> str: ...\n", "\n", " @pList\n", " def d(self) -> list[int]: ..." ] }, { "cell_type": "markdown", "id": "a790880f", "metadata": {}, "source": [ "Here, a paramdict class named \"pdtest\" with 4 parameters, \"a\", \"b\", \"c\", and \"d\", is defined. Any paramdict instance of this class will be able to and only able to contain these 4 parameters. The decorators, `pSimple`, `pStrict`, and `pList`, works like Python's built-in `property` decorator, except that setting method is implemented automatically. So to set or get a parameter:" ] }, { "cell_type": "code", "execution_count": 2, "id": "cc808bbd", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1\n", "b: 2.0\n", "c: 3\n", "d: [4, 5, 6]\n" ] } ], "source": [ "pd1 = pdTest()\n", "\n", "# set values\n", "pd1.a = 1\n", "pd1.b = 2.0\n", "pd1.c = '3'\n", "pd1.d = [4, 5, 6]\n", "\n", "# get values\n", "print(f\"a: {pd1.a}\")\n", "print(f\"b: {pd1.b}\")\n", "print(f\"c: {pd1.c}\")\n", "print(f\"d: {pd1.d}\")" ] }, { "cell_type": "markdown", "id": "b7e0f745", "metadata": {}, "source": [ "Annotations matters for the decorated parameter methods. When setting a parameter decorated by `pSimple`, the input value will be automatically converted to the annotated type:" ] }, { "cell_type": "code", "execution_count": 3, "id": "ded16245", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 3\n", "Type of a: \n" ] } ], "source": [ "pd1.a = 3.0\n", "print(f\"a: {pd1.a}\")\n", "print(f\"Type of a: {type(pd1.a)}\")" ] }, { "cell_type": "markdown", "id": "68cbbf65", "metadata": {}, "source": [ "`pStrict` behaves differently from `pSimple`. It checks the type of input value, and raise errors when input type is not the annotated type and does not inherit from the annotated type:" ] }, { "cell_type": "code", "execution_count": 4, "id": "d0629af1", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Property \u001b[93m'c'\u001b[0m must be of type \u001b[92m\u001b[0m.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[4], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m pd1\u001b[38;5;241m.\u001b[39mc \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n", "File \u001b[0;32m~/utils/paramdict/paramDictProperties.py:79\u001b[0m, in \u001b[0;36mpStrict.__set__\u001b[0;34m(self, instance, value)\u001b[0m\n\u001b[1;32m 77\u001b[0m aType:\u001b[38;5;28mtype\u001b[39m \u001b[38;5;241m=\u001b[39m get_type_hints(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfget)[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mreturn\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 78\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(value, aType):\n\u001b[0;32m---> 79\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(_(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mProperty \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m must be of type \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m%\u001b[39m (outYellow(\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\'\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;241m+\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfget\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;241m+\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\'\u001b[39;00m\u001b[38;5;124m'\u001b[39m), outGreen(aType)))\n\u001b[1;32m 80\u001b[0m instance\u001b[38;5;241m.\u001b[39m_cont[\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfget\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m] \u001b[38;5;241m=\u001b[39m value\n", "\u001b[0;31mTypeError\u001b[0m: Property \u001b[93m'c'\u001b[0m must be of type \u001b[92m\u001b[0m." ] } ], "source": [ "pd1.c = 1" ] }, { "cell_type": "markdown", "id": "9346d4bf", "metadata": {}, "source": [ "`pList` not only checks if the input value is a list, but also if all the elements are of the right type." ] }, { "cell_type": "code", "execution_count": 5, "id": "87e8e077", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "Property \u001b[93m'd'\u001b[0m must be of type \u001b[92mtyping.List[int]\u001b[0m.", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[5], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m pd1\u001b[38;5;241m.\u001b[39md \u001b[38;5;241m=\u001b[39m [\u001b[38;5;241m4\u001b[39m, \u001b[38;5;241m5\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124m6\u001b[39m\u001b[38;5;124m'\u001b[39m]\n", "File \u001b[0;32m~/utils/paramdict/paramDictProperties.py:110\u001b[0m, in \u001b[0;36mpList.__set__\u001b[0;34m(self, instance, value)\u001b[0m\n\u001b[1;32m 108\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m item \u001b[38;5;129;01min\u001b[39;00m value:\n\u001b[1;32m 109\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(item, aType):\n\u001b[0;32m--> 110\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(_(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mProperty \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m must be of type \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[38;5;124m.\u001b[39m\u001b[38;5;124m'\u001b[39m) \u001b[38;5;241m%\u001b[39m (outYellow(\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\'\u001b[39;00m\u001b[38;5;124m'\u001b[39m\u001b[38;5;241m+\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfget\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m\u001b[38;5;241m+\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;130;01m\\'\u001b[39;00m\u001b[38;5;124m'\u001b[39m), outGreen(List[aType])))\n\u001b[1;32m 111\u001b[0m instance\u001b[38;5;241m.\u001b[39m_cont[\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfget\u001b[38;5;241m.\u001b[39m\u001b[38;5;18m__name__\u001b[39m] \u001b[38;5;241m=\u001b[39m value\n", "\u001b[0;31mTypeError\u001b[0m: Property \u001b[93m'd'\u001b[0m must be of type \u001b[92mtyping.List[int]\u001b[0m." ] } ], "source": [ "pd1.d = [4, 5, '6']" ] }, { "cell_type": "markdown", "id": "0abb3d71", "metadata": {}, "source": [ "## Paramdict Nested in Paramdict\n", "\n", "An important trait of paramdict is that a paramdict can be nested in another paramdict while maintaining all the functionality:" ] }, { "cell_type": "code", "execution_count": 6, "id": "fe628ade", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'class': 'pdtest2',\n", " 'id': '',\n", " 'param_pdtest': {'class': 'pdtest',\n", " 'id': '',\n", " 'a': 10,\n", " 'b': 2.0,\n", " 'c': '3',\n", " 'd': [4, 5, 6]},\n", " 'param_pdtest_list': [{'class': 'pdtest',\n", " 'id': '',\n", " 'a': 3,\n", " 'b': 20.0,\n", " 'c': '3',\n", " 'd': [4, 5, 6]},\n", " {'class': 'pdtest', 'id': '', 'a': 3, 'b': 2.0, 'c': '30', 'd': [4, 5, 6]}]}" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from copy import deepcopy\n", "\n", "class pdTest2(baseParamDict):\n", "\n", " def __init__(self, id:str='', source=None):\n", " super().__init__(id, source)\n", " def _setcls(self):\n", " return 'pdtest2'\n", " def _pdinit(self):\n", " pass\n", " def _check(self):\n", " return super()._check()\n", "\n", " @pPd\n", " def param_pdtest(self) -> pdTest: ...\n", "\n", " @pPdList\n", " def param_pdtest_list(self) -> list[pdTest]: ...\n", "\n", "pd2 = pdTest2()\n", "pd2.param_pdtest = deepcopy(pd1)\n", "pd2.param_pdtest.a = 10\n", "pd2.param_pdtest_list = [deepcopy(pd1), deepcopy(pd1)]\n", "pd2.param_pdtest_list[0].b = 20.0\n", "pd2.param_pdtest_list[1].c = '30'\n", "\n", "pd2.content" ] }, { "cell_type": "markdown", "id": "329674c4", "metadata": {}, "source": [ "This paramdict class contains two parameters: \"param_pdtest\" is a paramdict that follows `pdTest` paramdict class definition, and \"param_pdtest_list\" is a list that contains paramdicts that also follows `pdTest`. Then a paramdict instance (pd2) is initialized and modified. By referring the `content` built-in property, we can check the dictionary where values are actually stored.\n", "\n", "## Persistence\n", "\n", "It is also easy to serialize the content:" ] }, { "cell_type": "code", "execution_count": 7, "id": "df4f5a21", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"class\": \"pdtest2\",\n", " \"id\": \"\",\n", " \"param_pdtest\": {\n", " \"class\": \"pdtest\",\n", " \"id\": \"\",\n", " \"a\": 10,\n", " \"b\": 2.0,\n", " \"c\": \"3\",\n", " \"d\": [\n", " 4,\n", " 5,\n", " 6\n", " ]\n", " },\n", " \"param_pdtest_list\": [\n", " {\n", " \"class\": \"pdtest\",\n", " \"id\": \"\",\n", " \"a\": 3,\n", " \"b\": 20.0,\n", " \"c\": \"3\",\n", " \"d\": [\n", " 4,\n", " 5,\n", " 6\n", " ]\n", " },\n", " {\n", " \"class\": \"pdtest\",\n", " \"id\": \"\",\n", " \"a\": 3,\n", " \"b\": 2.0,\n", " \"c\": \"30\",\n", " \"d\": [\n", " 4,\n", " 5,\n", " 6\n", " ]\n", " }\n", " ]\n", "}\n" ] } ], "source": [ "import json\n", "\n", "json_str = json.dumps(pd2.content, indent=4)\n", "print(json_str)" ] }, { "cell_type": "markdown", "id": "085ccaac", "metadata": {}, "source": [ "Or to deserialize:" ] }, { "cell_type": "code", "execution_count": 8, "id": "4a8bef45", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'class': 'pdtest2',\n", " 'id': '',\n", " 'param_pdtest': {'class': 'pdtest',\n", " 'id': '',\n", " 'a': 10,\n", " 'b': 2.0,\n", " 'c': '3',\n", " 'd': [4, 5, 6]},\n", " 'param_pdtest_list': [{'class': 'pdtest',\n", " 'id': '',\n", " 'a': 3,\n", " 'b': 20.0,\n", " 'c': '3',\n", " 'd': [4, 5, 6]},\n", " {'class': 'pdtest', 'id': '', 'a': 3, 'b': 2.0, 'c': '30', 'd': [4, 5, 6]}]}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pdcont = json.loads(json_str)\n", "pd3 = pdTest2(source=pdcont)\n", "\n", "pd3.content" ] } ], "metadata": { "kernelspec": { "display_name": "ADF", "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.12.4" } }, "nbformat": 4, "nbformat_minor": 5 }