用Python编写自己的微型Redis

news2024/11/18 17:25:04

前几天我想到,写一个简单的东西会很整洁 雷迪斯-像数据库服务器。虽然我有很多 WSGI应用程序的经验,数据库服务器展示了一种新颖 挑战,并被证明是学习如何工作的不错的实际方法 Python中的套接字。在这篇文章中,我将分享我在此过程中学到的知识。

我项目的目的是 编写一个简单的服务器 我可以用 我的任务队列项目称为 Huey。 Huey使用Redis作为默认存储引擎来跟踪被引用的工作, 完成的工作和其他结果。就本职位而言, 我进一步缩小了原始项目的范围,以免造成混乱 使用代码的水域,您可以很容易地自己写,但是如果您 很好奇,你可以看看 最终结果 这里 (文件)。

我们将要构建的服务器将能够响应以下命令:

  • GET <key>

  • SET <key> <value>

  • DELETE <key>

  • FLUSH

  • MGET <key1> ... <keyn>

  • MSET <key1> <value1> ... <keyn> <valuen>

我们还将支持以下数据类型:

  • Strings and Binary Data

  • Numbers

  • NULL

  • Arrays (which may be nested)

  • Dictionaries (which may be nested)

  • Error messages

为了异步处理多个客户端,我们将使用 gevent, 但是您也可以使用标准库的 SocketServer 模块与 要么 ForkingMixin 或 ThreadingMixin

骨架

让我们为服务器设置一个框架。我们需要服务器本身,以及 新客户端连接时要执行的回调。另外,我们需要 某种逻辑来处理客户端请求并发送响应。

这是一个开始:

from gevent import socket
from gevent.pool import Pool
from gevent.server import StreamServer

from collections import namedtuple
from io import BytesIO
from socket import error as socket_error


# We'll use exceptions to notify the connection-handling loop of problems.
class CommandError(Exception): pass
class Disconnect(Exception): pass

Error = namedtuple('Error', ('message',))


class ProtocolHandler(object):
    def handle_request(self, socket_file):
        # Parse a request from the client into it's component parts.
        pass

    def write_response(self, socket_file, data):
        # Serialize the response data and send it to the client.
        pass


class Server(object):
    def __init__(self, host='127.0.0.1', port=31337, max_clients=64):
        self._pool = Pool(max_clients)
        self._server = StreamServer(
            (host, port),
            self.connection_handler,
            spawn=self._pool)

        self._protocol = ProtocolHandler()
        self._kv = {}

    def connection_handler(self, conn, address):
        # Convert "conn" (a socket object) into a file-like object.
        socket_file = conn.makefile('rwb')

        # Process client requests until client disconnects.
        while True:
            try:
                data = self._protocol.handle_request(socket_file)
            except Disconnect:
                break

            try:
                resp = self.get_response(data)
            except CommandError as exc:
                resp = Error(exc.args[0])

            self._protocol.write_response(socket_file, resp)

    def get_response(self, data):
        # Here we'll actually unpack the data sent by the client, execute the
        # command they specified, and pass back the return value.
        pass

    def run(self):
        self._server.serve_forever()

希望以上代码相当清楚。我们已经分开了担忧,以便 协议处理属于自己的类,有两种公共方法: handle_request 和 write_response。服务器本身使用协议 处理程序解压缩客户端请求并将服务器响应序列化回 客户。The get_response() 该方法将用于执行命令 由客户发起。

仔细查看代码 connection_handler() 方法,你可以 看到我们在套接字对象周围获得了类似文件的包装纸。这个包装器 让我们抽象一些 怪癖 通常会遇到使用原始插座的情况。函数输入 无穷循环,读取客户端的请求,发送响应,最后 客户端断开连接时退出循环(由 read() 返回 一个空字符串)。

我们使用键入的异常来处理客户端断开连接并通知用户 错误处理命令。例如,如果用户做错了 对服务器的格式化请求,我们将提出一个 CommandError, 哪个是 序列化为错误响应并发送给客户端。

在继续之前,让我们讨论客户端和服务器将如何通信。

线程

我面临的第一个挑战是如何处理通过 线。我在网上找到的大多数示例都是毫无意义的回声服务器,它们进行了转换 套接字到类似文件的对象,并且刚刚调用 readline()。如果我想 用新线存储一些腌制的数据或字符串,我需要一些 一种序列化格式。

在浪费时间尝试发明合适的东西之后,我决定阅读 有关文档 Redis协议, 其中 事实证明实施起来非常简单,并且具有 支持几种不同的数据类型。

Redis协议使用请求/响应通信模式与 客户。来自服务器的响应将使用第一个字节来指示 数据类型,然后是数据,以回车/线路进给终止。

数据类型.png

让我们填写协议处理程序的类,使其实现Redis 协议。

class ProtocolHandler(object):
    def __init__(self):
        self.handlers = {
            '+': self.handle_simple_string,
            '-': self.handle_error,
            ':': self.handle_integer,
            '$': self.handle_string,
            '*': self.handle_array,
            '%': self.handle_dict}

    def handle_request(self, socket_file):
        first_byte = socket_file.read(1)
        if not first_byte:
            raise Disconnect()

        try:
            # Delegate to the appropriate handler based on the first byte.
            return self.handlers[first_byte](socket_file)
        except KeyError:
            raise CommandError('bad request')

    def handle_simple_string(self, socket_file):
        return socket_file.readline().rstrip('\r\n')

    def handle_error(self, socket_file):
        return Error(socket_file.readline().rstrip('\r\n'))

    def handle_integer(self, socket_file):
        return int(socket_file.readline().rstrip('\r\n'))

    def handle_string(self, socket_file):
        # First read the length ($<length>\r\n).
        length = int(socket_file.readline().rstrip('\r\n'))
        if length == -1:
            return None  # Special-case for NULLs.
        length += 2  # Include the trailing \r\n in count.
        return socket_file.read(length)[:-2]

    def handle_array(self, socket_file):
        num_elements = int(socket_file.readline().rstrip('\r\n'))
        return [self.handle_request(socket_file) for _ in range(num_elements)]

    def handle_dict(self, socket_file):
        num_items = int(socket_file.readline().rstrip('\r\n'))
        elements = [self.handle_request(socket_file)
                    for _ in range(num_items * 2)]
        return dict(zip(elements[::2], elements[1::2]))

对于协议的序列化方面,我们将执行与上述相反的操作: 将Python对象转换为序列化的对象!

class ProtocolHandler(object):
    # ... above methods omitted ...
    def write_response(self, socket_file, data):
        buf = BytesIO()
        self._write(buf, data)
        buf.seek(0)
        socket_file.write(buf.getvalue())
        socket_file.flush()

    def _write(self, buf, data):
        if isinstance(data, str):
            data = data.encode('utf-8')

        if isinstance(data, bytes):
            buf.write('$%s\r\n%s\r\n' % (len(data), data))
        elif isinstance(data, int):
            buf.write(':%s\r\n' % data)
        elif isinstance(data, Error):
            buf.write('-%s\r\n' % error.message)
        elif isinstance(data, (list, tuple)):
            buf.write('*%s\r\n' % len(data))
            for item in data:
                self._write(buf, item)
        elif isinstance(data, dict):
            buf.write('%%%s\r\n' % len(data))
            for key in data:
                self._write(buf, key)
                self._write(buf, data[key])
        elif data is None:
            buf.write('$-1\r\n')
        else:
            raise CommandError('unrecognized type: %s' % type(data))

将协议处理保持在其自己的类中的另一个好处是 我们可以重复使用 handle_request 和 write_response 建立方法 客户端库。

执行命令

Server 我们模拟的课程现在需要 get_response() 方法 已实施。命令将假定由客户端以简单方式发送 字符串或命令参数数组,因此 data 传递给 get_response() 将是字节或列表。为了简化处理,如果 data 这是一个简单的字符串,我们将通过拆分将其转换为列表 空格。

第一个参数将是命令名称,并带有任何其他参数 属于指定命令。就像我们对第一个的映射一样 字节给处理者 ProtocolHandler, 让我们创建一个的映射 命令回调 Server:

class Server(object):
    def __init__(self, host='127.0.0.1', port=31337, max_clients=64):
        self._pool = Pool(max_clients)
        self._server = StreamServer(
            (host, port),
            self.connection_handler,
            spawn=self._pool)

        self._protocol = ProtocolHandler()
        self._kv = {}

        self._commands = self.get_commands()

    def get_commands(self):
        return {
            'GET': self.get,
            'SET': self.set,
            'DELETE': self.delete,
            'FLUSH': self.flush,
            'MGET': self.mget,
            'MSET': self.mset}

    def get_response(self, data):
        if not isinstance(data, list):
            try:
                data = data.split()
            except:
                raise CommandError('Request must be list or simple string.')

        if not data:
            raise CommandError('Missing command')

        command = data[0].upper()
        if command not in self._commands:
            raise CommandError('Unrecognized command: %s' % command)

        return self._commands[command](*data[1:])

我们的服务器快完成了! 我们只需要执行六个命令 在 get_commands() 方法:

class Server(object):
    def get(self, key):
        return self._kv.get(key)

    def set(self, key, value):
        self._kv[key] = value
        return 1

    def delete(self, key):
        if key in self._kv:
            del self._kv[key]
            return 1
        return 0

    def flush(self):
        kvlen = len(self._kv)
        self._kv.clear()
        return kvlen

    def mget(self, *keys):
        return [self._kv.get(key) for key in keys]

    def mset(self, *items):
        data = zip(items[::2], items[1::2])
        for key, value in data:
            self._kv[key] = value
        return len(data)

而已! 我们的服务器现在可以开始处理请求了。在下一个 本节,我们将实现一个客户端与服务器进行交互。

客户端

要与服务器交互,让我们重新使用 ProtocolHandler 类到 实现一个简单的客户端。客户端将连接到服务器并发送 命令编码为列表。我们将同时使用 write_response() 和 handle_request() 编码请求和处理服务器响应的逻辑 分别。

class Client(object):
    def __init__(self, host='127.0.0.1', port=31337):
        self._protocol = ProtocolHandler()
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.connect((host, port))
        self._fh = self._socket.makefile('rwb')

    def execute(self, *args):
        self._protocol.write_response(self._fh, args)
        resp = self._protocol.handle_request(self._fh)
        if isinstance(resp, Error):
            raise CommandError(resp.message)
        return resp

与 execute() 方法上,我们可以传递任意参数列表,这些参数将被编码为数组并发送到服务器。来自服务器的响应被解析并作为Python对象返回。为了方便起见,我们可以为各个命令编写客户端方法:

class Client(object):
    # ...
    def get(self, key):
        return self.execute('GET', key)

    def set(self, key, value):
        return self.execute('SET', key, value)

    def delete(self, key):
        return self.execute('DELETE', key)

    def flush(self):
        return self.execute('FLUSH')

    def mget(self, *keys):
        return self.execute('MGET', *keys)

    def mset(self, *items):
        return self.execute('MSET', *items)

为了测试我们的客户端,让我们配置Python脚本以启动服务器 直接从命令行执行时:

测试服务器

要测试服务器,只需从命令行执行服务器的Python模块即可。在另一个终端中,打开Python解释器并导入 Client 来自服务器模块的类。安装客户端将打开连接,您可以开始运行命令!

>>> from server_ex import Client
>>> client = Client()
>>> client.mset('k1', 'v1', 'k2', ['v2-0', 1, 'v2-2'], 'k3', 'v3')
3
>>> client.get('k2')
['v2-0', 1, 'v2-2']
>>> client.mget('k3', 'k1')
['v3', 'v1']
>>> client.delete('k1')
1
>>> client.get('k1')
>>> client.delete('k1')
0
>>> client.set('kx', {'vx': {'vy': 0, 'vz': [1, 2, 3]}})
1
>>> client.get('kx')
{'vx': {'vy': 0, 'vz': [1, 2, 3]}}
>>> client.flush()
2

完整代码

from gevent import socket
from gevent.pool import Pool
from gevent.server import StreamServer

from collections import namedtuple
from io import BytesIO
from socket import error as socket_error
import logging


logger = logging.getLogger(__name__)


class CommandError(Exception): pass
class Disconnect(Exception): pass

Error = namedtuple('Error', ('message',))


class ProtocolHandler(object):
    def __init__(self):
        self.handlers = {
            '+': self.handle_simple_string,
            '-': self.handle_error,
            ':': self.handle_integer,
            '$': self.handle_string,
            '*': self.handle_array,
            '%': self.handle_dict}

    def handle_request(self, socket_file):
        first_byte = socket_file.read(1)
        if not first_byte:
            raise Disconnect()

        try:
            # Delegate to the appropriate handler based on the first byte.
            return self.handlers[first_byte](socket_file)
        except KeyError:
            raise CommandError('bad request')

    def handle_simple_string(self, socket_file):
        return socket_file.readline().rstrip('\r\n')

    def handle_error(self, socket_file):
        return Error(socket_file.readline().rstrip('\r\n'))

    def handle_integer(self, socket_file):
        return int(socket_file.readline().rstrip('\r\n'))

    def handle_string(self, socket_file):
        # First read the length ($<length>\r\n).
        length = int(socket_file.readline().rstrip('\r\n'))
        if length == -1:
            return None  # Special-case for NULLs.
        length += 2  # Include the trailing \r\n in count.
        return socket_file.read(length)[:-2]

    def handle_array(self, socket_file):
        num_elements = int(socket_file.readline().rstrip('\r\n'))
        return [self.handle_request(socket_file) for _ in range(num_elements)]
    
    def handle_dict(self, socket_file):
        num_items = int(socket_file.readline().rstrip('\r\n'))
        elements = [self.handle_request(socket_file)
                    for _ in range(num_items * 2)]
        return dict(zip(elements[::2], elements[1::2]))

    def write_response(self, socket_file, data):
        buf = BytesIO()
        self._write(buf, data)
        buf.seek(0)
        socket_file.write(buf.getvalue())
        socket_file.flush()

    def _write(self, buf, data):
        if isinstance(data, str):
            data = data.encode('utf-8')

        if isinstance(data, bytes):
            buf.write('$%s\r\n%s\r\n' % (len(data), data))
        elif isinstance(data, int):
            buf.write(':%s\r\n' % data)
        elif isinstance(data, Error):
            buf.write('-%s\r\n' % error.message)
        elif isinstance(data, (list, tuple)):
            buf.write('*%s\r\n' % len(data))
            for item in data:
                self._write(buf, item)
        elif isinstance(data, dict):
            buf.write('%%%s\r\n' % len(data))
            for key in data:
                self._write(buf, key)
                self._write(buf, data[key])
        elif data is None:
            buf.write('$-1\r\n')
        else:
            raise CommandError('unrecognized type: %s' % type(data))


class Server(object):
    def __init__(self, host='127.0.0.1', port=31337, max_clients=64):
        self._pool = Pool(max_clients)
        self._server = StreamServer(
            (host, port),
            self.connection_handler,
            spawn=self._pool)

        self._protocol = ProtocolHandler()
        self._kv = {}

        self._commands = self.get_commands()

    def get_commands(self):
        return {
            'GET': self.get,
            'SET': self.set,
            'DELETE': self.delete,
            'FLUSH': self.flush,
            'MGET': self.mget,
            'MSET': self.mset}

    def connection_handler(self, conn, address):
        logger.info('Connection received: %s:%s' % address)
        # Convert "conn" (a socket object) into a file-like object.
        socket_file = conn.makefile('rwb')

        # Process client requests until client disconnects.
        while True:
            try:
                data = self._protocol.handle_request(socket_file)
            except Disconnect:
                logger.info('Client went away: %s:%s' % address)
                break

            try:
                resp = self.get_response(data)
            except CommandError as exc:
                logger.exception('Command error')
                resp = Error(exc.args[0])

            self._protocol.write_response(socket_file, resp)

    def run(self):
        self._server.serve_forever()

    def get_response(self, data):
        if not isinstance(data, list):
            try:
                data = data.split()
            except:
                raise CommandError('Request must be list or simple string.')

        if not data:
            raise CommandError('Missing command')

        command = data[0].upper()
        if command not in self._commands:
            raise CommandError('Unrecognized command: %s' % command)
        else:
            logger.debug('Received %s', command)

        return self._commands[command](*data[1:])

    def get(self, key):
        return self._kv.get(key)

    def set(self, key, value):
        self._kv[key] = value
        return 1

    def delete(self, key):
        if key in self._kv:
            del self._kv[key]
            return 1
        return 0

    def flush(self):
        kvlen = len(self._kv)
        self._kv.clear()
        return kvlen

    def mget(self, *keys):
        return [self._kv.get(key) for key in keys]

    def mset(self, *items):
        data = zip(items[::2], items[1::2])
        for key, value in data:
            self._kv[key] = value
        return len(data)


class Client(object):
    def __init__(self, host='127.0.0.1', port=31337):
        self._protocol = ProtocolHandler()
        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._socket.connect((host, port))
        self._fh = self._socket.makefile('rwb')

    def execute(self, *args):
        self._protocol.write_response(self._fh, args)
        resp = self._protocol.handle_request(self._fh)
        if isinstance(resp, Error):
            raise CommandError(resp.message)
        return resp

    def get(self, key):
        return self.execute('GET', key)

    def set(self, key, value):
        return self.execute('SET', key, value)

    def delete(self, key):
        return self.execute('DELETE', key)

    def flush(self):
        return self.execute('FLUSH')

    def mget(self, *keys):
        return self.execute('MGET', *keys)

    def mset(self, *items):
        return self.execute('MSET', *items)


if __name__ == '__main__':
    from gevent import monkey; monkey.patch_all()
    logger.addHandler(logging.StreamHandler())
    logger.setLevel(logging.DEBUG)
    Server().run()

文章转载自:Python魔法师

原文链接:https://www.cnblogs.com/meet/p/18067850

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

基于大数据的商业模式创新研究

目录 一、导论 2 &#xff08;一&#xff09;研究背景 2 &#xff08;二&#xff09;研究意义 2 &#xff08;三&#xff09;研究内容 3 &#xff08;四&#xff09;研究思路与研究方法 4 &#xff08;五&#xff09;国内外研究现状 6 二、大数据的界定与应用概况 7 &#xff…

【Android】源码中的建造者模式

本文是基于 Android 14 的源码解析 在 Android 源码中&#xff0c;最常用到的建造者模式就是 AlertDialog.Builder&#xff0c;使用该建造者来构建复杂的 AlertDialog 对象。在开发过程中&#xff0c;我们经常用到 AlertDialog&#xff0c;具体示例如下&#xff1a; private f…

Linux_网络项目_WEB服务器 处理服务器写入失败后sigpipe信号导致服务器崩溃退出问题,引入线程池缓解大量请求,服务器组件化重构,在线计算机业务测试

文章目录 1. 处理服务器写入管道出错2. 引入线程池缓解大量请求导致服务器崩溃设计线程任务类单例线程池组件设计 3.代码位置4. 在线计算机业务运行截图 1. 处理服务器写入管道出错 经过测试&#xff0c;服务器在读取报文时如果出错可以选择直接关闭这个TCP里链接来节省资源。…

界面开发框架DevExpress XAF v24.1新版预告 - 跨平台应用UI(二)

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;允许同时开发ASP.NET和WinForms。XAF采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 本文中的内容概述了…

Toy 语言到 LLVM IR 实现源码注释

对从程序源代码到AST的转换部分做了注释 源码&#xff1a; toy.cpp #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/Ver…

Java Day9 Stream流

Stream流 1、认识2、Stream流使用步骤3、如何获取Stream流4.Stream流的中间方法5、 Stream流终结方法 1、认识 2、Stream流使用步骤 3、如何获取Stream流 //list获取stream流List<String> listnew ArrayList<>();Collections.addAll(list,"崔十一","…

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列二:Fast R-CNN图文详解

RCNN算法详解&#xff1a;【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列一&#xff1a;R-CNN图文详解 学习视频&#xff1a;Faster RCNN理论合集 Fast RCNN 概念辨析 1. RoI 在Fast R-CNN中&#xff0c;RoI&#xff08;Region of Interest&#xff0c;感兴…

Python导入类说一说

要在Python中导入一个类&#xff0c;需要使用import关键字。 详细去看下面的代码 1、多例类 class Restaurant:餐馆类def __init__(self,restaurant_name,cuisine_type):#类的属性self.restaurant_name restaurant_nameself.cuisine_type cuisine_type# self.stregth_leve…

Python网络基础爬虫-python基本语法

文章目录 逻辑语句if,else,elifforwhile异常处理 函数与类defpassclass 逻辑语句 熟悉C/C语言的人们可能很希望Python提供switch语句&#xff0c;但Python中并没有这个关键词&#xff0c;也没有这个语句结构。但是可以通过if-elif-elif-…这样的结构代替&#xff0c;或者使用字…

解决JVM进程被系统杀掉问题

背景 服务A在测试环境&#xff0c;隔几个小时接口就无法访问。登录机器查看&#xff0c;发现进程已经没了。大致猜想是进程使用的内存或CPU资源使用太多&#xff0c;导致被系统kill。 问题定位 使用dmesg命令查看进程被kill的详情。 > dmesg --time-format iso2024-03-0…

【Python如何与电脑玩石头剪刀布游戏】

1、石头剪刀布Python代码如下&#xff1a; import random while True:a random.randint(0, 2)b int(input("请输入一个数字&#xff08;0石头, 1剪刀, 2布&#xff09;: "))c [石头, 剪刀, 布]if b ! 0 and b ! 1 and b ! 2:print("傻子&#xff0c;你出错了…

五子棋小游戏(sut实验报告)

实验目的 实现人与人或人与电脑进行五子棋对弈 实验内容 启动游戏&#xff0c;显示游戏参数设置界面&#xff0c;用户输入参数后进入游戏界面&#xff0c;显示棋盘及双方博弈过程&#xff0c;游戏过程中可选择退出游戏。判定一方获胜后结束本局游戏&#xff0c;可选择继续下…

S4 Hana SD -信贷管理 - 02

2.3 给信贷控制范围分配公司代码 TCODE: SPRO 配置路径:IMG > 企业结构 > 分配 > 财务会计 > 给信贷控制区分配公司代码 配置路径截图: 公司:被分配的公司代码。 公司名称&城市:已在公司代码数据中维护。 CCAR:分配的信贷控制范围。 覆盖CC范围:如…

InstantID Zero-shot Identity-Preserving Generation in Seconds

InstantID: Zero-shot Identity-Preserving Generation in Seconds TL; DR&#xff1a;InstantID IP-Adapter (Face) ControlNet&#xff0c;实现了具有较高保真度的人脸 ID 生成。 方法 InstantID 想做到的事情是&#xff1a;给定一张参考人脸 ID 图片&#xff0c;生成该…

专升本 C语言笔记-07 逗号运算符

1.逗号表达式的用法 就是用逗号隔开的多个表达式。逗号表达式&#xff0c;从左向右依次执行。 2.逗号表达式的特性 2.1.当没有括号时&#xff0c;第一个表达式为整个表达式的值。 代码 int x 3,y 5,a 0; a x,y; printf("a %d",a); 说明:因为逗号优先级最低,会…

利用Python进行网络爬虫:Beautiful Soup和Requests的应用【第131篇—Beautiful Soup】

利用Python进行网络爬虫&#xff1a;Beautiful Soup和Requests的应用 在网络数据变得日益丰富和重要的今天&#xff0c;网络爬虫成为了获取和分析数据的重要工具之一。Python作为一种强大而灵活的编程语言&#xff0c;在网络爬虫领域也拥有广泛的应用。本文将介绍如何使用Pyth…

【智能硬件、大模型、LLM 智能音箱】MBO:基于树莓派、ChatGPT 的桌面机器人

MAKER:David Packman/译:趣无尽(转载请注明出处) 这是国外 Maker David Packman 制作的基于树莓派机器人 MBO,该机器人的外观设计灵感来自动漫 Adventure Time 中的机器人 MBO。它具有强大的交互功能,可实现脱机唤醒词检测、调用 ChatGPT 3.5 进行聊天、机器视觉对图像进…

解决Git:Author identity unknown Please tell me who you are.

报错信息&#xff1a; 意思&#xff1a; 作者身份未知 ***请告诉我你是谁。 解决办法&#xff1a; git config --global user.name "你的名字"git config --global user.email "你的邮箱"

Android 15 首个开发者预览版到来

作者 / 工程副总裁 Dave Burke Android 15 的首个开发者预览版现已发布&#xff0c;以便各位开发者能与我们通力协作&#xff0c;打造更优秀的 Android 平台。 在 Android 15 中&#xff0c;我们继续致力于打造一个既能提升工作效率&#xff0c;又能提供全新功能的平台。这些新…

蓝桥杯-模拟-4402. 刷题统计

题目 思路 代码 a,b,nmap(int,input().split()) sa*5b*2 resn//s*7 # 存在周期 d[a,a,a,a,a,b,b] n%s i0 while n>0: # 对剩余数量进行枚举&#xff0c;如果等于0&#xff0c;相当于还会再进去加一天n-d[i]i1res1 print(res)