# Python's Built-in Functions: A Complete Exploration

by Leodanis Pozo Ramos Jul 01, 2024
Copied!
Happy Pythoning!

Python has many built-in functions that you can use directly without importing anything. These functions cover a wide variety of common programming tasks that include performing math operations, working with built-in data types, processing iterables of data, handling input and output in your programs, working with scopes, and more.

In this tutorial, you’ll:

• Get to know Python’s built-in functions
• Learn about common use cases of Python’s built-in functions
• Use these functions to solve practical problems

To get the most out of this tutorial, you’ll need to be familiar with Python programming, including topics like working with built-in data types, functions, classes, decorators, scopes, and the import system.

Take the Quiz: Test your knowledge with our interactive “Python's Built-in Functions: A Complete Exploration” quiz. You’ll receive a score upon completion to help you track your learning progress:

Interactive Quiz

Python's Built-in Functions: A Complete Exploration

Take this quiz to test your knowledge about the available built-in functions in Python. By taking this quiz, you'll deepen your understanding of how to use these functions and the common programming problems they cover, from mathematical computations to Python-specific features.

## Built-in Functions in Python

Python has several functions available for you to use directly from anywhere in your code. These functions are known as built-in functions and they cover many common programming problems, from mathematical computations to Python-specific features.

In this tutorial, you’ll learn the basics of Python’s built-in functions. By the end, you’ll know what their use cases are and how they work. To kick things off, you’ll start with those built-in functions related to math computations.

In Python, you’ll find a few built-in functions that take care of common math operations, like computing the absolute value of a number, calculating powers, and more. Here’s a summary of the math-related built-in functions in Python:

Function Description
`abs()` Calculates the absolute value of a number
`divmod()` Computes the quotient and remainder of integer division
`max()` Finds the largest of the given arguments or items in an iterable
`min()` Finds the smallest of the given arguments or items in an iterable
`pow()` Raises a number to a power
`round()` Rounds a floating-point value
`sum()` Sums the values in an iterable

In the following sections, you’ll learn how these functions work and how to use them in your Python code.

### Getting the Absolute Value of a Number: `abs()`

The absolute value or modulus of a real number is its non-negative value. In other words, the absolute value is the number without its sign. For example, the absolute value of -5 is 5, and the absolute value of 5 is also 5.

Python’s built-in `abs()` function allows you to quickly compute the absolute value or modulus of a number:

Python
``````>>> from decimal import Decimal
>>> from fractions import Fraction

>>> abs(-42)
42
>>> abs(42)
42

>>> abs(-42.42)
42.42
>>> abs(42.42)
42.42

>>> abs(complex("-2+3j"))
3.605551275463989
>>> abs(complex("2+3j"))
3.605551275463989

>>> abs(Fraction("-1/2"))
Fraction(1, 2)
>>> abs(Fraction("1/2"))
Fraction(1, 2)

>>> abs(Decimal("-0.5"))
Decimal('0.5')
>>> abs(Decimal("0.5"))
Decimal('0.5')
``````
Copied!

In these examples, you compute the absolute value of different numeric types using the `abs()` function. First, you use integer numbers, then floating-point and complex numbers, and finally, fractional and decimal numbers. In all cases, when you call the function with a negative value, the final result removes the sign.

For a practical example, say that you need to compute the total profits and losses of your company from a month’s transactions:

Python
``````>>> transactions = [-200, 300, -100, 500]

>>> incomes = sum(income for income in transactions if income > 0)
>>> expenses = abs(
...     sum(expense for expense in transactions if expense < 0)
... )

>>> print(f"Total incomes: \${incomes}")
Total incomes: \$800
>>> print(f"Total expenses: \${expenses}")
Total expenses: \$300
>>> print(f"Total profit: \${incomes - expenses}")
Total profit: \$500
``````
Copied!

In this example, to compute the expenses, you use the `abs()` function to get the absolute value of the expenses, which results in a positive value.

### Finding the Quotient and Remainder in Division: `divmod()`

Python provides a built-in function called `divmod()` that takes two numbers as arguments and returns a tuple with the quotient and remainder that result from the integer division of the input numbers:

Python
``````>>> divmod(8, 4)
(2, 0)

>>> divmod(6.5, 3.5)
(1.0, 3.0)
``````
Copied!

With integers as arguments, the result is the same as `(a // b, a % b)`. With floating-point numbers, the result is `(q, a % b)`, where `q` is usually `math.floor(a / b)`, but may be `1` less than that.

As a practical example of when to use this function, say that you want to code a function that takes a time value in milliseconds and returns a string with the `"00:00:00"` format. Here’s a possible implementation using the `divmod()` function:

Python
``````>>> def hh_mm_ss(milliseconds):
...     seconds = round(milliseconds / 1000)
...     minutes, seconds = divmod(seconds, 60)
...     hours, minutes = divmod(minutes, 60)
...     return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
...

>>> hh_mm_ss(10000)
'00:00:10'
>>> hh_mm_ss(68000)
'00:01:08'
>>> hh_mm_ss(3680000)
'01:01:20'
``````
Copied!

In this function, you first convert the input milliseconds to seconds and round the result to the nearest whole number.

Then, you use `divmod()` to divide the total seconds by 60 because there are 60 seconds in a minute. This computation gives you the minutes and the remaining seconds. Finally, you use `divmod()` again to divide the minutes by 60 because there are 60 minutes in an hour. This time, you get the hours and the remaining minutes.

### Finding Minimum and Maximum Values: `min()` and `max()`

Sometimes, you need to find the smallest and largest values in an iterable or in a series of values. These can be common computations in programming, and Python has built-in functions for them.

The `min()` function allows you to find the minimum value in an iterable, while the `max()` function helps you find the maximum value. Here’s the signature of both functions:

Python
``````min(iterable, *[, default, key])
max(iterable, *[, default, key])
``````
Copied!

Both functions take a required argument called `iterable` and return the minimum and maximum values, respectively. They also take two optional keyword-only arguments:

Argument Description
`default` Can hold the value you want to return if the input iterable is empty
`key` Accepts a single-argument function to customize the comparison criteria

Here are some quick examples of how to use the `min()` and `max()` functions with different sets of arguments:

Python
``````>>> min([1, 2, 3, 4])
1
>>> max([1, 2, 3, 4])
4

>>> min(1, 2, 3, 4)
1
>>> max(1, 2, 3, 4)
4

>>> min([], default=0)
0
>>> max([], default=0)
0

>>> min([-2, 3, 4, -5, 1], key=abs)
1
>>> max([-2, 3, 4, -5, 1], key=abs)
-5
``````
Copied!

In the first two examples, you use `min()` and `max()` with a list of numbers. You can also use these functions with a series of positional arguments.

Then, you have two examples of using the `default` argument to return a suitable value when the input iterable is empty. Finally, you have two examples of using the `key` argument. In these examples, you use the `abs()` function to provide the comparison criteria.

### Computing Powers: `pow()`

When you need to compute powers in your code, you can use the built-in `pow()` function. This function takes a number and raises it to a given power. Here’s the function’s signature:

Python
``````pow(base, exp[, mod=None])
``````
Copied!

When you call `pow()`, you get `base` to the power of `exp`. With these two arguments, `pow()` is equivalent to something like `base**exp`:

Python
``````>>> pow(2, 8)
256

>>> 2**8
256
``````
Copied!

This operation computes `2` to the power of `8`, which is `256`. This is equivalent to a power operation with the `**` operator, which you’ll find more often in real-world code.

The `mod` argument allows you to do something like `pow(base, exp) % mod` but computed more efficiently:

Python
``````>>> import timeit

>>> base = 2
>>> exp = 1000000
>>> mod = 1000000

>>> timeit.timeit(
...     "pow(base, exp, mod)", globals=globals(), number=10
... ) * 1000
0.021042011212557554

>>> timeit.timeit(
...     "pow(base, exp) % mod", globals=globals(), number=10
... ) * 1000
61.956208024639636
``````
Copied!

In this example, you use the `timeit()` function from the `timeit` module to measure the computation speed. Then, you define a few variables to do the computation. In the first call to `timeit()`, you use the `mod` argument. In the second call, you use the modulo operator (`%`).

When you compare the resulting time consumption, you can conclude that using the `mod` argument is way faster than computing the power and then applying the modulo operator like in `pow(base, exp) % mod`.

### Rounding Numbers: `round()`

Python’s built-in `round()` function takes a numeric argument and returns it rounded to a given number of digits.

The signature of `round()` is shown in the code below:

Python
``````round(number[, ndigits])
``````
Copied!

In this signature, `number` is typically a floating-point number, while `ndigits` is an optional argument that should be an integer number. This latter argument will define the precision or number of digits after the decimal point.

Here are a few examples:

Python
``````>>> from math import pi
>>> pi
3.141592653589793

>>> round(pi, 2)
3.14

>>> round(pi, 4)
3.1416

>>> round(pi, 6)
3.141593
``````
Copied!

In these examples, you use the `pi` constant from the math module and the `round()` function to express the number using different precision.

When you use `round()` with a single argument, you may get surprising results:

Python
``````>>> round(1.5)
2

>>> round(2.5)
2
``````
Copied!

In these two examples, the `round()` function rounds `1.5` up to `2` and `2.5` down to `2`. This is because `round()` rounds to the closest multiple of `10` to the power minus `ndigits`. If two multiples are equally close, rounding is done toward the even choice. This rounding half to even strategy helps mitigate rounding bias. That’s why `2.5` rounds to `2` rather than `3`.

### Calculating Totals: `sum()`

Python’s built-in `sum()` function provides an efficient and Pythonic way to sum a list of numeric values, which is also a common intermediate step in many computations. So `sum()` is a pretty handy tool for a Python programmer.

The `sum()` function allows you to add together a series of values. Its signature is like the following:

Python
``````sum(iterable[, start=0])
``````
Copied!

You can call `sum()` with the following two arguments:

Argument Description
`iterable` A required argument that can hold any Python iterable.
`start` An optional argument that can hold an initial value.

When you call `sum()`, the function internally adds `start` plus the values in `iterable`. The items in the input `iterable` are usually numeric values. However, you can also use lists or tuples. The `start` argument can accept a number, list, or tuple, depending on what your `iterable` contains.

Here are a few examples of how to use `sum()` with different inputs:

Python
``````>>> sum([])
0

>>> sum([1, 2, 3, 4, 5])
15

>>> sum([1, 2, 3, 4, 5], 100)  # As a positional argument
115

>>> sum([1, 2, 3, 4, 5], start=100)  # As a keyword argument
115

>>> num_lists = [[1, 2, 3], [4, 5, 6]]
>>> sum(num_lists, start=[])
[1, 2, 3, 4, 5, 6]

>>> num_tuples = ((1, 2, 3), (4, 5, 6))
>>> sum(num_tuples, start=())
(1, 2, 3, 4, 5, 6)
``````
Copied!

When you call `sum()` with an empty iterable, you get `0` as a result because that’s the default value of `start`. Calling the function with a list of values returns the total sum of the provided values.

If you want to use a `start` value other than `0`, then you can provide it as a positional or keyword argument. However, the latter approach is more readable.

The final two examples show that you can also use `sum()` to concatenate lists and tuples. Note that for this trick to work, you need to set `start` to the appropriate object. If you want to concatenate lists, then `start` must hold a list, and so on. Even though this trick works, the practice isn’t efficient or common. Instead, you should use the plus operator (`+`) for regular concatenations.

A classic example of using `sum()` is when you need to compute the mean or average of several numeric values. In this situation, you need to sum the input data as an intermediate step. Here’s an example:

Python `mean.py`
``````def mean(values):
try:
return sum(values) / len(values)
except ZeroDivisionError:
raise ValueError("mean() arg shouldn't be empty") from None
``````
Copied!

In this `mean()` function, you use `sum()` to sum the input values and then divide the result by the number of values in the input data.

## Creating and Manipulating Basic Data Types

Python has several built-in functions that allow you to manipulate basic data types, such as integer and floating-point numbers, strings, and Boolean values. Here’s a summary of the built-in functions that help you process basic data types:

Function Description
`int()` Constructs an integer object from a number or string
`bin()` Converts an integer to a binary string
`oct()` Converts an integer to an octal string
`hex()` Converts an integer to a hexadecimal string
`float()` Constructs a floating-point object from a number or string
`complex()` Constructs a complex number from arguments
`str()` Creates a string object
`repr()` Creates a developer-friendly string representation of an object
`bool()` Converts an argument to a Boolean value
`ord()` Looks up the integer code point of a character
`chr()` Looks up the character for the given integer code point
`bytes()` Creates a `bytes` object (similar to `bytearray`, but immutable)
`bytearray()` Creates an object of the `bytearray` class

In the following sections, you’ll learn the basics of working with these functions and how to use them in your Python code.

### Representing Integer Numbers: `int()`, `bin()`, `oct()`, and `hex()`

Integer numbers are pretty useful in programming. Python has a built-in data type called `int` that represents integers. When working with integers, sometimes you need to express them in different bases like `2`, `8`, or `16`. You may also need to convert strings or other numeric types to integers.

For the latter task, you can use the built-in `int()` function. Here are some examples of using it:

Python
``````>>> int()
0

>>> int(42.42)
42

>>> int("42")
42

>>> int("42.42")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: '42.42'

>>> int("one")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'one'
``````
Copied!

With no argument, `int()` returns `0`. This behavior is especially useful when you need a factory function for classes like `defaultdict` from the `collections` module. With floating-point numbers, `int()` just removes the decimal part and returns the whole part. Finally, with a string as an argument, `int()` returns the corresponding integer only if the string represents a valid integer number.

You can also use `int()` to convert a binary, octal, or hexadecimal string representation into an integer number:

Python
``````>>> int("0b10", base=2)
2

>>> int("0o10", base=8)
8

>>> int("0x10", base=16)
16
``````
Copied!

In the first example, you use `int()` to convert a string representing a number in binary format to its equivalent decimal integer. Note that for this operation to work, you need to set the `base` argument to the appropriate base, which is `2` for binary numbers. Next, you do similar conversions with octal and hexadecimal strings. Again, you have to set `base` to the appropriate value.

The `bin()`, `oct()`, and `hex()` functions allow you to do the opposite operation. With them, you can convert a given integer into its binary, octal, or hexadecimal representation:

Python
``````>>> bin(42)
'0b101010'

>>> oct(42)
'0o52'

>>> hex(42)
'0x2a'
``````
Copied!

In these examples, you use an integer number as an argument to `bin()`, `oct()`, and `hex()`. As a result, you get the string representation of the input value in binary, octal, and hexadecimal format, respectively.

### Manipulating Other Numbers: `float()` and `complex()`

Python has basic built-in types to represent floating-point and complex numbers. These types have associated built-in functions for conversion purposes. So, for floating-point numbers, you have the `float()` function, and for complex numbers you have `complex()`.

Here are the signatures of both functions:

Python
``````float(number=0.0)

complex(real=0, imag=0)
complex(string)
``````
Copied!

The `float()` function takes a single argument representing a numeric value. This argument accepts numbers or strings that represent valid numbers:

Python
``````>>> float()
0.0

>>> float(42)
42.0

>>> float("42")
42.0

>>> float("3.14")
3.14

>>> float("one")
Traceback (most recent call last):
...
ValueError: could not convert string to float: 'one'
``````
Copied!

With no arguments, `float()` returns `0.0`. With integer numbers, it returns the equivalent floating-point number with `0` as the decimal part. With strings representing numbers, `float()` returns the equivalent floating-point number. However, it fails if the input string doesn’t represent a valid numeric value.

The `complex()` function allows you to work with complex numbers. This function has two different signatures. The first signature has two arguments:

Argument Description
`real` The number’s real part
`imag` The number’s imaginary part

These arguments accept numeric values, such as integer or floating-point numbers. Here’s how this variation of `complex()` works:

Python
``````>>> complex(3, 6)
(3+6j)

>>> complex(1, 0)
(1+0j)

>>> complex(0, 1)
1j

>>> complex(3.14, -2.75)
(3.14-2.75j)
``````
Copied!

You can call `complex()` with numeric values, resulting in a complex number. Note that Python uses a `j` to define the imaginary part.

The second signature of `complex()` takes a single argument that should be a string:

Python
``````>>> complex("3+6j")
(3+6j)

>>> complex("1+0j")
(1+0j)

>>> complex("1j")
1j

>>> complex("3.14-2.75j")
(3.14-2.75j)
``````
Copied!

When you use strings to create complex numbers with `complex()`, you have to make sure that the input string has a valid format. It should consist of the real part, the sign, and the imaginary part. You can’t add spaces to separate these components.

### Building and Representing Strings: `str()` and `repr()`

When it comes to creating and working with Python strings, you have two fundamental built-in functions to consider:

1. `str()`
2. `repr()`

With the `str()` function, you can create new strings or convert existing objects to strings:

Python
``````>>> str()
''

>>> str(42)
'42'

>>> str(3.14)
'3.14'

>>> str([1, 2, 3])
'[1, 2, 3]'

>>> str({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"

>>> str({"A", "B", "C"})
"{'B', 'C', 'A'}"
``````
Copied!

In the first example, you use `str()` without an argument to create an empty string. In the other examples, you get strings with user-friendly representations of the input objects.

For a practical use case, say that you have a list of numeric values and want to join them using the `str.join()` method, which only accepts iterables of strings. In this case, you can do something like the following:

Python
``````>>> "-".join(str(value) for value in [1, 2, 3, 4, 5])
'1-2-3-4-5'
``````
Copied!

In this example, you use a generator expression to convert each number to its string representation before calling `.join()`. This way, you avoid getting an error in your code.

For its part, the built-in `repr()` function gives you a developer-friendly representation of the object at hand:

Python
``````>>> repr(42)
'42'

>>> repr(3.14)
'3.14'

>>> repr([1, 2, 3])
'[1, 2, 3]'

>>> repr({"one": 1, "two": 2, "three": 3})
"{'one': 1, 'two': 2, 'three': 3}"

>>> repr({"A", "B", "C"})
"{'B', 'C', 'A'}"
``````
Copied!

For built-in types, the string representation you get with `repr()` is the same as the one you get with the `str()` function.

To see the difference between `str()` and `repr()`, consider the following example that uses the `datetime` module:

Python
``````>>> import datetime
>>> today = datetime.datetime.now()

>>> repr(today)
'datetime.datetime(2024, 7, 1, 12, 38, 53, 180208)'

>>> str(today)
'2024-07-01 12:38:53.180208'
``````
Copied!

The `repr()` method gives you a developer-friendly string representation of the `datetime` object. Ideally, you should be able to re-create the object using this representation. In other words, you should be able to copy and paste the resulting representation to re-create the object. That’s why this string representation is said to be developer-friendly.

In contrast, the string representation that you get from calling `str()` should aim to be readable and informative for end users.

### Processing Boolean Values: `bool()`

Python’s built-in `bool()` function allows you to determine the truth value of any Python object. It’s a predicate function because it always returns `True` or `False`. To figure out if an object is falsy, in other words, whether `bool()` returns `False` when applied to the object, Python uses the following internal rules:

• Constants that are defined to be false: `None` and `False`
• The zero of any numeric type: `0`, `0.0`, `0j`, `Decimal(0)`, `Fraction(0, 1)`
• Empty sequences and collections: `''`, `()`, `[]`, `{}`, `set()`, `range(0)`

The rest of the objects are considered truthy in Python. Custom objects are considered truthy by default unless they provide a `.__bool__()` special method that defines a different behavior.

Here are a few examples of how `bool()` works:

Python
``````>>> bool()
False

>>> bool(0)
False
>>> bool(42)
True

>>> bool(0.0)
False
>>> bool(3.14)
True

>>> bool("")
False
>>> bool("Hello")
True

>>> bool([])
False
>>> bool([1, 2, 3])
True
``````
Copied!

In the first example, you call `bool()` without an argument and get `False` as a result. In the rest of the examples, you can confirm that Python consistently applies the rules listed above. In practice, you’ll only need to use `bool()` when your code explicitly requires a Boolean value instead of a different object.

As an example of using `bool()`, say that you have the following implementation of a stack data structure:

Python `stack.py`
``````class Stack:
def __init__(self, items=None):
self.items = list(items) if items is not None else []

def push(self, item):
self.items.append(item)

def pop(self):
return self.items.pop()

def __bool__(self):
return bool(self.items)
``````
Copied!

In this example, your `Stack` class implements the `.__bool__()` special method to support Boolean operations on its objects. This method guarantees that when a given `Stack` object is empty, the `bool()` function returns `False` and `True` otherwise. Here’s an example:

Python
``````>>> from stack import Stack

>>> stack = Stack()

>>> bool(stack)
False

>>> stack.push(4)

>>> bool(stack)
True
``````
Copied!

In this code snippet, you first create an empty stack. When you pass this object to `bool()`, you get `False`. Then, you push a value into the stack and call `bool()` again. This time, you get `True` because the stack isn’t empty anymore.

### Encoding Strings: `ord()` and `chr()`

Character encoding is an important topic in most programming languages. In Python, strings use the Unicode characters set by default. Each Unicode character has an associated code point, which is an integer number. To get the code point of a given character, you can use the built-in `ord()` function:

Python
``````>>> ord("A")
65

>>> ord("Z")
90

>>> ord("x")
120

>>> ord("ñ")
241

>>> ord("&")
38
``````
Copied!

Every Unicode character has an associated code point that uniquely identifies the character in the Unicode table. In these examples, you use the function to get the code point of a few characters.

In practice, you can use the `ord()` function to implement basic cryptographic techniques, sort strings or characters, validate input characters, and so on. Here’s a quick toy example of a function that only checks whether all the characters in a string are uppercase letters of the English alphabet:

Python
``````>>> def is_uppercase(text):
...     for char in text:
...         if not (65 <= ord(char) <= 90):
...             return False
...     return True
...

>>> is_uppercase("HELLO")
True

>>> is_uppercase("Hello")
False
``````
Copied!

In this function, you use `ord()` to determine whether the characters in a string are between `65` and `90`, which is the interval of code points for uppercase letters, A to Z, in the Unicode table.

Sometimes, you may need to determine the code point that identifies a given Unicode character. In this situation, you can use the built-in `chr()` function:

Python
``````>>> chr(65)
'A'

>>> chr(90)
'Z'

>>> chr(120)
'x'

>>> chr(241)
'ñ'

>>> chr(38)
'&'
``````
Copied!

The `chr()` function does the opposite operation of `ord()`. It allows you to find the code point associated with a specific character.

The `ord()` and `chr()` functions are sort of complementary, and therefore, you’ll probably find them used together in tandem.

### Creating Bytes and Bytearrays: `bytes()` and `bytearray()`

Python’s bytes and byte arrays are built-in types that Python provides out of the box to manipulate binary data, encode and decode text, process file input and output, and communicate through networks.

The `bytes` data type is immutable, while the `bytearray` type is mutable. To create objects derived from these data types, you can use the built-in `bytes()` and `bytearray()` functions.

The `bytes()` and `bytearray()` functions have the following signatures:

Python
``````bytes(source=b"")
bytes(source, encoding)
bytes(source, encoding, errors)

bytearray(source=b"")
bytearray(source, encoding)
bytearray(source, encoding, errors)
``````
Copied!

Both functions have three different signatures. The first signature of both functions accepts a `bytes` literal as an argument. These literals are similar to string literals, but they start with a `b` and only accept ASCII characters.

Here’s a summary of the arguments and their meaning:

Argument Description
`source` A `bytes` literal or a string
`encoding` The character encoding to use for decoding `source` if it holds a string
`errors` A handler for encoding and decoding errors

The `encoding` argument is only required if the `source` argument is a string, in which case, you must provide the appropriate encoding so that Python can convert the string into bytes.

Finally, the `errors` arguments is also optional and should hold one of the following error handlers:

Handler Description
`"strict"` Raises a `UnicodeDecodeError` or `UnicodeEncodeError` exception when encoding problems appear
`"ignore"` Ignores the characters that can’t be encoded
`"replace"` Replaces the characters that can’t be encoded with a question mark (`?`)
`"xmlcharrefreplace"` Replaces the characters that can’t be encoded with an XML character reference
`"backslashreplace"` Replaces the characters that can’t be encoded with Python’s string backslash escape sequences

By choosing the appropriate error handlers, you can set up a good strategy for those situations when you call the `bytes()` and `bytearray()` functions with erroneous data.

Here are a few examples of using the `bytes()` and `bytearray()` functions:

Python
``````>>> bytes()
b''

>>> bytes(b"Using ASCII characters or bytes \xc3\xb1")
b'Using ASCII characters or bytes \xc3\xb1'

>>> bytes("Using non-ASCII characters: ñ Ł", encoding="utf-8")
b'Using non-ASCII characters: \xc3\xb1 \xc5\x81'

>>> bytearray()
bytearray(b'')

>>> bytearray(b"Using ASCII characters or bytes \xc3\xb1")
bytearray(b'Using ASCII characters or bytes \xc3\xb1')

>>> bytearray("Using non-ASCII characters: ñ Ł", encoding="utf-8")
bytearray(b'Using non-ASCII characters: \xc3\xb1 \xc5\x81')
``````
Copied!

In these examples, you create `bytes` and `bytearray` objects using `bytes` literals, and strings with the correct encoding as an argument. Note that you can call the `bytes()` function with no arguments to create an empty `bytes` object.

Now consider the following examples that show how to use error handlers:

Python
``````>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii"
... )
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\xf1'
in position 52: ordinal not in range(128)

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="ignore"
... )
b'Using non-ASCII characters with the ASCII encoding:  '

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="replace"
... )
b'Using non-ASCII characters with the ASCII encoding: ? ?'

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="xmlcharrefreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: &#241; &#321;'

>>> bytes(
...     "Using non-ASCII characters with the ASCII encoding: ñ Ł",
...     encoding="ascii",
...     errors="backslashreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: \\xf1 \\u0141'
``````
Copied!

In these examples, you only use `bytes()` because `bytearray()` would work similarly. The only difference is that `bytes()` returns immutable objects while `bytearray()` returns mutable ones.

These examples use non-ASCII characters with the ASCII encoding, which will cause encoding errors that you’ll need to handle. The default value of the `errors` argument is `"strict"`. That’s why you get a `UnicodeEncodeError` exception in the first example above.

Then you set `errors` to `"ignore"` so that Python ignores any encoding error. In this case, the `ñ` and `Ł` characters are removed. If you set `errors` to `"replace"`, then `ñ` and `Ł` are each replaced with a question mark.

Using `"xmlcharrefreplace"` as the error handler makes Python replace the `ñ` and `Ł` characters with their respective XML character reference. Finally, using `"backslashreplace"` escapes the problematic characters by using the appropriate escape sequence.

## Creating Collection Data Types

A fundamental feature of Python is the rich set of collection data types built into the language. You’ll have several built-in functions that allow you to manipulate these data types, which include lists, tuples, dictionaries, sets, and bytes.

Here’s a summary of the built-in functions that help you process collection data types:

Function Description
`list()` Creates a `list` object from an iterable
`tuple()` Creates a `tuple` object from an iterable
`dict()` Creates a `dict` object from a series of key-value pairs or keyword arguments
`set()` Creates a `set` object from an iterable
`frozenset()` Creates a `frozenset` object from an iterable

In the following sections, you’ll learn the basics of working with these functions and using them to create and manipulate collections in your Python code.

### Creating Lists and Tuples: `list()` and `tuple()`

Python’s `list` is a fundamental built-in data type with an impressive set of features. Lists are mutable and allow you to efficiently organize and manipulate data that can be heterogeneous but is typically homogeneous. For example, you can use a list to store a column from a database table.

Similarly, Python’s `tuple` is another built-in type. Unlike lists, tuples are immutable. You can use them to organize data that can be homogeneous but is typically heterogeneous. For example, you can use a tuple to store a row from a database table.

Python’s built-in `list()` and `tuple()` functions allow you to create `list` and `tuple` objects.

The `list()` function takes an iterable as an argument and returns a `list` object built out of the input data. So, its signature looks something like the following:

Python
``````list([iterable])
``````
Copied!

Note that the square brackets around `iterable` mean that the argument is optional, so the brackets aren’t part of the syntax.

Here are a few examples of using `list()` to create `list` objects:

Python
``````>>> list()
[]

>>> list("Hello")
['H', 'e', 'l', 'l', 'o']

>>> list((1, 2, 3, 4, 5))
[1, 2, 3, 4, 5]

>>> list({"circle", "square", "triangle", "rectangle", "pentagon"})
['square', 'rectangle', 'triangle', 'pentagon', 'circle']

>>> list({"name": "John", "age": 30, "city": "New York"})
['name', 'age', 'city']
>>> list({"name": "John", "age": 30, "city": "New York"}.keys())
['name', 'age', 'city']

>>> list({"name": "John", "age": 30, "city": "New York"}.values())
['John', 30, 'New York']

>>> list({"name": "John", "age": 30, "city": "New York"}.items())
[('name', 'John'), ('age', 30), ('city', 'New York')]
``````
Copied!

When you call `list()` without an argument, you create a new empty list. When you use a string as an argument, you create a list of characters. When you use a tuple, you convert the tuple into a list.

The `list()` function even accepts sets, but you need to remember that sets are unordered data structures, so you won’t be able to predict the final order of items in the resulting list.

When it comes to using dictionaries with `list()`, you have four possibilities. You can create a list of keys using the dictionary directly or using its `.keys()` method. If you want to create a list of values, then you can use the `.values()` method. Finally, if you want to create a list of key-value pairs, then you can use the `.items()` method.

Lists have many use cases in Python code. They’re flexible, powerful, and feature-full, so you’ll find them in almost every piece of Python code.

Tuples are commonly used to store heterogeneous and immutable data. The `tuple()` function allows you to create tuples on the fly. Here’s the signature:

Python
``````tuple([iterable])
``````
Copied!

The square brackets around `iterable` mean that the argument is optional, so the brackets aren’t part of the syntax.

Consider the following examples of using `tuple()` in your code:

Python
``````>>> tuple()
()

>>> tuple("Hello")
('H', 'e', 'l', 'l', 'o')

>>> tuple(["Jane Doe", 25, 1.75, "Canada"])
('Jane Doe', 25, 1.75, 'Canada')

>>> tuple({
...     "manufacturer": "Ford",
...     "model": "Mustang",
...     "color": "Blue",
... }.values())
('Ford', 'Mustang', 'Blue')
``````
Copied!

You can use `tuple()` with no arguments to create an empty tuple. This will be more readable than using an empty pair of parentheses `()`. When you pass a string into `tuple()`, you get a tuple of characters.

In the third example, you use `tuple()` to convert a list of heterogeneous data into a tuple, which would be a more appropriate data structure for storing this type of data. Finally, you use the values of a dictionary to build a tuple.

Just like lists, tuples are pretty useful in Python. You’ll see them used in many use cases, especially in those situations where you need to store immutable heterogeneous data.

### Constructing Dictionaries: `dict()`

Dictionaries are a fundamental built-in data structure in Python. They’re everywhere and a core part of the language itself. You’ll find many use cases for dictionaries in your code. As for other built-in collections, Python also has a built-in function that allows you to create dictionaries: the `dict()` function.

The `dict()` function has the following signatures:

Python
``````dict(**kwargs)
dict(mapping, **kwargs)
dict(iterable, **kwargs)
``````
Copied!

All these signatures accept what is known as keyword arguments (`**kwargs`) or named arguments. The second signature takes a mapping, which can be another dictionary. Finally, the third signature accepts an iterable of key-value pairs, which can be a list of two-item tuples, for example.

Here are some quick examples of using the `dict()` function in different ways:

Python
``````>>> dict()
{}

>>> jane = dict(name="Jane", age="30", country="Canada")
>>> jane
{'name': 'Jane', 'age': '30', 'country': 'Canada'}

>>> dict(jane, job="Python Dev")
{'name': 'Jane', 'age': '30', 'country': 'Canada', 'job': 'Python Dev'}

>>> dict([("name", "Jane"), ("age", 30), ("country", "Canada")])
{'name': 'Jane', 'age': 30, 'country': 'Canada'}
``````
Copied!

Again, when creating an empty dictionary, you can use the `dict()` function without arguments. This is less common than using a pair of curly brackets `{}`, but again, it can be more readable and explicit in some contexts.

Then, you create a `jane` dictionary using keyword arguments. This is a clean and elegant way to build dictionaries in Python.

The third example shows how you can combine a mapping with keyword arguments to create a new dictionary object. Finally, in the fourth example, you create a new dictionary from a list of tuples.

### Creating Sets and Frozen Sets: `set()` and `frozenset()`

Python’s `set` is a built-in data type for creating collections of unique and hashable objects, typically called elements or members. In Python, sets support the operations defined for mathematical sets, including union, difference, symmetric difference, and others.

Python has two types of sets:

1. `set`
2. `frozenset`

The difference between these two data types is that `set` objects are mutable, and `frozenset` objects are immutable.

As with other data types, Python also provides built-in functions for creating sets and frozen sets. You’ll have the `set()` and `frozenset()` functions, respectively. The signature for these functions is shown below:

Python
``````set([iterable])
frozenset([iterable])
``````
Copied!

Again, the square brackets indicate that the input iterable is optional. Now check out the following examples of creating sets and frozen sets:

Python
``````>>> set()
set()

>>> frozenset()
frozenset()

>>> set(["square", "rectangle", "triangle", "pentagon", "circle"])
{'square', 'triangle', 'circle', 'rectangle', 'pentagon'}

>>> frozenset(["square", "rectangle", "triangle", "pentagon", "circle"])
frozenset({'square', 'triangle', 'circle', 'rectangle', 'pentagon'})

>>> set(("red", "green", "blue", "red"))
{'green', 'red', 'blue'}

>>> frozenset(("red", "green", "blue", "red"))
frozenset({'green', 'red', 'blue'})
``````
Copied!

When you call `set()` and `frozenset()` without arguments, you create an empty set or frozen set, respectively. In the case of sets, you don’t have a literal that you can use to create an empty set because a pair of curly brackets (`{}`) defines an empty dictionary. So, to create an empty set, you must use the `set()` function.

In the rest of the examples, you use iterables, such as lists and tuples, to create sets and frozen sets. It’s important to note that when the input iterable has repeated elements, the final set will have a single instance of the repeated item. Also, sets are unordered data structures, so you won’t be able to predict the final order of items in the resulting set when providing a list.

## Processing Iterables and Iterators

Python’s iterators and iterables are two different but related tools that come in handy when you need to iterate over a data stream or collection of data. Iterators power and control the iteration process, while iterables typically hold data that can be iterated over one value at a time.

Python has several built-in functions that you can use to work with iterables and iterators. Here’s a summary of these functions:

Function Description
`len()` Calculates the length of a sized object
`reversed()` Constructs a reverse iterator
`sorted()` Creates a sorted list from an iterable
`all()` Checks if all elements of an iterable are true
`any()` Checks if any elements of an iterable are true
`range()` Generates a range of integer values
`enumerate()` Creates an iterator of tuples containing indices and values from an iterable
`slice()` Creates a `slice` object
`zip()` Creates an iterator that aggregates elements from iterables
`iter()` Constructs an iterator object
`next()` Retrieves the next item from an iterator
`filter()` Filters elements from an iterable
`map()` Applies a function to every item of an iterable

In the following sections, you’ll learn about all these built-in functions and how they can be useful when processing iterables and iterators in your Python code.

### Determining the Number of Items in a Container: `len()`

One of the most common operations that you’ll perform on collections is to determine the number of items stored in an existing sequence or collection. To complete this task, Python has the built-in `len()` function.

The `len()` function takes a single argument, which can be a sequence, such as a string, tuple, or list. It can also be a collection, such as a dictionary, set, or frozen set. The function returns the length or number of items of the input object.

Here are a few examples of using `len()` with different objects:

Python
``````>>> len("Python")
6

>>> len(("Jane Doe", 25, 1.75, "Canada"))
4

>>> len([1, 2, 3, 4, 5])
5

>>> len({"green", "red", "blue"})
3

>>> len({"name": "Jane", "age": 30, "country": "Canada"})
3
``````
Copied!

In the first example, you use `len()` to get the number of characters in a string. Then, you use the function to determine the length of a tuple, list, and set. Finally, you use `len()` with a dictionary as an argument. In this case, you get the number of key-value pairs in the input dictionary.

Note that `len()` returns `0` when you call it with empty containers:

Python
``````>>> len("")
0
>>> len(())
0
>>> len([])
0
``````
Copied!

In these examples, you use `len()` with an empty string, tuple, and list. In all cases, you get `0` as a result.

The `len()` function can be useful in several situations. A common example is when you need to compute the average of a series of numeric values:

Python
``````>>> grades = [90, 97, 100, 87]

>>> sum(grades) / len(grades)
93.5
``````
Copied!

In this example, `len()` gives you the number of values in the list of grades. Then, you use this value to compute the average grade.

### Reversing and Sorting Iterables: `reversed()` and `sorted()`

Reversing and sorting the values in an iterable is another useful operation in programming. Because these operations are so common, Python has built-in functions for them. When you want to reverse an iterable, then you can use the built-in `reversed()` function.

The `reversed()` function takes an iterable as an argument and returns an iterator that yields the items in reverse order:

Python
``````>>> reversed([0, 1, 2, 3, 4, 5])
<list_reverseiterator object at 0x107062b30>

>>> list(reversed([0, 1, 2, 3, 4, 5]))
[5, 4, 3, 2, 1, 0]
``````
Copied!

In this example, you call `reversed()` with a list of numbers. The function returns a reverse iterator. To display its content, you can use the `list()` function to build a list from the iterator.

Similarly, when you need to sort the values in an iterable, you can use the `sorted()` function. This function has the following signature:

Python
``````sorted(iterable, key=None, reverse=False)
``````
Copied!

The first argument is an iterable object, such as a string, list, tuple, or dictionary. Then, you have the `key` and `reverse` arguments, which have the following meanings:

Argument Description
`key` Specifies a one-argument function that extracts a comparison key from each element in the input iterable
`reverse` Is a Boolean value that allows you to sort the items in reverse order if you set it to `True`

It’s important to note that the `key` argument accepts function objects. In other words, functions without the calling parentheses.

Here are a few examples of using `sorted()` with different built-in containers as arguments:

Python
``````>>> sorted("bdeac")
['a', 'b', 'c', 'd', 'e']

>>> sorted([4, 2, 7, 5, 1, 6, 3])
[1, 2, 3, 4, 5, 6, 7]

>>> sorted([4, 2, 7, 5, 1, 6, 3], reverse=True)
[7, 6, 5, 4, 3, 2, 1]
``````
Copied!

Unlike `reversed()`, the `sorted()` function always returns a list object rather than an iterator. In the first example, you use a string as an argument to `sorted()` and get a list of characters in alphabetical order. Under the hood, Python uses the character’s Unicode code point to compare strings.

In the third example, you set the `reverse` argument to `True` and get the list of numbers sorted in reverse order.

Finally, the `key` argument may be useful in several situations. A common use case for this argument is when you need to sort items that are also containers. Here’s an example of sorting a list of two-value tuples:

Python
``````>>> points = [(1, 2), (3, 1), (4, 0), (2, 1)]

>>> sorted(points, key=lambda point: point[0])
[(1, 2), (2, 1), (3, 1), (4, 0)]

>>> sorted(points, key=lambda point: point[1])
[(4, 0), (3, 1), (2, 1), (1, 2)]
``````
Copied!

In the first highlighted line, you use a `lambda` function that takes a point as an argument and returns its first coordinate. This example would produce the same result if you call `sorted()` without `key` because of the way Python compares tuples. In the second highlighted line, the `lambda` function returns the second coordinate. These functions provide comparison keys for the sorting processes.

So, in the first example, you sort the points by their first coordinate, while in the second example, you sort points by their second coordinate.

### Determining the Truth Value of Items in Iterables: `all()` and `any()`

Sometimes, you may need to determine whether all the items in an iterable are true. To do this, Python has the built-in `all()` function. At other times, you may need to find out if at least one item in an iterable is true. For this purpose, Python has the built-in `any()` function.

The signature of `all()` and `any()` are shown below:

Python
``````all(iterable)

any(iterable)
``````
Copied!

Both functions take an iterable of objects as an argument. The `all()` function returns `True` when all the items in the input iterable are true and `False` when at least one item is false.

Here are a few examples of how `all()` works:

Python
``````>>> all([1, 2, 3, 4])
True
>>> all([1, 2, 3, 4, 0])
False

>>> all(["Hello", ""])
False
>>> all(["Hello", "World"])
True
``````
Copied!

In the first two examples, you use `all()` with a list of numbers. The first list contains integer values different from `0`, which Python considers true. So, `all()` returns `True`. Then, in the second example, you add a `0` to the list, and `all()` returns `False` because this value is considered false in Python.

In the final two examples, you use a list of strings. Python considers an empty string false, so `all()` returns `False`. Finally, you pass a list with two non-empty strings, and `all()` reruns `True`.

You may find several use cases for `all()` in real-world code. Perhaps passing a generator expression as an argument to `all()` is one of the most powerful ways to use the function. For example, say that you want to determine whether all the numbers in a list are in a given interval. In this situation, you can use `all()` like in the following code:

Python
``````>>> numbers = [10, 5, 6, 4, 7, 8, 18]

>>> # Between 0 and 10
>>> all(0 <= x <= 10 for x in numbers)
False

>>> # Between 0 and 20
>>> all(0 <= x <= 20 for x in numbers)
True
``````
Copied!

In the first highlighted line, you use a generator expression as an argument to `all()`. The generator checks whether the numbers are between `0` and `10` and generates Boolean values according to the condition’s result.

The `all()` function checks if all the generated values are `True`. If that’s the case, then you get `True`. On the other hand, if at least one of the generated values is `False`, then you get `False`.

In contrast to `all()`, the `any()` function returns `True` if at least one item is true and `False` if all the items are false. So, you can use `any()` when you need to determine if at least one item in an iterable is true.

Here are a couple of examples of using `any()` in Python:

Python
``````>>> any([0, 0.0, False, "", []])
False

>>> any([0, 0.0, False, "", [], 42])
True
``````
Copied!

In the first example, all the objects in the input list are false according to Python’s internal rules for determining the truth value of an object. Because all the objects are false, `any()` returns `False`. In the second example, you add the number `42` to the end of the input list. Because `42` is truthy, `any()` returns `True`.

Again, just like `all()`, the `any()` function can shine when you use it with a generator expression that checks for some condition. For example, say that you want to know if at least one letter in a given text is in uppercase. In this situation, you can do something like the following:

Python
``````>>> any(letter.isupper() for letter in  "hello, world!")
False

>>> any(letter.isupper() for letter in  "Hello, World!")
True
``````
Copied!

In these examples, you use the `str.isupper()` method to determine whether a letter is in uppercase. This method returns `True` or `False`, so you get an iterable of Boolean values. The job of `any()` is to determine if any of these values is true.

In the first example, you have no uppercase letters, so `any()` returns `False`. In the second example, you have an uppercase `H`, so `any()` returns `True`.

### Creating Ranges of Integer Values: `range()`

Sometimes, you need to build numerical ranges to represent a series of integer values in a given interval. Usually, you need the numbers to be consecutive, but you may also want them to be nonsequential.

For example, you can create a range of values that contains all the digits using a Python list:

Python
``````[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
``````
Copied!

This approach will work if your range is relatively small like the one in this example. However, what if your range needs to have a million values? Building that kind of range with a list would be a difficult task. There’s a better way.

Python has the built-in `range()` function to facilitate the creation of numerical ranges:

Python
``````>>> range(10)
range(0, 10)

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> range(1_000_000)
range(0, 1000000)

>>> list(range(1_000_000))
[1, 2, 3,
...,
999998, 999999]
``````
Copied!

The `range()` function returns a `range` object rather than a list. If you want to check the values in a concrete range, then you can wrap it in a call to `list()`, as you did in the second example. Now you have a list of digits.

In the third example, you create a range with a million integers from `0` to `999999`. If you want to see it in action, pass it to `list()`. You’ll get a terminal window full of numbers!

The built-in `range()` function has the following signatures:

Python
``````range(stop)
range(start, stop, step=1)
``````
Copied!

You have a one-argument and three-argument variant of the function. Here’s what each argument means:

Argument Description
`start` It holds the initial value in the range. It defaults to `0` and is included in the range.
`stop` It holds the value at which the range stops. It’s a required argument, and its value isn’t included in the range.
`step` It holds the step through successive values. It’s an optional argument that defaults to `1`.

So far, you’ve used the one-argument variation, which starts the range at `0` and builds a range of consecutive integers. Here are a couple of examples showing the three-argument variation:

Python
``````>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> list(range(10, 101, 10))
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
``````
Copied!

Note that in the three-argument variation, the third argument is optional. This means that you can call the function with two arguments and rely on the default value of `step`, which is `1`. That’s what you do in the first example, which builds a range of consecutive integers from `1` to `11`. Again, `11` isn’t included in the final range because it’s the value at which `range()` stops issuing values.

In the second example, you create a `range` that starts at `10` and goes up to `101` with a step of `10`.

You can also use negative values for the arguments of `range()`. A common use case for this is when you need to create ranges of negative numbers and ranges that count backward:

Python
``````>>> list(range(-10, 0))
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1]

>>> list(range(100, 0, -10))
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
``````
Copied!

In this example, the first `range` contains consecutive negative numbers from `-10` up to `0`. The second `range` counts backward from `100` to `0` with a step of `-10`.

Ranges can be useful in several situations. A good use case for a range is when you need to perform an operation a given number times:

Python
``````>>> for _ in range(3):
...     print("Beep")
...
Beep
Beep
Beep
``````
Copied!

This loop runs three times and prints a message to the screen. However, the loop doesn’t need to use the loop variable in its body. So, you use an underscore to denote that this is a throwaway variable.

A common misuse of `range` objects in Python is to use them as a way to loop over an existing iterable:

Python
``````>>> letters = ["a", "b", "c", "d"]

>>> for index in range(len(letters)):
...     print(index, letters[index])
...
0 a
1 b
2 c
3 d
``````
Copied!

This kind of `for` loop isn’t the best example of a Pythonic construct. It uses `range()` to loop over the letters with numeric indices. In the following section, you’ll learn about the Pythonic way to write this kind of loop.

### Enumerating Items in Loops: `enumerate()`

A Pythonic `for` loop iterates over the items in an iterable without considering the index at which a given item lives or the order in which the items were processed. You’ll find that most Python `for` loops will look something like the following:

Python
``````>>> colors = ["red", "orange", "yellow", "green"]

>>> for color in colors:
...     print(color)
...
red
orange
yellow
green
``````
Copied!

This way of writing loops in Python is explicit and intuitive, which deeply impacts the loop’s readability. However, sometimes you’ll need a way to access the index where a given item lives in the input iterable.

Often, when you’re starting with Python and need to access indices in a loop, you might end up writing a loop like the following:

Python
``````>>> for index in range(len(colors)):
...     print(index, colors[index])
...
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
``````
Copied!

This kind of loop works. However, it’s not what you can call a Pythonic loop. It uses a couple of tricks to somehow mimic iteration over indices. Python offers a better tool to do this, and that tool is the built-in `enumerate()` function.

Here’s how you’ll write the above loop using the `enumerate()` function:

Python
``````>>> for index, color in enumerate(colors):
...     print(index, color)
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
``````
Copied!

This loop is readable and explicit. The `enumerate()` function takes an iterable as an argument and generates two-item tuples containing an integer index and the associated item.

Here’s the function’s signature:

Python
``````enumerate(iterable, start=0)
``````
Copied!

The first argument is an iterable object. The second argument, `start`, gives you the option to define a starting value for the enumeration. The default value is `0` because that’s the usual start value of indices in programming. However, in some situations, using a different starting point, like `1`, can be convenient.

To illustrate how to use the `start` argument, say that you’re building a text-based user interface (TUI) application and want to display a menu with some options. The options should have an associated number so that the user can choose the desired action. In this situation, you can use `enumerate()` as in the code below:

Python
``````>>> def list_menu(options):
...     print("Main Menu:")
...     for index, option in enumerate(options, start=1):
...         print(f"{index}. {option}")
...

>>> list_menu(["Open", "Save", "Settings", "Quit"])
Main Menu:
1. Open
2. Save
3. Settings
4. Quit
``````
Copied!

From the end user’s perspective, starting the menu list at `1` is the natural way to go. You can achieve this effect by setting `start` to `1` in the call to `enumerate()`. Now, the menu starts at `1` instead of `0`.

### Extracting Slices or Portions of Sequences: `slice()`

When you’re working with Python, you may need to extract a portion or a slice of an existing sequence, such as a string, list, or tuple. To do this, you typically use the slicing operator (`[]`). However, you can also use the built-in `slice()` function. The `slice()` function has the following signatures:

Python
``````slice(stop)
slice(start, stop, step=None)
``````
Copied!

The `slice()` function returns a `slice` object representing the set of indices specified by `range(start, stop, step)`. The arguments here have a similar meaning as in the `range()` function:

Argument Description
`start` It holds the initial value in the slice. It defaults to `None`, which indicates the start of the sequence.
`stop` It holds the value at which the slice stops. It’s a required argument, and its value isn’t included in the slice. When set to `None`, it means the end of the sequence (`len(sequence)`).
`step` It holds the step through successive values. It’s an optional argument that defaults to `None`, which means a step of `1`.

Slices don’t represent numeric ranges but sets of indices. You can use these indices to extract a portion of a list. Here are a few examples:

Python
``````>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> even = numbers[slice(1, None, 2)]
>>> even
[2, 4, 6, 8]

>>> odd = numbers[slice(None, None, 2)]
>>> odd
[1, 3, 5, 7, 9]
``````
Copied!

In the first slice, you start at `1` and go up to the end of the list with a step of `2`. This slice gives you a list of even numbers. In the second example, you start at the beginning of the list and go up to the end of the list with a step of `2`. With this slice, you get a list of odd numbers.

Note that in most Python code, you won’t see or use `slice()` the way you did in the example above. In most cases, you’ll use the slicing operator, `[start:stop:step]`. Here’s what this looks like with this operator:

Python
``````>>> even = numbers[1::2]
>>> even
[2, 4, 6, 8]

>>> odd = numbers[::2]
>>> odd
[1, 3, 5, 7, 9]
``````
Copied!

In these examples, you use the slicing operator to get the even and odd numbers from your original list. Note that skipping a given index makes that index rely on its default value. For example, when you don’t provide a `start` index, then it defaults to the beginning of the list.

### Zipping Iterables for Parallel Iteration: `zip()`

Python’s built-in `zip()` function allows you to iterate over multiple iterables in parallel. This function creates an iterator that will aggregate elements from two or more iterables, yielding tuples of values.

Here’s the signature of the built-in `zip()` function:

Python
``````zip(*iterables, strict=False)
``````
Copied!

This function takes an undefined number of iterables as arguments and yields tuples of items on demand. The tuples will contain an item from each input iterable, making it ideal for parallel iteration.

Here are some quick examples:

Python
``````>>> letters = ["a", "b", "c"]
>>> numbers = [1, 2, 3]
>>> operators = ["*", "/", "+"]

>>> for characters in zip(letters, numbers, operators):
...     print(characters)
...
('a', 1, '*')
('b', 2, '/')
('c', 3, '+')

>>> for l, n, o in zip(letters, numbers, operators):
...     print(f"{l} {n} {o}")
...
a 1 *
b 2 /
c 3 +
``````
Copied!

In the first loop, you use a single loop variable to store each tuple that you get from `zip()`. The first tuple contains the first items of each input iterable. The second tuple contains the second items, and so on. In the second loop, you use three loop variables to unpack the items of each generated tuple.

A nice use case of `zip()` is when you have two lists and want to create a dictionary from them. Consider the following example:

Python
``````>>> keys = ["name", "age", "country"]
>>> values = ["Jane", "30", "Canada"]

>>> dict(zip(keys, values))
{'name': 'Jane', 'age': '30', 'country': 'Canada'}
``````
Copied!

In this example, you combine two existing lists using `zip()` and pass the resulting tuples to the `dict()` function to create a dictionary.

The `strict` argument to `zip()` was added in Python 3.10 and is a keyword-only argument that provides a safe way to handle iterables of unequal length. The argument’s default value is `False`, which means that `zip()` will only generate as many tuples as items in the shortest iterable.

If you set `strict` to `True`, then you’ll get a `ValueError` exception when the input iterables don’t have the same length:

Python
``````>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

>>> list(zip(range(5), range(100), strict=True))
Traceback (most recent call last):
...
ValueError: zip() argument 2 is longer than argument 1
``````
Copied!

In the first argument, you rely on the default value of `strict` and get five tuples because the shortest range only has five values. In the second example, you set `strict` to `True`. This time, you get an error because the input ranges don’t have the same number of values.

### Building and Consuming Iterators: `iter()` and `next()`

In Python, iterators implement the iterator design pattern, which allows you to traverse a container and access its elements. The iterator pattern decouples the iteration algorithms from containers, such as lists, tuples, dictionaries, and sets.

Python has two built-in functions that can help you out when working with iterators. The `iter()` function allows you to build an iterator from an iterable, and the `next()` function lets you consume an iterator one item at a time.

Consider the following toy example:

Python
``````>>> colors = ["red", "orange", "yellow", "green"]

>>> colors_it = iter(colors)
>>> colors_it
<list_iterator object at 0x10566a170>

>>> next(colors_it)
'red'
>>> next(colors_it)
'orange'
>>> next(colors_it)
'yellow'
>>> next(colors_it)
'green'
>>> next(colors_it)
Traceback (most recent call last):
...
StopIteration
``````
Copied!

In this example, you use the `iter()` function to create an iterator object from an existing list of colors. Unlike iterables, iterators support the `next()` function. When you call this function with an iterator as an argument, you get the first item in the first call. When you call the function again, you get the second item, and so on.

When you traverse all the items in the iterator, `next()` raises a `StopIteration` exception. Python internally uses this exception to stop the iteration process in a `for` loop or a comprehension. Note that you can traverse an iterator only once. After that, the iterator will be exhausted or consumed.

Here are the signatures of `iter()` :

Python
``````iter(iterable)
iter(object, sentinel)
``````
Copied!

In the first signature, `iterable` represents any iterable type. In the second signature, the `object` argument must be a callable. You’ve already seen the first signature in action. Now, it’s time to take a quick look at the second signature.

To illustrate with an example, say that you’re working on a command-line interface (CLI) app and want to take the user’s input until they enter the word `"done"`. Here’s how you can do this using the `iter()` function:

Python
``````>>> def read_user_input():
...     print("Enter word (type 'done' to finish):")
...     for word in iter(input, "done"):
...         print(f"Processing word: '{word}'")
...

>>> read_user_input()
Enter word (type 'done' to finish):
Python
Processing word: 'Python'
Programming
Processing word: 'Programming'
Iterators
Processing word: 'Iterators'
done
``````
Copied!

In the highlighted line, you use `iter()` with two arguments. For the first argument, you use the built-in `input()` function, which allows you to take input from the user. Note that you don’t call the function but pass it as a function object.

Then, you have the word `"done"`, which works as a sentinel. In other words, `iter()` will call `input()` for you and raise a `StopIteration` exception if its return value matches the sentinel word.

When you call the function, you’re asked to enter a word, then the code processes the word and lets you enter another word. These actions repeat until you enter your sentinel, the word `"done"`.

When it comes to the `next()` function, you’ll also have two different signatures that looks something like this:

Python
``````next(iterator)
next(iterator, default)
``````
Copied!

Again, you’ve seen how to use `next()` with an iterable as its unique argument. Now, you can focus on using the second signature. In this case, you have a second argument called `default`. This argument allows you to provide the value that you want to return when the input iterable is exhausted or its data is over:

Python
``````>>> count_down = iter([3, 2, 1])

>>> next(count_down, 0)
3
>>> next(count_down, 0)
2
>>> next(count_down, 0)
1
>>> next(count_down, 0)
0
>>> next(count_down, 0)
0
``````
Copied!

In this example, you create an iterator from a list of numbers. Then, you use `next()` to consume the iterator one number at a time. After `next()` has consumed the entire iterator, you get `0` as a result because that’s the value you passed to `default`, the second positional argument. Successive calls to the function will return `0` as well.

### Filtering and Mapping Iterables: `filter()` and `map()`

Have you heard about functional programming? It’s a programming paradigm where a program is dominated by calls to pure functions, which are functions whose output values depend solely on their input values without any observable side effects.

Python isn’t what you could call a functional programming language. However, it has a couple of built-in functions that are classical functional tools. These tools are the built-in `filter()` and `map()` functions.

You can use the `filter()` function to extract values from iterables, which is known as a filtering operation.

The signature of `filter()` looks something like this:

Python
``````filter(function, iterable)
``````
Copied!

The first argument, `function`, must be a single-argument function, while the second argument can be any Python iterable. Here’s a short description of these arguments:

Argument Description
`function` A predicate or Boolean-valued function that accepts a single argument
`iterable` A Python iterable

The `function` argument is a decision function, also known as a filtering function. It provides the criteria for deciding whether to keep a given value.

In practice, `filter()` applies `function` to all the items in `iterable`. Then, it creates an iterator that yields only the items that meet the criteria that `function` checks. In other words, it yields the items that make `function` return `True`.

The `filter()` function allows you to process iterables without a formal loop. Here’s an example of using `filter()` to extract even numbers from a list of values:

Python
``````>>> numbers = [1, 3, 10, 45, 6, 50]

>>> list(filter(lambda n: n % 2 == 0, numbers))
[10, 6, 50]
``````
Copied!

In this example, the `lambda` function takes an integer and returns `True` if the input value is an even number and `False` otherwise. The call to `filter()` applies this `lambda` function to the values in `numbers` and filters out the odd numbers, returning the even numbers. Note that you use the `list()` function to create a list from the iterator that `filter()` returns.

The `map()` function is another common tool in functional programming. This function allows you to apply a transformation function to all the values in an iterable.

Python’s `map()` has the following signature:

Python
``````map(function, iterable, *iterables)
``````
Copied!

The `map()` function applies `function` to each item in `iterable` in a loop and returns a new iterator that yields transformed items on demand.

Here’s a summary of the arguments and their meanings:

Argument Description
`function` A Python function that takes a number of arguments equal to the number of input. iterables
`iterable` A required argument that can hold any Python iterable.
`*iterables` A variable number of Python iterables.

The `function` argument is what is called a transformation function. It applies a specific transformation to its arguments and returns a transformed value.

To illustrate how `map()` works, say that you have two lists. The first list contains a series of values that you want to use as the bases in power computations. The second list contains the exponents that you want to apply to each base. You can use the `map()` function to process these lists and get an iterator of powers:

Python
``````>>> bases = [8, 5, 2]
>>> exponents = [2, 3, 4]

>>> list(map(pow, bases, exponents))
[64, 125, 16]
``````
Copied!

In this example, you use the built-in `pow()` function as the first argument to `map()`. As you already learned, `pow()` takes a base and an exponent as arguments and returns the power. Then, you pass the bases and exponents to `map()` so that it computes the desired powers.

## Processing Input and Output

If you need to take input from the user or files and present output to the user, then you should know that the language has a few built-in functions that can help you with these tasks:

Function Description
`input()` Reads input from the console
`open()` Opens a file and provides access to a file object
`print()` Prints to a text stream or the console
`format()` Converts a value to a formatted representation

In the following sections, you’ll dive into using these functions to process input and output operations in your Python code.

### Accepting Input From the User: `input()`

Taking input from your users is a common operation in CLI and text-based interface (TUI) applications. Python has a built-in function that’s specifically targeted to this type of operation. The function is conveniently called `input()`.

The built-in `input()` function reads the user’s input and grabs it as a string. Here’s the function’s signature:

Python
``````input([prompt])
``````
Copied!

The square brackets around `prompt` are an indication that this argument is optional. This argument allows you to provide a prompt to ask the user for the required or desired input.

As an example of using `input()`, say that you want to create a number-guessing game. The game will prompt the user to enter a number from 1 to 10 and check if the input value matches a secret number.

Here’s the code for the game:

Python `guess.py`
``````from random import randint

LOW, HIGH = 1, 10

secret_number = randint(LOW, HIGH)
clue = ""

while True:
guess = input(f"Guess a number between {LOW} and {HIGH} {clue} ")
number = int(guess)
if number > secret_number:
clue = f"(less than {number})"
elif number < secret_number:
clue = f"(greater than {number})"
else:
break

print(f"You guessed it! The secret number is {number}")
``````
Copied!

In this code, you define an infinite loop where you prompt the user to make their guess by entering a number between 1 and 10. The first line in the loop is a call to the built-in `input()` function. You’ve used a descriptive prompt to inform the users what to do.

Go ahead and run the script from your command line to try it out:

Shell
``````\$ python guess.py
Guess a number between 1 and 10  2
Guess a number between 1 and 10 (greater than 2) 3
Guess a number between 1 and 10 (greater than 3) 8
Guess a number between 1 and 10 (less than 8) 6
You guessed it! The secret number is 6
``````
Copied!

Cool! Your game asks the user to enter a number, compares it with the secret number, and lets the users know when they guess correctly. The `input()` function plays a core role in the game’s flow, allowing you to get and process the user’s input.

### Opening Files: `open()`

Reading from and writing to files are common programming tasks. In Python, you can use the built-in `open()` function for these purposes. You typically use the `open()` function in a `with` statement.

As a quick example, say that you have a text file with the following content:

Text `fruits.txt`
``````apple
banana
cherry
orange
mango
``````
Copied!

You want to open the file and read its content while you print it to the screen. To do this, you can use the following code:

Python
``````>>> with open("fruits.txt") as file:
...     print(file.read())
...
apple
banana
cherry
orange
mango
``````
Copied!

In this `with` statement, you call `open()` with the filename as an argument. This call opens the file for reading. The `open()` function returns a file object, which the `with` statement assigns to the `file` variables with the `as` specifier.

The `open()` function has the following signature:

Python
``````open(
file,
mode="r",
buffering=-1,
encoding=None,
errors=None,
newline=None,
closefd=True,
opener=None,
)
``````
Copied!

The function can take up to eight arguments. The first argument, `file`, is the only required argument. The rest of the arguments are optional. Here’s a summary of the arguments’ meaning:

Argument Description Comment
`file` A path-like object holding the path to the target file It’s a required argument.
`mode` A string that specifies the mode in which you want to open the file It defaults to `"r"`, which is the reading mode. You’ll learn about the available modes in a moment.
`buffering` An integer that sets the buffering policy You can pass `0` to switch buffering off, which is only possible in binary mode. You can use `1` to select line buffering, which is only usable in text mode. Finally, you can use an integer greater than `1` to indicate the size in bytes of a fixed-size chunk buffer.
`encoding` The name of the encoding used to decode or encode the file You can only use this argument in text mode.
`errors` A string that specifies how encoding and decoding errors are to be handled You can only use this argument in text mode. It can take one of the following values: `"strict"`, `"ignore"`, `"replace"`, `"surrogateescape"`, `"xmlcharrefreplace"`, `"backslashreplace"`, or `"namereplace"`. These values have similar meanings to those you learned in the section about the `ord()` and `chr()` functions.
`newline` A string that determines how to parse newline characters from the stream It can be `None`, `""`, `"\n"`, `"\r"`, or `"\r\n"`.
`closefd` A Boolean value that defines whether you want to close a file descriptor It can be `False` when you provide a file descriptor instead of a filename and want the descriptor to remain open when the file is closed. Otherwise, it must be `True`.
`opener` A callable that you use as a custom opener for the target file The opener must return an open file descriptor.

In this tutorial, you won’t cover all these arguments. Instead, you’ll learn about two of the most commonly used arguments, which are `mode` and `encoding`.

Here’s a list of allowed `mode` values:

Value Description
`"r"` Opens the file for reading and is the default value
`"w"` Opens the file for writing, truncating the file first
`"x"` Opens the file for exclusive creation, failing if the file already exists
`"a"` Opens the file for writing, appending the new data to the end of the file if it already exists
`"b"` Opens the file in binary mode
`"t"` Opens the file in text mode, which is the default mode
`"+"` Opens the file for updating, which allows reading and writing operations

In this table, the `"b"` and `"t"` values define two generic modes for binary and text files, respectively. You can combine these two modes with other modes. For example, the `"wb"` mode allows you to write binary data to a file, the `"rt"` mode enables you to read text-based data from a file, and so on.

Note that `"t"` is the default mode. So, if you set the mode to `"w"`, Python assumes that you want to write text to the target file.

Here’s a code snippet that writes some text to a file in your working directory:

Python
``````>>> with open("hello.txt", "w") as file:
...     file.write("Hello, World!")
...
13
``````
Copied!

In this example, you open a file called `hello.txt` so you can write text to it. In the code block of the `with` statement, you call the `.write()` method on the file object to write some text. Note that the method returns the number of written bytes. That’s why you get `13` on the screen.

After running the code, you’ll have the `hello.txt` file in your working directory. Go ahead and open it to check its content.

You can experiment with other modes and get an idea of how they work so that you can use them safely in your code. Take this as a practical exercise!

Using the `encoding` argument is another typical requirement when you’re working with text files. In this situation, it’s a best practice to explicitly state the text encoding that you’re using in your code. The UTF-8 encoding is a common example of a value that you’d pass to `encoding`:

Python
``````>>> with open("hello.txt", "w", encoding="utf-8") as file:
...     file.write("Hello, Pythonista!")
...
13

>>> with open("hello.txt", "r", encoding="utf-8") as file:
...     print(file.read())
...
Hello, Pythonista!
``````
Copied!

In this example, you use the UTF-8 encoding to write and read from a text file. Note that you need to explicitly use the argument’s name to provide the encoding value. This is because the following argument in the list is `buffering` rather than `encoding`, and if you don’t use the explicit name, then you get a `TypeError` exception.

### Printing Text to the Screen or Another Output: `print()`

Another common requirement that arises when you’re creating CLI or TUI applications is to display information on the screen to inform the user about the app’s state. In this case, you can use the built-in `print()` function, which is a fundamental tool in Python programming.

The `print()` function has the following signature:

Python
``````print(*objects, sep=" ", end="\n", file=None, flush=False)
``````
Copied!

Calling `print()` will print the input objects to the screen by default. You can use the rest of the arguments to tweak how the function works. Here’s a summary of the arguments and their meaning:

Argument Description
`*objects` An arbitrary number of Python objects
`sep` The string you want to use to separate input objects from one another
`end` The string to use after the last input object
`file` The open file object where you want to write the input objects
`flush` A Boolean value that defines whether you want to flush the output buffer

When you call `print()`, it takes the input `objects`, converts them into strings, joins them using `sep`, and appends `end`. If you call `print()` with no argument, then it prints `end`. Note that the arguments `sep`, `end`, `file`, and `flush` are keyword arguments.

Here are a few examples of how to use the `print()` function:

Python
``````>>> print()

>>> print("Hello")
Hello
>>> print("Hello", "Pythonista!")
Hello Pythonista!
>>> print("Hello", "Pythonista!", sep="\t")
Hello    Pythonista!
>>> print("Hello", "Pythonista!", sep="\t", end=" 👋\n")
Hello    Pythonista! 👋
``````
Copied!

When you call `print()` with no arguments, then `end` is printed on the screen. This argument defaults to a newline character, so that’s what you get. With an object as an argument, the object is printed, followed by a newline character. With several objects as arguments, the arguments are joined by `sep`, and a newline is added at the end.

You can also tweak the value of `end` and make Python print something different at the end of your output.

The `file` argument defaults to the standard output, which is your screen. The `sys.stdout` stream provides this default value. However, you can redirect the output to a file object of your preference:

Python
``````>>> with open("hello.txt", mode="w") as text_file:
...     print("Hello, World!", file=text_file)
...
``````
Copied!

This code snippet will override your existing `hello.txt` file from the previous section, writing the phrase `"Hello, World!"` into it.

Finally, you have the `flush` argument that has to do with data buffering. By default, Python buffers the calls to `print()` in a RAM data buffer. This allows Python to make fewer system calls for write operations by batching characters in the buffer and writing them all at once with a single system call.

You can set the `flush` argument to `True` if you want your code’s output to display in real time. If you keep `flush` at its default value of `False`, then Python will buffer the output, and that output will only show up once the data buffer is full or when your program finishes execution.

A cool example of using `flush` is when you need to create a progress bar for a CLI application. Consider the following function:

Python `progress.py`
``````def progress(percent=0, width=30):
end = "" if percent < 100 else "\n"
left = width * percent // 100
right = width - left
print(
"\r[",
"#" * left,
" " * right,
"]",
f" {percent:.0f}%",
sep="",
end=end,
flush=True,
)
``````
Copied!

This function generates a horizontal progress bar by taking advantage of the `flush` argument. Here’s how you can use it in your code:

Python
``````>>> from time import sleep
>>> from progress import progress

>>> for percent in range(101):
...     sleep(0.2)
...     progress(percent)
...
[###########                   ] 38%
``````
Copied!

This loop calls `progress()` with successive hypothetical progress values. The output of each call is flushed and the progress bar is displayed in the same line.

### Formatting Strings: `format()`

Python has a couple of handy tools for string interpolation and formatting, including f-strings and the `str.format()` method. These tools take advantage of Python’s string formatting mini-language, which allows you to nicely format your strings using a dedicated syntax.

The built-in `format()` function is another tool that you can use to format values. The function has the following signature:

Python
``````format(value, format_spec="")
``````
Copied!

The function converts `value` to a formatted representation. To define the desired format, you can use the `format_spec` argument, which accepts a string that follows the syntax defined in the string formatting mini-language. The `format_spec` argument defaults to an empty string, which causes the function to return the value as passed.

Consider the following examples of using the `format()` function:

Python
``````>>> import math
>>> from datetime import datetime

>>> format(math.pi, ".4f")  # Four decimal places
'3.1416'

>>> format(math.pi, "e")  # In scientific notation
'3.141593e+00'

>>> format(1000000, ",.2f")  # Thousand separators
'1,000,000.00'

>>> format("Header", "=^30")  # Centered and filled
'============Header============'

>>> format(datetime.now(), "%a %b %d, %Y")  # Date
'Mon Jul 1, 2024'
``````
Copied!

In these examples, you’ve used several different format specifiers. The `".4f"` specifier formats the input value as a floating-point number with four decimal places. The `"e"` specifier allows you to format the input value using scientific notation.

With the `",.2f"` format specifier, you can format a number using commas as thousand separators and with two decimal places, which is an appropriate format for currency values. Then, you use the `"=^30"` specifier to format the string `"Header"` centered in a width of `30` characters using the equal sign as a filler character. Finally, you use the `"%a %b %d, %Y"` to format a date.

## Working With Classes, Objects, and Attributes

Python supports object-oriented programming (OOP) with classes, types, inheritance, and many other related features. In Python, everything is an object. So, the OOP paradigm is core to the language itself.

You’ll have several built-in functions that help you with different tasks related to classes, types, attributes, methods, inheritance, and other OOP-related concepts.

Here’s a summary of Python’s OOP-related built-in functions:

Function Description
`property()` Returns a property value of a class
`classmethod()` Returns a class method
`staticmethod()` Returns a static method
`getattr()` Returns the value of a named attribute of an object
`setattr()` Sets the value of a named attribute of an object
`delattr()` Deletes an attribute from an object
`hasattr()` Returns `True` if an object has a given attribute
`type()` Return the type of an object or allows for creating new classes dynamically
`isinstance()` Determines whether an object is an instance of a given class
`issubclass()` Determines whether a class is a subclass of a given class
`callable()` Returns `True` if an object appears to be callable
`super()` Returns a proxy object that delegates method calls to a parent or sibling class
`object()` Creates a new featureless object

In the following sections, you’ll learn about all these functions and how to use them in your object-oriented Python code.

### Building Properties: `property()`

Python’s built-in `property()` function allows you to create managed attributes in your classes. Managed attributes, also known as properties, have an associated value and an internal implementation or function-like behavior.

To illustrate this with an example, say that you want to create a `Point` class. In Python, you’ll start with something like the following:

Python
``````>>> class Point:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...

>>> point = Point(42, 21)

>>> point.x
42
>>> point.y
21

>>> point.x = 0
>>> point.x
0
``````
Copied!

In this class, you define two attributes, `.x` and `.y`, to represent the point’s coordinates. You can access and update the attributes directly using the dot notation. So, from now on, both attributes are part of your class’s public API.

Now, say that you need to add some validation logic on top of `.x` and `.y`. For example, you may need to validate the input values for both attributes. How would you do that? In programming languages like Java or C++, you’d use the getter and setter methods, which translated into Python may look something like this:

Python `point_v1.py`
``````class Point:
def __init__(self, x, y):
self.set_x(x)
self.set_y(y)

def get_x(self):
return self._x

def set_x(self, x):
self._x = self.validate(x)

def get_y(self):
return self._y

def set_y(self, y):
self._y = self.validate(y)

def validate(self, value):
if not isinstance(value, int | float):
raise ValueError("coordinates must be numbers")
return value
``````
Copied!

In this new implementation of `Point`, you’ve turned `.x` and `.y` into non-public attributes by prepending underscores to their names, which now are `._x` and `._y`. Then, you define getter and setter methods for both attributes. In the setter methods, `.set_x()` and `.set_y()`, you insert the validation logic defined in the `.validate()` method.

Now, you have to use the class as in the following code:

Python
``````>>> from point_v1 import Point

>>> point = Point(42, 21)

>>> point.get_x()
42
>>> point.get_y()
21

>>> point.set_x(0)
>>> point.get_x()
0
>>> point.set_y("7")
Traceback (most recent call last):
...
ValueError: coordinates must be numbers
``````
Copied!

Your class works differently after the update. Instead of accessing the `.x` and `.y` attributes directly, you have to use the getter and setter methods. The validation logic works, which is great, but you’ve broken your class’s API. Your users won’t be able to do something like the following:

Python
``````>>> point.x
Traceback (most recent call last):
...
AttributeError: 'Point' object has no attribute 'x'
``````
Copied!

Old users of your class will be surprised that their code is broken after updating to your new version of `Point`. So, how can you avoid this kind of issue? The Pythonic approach is to convert public attributes into properties instead of using getter and setter methods.

You can use the built-in `property()` function to do this conversion. Here’s how you can keep your `Point` class’s API unchanged:

Python `point_v2.py`
``````class Point:
def __init__(self, x, y):
self.x = x
self.y = y

@property
def x(self):
return self._x

@x.setter
def x(self, value):
self._x = self.validate(value)

@property
def y(self):
return self._y

@y.setter
def y(self, value):
self._y = self.validate(value)

def validate(self, value):
if not isinstance(value, int | float):
raise ValueError("coordinates must be numbers")
return value
``````
Copied!

`Point` now looks a bit different. It doesn’t have formal getter and setter methods. Instead, it has some methods that are decorated with `@property`. Yes, the built-in `property()` function is mostly used as a decorator.

The methods that you decorate with `@property` are equivalent to getter methods. Meanwhile, the methods that you decorate with the getter’s name plus `.setter()` are equivalent to setter methods. The cool thing about properties is that you can still use the attributes as regular attributes:

Python
``````>>> from point_v2 import Point

>>> point = Point(42, 21)

>>> point.x
42
>>> point.y
21

>>> point.x = 0
>>> point.x
0

>>> point.x = "7"
Traceback (most recent call last):
...
ValueError: coordinates must be numbers
``````
Copied!

By turning regular attributes into properties, you can add function-like behavior to them without losing the ability to use them as regular attributes. Properties save you from introducing breaking changes into your code’s public API, so you don’t break your users’ code.

### Creating Class and Static Methods: `classmethod()` and `staticmethod()`

Classes allow you to define reusable pieces of code that encapsulate data and behavior in a single entity. Usually, you store data in attributes, which are variables defined inside classes. When it comes to behaviors, you’ll use methods, which are functions defined in classes.

In Python, you have three different types of methods:

1. Instance methods, which take the current object as their first argument
2. Class methods, which take the current class as their first argument
3. Static methods, which take neither the current instance nor the current class as arguments

Instance methods need to take the current instance as an argument. By convention, this argument is called `self` in Python.

To create a class method, you need to decorate the method with the `@classmethod` decorator. Similarly, to create a static method, you need to decorate the method with the `@staticmethod` decorator. Both decorators are part of Python’s built-in functions.

A common use case for class methods is to provide multiple constructors for a class. To illustrate how to write a class method, say that you want a `Point` class that you can construct using either Cartesian or polar coordinates. In this situation, you can do something like the following:

Python `point.py`
``````import math

class Point:
def __init__(self, x, y):
self.x = x
self.y = y

@classmethod
def from_polar(cls, distance, angle):
return cls(
x=distance * math.cos(math.radians(angle)),
y=distance * math.sin(math.radians(angle)),
)
``````
Copied!

In this example, the `.from_polar()` method is a class method. It takes the current class as its first argument, which you typically call `cls` by convention. The method returns a new instance of the class by computing the Cartesian coordinates from the polar coordinates.

Here’s how you can use this method in practice:

Python
``````>>> from point import Point

>>> point = Point.from_polar(20, 15)

>>> point.y
5.176380902050415
>>> point.x
19.318516525781366
``````
Copied!

In this code snippet, you create a new `Point` instance using the `.from_polar()` class method. In the example, you call the method on the class rather than on an instance to signal that this is a class method. You can call a class method on an instance of its containing class too.

The third type of method is the static method. A static method doesn’t take the current instance or class as arguments. These methods are like regular functions that you decide to include in a given class for convenience. Functionally, they could also be defined as regular functions in a module.

For example, consider the following `Formatter` class:

Python `formatting.py`
``````class Formatter:
@staticmethod
def as_currency(value):
return f"\${value:,.2f}"

@staticmethod
def as_percent(value):
return f"{value:.2%}"
``````
Copied!

This class defines two static methods. The first method takes a numeric value and formats it as a currency value. The second method takes a numeric value and expresses it as a percent. You could have defined these methods as regular functions at the module level. However, you’ve defined them in a class as a way to conveniently group them according to how they’ll be used.

You can use this class as in the following examples:

Python
``````>>> from formatting import Formatter

>>> Formatter.as_currency(1000)
'\$1,000.00'

>>> Formatter.as_percent(0.75)
'75.00%'

>>> formatter = Formatter()
>>> formatter.as_currency(1000)
'\$1,000.00'

>>> formatter.as_percent(0.8)
'80.00%'
``````
Copied!

You can use static methods by calling them on the class or one of its instances. In this example, the `Formatter` class works as a namespace where you define related methods for convenience. However, you can get the same result by defining the methods as module-level functions.

### Managing Attributes: `getattr()`, `setattr()`, and `delattr()`

Sometimes, you may need to access, set, or delete attributes from your objects in Python. In most cases, you can do these operations directly using the dot notation, the assignment operator, and the `del` statement.

In other situations, you only know the attributes’ names at runtime, so you can’t access them with the regular syntax. In these cases, you can use the built-in `getattr()`, `setattr()`, and `delattr()` functions. These functions have the following signatures:

Python
``````getattr(object, name)
getattr(object, name, default)

setattr(object, name, value)

delattr(object, name)
``````
Copied!

In all cases, the `object` argument must take an instance of an existing class. Similarly, `name` must be the name of an attribute or method as a string.

In the second signature of `getattr()`, the `default` argument is an optional value that you’ll get if the desired attribute doesn’t exist in the target object.

In the signature of `setattr()`, the `value` argument must hold the new value that you want to assign to a given argument.

To illustrate how these functions work, consider the following class:

Python `person.py`
``````class Person:
def __init__(self, name, age):
self.name = name
self.age = age
``````
Copied!

This class has two instance attributes, `.name` and `.age`. Here’s how you can access, set, or delete the attributes using their names as strings:

Python
``````>>> from person import Person

>>> jane = Person("Jane", 25)

>>> getattr(jane, "name")
'Jane'
>>> getattr(jane, "age")
25
``````
Copied!

In these examples, you use `getattr()` to retrieve the values stored in `.name` and `.age`. The first argument to this function is the object you need to retrieve an attribute from. The second argument is the attribute’s name as a string.

Now say that you want to update Jane’s age. You can do this using the `setattr()` function:

Python
``````>>> setattr(jane, "age", 26)
>>> jane.age
26
``````
Copied!

Then, you use the `setattr()` function to assign a new value to the `.age` attribute. This function takes three arguments: the object, the attribute’s name, and the new value.

Finally, you can use the built-in `delattr()` function to delete an attribute from a given object:

Python
``````>>> delattr(jane, "age")
>>> jane.age
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'age'
``````
Copied!

The `delattr()` function takes the object as its first argument and the attribute’s name as its second argument. After calling the function, trying to access `.age` will raise an `AttributeError` exception.

In practice, the built-in `getattr()`, `setattr()`, and `delattr()` functions come in handy when you need to manipulate attributes using their names as strings. For example, say that you want to create a `FileProcessor` class to read and write CSV and JSON files. In this situation, you can have dedicated classes for processing each file type:

Python `processors.py`
``````import csv
import json

class CSVProcessor:
def __init__(self, filename):
self.filename = filename

def read(self):
with open(self.filename, encoding="utf-8", newline="") as file:
return list(csv.DictReader(file))

def write(self, data):
with open(
self.filename, mode="w", encoding="utf-8", newline=""
) as file:
writer = csv.DictWriter(file, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)

class JSONProcessor:
def __init__(self, filename):
self.filename = filename

def read(self):
with open(self.filename, encoding="utf-8") as file:
return json.load(file)

def write(self, data):
with open(self.filename, mode="w", encoding="utf-8") as file:
json.dump(data, file, indent=2)
``````
Copied!

In this `processors.py` file, you define two classes that can process CSV and JSON files, respectively. Both classes have the `.read()` and `.write()` methods. These classes look fine, but now you need to make them usable from your `FileProcessor` class.

To write the `FileProcessor` class, you can use a technique called delegation, which consists of evaluating an object’s attribute or method in the context of another object. Here’s how you can do this in Python:

Python `processors.py`
``````# ...

class FileProcessor:
def __init__(self, filename, processor):
self.filename = filename
self.processor = processor(filename)

def __getattr__(self, attr):
return getattr(self.processor, attr)
``````
Copied!

In this class, you define the `.__getattr__()` special method. This method supports attribute access operations in Python classes. In the method definition, you use the `getattr()` function to access attributes and methods from the provided processor object.

In practice, you use the combination of the `.__getattr__()` method and the `getattr()` function to implement delegation. The `FileProcessor` class delegates the file processing to the concrete processor class that you pass in during instantiation.

Here’s how you can use the `FileProcessor` class in your code:

Python
``````>>> from processors import FileProcessor

>>> file_proc = FileProcessor("products.csv", CSVProcessor)
>>> file_proc.read()
[
{'product': 'Laptop', 'price': '1200', 'sold_units': '30'},
{'product': 'Phone', 'price': '700', 'sold_units': '50'},
{'product': 'Tablet', 'price': '450', 'sold_units': '100'},
{'product': 'Desktop', 'price': '1000', 'sold_units': '20'},
{'product': 'Monitor', 'price': '300', 'sold_units': '50'}
]
``````
Copied!

In this code, you create a `FileProcessor` instance to process a CSV file with the `CSVProcessor`. Even though the instance doesn’t have a `.read()` method, you can call the method because of the delegation technique that relies on the `getattr()` function.

### Checking for Attributes: `hasattr()`

Another built-in function closely related to attributes and methods is the `hasattr()` function. This function allows you to check whether a given object has a certain attribute or method. The function has the following signature:

Python
``````hasattr(object, name)
``````
Copied!

In this signature, the `object` argument can take any Python object, while the `name` argument should hold the name of an attribute as a string. This function is a predicate that returns `True` if the object has an attribute with the provided name and `False` otherwise.

In practice, you can use this function to check whether an object has a given attribute or method before you try to use it. For example, say that you have the following classes:

Python `birds.py`
``````class Duck:
def fly(self):
print("The duck is flying")

def swim(self):
print("The duck is swimming")

class Pigeon:
def fly(self):
print("The pigeon is flying")
``````
Copied!

These classes represent two different birds. Both birds are capable of flying, but only the duck is capable of swimming. Now, say that you want to use them in a loop like the following:

Python
``````>>> from birds import Duck, Pigeon

>>> birds = [Duck(), Pigeon()]

>>> for bird in birds:
...     bird.fly()
...     bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
Traceback (most recent call last):
...
AttributeError: 'Pigeon' object has no attribute 'swim'
``````
Copied!

This loop works for the instance of `Duck`. However, it raises an `AttributeError` exception when you call `.swim()` on a `Pigeon` instance because the class doesn’t have this method. To avoid this error, you can use the `hasattr()` function to check whether the method exists before calling it:

Python
``````>>> for bird in birds:
...     bird.fly()
...     if hasattr(bird, "swim"):
...         bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
``````
Copied!

Your code doesn’t fail now because you’ve used the `hasattr()` function to ensure that the current bird has the `.swim()` method before calling it.

### Creating and Checking Types: `type()`, `isinstance()` and `issubclass()`

Python is a dynamically typed language, which means that Python checks types only as the code runs, and the type of a variable can change over its lifetime. Because of this language feature, you may need to explicitly check an object’s type before using it so that your code doesn’t fail.

To know the type of a given object, you can use the built-in `type()` function:

Python
``````>>> type(42)
<class 'int'>

>>> type(2.75)
<class 'float'>

>>> type("Hello")
<class 'str'>
``````
Copied!

When you call `type()` with any Python class as an argument, then you get the object’s type, which you can also call the object’s class. In this example, you call `type()` with an integer number as an argument and get the `int` class as a response. Then, you use `type()` with a floating-point number and get the `float` class, and so on.

If you want to check an object’s type with `type()`, then you can do something like the following:

Python
``````>>> type(42) == int
True
>>> type(42) == float
False
``````
Copied!

This way of using `type()` works. However, it’s not the recommended approach. You’ll learn more about type checking in a moment. For now, you’ll continue to learn the basics of `type()`. To kick things off, here are the function’s signatures:

Python
``````type(object)
type(name, bases, dict, **kwds)
``````
Copied!

You’ve already used the first signature. In this signature, the `object` argument represents any Python object.

The second signature is a bit more involved. You’ll use this signature to create new classes dynamically rather than to determine an object’s type. Here’s a summary of the arguments and their meaning:

Argument Description
`name` The class’s name
`base` A tuple containing the base classes
`dict` A dictionary of attributes and methods defined in the class body
`**kwds` Additional keyword arguments that are passed to the metaclass constructor

When you use `type()` with these arguments, you can build classes dynamically. In this way, `type()` is a dynamic form of the `class` statement. Consider the following toy example:

Python
``````>>> def greet(self):
...     print("Hello, World!")
...

>>> DemoClass = type("DemoClass", (), {"value": 42, "greet": greet})
>>> DemoClass.value
42
>>> instance = DemoClass()
>>> instance.value
42
>>> instance.greet()
Hello, World!

>>> dir(instance)
[
'__class__',
'__delattr__',
'__dict__',
...
'greet',
'value'
]
``````
Copied!

In this quick example, you use `type()` to create a demo class that automatically inherits from `object` because the `base` tuple is empty. The new class will have a method called `.greet()`, which you’ve defined beforehand. It also has a class attribute called `.value` that you set to `42`.

For attributes, you should provide the attribute’s name as a string and the attribute’s value. For methods, you should give the method’s name as a string and a method object, which is a method without the calling parentheses. Note that instance methods like `.greet()` must take the current object as an argument, which you typically call `self`.

For a more realistic example, say that you want to write a function that lets you build classes dynamically from different data schemas. In this situation, you can do something like the following:

Python `factory.py`
``````def create_class(name, custom_members):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)

def __repr__(self):
return f"{name}({self.__dict__})"

class_members = {
"__init__": __init__,
"__repr__": __repr__,
}
class_members.update(custom_members)

return type(name, (), class_members)
``````
Copied!

In this code, you create a function that takes two arguments. The first argument, `name`, should be a string providing a valid class name. The second argument, `custom_members`, should be a dictionary of attributes and methods.

Then, you define an inner function called `.__init__()`, which you’ll use as the class initializer. The `.__repr__()` function will allow you to provide a string representation for the objects of your class.

Next, you create a dictionary to include the functions as methods for your class and update the dictionary with the content of `class_members`, which should come from the user.

Finally, you use the `type()` function to generate the class with the provided name and the dictionary of members. Here are a couple of examples of how to use this function:

Python
``````>>> from factory import create_class

>>> User = create_class("User", {"name": "", "age": 0, "email": ""})
>>> Product = create_class(
...     "Product", {"name": "", "price": 0.0, "units": 0}
... )

>>> john = User(name="John", age=30, email="john@example.com")
>>> table = Product(name="Table", price=200.0, units=5)

>>> john.name
'John'
>>> john.age
30
>>> john.email
'john@example.com'

>>> table.name
'Table'
>>> table.price
200.0
>>> table.units
5
``````
Copied!

In this code snippet, you first create two classes using `create_class()`. The first class represents users, and the second represents products. Both have different sets of instance attributes.

Then, you create concrete instances of each class with proper values for the attributes. Finally, you access the attributes using the dot notation. That’s great! Your classes work as expected.

The `type()` function is a great tool for creating classes dynamically. Even though you can also use this function to check for an object’s type, the recommended tool for explicit type checking is the built-in `isinstance()` function because it takes subclasses into account.

The signature for `isinstance()` is like the following:

Python
``````isinstance(object, classinfo)
``````
Copied!

In this signature, `object` represents any Python object in which you’re interested. The `classinfo` argument is the class or classes that you want to check against. This argument can be a single class object, a tuple of class objects, or a union type.

Consider the following examples where you use `isinstance()` to check for numeric values:

Python
``````>>> isinstance(42, int)
True

>>> isinstance(42.0, (int, float))
True

>>> isinstance(42.0, int | float)
True
``````
Copied!

In the first example, you use `isinstance()` to check whether `42` is an instance of the `int` class. In the second example, you use `isinstance()` to check whether `42.0` is an instance of either `int` or `float`. In this example, you use a tuple of classes to provide the `classinfo` argument.

Finally, in the third example, you do the same check as in the second example. This time, you use the pipe character (`|`) to create a union type with `int` and `float`. Note that `isinstance()` is a predicate function that returns `True` if the input object is an instance of one of the provided classes.

The `isinstance()` function also considers subclasses. For example, the `bool` class is a subclass of `int` so if you compare an instance of `bool` with `int`, then you’ll get `True` as a result:

Python
``````>>> isinstance(False, int)
True

>>> type(False) == int
False
``````
Copied!

Because `bool` is a subclass of `int`, the `isinstance()` function returns `True` when you check a Boolean value against the `int` class. Note that if you try to do a similar check with `type()`, then you get `False` because `type()` doesn’t consider subclasses.

There’s another built-in function that can be useful for type checking. The function is called `issubclass()` and it checks whether a given class is a subclass of another class:

Python
``````>>> issubclass(int, object)
True

>>> issubclass(bool, int)
True

>>> issubclass(int, float)
False
``````
Copied!

In the first example, you check whether the `int` class is a subclass of `object`. In this case, you get `True` because all Python classes derive from `object`. Then, you check whether `bool` is a subclass of `int`, which is also true, as you already learned.

In the final example, you use `issubclass()` to check whether `int` is a subclass of `float`, which is `False`.

The signature of `issubclass()` is as below:

Python
``````issubclass(class, classinfo)
``````
Copied!

In this case, the `class` argument is the class that you want to check for, while the `classinfo` argument works the same as in `isinstance()`.

### Checking for Callable Objects: `callable()`

A callable in Python is any object that you can call using a pair of parentheses and a series of arguments if required. In Python, callable objects include functions, classes, methods, instances of classes with a `.__call__()` method, closures, and generator functions.

Sometimes, you may need to know whether an object is callable before calling it in your code. To do this, you can use the built-in `callable()` function, which takes an object as an argument and returns `True` if the object appears to be callable. Otherwise, it returns `False`.

Here are a few examples of using `callable()` with some built-in objects:

Python
``````>>> callable(abs)
True
>>> callable(int)
True
>>> callable(list)
True

>>> callable(True)
False
>>> callable(None)
False
``````
Copied!

In the first three examples, the arguments to `callable()` are all functions, so you get `True` as a result. In the final two examples, you use the `True` and `None` objects as arguments. These objects aren’t callable, so you get `False` as a result.

As a practical example, say that you need to build an app that processes commands. Every command should be callable, otherwise it won’t be valid. To check this condition, you can use `callable()`. Here’s a toy implementation:

Python `commands.py`
``````class CommandProcessor:
def __init__(self):
self.commands = {}

def register_command(self, command):
if not callable(command):
raise ValueError("command is not callable")
self.commands[command.__name__] = command

def execute_command(self, name, *args, **kwargs):
if (command := self.commands.get(name)) is None:
raise ValueError(f"command '{name}' not found")
return command(*args, **kwargs)
``````
Copied!

In this class, the `.register_command()` method uses `callable()` to check whether the input command is a callable object. If that’s the case, then you register the command as valid. Next, you have the `.execute_command()` method that runs the command as a callable.

Here’s an example of how to use this class:

Python
``````>>> from commands import CommandProcessor

>>> command_processor = CommandProcessor()

>>> def add(a, b):
...     return a + b
...

>>> command_processor.register_command(add)
>>> command_processor.execute_command("add", 1, 2)
3

>>> subtract = 3 - 2
>>> command_processor.register_command(subtract)
Traceback (most recent call last):
...
ValueError: command is not callable
``````
Copied!

In this example, you create a `CommandProcessor` instance to process commands. Then, you write `add()` to use it as a command. Because `add()` is a callable object, you can register it as a valid command and run it with the `.execute_command()` method.

Finally, you define the `subtract` variable to hold the result of a subtraction operation. This variable isn’t callable. Therefore, you get a `ValueError` exception when registering it as a command.

### Accessing the Parent’s Members: `super()`

When working with inheritance in Python classes, you’ll often need to access a parent class’s attributes or methods in a subclass. The Pythonic way to do this is to use the built-in `super()` function.

A common use case for `super()` is when you need to create a subclass of an existing class, and you need a proper way to initialize the parent class’s attributes. Consider the following classes that represent a rectangle and a square:

Python `shapes.py`
``````class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width

def area(self):
return self.length * self.width

def perimeter(self):
return 2 * (self.length + self.width)

class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
``````
Copied!

In this code, you define a `Rectangle` class with two attributes, `.length` and `.width`. It also has two methods for computing the rectangle’s area and perimeter. Next, you define the `Square` class. Since a square is a type of rectangle with equal sides, it makes sense to create `Square` as a subclass of `Rectangle` and reuse the functionality that you’ve already implemented.

In the `Square()` constructor, you only need a single argument to represent the side length. You can use this argument to initialize the parent class using `super()` as you did in the highlighted line. The super() function gives you access to the parent class `Rectangle`. Once you have access to the class, you can call its `.__init__()` method to initialize the `.length` and `.width` attribute with the value of `side`.

### Building Generic Objects: `object()`

In Python, every single class implicitly inherits from the `object` class, which is built into the language. In other words, the `object` class is the base class for every class in Python:

Python
``````>>> issubclass(int, object)
True
>>> issubclass(float, object)
True
>>> issubclass(bool, object)
True
>>> issubclass(dict, object)
True

>>> class DemoClass:
...     pass
...
>>> issubclass(DemoClass, object)
True
``````
Copied!

No matter if the class you’re considering is a built-in or a custom class, it inherits from `object`.

In some situations, you may want to create instances of the `object` class. To do this, you can use the built-in `object()` function, which is actually a class constructor rather than a function, but the Python documentation lists it among its built-in functions.

The `object()` function doesn’t take an argument and returns a new featureless object, which has the methods that are common to all Python objects. Unlike regular objects, the object that you get from calling `object()` doesn’t have a `.__dict__` attribute, so you can’t add attributes dynamically to this type of object:

Python
``````>>> obj = object()
>>> dir(obj)
[
'__class__',
'__delattr__',
'__dir__',
...
'__str__',
'__subclasshook__'
]
>>> obj.attr = "Some value"
Traceback (most recent call last):
...
AttributeError: 'object' object has no attribute 'attr'
``````
Copied!

In this example, you create a new featureless object by calling `object()`. With the built-in `dir()` function, you can list all the methods and attributes that this object provides. Finally, if you try to add an attribute to your featureless object dynamically, then you get an `AttributeError` exception.

In practice, you can use the `object()` function when you want to create unique sentinel values. A sentinel value is a unique marker that you can use to signify the absence of a value. You can also use it as a condition for stopping iterative or recursive algorithms.

To illustrate how to use `object()` to create a sentinel value, consider the following `Circle` class:

Python `circle.py`
``````from time import sleep

SENTINEL = object()

class Circle:
def __init__(self, radius):
self.radius = radius
self._diameter = SENTINEL

@property
def diameter(self):
if self._diameter is SENTINEL:
sleep(0.5)  # Simulate a costly computation
self._diameter = self.radius * 2
return self._diameter
``````
Copied!

In this class, you have a `.radius` attribute that takes a value at instantiation time. Then, you have a non-public attribute called `._diameter` that you initialize with the `SENTINEL` constant. To create this constant, you use the `object()` function.

Finally, you have the `.diameter` property, which computes the diameter from the provided radius. In this example, you use `sleep()` from the `time` module to simulate that finding the diameter is a costly operation. Because of the computation cost, you decide to cache the diameter so that it’s computed a single time during the object’s life.

To check whether the diameter was already computed, you compare its current value with the `SENTINEL` constant. In this example, you could’ve also used `None` as the sentinel value because a circle’s diameter is unlikely to take a null value. However, when `None` can be a valid value for the attribute at hand, then `object()` can be the way to go.

## Working With Python Scopes

Python, like many other programming languages, manages the concept of scopes. The scope rules how variables and names are looked up in your code. It determines the visibility of a variable or name within the code.

The scope depends on the place where you create that variable. Python’s scopes follow a rule known as the LEGB rule. The letters in this acronym stand for local, enclosing, global, and built-in scopes, and this rule summarizes the four scopes that you find in Python.

You’ll find two built-in functions that are closely related to scopes in Python. These functions are listed in the table below:

Function Description
`locals()` Updates and returns a dictionary representing the current local symbol table
`globals()` Returns a dictionary representing the current global symbol table

In the following sections, you’ll learn the basics of these functions and how to use them in your Python code to manage some aspects of your name scopes.

### Inspecting and Updating a Local Scope: `locals()`

The local scope is the function scope because it comprises the function’s body. Every time you call a function, Python creates a new local scope for that function. By default, arguments and names that you assign in a function’s body exist only within the local scope that Python creates when you call the function. When the function returns, the local scope vanishes, and the names are forgotten.

If you ever need to inspect the state of your current local scope, then you can use the built-in `locals()` function:

Python
``````>>> def add(a, b):
...     result = a + b
...     print(locals())
...     return result
...

>>> add(2, 5)
{'a': 2, 'b': 5, 'result': 7}
7
``````
Copied!

In this function, you take two arguments, `a` and `b`. These arguments are local to `add()`, which means you can only access and use them inside the function. Then, you create a local variable called `result`, which you use as a temporary variable to store the computation’s results. The call to `locals()` returns a dictionary containing the names and values of all these variables.

Note that `locals()` grabs the scope information only to the point at which you call it:

Python
``````>>> def add(a, b):
...     print(locals())
...     result = a + b
...     return result
...

>>> add(2, 5)
{'a': 2, 'b': 5}
7
``````
Copied!

In this variation of `add()`, you call `locals()` at the beginning of the function. So, you only get the arguments in the result dictionary. This is because when you call `locals()`, the `result` variable hasn’t yet been defined.

### Inspecting and Updating the Global Scope: `globals()`

The global scope is another important scope in Python. It’s the module-level scope and allows you to define global variables or names. You can access and modify global names from anywhere in your code.

To inspect and update the variables and names that live in your current global scope, you can use the built-in `globals()` function. For example, when you start a fresh REPL session and call `globals()`, then you get an output like the one below:

Python
``````>>> globals()
{
'__name__': '__main__',
'__doc__': None,
...
'__builtins__': <module 'builtins' (built-in)>
}
``````
Copied!

By default, when you start a REPL session, the interpreter loads several names and objects to your global scope. For example, the `__name__` object holds the current module’s name, which is `"__main__"` when you’re in an executable module. If you’re in an imported module, then this variable will hold the module’s name.

Then, you have the `__doc__` name, which will hold the module’s docstring if provided. You’ll also have several other names. Finally, you have the `__builtins__` name, which holds the namespace where built-in names are defined. This is a special module that includes all the built-in functions covered in this tutorial and several other built-in objects, like exceptions.

If you start defining variables and functions in your REPL session, then these names will be added to the dictionary that `globals()` returns:

Python
``````>>> language = "Python"
>>> number = 42
>>> def greet():
...     print("Hello, World!")
...
>>> class Demo:
...     pass
...

>>> globals()
{
...
'__builtins__': <module 'builtins' (built-in)>,
'language': 'Python',
'number': 42,
'greet': <function greet at 0x100984040>,
'Demo': <class '__main__.Demo'>
}
``````
Copied!

In this example, you define two variables, a function, and a class. When you call `globals()`, you get the names of all those objects at the end of the resulting dictionary.

The dictionary that `globals()` returns is a writable dictionary. You can take advantage of this feature when you need to modify or update the global scope’s content manually. A common use case for this feature is when you need to load configuration parameters from a file.

For example, say that you have the following JSON file with some configuration values for your database connection:

JSON `config.json`
``````{
"DATABASE_URL": "postgres://user:pass@localhost/dbname",
"DEBUG_MODE": true,
"MAX_CONNECTIONS": 10
}
``````
Copied!

You need to write a function that loads this file and adds the provided configuration parameters to your current global scope. Here’s a possible implementation of this function:

Python `config.py`
``````import json

def load_config(config_file):
with open(config_file) as file:
config = json.load(file)

globals().update(config)
``````
Copied!

In this function, you first open the configuration file and load its content to a dictionary called `config`. Then, you update the dictionary that `globals()` returns with the content of `config`.

Here’s how the above function works:

Python
``````>>> from config import load_config

>>> load_config("config.json")
>>> globals()
{
...
'DATABASE_URL': 'postgres://user:pass@localhost/dbname',
'DEBUG_MODE': True,
'MAX_CONNECTIONS': 10
}

>>> MAX_CONNECTIONS
10
``````
Copied!

After you call `load_config()` with the `config.json` file as an argument, you get the configuration parameters loaded as constants into your global scope. Now, you can use these constants directly in your code.

## Introspecting Objects

In programming, type introspection is the ability of a program to inspect an object’s type and properties at runtime. Everything in Python is an object, so being able to examine types and properties at runtime is a valuable asset.

Here are a few built-in functions that allow you to perform some kind of type introspection in Python:

Function Description
`id()` Returns the identity of an object
`dir()` Returns a list of names in the current local scope or a list of object attributes
`vars()` Returns the `__dict__` attribute for a module, class, or object

In the following sections, you’ll learn how these functions work and how you can use them in code to perform type introspection. To kick things off, you’ll start with the `id()` function.

### Knowing an Object’s Identity: `id()`

In Python, every individual object has an associated identity. This identity is a unique and constant integer that identifies the object during its lifetime. Two objects with non-overlapping lifetimes may have the same identity.

If you ever need to know the identity of a given object, then you can use the `id()` function with the object as an argument:

Python
``````>>> id(42)
4315605776

>>> id("Python")
4315120464

>>> def greet():
...     print("Hello, World!")
...
>>> id(greet)
4307259040

>>> class Demo:
...     pass
...
>>> id(Demo)
4892672720
``````
Copied!

When you call `id()` with any Python object as an argument, you get a number that is the object’s identity. In the CPython implementation of Python, an object’s identity is also the memory address where that object lives.

Knowing an object’s identity may be of great help when you’re debugging your code. For example, say that you want to write code that computes individual values from a kind of Fibonacci sequence. You can do this in many ways. However, you think of using a class with callable instances and an instance attribute that allows you to cache already computed values.

Here’s a possible implementation of this class:

Python `fibonacci.py`
``````class Fibonaccish:
def __init__(self, initial_value=1):
self._cache = [0, initial_value]

def __call__(self, index):
if index < len(self._cache):
fib_number = self._cache[index]
print(f"{index} {fib_number} id = {id(fib_number)}")
else:
fib_number = self(index - 1) + self(index - 2)
self._cache.append(fib_number)
return fib_number
``````
Copied!

In the initializer method of `Fibonaccish`, you define a list to hold the cache of computed values. Then, you define the `.__call__()` special method, which allows the instances of your class to be callable like functions.

In this method, you determine whether the Fibonacci value for the target index is already computed and stored in the cache. Then, you add a call to `print()` that will help you debug your code using `id()` to ensure the cached values are used.

Here’s how this class works in practice:

Python
``````>>> from fibonacci import Fibonaccish

>>> fibonacci_333 = Fibonacci(333)

>>> fibonacci_333(2)
0 0 id = 94800819952840
1 333 id = 140276932935312
333
>>> fibonacci_333(4)
2 333 id = 140276932934960
1 333 id = 140276932935312
2 333 id = 140276932934960
999
``````
Copied!

In this code example, you create an instance of `Fibonaccish` with an initial value of 333. The first values of this sequence will be 0, 333, 333, 666, 999, and 1665.

The instance you create is callable, so you can use it as a regular function. Then, you call the instance with `2` as an argument. The call prints the identity of the `0` and `333` values at index `0` and `1`, respectively. Next, you call `fibonacci_333()` with `4` as an argument. In this case, you get the identity of `333` three times, both at index 1 and 2.

When you look at the identities, you realize that your function uses the same object for the same indices, while it’s different for the two different instances of `333`. This way, you can confirm that the function uses the cache as expected.

If you repeat the example with the usual Fibonacci sequence, `Fibonacci(1)`, you’ll see slightly different results. In this case, Python will intern `1` under the hood, so that the same object is used at both index 1 and 2 in your cache.

### Checking Names and Attributes: `dir()` and `vars()`

Sometimes, you need to know the attributes or methods defined in a given object or scope. For this kind of requirement, Python has two different built-in functions, `dir()` and `vars()`.

The `dir()` function with no arguments returns the list of names in the current scope. So, the result will depend on the place in which you call the function. With any Python object as an argument, `dir()` attempts to return the list of attributes for that object.

For example, if you call `dir()` with no arguments in a fresh REPL session, then you’ll get an output like the following:

Python
``````>>> dir()
[
'__annotations__',
'__builtins__',
'__doc__',
'__loader__',
'__name__',
'__package__',
'__spec__'
]
``````
Copied!

As you can see, `dir()` returns the list of names that are defined in your current scope, which is the global scope in this example.

If you call `dir()` with a Python object as an argument, then you’ll get a list of the object’s attributes and methods. If the input object is a class, then you’ll get a list of methods and class attributes. If the object is an instance of an existing class, then you’ll get a list of methods, class attributes, and instance attributes.

Consider the following example that reuses your `Rectangle` class from the section on the `super()` function:

Python
``````>>> class Rectangle:
...     def __init__(self, length, width):
...         self.length = length
...         self.width = width
...     def area(self):
...         return self.length * self.width
...     def perimeter(self):
...         return 2 * (self.length + self.width)
...

>>> dir(Rectangle)
[
'__class__',
'__delattr__',
'__dict__',
...
'area',
'perimeter'
]

>>> rectangle = Rectangle(2, 4)
>>> dir(rectangle)
[
'__class__',
'__delattr__',
'__dict__',
...
'area',
'length',
'perimeter',
'width'
]
``````
Copied!

In the first call to `dir()`, you get the class attributes and methods of the `Rectangle` class. In this case, you use the class object as an argument. In the second call to `dir()`, you use an instance of `Rectangle` as an argument and get all the methods, class attributes, and instance attributes.

The default `dir()` behavior is different for different types of objects. Here’s a summary of these differences:

Object Behavior
A module object Returns the list of names defined in the module.
A type or class object Returns the list of names of class attributes and methods and of the base classes.
Other objects Returns the list of attributes and methods, including the class attributes and the attributes of the base classes.

You can also customize the default behavior of `dir()` by providing the `.__dir__()` special method in your custom classes. However, this topic is beyond the scope of this tutorial.

The `vars()` function returns the `.__dict__` attribute for a module, class, instance, or any other object with a `.__dict__` attribute:

Python
``````>>> vars(Rectangle)
mappingproxy(
{
'__module__': '__main__',
'__init__': <function Rectangle.__init__ at 0x10352d080>,
'area': <function Rectangle.area at 0x10352d120>,
'perimeter': <function Rectangle.perimeter at 0x10352d1c0>,
'__dict__': <attribute '__dict__' of 'Rectangle' objects>,
'__weakref__': <attribute '__weakref__' of 'Rectangle' objects>,
'__doc__': None
})

>>> vars(rectangle)
{'length': 2, 'width': 4}
``````
Copied!

In this example, you call `vars()` with the `Rectangle` class as an argument. You get the `.__dict__` attribute of the class, which contains methods and class attributes. Note that it also contains a `.__dict__` attribute that holds the attributes of the class’s instances. That `.__dict__` is what you get when you call `vars()` with an instance of the class.

The `.__dict__` attribute is a dictionary that works as a namespace that maps names to objects. For example, it can map a method name to a method object or an attribute name to a specific value or object.

## Running Python Code From Strings

In rare situations, it may be useful to evaluate expressions or run code that comes as a string object. This practice isn’t common in real-world code because it may not be secure, especially when the target code comes from an untrusted source, such as the user’s input.

Regardless of the security issues involved, Python has three built-in functions that allow you to evaluate expressions or run code that comes as a string. Here’s a summary of these functions:

Function Description
`eval()` Evaluates arbitrary Python expressions from a string or compiled code input
`exec()` Executes arbitrary Python code from a string or compiled code input
`compile()` Generates a compiled code object from a string

In the following sections, you’ll learn the basics of these functions. To kick things off, you’ll start by using the `eval()` function to evaluate Python expressions.

### Executing Expressions From Strings: `eval()`

In Python, an expression is a combination of objects and operators that returns a value. You’ll find several types of expressions, including math, Boolean, comparison, bitwise expressions, and more. When working with expressions, you can run them as regular Python code. However, what if you need to evaluate expressions defined as strings?

For example, think of how you would evaluate the following:

Python
``````"sum([2, 3, 4, 5]) / 4 + 100"
``````
Copied!

If you want to evaluate this string as an expression, then you’ll have to parse the string and figure out how to extract the operands and operators. Then, you can reconstruct the expression and run it in the Python interpreter. This process may sound like a quick thing to do. However, it can be overwhelming in practice, especially if you consider the infinite number of different expressions that you may need to evaluate in real code.

Fortunately, Python has a built-in `eval()` function that helps you evaluate expressions that come as strings.

If you have a string that holds a valid Python expression, then you can call `eval()` with that string as an argument. The function will parse the string, compile it into bytecode, and finally evaluate it as a normal expression:

Python
``````>>> eval("sum([2, 3, 4, 5]) / 4 + 100")
103.5
``````
Copied!

Wow! That was quick and smooth! You just passed your string to `eval()`, ran the code, and got the expression’s result.

The signature of `eval()` looks something like the following:

Python
``````eval(expression[, globals[, locals]])
``````
Copied!

The first argument, `expression`, holds the expression that you need to evaluate. The rest of the arguments are optional. That’s why they’re enclosed in square brackets. Here’s a summary of these arguments and their meaning:

Argument Description
`expression` A string holding a valid Python expression
`globals` A dictionary holding a global namespace to use in the call to `eval()`
`locals` A dictionary holding a local namespace to use in the call to `eval()`

You’ve already seen an example of using the `expression` argument, so now you can focus on the other two arguments. In each example, you’ll need to provide an expression.

Here’s an example of using the `globals` argument:

Python
``````>>> numbers = [2, 3, 4, 5]
>>> n = len(numbers)

>>> eval("sum(numbers) / n + 100")
103.5

>>> eval("sum(numbers) / n + 100", {})
Traceback (most recent call last):
...
NameError: name 'numbers' is not defined

>>> eval("sum(numbers) / n + 100", {"numbers": numbers, "n": n})
103.5
``````
Copied!

By default, `eval()` has access to the global scope, so you can use all the names defined in this scope in the expression you pass to the function. If you set `globals` to an empty dictionary, then you restrict access to the global scope, and the function fails.

Finally, you can use an explicit dictionary, as you did in the final example, to provide the global variables that you want to use when evaluating the target expression.

The `locals` argument works similarly. It takes a dictionary of local names:

Python
``````>>> def evaluator(expression):
...     numbers = [2, 3, 4, 5]
...     n = len(numbers)
...     return eval(expression, {}, {"numbers": numbers, "n": n})
...

>>> evaluator("sum(numbers) / n + 100")
103.5
``````
Copied!

Inside the `evaluator()` function, you define `numbers` and `n` as local variables. In the call to `eval()`, you use an empty dictionary for `globals`, and a dictionary containing the local variables for `locals`.

Even though the `eval()` function may seem like an amazing tool, you must be careful when using it in your code. In practice, you’ll be safer if you don’t use this tool in real-world code. Why?

The `eval()` function has security implications that can be hard to circumvent. For example, if you use the function to evaluate expressions provided by external users, then you expose your system to the execution of arbitrary Python code.

For more insights on how to reduce the security risks associated with `eval()`, check out the Minimizing the Security Issues of `eval()` in the Python `eval()`: Evaluate Expressions Dynamically tutorial.

### Running Code From Strings: `exec()` and `compile()`

The `eval()` function is a powerful tool in Python. However, it’s designed to evaluate expressions. Sometimes, you may want to run more complex pieces of code that come as strings. For example, you may want to run loops, conditional statements, compound statements, and even entire scripts. In this scenario, you can use the built-in `exec()` and `compile()` functions.

Here’s the signature of the `exec()` function:

Python
``````exec(code [, globals [, locals]])
``````
Copied!

The `code` argument can be a string containing valid Python code. It can also be a compiled code object, which you can create with the `compile()` function. You’ll learn about `compile()` in a moment. For now, you’ll use a string to provide the `code` argument.

If `code` comes as a string, then `exec()` parses it as a sequence of Python statements. Then, it compiles the code into bytecode, and finally, it executes the code unless a syntax error occurs during the parsing or compilation step.

Consider the following example:

Python `calculations.py`
``````>>> functions = [
...     "def add(a, b): return a + b",
...     "def subtract(a, b): return a - b",
...     "def multiply(a, b): return a * b",
...     "def divide(a, b): return a / b",
... ]

>>> for function in functions:
...     exec(function)
...
``````
Copied!

In this example, you define a list of strings. Each string holds Python functions for a basic arithmetic operation. Then, you start a loop over the list. With `exec()`, you execute the strings that define the functions. This step brings every function to your current global scope. Now, you can use them as you would a regular function:

Python
``````>>> add(1, 2)
3
>>> subtract(3, 2)
1
>>> multiply(2, 3)
6
>>> divide(6, 3)
2.0
``````
Copied!

The arithmetic functions are now available in your global scope, so you can use them to run your calculations.

Like `eval()`, `exec()` takes the `globals` and `locals` arguments, which again are optional. These arguments have similar meanings in both functions, so you can give them a try as an exercise.

When you have a string containing code that you’ll reuse several times, you can use the `compile()` function to compile the code once and use it everywhere. This practice will make your code more efficient and fast because the compilation step runs only once.

The signature of `compile()` looks something like the following:

Python
``````compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
``````
Copied!

This signature is a bit involved because it has several arguments that you need to understand. Here’s a summary of the arguments and their meaning:

Argument Description
`source` Holds the code that you need to compile into bytecode
`filename` Hold the file from which the code was read
`mode` Specifies what kind of code must be compiled
`flags` and `dont_inherit` Controls which compiler options should be activated and which future features should be allowed
`optimize` Specifies the compilation’s optimization level

To read from a string object, you’ll have to set `filename` to the `"<string>"` value. The `mode` argument can take one of the following values:

• `"eval"` when `source` consists of a single expression
• `"exec"` when `source` is a sequence of statements
• `"single"` when `source` is a single interactive statement

Depending on the source code and the function you plan to use to run it, you’ll select the first or the second value. The `single` argument comes in handy when you want to run a statement like `print("Hello, World!")` that you’d typically run in an interactive session.

To illustrate how to use `compile()`, consider the following toy example:

Python
``````>>> code = """
... result = sum(number for number in iterable if not number % 2)
... """

>>> compiled_code = compile(code, "<string>", "exec")

>>> context = {"iterable": [1, 2, 3, 4]}
>>> exec(compiled_code, context)
>>> context["result"]
6

>>> context = {"iterable": [10, 40, 50, 20]}
>>> exec(compiled_code, context)
>>> context["result"]
120
``````
Copied!

In this example, you have a piece of code in a string. The code consists of a call to `sum()` that wraps a generator expression that takes an iterable of numbers and returns the even numbers. Next, you use the `compile()` function to compile the string into a code object ready for execution. The `context` dictionary holds an iterable of numbers.

You call `exec()` with the compiled code and the context dictionary as arguments. Note that you use `context` to provide the `globals` argument. The call to `exec()` will update this dictionary with any name that you define in the compiled code. In this specific example, `context` ends up holding the `result` variable with the sum of the even numbers in the iterable.

To access the computed value, you use the `context` dictionary with the `"result"` key. In the final example, you reuse the compiled code to perform a similar computation with a different list of values.

## Using Miscellaneous Functions

Python has a few other built-in functions that cover miscellaneous topics. Here’s a summary of these functions:

Function Description
`help()` Invokes the built-in help system
`hash()` Calculates the hash value of an object
`__import__()` Invoked by the `import` statement
`memoryview()` Returns a memory view object

In the following sections, you’ll learn the basics of these functions and how to use them in your Python code or in an interactive session of the language. To kick things off, you’ll start with the built-in `help()` function.

### Accessing the Built-in Help System: `help()`

The built-in `help()` function comes in handy when you’re working in a Python REPL session. This function gives you access to the built-in interactive help system. Go ahead and open an interactive Python session in your terminal. Then, call `help()` with no arguments. You’ll be presented with the help system:

Python
``````>>> help()

Welcome to Python 3.x's help utility!

If this is your first time using Python, you should definitely check out
the tutorial on the internet at https://docs.python.org/3.x/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics".  Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".

help>
``````
Copied!

The output gives you a warm welcome to the Python help utility. Then, it suggests that you take the official Python tutorial if you’re new to the language. In the third and fourth paragraphs, you’re given instructions for using the help system.

At the end of the page, you have the `help>` prompt waiting for your input. Go ahead and type in the `str` name and then press Enter. You’ll see something like the following:

Text
``````Help on class str in module builtins:

class str(object)
|  str(object='') -> str
|  str(bytes_or_buffer[, encoding[, errors]]) -> str
|
|  Create a new string object from the given object. If encoding or
|  errors is specified, then the object must expose a data buffer
|  that will be decoded using the given encoding and error handler.
|  Otherwise, returns the result of object.__str__() (if defined)
|  or repr(object).
|  encoding defaults to sys.getdefaultencoding().
|  errors defaults to 'strict'.
|
|  Methods defined here:
|
|  __add__(self, value, /)
|      Return self+value.
|
|  __contains__(self, key, /)
|      Return key in self.
...
``````
Copied!

This is the help page for the `str` class. On this page, you’ll find detailed information about the class and its goals. To leave the page and get back to the help system, go ahead and press Q.

While in the help system, you can consult the help for many Python objects, including built-in modules, functions, keywords, and much more. Go ahead and give it a try. You may find some useful information!

There’s a second way you can use the `help()` function. First, type Q at `help>` prompt and then press Enter to get back to your interactive session. Once in there, you can call `help()` with any Python object as an argument. For example, if you call the function with the `str` class as an argument, then it’ll take you to same page you saw before:

Python
``````>>> help(str)
``````
Copied!

This way of calling `help()` gives you quick access to the help page of a given object. It’s important to note that you can either use the object directly as an argument to `help()` or the name of the object as a string like in `help("str")`. However, in most cases, using the name of the object as a string is safer:

Python
``````>>> help(sys)
Traceback (most recent call last):
...
NameError: name 'sys' is not defined
``````
Copied!

In this example, you try to access the help page for the `sys` module. You use the module’s name as an argument and get a `NameError` exception because the module isn’t present in your current scope. The call to `help()` will work safely if you use the module’s name as a string, as in `"sys"`. Go ahead and give it a try!

### Creating Hash Codes: `hash()`

If you work in fields like data integrity, security, or cryptography, then you may be familiar with hash codes. A hash code is a number that can act as a digital fingerprint for a given piece of data. It’s usually much smaller than the original data and lets you verify its integrity.

To create a hash code for a given object, you need a hash function. Python has its own built-in function for creating hash codes. The function is conveniently called `hash()`.

The signature of `hash()` is like the following:

Python
``````hash(object)
``````
Copied!

It takes an object as an argument and returns the hash value of the input object. The hash value has to be an integer number.

Here are a few examples of using the `hash()` function:

Python
``````>>> hash(42)
42
>>> hash(2.7)
1614090106449586178

>>> hash("Hello")
-6239611042439236057

>>> hash(int)
270201092

>>> class DemoClass: pass
...
>>> hash(DemoClass)
346520709

>>> demo_instance = DemoClass()
>>> hash(demo_instance)
271491289
``````
Copied!

In these examples, you’ve used the `hash()` function with different objects, including numeric values, strings, function objects, and custom classes. In all cases, you get a unique hash code.

In practice, there are objects that don’t have a hash value:

Python
``````>>> hash([1, 2, 3])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'

>>> hash({"one": 1, "two": 2})
Traceback (most recent call last):
...
TypeError: unhashable type: 'dict'

>>> hash({"red", "green", "bleu"})
Traceback (most recent call last):
...
TypeError: unhashable type: 'set'
``````
Copied!

You’ll note that mutable objects aren’t hashable in Python because you can change the value of a mutable object during its lifetime.

### Importing Objects From String Names: `__import__()`

Python’s built-in `__import__()` function is an advanced tool that isn’t common in everyday programming. The function is called internally by the `import` statement. The direct use of `__import__()` is discouraged in favor of `importlib.import_module()`. However, it’s a built-in function, so you’ll learn a bit about it in this section.

The signature of `__import__()` looks something like the following:

Python
``````__import__(name, globals=None, locals=None, fromlist=(), level=0)
``````
Copied!

With this function, you can import a module by its `name`. This `name` should be a string. Here’s a summary of the function’s arguments and their meaning:

Argument Description
`name` A module’s name as a string
`globals` A dictionary representing the global namespace
`locals` A dictionary representing the local namespace
`fromlist` A list of objects or submodules that should be imported from the module
`level` A positive value indicating the number of parent directories to search relative to the directory of the module calling `__import__()`

To do something equivalent to `import sys` with the `__import__()` function, you can do something like the following:

Python
``````>>> sys = __import__("sys")
>>> sys
<module 'sys' (built-in)>
``````
Copied!

In this example, you create a `sys` variable and assign it the result of calling `__import__()` with the string `"sys"` as an argument. This call to `__import__()` imports the `sys` module into your current global scope.

### Manipulating Binary Data Efficiently: `memoryview()`

The built-in `memoryview()` function allows you to access the internal data of an object that supports the buffer protocol, such as `array.array`, `bytes`, and `bytearray` objects. You can use this function for manipulating large datasets or interfacing with binary data.

For example, if you have a large dataset and want to use a chunk of it, then creating a copy would be inefficient. Instead, you can make a `memoryview` object to access the data without copying it. This allows you to use less memory and increases the execution speed.

For example, say that you have the pixel data of an image represented as a `bytearray`, and you want to invert the pixel values. To do this operation efficiently, you can use the `memoryview()` function:

Python
``````>>> image = bytearray([0, 127, 255, 64, 128, 192, 32, 96, 160])
>>> mv = memoryview(image)

>>> for i in range(len(mv)):
...     mv[i] = 255 - mv[i]
...

>>> list(mv)
[255, 128, 0, 191, 127, 63, 223, 159, 95]
>>> list(image)
[255, 128, 0, 191, 127, 63, 223, 159, 95]
``````
Copied!

In this example, you create a `memoryview` object to access the data that represents your image. In the `for` loop, you iterate over the data and invert the pixel values. The transformation reflects on the original data.

In short, the `memoryview()` function is a powerful tool for working with objects that support the buffer protocol without copying the data, which makes your code memory-efficient and faster.

## Conclusion

You’ve learned the basics of Python’s built-in functions. These are functions that you can use directly without importing anything because they’re available in the built-in scope or namespace.

Built-in functions solve a wide range of common programming problems such as performing math operations, working with common data types, processing iterables of data, handling input and output, working with scopes, and more.

In this tutorial, you’ve learned:

• The basics of Python’s built-in functions
• About common use cases of Python’s built-in functions
• How to use these functions to solve practical problems in Python

With this knowledge, you’ve got foundational Python skills that will help you write robust Pythonic code. More importantly, you’re now aware of all these incredible functions and can use them in your code to tackle common tasks efficiently without reinventing the wheel.

Take the Quiz: Test your knowledge with our interactive “Python's Built-in Functions: A Complete Exploration” quiz. You’ll receive a score upon completion to help you track your learning progress:

Interactive Quiz

Python's Built-in Functions: A Complete Exploration

Take this quiz to test your knowledge about the available built-in functions in Python. By taking this quiz, you'll deepen your understanding of how to use these functions and the common programming problems they cover, from mathematical computations to Python-specific features.

Copied!
Happy Pythoning!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

About Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.

Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics: basics python