{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[Home](Home.ipynb)\n", "\n", "# Calling Python Objects\n", "\n", "We say an object is \"callable\" if it \"has a mouth\" meaning it makes sense to put parentheses after it. \n", "\n", "Object( ) \"has a mouth\" in that \"( )\" looks like \"lips\" 💋 turned sideways, as in [the old sideways emoticons](https://en.wikipedia.org/wiki/List_of_emoticons), such as ;-() instead of a \"winking face\" 😉 emoji.\n", "\n", "Careful though: putting parentheses after an object may also be a harmless use of syntax that doesn't end up triggering an object's ```__call__``` method -- because maybe it doesn't have one!\n", "\n", "For example you may write:\n", "\n", "```python \n", " if(1 != 2):\n", " print(\"OK then\")\n", "``` \n", "The parens after the ```if``` are harmless. But they make it look as if the keyword ```if``` might be callable. It isn't (in Python that is). ```if``` is a Python keyword and none of the keywords are callable. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The better more stylish way to write the above is:\n", "```python \n", " if 1 != 2:\n", " print(\"OK then\")\n", "```\n", "or even:\n", "```python \n", " if (1 != 2):\n", " print(\"OK then\")\n", "``` \n", "The single space after ```if``` proves you know you're not \"calling\" ```if``` (even though, if if *were* callable, that space would not matter, i.e. ```if``` would be called). Confused yet?\n", "\n", "Just remember: no Python keyword is callable. And what are the keywords again?" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']\n" ] } ], "source": [ "import keyword\n", "print(keyword.kwlist)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Likewise, if this were Python 2.7, the ```print``` statement followed by a string in parentheses would still work, even though ```print``` is not actually being called. \n", "\n", "Both of these work in Python 2.x:\n", "```python \n", " print \"OK then\" # better in 2.x\n", " print(\"OK then\") # harmless parens\n", "``` \n", "In Python 3.x, on the other hand, print is most definitely a callable. ```print``` is now a function, not a keyword. Parens are *mandatory* plus you now have a few optional named arguments such as sep=, end= and file=." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "callable(print)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.7.9 (default, Aug 31 2020, 07:22:35) \n", "[Clang 10.0.0 ]\n" ] } ], "source": [ "import sys\n", "print(sys.version)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello_there!\n" ] } ], "source": [ "print(\"Hello\", \"there\", sep=\"_\", end=\"!\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### About Arguments\n", "\n", "Once you have determined an object is callable, the next question is does it take arguments. Not all callables do, though they do require the parens anyway.\n", "\n", "A related question would be: how do I define my own callables, including specifying its arguments.\n", "\n", "Technically speaking, we may want to distinguish parameters from arguments. What you pass to a callable at runtime are arguments. What they match up with, are parameters defined at design time." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def function_0(a, b): # two positional arguments\n", " pass\n", "\n", "def function_1(a, b=0): # one positional, one named\n", " pass\n", "\n", "def function_2(a=0, b=1): # two named arguments\n", " pass\n", "\n", "# no errors\n", "function_0(1, 2)\n", "function_1(1)\n", "function_2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above you'll see three functions having their parameters defined at design time. Parameters come in two basic flavors: positional and named. Named arguments are also sometimes called keyword arguments, not to be confused with Python keywords.\n", "\n", "Then each of the functions gets called, with as few arguments as necessary. Named arguments supply default values, which may be overridden, or left alone." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Argument-Parameter Matching Rules\n", "\n", "Think of the callable's \"mouth\" or intake, expecting arguments in a certain order, and/or with specific names. \n", "\n", "How Python matches arguments, to parameters set up to receive them, should be an unambiguous. And it is. But it's also somewhat of a long story. Many rules apply, consistently with one another.\n", "\n", "For example, named arguments should have only one target parameter, or Python will raise a SyntaxError.\n", "\n", "No good:\n", "\n", "```python\n", " def bad_form(a, a=10):\n", " pass\n", "\n", " bad_form(a = 11) # would leave Python guessing.\n", "``` " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def any_function(x, y, z, a=1, b=2):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Given the above design time definition, all of these functions calls would be valid at runtime:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "any_function(1,2,3,4,5) # reach all five positionally\n", "any_function(b=1, a=2, x=3, y=2, z=1) # match by name\n", "any_function(1,2,3) # named parameters don't need args" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scatter / Gather Operators\n", "\n", "Wouldn't it be lovely if parameters could be defined in a more open-ended manner, such that any number of positional and/or named arguments might be passed, and get matched up with something.\n", "\n", "That's exactly what the [star and double-star operators](https://qr.ae/pGHQMI) are for. Used with parameters, they allow for positionals and named arguments to get collected into a tuple and dictionary respectively." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def function_3(a, *b): # at least one positional, scoop to tuple\n", " print(\"a:\", a, \"b:\", b)\n", "\n", "def function_4(a, **b): # one positional or a= + any named args \n", " print(\"a:\", a, \"b:\", b)\n", "\n", "def function_5(a, *, b): # a might be name, b must be named\n", " print(\"a:\", a, \"b:\", b)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: ()\n" ] } ], "source": [ "function_3(1) # b left empty" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: (2, 3, 4, 5, 6)\n" ] } ], "source": [ "function_3(1, 2, 3, 4, 5, 6)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: {'r': 2, 's': 3, 't': 4}\n" ] } ], "source": [ "function_4(a=1, r=2, s=3, t=4) # naming a positional is OK" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: {'r': 2, 's': 3, 't': 4}\n" ] } ], "source": [ "function_4(1, r=2, s=3, t=4)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: 2\n" ] } ], "source": [ "function_5(1, b=2) # b must be named" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a: 1 b: 2\n" ] } ], "source": [ "function_5(a=1, b=2) # a might be" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# Python 3.9 and above\n", "#def function_6(a, b, /, c):\n", "# pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above, positionals to the left of the slash may not be referred to by name, whereas positionals to the right still might be." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "# function_6(1,2,c=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below, two and only two inputs may be given." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "def average(a, b):\n", " return (a + b)/2" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "15.0" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "average(10, 20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now with a star prefix, args will be a tuple containing as many arguments as were positionally passed in." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "def average(*args): # * makes args a scooper\n", " print(args) # scooped to tuple\n", " return sum(args)/len(args)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(10, 20, 15, 6, 7, 14, 5, -6)\n" ] }, { "data": { "text/plain": [ "8.875" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "average(10, 20, 15, 6, 7, 14, 5, -6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Packing / Unpacking (Continued)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'tetrahedron': 1, 'cube': 3, 'octahedron': 4}" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shapes = {\"tetrahedron\": 1, \"cube\": 3, \"octahedron\": 4}\n", "{**shapes} # unpacking a dict and repacking" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'tetrahedron': 1, 'cube': 3, 'octahedron': 4}" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(shapes.items()) # dict eats tuple of tuples" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'tetrahedron': 1, 'cube': 3, 'octahedron': 4}" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dict(**shapes) # dict eats named arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The format method of the string type serves as an intake for positional and/or named arguments. If you have a dict, and want to turn it into named arguments, you're in luck, thanks to the double-star unpacking operator." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", " POLYHEDRON VOLUMES\n", " \n", "Tetrahedron: 1\n", "Cube: 3\n", "Octahedron: 4\n", "\n" ] } ], "source": [ "print(\"\"\"\\\n", "\n", " POLYHEDRON VOLUMES\n", " \n", "Tetrahedron: {tetrahedron}\n", "Cube: {cube}\n", "Octahedron: {octahedron}\n", "\"\"\".format(**shapes)\n", ")" ] } ], "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.7.9" } }, "nbformat": 4, "nbformat_minor": 4 }