python argparse 源码阅读

发表于 3年以前  | 总阅读数:271 次

http.server 可以使用 -h 查看帮助。这种自定义的命令行工具对用户使用程序非常有帮助,我们一起学习是如何实现命令工具的。

先看看展示:

python -m http.server -h
usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]

positional arguments:
  port                  Specify alternate port [default: 8000]

optional arguments:
  -h, --help            show this help message and exit
  --cgi                 Run as CGI Server
  --bind ADDRESS, -b ADDRESS
                        Specify alternate bind address [default: all interfaces]
  --directory DIRECTORY, -d DIRECTORY
                        Specify alternative directory [default:current directory]

http.server 模块帮助信息中可以看到:

  • usage 提供了使用的完整的使用示例
  • 位置参数 port , 可以自定义服务端口, 默认值 8000
  • 可选参数,叫关键字参数可能更合适些:
  • -h/--help 展示帮助信息
  • --cgi 使用cgi服务,看起来是个bool值
  • --bind/-b 服务ip地址,默认是所有地址
  • --directory/-d 服务文件目录,默认当前目录

我们把上面的描述换个方式,使用python函数定义,这样就很容易理解位置参数和关键字参数了:

def http_server(port, cgi=False, bind="all interfaces", directory="current directory"):
    pass

函数和命令行参数定义有所不同:

  • 关键字参数有长短选项的写法,-b--bind都可以
  • port会限定数值是int类似型
  • usage信息和help信息是如何自动生成的

带着这几个疑问,我们去python源码中查找答案。本文分下面几个部分:

  • sys.argv 简介
  • getopt 解析
  • optparse 解析
  • argparser 解析
  • 小结
  • 小技巧

sys.argv 简介

编写测试脚本

# simple.py

import sys

if __name__ == "__main__":
    print(type(sys.argv), sys.argv)

使用下面的命令运行测试脚本

python simple.py 1 a bbb c=2 d@3
<class 'list'> ['simple.py', '1', 'a', 'bbb', 'c=2', 'd@3']

可以看到 sys.argv 是一个列表,其中包含了被传递给 Python 脚本的命令行参数, argv[0] 为脚本的名称。这是命令工具的起点,所有命令行工具从这里派生扩展。

getopt 解析

我在上个例子中使用了 d@3 , 这是搞笑的。命令行参数有约定的惯例和规范,在unix-shell中由getopt函数实现,具体可以看参考链接中的wiki部分。python中也提供了 getopt 实现。先看看如何使用,短选项使用单个 - 前缀:

import getopt
args = '-a -b -cfoo -d bar a1 a2'.split()
print(args)  # ['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2']

optlist, args = getopt.getopt(args, 'abc:d:')
print(optlist)  # [('-a', ''), ('-b', ''), ('-c', 'foo'), ('-d', 'bar')]
print(args)  # ['a1', 'a2']

长选项使用两个-- 前缀:

s = '--condition=foo --testing --output-file abc.def -x a1 a2'
args = s.split()
print(args)  # ['--condition=foo', '--testing', '--output-file', 'abc.def', '-x', 'a1', 'a2']

optlist, args = getopt.getopt(args, 'x', [
    'condition=', 'output-file=', 'testing'])
print(optlist)  # [('--condition', 'foo'), ('--testing', ''), ('--output-file', 'abc.def'), ('-x', '')]
print(args)  # ['a1', 'a2']

getopt返关键字参数optlist和位置参数args。关键字有长关键字 --connddition 和短关键字 -c 两个名称,为什么会有长短两种方式,我的理解是长关键字语义更明确,单独的字母 c 很难知道代表的含义,而使用单词 condditon 则一目了然; 短关键字使用更便捷,只需要敲一个字母,还可以多个参数合并,比如 ls -lah

quopri 中演示了如何使用 getopt 实现命令行参数解析:

# quopri

import getopt
try:
    opts, args = getopt.getopt(sys.argv[1:], 'td')
except getopt.error as msg:
    sys.stdout = sys.stderr
    print(msg)
    print("usage: quopri [-t | -d] [file] ...")  # 帮助信息
    print("-t: quote tabs")
    print("-d: decode; default encode")
    sys.exit(2)
...

for o, a in opts:  # 解析关键字参数
    if o == '-t': tabs = 1
    if o == '-d': deco = 1

for file in args:  # 解析位置参数
    ...

主要的getopt函数代码如下:

# getopt 

def getopt(args, shortopts, longopts = []): 
    opts = []
    longopts = list(longopts)
    while args and args[0].startswith('-') and args[0] != '-': 
        ...
        if args[0].startswith('--'):  
            opts, args = do_longs(opts, args[0][2:], longopts, args[1:])
        else:
            opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])  # args[0][1:] 移除前缀
    return opts, args
  • 3个参数:待解析参数,短参数定义,长参数定义。短参数定义使用字符串,比如abc:d:;长参数使用数组,比如: ['condition=', 'output-file=', 'testing']
  • 使用while循环持续的解析关键字参数,关键字参数解析完成后剩余的就是位置参数args
  • 长参数使用do_longs解析,短参数使用do_shorts解析

短参数的解析方法

def do_shorts(opts, optstring, shortopts, args):
    while optstring != '':  # while循环
        opt, optstring = optstring[0], optstring[1:]  # 截取参数关键字和剩余字符
        if short_has_arg(opt, shortopts):
            if optstring == '':
                if not args:
                    raise GetoptError(_('option -%s requires argument') % opt,
                                      opt)
                optstring, args = args[0], args[1:]  # 贪婪后面的参数 '-d', 'bar'
            optarg, optstring = optstring, ''  # 取甚于部分 -cfoo
        else:
            optarg = ''
        opts.append(('-' + opt, optarg))  # 无参数 '-a', '-b
    return opts, args

def short_has_arg(opt, shortopts):
    for i in range(len(shortopts)):
        if opt == shortopts[i] != ':':
            return shortopts.startswith(':', i+1)  # 判断之后是否跟着:字符 
    raise GetoptError(_('option -%s not recognized') % opt, opt)

args=['-a', '-b', '-cfoo', '-d', 'bar', 'a1', 'a2']shortopts='abc:d:' 为例,介绍一下解析的执行过程:

  • -a, -b 无需参数值,,在shortopts仅ab,没有:后缀,未命中short_has_arg,返回两个元祖 ('-a', ''), ('-b', '')
  • -cfoo 需要参数值,在shortopts中有c:,命中short_has_arg,返回 ('-c', 'foo')
  • -d, 需要参数值,在shortopts中有d:,命中short_has_arg,并捕获后面跟着的bar,一起返回 ('-d', 'bar')
  • a1, a2 位置参数最后剩余

这样我们就很清楚 abc:d: 的含义了,每个字符是一个参数,如果需要参数值,则后面跟一个:字符。长参数的解析方法,比较类似,就不再赘述了。

optparse 解析

quopri源码中可以看到 getopt 提供的方法比较单薄,还需要手工print(usage && help)信息,解析后的参数使用也不直观, 需要按照位置获取。接下来登场的是 optparse , 可以在cProfile中看到使用示例:

# cProfile.py

from optparse import OptionParser

usage = "cProfile.py [-o output_file_path] [-s sort] scriptfile [arg] ..."  # 注1
parser = OptionParser(usage=usage)
parser.allow_interspersed_args = False
parser.add_option('-o', '--outfile', dest="outfile",
    help="Save stats to <outfile>", default=None)  # 注2
parser.add_option('-s', '--sort', dest="sort",
    help="Sort order when printing to stdout, based on pstats.Stats class",
    default=-1)

(options, args) = parser.parse_args()

runctx(code, globs, None, options.outfile, options.sort)  # 注3

查看效果:

python -m cProfile -h
Usage: cProfile.py [-o output_file_path] [-s sort] [-m module | scriptfile] [arg] ...

Options:
  -h, --help            show this help message and exit
  -o OUTFILE, --outfile=OUTFILE
                        Save stats to <outfile>
  -s SORT, --sort=SORT  Sort order when printing to stdout, based on
                        pstats.Stats class
  -m                    Profile a library module

optparse对比getopt:

  • 可以设置usage信息 (注1)
  • 自动收集参数帮助,生成help信息 (注2)
  • option使用比较直观,可以使用 options.outfile 获取参数值 (注3)

官方的文档中介绍optparse难以扩展,已经被废弃,推荐使用基于它的argparse替代。但是我们还是不放过它,这对理解argparser有帮助。

optparse 模块结构

optparse的模块类图:

optparse

可以看到optparse模块主要就是 OptionParser, OptionHelpFormatter 三个类。

optparse 实现

parser的使用模版,就是下面3行代码:创建对象,添加option和进行参数解析并返回

<parser = OptionParser(usage=usage)
parser.add_option('-o', '--outfile', dest="outfile",
    help="Save stats to <outfile>", default=None)  #  添加多个参数...
parser.parse_args()  # 自动获取sys.argv 不需要传入

先查看 OptionParser 对象创建

class OptionContainer:

    def __init__(self, option_class, conflict_handler, description):
        # Initialize the option list and related data structures.
        # This method must be provided by subclasses, and it must
        # initialize at least the following instance attributes:
        # option_list, _short_opt, _long_opt, defaults.
        self._create_option_list()  # 抽象方法,由子类实现,这时候可能没有abc模块,抽象方法使用注释进行要求
        self.option_class = option_class  # 选项类,可以由用户扩展
        self.conflict_handler = handler
        self.description = description

class OptionParser(OptionContainer):

    def __init__(self,
                 usage=None,
                 option_list=None,
                 option_class=Option,
                 version=None,
                 conflict_handler="error",
                 description=None,
                 formatter=None,
                 add_help_option=True,
                 prog=None,
                 epilog=None):
        OptionContainer.__init__(
            self, option_class, conflict_handler, description)
        self.usage = usage
        self.version = version
        if formatter is None:
            formatter = IndentedHelpFormatter()  # 默认帮助类
        self.formatter = formatter
        self.formatter.set_parser(self)

        self._populate_option_list(option_list,
                                   add_help=add_help_option)

        self._init_parsing_state()

上面代码创建了OptionParser对象,下面代码初始化了部分属性

def _create_option_list(self):
    self.option_list = []
    self.option_groups = [] 
    self._short_opt = {}            # single letter -> Option instance
    self._long_opt = {}             # long option -> Option instance
    self.defaults = {}              # maps option dest -> default value

def _add_help_option(self):
    self.add_option("-h", "--help",
                    action="help",
                    help=_("show this help message and exit"))

def _populate_option_list(self, option_list, add_help=True):
    ...
    if self.version:
        self._add_version_option()  # version-option默认未开启
    if add_help:
        self._add_help_option()  # 默认添加help-option

def _init_parsing_state(self):
    # These are set in parse_args() for the convenience of callbacks.
    self.rargs = None  # 初始化
    self.largs = None
    self.values = None

add_option实现,可以看到很熟悉的长参数和短参数

def add_option(self, *args, **kwargs):
    if isinstance(args[0], str):
        option = self.option_class(*args, **kwargs)  # Option

    self.option_list.append(option)
    option.container = self

    for opt in option._short_opts:
        self._short_opt[opt] = option  # 长参数
    for opt in option._long_opts: 
        self._long_opt[opt] = option  # 短参数

    if option.dest is not None:     # option has a dest, we need a default
        if option.default is not NO_DEFAULT:
            self.defaults[option.dest] = option.default
        elif option.dest not in self.defaults:
            self.defaults[option.dest] = None

    return option

Option存储参数设置, 构建长选项和短选项列表,并check选项是否合法:

class Option:

    def __init__(self, *opts, **attrs):
    self._short_opts = []  
    self._long_opts = []  # 为什么option要有short和long两个数组
    ...
    self._set_opt_strings(opts)

    # Set all other attrs (action, type, etc.) from 'attrs' dict
    self._set_attrs(attrs)

    for checker in self.CHECK_METHODS:
        checker(self)

使用前缀判断参数列表:

def _set_opt_strings(self, opts):
    for opt in opts:
        if len(opt) < 2:
            raise
        elif len(opt) == 2:
            if not (opt[0] == "-" and opt[1] != "-"):
                raise 
            self._short_opts.append(opt)
        else:
            if not (opt[0:2] == "--" and opt[2] != "-"):
                raise 
            self._long_opts.append(opt)

option的检查方法比较多,我们看一下的action和type检查

CHECK_METHODS = [_check_action,
                 _check_type,
                 _check_choice,
                 _check_dest,
                 _check_const,
                 _check_nargs,
                 _check_callback]

def _check_action(self):
    if self.action is None:
        self.action = "store"  # 默认原样存储
    elif self.action not in self.ACTIONS:
        raise

def _check_type(self):  # 判断参数类型
    if self.type is None:
        if self.action in self.ALWAYS_TYPED_ACTIONS:
            if self.choices is not None:
                # The "choices" attribute implies "choice" type.
                self.type = "choice"  # 枚举
            else:
                # No type given?  "string" is the most sensible default.
                self.type = "string"
    else:
        # Allow type objects or builtin type conversion functions
        # (int, str, etc.) as an alternative to their names.
        if isinstance(self.type, type):  # 其它类型
            self.type = self.type.__name__

        if self.type == "str":
            self.type = "string"

        if self.type not in self.TYPES:
            raise 
        if self.action not in self.TYPED_ACTIONS:
            raise 

store-action的使用等解析参数时候再介绍。完成Parser对象的构建后,就是如何使用parse_args解析参数:

def parse_args(self, args=None, values=None):
    rargs = sys.argv[1:]  # 从sys.argv中获取输入
    ...
    values = self.get_default_values()  # 获取默认值
    ...
    stop = self._process_args([], rargs, values)  # 解析参数

熟悉的参数解析分支:

def _process_args(self, largs, rargs, values):
    while rargs:  # while循环
        arg = rargs[0]
        elif arg[0:2] == "--":
            # process a single long option (possibly with value(s))
            self._process_long_opt(rargs, values)  # 处理长参数
        elif arg[:1] == "-" and len(arg) > 1:
            # process a cluster of short options (possibly with
            # value(s) for the last one only)
            self._process_short_opts(rargs, values)  # 处理短参数
        ...

短参数的解析过程:

def _process_short_opts(self, rargs, values):
    arg = rargs.pop(0)
    stop = False
    i = 1
    for ch in arg[1:]:  # 逐个字符解析
        opt = "-" + ch
        option = self._short_opt.get(opt)  # 获取对应的 Option 规则
        i += 1                      # we have consumed a character

        if option.takes_value():
            # Any characters left in arg?  Pretend they're the
            # next arg, and stop consuming characters of arg.
            if i < len(arg):
                rargs.insert(0, arg[i:])
                stop = True

            nargs = option.nargs
            if len(rargs) < nargs:
                ...
            elif nargs == 1:
                value = rargs.pop(0)  # 解析出参数值
            else:
                value = tuple(rargs[0:nargs])
                del rargs[0:nargs]

        else:                       # option doesn't take a value
            value = None

        option.process(opt, value, values, self) # 存储到option 

Option存储参数Action的实现:

def process(self, opt, value, values, parser):
    # And then take whatever action is expected of us.
    # This is a separate method to make life easier for
    # subclasses to add new actions.
    return self.take_action(
        self.action, self.dest, opt, value, values, parser)

def take_action(self, action, dest, opt, value, values, parser):
    if action == "store":
        setattr(values, dest, value)  # 直接存储
    elif action == "store_const":
        setattr(values, dest, self.const)  # 使用定义的常量
    elif action == "store_true":
        setattr(values, dest, True)  # int=true
    elif action == "store_false":
        setattr(values, dest, False)  # int=false
    elif action == "append":
        values.ensure_value(dest, []).append(value)  # 接受数组
    elif action == "append_const":
        values.ensure_value(dest, []).append(self.const)  # 常量数组
    elif action == "count":
        setattr(values, dest, values.ensure_value(dest, 0) + 1) # 计数参数,可以重复使用
    elif action == "callback":  # 支持回掉
        args = self.callback_args or ()
        kwargs = self.callback_kwargs or {}
        self.callback(self, opt, value, parser, *args, **kwargs)
    elif action == "help":  # 帮助
        parser.print_help()
        parser.exit()
    elif action == "version":  # 版本
        parser.print_version()
        parser.exit()
    else:
        raise ValueError("unknown action %r" % self.action)
    return 1

action的使用,可以看参考链接中的howto部分,介绍的非常详细。接下来重点看一下帮助部分的实现。

# parse
def format_help(self, formatter=None):
        if formatter is None:
            formatter = self.formatter
        result = []
        if self.usage:
            result.append(self.get_usage() + "\n")  # 输出usage 
        if self.description:
            result.append(self.format_description(formatter) + "\n")  # 输出description
        result.append(self.format_option_help(formatter))  # 开始option-help 
        ...
        return "".join(result)

def format_option_help(self, formatter=None):
    formatter.store_option_strings(self)
    result = []
    result.append(formatter.format_heading(_("Options")))  
    formatter.indent()
    if self.option_list:
        result.append(OptionContainer.format_option_help(self, formatter)) # 收集option的帮助
        result.append("\n")
    ...
    formatter.dedent()
    return "".join(result[:-1])

def format_option(self, option):
    result = []
    opts = self.option_strings[option]
    opt_width = self.help_position - self.current_indent - 2
    if len(opts) > opt_width: # 输出option的关键字
        opts = "%*s%s\n" % (self.current_indent, "", opts)
        indent_first = self.help_position
    else:                       # start help on same line as opts
        opts = "%*s%-*s  " % (self.current_indent, "", opt_width, opts)
        indent_first = 0
    result.append(opts)
    if option.help:  # 输出option帮助信息
        help_text = self.expand_default(option)
        help_lines = textwrap.wrap(help_text, self.help_width)
        result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
        result.extend(["%*s%s\n" % (self.help_position, "", line)
                       for line in help_lines[1:]])
    elif opts[-1] != "\n":
        result.append("\n")
    return "".join(result)

看完处理流程,我们大概还有2个疑问:

  1. option为什么有 _short_opts 和 _long_opts 2个数组
  2. 如何使用 options.outfile 获取参数值的

第一个问题,请看代码:

# -o 和 --outfile 都可以表示同一个option
parser.add_option('-o', '--outfile', dest="outfile",
        help="Save stats to <outfile>", default=None)

def _set_opt_strings(self, opts):
    # opts = ['-o', '--outfile']
    for opt in opts:
        if ..:
            self._short_opts.append(opt)
        if ..:
            self._long_opts.append(opt)

第二个问题,请看代码:

# 参数值存储到一个字典
setattr(values, dest, value)
...
def parse_args(self, args=None, values=None):
    ...
    # 返回参数字典
    return (values, args)

optparse比较难以扩展,我认为主要是因为这段代码:

def take_action(self, action, dest, opt, value, values, parser):
    if action == "store":
        setattr(values, dest, value)
    elif action == "store_const":
        setattr(values, dest, self.const)
    ....

这种if-else的代码逻辑,分支一旦变多,就难以维护。可以考虑用设计模式替换。

argparse

argparse 模块结构

argparser的类图,可以看到继承自optparse,左侧基本一致。只是右侧将option换成了action的实现。

argparse

argparse 使用示例

http.server中argparse使用示例

# http.server 

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
                   help='Run as CGI Server')
parser.add_argument('--bind', '-b', metavar='ADDRESS',
                    help='Specify alternate bind address '
                         '[default: all interfaces]')
parser.add_argument('--directory', '-d', default=os.getcwd(),
                    help='Specify alternative directory '
                    '[default:current directory]')
parser.add_argument('port', action='store',
                    default=8000, type=int,
                    nargs='?',
                    help='Specify alternate port [default: 8000]')
args = parser.parse_args()

if args.cgi:
    handler_class = CGIHTTPRequestHandler
else:
    handler_class = SimpleHTTPRequestHandler
test(HandlerClass=handler_class, port=args.port, bind=args.bind)

和optparse一样的使用模版:创建对象,添加参数,解析参数

parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true',
                   help='Run as CGI Server')
args = parser.parse_args()
# args.port, args.bind

argparse action实现

由于文章篇幅,我们重点看看action部分的实现,使用了注册模式解决if-else问题:

class _ActionsContainer(object):

    def __init__(self,
                 description,
                 prefix_chars,
                 argument_default,
                 conflict_handler):
        super(_ActionsContainer, self).__init__()
        # set up registries
        self._registries = {}  # 注册中心

        # register actions
        self.register('action', None, _StoreAction)  # 注册action类
        self.register('action', 'store', _StoreAction)


    def _pop_action_class(self, kwargs, default=None):
        action = kwargs.pop('action', default)
        return self._registry_get('action', action, action)  # 获取对应action类

    def add_argument(self, *args, **kwargs):

        # create the action object, and add it to the parser
        action_class = self._pop_action_class(kwargs)
        action = action_class(**kwargs)  # 创建action对象

    def parse_args(self, args=None, namespace=None):
        for action in self._actions:
            if action.dest is not SUPPRESS:
                if not hasattr(namespace, action.dest):
                    if action.default is not SUPPRESS:
                        setattr(namespace, action.dest, action.default)  # 执行action对象

# 使用方法
parser.add_argument('--cgi', action='store_true',
                   help='Run as CGI Server')
args = parser.parse_args()

小结

最后我们再来简单小结一下:

  • 使用sys.argv接受命令行参数
  • 命令行参数有位置参数和可选参数
  • 可选参数有-+单个字符和--+单词的长参数两种
  • 命令行参数的解析模版都是3步:创建解析器,添加解析器规则和解析参数

小技巧

分析参数解析过程中,发现切片的一个特点,索引不会越界:

>>> b =[1,2]
>>> b[1:]
[2]
>>> 
>>> b[3:]  # 安全
[]
>>> b[3]  # 异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Python 能够优雅地处理那些没有意义的切片索引:一个过大的索引值(即下标值大于字符串实际长度)将被字符串实际长度所代替,当上边界比下边界大时(即切片左值大于右值)就返回空字符串。(摘自python-tutorial3)

另外使用gettext支持国际化,也可以插一个眼:

try:
    from gettext import gettext, ngettext
except ImportError:
    def gettext(message):
        return message

_ = gettext

class BadOptionError (OptParseError):

    ...

    def __str__(self):
        return _("no such option: %s") % self.opt_str

参考链接

  • https://docs.python.org/zh-cn/3/library/getopt.html
  • https://en.wikipedia.org/wiki/Getopts
  • https://docs.python.org/zh-cn/3/howto/argparse.html
  • http://www.pythondoc.com/pythontutorial3/index.html

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/UeXsuLxxbwcjIQgxUUWX9Q

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237229次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录