Streamline previous content
This commit is contained in:
parent
5e71194787
commit
04d53956a3
11 changed files with 1097 additions and 410 deletions
|
|
@ -139,7 +139,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb) provides more insights into this \"bug.\""
|
||||
"There are, however, cases where even well-behaved Python does not make us happy. [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers.ipynb#Imprecision) provides more insights into this \"bug.\""
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"94906834637792"
|
||||
"94731133531104"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
|
|
@ -213,7 +213,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"94906834637760"
|
||||
"94731133531072"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Let's not confuse the boolean `False` with `None`, another built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) as the *implicit* return value of a function without a `return` statement.\n",
|
||||
"Let's not confuse the boolean `False` with `None`, another built-in object! We saw the latter before in [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb#Function-Definition) as the *implicit* return value of a function without a `return` statement.\n",
|
||||
"\n",
|
||||
"We might think of `None` in a boolean context indicating a \"maybe\" or even an \"unknown\" answer; however, for Python, there are no \"maybe\" or \"unknown\" objects, as we see further below!\n",
|
||||
"\n",
|
||||
|
|
@ -313,7 +313,7 @@
|
|||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"94906834624752"
|
||||
"94731133518064"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n",
|
||||
"`True`, `False`, and `None` have the property that they each exist in memory only *once*. Objects designed this way are so-called **singletons**. This **[design pattern](https://en.wikipedia.org/wiki/Design_Patterns)** was originally developed to keep a program's memory usage at a minimum. It may only be employed in situations where we know that an object does *not* mutate its value (i.e., to re-use the bag analogy from [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb#Objects-vs.-Types-vs.-Values), no flipping of $0$s and $1$s in the bag is allowed). In languages \"closer\" to the memory like C, we would have to code this singleton logic ourselves, but Python has this built in for *some* types.\n",
|
||||
"\n",
|
||||
"We verify this with either the `is` operator or by comparing memory addresses."
|
||||
]
|
||||
|
|
@ -897,9 +897,11 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"The operands of the logical operators do not need to be *boolean* expressions as defined above but may be *any* expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts it as such.\n",
|
||||
"The operands of the logical operators do not need to be *boolean* expressions but may be *any* expression. If a sub-expression does *not* evaluate to an object of type `bool`, Python automatically casts it as such.\n",
|
||||
"\n",
|
||||
"For example, any non-zero numeric object becomes `True`. While this behavior allows writing more concise and thus more \"beautiful\" code, it is also a common source of confusion. `(x - 9)` is cast as `True` and then the overall expression evaluates to `True` as well."
|
||||
"For example, any non-zero numeric object becomes `True`. While this behavior allows writing more concise and thus more \"beautiful\" code, it is also a common source of confusion.\n",
|
||||
"\n",
|
||||
"So, `(x - 9)` is cast as `True` and then the overall expression evaluates to `True` as well."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1122,7 +1124,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Pythonistas often use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when used in place of a boolean one."
|
||||
"Pythonistas use the terms **truthy** or **falsy** to describe a non-boolean expression's behavior when evaluated in a boolean context."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1144,14 +1146,18 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"When evaluating boolean expressions with logical operators in it, Python follows the **[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)** strategy: First, the inner-most sub-expressions are evaluated. Second, with identical **[operator precedence](https://docs.python.org/3/reference/expressions.html#operator-precedence)**, evaluation goes from left to right. Once it is clear what the overall truth value is, no more sub-expressions are evaluated, and the result is *immediately* returned.\n",
|
||||
"When evaluating expressions involving the `and` and `or` operators, Python follows the **[short-circuiting](https://en.wikipedia.org/wiki/Short-circuit_evaluation)** strategy: Once it is clear what the overall truth value is, no more sub-expressions are evaluated, and the result is *immediately* returned.\n",
|
||||
"\n",
|
||||
"In summary, data science practitioners must know *how* the following two generic expressions are evaluated:\n",
|
||||
"Also, if such expressions are evaluated in a non-boolean context, the result is returned as is and *not* cast as a `bool` type.\n",
|
||||
"\n",
|
||||
"- `x or y`: The `y` expression is evaluated *only if* `x` evaluates to `False`, in which case `y` is returned; otherwise, `x` is returned *without* even looking at `y`.\n",
|
||||
"- `x and y`: The `y` expression is evaluated *only if* `x` evaluates to `True`. Then, if `y` also evaluates to `True`, it is returned; otherwise, `x` is returned.\n",
|
||||
"The two rules can be summarized as:\n",
|
||||
"\n",
|
||||
"Let's look at a couple of examples."
|
||||
"- `x or y`: If `x` is truthy, it is returned *without* evaluating `y`. Otherwise, `y` is evaluated *and* returned.\n",
|
||||
"- `x and y`: If `x` is falsy, it is returned *without* evaluating `y`. Otherwise, `y` is evaluated *and* returned.\n",
|
||||
"\n",
|
||||
"The rules may also be chained or combined.\n",
|
||||
"\n",
|
||||
"Let's look at a couple of examples below. To visualize which sub-expressions are evaluated, we define a helper function `expr()` that prints out the only argument it is passed before returning it."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1163,32 +1169,6 @@
|
|||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = 0\n",
|
||||
"y = 1\n",
|
||||
"z = 2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"We define a helper function `expr()` that prints out the only argument it is passed before returning it. With `expr()`, we can see if a sub-expression is evaluated or not."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 37,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def expr(arg):\n",
|
||||
" \"\"\"Print and return the only argument.\"\"\"\n",
|
||||
|
|
@ -1204,12 +1184,12 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"With the `or` operator, the first sub-expression that evaluates to `True` is returned."
|
||||
"With the `or` operator, the first truthy sub-expression is returned."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 38,
|
||||
"execution_count": 37,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1230,18 +1210,18 @@
|
|||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 38,
|
||||
"execution_count": 37,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(x) or expr(y)"
|
||||
"expr(0) or expr(1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 39,
|
||||
"execution_count": 38,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
|
|
@ -1261,18 +1241,18 @@
|
|||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 39,
|
||||
"execution_count": 38,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(y) or expr(z)"
|
||||
"expr(1) or expr(2) # 2 is not evaluated due to short-circuiting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"execution_count": 39,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
|
|
@ -1293,13 +1273,13 @@
|
|||
"1"
|
||||
]
|
||||
},
|
||||
"execution_count": 40,
|
||||
"execution_count": 39,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(x) or expr(y) or expr(z)"
|
||||
"expr(0) or expr(1) or expr(2) # 2 is not evaluated due to short-circuiting"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1310,12 +1290,12 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"If all sub-expressions evaluate to `False`, the last one is the result."
|
||||
"If all sub-expressions are falsy, the last one is returned."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"execution_count": 40,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
|
|
@ -1337,13 +1317,13 @@
|
|||
"0"
|
||||
]
|
||||
},
|
||||
"execution_count": 41,
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(False) or expr([]) or expr(x)"
|
||||
"expr(False) or expr([]) or expr(0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1354,12 +1334,12 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"With the `and` operator, the first sub-expression that evaluates to `False` is returned."
|
||||
"With the `and` operator, the first falsy sub-expression is returned."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"execution_count": 41,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1373,6 +1353,38 @@
|
|||
"Arg: 0\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
"execution_count": 41,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(0) and expr(1) # 1 is not evaluated due to short-circuiting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 42,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Arg: 1\n",
|
||||
"Arg: 0\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
|
|
@ -1385,7 +1397,7 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(x) and expr(y)"
|
||||
"expr(1) and expr(0)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1417,40 +1429,7 @@
|
|||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(y) and expr(x)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Arg: 2\n",
|
||||
"Arg: 1\n",
|
||||
"Arg: 0\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(z) and expr(y) and expr(x)"
|
||||
"expr(1) and expr(0) and expr(2) # 2 is not evaluated due to short-circuiting"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1461,12 +1440,12 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"If all sub-expressions evaluate to `True`, the last one is returned."
|
||||
"If all sub-expressions are truthy, the last one is returned."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 45,
|
||||
"execution_count": 44,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
|
|
@ -1487,13 +1466,13 @@
|
|||
"2"
|
||||
]
|
||||
},
|
||||
"execution_count": 45,
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"expr(y) and expr(z)"
|
||||
"expr(1) and expr(2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1543,7 +1522,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 46,
|
||||
"execution_count": 45,
|
||||
"metadata": {
|
||||
"code_folding": [],
|
||||
"slideshow": {
|
||||
|
|
@ -1557,7 +1536,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 47,
|
||||
"execution_count": 46,
|
||||
"metadata": {
|
||||
"code_folding": [],
|
||||
"slideshow": {
|
||||
|
|
@ -1599,7 +1578,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 48,
|
||||
"execution_count": 47,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1612,13 +1591,21 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 49,
|
||||
"execution_count": 48,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"You read this just as often as you see heads when tossing a coin\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"if random.random() > 0.5:\n",
|
||||
" print(\"You read this just as often as you see heads when tossing a coin\")"
|
||||
|
|
@ -1637,7 +1624,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 50,
|
||||
"execution_count": 49,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
|
|
@ -1674,7 +1661,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 51,
|
||||
"execution_count": 50,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1717,7 +1704,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 52,
|
||||
"execution_count": 51,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1790,7 +1777,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 53,
|
||||
"execution_count": 52,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -1814,7 +1801,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 54,
|
||||
"execution_count": 53,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
|
|
@ -1830,7 +1817,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 55,
|
||||
"execution_count": 54,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
|
|
@ -1843,7 +1830,7 @@
|
|||
"9"
|
||||
]
|
||||
},
|
||||
"execution_count": 55,
|
||||
"execution_count": 54,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
|
|
@ -1865,7 +1852,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 56,
|
||||
"execution_count": 55,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
|
|
@ -1878,7 +1865,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"execution_count": 56,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
|
|
@ -1891,7 +1878,7 @@
|
|||
"9"
|
||||
]
|
||||
},
|
||||
"execution_count": 57,
|
||||
"execution_count": 56,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
|
|
@ -1913,7 +1900,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 58,
|
||||
"execution_count": 57,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
|
|
@ -1926,7 +1913,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 59,
|
||||
"execution_count": 58,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "skip"
|
||||
|
|
@ -1939,7 +1926,7 @@
|
|||
"9"
|
||||
]
|
||||
},
|
||||
"execution_count": 59,
|
||||
"execution_count": 58,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
|
|
@ -1956,7 +1943,7 @@
|
|||
}
|
||||
},
|
||||
"source": [
|
||||
"Conditional expressions may not only be used in the way described in this section. We already saw them as part of a *list comprehension* in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) and [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) and revisit this in Chapter 7 in greater detail."
|
||||
"Conditional expressions may not only be used in the way described in this section. We already saw them as part of a *list comprehension* in [Chapter 1](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements.ipynb) and [Chapter 2](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions.ipynb) and revisit this construct in greater detail in [Chapter 7](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences.ipynb#List-Comprehensions)."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -1987,7 +1974,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 60,
|
||||
"execution_count": 59,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -2000,7 +1987,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 61,
|
||||
"execution_count": 60,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "-"
|
||||
|
|
@ -2039,7 +2026,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 62,
|
||||
"execution_count": 61,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "fragment"
|
||||
|
|
@ -2078,7 +2065,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 63,
|
||||
"execution_count": 62,
|
||||
"metadata": {
|
||||
"slideshow": {
|
||||
"slide_type": "slide"
|
||||
|
|
@ -2089,7 +2076,7 @@
|
|||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Yes, division worked smoothly.\n",
|
||||
"Oops. Division by 0. How does that work?\n",
|
||||
"I am always printed\n"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue