{"id":5414,"date":"2022-02-12T06:28:46","date_gmt":"2022-02-12T06:28:46","guid":{"rendered":"https:\/\/www.aiproblog.com\/index.php\/2022\/02\/12\/comments-docstrings-and-type-hints-in-python-code\/"},"modified":"2022-02-12T06:28:46","modified_gmt":"2022-02-12T06:28:46","slug":"comments-docstrings-and-type-hints-in-python-code","status":"publish","type":"post","link":"https:\/\/www.aiproblog.com\/index.php\/2022\/02\/12\/comments-docstrings-and-type-hints-in-python-code\/","title":{"rendered":"Comments, docstrings, and type hints in Python code"},"content":{"rendered":"<p>Author: Adrian Tam<\/p>\n<div>\n<p>The source code of a program should be readable to human. Making it run correctly is only half of its purpose. Without a properly commenting code, it would be difficult for one, including the future you, to understand the rationale and intent behind the code. It would also make the code impossible to maintain. In Python, there are multiple ways to add descriptions to the code to make it more readable or make the intent more explicit. In the following, we will see how we should properly use comments, docstrings, and type hints to make our code easier to understand. After finishing this tutorial, you will know<\/p>\n<ul>\n<li>What is the proper way of using comments in Python<\/li>\n<li>How string literal or docstring can replace comments in some cases<\/li>\n<li>What is type hints in Python and how it can help us understand the code better<\/li>\n<\/ul>\n<p>Let\u2019s get started.<\/p>\n<div id=\"attachment_13220\" style=\"width: 810px\" class=\"wp-caption aligncenter\">\n<img decoding=\"async\" aria-describedby=\"caption-attachment-13220\" class=\"size-full wp-image-13220\" src=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash.jpg\" alt=\"\" width=\"800\" srcset=\"https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash.jpg 1920w, https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash-300x200.jpg 300w, https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash-1024x683.jpg 1024w, https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash-768x512.jpg 768w, https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash-1536x1024.jpg 1536w, https:\/\/machinelearningmastery.com\/wp-content\/uploads\/2022\/02\/rhythm-goyal-_-Ofoh09q_o-unsplash-600x400.jpg 600w\" sizes=\"(max-width: 1920px) 100vw, 1920px\"><\/p>\n<p id=\"caption-attachment-13220\" class=\"wp-caption-text\">Comments, docstrings, and type hints in Python code. Photo by <a href=\"https:\/\/unsplash.com\/photos\/_-Ofoh09q_o\">Rhythm Goyal<\/a>. Some rights reserved<\/p>\n<\/div>\n<h2 id=\"Overview\">Overview<\/h2>\n<p>This tutorial is in 3 parts, they are<\/p>\n<ul>\n<li>Adding comments to Python code<\/li>\n<li>Using docstrings<\/li>\n<li>Using type hints in Python code<\/li>\n<\/ul>\n<h2 id=\"Adding-comments-to-Python-code\">Adding comments to Python code<\/h2>\n<p>Almost all programming languages have dedicated syntax for comments. Comments are to be ignored by compilers or interpreters and hence they have no effect to the programming flow or logic. But with comments, we are easier to read the code.<\/p>\n<p>In languages like C++, we can add \u201cinline comments\u201d with a leading double slash (<code>\/\/<\/code>) or add comment blocks enclosed by\u00a0<code>\/*<\/code>\u00a0and\u00a0<code>*\/<\/code>. However, in Python we only have the \u201cinline\u201d version and they are introduced by the leading hash character (<code>#<\/code>).<\/p>\n<p>It is quite easy to write comments to explain every line of code but usually that is a waste. When people read the source code, quite often comments are easier to catch attention and hence putting too much comments would distract the reading. For example, the following is unnecessary and distracting:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">import datetime\r\n\r\ntimestamp = datetime.datetime.now()  # Get the current date and time\r\nx = 0    # initialize x to zero<\/pre>\n<p>Comments like these is merely repeating what the code does. Unless the code is obscured, these comments added no value to the code. The example below might be a marginal case, in which the name \u201cppf\u201d (percentage point function) is less well-known than the term \u201cCDF\u201d (cumulative distribution function):<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">import scipy.stats\r\n\r\nz_alpha = scipy.stats.norm.ppf(0.975)  # Call the inverse CDF of standard normal<\/pre>\n<p>Good comments should be telling why we are doing something. Let\u2019s look at the following example:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">def adadelta(objective, derivative, bounds, n_iter, rho, ep=1e-3):\r\n    # generate an initial point\r\n    solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])\r\n    # lists to hold the average square gradients for each variable and\r\n    # average parameter updates\r\n    sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]\r\n    sq_para_avg = [0.0 for _ in range(bounds.shape[0])]\r\n    # run the gradient descent\r\n    for it in range(n_iter):\r\n        gradient = derivative(solution[0], solution[1])\r\n        # update the moving average of the squared partial derivatives\r\n        for i in range(gradient.shape[0]):\r\n            sg = gradient[i]**2.0\r\n            sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))\r\n        # build a solution one variable at a time\r\n        new_solution = list()\r\n        for i in range(solution.shape[0]):\r\n            # calculate the step size for this variable\r\n            alpha = (ep + sqrt(sq_para_avg[i])) \/ (ep + sqrt(sq_grad_avg[i]))\r\n            # calculate the change and update the moving average of the squared change\r\n            change = alpha * gradient[i]\r\n            sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))\r\n            # calculate the new position in this variable and store as new solution\r\n            value = solution[i] - change\r\n            new_solution.append(value)\r\n        # evaluate candidate point\r\n        solution = asarray(new_solution)\r\n        solution_eval = objective(solution[0], solution[1])\r\n        # report progress\r\n        print('&gt;%d f(%s) = %.5f' % (it, solution, solution_eval))\r\n    return [solution, solution_eval]<\/pre>\n<p>The function above is implementing AdaDelta algorithm. At the first line, when we assign something to the variable\u00a0<code>solution<\/code>, we do not write comments like \u201ca random interpolation between bounds[:,0] and bounds[:,1]\u201d because that is just repeating the code literally. We say the intent of this line is to \u201cgenerate an initial point\u201d. Similarly for the other comments in the function, we mark one of the for loop as the gradient descent algorithm rather than just saying iterate for certain times.<\/p>\n<p>One important issue we want to remember when writing the comment or modifying code is to make sure the comment accurately describe the code. If they are contradicting, it would be confusing to the readers. If we should not put the comment on the first line of the above example to \u201cset initial solution to the lowerbound\u201d while the code obviously is randomizing the initial solution, or vice versa. If this is what you intented to do, you should update the comment and the code at the same time.<\/p>\n<p>An exception would be the \u201cto-do\u201d comments. From time to time, when we have an idea on how to improve the code but not yet changed it, we may put a to-do comments on the code. We can also use it to mark incomplete implementations. For example,<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\"># TODO replace Keras code below with Tensorflow\r\nfrom keras.models import Sequential\r\nfrom keras.layers import Conv2D\r\n\r\nmodel = Sequential()\r\nmodel.add(Conv2D(1, (3,3), strides=(2, 2), input_shape=(8, 8, 1)))\r\nmodel.summary()\r\n...<\/pre>\n<p>This is a common practice and many IDE will highlight the comment block differently when the keyword\u00a0<code>TODO<\/code>\u00a0is found. However, it suppposed to be temporary and we should not abuse it as an issue tracking system.<\/p>\n<p>In summary, some common \u201cbest practice\u201d on commenting code as listed as follows:<\/p>\n<ul>\n<li>Comments should not restate the code, but to explain it<\/li>\n<li>Comments should not cause confusion, but to eliminate it<\/li>\n<li>Put comments on code that is not trivial to understand, for example, state the unidiomatic use of syntax, name the algorithm being used, or explain the intent or assumptions<\/li>\n<li>Comments should be concise and simple<\/li>\n<li>Keep a consistent style and use of language in commenting<\/li>\n<li>Always prefer to have a better written code that needs no additional comment<\/li>\n<\/ul>\n<h2 id=\"Using-docstrings\">Using docstrings<\/h2>\n<p>In C++, we may write a large block of comments such as in the following:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">TcpSocketBase::~TcpSocketBase (void)\r\n{\r\n  NS_LOG_FUNCTION (this);\r\n  m_node = nullptr;\r\n  if (m_endPoint != nullptr)\r\n    {\r\n      NS_ASSERT (m_tcp != nullptr);\r\n      \/*\r\n       * Upon Bind, an Ipv4Endpoint is allocated and set to m_endPoint, and\r\n       * DestroyCallback is set to TcpSocketBase::Destroy. If we called\r\n       * m_tcp-&gt;DeAllocate, it will destroy its Ipv4EndpointDemux::DeAllocate,\r\n       * which in turn destroys my m_endPoint, and in turn invokes\r\n       * TcpSocketBase::Destroy to nullify m_node, m_endPoint, and m_tcp.\r\n       *\/\r\n      NS_ASSERT (m_endPoint != nullptr);\r\n      m_tcp-&gt;DeAllocate (m_endPoint);\r\n      NS_ASSERT (m_endPoint == nullptr);\r\n    }\r\n  if (m_endPoint6 != nullptr)\r\n    {\r\n      NS_ASSERT (m_tcp != nullptr);\r\n      NS_ASSERT (m_endPoint6 != nullptr);\r\n      m_tcp-&gt;DeAllocate (m_endPoint6);\r\n      NS_ASSERT (m_endPoint6 == nullptr);\r\n    }\r\n  m_tcp = 0;\r\n  CancelAllTimers ();\r\n}<\/pre>\n<p>But in Python, we do not have the equivalent to the delimiters\u00a0<code>\/*<\/code>\u00a0and\u00a0<code>*\/<\/code>, but we can write multi-line comments like the following instead:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">async def main(indir):\r\n    # Scan dirs for files and populate a list\r\n    filepaths = []\r\n    for path, dirs, files in os.walk(indir):\r\n        for basename in files:\r\n            filepath = os.path.join(path, basename)\r\n            filepaths.append(filepath)\r\n\r\n    \"\"\"Create the \"process pool\" of 4 and run asyncio.\r\n    The processes will execute the worker function\r\n    concurrently with each file path as parameter\r\n    \"\"\"\r\n    loop = asyncio.get_running_loop()\r\n    with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:\r\n        futures = [loop.run_in_executor(executor, func, f) for f in filepaths]\r\n        for fut in asyncio.as_completed(futures):\r\n            try:\r\n                filepath = await fut\r\n                print(filepath)\r\n            except Exception as exc:\r\n                print(\"failed one job\")<\/pre>\n<p>This works because Python supports to declare a string literal spanning across multiple lines if it is delimited with triple quotation marks (<code>\"\"\"<\/code>). And a string literal in the code is merely a string declared with no impact. Therefore it is functionally no different to the comments.<\/p>\n<p>One reason we want to use string literals is to comment out a large block of code. For example,<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">from sklearn.linear_model import LogisticRegression\r\nfrom sklearn.datasets import make_classification\r\n\"\"\"\r\nX, y = make_classification(n_samples=5000, n_features=2, n_informative=2,\r\n                           n_redundant=0, n_repeated=0, n_classes=2,\r\n                           n_clusters_per_class=1,\r\n                           weights=[0.01, 0.05, 0.94],\r\n                           class_sep=0.8, random_state=0)\r\n\"\"\"\r\nimport pickle\r\nwith open(\"dataset.pickle\", \"wb\") as fp:\r\n    X, y = pickle.load(fp)\r\n\r\nclf = LogisticRegression(random_state=0).fit(X, y)\r\n...<\/pre>\n<p>The above is a sample code that we may develop with experimenting on a machine learning problem. While we generated a dataset randomly at the beginning (the call to\u00a0<code>make_classification()<\/code>\u00a0above), we may want to switch to a different dataset and repeat the same process at a later time (e.g., the pickle part above). Rather than removing the block of code, we may simply comment those lines so we can store the code later. It is not in a good shape for the finalized code but convenient while we are developing our solution.<\/p>\n<p>The string literal in Python as comment has a special purpose if it is at the first line under a function. The string literal in that case is called the \u201cdocstring\u201d of the function. For example,<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">def square(x):\r\n    \"\"\"Just to compute the square of a value\r\n    \r\n    Args:\r\n        x (int or float): A numerical value\r\n\r\n    Returns:\r\n        int or float: The square of x\r\n    \"\"\"\r\n    return x * x<\/pre>\n<\/p>\n<div class=\"lm-Widget p-Widget jp-Cell jp-MarkdownCell jp-Notebook-cell jp-mod-rendered jp-mod-active jp-mod-selected\">\n<div class=\"lm-Widget p-Widget lm-Panel p-Panel jp-Cell-inputWrapper\">\n<div class=\"lm-Widget p-Widget jp-InputArea jp-Cell-inputArea\">\n<div class=\"lm-Widget p-Widget jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput\" data-mime-type=\"text\/markdown\">\n<p>We can see the first line under the function is a literal string and it serve as the same purpose as comment. It makes the code more readable, but at the same time, we can retrieve it from the code:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">print(\"Function name:\", square.__name__)\r\nprint(\"Docstring:\", square.__doc__)<\/pre>\n<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">Function name: square\r\nDocstring: Just to compute the square of a value\r\n    \r\n    Args:\r\n        x (int or float): A numerical value\r\n\r\n    Returns:\r\n        int or float: The square of x<\/pre>\n<p>Because of the special status of the docstring, there are several conventions on how to write a proper one.<\/p>\n<p>In C++ we may use Doxygen to generate code documentation from comments and similarly we have Javadoc for Java code. The closest match in Python would be the tool \u201cautodoc\u201d from Sphinx or pdoc. Both will try to parse the docstring to generate documentations automatically.<\/p>\n<p>There are no standard way of making docstrings but generally we expect they will explain the purpose of a function (or a class or module) as well as the arguments and the return values. One common style is like the one above, which is advocated by Google. A different style is from NumPy:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">def square(x):\r\n    \"\"\"Just to compupte the square of a value\r\n    \r\n    Parameters\r\n    ----------\r\n    x : int or float\r\n        A numerical value\r\n\r\n    Returns\r\n    -------\r\n    int or float\r\n        The square of `x`\r\n    \"\"\"\r\n    return x * x<\/pre>\n<p>Tools such as autodoc can parse these docstring and generate the API documentation. But even if it is not the purpose, having a docstring describing the nature of the function, the data types of the function arguments and return values can surely make your code easier to read. This is particularly true since Python, unlike C++ or Java, is a\u00a0<strong>duck-typing<\/strong>\u00a0language which variables and function arguments are not declared with a particular type. We can make use of docstring to spell out the assumption of the data type so people are easier to follow or use your function.<\/p>\n<h2 id=\"Using-type-hints-in-Python-code\">Using type hints in Python code<\/h2>\n<p>Since Python 3.5, type hint syntax is allowed. As the name implies, its purpose is to hint for the type, and nothing else. Hence even it looks like to bring Python closer to Java, it does not mean to restrict the data to be stored in a variable. The example above can be rewritten with type hint:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">def square(x: int) -&gt; int:\r\n    return x * x<\/pre>\n<p>In a function, the arguments can be followed by a\u00a0<code>: type<\/code>\u00a0syntax to spell out the\u00a0<em>intended<\/em>\u00a0types. The return value of a function is identified by the\u00a0<code>-&gt; type<\/code>\u00a0syntax before the colon. In fact, type hint can be declared for variables too, e.g.,<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">def square(x: int) -&gt; int:\r\n    value: int = x * x\r\n    return value<\/pre>\n<p>The benefit of type hint is two fold: We can use it to eliminate some comments if we need to describe explicitly the data type being used. We can also help\u00a0<em>static analyzers<\/em>\u00a0to understand our code better so they can help identifying potential issues in the code.<\/p>\n<p>Sometimes the type can be complex and therefore Python provided the\u00a0<code>typing<\/code>\u00a0module in its standard library to help clean up the syntax. For example, we can use\u00a0<code>Union[int,float]<\/code>\u00a0to mean\u00a0<code>int<\/code>\u00a0type or\u00a0<code>float<\/code>\u00a0type,\u00a0<code>List[str]<\/code>\u00a0to mean a list that every element is a string, and use\u00a0<code>Any<\/code>\u00a0to mean anything. Like as follows:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">from typing import Any, Union, List\r\n\r\ndef square(x: Union[int, float]) -&gt; Union[int, float]:\r\n    return x * x\r\n\r\ndef append(x: List[Any], y: Any) -&gt; None:\r\n    x.append(y)<\/pre>\n<p>However, it is important to remember that type hints are\u00a0<em>hints<\/em>\u00a0only. It does not impose any restriction to the code. Hence the following is confusing to the reader, but perfectly fine:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">n: int = 3.5\r\nn = \"assign a string\"<\/pre>\n<\/p>\n<div class=\"lm-Widget p-Widget jp-Cell jp-MarkdownCell jp-Notebook-cell jp-mod-rendered jp-mod-active jp-mod-selected\">\n<div class=\"lm-Widget p-Widget lm-Panel p-Panel jp-Cell-inputWrapper\">\n<div class=\"lm-Widget p-Widget jp-InputArea jp-Cell-inputArea\">\n<div class=\"lm-Widget p-Widget jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput\" data-mime-type=\"text\/markdown\">\n<p>Using type hints may improve the readability of the code. However, the most important benefit of type hint is to allow\u00a0<em>static analyzer<\/em>\u00a0such as mypy to tell us whether our code has any potential bug. If you process the above lines of code with mypy, we will see the following error:<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">test.py:1: error: Incompatible types in assignment (expression has type \"float\", variable has type \"int\")\r\ntest.py:2: error: Incompatible types in assignment (expression has type \"str\", variable has type \"int\")\r\nFound 2 errors in 1 file (checked 1 source file)<\/pre>\n<p>The use of static analyzers will be covered in another post.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"lm-Widget p-Widget jp-Cell jp-MarkdownCell jp-Notebook-cell jp-mod-rendered\">\n<div class=\"lm-Widget p-Widget lm-Panel p-Panel jp-Cell-inputWrapper\">\n<div class=\"lm-Widget p-Widget jp-InputArea jp-Cell-inputArea\">\n<div class=\"lm-Widget p-Widget jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput\" data-mime-type=\"text\/markdown\">\n<p>To illustrate the use of comments, docstrings, and type hints, below is an example to define a generator function that samples a pandas DataFrame on fixed-width windows. It is useful for training a LSTM network, which a few consecutive time steps should be provided. In the function below, we start from a random row on the DataFrame and clip a few rows following it. As long as we can successfully get one full window, we take it as a sample. Once we collected enough samples to make a batch, the batch is dispatched.<\/p>\n<p>You should see that it is clearer if we can provide type hints on the function arguments so we know, for example,\u00a0<code>data<\/code>\u00a0is supposed to be a pandas DataFrame. But we describe further that it is expected to carry a datetime index in the docstring. The describe the algorithm on how to exact a window of rows from the input data as well as the intention of the \u201cif\u201d block in the inner while-loop using comments. In this way, the code would be much easier to understand and much easier to maintain, or modified for other use.<\/p>\n<pre class=\"urvanov-syntax-highlighter-plain-tag\">from typing import List, Tuple, Generator\r\nimport pandas as pd\r\nimport numpy as np\r\n\r\nTrainingSampleGenerator = Generator[Tuple[np.ndarray,np.ndarray], None, None]\r\n\r\ndef lstm_gen(data: pd.DataFrame,\r\n             timesteps: int,\r\n             batch_size: int) -&gt; TrainingSampleGenerator:\r\n    \"\"\"Generator to produce random samples for LSTM training\r\n\r\n    Args:\r\n        data: DataFrame of data with datetime index in chronological order,\r\n              samples are drawn from this\r\n        timesteps: Number of time steps for each sample, data will be\r\n                   produced from a window of such length\r\n        batch_size: Number of samples in each batch\r\n\r\n    Yields:\r\n        ndarray, ndarray: The (X,Y) training samples drawn on a random window\r\n        from the input data\r\n    \"\"\"\r\n    input_columns = [c for c in data.columns if c != \"target\"]\r\n    batch: List[Tuple[pd.DataFrame, pd.Series]] = []\r\n    while True:\r\n        # pick one start time and security\r\n        while True:\r\n            # Start from a random point from the data and clip a window\r\n            row = data[\"target\"].sample()\r\n            starttime = row.index[0]\r\n            window: pd.DataFrame = data[starttime:].iloc[:timesteps]\r\n            # If we are at the end of the DataFrame, we can't get a full\r\n            # window and we must start over\r\n            if len(window) == timesteps:\r\n                break\r\n        # Extract the input and output\r\n        y = window[\"target\"]\r\n        X = window[input_columns]\r\n        batch.append((X, y))\r\n        # If accumulated enough for one batch, dispatch\r\n        if len(batch) == batch_size:\r\n            X, y = zip(*batch)\r\n            yield np.array(X).astype(\"float32\"), np.array(y).astype(\"float32\")\r\n            batch = []<\/pre>\n<\/p>\n<h2 id=\"Further-reading\">Further reading<\/h2>\n<p>This section provides more resources on the topic if you are looking to go deeper.<\/p>\n<h4 id=\"Articles\">Articles<\/h4>\n<ul>\n<li>Best practices for writing code comments,\u00a0<a href=\"https:\/\/stackoverflow.blog\/2021\/12\/23\/best-practices-for-writing-code-comments\/\" target=\"_blank\" rel=\"noopener\">https:\/\/stackoverflow.blog\/2021\/12\/23\/best-practices-for-writing-code-comments\/<\/a>\n<\/li>\n<li>PEP483, the theory of type hints,\u00a0<a href=\"https:\/\/www.python.org\/dev\/peps\/pep-0483\/\" target=\"_blank\" rel=\"noopener\">https:\/\/www.python.org\/dev\/peps\/pep-0483\/<\/a>\n<\/li>\n<li>Google Python Style Guide,\u00a0<a href=\"https:\/\/google.github.io\/styleguide\/pyguide.html\" target=\"_blank\" rel=\"noopener\">https:\/\/google.github.io\/styleguide\/pyguide.html<\/a>\n<\/li>\n<\/ul>\n<h4 id=\"Software\">Software<\/h4>\n<ul>\n<li>Sphinx documentation,\u00a0<a href=\"https:\/\/www.sphinx-doc.org\/en\/master\/index.html\" target=\"_blank\" rel=\"noopener\">https:\/\/www.sphinx-doc.org\/en\/master\/index.html<\/a>\n<\/li>\n<li>Napoleon module of Sphinx,\u00a0<a href=\"https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/index.html\" target=\"_blank\" rel=\"noopener\">https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/index.html<\/a>\n<ul>\n<li>Google style docstring example:\u00a0<a href=\"https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/example_google.html\" target=\"_blank\" rel=\"noopener\">https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/example_google.html<\/a>\n<\/li>\n<li>NumPy style docstring example:\u00a0<a href=\"https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/example_numpy.html\" target=\"_blank\" rel=\"noopener\">https:\/\/sphinxcontrib-napoleon.readthedocs.io\/en\/latest\/example_numpy.html<\/a>\n<\/li>\n<\/ul>\n<\/li>\n<li>pdoc,\u00a0<a href=\"https:\/\/pdoc.dev\/\" target=\"_blank\" rel=\"noopener\">https:\/\/pdoc.dev\/<\/a>\n<\/li>\n<li>typing module,\u00a0<a href=\"https:\/\/docs.python.org\/3\/library\/typing.html\" target=\"_blank\" rel=\"noopener\">https:\/\/docs.python.org\/3\/library\/typing.html<\/a>\n<\/li>\n<\/ul>\n<h2 id=\"Summary\">Summary<\/h2>\n<p>In this tutorial, you\u2019ve see how we should use the comments, docstrings, and type hints in Python. Specifically, you now knows<\/p>\n<ul>\n<li>How to write a good, useful comment<\/li>\n<li>The conventions in explaining a function using docstring<\/li>\n<li>How to use type hint to address the readability weakness of duck typing in Python<\/li>\n<\/ul>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>The post <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/comments-docstrings-and-type-hints-in-python-code\/\">Comments, docstrings, and type hints in Python code<\/a> appeared first on <a rel=\"nofollow\" href=\"https:\/\/machinelearningmastery.com\/\">Machine Learning Mastery<\/a>.<\/p>\n<\/div>\n<p><a href=\"https:\/\/machinelearningmastery.com\/comments-docstrings-and-type-hints-in-python-code\/\">Go to Source<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Author: Adrian Tam The source code of a program should be readable to human. Making it run correctly is only half of its purpose. Without [&hellip;] <span class=\"read-more-link\"><a class=\"read-more\" href=\"https:\/\/www.aiproblog.com\/index.php\/2022\/02\/12\/comments-docstrings-and-type-hints-in-python-code\/\">Read More<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":5415,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"footnotes":""},"categories":[24],"tags":[],"_links":{"self":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/5414"}],"collection":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/comments?post=5414"}],"version-history":[{"count":0,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/posts\/5414\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media\/5415"}],"wp:attachment":[{"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/media?parent=5414"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/categories?post=5414"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.aiproblog.com\/index.php\/wp-json\/wp\/v2\/tags?post=5414"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}