安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

后端编程Python3调试测试和性能剖析(下)

7月7日 星宿房投稿
  单元测试(UnitTesting)
  为程序编写测试如果做的到位有助于减少bug的出现,并可以提高我们对程序按预期目标运行的信心。通常,测试并不能保证正确性,因为对大多数程序而言,可能的输入范围以及可能的计算范围是如此之大,只有其中最小的一部分能被实际地进行测试。尽管如此,通过仔细地选择测试的方法和目标,可以提高代码的质量。
  大量不同类型的测试都可以进行,比如可用性测试、功能测试以及整合测试等。这里,我们只讲单元测试一对单独的函数、类与方法进行测试,确保其符合预期的行为。
  TDD的一个关键点是,当我们想添加一个功能时比如为类添加一个方法我们首次为其编写一个测试用例。当然,测试将失败,因为我们还没有实际编写该方法。现在,我们编写该方法,一旦方法通过了测试,就可以返回所有测试,确保我们新添加的代码没有任何预期外的副作用。一旦所有测试运行完毕(包括我们为新功能编写的测试),就可以对我们的代码进行检查,并有理有据地相信程序行为符合我们的期望当然,前提是我们的测试是适当的。
  比如,我们编写了一个函数,该函数在特定的索引位置插入一个字符串,可以像下面这样开始我们的TDD:
  definsertat(string,position,insert):
  Returnsacopyofstringwithinsertinsertedattheposition
  stringABCDE
  result〔〕
  foriinrange(2,len(string)2):
  。。。result。append(insertat(string,i,))
  result〔:5〕
  〔ABCDE,ABCDE,ABCDE,ABCDE,ABCDE〕
  result〔5:〕
  〔ABCDE,ABCDE,ABCDE,ABCDE〕
  returnstring
  对不返回任何参数的函数或方法(通常返回None),我们通常赋予其由pass构成的一个suite,对那些返回值被试用的,我们或者返回一个常数(比如0),或者某个不变的参数这也是我们这里所做的。(在更复杂的情况下,返回fake对象可能更有用一一对这样的类,提供mock对象的第三方模块是可用的。)
  运行doctest时会失败,并列出每个预期内的字符串(ABCDEF、ABCDEF等),及其实际获取的字符串(所有的都是ABCDEF)。一旦确定doctest是充分的和正确的,就可以编写该函数的主体部分,在本例中只是简单的returnstring〔:position〕insertstring〔position:〕。(如果我们编写的是returnstring〔:position〕insert,之后复制string〔:position〕并将其粘贴在末尾以便减少一些输入操作,那么doctest会立即提示错误。)
  Python的标准库提供了两个单元测试模块,一个是doctest,这里和前面都简单地提到过,另一个是unittest。此外,还有一些可用于Python的第三方测试工具。其中最著名的两个是nose(code。google。comppythonnose)与py。test(codespeak。netpydisttesttest。html),nose致力于提供比标准的unittest模块更广泛的功能,同时保持与该模块的兼容性,py。test则采用了与unittest有些不同的方法,试图尽可能消除样板测试代码。这两个第三方模块都支持测试发现,因此没必要写一个总体的测试程序因为模块将自己搜索测试程序。这使得测试整个代码树或某一部分(比如那些已经起作用的模块)变得很容易。那些对测试严重关切的人,在决定使用哪个测试工具之前,对这两个(以及任何其他有吸引力的)第三方模块进行研究都是值得的。
  创建doctest是直截了当的:我们在模块中编写测试、函数、类与方法的docstrings。对于模块,我们简单地在末尾添加了3行:
  ifnamemain:
  importdoctest
  doctest。testmod()
  在程序内部使用doctest也是可能的。比如,blocks。py程序(其模块在后面)有自己函数的doctest,但以如下代码结尾:
  ifnamemain:
  main()
  这里简单地调用了程序的main()函数,并且没有执行程序的doctest。要实验程序的doctest,有两种方法。一种是导入doctest模块,之后运行程序比如,在控制台中输入python3mdoctestblocks。py(在Wndows平台上,使用类似于C:Python3lpython。exe这样的形式替代python3)。如果所有测试运行良好,就没有输出,因此,我们可能宁愿执行python3mdoctestblocks。pyv,因为这会列出每个执行的doctest,并在最后给出结果摘要。
  另一种执行doctest的方法是使用unittest模块创建单独的测试程序。在概念上,unittest模块是根据Java的JUnit单元测试库进行建模的,并用于创建包含测试用例的测试套件。unittest模块可以基于doctests创建测试用例,而不需要知道程序或模块包含的任何事物只要知道其包含doctest即可。因此,为给blocks。py程序制作一个测试套件,我们可以创建如下的简单程序(将其称为testblocks。py):
  importdoctest
  importunittest
  importblocks
  suiteunittest。TestSuite()
  suite。addTest(doctest。DocTestSuite(blocks))
  runnerunittest。TextTestRunner()
  print(runner。run(suite))
  注意,如果釆用这种方法,程序的名称上会有一个隐含的约束:程序名必须是有效的模块名。因此,名为convertincidents。py的程序的测试不能写成这样。因为importconvertincidents不是有效的,在Python标识符中,连接符是无效的(避开这一约束是可能的,但最简单的解决方案是使用总是有效模块名的程序文件名,比如,使用下划线替换连接符)。这里展示的结构(创建一个测试套件,添加一个或多个测试用例或测试套件,运行总体的测试套件,输出结果)是典型的机遇unittest的测试。运行时,这一特定实例产生如下结果:
  。。。
  。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  Ran3testsin0。244s
  OK
  unittest。TextTestResultrun3errors0failures0
  每次执行一个测试用例时,都会输出一个句点(因此上面的输出最前面有3个句点),之后是一行连接符,再之后是测试摘要(如果有任何一个测试失败,就会有更多的输出信息)。
  如果我们尝试将测试分离开(典型情况下是要测试的每个程序和模块都有一个测试用例),就不要再使用doctests,而是直接使用unittest模块的功能尤其是我们习惯于使用JUnit方法进行测试时ounittest模块会将测试分离于代码对大型项目(测试编写人员与开发人员可能不一致)而言,这种方法特别有用。此外,unittest单元测试编写为独立的Python模块,因此,不会像在docstring内部编写测试用例时受到兼容性和明智性的限制。
  unittest模块定义了4个关键概念。测试夹具是一个用于描述创建测试(以及用完之后将其清理)所必需的代码的术语,典型实例是创建测试所用的一个输入文件,最后删除输入文件与结果输出文件。测试套件是一组测试用例的组合。测试用例是测试的基本单元我们很快就会看到实例。测试运行者是执行一个或多个测试套件的对象。
  典型情况下,测试套件是通过创建unittest。TestCase的子类实现的,其中每个名称以test开头的方法都是一个测试用例。如果我们需要完成任何创建操作,就可以在一个名为setUp()的方法中实现;类似地,对任何清理操作,也可以实现一个名为tearDown()的方法。在测试内部,有大量可供我们使用的unittest。TestCase方法,包括assertTrue()、assertEqual()、assertAlmostEqual()(对于测试浮点数很有用)、assertRaises()以及更多,还包括很多对应的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、failUnlessEqual()等。
  unittest模块进行了很好的归档,并且提供了大量功能,但在这里我们只是通过一个非常简单的测试套件来感受一下该模块的使用。这里将要使用的实例,该练习要求创建一个Atomic模块,该模块可以用作一个上下文管理器,以确保或者所有改变都应用于某个列表、集合或字典,或者所有改变都不应用。作为解决方案提供的Atomic。py模块使用30行代码来实现Atomic类,并提供了100行左右的模块doctest。这里,我们将创建testAtomic。py模块,并使用unittest测试替换doctest,以便可以删除doctest。
  在编写测试模块之前,我们需要思考都需要哪些测试。我们需要测试3种不同的数据类型:列表、集合与字典。对于列表,需要测试的是插入项、删除项或修改项的值。对于集合,我们必须测试向其中添加或删除一个项。对于字典,我们必须测试的是插入一个项、修改一个项的值、删除一个项。此外,还必须要测试的是在失败的情况下,不会有任何改变实际生效。
  结构上看,测试不同数据类型实质上是一样的,因此,我们将只为测试列表编写测试用例,而将其他的留作练习。testAtomic。py模块必须导入unittest模块与要进行测试的Atomic模块。
  创建unittest文件时,我们通常创建的是模块而非程序。在每个模块内部,我们定义一个或多个unittest。TestCase子类。比如,testAtomic。py模块中仅一个单独的unittestTestCase子类,也就是TestAtomic(稍后将对其进行讲解),并以如下两行结束:
  ifnamemain:
  unittest。main()
  这两行使得该模块可以单独运行。当然,该模块也可以被导入并从其他测试程序中运行如果这只是多个测试套件中的一个,这一点是有意义的。
  如果想要从其他测试程序中运行testAtomic。py模块,那么可以编写一个与此类似的程序。我们习惯于使用unittest模块执行doctests,比如:
  importunittest
  importtestAtomic
  suiteunittest。TestLoader()。loadTestsFromTestCase(testAtomic。TestAtomic)
  runnerunittest。TextTestRunner()
  pnnt(runner。run(suite))
  这里,我们已经创建了一个单独的套件,这是通过让unittest模块读取testAtomic模块实现的,并且使用其每一个test()方法(本实例中是testlistsuccess()、testlistfail(),稍后很快就会看到)作为测试用例。
  我们现在将查看TestAtomic类的实现。对通常的子类(不包括unittest。TestCase子类),不怎么常见的是,没有必要实现初始化程序。在这一案例中,我们将需要建立一个方法,但不需要清理方法,并且我们将实现两个测试用例。
  defsetUp(self):
  self。originallistlist(range(10))
  我们已经使用了unittest。TestCase。setUp()方法来创建单独的测试数据片段。
  deftestlistsucceed(self):
  itemsself。originallist〔:〕
  withAtomic。Atomic(items)asatomic:
  atomic。append(1999)
  atomic。insert(2,915)
  delatomic〔5〕
  atomic〔4〕782
  atomic。insert(0,9)
  self。assertEqual(items,
  〔9,0,1,915,2,782,5,6,7,8,9,1999〕)
  deftestlistfail(self):
  itemsself。originallist〔:〕
  withself。assertRaises(AttributeError):
  withAtomic。Atomic(items)asatomic:
  atomic。append(1999)
  atomic。insert(2,915)
  delatomic〔5〕
  atomic〔4〕782
  atomic。poop()Typo
  self。assertListEqual(items,self。originallist)
  这里,我们直接在测试方法中编写了测试代码,而不需要一个内部函数,也不再使用unittest。TestCase。assertRaised()作为上下文管理器(期望代码产生AttributeError)。最后我们也使用了Python3。1的unittest。TestCase。assertListEqual()方法。
  正如我们已经看到的,Python的测试模块易于使用,并且极为有用,在我们使用TDD的情况下更是如此。它们还有比这里展示的要多得多的大量功能与特征比如,跳过测试的能力,这有助于理解平台差别并且这些都有很好的文档支持。缺失的一个功能但nose与py。test提供了是测试发现,尽管这一特征被期望在后续的Python版本(或许与Python3。2起)中出现。
  性能剖析(Profiling)
  如果程序运行很慢,或者消耗了比预期内要多得多的内存,那么问题通常是选择的算法或数据结构不合适,或者是以低效的方式进行实现。不管问题的原因是什么,最好的方法都是准确地找到问题发生的地方,而不只是检査代码并试图对其进行优化。随机优化会导致引入bug,或者对程序中本来对程序整体性能并没有实际影响的部分进行提速,而这并非解释器耗费大部分时间的地方。
  在深入讨论profiling之前,注意一些易于学习和使用的Python程序设计习惯是有意义的,并且对提高程序性能不无裨益。这些技术都不是特定于某个Python版本的,而是合理的Python程序设计风格。第一,在需要只读序列时,最好使用元组而非列表;第二,使用生成器,而不是创建大的元组和列表并在其上进行迭代处理;第三,尽量使用Python内置的数据结构dicts、lists、tuples而不实现自己的自定义结构,因为内置的数据结构都是经过了高度优化的;第四,从小字符串中产生大字符串时,不要对小字符串进行连接,而是在列表中累积,最后将字符串列表结合成为一个单独的字符串;第五,也是最后一点,如果某个对象(包括函数或方法)需要多次使用属性进行访问(比如访问模块中的某个函数),或从某个数据结构中进行访问,那么较好的做法是创建并使用一个局部变量来访问该对象,以便提供更快的访问速度。
  Python标准库提供了两个特别有用的模块,可以辅助调査代码的性能问题。一个是timeit模块该模块可用于对一小段Python代码进行计时,并可用于诸如对两个或多个特定函数或方法的性能进行比较等场合。另一个是cProfile模块,可用于profile程序的性能该模块对调用计数与次数进行了详细分解,以便发现性能瓶颈所在。
  为了解timeit模块,我们将查看一些小实例。假定有3个函数functiona()、functionb()、functionc(),3个函数执行同样的计算,但分别使用不同的算法。如果将这些函数放于同一个模块中(或分别导入),就可以使用timeit模块对其进行运行和比较。下面给出的是模块最后使用的代码:
  ifnamemain:
  repeats1000
  forfunctionin(functiona,functionb,functionc):
  ttimeit。Timer({0}(X,Y)。format(function),frommainimport{0},X,Y。format(function))
  sect。timeit(repeats)repeats
  print({function}(){sec:。6f}sec。format(locals()))
  赋予timeit。Timer()构造子的第一个参数是我们想要执行并计时的代码,其形式是字符串。这里,该字符串是functiona(X,Y);第二个参数是可选的,还是一个待执行的字符串,这一次是在待计时的代码之前,以便提供一些建立工作。这里,我们从main(即this)模块导入了待测试的函数,还有两个作为输入数据传入的变量(X与Y),这两个变量在该模块中是作为全局变量提供的。我们也可以很轻易地像从其他模块中导入数据一样来进行导入操作。
  调用timeit。Timer对象的timeit()方法时,首先将执行构造子的第二个参数(如果有),之后执行构造子的第一个参数并对其执行时间进行计时。timeit。Timer。timeit()方法的返回值是以秒计数的时间,类型是float。默认情况下,timeit()方法重复100万次,并返回所有这些执行的总秒数,但在这一特定案例中,只需要1000次反复就可以给出有用的结果,因此对重复计数次数进行了显式指定。在对每个函数进行计时后,使用重复次数对总数进行除法操作,就得到了平均执行时间,并在控制台中打印出函数名与执行时间。
  functiona()0。001618sec
  functionb()0。012786sec
  functionc()0。003248sec
  在这一实例中,functiona()显然是最快的至少对于这里使用的输入数据而言。在有些情况下一一比如输入数据不同会对性能产生巨大影响可能需要使用多组输入数据对每个函数进行测试,以便覆盖有代表性的测试用例,并对总执行时间或平均执行时间进行比较。
  有时监控自己的代码进行计时并不是很方便,因此timeit模块提供了一种在命令行中对代码执行时间进行计时的途径。比如,要对MyModule。py模块中的函数functiona()进行计时,可以在控制台中输入如下命令:python3mtimeitn1000sfromMyModuleimportfunctiona,X,Yfunctiona(X,Y)(与通常所做的一样,对Windows环境,我们必须使用类似于C:Python3lpython。exe这样的内容来替换python3)。m选项用于Python解释器,使其可以加载指定的模块(这里是timeit),其他选项则由timeit模块进行处理。n选项指定了循环计数次数,s选项指定了要建立,最后一个参数是要执行和计时的代码。命令完成后,会向控制台中打印运行结果,比如:
  1000loops,bestof3:1。41msecperloop
  之后我们可以轻易地对其他两个函数进行计时,以便对其进行整体的比较。
  cProfile模块(或者profile模块,这里统称为cProfile模块)也可以用于比较函数与方法的性能。与只是提供原始计时的timeit模块不同的是,cProfile模块精确地展示了有什么被调用以及每个调用耗费了多少时间。下面是用于比较与前面一样的3个函数的代码:
  ifnamemain:
  forfunctionin(functiona,functionb,functionc):
  cProfile。run(foriinranged1000):{0}(X,Y)。format(function))
  我们必须将重复的次数放置在要传递给cProfile。run()函数的代码内部,但不需要做任何创建,因为模块函数会使用内省来寻找需要使用的函数与变量。这里没有使用显式的print()语句,因为默认情况下,cProfile。run()函数会在控制台中打印其输出。下面给出的是所有函数的相关结果(有些无关行被省略,格式也进行了稍许调整,以便与页面适应):
  1003functioncallsin1。661CPUseconds
  ncallstottimepercallcumtimepercallfilename:lineno(function)
  10。0030。0031。6611。661:1()
  10001。6580。0021。6580。002MyModule。py:21(functiona)
  10。0000。0001。6611。661{builtinmethodexec}
  5132003functioncallsin22。700CPUseconds
  ncallstottimepercallcumtimepercallfilename:lineno(function)
  10。4870。48722。70022。700:1()
  10000。0110。00022。2130。022MyModule。py:28(functionb)
  51280007。0480。0007。0480。000MyModule。py:29()
  10000。0050。0000。0050。000{builtinmethodbisectjeft}
  10。0000。00022。70022。700{builtinmethodexec}
  10000。0010。0000。0010。000{builtinmethodlen}
  100015。1490。01522。1960。022{builtinmethodsorted}
  5129003functioncallsin12。987CPUseconds
  ncallstottimepercallcumtimepercallfilename:lineno(function)
  10。2050。20512。98712。987:l()
  10006。4720。00612。7820。013MyModule。py:36(functionc)
  51280006。3110。0006。3110。000MyModule。py:37()
  10。0000。00012。98712。987{builtinmethodexec}
  ncalls(调用的次数)列列出了对指定函数(在filename:lineno(function)中列出)的调用次数。回想一下我们重复了1000次调用,因此必须将这个次数记住。tottime(总的时间)列列出了某个函数中耗费的总时间,但是排除了函数调用的其他函数内部花费的时间。第一个percall列列出了对函数的每次调用的平均时间(tottimencalls)。cumtime(累积时间)列出了在函数中耗费的时间,并且包含了函数调用的其他函数内部花费的时间。第二个percall列列出了对函数的每次调用的平均时间,包括其调用的函数耗费的时间。
  这种输出信息要比timeit模块的原始计时信息富有启发意义的多。我们立即可以发现,functionb()与functionc()使用了被调用5000次以上的生成器,使得它们的速度至少要比functiona()慢10倍以上。并且,functionb()调用了更多通常意义上的函数,包括调用内置的sorted()函数,这使得其几乎比functionc()还要慢两倍。当然,timeit()模块提供了足够的信息来查看计时上存在的这些差别,但cProfile模块允许我们了解为什么会存在这些差别。正如timeit模块允许对代码进行计时而又不需要对其监控一样,cProfile模块也可以做到这一点。然而,从命令行使用cProfile模块时,我们不能精确地指定要执行的是什么而只是执行给定的程序或模块,并报告所有这些的计时结果。需要使用的命令行是python3mcProfileprogramOrModule。py,产生的输出信息与前面看到的一样,下面给出的是输出信息样例,格式上进行了一些调整,并忽略了大多数行:
  10272458functioncalls(10272457primitivecalls)in37。718CPUsecs
  ncallstottimepercallcumtimepercallfilename:lineno(function)
  10。0000。00037。71837。718:1()
  10。7190。71937。71737。717:12()
  10001。5690。0021。5690。002:20(functiona)
  10000。0110。00022。5600。023:27(functionb)
  51280007。0780。0007。0780。000:28()
  10006。5100。00712。8250。013:35(functionc)
  51280006。3160。0006。3160。000:36()
  在cProfile术语学中,原始调用指的就是非递归的函数调用。
  以这种方式使用cProfile模块对于识别值得进一步研究的区域是有用的。比如,这里我们可以清晰地看到functionb()需要耗费更长的时间,但是我们怎样获取进一步的详细资料?我们可以使用cProfile。run(functionb())来替换对functionb()的调用。或者可以保存完全的profile数据并使用pstats模块对其进行分析。要保存profile,就必须对命令行进行稍许修改:python3mcProfileoprofileDataFileprogramOrModule。py。之后可以对profile数据进行分析,比如启动IDLE,导入pstats模块,赋予其已保存的profileDataFile,或者也可以在控制台中交互式地使用pstats。
  下面给出的是一个非常短的控制台会话实例,为使其适合页面展示,进行了适当调整,我们自己的输入则以粗体展示:
  python3mcProfileoprofile。datMyModule。py
  python3mpstats
  Welcometotheprofilestatisticsbrowser。
  readprofile。dat
  profile。datcallersfunctionb
  Randomlistingorderwasused
  Listreducedfrom44to1duetorestrictionfunctionb‘
  Functionwascalledby。。。
  ncallstottimecumtime
  :27(functionb)10000。01122。251:12()
  profile。datcalleesfunctionb
  Randomlistingorderwasused
  Listreducedfrom44to1duetorestrictionfunctionb
  Functioncalled。。。
  ncallstottimecumtime
  :27(functionb)
  10000。0050。005builtinmethodbisectJeft
  10000。0010。001builtinmethodlen
  100015。29722。234builtinmethodsorted
  profile。datquit
  输入help可以获取命令列表,help后面跟随命令名可以获取该命令的更多信息。比如,helpstats将列出可以赋予stats命令的参数。还有其他一些可用的工具,可以提供profile数据的图形化展示形式,比如RunSnakeRun(www。vrplumber。comprograinmingrunsnakerun),该工具需要依赖于wxPythonGUI库。
  使用timeit与cProfile模块,我们可以识别出我们自己代码中哪些区域会耗费超过预期的时间;使用cProfile模块,还可以准确算岀时间消耗在哪里。
  以上内容部分摘自视频课程05后端编程Python19调试、测试和性能调优(下),更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。
投诉 评论 转载

会议室高效智能助手明基E540入门级智能投影仪投影仪是商务洽谈、办公会议展示的重要媒介,几乎是每个会议室的标配。但它并不是会议的主角,而是作为会议室中的一个高效助手,帮助汇报人完整的表述方案,高效完成会议内容。现在的……怎样选择一台好的电视机(三)讲完了硬件,我们来讲一讲软件。作为智能电视,这里的软件分为两个部分,一个是基于安卓操作系统的,大多数人都会问的运行内存,内存之类的。比如你这个电视是多大的内存?现在的电视一般来……后端编程Python3调试测试和性能剖析(下)单元测试(UnitTesting)为程序编写测试如果做的到位有助于减少bug的出现,并可以提高我们对程序按预期目标运行的信心。通常,测试并不能保证正确性,因为对大多数程序……对不起,新能源车主,以前是我说话太大声了!最近热度最高的话题之一,想必就是油价的上涨了。毕竟这是一个普通人都不能置身事外的事,对于车主来说更是感同身受。当油价已经成为了影响出行的关键,对于人们生活也会发生潜移默化的影响……Karcher凯驰公司推出的双磁带盒式录音机4波段收音机一个RR512KarcherRR512RadioBoombox(双磁带仓,CDMp3播放器,USB2。0)音响简介生产国家:德国制造商:德国Karcher凯驰公司生产地址:德……为什么说华为是中国最有责任心的民族企业?因为华为是我们中国科研技术的先行者!是我们中国突破美帝国主义封锁的探路者!是我们中国走向富强的领路人!是我们中国未来的希望!是我们中国企业的脊梁!是我们中国企业的舵手!是我们中……HarmonyOS2来了,手里的华为Mate40系列用上后,华为Mate40系列手机发布至今,一直是非常受欢迎的产品。而近日,伴随着华为正式推出了HarmonyOS2并在华为Mate40系列上率先升级,为华为Mate40系列带来了全新的……车顶维权让特斯拉再陷风波!车主此前已多次维权,售后经理回复惹每经编辑:王晓波车顶维权事件再次将特斯拉推上了舆论的风口浪尖。针对这一热点事件及特斯拉公司全球副总裁陶琳回应称没有办法妥协,这是新产品发展必经的一个过程,还强调自己……华为手机那么多系列,哪个系列才是它的真旗舰呢?爱科技爱数码多年玩机经验,欢迎和我交流,点点关注哦华为手机那么多系列,哪个系列才是它的真旗舰呢?毫无疑问,就是Meta系列,有人会要说是P系列,其实不是,从首发最新麒麟芯……这不是科幻,是即将到来的医学突破西班牙《国家报》网站4月6日发表题为《这是医学,不是科幻:即将到来的医学突破》的文章,作者系杰茜卡莫索。全文摘编如下:用天体物理学家和科普作家卡尔萨根的话来说,科学的本质……中公教育终止近39亿定增北京经开区没有校外培训机构通过重审一实习记者陈银霞本周,多家教育机构出现业务变动,DaDa英语全面停止所有外教服务;一起教育科技年底关停K12校外学科培训,转型校内SaaS服务商。中公教育终止了近39亿定增……马斯克叛变,特斯拉停止接受比特币支付!比特币跌破3。9万美元比特币周三大幅下跌,一度跌破39000美元,创2月9日以来新低。其他加密货币也被带崩,截至美东时间周三凌晨1点15分(北京时间中午1点15分),以太坊价格下跌近15,至3……
马斯克的星链计划和中国的5g相比哪个更快?沃派大屏幕防爆电视尽显高端大气人社部工信部联合发布集成电路人工智能云计算等7个国家职业技能JVM垃圾回收怎么样OPPO为什么不用折叠屏,而是选择用卷轴屏做手机?阿里云回应未及时上报Log4j2重大漏洞早期未意识到严重性中国移动2个罕见老板号段,很多人没用见过,记得保存好了你的华为MATE10有什么缺点,什么BUG?2599起!搭载鸿蒙OS的华为WATCH3发布像手机一样强大电子牌照即将运行,送单费估计要涨iPhone1414Max仍采用A15处理器贵州到底有什么?为啥全球科技巨头,抢着把服务器放这里?

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利