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.
Get Your Code: Click here to download the free sample code that shows you how to use Python’s built-in functions.
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 ExplorationTake 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.
Note: Many of Python’s built-in functions are classes with function-style names. Good examples are str
, tuple
, list
, and dict
, which are classes that define built-in data types. These classes are listed in the Python documentation as built-in functions and you’ll find them in this tutorial.
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.
Using Math-Related Built-in Functions
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.
Note: To learn more about abs()
, check out the How to Find an Absolute Value in Python tutorial.
Python’s built-in abs()
function allows you to quickly compute the absolute value or modulus of a number:
>>> 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')
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:
>>> 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
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:
>>> divmod(8, 4)
(2, 0)
>>> divmod(6.5, 3.5)
(1.0, 3.0)
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:
>>> 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'
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.
Note: To learn more about the min()
and max()
functions, check out the Python’s min()
and max()
: Find Smallest and Largest Values tutorial.
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:
min(iterable, *[, default, key])
max(iterable, *[, default, key])
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:
>>> 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
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:
pow(base, exp[, mod=None])
When you call pow()
, you get base
to the power of exp
. With these two arguments, pow()
is equivalent to something like base**exp
:
>>> pow(2, 8)
256
>>> 2**8
256
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:
>>> 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
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.
Note: To learn more about rounding numbers and the round()
function, check out the How to Round Numbers in Python tutorial.
The signature of round()
is shown in the code below:
round(number[, ndigits])
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:
>>> from math import pi
>>> pi
3.141592653589793
>>> round(pi, 2)
3.14
>>> round(pi, 4)
3.1416
>>> round(pi, 6)
3.141593
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:
>>> round(1.5)
2
>>> round(2.5)
2
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.
Note: To dive deeper into how to use sum()
, check out the Python’s sum()
: The Pythonic Way to Sum Values tutorial.
The sum()
function allows you to add together a series of values. Its signature is like the following:
sum(iterable[, start=0])
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:
>>> 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)
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:
mean.py
def mean(values):
try:
return sum(values) / len(values)
except ZeroDivisionError:
raise ValueError("mean() arg shouldn't be empty") from None
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:
>>> 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'
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:
>>> int("0b10", base=2)
2
>>> int("0o10", base=8)
8
>>> int("0x10", base=16)
16
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:
>>> bin(42)
'0b101010'
>>> oct(42)
'0o52'
>>> hex(42)
'0x2a'
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()
.
Note: To dive deeper into complex numbers and the complex()
function, check out the Simplify Complex Numbers With Python tutorial.
Here are the signatures of both functions:
float(number=0.0)
complex(real=0, imag=0)
complex(string)
The float()
function takes a single argument representing a numeric value. This argument accepts numbers or strings that represent valid numbers:
>>> 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'
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:
>>> complex(3, 6)
(3+6j)
>>> complex(1, 0)
(1+0j)
>>> complex(0, 1)
1j
>>> complex(3.14, -2.75)
(3.14-2.75j)
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:
>>> complex("3+6j")
(3+6j)
>>> complex("1+0j")
(1+0j)
>>> complex("1j")
1j
>>> complex("3.14-2.75j")
(3.14-2.75j)
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:
str()
repr()
With the str()
function, you can create new strings or convert existing objects to strings:
>>> 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'}"
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:
>>> "-".join(str(value) for value in [1, 2, 3, 4, 5])
'1-2-3-4-5'
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:
>>> 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'}"
For built-in types, the string representation you get with repr()
is the same as the one you get with the str()
function.
Note: Behind the str()
function, you have the .__str__()
special method. Similarly, behind repr()
, you have the .__repr__()
method. To learn more about these special methods, check out the When Should You Use .__repr__()
vs .__str__()
in Python? tutorial.
To see the difference between str()
and repr()
, consider the following example that uses the datetime
module:
>>> 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'
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
andFalse
- 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.
Note: To learn more about Boolean values and logic, check out the Python Booleans: Use Truth Values in Your Code tutorial.
Here are a few examples of how bool()
works:
>>> 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
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:
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)
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:
>>> from stack import Stack
>>> stack = Stack()
>>> bool(stack)
False
>>> stack.push(4)
>>> bool(stack)
True
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:
>>> ord("A")
65
>>> ord("Z")
90
>>> ord("x")
120
>>> ord("ñ")
241
>>> ord("&")
38
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:
>>> 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
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:
>>> chr(65)
'A'
>>> chr(90)
'Z'
>>> chr(120)
'x'
>>> chr(241)
'ñ'
>>> chr(38)
'&'
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:
bytes(source=b"")
bytes(source, encoding)
bytes(source, encoding, errors)
bytearray(source=b"")
bytearray(source, encoding)
bytearray(source, encoding, errors)
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:
>>> 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')
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:
>>> 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: ñ Ł'
>>> bytes(
... "Using non-ASCII characters with the ASCII encoding: ñ Ł",
... encoding="ascii",
... errors="backslashreplace"
... )
b'Using non-ASCII characters with the ASCII encoding: \\xf1 \\u0141'
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.
Note: To learn more about working with lists and the list()
function, check out Python’s list
Data Type: A Deep Dive With Examples tutorial.
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.
Note: To learn more about working with tuples and the tuple()
function, check out Python’s tuple
Data Type: A Deep Dive With Examples tutorial.
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:
list([iterable])
Note that the square brackets around iterable
mean that the argument is optional, so the brackets aren’t part of the syntax.
Note: In practice, list()
is a class constructor rather than a function. However, the Python documentation calls it a function.
Here are a few examples of using list()
to create list
objects:
>>> 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')]
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.
Note: In most cases, you’ll use a pair of square braces, []
, to create an empty list. However, in some situations, using list()
can be more readable or explicit.
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:
tuple([iterable])
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:
>>> 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')
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.
Note: To learn more about dictionaries and the dict()
function, check out the Dictionaries in Python tutorial.
The dict()
function has the following signatures:
dict(**kwargs)
dict(mapping, **kwargs)
dict(iterable, **kwargs)
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:
>>> 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'}
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:
set
frozenset
The difference between these two data types is that set
objects are mutable, and frozenset
objects are immutable.
Note: To learn more about sets and the set()
function, check out the Sets in Python tutorial.
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:
set([iterable])
frozenset([iterable])
Again, the square brackets indicate that the input iterable is optional. Now check out the following examples of creating sets and frozen sets:
>>> 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'})
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.
Note: To learn more about the len()
function, check out the Using the len()
Function in Python tutorial.
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:
>>> 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
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:
>>> len("")
0
>>> len(())
0
>>> len([])
0
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:
>>> grades = [90, 97, 100, 87]
>>> sum(grades) / len(grades)
93.5
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.
Note: To dive deeper into reversing lists with the reversed()
function, check out the Reverse Python Lists: Beyond .reverse()
and reversed()
tutorial.
The reversed()
function takes an iterable as an argument and returns an iterator that yields the items in reverse order:
>>> 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]
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.
Note: Converting iterators into lists is a common use case for the list()
function, especially when debugging or working interactively.
Similarly, when you need to sort the values in an iterable, you can use the sorted()
function. This function has the following signature:
sorted(iterable, key=None, reverse=False)
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.
Note: To learn more about the sorted()
function, check out the How to Use sorted()
and .sort()
in Python tutorial.
Here are a few examples of using sorted()
with different built-in containers as arguments:
>>> 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]
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.
Note: To dive deeper into sorting strings, check out the How to Sort Unicode Strings Alphabetically in Python tutorial.
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:
>>> 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)]
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:
all(iterable)
any(iterable)
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.
Note: To learn more about the all()
function, check out the Python’s all()
: Check Your Iterables for Truthiness tutorial.
Here are a few examples of how all()
works:
>>> all([1, 2, 3, 4])
True
>>> all([1, 2, 3, 4, 0])
False
>>> all(["Hello", ""])
False
>>> all(["Hello", "World"])
True
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:
>>> 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
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.
Note: To learn more about the any()
function, check out the How to Use any()
in Python tutorial.
Here are a couple of examples of using any()
in Python:
>>> any([0, 0.0, False, "", []])
False
>>> any([0, 0.0, False, "", [], 42])
True
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:
>>> any(letter.isupper() for letter in "hello, world!")
False
>>> any(letter.isupper() for letter in "Hello, World!")
True
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:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
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:
>>> 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]
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!
Note: To dive deeper into working with ranges and the range()
function, check out the Python range()
: Represent Numerical Ranges tutorial.
The built-in range()
function has the following signatures:
range(stop)
range(start, stop, step=1)
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:
>>> 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]
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:
>>> 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]
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:
>>> for _ in range(3):
... print("Beep")
...
Beep
Beep
Beep
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:
>>> letters = ["a", "b", "c", "d"]
>>> for index in range(len(letters)):
... print(index, letters[index])
...
0 a
1 b
2 c
3 d
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:
>>> colors = ["red", "orange", "yellow", "green"]
>>> for color in colors:
... print(color)
...
red
orange
yellow
green
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:
>>> for index in range(len(colors)):
... print(index, colors[index])
...
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
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.
Note: To dive deeper into using the enumerate()
function, check out the Python enumerate()
: Simplify Loops That Need Counters tutorial.
Here’s how you’ll write the above loop using the enumerate()
function:
>>> for index, color in enumerate(colors):
... print(index, color)
...
0 red
1 orange
2 yellow
3 green
4 blue
5 indigo
6 violet
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:
enumerate(iterable, start=0)
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:
>>> 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
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:
slice(stop)
slice(start, stop, step=None)
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:
>>> 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]
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:
>>> even = numbers[1::2]
>>> even
[2, 4, 6, 8]
>>> odd = numbers[::2]
>>> odd
[1, 3, 5, 7, 9]
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.
Note: To learn more about the zip()
function, check out the Using the Python zip()
Function for Parallel Iteration tutorial.
Here’s the signature of the built-in zip()
function:
zip(*iterables, strict=False)
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:
>>> 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 +
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:
>>> keys = ["name", "age", "country"]
>>> values = ["Jane", "30", "Canada"]
>>> dict(zip(keys, values))
{'name': 'Jane', 'age': '30', 'country': 'Canada'}
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:
>>> 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
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.
Note: To learn more about iterators and iterables, check out the Iterators and Iterables in Python: Run Efficient Iterations tutorial.
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:
>>> 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
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()
:
iter(iterable)
iter(object, sentinel)
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:
>>> 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
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:
next(iterator)
next(iterator, default)
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:
>>> 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
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.
Note: To dive deeper into using the filter()
function, check out the Python’s filter()
: Extract Values From Iterables tutorial.
The signature of filter()
looks something like this:
filter(function, iterable)
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:
>>> numbers = [1, 3, 10, 45, 6, 50]
>>> list(filter(lambda n: n % 2 == 0, numbers))
[10, 6, 50]
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.
Note: Anonymous functions are common tools in functional programming. Python allows you to create this kind of function using the lambda
keyword. To learn more about them, check out How to Use Python Lambda Functions.
Another fundamental tool in functional programming is reduce()
which used to be a built-in function, but is now available in the functools
module. Have a look at Python’s reduce()
: From Functional to Pythonic Style for more information.
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.
Note: To learn more about the map()
function, check out the Python’s map()
: Processing Iterables Without a Loop tutorial.
Python’s map()
has the following signature:
map(function, iterable, *iterables)
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:
>>> bases = [8, 5, 2]
>>> exponents = [2, 3, 4]
>>> list(map(pow, bases, exponents))
[64, 125, 16]
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()
.
Note: To dive deeper into using the input()
function, check out the How to Read User Input From the Keyboard in Python tutorial.
The built-in input()
function reads the user’s input and grabs it as a string. Here’s the function’s signature:
input([prompt])
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:
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}")
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:
$ 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
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:
fruits.txt
apple
banana
cherry
orange
mango
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:
>>> with open("fruits.txt") as file:
... print(file.read())
...
apple
banana
cherry
orange
mango
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.
Note: To learn more about working with files, check out the Reading and Writing Files in Python (Guide) tutorial.
The open()
function has the following signature:
open(
file,
mode="r",
buffering=-1,
encoding=None,
errors=None,
newline=None,
closefd=True,
opener=None,
)
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:
>>> with open("hello.txt", "w") as file:
... file.write("Hello, World!")
...
13
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
:
>>> 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!
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.
Note: To dive deeper into using the print()
function, check out the Your Guide to the Python print()
Function tutorial.
The print()
function has the following signature:
print(*objects, sep=" ", end="\n", file=None, flush=False)
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:
>>> 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! 👋
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:
>>> with open("hello.txt", mode="w") as text_file:
... print("Hello, World!", file=text_file)
...
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.
Note: To dive deeper into flushing the output of print()
, check out the How to Flush the Output of the Python Print Function tutorial.
A cool example of using flush
is when you need to create a progress bar for a CLI application. Consider the following function:
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,
)
This function generates a horizontal progress bar by taking advantage of the flush
argument. Here’s how you can use it in your code:
>>> from time import sleep
>>> from progress import progress
>>> for percent in range(101):
... sleep(0.2)
... progress(percent)
...
[########### ] 38%
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:
format(value, format_spec="")
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:
>>> 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'
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.
Note: To learn more about format specifiers, check out the Python’s Format Mini-Language for Tidy Strings tutorial.
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:
>>> 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
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:
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
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:
>>> 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
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:
>>> point.x
Traceback (most recent call last):
...
AttributeError: 'Point' object has no attribute 'x'
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:
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
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:
>>> 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
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:
- Instance methods, which take the current object as their first argument
- Class methods, which take the current class as their first argument
- 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:
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)),
)
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:
>>> from point import Point
>>> point = Point.from_polar(20, 15)
>>> point.y
5.176380902050415
>>> point.x
19.318516525781366
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:
formatting.py
class Formatter:
@staticmethod
def as_currency(value):
return f"${value:,.2f}"
@staticmethod
def as_percent(value):
return f"{value:.2%}"
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:
>>> 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%'
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:
getattr(object, name)
getattr(object, name, default)
setattr(object, name, value)
delattr(object, name)
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:
person.py
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
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:
>>> from person import Person
>>> jane = Person("Jane", 25)
>>> getattr(jane, "name")
'Jane'
>>> getattr(jane, "age")
25
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:
>>> setattr(jane, "age", 26)
>>> jane.age
26
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:
>>> delattr(jane, "age")
>>> jane.age
Traceback (most recent call last):
...
AttributeError: 'Person' object has no attribute 'age'
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.
Note: To learn more about deleting objects in Python, check out the Python’s del
: Remove References From Scopes and Containers tutorial.
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:
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)
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:
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)
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:
>>> 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'}
]
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:
hasattr(object, name)
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:
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")
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:
>>> 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'
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:
>>> for bird in birds:
... bird.fly()
... if hasattr(bird, "swim"):
... bird.swim()
...
The duck is flying
The duck is swimming
The pigeon is flying
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.
Note: Because Python is a dynamically typed language, the duck typing style is favored over explicit type checking. To learn about duck typing, check out the Duck Typing in Python: Writing Flexible and Decoupled Code tutorial.
To know the type of a given object, you can use the built-in type()
function:
>>> type(42)
<class 'int'>
>>> type(2.75)
<class 'float'>
>>> type("Hello")
<class 'str'>
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:
>>> type(42) == int
True
>>> type(42) == float
False
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:
type(object)
type(name, bases, dict, **kwds)
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:
>>> 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'
]
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:
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)
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:
>>> 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
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:
isinstance(object, classinfo)
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:
>>> isinstance(42, int)
True
>>> isinstance(42.0, (int, float))
True
>>> isinstance(42.0, int | float)
True
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:
>>> isinstance(False, int)
True
>>> type(False) == int
False
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:
>>> issubclass(int, object)
True
>>> issubclass(bool, int)
True
>>> issubclass(int, float)
False
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:
issubclass(class, classinfo)
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:
>>> callable(abs)
True
>>> callable(int)
True
>>> callable(list)
True
>>> callable(True)
False
>>> callable(None)
False
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:
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)
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:
>>> 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
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.
Note: To learn more about super()
, check out the Supercharge Your Classes With Python super()
tutorial.
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:
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)
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:
>>> issubclass(int, object)
True
>>> issubclass(float, object)
True
>>> issubclass(bool, object)
True
>>> issubclass(dict, object)
True
>>> class DemoClass:
... pass
...
>>> issubclass(DemoClass, object)
True
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:
>>> 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'
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:
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
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.
Note: To dive deeper into Python scopes, check out the Python Scope & the LEGB Rule: Resolving Names in Your Code tutorial.
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:
>>> def add(a, b):
... result = a + b
... print(locals())
... return result
...
>>> add(2, 5)
{'a': 2, 'b': 5, 'result': 7}
7
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:
>>> def add(a, b):
... print(locals())
... result = a + b
... return result
...
>>> add(2, 5)
{'a': 2, 'b': 5}
7
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:
>>> globals()
{
'__name__': '__main__',
'__doc__': None,
...
'__builtins__': <module 'builtins' (built-in)>
}
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:
>>> 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'>
}
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:
config.json
{
"DATABASE_URL": "postgres://user:pass@localhost/dbname",
"DEBUG_MODE": true,
"MAX_CONNECTIONS": 10
}
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:
config.py
import json
def load_config(config_file):
with open(config_file) as file:
config = json.load(file)
globals().update(config)
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:
>>> 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
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:
>>> id(42)
4315605776
>>> id("Python")
4315120464
>>> def greet():
... print("Hello, World!")
...
>>> id(greet)
4307259040
>>> class Demo:
... pass
...
>>> id(Demo)
4892672720
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:
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
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.
Note: The regular Fibonacci sequence starts with 0 and 1. In your Fibonacci like sequence, the second number can be anything, but you keep the rule that a number is the sum of the two previous numbers in the sequence.
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:
>>> 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
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:
>>> dir()
[
'__annotations__',
'__builtins__',
'__doc__',
'__loader__',
'__name__',
'__package__',
'__spec__'
]
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:
>>> 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'
]
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:
>>> 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}
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:
"sum([2, 3, 4, 5]) / 4 + 100"
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.
Note: To learn more about eval()
, check out the Python eval()
: Evaluate Expressions Dynamically tutorial.
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:
>>> eval("sum([2, 3, 4, 5]) / 4 + 100")
103.5
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:
eval(expression[, globals[, locals]])
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:
>>> 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
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:
>>> 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
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.
Note: To learn more about exec()
, check out the Python’s exec()
: Execute Dynamically Generated Code tutorial.
Here’s the signature of the exec()
function:
exec(code [, globals [, locals]])
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:
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)
...
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:
>>> add(1, 2)
3
>>> subtract(3, 2)
1
>>> multiply(2, 3)
6
>>> divide(6, 3)
2.0
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.
Note: The exec()
function also has security implications. To learn more about them, check out the Uncovering and Minimizing the Security Risks Behind exec()
section in the Python’s exec()
: Execute Dynamically Generated Code tutorial.
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:
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
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"
whensource
consists of a single expression"exec"
whensource
is a sequence of statements"single"
whensource
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:
>>> 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
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:
>>> 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>
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:
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.
...
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:
>>> help(str)
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:
>>> help(sys)
Traceback (most recent call last):
...
NameError: name 'sys' is not defined
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()
.
Note: To learn more about hash tables, check out the Build a Hash Table in Python With TDD tutorial.
The signature of hash()
is like the following:
hash(object)
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:
>>> 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
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:
>>> 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'
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.
Note: To dive deeper into the import system, check out the Python import
: Advanced Techniques and Tips tutorial.
The signature of __import__()
looks something like the following:
__import__(name, globals=None, locals=None, fromlist=(), level=0)
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:
>>> sys = __import__("sys")
>>> sys
<module 'sys' (built-in)>
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:
>>> 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]
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.
Get Your Code: Click here to download the free sample code that shows you how to use Python’s built-in functions.
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 ExplorationTake 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.