https://erikvandeven.medium.com/python-uncovering-the-overlooked-core-functionalities-54590420c225 [ ] Python: Uncovering the Overlooked Core Functionalities Erik van de Ven Erik van de Ven * Follow 7 min read * 7 hours ago -- Listen Share Photo by Stefan Steinbauer on Unsplash What Kyle Simpson mentions about JavaScript in his books, Luciano Ramalho mentions in his book 'Fluent Python' about Python. They basically address the same issue of both languages. To put it in my own words: Because the language is so easy to learn, many practitioners only scratch the surface of its full potential, neglecting to delve into the more advanced and powerful aspects of the language which makes it so truly unique and powerful So let's discuss briefly all functionalities you might haven't heard of, but you definitely want to know if you aim to become a truly seasoned Pythonista. Evaluation of Default Arguments Python arguments are evaluated when the function definition is encountered. Good to remember that! This means that each time the fib_memo function (which is mentioned below) is called without explicitly providing a value for the memo argument, it will use the same dictionary object that was created when the function was defined. def fib_memo(n, memo={0:0, 1:1}): """ n is the number nth number you would like to return in the sequence """ if not n in memo: memo[n] = fib_memo(n-1) + fib_memo(n-2) return memo[n] # 6th Fibonacci (including 0 as first number) fib_memo(6) # should return 8 So this code works, in Python. This also means that you could execute the fib_memo function in a single script multiple times, like in a for loop, with each execution increasing the fibonacci number to be computed, without hitting the "maximum recursion depth exceeded" limit, as the memo will keep expanding. More information can be found in my other article. Walrus Operator The walrus operator (:= ), introduced in Python 3.8, allows you to assign a value to a variable within an expression. This way, you can assign the value to a variable and check its value in one expression: import random some_value = random.randint(0,100) # return a number between 0 and, including, 100 if((below_ten := some_value) < 10): print(f"{below_ten} is smaller than 10") Obviously, it's also easy to assign and check whether the returned value contains a truthy value or not: if(result := some_method()): # If result is not Falsy print(result) *args and **kwargs With the asterisk (* ) you can unpack the arguments or keyword arguments (with ** ) before passing them to the function. For example, let's consider the following code: my_numbers = [1,2] def sum_numbers(first_number, second_number): return first_number + second_number # This will return a TypeError. # TypeError: sum() missing 1 required positional argument: 'second_number' sum_numbers(my_numbers) # This will return the expected result, 3 sum_numbers(*my_numbers) When we call the sum_numbers function without unpacking my_numbers, it raises a TypeError because the function expects two separate arguments. However, by using the asterisk (*), we can unpack the values from my_numbers and pass them as individual arguments, resulting in the correct output. This unpacking technique works not only with tuples and lists, but also with dictionaries (though it will pass the keys as arguments). But what about keyword arguments? For that, we can utilize the double asterisk (**). Take the following code as an example: def greet_person(last_name, first_name): print(f"Hello {first_name} {last_name}") data = {"first_name": "John", "last_name": "Doe"} greet_person(**data) Besides unpacking a sequence to pass them as arguments to a function, you could also use it to create a new sequence, for example: numbers = [1, 2, 3, 4, 5] new_list_numbers = [*numbers] The original numbers list remains unaffected, and you have a new_list_numbers variable which contains a copy of the same list. Be careful with links containing objects, though: numbers = [[1, 2], [3, 4], [5, 6]] packed_numbers = [*numbers] numbers[0].append(10) # Modify the nested list within the original list print(numbers) # Output: [[1, 2, 10], [3, 4], [5, 6]] print(packed_numbers) # Output: [[1, 2, 10], [3, 4], [5, 6]] any and all any and all are built-in functions that operate on iterable objects (such as lists, tuples, or sets) and return a Boolean value based on the elements in the iterable. An example: some_booleans = [True, False, False, False] any(some_booleans) # returns True all(some_booleans) # returns You could use the all and any functions in combination with list comprehensions, which return an iterable and pass it as argument to the all functions: numbers = [5, 10, 3, 8, -2] all_positive = all(num > 0 for num in numbers) ... or any functions: fruits = ['apple', 'banana', 'cherry', 'durian'] # Check if all fruits start with 'a' result = all(fruit.startswith('a') for fruit in fruits) print(result) # Output: False A table which shows the differences of outputs depending on the values in the iterable, is shown below. Swapping Variables You can combine tuple packing (what is happening on the right of the equal (=) sign) and unpacking (what is happening on the left of the equal(=) sign) and leverage this functionality for swapping variables: a = 10 b = 5 # Swap the values of b and a by packing and unpacking a, b = b, a print(a) # 5 print(b) # 10 str vs repr We are used to converting some variable or value to a string, usingstr(some_value), so we can print it for debugging purposes. I would like to make you aware of repr(some_value). The main difference is that repr tries to return a printable representation of the object, while str just tries to return a string representation. A better example is shown below: import datetime today = datetime.datetime.now() print(str(today)) # Output: 2023-07-20 15:30:00.123456 print(repr(today)) # Output: datetime.datetime(2023, 7, 20, 15, 30, 0, 123456) As you can see, str() simply returns the datetime as a string representation. If you want to determine whether the variable today contains a string or a datetime object, you wouldn't be able to discern that information from this alone. On the other hand, [?]repr() provides information about the actual object that the variable holds. This information is significantly more valuable during debugging. Extended Iterable Unpacking We can keep this simple: if you would like to get the first and last value of a sequence in a single command: first, *middle, last = [1, 2, 3, 4, 5] print(first) # 1 print(middle) # [2, 3, 4] print(last) # 5 But this works as well *the_first_three, second_last, last = [1, 2, 3, 4, 5] print(the_first_three) # [1, 2, 3] print(second_last) # 4 print(last) # 5 Or other combinations. Multiple Context Managers We are used to using one context manager at a time, like opening a file: with open('file.txt', 'r') as file: # Code that uses the file # The file will be automatically closed at the end of the block # even if an exception occurs # Example: reading lines from the file for line in file: print(line.strip()) with open('file_2.txt', 'r') as other_file: # Second context manager for line in other_file: print(line.strip()) But we could easily open multiple files in a single statement. Easy if you would like to write lines to the other file for example: with open('file1.txt') as file1, open('file2.txt') as file2: # Code that uses both file1 and file2 # The files will be automatically closed at the end of the block # even if an exception occurs # Example: reading lines from file1 and writing them to file2 for line in file1: file2.write(line) The Python Debugger We could just print a ton of variables in our file, for debugging purposes, or we could simply use the Python Debugger (pdb), which helps us to set breakpoints which makes it so much easier: import pdb # Set this breakpoint somewhere in your code pdb.set_trace() What makes this so much more valuable is that the program will stop at the breakpoint at which you could print any variable to check its value or existence at that specific breakpoint. Try it! These are several commands you could use when the program hits a breakpoint: * n or next: Execute the next line. * s or step: Step into a function call. * c or continue: Continue execution until the next breakpoint. * l or list: Show the current code context. * p or pp : Print the value of an expression. * b or break : Set a new breakpoint at the specified line. * h or help: Get help on using pdb. * q or quit: Quit the debugger and terminate the program. collections.Counter The Counter class from the collections module provides a convenient way to count elements in an iterable: from collections import Counter my_list = [1, 2, 3, 1, 2, 1, 3, 4, 5] counts = Counter(my_list) print(counts) # Output: Counter({1: 3, 2: 2, 3: 2, 4: 1, 5: 1}) Combinations Using Itertools We could combine different for loops to create permutations, combinations or a cartesian product, or we could simply use the built in itertools. Permutations import itertools # Generating permutations perms = itertools.permutations([1, 2, 3], 2) print(list(perms)) # Output: [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] Combinations import itertools # Generating combinations combs = itertools.combinations('ABC', 2) print(list(combs)) # Output: [('A', 'B'), ('A', 'C'), ('B', 'C')] Cartesian product import itertools # Generating Cartesian product cartesian = itertools.product('AB', [1, 2]) print(list(cartesian)) # Output: [('A', 1), ('A', 2), ('B', 1), ('B', 2)] Two Ways of Using Underscore These are two ways to use the underscore in Python: as a separater for large numbers or as a throwaway variable. Throwaway Variable The underscore _ can be used as a throwaway variable to discard unwanted values: # Ignoring the first return value of a function _, result = some_function() # Looping without using the loop variable for _ in range(5): do_something() # You just need the first and the last first, *_, last = [1, 2, 3, 4, 5] Separater for Large Numbers You can use underscores (_) as visual separators to enhance readability when working with large numeric values. This feature was introduced in Python 3.6 and is known as "underscore literals." population = 7_900_000_000 revenue = 3_249_576_382.50 print(population) # Output: 7900000000 print(revenue) # Output: 3249576382.5 Liked this article? A few claps is very much appreciated (Just hold that clap button and never let go )! And if you want to read more, make sure to give me a follow. Thanks so much for reading! Python Python Programming Data Science Python3 Software Development -- -- Erik van de Ven Follow Written by Erik van de Ven 28 Followers Erik is a Senior SE with 15+ years of experience in programming and 8+ years in Python. He ranked the top 9% on Stack Overflow and is a Kaggle Expert. Follow More from Erik van de Ven Python's Hidden Gems: 3 Must-Know Functionalities Erik van de Ven Erik van de Ven in Better Programming Python's Hidden Gems: 3 Must-Know Functionalities You know Python! Or at least, you think you do 4 min read*Jul 11 -- 1 Memoization done right in Python Erik van de Ven Erik van de Ven in Level Up Coding Memoization done right in Python Take notes! You know how to create the Fibonacci sequence in Python? Well keep reading, because since Python 3.8 there is a cleaner way of... 5 min read*Jul 14 -- See all from Erik van de Ven Recommended from Medium Web Scraping in Python: Avoid Detection Like a Ninja ZenRows ZenRows Web Scraping in Python: Avoid Detection Like a Ninja Scraping should be about extracting content from HTML. It sounds simple but has many obstacles. The first one is to obtain the said HTML... 13 min read*Apr 5 -- 5 We Analyzed 1,626 Banned Books...Here's What We Found Statecraft by Arman Madani Statecraft by Arman Madani We Analyzed 1,626 Banned Books...Here's What We Found What do banned books have in common? 5 min read*Jul 3 -- 54 Lists [0] [1] [1] Coding & Development 11 stories*63 saves [0] [1] [0] Predictive Modeling w/ Python 18 stories*149 saves Databricks role-based and specialty certification line-up. [0] [1] New_Reading_List 174 stories*32 saves Principal Component Analysis for ML Time Series Analysis deep learning cheatsheet for beginner Practical Guides to Machine Learning 10 stories*168 saves How to Build a 5-Layer Data Stack Barr Moses Barr Moses in Towards Data Science How to Build a 5-Layer Data Stack Spinning up a data platform doesn't have to be complicated. Here are the 5 must-have layers to drive data product adoption at scale. 10 min read*3 days ago -- 9 Python at the speed of light Andrea Dalseno Andrea Dalseno Python at the speed of light Even if every new version dramatically improves performance, Python is still a high-level interpreted language without strong typing... 3 min read*Jun 14 -- 1 Understanding why you won't need Python Coroutines 99.9% of the time Abhijit Mondal Abhijit Mondal Understanding why you won't need Python Coroutines 99.9% of the time Having worked extensively with Python across multiple projects in my career, I have used multithreading and multiprocessing in many places... 11 min read*Feb 4 -- 2 Best Coding IDEs for Python Swift Swift Best Coding IDEs for Python Looking for the best coding IDEs for Python? Discover the top choices for Python developers 3 min read*Jul 11 -- 2 See more recommendations Help Status Writers Blog Careers Privacy Terms About Text to speech Teams