# Others

## Decorators

{% hint style="info" %}
You can create custom decorators also, and import them in your scripts.

Like decorators, for performance, and etc.
{% endhint %}

### Existing decorators in python

#### @cache

Cache result of functions.

```python
@cache
def some_func() -> int:
    ...
```

You can also see the cached info

```python
print(some_func.cache_info())
```

#### @atexit.register

It will call this decorated function when script terminates, for whatever reason.

{% hint style="info" %}
Interesting for DB connection close.
{% endhint %}

```python
import atexit

@atexit.register
def exit_handler() -> None:
    print('something')
```

You can also unregister these functions

```python
atexit.unregister(exit_handler)
```

## Walrus Operator

Transform this

```python
users: dict[int, str] = {0: 'Bob', 1: 'Mario'}

user: str | None = users.get(3)

if user:
    print('User exists')
else:
    print('User not found)
```

to this

```python
users: dict[int, str] = {0: 'Bob', 1: 'Mario'}

if user := users.get(3):
    print('User exists')
...
```

This operator firts evaluates the expression or function and than assign it to the variable.

```python
def get_into(text: str) -> dict:
    return {
        'words': (words := text.split()),
        'word_count': len(words)
    }
```

## [String formatting with F\_STRINGS](https://www.w3schools.com/python/python_string_formatting.asp)

### Thousand separator

```python
print(f'{n:_}') => 1_000
print(f'{n:,}') => 1,000
```

### Aligning output strings

```python
s: str = 'var'

# Fill before
print(f'{s:>5}') => '  var'

#Fill After (Optional to specify '<')
print(f'{s:<5}') => 'var  '

# Center the text
print(f'{s:^5}') => ' var '

# Will fill the spaces with char '#'
print(f'{s:#^5}') => '##var'
print(f'{s:#^5}') => 'var##'
print(f'{s:#^5}') => '#var#'
```

### Formatting datetime

```python
now: datetime = datetime.now()

print(f'{now:%d/%m/%y (%H:%M:%S)}') => '09/02/24 (10:40:20)'

# Local version of datetime
print(f'{now:%c}') => 'Fri Feb  9 10:40:20 2024'
```

### Formatting decimals

```python
# For truncating, instead of rounding (2 decimals)
print(f'{n:.2f}')

# Removing decimals
print(f'{n:.0f}')

# Adding thousand separator
print(f'{n:,.2f}')
```

### Showing "var name" in print

```python
a: int = 5
b: int = 5
s: str = 'Hi bob'
b: bool = True

# For showing the formulas along with result
print(f'{a + b = }') => 'a + b = 10'
print(f'{bool(b) = }') => 'bool(b) = True'

# For showing the var name and the result
print(f'{s = }') => "s = 'Hi bob'"
```

## Getting Memory Variable Use

```python
from sys import getsizeof

print(getsizeof(var))
```

## Random Functions

### shuffle

Shuffles a list in place.

```python
from random import shuffle

people: list[str] = ['Bob', 'Tom', 'James']

shuffle(people)
print(people)
```

### random

Returns a random number between `0 and 1`.

```python
from random import random

value: float = random()
```

### randint

Returns a random `int` number within a specified range `[beginning, end]`.

```python
from random import randint

value: int = randint(10, 20)
```

### randrange

Works the same as `randint`, but with a specified range `[beginning, end)`.

It also let's you provide a `step`, `randrange(beginning, end, step)`.

```python
from random import randrange

value: int = randrange(10, 20)

value: list[int] = randrange(10, 20, 2)  # -> Produces only 10, 12, 14, 16 and 18
```

### choice

Selects a random element from a list.

```python
from random import choice

people: list[str] = ['Bob', 'Tom', 'James']

choosen: str = choice(people)
```

### choices

Selects a specified `k` number of random elements from a list. *(By default `1` element)*

You can also provide a `weight` parameter, which will be a `tuple | list` of `float` that will be applied to the elements. *(Must be in the same order as the element's list)*

Returns a `list`.

```python
from random import choices

people: list[str] = ['Bob', 'Tom', 'James']
selected: list[str] = choices(people, k=2)

weights: tuple = (.15, .50, .05)
selected: list[str] = choices(people, k=5, weights=weights)
```

### sample

Does the same as `choices`, but it only retuns `unique` elements. *(Each will only appear once)*

This means `k` must be greater or equal to the length of the provided list.

If the element is repeated inside the provided list, IT CAN repeat.

```python
from random import sample

samples: list[int] = sample(range(100), k=10, weights=[...])
```

### seed

It will `"save"` the state of the other random functions, so that, they will reproduce the same result if necessary.

```python
from random import seed, ...

seed(1)

random()
choice(...)
sample(...)

seed(5)

...
```
