Posted on Leave a comment

Python Async IO – The Ultimate Guide in a Single Post

5/5 – (1 vote)

As a Python developer, you might have come across the concept of asynchronous programming. Asynchronous programming, or async I/O, is a concurrent programming design that has received dedicated support in Python, evolving rapidly from Python 3.4 through 3.7 and beyond. With async I/O, you can manage multiple tasks concurrently without the complexities of parallel programming, making it a perfect fit for I/O bound and high-level structured network code.

In the Python world, the asyncio library is your go-to tool for implementing asynchronous I/O. This library provides various high-level APIs to run Python coroutines concurrently, giving you full control over their execution. It also enables you to perform network I/O, Inter-process Communication (IPC), control subprocesses, and synchronize concurrent code using tasks and queues.

Understanding Asyncio

In the world of Python programming, asyncio plays a crucial role in designing efficient and concurrent code without using threads. It is a library that helps you manage tasks, event loops, and coroutines. To fully benefit from asyncio, you must understand some key components.

First, let’s start with coroutines. They are special functions that can pause their execution at specified points without completely terminating it. In Python, you declare a coroutine using the async def syntax.

For instance:

async def my_coroutine(): # Your code here

Next, the event loop is a core feature of asyncio and is responsible for executing tasks concurrently and managing I/O operations. An event loop runs tasks one after the other and can pause a task when it is waiting for external input, such as reading data from a file or from the network. It also listens for other tasks that are ready to run, switches to them, and resumes the initial task when it receives the input.

Tasks are the coroutines wrapped in an object, managed by the event loop. They are used to run multiple concurrent coroutines simultaneously. You can create a task using the asyncio.create_task() function, like this:

async def my_coroutine(): # Your code here task = asyncio.create_task(my_coroutine())

Finally, the sleep function in asyncio is used to simulate I/O bound tasks or a delay in the code execution. It works differently than the standard time.sleep() function as it is non-blocking and allows other coroutines to run while one is paused. You can use await asyncio.sleep(delay) to add a brief pause in your coroutine execution.

Putting it all together, you can use asyncio to efficiently manage multiple coroutines concurrently:

import asyncio async def task_one(): print('Starting task one') await asyncio.sleep(3) print('Finished task one') async def task_two(): print('Starting task two') await asyncio.sleep(1) print('Finished task two') async def main(): task1 = asyncio.create_task(task_one()) task2 = asyncio.create_task(task_two()) await task1 await task2 # Run the event loop
asyncio.run(main())

In this example, the event loop will start running both tasks concurrently, allowing task two to complete while task one is paused during the sleep period. This allows you to handle multiple tasks in a single-threaded environment.

You can see it play out in this Gif:

Async/Await Syntax

In Python, the async/await syntax is a powerful tool to create and manage asynchronous tasks without getting lost in callback hell or making your code overly complex.

The async/await keywords are at the core of asynchronous code in Python. You can use the async def keyword to define an asynchronous function. Inside this function, you can use the await keyword to pause the execution of the function until some asynchronous operation is finished.

For example:

import asyncio async def main(): print("Start") await asyncio.sleep(2) print("End")

yield and yield from are related to asynchronous code in the context of generators, which provide a way to iterate through a collection of items without loading all of them into memory at once. In Python 3.3 and earlier, yield from was used to delegate a part of a generator’s operation to another generator. However, in later versions of Python, the focus shifted to async/await for managing asynchronous tasks, and yield from became less commonly used.

For example, before Python 3.4, you might have used a generator with yield and yield from like this:

def generator_a(): for i in range(3): yield i def generator_b(): yield from generator_a() for item in generator_b(): print(item)

With the introduction of async/await, asynchronous tasks can be written more consistently and readably. You can convert the previous example to use async/await as follows:

import asyncio async def async_generator_a(): for i in range(3): yield i await asyncio.sleep(1) async def async_generator_b(): async for item in async_generator_a(): print(item) await async_generator_b()

Working with Tasks and Events

In asynchronous programming with Python, you’ll often work with tasks and events to manage the execution of simultaneous IO-bound operations. To get started with this model, you’ll need to understand the event loop and the concept of tasks.

The event loop is a core component of Python’s asyncio module. It’s responsible for managing and scheduling the execution of tasks. A task, created using asyncio.create_task(), represents a coroutine that runs independently of other tasks in the same event loop.

To create tasks, first, define an asynchronous function using the async def syntax. Then, you can use the await keyword to make non-blocking calls within this function. The await keyword allows the event loop to perform other tasks while waiting for an asynchronous operation to complete.

Here’s an example:

import asyncio async def my_async_function(): print("Task started") await asyncio.sleep(2) print("Task finished") event_loop = asyncio.get_event_loop()
task = event_loop.create_task(my_async_function())
event_loop.run_until_complete(task)

In this example, my_async_function is an asynchronous function, and await asyncio.sleep(2) represents an asynchronous operation. The event_loop.create_task() method wraps the coroutine into a task, allowing it to run concurrently within the event loop.

To execute tasks and manage their output, you can use asyncio.gather(). This function receives a list of tasks and returns their outputs as a list in the same order they were provided. Here’s an example of how you can use asyncio.gather():

import asyncio async def async_task_1(): await asyncio.sleep(1) return "Task 1 completed" async def async_task_2(): await asyncio.sleep(2) return "Task 2 completed" async def main(): tasks = [async_task_1(), async_task_2()] results = await asyncio.gather(*tasks) print(results) asyncio.run(main())

In this example, asyncio.gather() awaits the completion of both tasks and then collects their output in a list, which is printed at the end.

Working with tasks and events in Python’s asynchronous IO model helps improve the efficiency of your code when dealing with multiple IO operations, ensuring smoother and faster execution. Remember to use asyncio.create_task(), await, and asyncio.gather() when handling tasks within your event loop.

Coroutines and Futures

In Python, async IO is powered by coroutines and futures. Coroutines are functions that can be paused and resumed at specific points, allowing other tasks to run concurrently. They are declared with the async keyword and used with await. Asyncio coroutines are the preferred way to write asynchronous code in Python.

On the other hand, futures represent the result of an asynchronous operation that hasn’t completed yet. They are primarily used for interoperability between callback-based code and the async/await syntax. With asyncio, Future objects should be created using loop.create_future().

To execute multiple coroutines concurrently, you can use the gather function. asyncio.gather() is a high-level function that takes one or more awaitable objects (coroutines or futures) and schedules them to run concurrently. Here’s an example:

import asyncio async def foo(): await asyncio.sleep(1) return "Foo" async def bar(): await asyncio.sleep(2) return "Bar" async def main(): results = await asyncio.gather(foo(), bar()) print(results) asyncio.run(main())

In this example, both foo() and bar() coroutines run concurrently, and the gather() function returns a list of their results.

Error handling in asyncio is done through the set_exception() method. If a coroutine raises an exception, you can catch the exception and attach it to the associated future using future.set_exception(). This allows other coroutines waiting for the same future to handle the exception gracefully.

In summary, working with coroutines and futures helps you write efficient, asynchronous code in Python. Use coroutines along with the async/await syntax for defining asynchronous tasks, and futures for interacting with low-level callback-based code. Utilize functions like gather() for running multiple coroutines concurrently, and handle errors effectively with future.set_exception().

Threading and Multiprocessing

In the world of Python, you have multiple options for concurrent execution and managing concurrency. Two popular approaches to achieve this are threading and multiprocessing.

Threading can be useful when you want to improve the performance of your program by efficiently utilizing your CPU’s time. It allows you to execute multiple threads in parallel within a single process. Threads share memory and resources, which makes them lightweight and more suitable for I/O-bound tasks. However, because of the Global Interpreter Lock (GIL) in Python, only one thread can execute at a time, limiting the benefits of threading for CPU-bound tasks. You can explore the threading module for building multithreaded applications.

Multiprocessing overcomes the limitations of threading by using multiple processes working independently. Each process has its own Python interpreter, memory space, and resources, effectively bypassing the GIL. This approach is better for CPU-bound tasks, as it allows you to utilize multiple cores to achieve true parallelism. To work with multiprocessing, you can use Python’s multiprocessing module.

While both threading and multiprocessing help manage concurrency, it is essential to choose the right approach based on your application’s requirements. Threading is more suitable when your tasks are I/O-bound, and multiprocessing is advisable for CPU-bound tasks. When dealing with a mix of I/O-bound and CPU-bound tasks, using a combination of the two might be beneficial.

Async I/O offers another approach for handling concurrency and might be a better fit in some situations. However, understanding threading and multiprocessing remains crucial to make informed decisions and efficiently handle concurrent execution in Python.

Understanding Loops and Signals

In the world of Python async IO, working with loops and signals is an essential skill to grasp. As a developer, you must be familiar with these concepts to harness the power of asynchronous programming.

Event loops are at the core of asynchronous programming in Python. They provide a foundation for scheduling and executing tasks concurrently. The asyncio library helps you create and manage these event loops. You can experiment with event loops using Python’s asyncio REPL, which can be started by running python -m asyncio in your command line.

Signals, on the other hand, are a way for your program to receive notifications about certain events, like a user interrupting the execution of the program. A common use case for handling signals in asynchronous programming involves stopping the event loop gracefully when it receives a termination signal like SIGINT or SIGTERM.

A useful method for running synchronous or blocking functions in an asynchronous context is the loop.run_in_executor() method. This allows you to offload the execution of such functions to a separate thread or process, preventing them from blocking the event loop. For example, if you have a CPU-bound operation that cannot be implemented using asyncio‘s native coroutines, you can utilize loop.run_in_executor() to keep the event loop responsive.

Here’s a simple outline of using loops and signals together in your asynchronous Python code:

  1. Create an event loop using asyncio.get_event_loop().
  2. Register your signal handlers with the event loop, typically by using the loop.add_signal_handler() method.
  3. Schedule your asynchronous tasks and coroutines in the event loop.
  4. Run the event loop using loop.run_forever(), which will keep running until you interrupt it with a signal or a coroutine stops it explicitly.

Managing I/O Operations

When working with I/O-bound tasks in Python, it’s essential to manage I/O operations efficiently. Using asyncio can help you handle these tasks concurrently, resulting in more performant and scalable code.

I/O-bound tasks are operations where the primary bottleneck is fetching data from input/output sources like files, network requests, or databases. To improve the performance of your I/O-bound tasks, you can use asynchronous programming techniques. In Python, this often involves using the asyncio library and writing non-blocking code.

Typically, you’d use blocking code for I/O operations, which means waiting for the completion of an I/O task before continuing with the rest of the code execution. This blocking behavior can lead to inefficient use of resources and poor performance, especially in larger programs with multiple I/O-bound tasks.

Non-blocking code, on the other hand, allows your program to continue executing other tasks while waiting for the I/O operation to complete. This can significantly improve the efficiency and performance of your program. When using Python’s asyncio library, you write non-blocking code with coroutines.

For I/O-bound tasks involving file operations, you can use libraries like aiofiles to perform asynchronous file I/O. Just like with asyncio, aiofiles provides an API to work with files using non-blocking code, improving the performance of your file-based tasks.

When dealing with network I/O, the asyncio library provides APIs to perform tasks such as asynchronous reading and writing operations for sockets and other resources. This enables you to manage multiple network connections concurrently, efficiently utilizing your system resources.

In summary, when managing I/O operations in Python:

  • Identify I/O-bound tasks in your program
  • Utilize the asyncio library to write non-blocking code using coroutines
  • Consider using aiofiles for asynchronous file I/O
  • Utilize asyncio APIs to manage network I/O efficiently

Handling Transports and Timeouts

When working with Python’s Async IO, you might need to handle transports and timeouts effectively. Transports and protocols are low-level event loop APIs for implementing network or IPC protocols such as HTTP. They help improve the performance of your application by using callback-based programming style. You can find more details in the Python 3.11.4 documentation.

Timeouts are often useful when you want to prevent your application from waiting indefinitely for a task to complete. To handle timeouts in asyncio, you can use the asyncio.wait_for function. This allows you to set a maximum time that your function can run. If the function doesn’t complete within the specified time, an asyncio.TimeoutError is raised.

import asyncio async def some_function(): await asyncio.sleep(5) async def main(): try: await asyncio.wait_for(some_function(), timeout=3) except asyncio.TimeoutError: print("Task took too long.") asyncio.run(main())

In this example, some_function takes 5 seconds to complete, but we set a timeout of 3 seconds. As a result, an asyncio.TimeoutError is raised, and the program prints “Task took too long.”

Another concept to be familiar with is the executor, which allows you to run synchronous functions in an asynchronous context. You can use the loop.run_in_executor() method, where loop is an instance of the event loop. This method takes three arguments: the executor, the function you want to run, and any arguments for that function. The executor can be a custom one or None for the default ThreadPoolExecutor.

Here’s an example:

import asyncio
import time def sync_function(seconds): time.sleep(seconds) return "Slept for {} seconds".format(seconds) async def main(): loop = asyncio.get_event_loop() result = await loop.run_in_executor(None, sync_function, 3) print(result) asyncio.run(main())

In this example, we run the synchronous sync_function inside the async main() function using the loop.run_in_executor() method.

Dealing with Logging and Debugging

When working with Python’s asyncio library, properly handling logging and debugging is essential for ensuring efficient and smooth development. As a developer, it’s crucial to stay confident and knowledgeable when dealing with these tasks.

To begin logging in your asynchronous Python code, you need to initialize a logger object. Import the logging module and create an instance of the Logger class, like this:

import logging logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

This configuration sets up a logger object that will capture debug-level log messages. To log a message, simply call the appropriate method like logger.debug, logger.info, or logger.error:

async def my_async_function(): logger.debug("Debug message") logger.info("Info message") logger.error("Error message") await some_async_operation()

Keep in mind that Python’s logging module is not inherently asynchronous. However, there are ways to work around this issue. One approach is to use a ThreadPoolExecutor, which executes logging methods in a separate thread:

import concurrent.futures
import logging executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) def log_info(msg, *args): executor.submit(logging.info, msg, *args) async def my_async_function(): log_info("Info message") await some_async_operation()

For debugging your asynchronous code, it’s possible to enable the debug mode in asyncio by calling the loop.set_debug() method. Additionally, consider setting the log level of the asyncio logger to logging.DEBUG and configuring the warnings module to display ResourceWarning warnings. Check the official Python documentation for more information and best practices.

Understanding Virtual Environments and Resources

When working with Python, you’ll often encounter the need for a virtual environment. A virtual environment is an isolated environment for your Python applications, which allows you to manage resources and dependencies efficiently. It helps ensure that different projects on your computer do not interfere with each other in terms of dependencies and versions, maintaining the availability of the required resources for each project.

To create a virtual environment, you can use built-in Python libraries such as venv or third-party tools like conda. Once created, you’ll activate the virtual environment and install the necessary packages needed for your project. This ensures that the resources are available for your application without causing conflicts with other Python packages or applications on your computer.

🔗 For a more detailed explanation of virtual environments, check out this complete guide to Python virtual environments.

When working with async IO in Python, it’s crucial to manage resources effectively, especially when dealing with asynchronous operations like networking requests or file I/O. By using a virtual environment, you can make sure that your project has the correct version of asyncio and other async libraries, ensuring that your code runs smoothly and efficiently.

In a virtual environment, resources are allocated based, on the packages and libraries you install. This way, only the necessary resources for your project are used, improving performance and consistency across development. The virtual environment lets you keep track of your project’s dependencies, making it easier to maintain and share your project with others, ensuring that they can access the required resources without compatibility issues.

Optimizing Asynchronous Program

When working with Python, you may often encounter situations where an asynchronous program can significantly improve the performance and responsiveness of your application. This is especially true when dealing with I/O-bound tasks or high-level structured network code, where asyncio can be your go-to library for writing concurrent code.

Before diving into optimization techniques, it’s crucial to understand the difference between synchronous and asynchronous programs. In a synchronous program, tasks are executed sequentially, blocking other tasks from running. Conversely, an asynchronous program allows you to perform multiple tasks concurrently without waiting for one to complete before starting another. This cooperative multitasking approach enables your asynchronous program to run much faster and more efficiently.

To make the most of your asynchronous program, consider applying the following techniques:

  1. Use async/await syntax: Employing the async and await keywords when defining asynchronous functions and awaiting their results ensures proper execution and responsiveness.
  2. Implement an event loop: The event loop is the core of an asyncio-based application. It schedules, executes, and manages tasks within the program, so it’s crucial to utilize one effectively.
  3. Leverage libraries: Many asynchronous frameworks, such as web servers and database connection libraries, have been built on top of asyncio. Take advantage of these libraries to simplify and optimize your asynchronous program.
  4. Avoid blocking code: Blocking code can slow down the execution of your asynchronous program. Ensure your program is entirely non-blocking by avoiding time-consuming operations or synchronous APIs.

It’s essential to remember that while asynchronous programming has its advantages, it might not always be the best solution. In situations where your tasks are CPU-bound or require a more straightforward processing flow, a synchronous program might be more suitable.

Exploring Asyncio Libraries and APIs

When working with asynchronous programming in Python, it’s essential to explore the available libraries you can use. One such library is aiohttp. It allows you to make asynchronous HTTP requests efficiently using asyncio. You can find more details about this library from the aiohttp documentation.

To get started with aiohttp, you’ll first need to install the library:

pip install aiohttp

In your Python code, you can now import aiohttp and use it with the asyncio library. For example, if you want to make an asynchronous GET request, you can use the following code:

import aiohttp
import asyncio async def fetch_data(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() async def main(): url = 'https://api.example.com/data' data = await fetch_data(url) print(data) await main()

In the example above, the fetch_data function is defined as an async function using the async def syntax. This indicates that this function can be called with the await statement within other asynchronous functions.

The pathlib library provides classes for working with filesystem paths. While it is not directly related to async IO, it can be useful when working with file paths in your async projects. The pathlib.Path class offers a more Pythonic way to handle file system paths, making it easier to manipulate file and directory paths across different operating systems. You can read more about this library in the official Python documentation on pathlib.

When you create async function calls in your code, remember to use the await keyword when calling them. This ensures that the function is executed asynchronously. By combining the power of aiohttp, asyncio, and other async-compatible libraries, you can efficiently perform multiple tasks concurrently in your Python projects.

Understanding Queues and Terminals

With Python’s asyncio module, you can write concurrent, asynchronous code that works efficiently on I/O-bound tasks and network connections. In this context, queues become helpful tools for coordinating the execution of multiple tasks and managing shared resources.

Queues in asyncio are similar to standard Python queues, but they have special asynchronous properties. With coroutine functions such as get() and put(), you can efficiently retrieve an item from the queue or insert an item, respectively. When the queue is empty, the get() function will wait until an item becomes available. This enables smooth flow control and ensures that your async tasks are executed in the most optimal order.

Terminals, on the other hand, are interfaces for interacting with your system – either through command-line or graphical user interfaces. When working with async tasks in Python, terminals play a crucial role in tracking the progress and execution of your tasks. You can use terminals to initiate and monitor the state of your async tasks by entering commands and viewing the output.

When it comes to incorporating multithreaded or asynchronous programming in a parent-child relationship, queues and terminals can come in handy. Consider a scenario where a parent task is responsible for launching multiple child tasks that operate concurrently. In this case, a queue can facilitate the communication and synchronization between parent and child tasks by efficiently passing data to and fro.

Here are a few tips to keep in mind while working with queues and terminals in asynchronous Python programming:

  • Use asyncio.Queue() to create an instance suitable for async tasks, while still maintaining similar functionality as a standard Python queue.
  • For managing timeouts, remember to use the asyncio.wait_for() function in conjunction with queue operations, since the methods of asyncio queues don’t have a built-in timeout parameter.
  • When working with terminals, be mindful of potential concurrency issues. Make sure you avoid race conditions by properly synchronizing your async tasks’ execution using queues, locks, and other synchronization primitives provided by the asyncio module.

Frequently Asked Questions

How does asyncio compare to threading in Python?

Asyncio is a concurrency model that uses a single thread and an event loop to execute tasks concurrently. While threading allows for concurrent execution of tasks using multiple threads, asyncio provides better performance by managing tasks in a non-blocking manner within a single thread. Thus, asyncio is often preferred when dealing with I/O-bound tasks, as it can handle many tasks without creating additional threads.

What are the main components of the asyncio event loop?

The asyncio event loop is responsible for managing asynchronous tasks in Python. Its main components include:

  1. Scheduling tasks: The event loop receives and schedules coroutine functions for execution.
  2. Managing I/O operations: The event loop monitors I/O operations and receives notifications when the operations are complete.
  3. Executing asynchronous tasks: The event loop executes scheduled tasks in a non-blocking manner, allowing other tasks to run concurrently.

How do I use asyncio with pip?

To use asyncio in your Python projects, no additional installation is needed, as it is included in the Python Standard Library from Python version 3.4 onwards. Simply import asyncio in your Python code and make use of its features.

What is the difference between asyncio.run() and run_until_complete()?

asyncio.run() is a newer and more convenient function for running an asynchronous coroutine until it completes. It creates an event loop, runs the passed coroutine, and closes the event loop when the task is finished. run_until_complete() is an older method that requires an existing event loop object on which to run a coroutine.

Here’s an example of how to use asyncio.run():

import asyncio async def example_coroutine(): await asyncio.sleep(1) print("Coroutine has completed") asyncio.run(example_coroutine())

How can I resolve the ‘asyncio.run() cannot be called from a running event loop’ error?

This error occurs when you try to call asyncio.run() inside an already running event loop. Instead of using asyncio.run() in this case, you should use create_task() or gather() functions to schedule your coroutines to run concurrently within the existing loop.

import asyncio async def example_coroutine(): await asyncio.sleep(1) print("Coroutine has completed") async def main(): task = asyncio.create_task(example_coroutine()) await task asyncio.run(main())

Can you provide an example of using async/await in Python?

Here’s a simple example demonstrating the use of async/await in Python:

import asyncio async def async_function(): print("Function starting") await asyncio.sleep(2) print("Function completed") async def main(): await asyncio.gather(async_function(), async_function()) asyncio.run(main())

This example demonstrates two async functions running concurrently. The main() function uses asyncio.gather() to run both async_function() tasks at the same time, and asyncio.run(main()) starts the event loop to execute them.

⚡ Recommended: Can I Run OpenAI’s API in Parallel? Yes, with Python Async!

The post Python Async IO – The Ultimate Guide in a Single Post appeared first on Be on the Right Side of Change.

Posted on Leave a comment

Round Up: The Reviews Are In For Pokémon Scarlet & Violet – The Teal Mask

Pokemon The Teal Mask
Image: The Pokémon Company

The first DLC part of Pokémon Scarlet & Violet – The Hidden Treasure of Area Zero arrived on the Nintendo Switch last week and there have now been multiple reviews shared online, so we’ve put together a roundup.

Here on Nintendo Life, we said The Teal Mask was a fun diversion for Scarlet & Violet players even if there are shortcomings:

So, what did the other outlets think? Starting off with IGN, it said it offered more of the same:

“If you’re considering The Teal Mask because you thought Scarlet and Violet were fun and just want more Pokemon, sure, this DLC will give you that”

The Verge summed it up as a “short, sweet reminder” of Scarlet and Violet’s real potential:

“The Teal Mask doesn’t solve many of Scarlet and Violet’s core gameplay issues, and people hoping for a drastic overhaul are going to be rather disappointed. But playing the DLC at a time when the rumors about a Switch follow-up have shifted into “it’s getting serious” territory is especially fun because you can see clearly how beefier hardware could really work wonders for Scarlet and Violet and how the more Kitikami-like approach to design might be what makes next big Pokémon game shine.”

Kotaku said the new DLC embodies “the best and worst parts” of the base game, but felt much the DLC experience was holding out for the Indigo Disk:

“The final scene of the DLC, which I won’t spoil here, did at least hint that these smaller stories may soon escalate into something larger, and given how well Scarlet and Violet’s writing has handled stories of trauma, especially that felt by children, I’m really eager to see how Game Freak concludes this story in the next download.”

CGMagazine called it a “worthy field trip”:

“Despite its flaws, The Teal Mask DLC, with its fresh setting and intriguing side quests, amplifies the experience of Pokémon Scarlet & Violet—perfect for those seeking a return trip to the vibrant world of Paldea.”

And Digital Trends didn’t mind the first DLC, but felt it didn’t do enough to address the main game’s biggest problems:

“Though those technical deficiencies only get more disappointing with each release, it’s a testament to the power of Pokémon that I still breezed through Teal Mask’s main story in one sitting. There’s an inherent joy in collecting new monsters and watching my Pokédex fill up. Even in the franchise’s weakest moments, that loop I loved as a kid still retains its power here.”


What are your own impressions of the Teal Mask so far? Let us know in the comments.

Posted on Leave a comment

Get Apple’s new USB-C AirPods Pro 2 for $199.99, a $50 discount off retail

Apple may have just released AirPods Pro 2 with a USB-C charging case, but a price war has already erupted, driving the cost down to $199.99.

You can pick up the latest AirPods Pro 2 with USB-C at Amazon.com and BestBuy.com for $199.99, reflecting a $50 price cut off the regular $249 price.

Whether you’re looking for lossless audio for the upcoming Apple Vision Pro or simply want the convenience of a USB-C charging port, this 2023 release matches the $199 price we’ve been seeing on what is now the last-gen model with a Lightning charging port.

If you’re looking for the best Apple Watch deal on the new Ultra 2, Amazon also has a double-digit discount going on, bringing the cost down to $779 on select styles.

More offers saving you money around the web

Best Apple prices

The AppleInsider Price Guide is home to hundreds of additional markdowns on Apple hardware. Here are a few of our favorite picks:

Posted on Leave a comment

What is the Python Dunder Method for the “not and” Operator?

Rate this post

In Python, “dunder” methods, short for “double underscore” methods, are special methods that allow developers to define the behavior of built-in operations for custom objects. For instance, when you use the + operator to add two objects, Python internally calls the __add__ method. Similarly, other operators have their corresponding dunder methods.

However, the term “not and” operator might be a bit misleading, as there isn’t a direct “not and” operator in Python.

Instead, Python provides individual operators for not, and and. But if we delve into the realm of bitwise operations, we find operators that might resemble this behavior: the bitwise NOT (~) and the bitwise AND (&).

Let’s explore the dunder methods associated with these operators.

Bitwise NOT (~) and its Dunder Method __invert__

The bitwise NOT operator flips the bits of a number. For a custom class, if you want to define or override the behavior of the ~ operator, you’d use the __invert__ method.

class BitwiseNumber: def __init__(self, value): self.value = value def __invert__(self): return BitwiseNumber(~self.value) def __repr__(self): return str(self.value) number = BitwiseNumber(5)
print(~number) # Outputs: -6

In the above example, the __invert__ method returns a new BitwiseNumber object with its value inverted.

Bitwise AND (&) and its Dunder Method __and__

The bitwise AND operator performs a bitwise AND operation between two numbers. For custom classes, the behavior of the & operator can be defined or overridden using the __and__ method.

class BitwiseNumber: def __init__(self, value): self.value = value def __and__(self, other): if isinstance(other, BitwiseNumber): return BitwiseNumber(self.value & other.value) return NotImplemented def __repr__(self): return str(self.value) number1 = BitwiseNumber(5) # Binary: 101
number2 = BitwiseNumber(3) # Binary: 011
print(number1 & number2) # Outputs: 1 (Binary: 001)

In this example, the __and__ method checks if the other object is an instance of BitwiseNumber and then performs a bitwise AND operation.

TLDR

While there isn’t a direct “not and” operator in Python, leveraging the __invert__ and __and__ methods, you can define how the bitwise NOT and AND operations work for custom objects, respectively.

The post What is the Python Dunder Method for the “not and” Operator? appeared first on Be on the Right Side of Change.

Posted on Leave a comment

Unity Reveals New Pricing Model For Runtime Fee Policy

Unity Logo
Image: Unity

It only took just over a week for Unity to make changes to its brand new Runtime Fee policy, but that’s exactly what it has done.

Announcing the changes in an open letter published today on the Unity Blog (shared on social media), the revamped model includes some pretty sweeping changes from the initial policy. Unity Create lead Marc Whitten started the letter off with an apology, acknowledging that “We should have spoken with more of you and we should have incorporated more of your feedback” before making last week’s announcement.

Some of the changes sound much easier to stomach than those in the previous policy; Unity Personal subscribers will no longer be charged the fee, and the cap will be increased to $200,000 as opposed to $100,000. Games with less than $1 million in a 12-month revenue will not be subject to the fee, either. And the fee will also only apply to games made in the new version of Unity, which launches in 2024.

Whitten’s statement also mentions that “Your games that are currently shipped and the projects you are currently working on will not be included – unless you choose to upgrade them to this new version of Unity,” meaning that the fees will no longer be applied retroactively to games already on the market and made in Unity.

For games that will be subject to the fees, developers will no longer be charged per install; instead, devs be able to choose to be charged either a 2.5% revenue share or a “calculated amount” which will be based on the number of new people playing the game per month, and that the numbers will be self-reported by the developers themselves.

Of course, this may all sound much better, and the response this time around has been much better, but time will tell if the damage has already been done with Unity’s initial announcement.

The initial policy would have effectively charged developers a fee every time a game built in Unity was installed (depending on the number of installs and sales, etc.) from January 2024. Multiple developers took to social media to call out Unity for the sudden change — which would also have applied retroactively to games already released, and games that are uninstalled and reinstalled.

Unity has issued an updated FAQs page going over the changes — these clarify that the fee would only ever apply on an “initial engagement” and also provide a Runtime Fee Estimator.

What do you think of these announced changes? Share your thoughts down below.

Posted on Leave a comment

Soapbox: FOMO Nearly Ruined Zelda: Tears Of The Kingdom For Me

Keep it away!
Image: Zion Grassl / Nintendo Life

Soapbox features enable our individual writers and contributors to voice their opinions on hot topics and random stuff they’ve been chewing over. Today, Ollie looks back on how Tears of the Kingdom’s launch hype on social media may have affected his own enjoyment of the game…


On May 12th, 2023, I, like countless other Switch owners, booted up The Legend of Zelda: Tears of the Kingdom almost as soon as I was physically able and dove headfirst — quite literally, it seemed — back into the world of Hyrule.

Over the course of the next couple of weeks or so, I was in the midst of a total and all-consuming state of bliss. Finally, I was back in the world that I fell in love with in 2017 and it had changed just enough to keep me compelled and engaged all over again. Yet, as time went on, each play session felt less special, and my enthusiasm for the experience diminished until I stopped playing altogether.

Now, one reason is that I reached what I genuinely consider to be one of the most frustrating and poorly designed dungeons in Zelda history, and getting through this section took a good deal of tongue-biting on my part, but I won’t go into that here. The other reason is that I’d just simply had enough. I couldn’t even look at the game anymore, let alone play it.

Zelda Sky Island
Image: Nintendo

How could this be? This is Tears of the Kingdom, for goodness’ sake — Breath of the Wild 2! After I put it down for what felt like the last time, I could acknowledge its greatness and the significant improvements it made over Breath of the Wild, but I’d just had enough.

So what happened? At the time, I’d probably put in around 25 hours over the course of a few weeks, so it wasn’t like it had completely consumed my every waking moment. I didn’t really have anything else to distract me, either. Yes, I was still playing the Resident Evil 4 remake, but anyone who knows me is aware that this is just weekly business as usual. It was only when I went back to TOTK just a couple of weeks ago and started the game from scratch that I realised what had happened.

While the game itself remained the same, the monumental hype surrounding its release had completely died down. The world had, for the most part, moved on. Tears of the Kingdom dominated social media in the weeks following its release, but platforms were now far more concerned with the likes of Starfield, Mortal Kombat 1, and Cyberpunk 2077 (again).

What I realised was that I simply couldn’t escape Tears of the Kingdom back when it launched, even when I wasn’t playing it. It was everywhere. TV commercials, billboards, internet ads… But mostly, of course, social media. Fans were enamored with the game and were posting about it left, right, and centre. I can’t blame them, of course; there was plenty to talk about, and the game became the hot topic on Nintendo Life (naturally — the clue’s in the name!) for several weeks as everyone on the team gradually peeled back its various layers. But on a personal level, I was drowning in it.

Zelda Simpsons
Image: Nintendo Life

It makes me think back to the times when I was a child; specifically playing The Legend of Zelda: The Wind Waker during those early years of high school. I would get home each day, boot up the game, and just sink into it for a few hours before bedtime. The next morning, my friends and I would gather together on school grounds and discuss what we’d done and what we’d seen. It was glorious, but most of all, it was natural; an intimate talking point amongst friends. Seeing thousands upon thousands of people share their hot takes, videos, and anecdotes online on a daily basis was suffocating by comparison, and thanks to the very nature of the work I do, muting it or deleting social media felt like it was out of the question. What if I missed out on a game-changing update, some zeitgeist-defining Zonai build, or the birth of a meme?!

So when I revisited Tears of the Kingdom after some time away, without the constant background noise that accompanied its launch, I felt revitalised. I spent about three or four hours in the opening Sky Island section just mucking about, free from the pressure I’d put on myself during those first few weeks. No longer was I bogged down by fear of spoilers. No longer was I intimidated by the ridiculous contraptions being showcased online. I could just play the game at my own pace and on my own terms.

Zelda Sleep
Ah, bliss — Image: Nintendo

Needless to say, my enjoyment of Tears of the Kingdom skyrocketed on that second playthrough, and I’ve progressed much further than I managed on my first go around. I still think the temple-which-shall-not-be-named is atrocious given the sheer quality on display elsewhere, but I have to say, I’m otherwise completely in love with the game.

Going completely dark on any major game prior to or during its launch can be a difficult task in this day and age; the pace of new releases and the desire to keep up with the conversation and get involved before social media is crawling with spoilers has instilled a deep sense of FOMO that can be nearly impossible to shake. Yet if you have the means or the willpower to either block out some of the noise or simply wait until it’s died down, then I think you might find your experience to be substantially improved. I certainly did.

Posted on Leave a comment

Random: Masahiro Sakurai Showcases His Rare Gaming Collectibles

Being one of the most recognisable and influential figures in gaming, it should come as no surprise that Masahiro Sakurai has managed to accumulate a number of extremely rare collectibles over the years.

In his latest YouTube ‘Grab Bag’ video, the legendary game designer has taken a bit of a different approach to his usual content, opting to showcase some of the most rare antiques in his collection; items that simply would never become available to the general public thanks to their unique features.

So we’ve got engraved Wii Remotes, autographs from Shigeru Miyamoto, Kid Icarus statues… The lot. There was potential for a video like this to feel a bit pretentious, but it actually comes across as a nice, wholesome bit of viewing. Well, see what you think, anyway.

What do you make of Sakurai’s collection? Have you got any rare items you’d like to show off? Let us know.

Posted on Leave a comment

Super Mario Bros. Wonder Overview Trailer Is A Wonderful Recap Of Game’s New Features

Nintendo has just released a brand new overview trailer for Super Mario Bros. Wonder, which does exactly what it says it’s going to do — go over the game’s features.

Now, if you were around for the Super Mario Bros. Wonder Direct at the beginning of the month, you might be getting a bit of déjà vu here, and that’s because this trailer is pretty much a distilled version of what we got then. There are a few new locations and levels on show, but this is a digested version of what the Direct gave us.

That means we get to see the Flower Kingdom in its full glory, as well as the new power-ups — Elephant, Drill, and Bubble — online modes, and more. The narration is slightly different too. We’ve got a rundown of everything that was shown off in the Direct, and this trailer, below.

Basically, there’s a lot to love, and the trailer serves as a good way to recap what to expect from next month’s 2D Mario adventure. Yep, that’s right, it’s less than a month out now, and it’s shaping up to be another amazing Mario game — if the first hour or so of the game is anything to go by.

Are you excited for Super Mario Bros. Wonder on 20th October? Let us know down below.

Please note that some external links on this page are affiliate links, which means if you click them and make a purchase we may receive a small percentage of the sale. Please read our FTC Disclosure for more information.

Super Mario Bros. Wonder

Super Mario Bros. Wonder

Posted on Leave a comment

Random: Modder Makes Switch OLED Run Cooler As A Dedicated Home Console

We have taken a look at the YouTube channel Macho Nacho Productions a few times on Nintendo Life (remember the world’s smallest GameCube and the ‘thicc boi SP’?), but we were particularly interested in the channel’s latest video, which saw a Switch OLED get converted into a dedicated home console, and run a lot cooler in the process.

In the video (above), Tito from Macho Nacho showcased the TVii Switch Re-Shell Mod (working title), created by a modder called Set. This build converts a standard Switch into a dedicated home console, removing the chance for handheld play, but letting the system run much cooler in the process. The casing even works on Switch consoles without a working display — making handheld play impossible from the get-go.

Perhaps the biggest perk of the mod, however, is its increased cooling capacity. The build packs in an 80mm fan, which helps to keep the motherboard from excessive overheating, even on consoles that are overclocked. Tito’s tests reveal that the casing is capable of reducing an overclocked Switch’s temperatures by anything from 2-8°C, actually allowing it to run cooler than a regular, unmodified console.

The process of putting the mod together actually seems like a relatively straightforward one (as far as mods go, that is). The above video from Macho Nacho provides a full tutorial, demonstrating how to line up all of the Switch’s internal components inside the casing and where to apply the provided screws.

Of course, the build does remove the handheld functionality that many of us rely on for hours of playtime, but the ability to rescue the console from a broken display and keep it cooler in the process makes this mod a pretty neat one. The designer has made the build available for purchase from their Etsy shop which bundles the casing, fans, screws and USB dongle for around £40 — which seems fairly reasonable if you have the technical know-how and required patience.

What do you make of this Switch mod? Let us know in the comments.

Please note that some external links on this page are affiliate links, which means if you click them and make a purchase we may receive a small percentage of the sale. Please read our FTC Disclosure for more information.

Posted on Leave a comment

Wartales Crashes Reported By Players, But A Patch Is Coming Says Dev Team

Wartales
Image: Shiro Unlimited

Open-world tactical RPG Wartales will be getting a patch on Switch “next week (at the latest)!” to fix a number of bugs and issues with the game, developer and publisher Shiro Unlimited announced.

The game was announced and released on Switch during last week’s Nintendo Direct, and while many were initially delighted with the reveal, many have reported major technical issues with the game, including frequent crashes, the inability to pick up or throw out items, and framerate stuttering.

YouTube channel NintendoGalaxy shared around 8 minutes of gameplay from the Switch version, highlighting a few issues, but hopefully, this upcoming patch will address most of these.

Otherwise, the wider reception for the game has been really positive. The Steam version was released earlier in the year and is currently sitting on a Very Positive rating with almost 18,000 reviews.

In Wartales, you are the leader of a group of mercenaries attempting to survive in a world ravaged by plague over a century ago. The game is huge, allowing you to customise your group’s skills, take on mercenary jobs, engage in tactical combat, and gather resources to survive. Cross-play is also reported to be coming soon to the Switch version.

Have you tried Wartales on Switch? Experienced any issues with the port? Let us know in the comments.