Of course! This is a fundamental concept in ROS. Launching a Python script (or any executable) using a ros2 launch file is the standard way to orchestrate multiple nodes and configure parameters for your robot or application.

Here’s a comprehensive guide covering everything from the basics to best practices.
The Core Concept: launch_ros Nodes
In ROS 2, you don't directly "launch" a Python script. Instead, you define a node within a launch file. A node is an isolated process that performs some computation. The launch_ros package provides the tools to describe ROS 2 nodes.
The key component is the Node action.
from launch_ros.actions import Node
This Node action tells the ROS 2 launch system how to start your executable (your Python script).

Step-by-Step: Creating a Launch File for a Python Script
Let's walk through a complete example.
Step 1: Create Your Python Node
First, you need a simple Python script that can be run as a ROS 2 node. It must use the rclpy library.
File: my_python_pkg/scripts/my_talker.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.count = 0
self.get_logger().info('Talker node has been started.')
def timer_callback(self):
msg = String()
msg.data = f'Hello, World! {self.count}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.count += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
try:
rclpy.spin(minimal_publisher)
except KeyboardInterrupt:
pass
finally:
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Important:

- Make this file executable:
chmod +x my_python_pkg/scripts/my_talker.py - The
#!/usr/bin/env python3shebang line is good practice. - The node must be importable. Make sure your package is properly built with
colcon build.
Step 2: Create the Launch File
Now, create a Python launch file in the launch directory of your package.
File: my_python_pkg/launch/my_launch_file.py
# Import necessary modules
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
"""
This function is called by the launch system to generate the launch description.
It must return a LaunchDescription object.
"""
# Define the action for our Python talker node
# The 'executable' is the name of the script file.
# The 'package' is the name of the package where the script is located.
talker_node = Node(
package='my_python_pkg',
executable='my_talker',
name='custom_talker_name' # Optional: overrides the node name from the script
)
# Return the LaunchDescription object containing the node
return LaunchDescription([
talker_node
])
Step 3: Declare the Executable in package.xml
ROS 2 needs to know that my_talker.py is an executable. You do this in your package.xml.
File: my_python_pkg/package.xml
Add this line inside the <package> tag:
<exec_depend>my_python_pkg</exec_depend> <!-- This should already be there for your Python code --> <!-- Add this line to declare the executable --> <exec_depend>my_python_pkg</exec_depend>
Note: In ROS 2, the exec_depend on your own package is often necessary to ensure all components, including executables, are correctly recognized and installed by the build system (ament).
Step 4: Build Your Package
Navigate to your workspace's root directory and build:
colcon build --packages-select my_python_pkg
Step 5: Run the Launch File
You can now launch your Python node using the ros2 launch command.
# Navigate to your workspace root cd ~/ros2_ws # Run the launch file ros2 launch my_python_pkg my_launch_file.py
You should see output from your my_talker.py script, publishing messages to the /topic every 0.5 seconds.
Key Parameters of the Node Action
The Node action is very flexible. Here are the most common parameters you'll use:
| Parameter | Description | Example |
|---|---|---|
package |
(Required) The name of the package where the executable is located. | package='my_python_pkg' |
executable |
(Required) The name of the executable file (without the .py extension if you use the shebang). |
executable='my_talker' |
name |
The name of the ROS 2 node. This is the name that will appear in ros2 node list. If not provided, it's usually the executable name. |
name='my_super_talker' |
output |
Where to redirect the stdout/stderr of the node. 'screen' (default) prints to your terminal. 'log' sends it to the log files. |
output='screen' |
parameters |
A dictionary or list of dictionaries to pass parameters to the node. | parameters=[{'my_param': 123}] |
arguments |
A list of command-line arguments to pass directly to the executable. | arguments=['--ros-args', '--log-level', 'info'] |
remappings |
A list of tuples to remap topics/services. | remappings=[('topic', 'my_custom_topic')] |
Advanced Example: Launching Multiple Nodes
A launch file's real power is launching multiple nodes and configuring them together.
Let's add a simple C++ or Python listener node and launch both.
Modified Launch File: my_python_pkg/launch/my_launch_file.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
# Node 1: Our Python talker
talker_node = Node(
package='my_python_pkg',
executable='my_talker',
name='talker',
output='screen'
)
# Node 2: A listener node (let's assume it's in the 'demo_nodes_py' package)
listener_node = Node(
package='demo_nodes_py',
executable='listener',
name='listener',
output='screen'
)
# Return a list of all nodes to be launched
return LaunchDescription([
talker_node,
listener_node
])
Now, when you run ros2 launch my_python_pkg my_launch_file.py, you will see the output from both the talker and the listener in the same terminal. The listener will automatically subscribe to the topic published by the talker.
Best Practices
-
Use
launch_ros.actions.Node: Always use this action for ROS 2 nodes. It handles the complexities of starting a node within the ROS 2 environment. -
Separate Concerns: Keep your launch logic in
.pyfiles and your application logic in your source code (scripts/,src/). -
Use
launch.substitutions: For making your launch files more flexible and reusable. For example, to get the current user's name:from launch.actions import DeclareLaunchArgument from launch.substitutions import LaunchConfiguration, PythonExpression # In generate_launch_description(): user_name_arg = DeclareLaunchArgument( 'user_name', default_value='default_user' ) talker_node = Node( # ... parameters=[{ 'greeting': PythonExpression(['"Hello from ", user_name, "!"]')], 'user_name': LaunchConfiguration('user_name') }] ) return LaunchDescription([ user_name_arg, talker_node ]) # To run with a custom user name: # ros2 launch my_python_pkg my_launch_file.py user_name:=Alice -
Organize with
IncludeLaunchDescription: For complex systems, break your launch files into smaller, reusable parts and include them usingIncludeLaunchDescription. This is the ROS 2 equivalent of the old<include>tag in ROS 1.
