问:

据我了解,range() 函数实际上是 an object type in Python 3,它动态生成其内容,类似于生成器。

在这种情况下,我预计以下行会花费过多的时间,因为为了确定 1 万亿是否在该范围内,必须生成 1 万亿值:

1_000_000_000_000_000 in range(1_000_000_000_000_001)

此外:似乎无论我添加多少个零,计算或多或少都需要相同的时间(基本上是瞬时的)。

我也尝试过这样的事情,但计算仍然几乎是即时的:

# count by tens
1_000_000_000_000_000_000_000 in range(0,1_000_000_000_000_000_000_001,10)

如果我尝试实现我自己的范围函数,结果不是那么好!

def my_crappy_range(N):i = 0while i < N:yield ii += 1return

range() 对象在幕后做了什么让它如此之快?

选择 Martijn Pieters’s answer 是因为它的完整性,但另请参阅 abarnert’s first answer 以很好地讨论 range 在 Python 3 中成为一个成熟的序列 的意义,以及一些信息/警告关于跨 Python 实现的 contains 函数优化的潜在不一致。 abarnert’s other answer 进行了更详细的介绍,并为那些对 Python 3 中的优化背后的历史感兴趣(以及 Python 2 中 xrange 缺乏优化)感兴趣的人提供了链接。答案 by poke 和 by wim 为感兴趣的人提供了相关的 C 源代码和解释。

答1:

HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com

Python 3 range() 对象不会立即产生数字;它是一个智能的sequence object,可以按需生成数字。它只包含您的开始、停止和步长值,然后当您迭代对象时,每次迭代都会计算下一个整数。

该对象还实现了 object.contains hook,并且计算您的数字是否在其范围内。计算是(接近)恒定时间操作*。永远不需要扫描范围内所有可能的整数。

从 range() object documentation:

范围类型优于常规列表或元组的优点是范围对象将始终占用相同(少量)的内存,无论它代表的范围大小(因为它只存储开始、停止和步长值,根据需要计算单个项目和子范围)。

因此,您的 range() 对象至少会:

class my_range:def __init__(self, start, stop=None, step=1, /):if stop is None:start, stop = 0, startself.start, self.stop, self.step = start, stop, stepif step < 0:lo, hi, step = stop, start, -stepelse:lo, hi = start, stopself.length = 0 if lo > hi else ((hi - lo - 1) // step) + 1def __iter__(self):current = self.startif self.step < 0:while current > self.stop:yield currentcurrent += self.stepelse:while current < self.stop:yield currentcurrent += self.stepdef __len__(self):return self.lengthdef __getitem__(self, i):if i < 0:i += self.lengthif 0 <= i < self.length:return self.start + i * self.stepraise IndexError('my_range object index out of range')def __contains__(self, num):if self.step < 0:if not (self.stop < num <= self.start):return Falseelse:if not (self.start <= num < self.stop):return Falsereturn (num - self.start) % self.step == 0

这仍然缺少真正的 range() 支持的几件事(例如 .index() 或 .count() 方法、散列、相等测试或切片),但应该会给您一个想法。

我还简化了 contains 实现,只关注整数测试;如果您给一个真正的 range() 对象一个非整数值(包括 int 的子类),则会启动慢速扫描以查看是否存在匹配项,就像您对所有对象的列表使用包含测试一样包含的值。这样做是为了继续支持其他恰好支持整数相等测试但预计也不支持整数算术的数字类型。请参阅实施遏制测试的原始 Python issue。

接近恒定时间,因为 Python 整数是无界的,所以数学运算也会随着 N 的增长而增长,这使得它成为 O(log N) 运算。由于它全部在优化的 C 代码中执行,并且 Python 将整数值存储在 30 位块中,因此由于此处涉及的整数的大小,您会在看到任何性能影响之前耗尽内存。

有趣的事实:因为您有 __getitem__ 和 __len__ 的有效实现,所以 __iter__ 实现实际上是不必要的。

@Lucretiel:In Python 2.3,特别添加了一个特殊的 xrangeiterator,因为它不够快。然后在 3.x 的某个地方(我不确定它是 3.0 还是 3.2)它被扔掉了,它们使用了与 list 使用的相同的 listiterator 类型。

我将构造函数定义为 def __init__(self, *start_stop_step) 并从那里解析出来;现在标记论点的方式有点令人困惑。尽管如此,+1;你仍然明确地解释了这种行为。

@CodyPiersall:实际上,这是 Guido 在 argclinic 讨论中的引述,当时 Nick Coghlan 提出了一种允许明确定义 range 的方法:“请不要让人们更容易复制我最糟糕的设计决定。”所以,我很确定他同意 range 的书面形式令人困惑。

@KarlKnechtel 你无法预测其他类型的行为,句号。不能保证 range 传递了一个实际的数字类型。仅仅将参数转换为 int 是不够的,为什么还要使用自定义类型呢?是否使用 int(custom_type) in range(....) 由开发人员决定。

答2:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

这里的基本误解是认为 range 是一个生成器。它不是。事实上,它不是任何类型的迭代器。

你可以很容易地告诉这个:

>>> a = range(5)
>>> print(list(a))
[0, 1, 2, 3, 4]
>>> print(list(a))
[0, 1, 2, 3, 4]

如果它是一个生成器,迭代一次就会耗尽它:

>>> b = my_crappy_range(5)
>>> print(list(b))
[0, 1, 2, 3, 4]
>>> print(list(b))
[]

range 实际上是一个序列,就像一个列表。你甚至可以测试这个:

>>> import collections.abc
>>> isinstance(a, collections.abc.Sequence)
True

这意味着它必须遵循序列的所有规则:

>>> a[3]         # indexable
3
>>> len(a)       # sized
5
>>> 3 in a       # membership
True
>>> reversed(a)  # reversible>>> a.index(3)   # implements 'index'
3
>>> a.count(3)   # implements 'count'
1

range 和 list 的区别在于 range 是 lazy 或 dynamic 序列;它不会记住它的所有值,它只记住它的 start、stop 和 step,并在 getitem 上按需创建值。

(附带说明一下,如果您是 print(iter(a)),您会注意到 range 使用与 list 相同的 listiterator 类型。这是如何工作的?listiterator 没有使用任何关于 {4 } 除了它提供了 getitem 的 C 实现,因此它也适用于 range。)

现在,没有什么说 Sequence.contains 必须是常数时间 - 事实上,对于像 list 这样的序列的明显示例,它不是。但是没有什么可以说它不可能。并且实现 range.contains 以仅在数学上检查它((val - start) % step,但处理负面步骤有一些额外的复杂性)比实际生成和测试所有值更容易,所以为什么不应该 它做得更好吗?

但是语言中似乎没有任何东西可以保证这会发生。正如 Ashwini Chaudhari 指出的那样,如果你给它一个非整数值,而不是转换为整数并进行数学测试,它将退回到迭代所有值并一一比较它们。仅仅因为 CPython 3.2+ 和 PyPy 3.x 版本恰好包含这种优化,而且这显然是一个好主意并且很容易做到,IronPython 或 NewKickAssPython 3.x 没有理由不能忽略它。 (事实上,CPython 3.0-3.1 并没有包含它。)

如果 range 实际上是一个生成器,例如 my_crappy_range,那么以这种方式测试 contains 是没有意义的,或者至少它有意义的方式并不明显。如果您已经迭代了前 3 个值,那么 1 仍然是 in 生成器吗?对 1 的测试是否应该导致它迭代并使用直到 1(或直到第一个值 >= 1)的所有值?

这是一件非常重要的事情。我想 Python 2 和 3 之间的差异可能导致我在这一点上感到困惑。无论如何,我应该意识到since range is listed (along with list and tuple) as a sequence type。

@RickTeachey:实际上,在 2.6+(我认为;也许是 2.5+)中,xrange 也是一个序列。请参阅2.7 docs。事实上,它总是一个几乎序列。

@RickTeachey:实际上,我错了;在 2.6-2.7(和 3.0-3.1)中,它声称是一个序列,但它仍然只是一个几乎序列。请参阅我的另一个答案。

它不是一个迭代器,它是一个序列(Java 中的 Iterable,C# 中的 IEnumerable)——带有 .__iter__() 方法的东西将返回一个迭代器。反过来,它只能使用一次。

@ThomasAhle:因为 range 不是整数时不检查类型,因为类型总是有可能具有与 int 兼容的 __eq__。当然,str 显然行不通,但他们不想通过显式检查所有 不能 的类型来减慢速度(毕竟,str子类可以覆盖 __eq__ 并包含在 range 中)。

答3:

huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。

使用 source,卢克!

在 CPython 中,range(…).contains(一个方法包装器)最终将委托给一个简单的计算,该计算检查该值是否可能在该范围内。这里速度的原因是我们使用关于边界的数学推理,而不是范围对象的直接迭代。解释使用的逻辑:

检查数字是否在开始和停止之间,并检查步幅值是否“跨过”我们的数字。

例如,994 位于 range(4, 1000, 2) 中,因为:

4 <= 994 < 1000,并且 (994 - 4) % 2 == 0。

完整的 C 代码包含在下面,由于内存管理和引用计数的细节,这有点冗长,但基本的想法是存在的:

static int
range_contains_long(rangeobject *r, PyObject *ob)
{int cmp1, cmp2, cmp3;PyObject *tmp1 = NULL;PyObject *tmp2 = NULL;PyObject *zero = NULL;int result = -1;zero = PyLong_FromLong(0);if (zero == NULL) /* MemoryError in int(0) */goto end;/* Check if the value can possibly be in the range. */cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);if (cmp1 == -1)goto end;if (cmp1 == 1) { /* positive steps: start <= ob < stop */cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);}else { /* negative steps: stop < ob <= start */cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);}if (cmp2 == -1 || cmp3 == -1) /* TypeError */goto end;if (cmp2 == 0 || cmp3 == 0) { /* ob outside of range */result = 0;goto end;}/* Check that the stride does not invalidate ob's membership. */tmp1 = PyNumber_Subtract(ob, r->start);if (tmp1 == NULL)goto end;tmp2 = PyNumber_Remainder(tmp1, r->step);if (tmp2 == NULL)goto end;/* result = ((int(ob) - start) % step) == 0 */result = PyObject_RichCompareBool(tmp2, zero, Py_EQ);end:Py_XDECREF(tmp1);Py_XDECREF(tmp2);Py_XDECREF(zero);return result;
}static int
range_contains(rangeobject *r, PyObject *ob)
{if (PyLong_CheckExact(ob) || PyBool_Check(ob))return range_contains_long(r, ob);return (int)_PySequence_IterSearch((PyObject*)r, ob,PY_ITERSEARCH_CONTAINS);
}

the line 中提到了这个想法的“肉”:

/* result = ((int(ob) - start) % step) == 0 */ 

最后一点 - 查看代码片段底部的 range_contains 函数。如果确切的类型检查失败,那么我们不使用所描述的聪明算法,而是使用 _PySequence_IterSearch 回退到范围的愚蠢迭代搜索!您可以在解释器中检查此行为(我在这里使用 v3.5.0):

>>> x, r = 1000000000000000, range(1000000000000001)
>>> class MyInt(int):
...     pass
...
>>> x_ = MyInt(x)
>>> x in r  # calculates immediately :)
True
>>> x_ in r  # iterates for ages.. :(
^\Quit (core dumped)

答4:

huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!

添加到 Martijn 的答案中,这是 the source 的相关部分(在 C 中,因为范围对象是用本机代码编写的):

static int
range_contains(rangeobject *r, PyObject *ob)
{if (PyLong_CheckExact(ob) || PyBool_Check(ob))return range_contains_long(r, ob);return (int)_PySequence_IterSearch((PyObject*)r, ob,PY_ITERSEARCH_CONTAINS);
}

因此,对于 PyLong 对象(在 Python 3 中为 int),它将使用 range_contains_long 函数来确定结果。该函数本质上检查 ob 是否在指定范围内(尽管它在 C 中看起来有点复杂)。

如果它不是 int 对象,它会退回到迭代直到找到值(或没有)。

整个逻辑可以像这样翻译成伪 Python:

def range_contains (rangeObj, obj):if isinstance(obj, int):return range_contains_long(rangeObj, obj)# default logic by iteratingreturn any(obj == x for x in rangeObj)def range_contains_long (r, num):if r.step > 0:# positive step: r.start <= num < r.stopcmp2 = r.start <= numcmp3 = num < r.stopelse:# negative step: r.start >= num > r.stopcmp2 = num <= r.startcmp3 = r.stop < num# outside of the range boundariesif not cmp2 or not cmp3:return False# num must be on a valid step inside the boundariesreturn (num - r.start) % r.step == 0

答5:

huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。

如果您想知道为什么将此优化添加到 range.contains,以及为什么在 2.7 中没有将它添加到 xrange.contains

首先,正如 Ashwini Chaudhary 所发现的,issue 1766304 被明确打开以优化 [x]range.contains。对此的补丁是 accepted and checked in for 3.2,但没有向后移植到 2.7,因为“xrange 的行为已经很长时间了,我看不出我们这么晚才提交补丁有什么好处。” (那时 2.7 差不多了。)

同时:

最初,xrange 是一个不完全序列的对象。正如 the 3.1 docs 所说:

Range 对象的行为很少:它们只支持索引、迭代和 len 函数。

这并不完全正确。 xrange 对象实际上支持索引和 len,* 自动附带的其他一些东西,包括 contains(通过线性搜索)。但当时没有人认为值得制作完整的序列。

然后,作为实现 Abstract Base Classes PEP 的一部分,重要的是要弄清楚哪些内置类型应该被标记为实现了哪些 ABC,并且 xrange/range 声称实现了 collections.Sequence,尽管它仍然只处理同样的“很少的行为”。在 issue 9213 之前没有人注意到这个问题。该问题的补丁不仅将 index 和 count 添加到 3.2 的 range 中,还重新设计了优化的 contains(与 index 共享相同的数学,并直接由 { 8}).** This change 也适用于 3.2,并且没有向后移植到 2.x,因为“这是一个添加新方法的错误修复”。 (此时,2.7 已经过了 rc 状态。)

因此,有两次机会将此优化向后移植到 2.7,但都被拒绝了。

  • 实际上,您甚至可以通过单独的索引免费获得迭代,但是 in 2.3 xrange 对象有一个自定义迭代器。

** 第一个版本实际上重新实现了它,但细节错误——例如,它会给你 MyIntSubclass(2) in range(5) == False。但 Daniel Stutzbach 的补丁更新版本恢复了大部分以前的代码,包括回退到 3.2 之前的 range.contains 在优化不适用时隐式使用的通用、缓慢的 _PySequence_IterSearch。

从这里的评论来看:improve xrange.__contains__,看起来他们没有将它反向移植到 Python 2 只是为了给用户留下一个惊喜元素,而且为时已晚 o_O。 count 和 index patch 是后来添加的。当时的文件:hg.python.org/cpython/file/d599a3f2e72d/Objects/rangeobject.c

我有一个险恶的怀疑,一些核心 python 开发人员偏爱对 python 2.x 的“严厉的爱”,因为他们想鼓励人们切换到更优越的 python3 :)

此外,我敢打赌,必须向旧版本添加新功能是一个巨大的负担。想象一下,如果您去 Oracle 并说:“看,我在 Java 1.4 上,我应该得到 lambda 表达式!免费向后移植它们。”

@RickTeachey 是的,这只是一个例子。如果我说 1.7,它仍然适用。这是数量上的差异,而不是质量上的差异。基本上(无偿)开发人员不能永远在 3.x 中制作很酷的新东西,并将其向后移植到 2.x 以供那些不想升级的人使用。这是一个巨大而荒谬的负担。你觉得我的推理还有问题吗?

@RickTeachey:2.7 介于 3.1 和 3.2 之间,而不是 3.3 左右。这意味着当对 3.2 的最后更改进入时,2.7 在 rc 中,这使得错误注释更容易理解。无论如何,我认为他们在回想起来时犯了一些错误(特别是假设人们会在 six 之类的库的帮助下通过 2to3 而不是通过双版本代码进行迁移,这就是为什么我们得到像 dict.viewkeys 这样的东西没有人永远不会使用),并且在 3.2 中出现了一些为时已晚的更改,但在大多数情况下,2.7 是一个令人印象深刻的“最后一个 2.x”版本。

答6:

一个优秀的自由职业者,应该有对需求敏感和精准需求捕获的能力,而huntsbot.com提供了这个机会

其他答案已经很好地解释了它,但我想提供另一个实验来说明范围对象的性质:

>>> r = range(5)
>>> for i in r:print(i, 2 in r, list(r))0 True [0, 1, 2, 3, 4]
1 True [0, 1, 2, 3, 4]
2 True [0, 1, 2, 3, 4]
3 True [0, 1, 2, 3, 4]
4 True [0, 1, 2, 3, 4]

如您所见,range 对象是一个可以记住其范围并且可以多次使用(甚至在对其进行迭代时)的对象,而不仅仅是一次性生成器。

答7:

huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。

这完全是关于评估的惰性方法和range的一些额外优化。范围内的值在实际使用之前不需要计算,甚至由于额外的优化而进一步计算。

顺便说一句,您的整数不是那么大,请考虑 sys.maxsize

sys.maxsize in range(sys.maxsize) 相当快

由于优化 - 很容易将给定的整数与范围的最小值和最大值进行比较。

但:

Decimal(sys.maxsize) in range(sys.maxsize) 很慢。

(在这种情况下,range中没有优化,所以如果python收到意外的Decimal,python会比较所有数字)

您应该了解实现细节,但不应依赖它,因为将来可能会发生变化。

小心浮动大整数。在大多数机器上,float(sys.maxsize) != sys.maxsize) 即使是 sys.maxsize-float(sys.maxsize) == 0。

答8:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

TL;博士

range() 返回的对象实际上是一个 range 对象。该对象实现了迭代器接口,因此您可以按顺序迭代其值,就像生成器、列表或元组一样。

但它也实现了 contains 接口,当对象出现在 in 运算符的右侧时,该接口实际上会被调用。 contains() 方法返回 in 左侧的项目是否在对象中的 bool。由于 range 对象知道它们的边界和步幅,这在 O(1) 中很容易实现。

答9:

huntsbot.com – 高效赚钱,自由工作

由于优化,很容易将给定的整数与最小和最大范围进行比较。在 Python3 中 range() 函数如此之快的原因是,这里我们使用数学推理来计算边界,而不是直接迭代 range 对象。所以为了解释这里的逻辑:

检查数字是否在开始和停止之间。

检查步长精度值是否不超过我们的数字。

举个例子,997 在 (4, 1000, 3) 范围内,因为:4 <= 997 < 1000,并且 (997 - 4) % 3 == 0。

你能分享一下来源吗?即使这听起来是合法的,用实际的代码来支持这些声明也是很好的

我认为这是可以实施的一个例子。不是它的实现方式。虽然没有提供参考,但它是一个很好的提示,足以理解为什么包含检查范围比列表或元组快得多

答10:

huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。

对较大的 x 值尝试 x-1 in (i for i in range(x)),它使用生成器推导来避免调用 range.contains 优化。

答11:

huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式

TLDR; range 是一个算术级数,因此它可以很容易地计算出对象是否存在。如果它像列表一样很快,它甚至可以得到它的索引。

原文链接:https://www.huntsbot.com/qa/aLR6/why-is-1000000000000000-in-range1000000000000001-so-fast-in-python-3?lang=zh_CN&from=csdn

HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com

为什么“1000000000000000 in range(1000000000000001)”在 Python 3 中如此之快?相关推荐

  1. python 类中定义类_Python中的动态类定义

    python 类中定义类 Here's a neat Python trick you might just find useful one day. Let's look at how you ca ...

  2. python语言中一切皆对象_2 python 中一切皆对象

    python 中一皆对象 在 python 编程语言中所有的一切都是对象 , python 语言相对于 C++ 或 java 这样的语言 (静态语言), 面向对象这个思想更加的彻底. 2.1 函数和类 ...

  3. 在python程序中的进程操作

    ********在python程序中的进程操作********之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了, 运行中的程序就是一个进程.所有的进程都是通过 ...

  4. python列表中的冒号_python 列表中[ ]中冒号‘:’的作用

    中括号[ ]:用于定义列表或引用列表.数组.字符串及元组中元素位置 list1 = ['physics', 'chemistry', 1997, 2000] list2 = [1, 2, 3, 4, ...

  5. python3在线-Python 3中的新功能

    教 程 目 录 __future__模块 Python 3.x介绍了一些Python 2不兼容的关键字和功能,可以通过Python 2中内置的__future__模块导入 .如果您计划对代码进行Pyt ...

  6. python面试常见问题-Python面试中常见的40个问题

    1)什么是Python?使用Python有什么好处? Python是一种编程语言,包含对象,模块,线程,异常和自动内存管理.Python的好处在于它简单易用,可移植,可扩展,内置数据结构,并且它是一个 ...

  7. python面试常见问题-Python面试中最常见的25个问题

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. Python是一个面向对象的解释型的交互式高级脚本语言.Python被设计成一种高可读性的语言,因为它大量 ...

  8. python期末考试题及答案翻译-[译]Python面试中8个必考问题

    1.下面这段代码的输出结果是什么?请解释. def extendList(val, list=[]): list.append(val) return list list1 = extendList( ...

  9. python在中小学教学中的应用-在python程序中的进程操作

    ********在python程序中的进程操作********之前我们已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,刚刚我们已经了解了, 运行中的程序就是一个进程.所有的进程都是通过 ...

最新文章

  1. type python_typepython
  2. php parseurl的反函数,字符串修改(处理)函数
  3. mina、netty消息边界问题
  4. 20172317 2018-2019-1 《程序设计与数据结构》 第五周学习总结
  5. 你所阅读的,决定你是什么样的人
  6. python ftplib bufsize_python:ftplib模块
  7. 《HTML5和CSS3快速参考》——1.3HTML5的品牌化
  8. [leetcode] 150. 逆波兰表达式求值
  9. 怎样解决mysql最后一步提示未响应
  10. matlab 2014
  11. 荐一个不错的UI设计网站: uimaker
  12. python使用redis_python redis使用菜鸟教程
  13. 数据结构-栈操作-用链表实现栈基本操作
  14. 001.Heartbeat简介
  15. 买房的疯了-开发商笑不起来了
  16. Java基于JSP的报刊订阅管理系统
  17. 重磅!微丰荣获2021企业微信CRM创新服务产品奖,沃丰科技加快企业数字化进程
  18. python tableau_Tableau集成Python机器学习实践(下)
  19. JAVA第四次作业——使用枚举类对扑克牌建模
  20. iOS动态修改app图标

热门文章

  1. 最新的漆包线生产工艺流程,9000余字细节介绍。让你读懂漆包线流程
  2. 360linux面试题,奇虎360运维工程师面试题
  3. The humbling of Goldman Sachs 高盛走下神坛 | 经济学人20230128版社论高质量双语精翻
  4. Revit 二次开发参照属性
  5. 不同层面禁用PUT、DELETE、HEAD、TRACE、OPTIONS请求方式
  6. 应用先进的传感器和算法进行低成本运动跟踪---凯利讯半导体
  7. Pytorch CNN 手写数字识别 0-9
  8. Cadence PSpice 模型10:ABM库的控制源型模型介绍与使用方法图文演示
  9. Android读取手机ROM总大小方法
  10. React教程(二):React组件基础