ROS2机器人编程简述humble-第二章-Launchers .3.3

news2024/9/30 7:27:27

ROS2机器人编程简述humble-第二章-Publishing and Subscribing .3.2

ros2 run一次只能开启一个node,如果一次开启一组相关node,需要使用ros2 launch。

支持Python, XML, 和 YAML。

推荐Python。

zhangrelay@LAPTOP-5REQ7K1L:~$ ros2 run -h
usage: ros2 run [-h] [--prefix PREFIX] package_name executable_name ...

Run a package specific executable

positional arguments:
  package_name     Name of the ROS package
  executable_name  Name of the executable
  argv             Pass arbitrary arguments to the executable

options:
  -h, --help       show this help message and exit
  --prefix PREFIX  Prefix command, which should go before the executable. Command
                   must be wrapped in quotes if it contains spaces (e.g. --prefix
                   'gdb -ex run --args').
zhangrelay@LAPTOP-5REQ7K1L:~$ ros2 launch -h
usage: ros2 launch [-h] [-n] [-d] [-p | -s] [-a] [--launch-prefix LAUNCH_PREFIX]
                   [--launch-prefix-filter LAUNCH_PREFIX_FILTER]
                   package_name [launch_file_name] [launch_arguments ...]

Run a launch file

positional arguments:
  package_name          Name of the ROS package which contains the launch file
  launch_file_name      Name of the launch file
  launch_arguments      Arguments to the launch file; '<name>:=<value>' (for
                        duplicates, last one wins)

options:
  -h, --help            show this help message and exit
  -n, --noninteractive  Run the launch system non-interactively, with no terminal
                        associated
  -d, --debug           Put the launch system in debug mode, provides more verbose
                        output.
  -p, --print, --print-description
                        Print the launch description to the console without
                        launching it.
  -s, --show-args, --show-arguments
                        Show arguments that may be given to the launch file.
  -a, --show-all-subprocesses-output
                        Show all launched subprocesses' output by overriding their
                        output configuration using the
                        OVERRIDE_LAUNCH_PROCESS_OUTPUT envvar.
  --launch-prefix LAUNCH_PREFIX
                        Prefix command, which should go before all executables.
                        Command must be wrapped in quotes if it contains spaces
                        (e.g. --launch-prefix 'xterm -e gdb -ex run --args').
  --launch-prefix-filter LAUNCH_PREFIX_FILTER
                        Regex pattern for filtering which executables the --launch-
                        prefix is applied to by matching the executable name.
zhangrelay@LAPTOP-5REQ7K1L:~$

启动程序launch文件是用Python编写的,其功能是声明使用哪些选项或参数执行哪些程序。一个启动器可以包含另一个启动器,允许重用现有的启动器。之所以需要launch,是因为一个机器人应用程序有许多节点,它们都应该同时启动。逐个启动并调整每个节点的特定参数,使节点进行协作可能会很乏味。

软件包的启动器位于软件包的启动launch目录中,其名称通常以launch.py结尾。正如ros2运行时使用软件包中可用的程序一样,ros2启动时也使用可用的启动器。从实现的角度来看,启动器是一个python程序,它包含一个返回LaunchDescription对象的generatelaunchdescription()函数。

LaunchDescription对象包含操作,其中突出显示:

•node操作:运行程序。

•IncludeLaunchDescription操作:包括另一个启动器。

•DeclareLaunchArgument操作:声明启动器参数。

•SetEnvironmentVariable操作:设置环境变量。

书中案例如下:

v1

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():

    pub_cmd = Node(
        package='br2_basics',
        executable='publisher',
        output='screen'
    )
    sub_cmd = Node(
        package='br2_basics',
        executable='subscriber_class',
        output='screen'
    )

    ld = LaunchDescription()
    ld.add_action(pub_cmd)
    ld.add_action(sub_cmd)

    return ld

v2

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():

    return LaunchDescription([
        Node(
            package='br2_basics',
            executable='publisher',
            output='screen'
        ),
        Node(
            package='br2_basics',
            executable='subscriber_class',
            output='screen'
        )
    ])

CMakeLists

install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})

效果如:

ros2 launch br2_basics pub_sub_v2_launch.py
[INFO] [launch]: All log files can be found below /home/zhangrelay/.ros/log/2023-01-20-09-06-49-444970-LAPTOP-5REQ7K1L-7896
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [7897]
[INFO] [subscriber_class-2]: process started with pid [7899]
[subscriber_class-2] [INFO] [1674176809.694879700] [subscriber_node]: Hello 1
[subscriber_class-2] [INFO] [1674176810.195718900] [subscriber_node]: Hello 2
[subscriber_class-2] [INFO] [1674176810.695717000] [subscriber_node]: Hello 3
[subscriber_class-2] [INFO] [1674176811.195712100] [subscriber_node]: Hello 4
[subscriber_class-2] [INFO] [1674176811.695709800] [subscriber_node]: Hello 5
[subscriber_class-2] [INFO] [1674176812.195715200] [subscriber_node]: Hello 6
[subscriber_class-2] [INFO] [1674176812.695273100] [subscriber_node]: Hello 7
[subscriber_class-2] [INFO] [1674176813.195665000] [subscriber_node]: Hello 8
[subscriber_class-2] [INFO] [1674176813.695714300] [subscriber_node]: Hello 9
[subscriber_class-2] [INFO] [1674176814.195719200] [subscriber_node]: Hello 10
[subscriber_class-2] [INFO] [1674176814.695718400] [subscriber_node]: Hello 11
[subscriber_class-2] [INFO] [1674176815.195444600] [subscriber_node]: Hello 12
[subscriber_class-2] [INFO] [1674176815.695682400] [subscriber_node]: Hello 13
[subscriber_class-2] [INFO] [1674176816.195665100] [subscriber_node]: Hello 14
[subscriber_class-2] [INFO] [1674176816.695713200] [subscriber_node]: Hello 15
[subscriber_class-2] [INFO] [1674176817.195601200] [subscriber_node]: Hello 16
[subscriber_class-2] [INFO] [1674176817.695310700] [subscriber_node]: Hello 17
[subscriber_class-2] [INFO] [1674176818.195711000] [subscriber_node]: Hello 18
[subscriber_class-2] [INFO] [1674176818.695683800] [subscriber_node]: Hello 19
[subscriber_class-2] [INFO] [1674176819.195543700] [subscriber_node]: Hello 20
[subscriber_class-2] [INFO] [1674176819.695711400] [subscriber_node]: Hello 21
[subscriber_class-2] [INFO] [1674176820.195726000] [subscriber_node]: Hello 22
[subscriber_class-2] [INFO] [1674176820.695710100] [subscriber_node]: Hello 23
[subscriber_class-2] [INFO] [1674176821.195487200] [subscriber_node]: Hello 24
[subscriber_class-2] [INFO] [1674176821.695709500] [subscriber_node]: Hello 25
[subscriber_class-2] [INFO] [1674176822.195718500] [subscriber_node]: Hello 26
[subscriber_class-2] [INFO] [1674176822.695726100] [subscriber_node]: Hello 27
[subscriber_class-2] [INFO] [1674176823.195704600] [subscriber_node]: Hello 28
[subscriber_class-2] [INFO] [1674176823.695726800] [subscriber_node]: Hello 29
[subscriber_class-2] [INFO] [1674176824.195718300] [subscriber_node]: Hello 30
[subscriber_class-2] [INFO] [1674176824.695579500] [subscriber_node]: Hello 31
[subscriber_class-2] [INFO] [1674176825.195705000] [subscriber_node]: Hello 32
[subscriber_class-2] [INFO] [1674176825.695724000] [subscriber_node]: Hello 33
[subscriber_class-2] [INFO] [1674176826.195724200] [subscriber_node]: Hello 34
[subscriber_class-2] [INFO] [1674176826.695665700] [subscriber_node]: Hello 35

更多内容参考官网:

launch

ROS 2启动文件允许同时启动和配置包含ROS 2节点的多个可执行文件。

1 创建启动文件

了解如何创建一个启动文件,该文件将同时启动节点及其配置。

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='turtlesim',
            namespace='turtlesim1',
            executable='turtlesim_node',
            name='sim'
        ),
        Node(
            package='turtlesim',
            namespace='turtlesim2',
            executable='turtlesim_node',
            name='sim'
        ),
        Node(
            package='turtlesim',
            executable='mimic',
            name='mimic',
            remappings=[
                ('/input/pose', '/turtlesim1/turtle1/pose'),
                ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),
            ]
        )
    ])

package

<exec_depend>ros2launch</exec_depend>

2 启动和监视多个节点

获取有关启动文件工作方式的更高级概述。

import launch
import launch_ros.actions

def generate_launch_description():
    return launch.LaunchDescription([
        launch_ros.actions.Node(
            package='demo_nodes_cpp',
            executable='talker',
            name='talker'),
  ])

3 使用替换

在描述可重用的启动文件时,使用替换来提供更大的灵活性。

from launch_ros.actions import Node

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, ExecuteProcess, TimerAction
from launch.conditions import IfCondition
from launch.substitutions import LaunchConfiguration, PythonExpression


def generate_launch_description():
    turtlesim_ns = LaunchConfiguration('turtlesim_ns')
    use_provided_red = LaunchConfiguration('use_provided_red')
    new_background_r = LaunchConfiguration('new_background_r')

    turtlesim_ns_launch_arg = DeclareLaunchArgument(
        'turtlesim_ns',
        default_value='turtlesim1'
    )
    use_provided_red_launch_arg = DeclareLaunchArgument(
        'use_provided_red',
        default_value='False'
    )
    new_background_r_launch_arg = DeclareLaunchArgument(
        'new_background_r',
        default_value='200'
    )

    turtlesim_node = Node(
        package='turtlesim',
        namespace=turtlesim_ns,
        executable='turtlesim_node',
        name='sim'
    )
    spawn_turtle = ExecuteProcess(
        cmd=[[
            'ros2 service call ',
            turtlesim_ns,
            '/spawn ',
            'turtlesim/srv/Spawn ',
            '"{x: 2, y: 2, theta: 0.2}"'
        ]],
        shell=True
    )
    change_background_r = ExecuteProcess(
        cmd=[[
            'ros2 param set ',
            turtlesim_ns,
            '/sim background_r ',
            '120'
        ]],
        shell=True
    )
    change_background_r_conditioned = ExecuteProcess(
        condition=IfCondition(
            PythonExpression([
                new_background_r,
                ' == 200',
                ' and ',
                use_provided_red
            ])
        ),
        cmd=[[
            'ros2 param set ',
            turtlesim_ns,
            '/sim background_r ',
            new_background_r
        ]],
        shell=True
    )

    return LaunchDescription([
        turtlesim_ns_launch_arg,
        use_provided_red_launch_arg,
        new_background_r_launch_arg,
        turtlesim_node,
        spawn_turtle,
        change_background_r,
        TimerAction(
            period=2.0,
            actions=[change_background_r_conditioned],
        )
    ])

4 使用事件处理程序

使用事件处理程序监视进程的状态或定义一组复杂的规则,这些规则可用于动态修改启动文件。

from launch_ros.actions import Node

from launch import LaunchDescription
from launch.actions import (DeclareLaunchArgument, EmitEvent, ExecuteProcess,
                            LogInfo, RegisterEventHandler, TimerAction)
from launch.conditions import IfCondition
from launch.event_handlers import (OnExecutionComplete, OnProcessExit,
                                OnProcessIO, OnProcessStart, OnShutdown)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable, FindExecutable,
                                LaunchConfiguration, LocalSubstitution,
                                PythonExpression)


def generate_launch_description():
    turtlesim_ns = LaunchConfiguration('turtlesim_ns')
    use_provided_red = LaunchConfiguration('use_provided_red')
    new_background_r = LaunchConfiguration('new_background_r')

    turtlesim_ns_launch_arg = DeclareLaunchArgument(
        'turtlesim_ns',
        default_value='turtlesim1'
    )
    use_provided_red_launch_arg = DeclareLaunchArgument(
        'use_provided_red',
        default_value='False'
    )
    new_background_r_launch_arg = DeclareLaunchArgument(
        'new_background_r',
        default_value='200'
    )

    turtlesim_node = Node(
        package='turtlesim',
        namespace=turtlesim_ns,
        executable='turtlesim_node',
        name='sim'
    )
    spawn_turtle = ExecuteProcess(
        cmd=[[
            FindExecutable(name='ros2'),
            ' service call ',
            turtlesim_ns,
            '/spawn ',
            'turtlesim/srv/Spawn ',
            '"{x: 2, y: 2, theta: 0.2}"'
        ]],
        shell=True
    )
    change_background_r = ExecuteProcess(
        cmd=[[
            FindExecutable(name='ros2'),
            ' param set ',
            turtlesim_ns,
            '/sim background_r ',
            '120'
        ]],
        shell=True
    )
    change_background_r_conditioned = ExecuteProcess(
        condition=IfCondition(
            PythonExpression([
                new_background_r,
                ' == 200',
                ' and ',
                use_provided_red
            ])
        ),
        cmd=[[
            FindExecutable(name='ros2'),
            ' param set ',
            turtlesim_ns,
            '/sim background_r ',
            new_background_r
        ]],
        shell=True
    )

    return LaunchDescription([
        turtlesim_ns_launch_arg,
        use_provided_red_launch_arg,
        new_background_r_launch_arg,
        turtlesim_node,
        RegisterEventHandler(
            OnProcessStart(
                target_action=turtlesim_node,
                on_start=[
                    LogInfo(msg='Turtlesim started, spawning turtle'),
                    spawn_turtle
                ]
            )
        ),
        RegisterEventHandler(
            OnProcessIO(
                target_action=spawn_turtle,
                on_stdout=lambda event: LogInfo(
                    msg='Spawn request says "{}"'.format(
                        event.text.decode().strip())
                )
            )
        ),
        RegisterEventHandler(
            OnExecutionComplete(
                target_action=spawn_turtle,
                on_completion=[
                    LogInfo(msg='Spawn finished'),
                    change_background_r,
                    TimerAction(
                        period=2.0,
                        actions=[change_background_r_conditioned],
                    )
                ]
            )
        ),
        RegisterEventHandler(
            OnProcessExit(
                target_action=turtlesim_node,
                on_exit=[
                    LogInfo(msg=(EnvironmentVariable(name='USER'),
                            ' closed the turtlesim window')),
                    EmitEvent(event=Shutdown(
                        reason='Window closed'))
                ]
            )
        ),
        RegisterEventHandler(
            OnShutdown(
                on_shutdown=[LogInfo(
                    msg=['Launch was asked to shutdown: ',
                        LocalSubstitution('event.reason')]
                )]
            )
        ),
    ])

5 管理大型项目

为大型项目构建启动文件,以便在不同的情况下尽可能地重用它们。请参阅不同启动工具的用法示例,如参数、YAML文件、重映射、名称空间、默认参数和RViz配置。

import os

from ament_index_python.packages import get_package_share_directory

from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource


def generate_launch_description():
   turtlesim_world_1 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_1.launch.py'])
      )
   turtlesim_world_2 = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_world_2.launch.py'])
      )
   broadcaster_listener_nodes = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/broadcaster_listener.launch.py']),
      launch_arguments={'target_frame': 'carrot1'}.items(),
      )
   mimic_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/mimic.launch.py'])
      )
   fixed_frame_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/fixed_broadcaster.launch.py'])
      )
   rviz_node = IncludeLaunchDescription(
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('launch_tutorial'), 'launch'),
         '/turtlesim_rviz.launch.py'])
      )

   return LaunchDescription([
      turtlesim_world_1,
      turtlesim_world_2,
      broadcaster_listener_nodes,
      mimic_node,
      fixed_frame_node,
      rviz_node
   ])

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/174207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mysql之约束

简介 not null前面也说过&#xff0c;这些约束是针对列的数据的&#xff0c;对应整个列的数据都起约束作用 基本但是创建表在字段后使用的语句 1.primary key-主键 主键特征1.对应列不能有重复的数据2.不能为NULL 唯一且非空 -- 主键 -- id,name,email CREATE TABLE t17(id…

AX7A200教程(1): DDR3仿真平台搭建(一)

本章节主要调用官方的MIG控制器&#xff0c;并使用官方的MIG控制器进行仿真&#xff0c;开发环境vivado2020.1鉴于很多童鞋无法仿真自己新建的DDR工程&#xff0c;即使使用modelsim仿真也仿真失败&#xff0c;本例程着重于在vivado中&#xff0c;对自己新建的带DDR3的工程进行仿…

力扣45.跳跃游戏Ⅱ(贪心思路详解)

文章目录力扣45.跳跃游戏Ⅱ题目描述算法思路代码实现力扣45.跳跃游戏Ⅱ 题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意…

114、【树与二叉树】leetcode ——77. 组合:回溯法+剪枝优化(C++版本)

题目描述 原题链接&#xff1a;77. 组合 解题思路 组合问题是回溯法里的经典问题&#xff0c;分别采用两个全局变量path记录当前组合情况&#xff0c;res作为结果集。每次因为结果集需要去重&#xff0c;因此还需要再设置一个局部变量startIndex作为每次遍历的起始值&#xf…

Linux kernel Memory Pin机制的实现以及测试

提起Memory Pin机制&#xff0c;就不得不提到swap的概念&#xff0c;这两个概念息息相关&#xff0c;为了避免在CPU忙碌的时候&#xff0c;也就是在缺页异常发生的时候&#xff0c;临时搜索可供换出的内存页面并加以换出&#xff0c;Linux内核定期地检查系统的空闲页面数量是否…

九大数据分析方法-单指标分析方法与多指标分析方法

文章目录1 单指标分析方法1.1 周期性分析法1.2 结构分析法1.3 分层分析法2 多指标分析方法2.1 矩阵分析法2.2 指标拆解法本文来源&#xff0c;为接地气的陈老师的知识星球&#xff0c;以及付同学的观看笔记。1 单指标分析方法 顾名思义&#xff0c;用单个数据指标进行数据分析…

RocketMQ 简介

一、简介 官方简介&#xff1a; l RocketMQ是一款分布式、队列模型的消息中间件&#xff0c;具有以下特点&#xff1a; l 能够保证严格的消息顺序 l 提供丰富的消息拉取模式 l 高效的订阅者水平扩展能力 l 实时的消息订阅机制 l 亿级消息堆积能力 二、网络架构 三、特性 1. na…

05-jquery基本过滤器

2.5过滤器 过滤器是一个字符串&#xff0c;用了筛选dom对象&#xff0c;过滤器是和选择器一起使用。在选择dom对象后&#xff0c;再进行过滤筛选。 .5.1基本过滤器 使用dom对象在数组中的位置&#xff0c;作为过滤条件。 1 选择数组中第一个dom成员。 语法&#xff1a;$(“选…

【nginx】全面实战-Mac

▒ 目录 ▒&#x1f6eb; 导读需求1️⃣ 安装brew替换为阿里源修复报错No such file or directorybrew install nginx常用文件及目录常用命令2️⃣ nginx配置配置结构3️⃣ web服务器默认服务器自定义静态服务器4️⃣ 反向代理配置及介绍5️⃣ 负载均衡配置及介绍负载均衡的策略…

杂记:python和pyinstaller从头安装步骤(附安装包的备份)

pyinstaller 简介 知道的就跳过本章 python 属于脚本语言&#xff0c;只要有 python 就能运行 .py 文件。而 pyinstaller 是可执行文件文件生成工具&#xff0c;约等于编译工具。 以 windows 为例&#xff0c;在 A 计算机上生成的 exe&#xff0c;复制到 B 计算机可以直接运行…

字节跳动青训营--前端day3

文章目录前言一、写好JavaScript的一些原则二、各司其职三、组件封装四、过程抽象前言 仅以此文章记录学习 一、写好JavaScript的一些原则 各司其职&#xff1a;让HTML、CSS和JavaScript职能分离组件封装&#xff1a;好的UI组件具备正确性、扩展性、复用性过程抽象&#xff1…

JVM堆内存分配策略(深入理解Java虚拟机第三章)

堆内存模型&#xff1a; 年轻代&#xff1a; 根据分代算法&#xff0c;默认小于15岁的对象称作年轻代&#xff0c;年轻代分为Eden区、幸存者区(Survivor Form&#xff0c;Survivor To),三者比例为&#xff1a;8&#xff1a;1&#xff1a;1 Eden 分区&#xff1a;对象出生分区…

Java 实现几种 异步的实现方式

前言 异步执行对于开发者来说并不陌生&#xff0c;在实际的开发过程中&#xff0c;很多场景多会使用到异步&#xff0c;相比同步执行&#xff0c;异步可以大大缩短请求链路耗时时间&#xff0c;比如&#xff1a;发送短信、邮件、异步更新等&#xff0c;这些都是典型的可以通过…

FL水果21最新版本电脑编曲软件FL Studio更新

电脑编曲软件也就是我们常说的宿主软件&#xff0c;英文简称DAW。 FL Studio俗称水果&#xff0c;是一款开发初衷为了电子音乐制作的宿主软件。内置了非常多优秀的合成器以及效果器插件&#xff0c;极为适合于电子音乐的编排。同时FL Studio支持第三方音源插件导入&#xff0c;…

[RootersCTF2019]ImgXweb

目录 信息收集 JWT伪造 工具使用 寻找秘钥 curl 补充知识 信息收集 进入查看源码未发现重要信息 注册admin失败&#xff0c;猜测应该需要垂直越权 先随意注册个账户coleak 登录后发现可以文件上传&#xff0c;上传shell后发现不能连接&#xff0c;执行命令也没有回显…

Linux常用命令——tailf命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tailf 在屏幕上显示指定文件的末尾若干行内容&#xff0c;通常用于日志文件的跟踪输出。 补充说明 tailf命令几乎等同于tail -f&#xff0c;严格说来应该与tail --followname更相似些。当文件改名之后它也能继…

微服务注册中心-Eureka

微服务注册中心-Eureka微服务注册中心-Eureka一、注册中心&#xff08;Eureka&#xff09;二、Euraka实现1.eureka-server端&#xff08;1&#xff09;新建项目&#xff0c;引入pom依赖&#xff08;2&#xff09;编写启动类&#xff0c;引入开启注册中心的注解&#xff08;3&am…

py第八章 面向对象 笔记

8.1类与对象的基础运用类是抽象的&#xff0c;对象是类的实例8.1.1类的定义class 类名:属性名属性值def 方法名(self):方法体类名:大驼峰命名法&#xff1a;首字母一般为大写方法参数列表中的第一个参数是一个指代对象的默认参数selfclass Car:wheels4def drive(self):print(行…

非常好用,绝对未来黑马的 Python 开源测试框架 PySimpleTest

非常好用&#xff0c;绝对未来黑马的 Python 开源测试框架 PySimpleTest 简单介绍 PySimpleTest 是一个非常简洁开源的python代码测试框架&#xff0c;作者是 https://github.com/Time-Coder?tabstars 你可以在以下网站找到PyPI索引&#xff1a;https://pypi.org/project/P…

CSS 排行榜

CSS 排行榜 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>排行榜</title><style type"text/css">* {margin: 0;padding: 0;}/* 容器 */.rank-container {width: 400px;color: #333;font-size: 14p…