{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# farben terminal\n",
    "to use colors in terminal printing you need to use the appropraite strings a list of them can be found here: https://gist.github.com/minism/1590432\n",
    "\n",
    "remember that the codes are based on your OS, thus the codes are different for windows, mac etc.\n",
    "\n",
    "\n",
    "Lets use this stuff within a class and let magic happen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class colors:\n",
    "    RED   = \"\\033[1;31m\"\n",
    "    BLUE  = \"\\033[1;34m\"\n",
    "    CYAN  = \"\\033[1;36m\"\n",
    "    GREEN = \"\\033[0;32m\"\n",
    "    RESET = \"\\033[0;0m\"\n",
    "    BOLD    = \"\\033[;1m\"\n",
    "    REVERSE = \"\\033[;7m\""
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "play around and get a feeling for them, they are pretty straight forward\n",
    "\n",
    "there are also ready to use librarys like 'termcolor' or 'colored' and way ore... find one you like or use the codes themself\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# f-strings are neat way to write strings\n",
    "they are basically functional strings, meaning they support variables, calculations and even code\n",
    "\n",
    "all variables, calculations and so on and so forth must be enclosed curly braces '{}'\n",
    "\n",
    "look at the examples below and play withem to get an intuitiv understanding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HI\n",
      "HI but cooler\n",
      "1 + 2 = 3\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "print(f\"HI\")\n",
    "\n",
    "val = \"HI but cooler\"\n",
    "print(f\"{val}\")\n",
    "\n",
    "num1 = 1\n",
    "num2 = 2\n",
    "\n",
    "print(f\"{num1} + {num2} = {num1 + num2}\")\n",
    "\n",
    "print(f\"{1 < 2}\")\n",
    "\n",
    "print(f\"{True == False}\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "they support formating as well.\n",
    "without going into detail here, there is one example which could give you some quality of life\n",
    "\n",
    "the equal sign simply alters your output to print the original statement as well as the according result\n",
    "a really simple way to prevent confusion when a lot of prints are used"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "i**2=0\n",
      "i**2=1\n",
      "i**2=4\n",
      "i**2=9\n",
      "i**2=16\n",
      "i**2=25\n",
      "i**2=36\n",
      "i**2=49\n",
      "i**2=64\n",
      "i**2=81\n"
     ]
    }
   ],
   "source": [
    "for i in range(10):\n",
    "    print(f\"{i**2=}\")\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Enums\n",
    "Enums are a smart way to give values you are using meaning\n",
    "\n",
    "they allow persons, that are working wiht your code to understand your intentions behind some random numbers\n",
    "\n",
    "look at the example below without enums"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Delivery:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.state = 0\n",
    "\n",
    "    def is_deliverable(self):\n",
    "        if self.state == 3: return True\n",
    "        else: return False"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "it's hard to understand what the intention of the integers really is\n",
    "\n",
    "now lets use some enums for this class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DeliveryState:\n",
    "    ORDERED = 0\n",
    "    PREPAIRED = 1\n",
    "    DELIVERING = 2\n",
    "    DELIVERED = 3\n",
    "\n",
    "class Delivery:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.state = DeliveryState.ORDERED\n",
    "\n",
    "    def is_delivered(self):\n",
    "        if self.state == DeliveryState.DELIVERED: return True\n",
    "        else: return False"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The code itself didn't changed but it is easier to understand what exactly is meant within the lines of code\n",
    "\n",
    "now there is some code to understand enums and how they work"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "True\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "class EnumEX:\n",
    "    STATE1 = 1\n",
    "    STATE2 = 2\n",
    "    STATE3 = 3\n",
    "\n",
    "print(EnumEX.STATE1)\n",
    "print(EnumEX.STATE1 == 1)\n",
    "print(EnumEX.STATE1 == EnumEX.STATE1)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# @property + setter\n",
    "\n",
    "Property decorators are used to define attributes.\n",
    "\n",
    "You must use them as a decorator of a method. The name of the method is the same as the name of the variable which will be defined. \n",
    "\n",
    "They do do allow a more precise defintion of attributes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n"
     ]
    }
   ],
   "source": [
    "class Test:\n",
    "\n",
    "    def __init__(self):\n",
    "        self.val = 1\n",
    "\n",
    "class TestProperty:\n",
    "\n",
    "    def __init__(self):\n",
    "        self._val = 2\n",
    "\n",
    "    @property # We are using the Decorator to allow a controled access to the private variable '_val'\n",
    "    def val(self):\n",
    "        return self._val\n",
    "\n",
    "t1 = Test()\n",
    "t2 = TestProperty()\n",
    "\n",
    "print(t1.val)\n",
    "print(t2.val)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When a property is defined this way, it can *not* altered:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "can't set attribute",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[8], line 2\u001b[0m\n\u001b[0;32m      1\u001b[0m t1\u001b[39m.\u001b[39mval \u001b[39m=\u001b[39m \u001b[39m3\u001b[39m\n\u001b[1;32m----> 2\u001b[0m t2\u001b[39m.\u001b[39;49mval \u001b[39m=\u001b[39m \u001b[39m4\u001b[39m \u001b[39m# THIS WILL PRODUCE AN AttributeError!\u001b[39;00m\n\u001b[0;32m      4\u001b[0m \u001b[39mprint\u001b[39m(t1\u001b[39m.\u001b[39mval)\n\u001b[0;32m      5\u001b[0m \u001b[39mprint\u001b[39m(t2\u001b[39m.\u001b[39mval)\n",
      "\u001b[1;31mAttributeError\u001b[0m: can't set attribute"
     ]
    }
   ],
   "source": [
    "t1.val = 3\n",
    "t2.val = 4 # THIS WILL PRODUCE AN AttributeError!\n",
    "\n",
    "print(t1.val)\n",
    "print(t2.val)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To fix this error, we need to define a setter function.\n",
    "\n",
    "A setter function is an optional extension of a property, to allow the user to set values for the attribute, hence the name 'setter'.\n",
    "\n",
    "It is common to call the method, which is decorated with '@property', as the 'getter'.\n",
    "\n",
    "This is because it defines what will be returned if the corresponding attribute is asked for.\n",
    "\n",
    "This will be shown in a later example.\n",
    "\n",
    "\n",
    "We can do define a setter like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "class TestProperty:\n",
    "\n",
    "    def __init__(self):\n",
    "        self._val = 2\n",
    "\n",
    "    @property\n",
    "    def val(self):\n",
    "        return self._val\n",
    "    \n",
    "    @val.setter\n",
    "    def val(self, user_input: int):\n",
    "        self._val = user_input\n",
    "\n",
    "t = TestProperty()\n",
    "print(t.val)\n",
    "t.val = 3\n",
    "print(t.val)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Although this might seem to overcomplicate thinks, it gives a lot of possbilities to have a better control about attributes.\n",
    "\n",
    "Let's say you want a class 'Test' which has an attribute 'val' (like above) where 'val' can be set to any positive integer, but not to integers below zero.\n",
    "\n",
    "Additionally, when 'val' is called, we want to return the square of 'val'.\n",
    "\n",
    "This is a possible implementation for a class with these features:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n",
      "9\n",
      "Only positive integers are allowed for the 'val' attribute of class 'TestProperty!'\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "class TestProperty:\n",
    "\n",
    "    def __init__(self):\n",
    "        self._val = 2\n",
    "\n",
    "    @property\n",
    "    def val(self):\n",
    "        return self._val**2\n",
    "    \n",
    "    @val.setter\n",
    "    def val(self, user_input: int):\n",
    "        if user_input < 0:\n",
    "            print(\"Only positive integers are allowed for the 'val' attribute of class 'TestProperty!'\")\n",
    "        else:\n",
    "            self._val = user_input\n",
    "    \n",
    "t = TestProperty()\n",
    "print(t.val)\n",
    "t.val = 3\n",
    "print(t.val)\n",
    "t.val = -2\n",
    "print(t.val)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Properties should be used to apply default behaviore to attributes.\n",
    "\n",
    "Note that you dont have to define a setter. This results in 'read-only' attributes, which produce errors when trying to change them.\n",
    "\n",
    "Implementing properties like this produces a much cleaner interface of a class and more reliablity on how it is used.\n",
    "\n",
    "For a more detailed guide use the following link: https://realpython.com/python-getter-setter/"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.9.13"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "11938c6bc6919ae2720b4d5011047913343b08a43b18698fd82dedb0d4417594"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
