GitHub

@@ -290,6 +290,179 @@ tags: [raises-exception]

290290

max(y)

291291

```

292292293+

## `*` and `**` Operators

294+295+

`*` and `**` are convenient and widely used tools to unpack lists and tuples and to allow users to define functions that take arbitrarily many arguments as input.

296+297+

In this section, we will explore how to use them and distinguish their use cases.

298+299+300+

### Unpacking Arguments

301+302+

When we operate on a list of parameters, we often need to extract the content of the list as individual arguments instead of a collection when passing them into functions.

303+304+

Luckily, the `*` operator can help us to unpack lists and tuples into [*positional arguments*](https://63a3119f7a9a1a12f59e7803--epic-agnesi-957267.netlify.app/functions.html#keyword-arguments) in function calls.

305+306+

To make things concrete, consider the following examples:

307+308+

Without `*`, the `print` function prints a list

309+310+

```{code-cell} python3

311+

l1 = ['a', 'b', 'c']

312+313+

print(l1)

314+

```

315+316+

While the `print` function prints individual elements since `*` unpacks the list into individual arguments

317+318+

```{code-cell} python3

319+

print(*l1)

320+

```

321+322+

Unpacking the list using `*` into positional arguments is equivalent to defining them individually when calling the function

323+324+

```{code-cell} python3

325+

print('a', 'b', 'c')

326+

```

327+328+

However, `*` operator is more convenient if we want to reuse them again

329+330+

```{code-cell} python3

331+

l1.append('d')

332+333+

print(*l1)

334+

```

335+336+

Similarly, `**` is used to unpack arguments.

337+338+

The difference is that `**` unpacks *dictionaries* into *keyword arguments*.

339+340+

`**` is often used when there are many keyword arguments we want to reuse.

341+342+

For example, assuming we want to draw multiple graphs using the same graphical settings,

343+

it may involve repetitively setting many graphical parameters, usually defined using keyword arguments.

344+345+

In this case, we can use a dictionary to store these parameters and use `**` to unpack dictionaries into keyword arguments when they are needed.

346+347+

Let's walk through a simple example together and distinguish the use of `*` and `**`

348+349+

```{code-cell} python3

350+

import numpy as np

351+

import matplotlib.pyplot as plt

352+353+

# Set up the frame and subplots

354+

fig, ax = plt.subplots(2, 1)

355+

plt.subplots_adjust(hspace=0.7)

356+357+

# Create a function that generates synthetic data

358+

def generate_data(β_0, β_1, σ=30, n=100):

359+

x_values = np.arange(0, n, 1)

360+

y_values = β_0 + β_1 * x_values + np.random.normal(size=n, scale=σ)

361+

return x_values, y_values

362+363+

# Store the keyword arguments for lines and legends in a dictionary

364+

line_kargs = {'lw': 1.5, 'alpha': 0.7}

365+

legend_kargs = {'bbox_to_anchor': (0., 1.02, 1., .102),

366+

'loc': 3,

367+

'ncol': 4,

368+

'mode': 'expand',

369+

'prop': {'size': 7}}

370+371+

β_0s = [10, 20, 30]

372+

β_1s = [1, 2, 3]

373+374+

# Use a for loop to plot lines

375+

def generate_plots(β_0s, β_1s, idx, line_kargs, legend_kargs):

376+

label_list = []

377+

for βs in zip(β_0s, β_1s):

378+379+

# Use * to unpack tuple βs and the tuple output from the generate_data function

380+

# Use ** to unpack the dictionary of keyword arguments for lines

381+

ax[idx].plot(*generate_data(*βs), **line_kargs)

382+383+

label_list.append(f'$β_0 = {βs[0]}$ | $β_1 = {βs[1]}$')

384+385+

# Use ** to unpack the dictionary of keyword arguments for legends

386+

ax[idx].legend(label_list, **legend_kargs)

387+388+

generate_plots(β_0s, β_1s, 0, line_kargs, legend_kargs)

389+390+

# We can easily reuse and update our parameters

391+

β_1s.append(-2)

392+

β_0s.append(40)

393+

line_kargs['lw'] = 2

394+

line_kargs['alpha'] = 0.4

395+396+

generate_plots(β_0s, β_1s, 1, line_kargs, legend_kargs)

397+

plt.show()

398+

```

399+400+

In this example, `*` unpacked the zipped parameters `βs` and the output of `generate_data` function stored in tuples,

401+

while `**` unpacked graphical parameters stored in `legend_kargs` and `line_kargs`.

402+403+

To summarize, when `*list`/`*tuple` and `**dictionary` are passed into *function calls*, they are unpacked into individual arguments instead of a collection.

404+405+

The difference is that `*` will unpack lists and tuples into *positional arguments*, while `**` will unpack dictionaries into *keyword arguments*.

406+407+

### Arbitrary Arguments

408+409+

When we *define* functions, it is sometimes desirable to allow users to put as many arguments as they want into a function.

410+411+

You might have noticed that the `ax.plot()` function could handle arbitrarily many arguments.

412+413+

If we look at the [documentation](https://github.com/matplotlib/matplotlib/blob/v3.6.2/lib/matplotlib/axes/_axes.py#L1417-L1669) of the function, we can see the function is defined as

414+415+

```

416+

Axes.plot(*args, scalex=True, scaley=True, data=None, **kwargs)

417+

```

418+419+

We found `*` and `**` operators again in the context of the *function definition*.

420+421+

In fact, `*args` and `**kargs` are ubiquitous in the scientific libraries in Python to reduce redundancy and allow flexible inputs.

422+423+

`*args` enables the function to handle *positional arguments* with a variable size

424+425+

```{code-cell} python3

426+

l1 = ['a', 'b', 'c']

427+

l2 = ['b', 'c', 'd']

428+429+

def arb(*ls):

430+

print(ls)

431+432+

arb(l1, l2)

433+

```

434+435+

The inputs are passed into the function and stored in a tuple.

436+437+

Let's try more inputs

438+439+

```{code-cell} python3

440+

l3 = ['z', 'x', 'b']

441+

arb(l1, l2, l3)

442+

```

443+444+

Similarly, Python allows us to use `**kargs` to pass arbitrarily many *keyword arguments* into functions

445+446+

```{code-cell} python3

447+

def arb(**ls):

448+

print(ls)

449+450+

# Note that these are keyword arguments

451+

arb(l1=l1, l2=l2)

452+

```

453+454+

We can see Python uses a dictionary to store these keyword arguments.

455+456+

Let's try more inputs

457+458+

```{code-cell} python3

459+

arb(l1=l1, l2=l2, l3=l3)

460+

```

461+462+

Overall, `*args` and `**kargs` are used when *defining a function*; they enable the function to take input with an arbitrary size.

463+464+

The difference is that functions with `*args` will be able to take *positional arguments* with an arbitrary size, while `**kargs` will allow functions to take arbitrarily many *keyword arguments*.

465+293466

## Decorators and Descriptors

294467295468

```{index} single: Python; Decorators

Read the original on github.com ↗