[译] Stack Overflow 2016 年度 20 个最佳 Python 问题(一)

JefHinton 8年前
   <p><img src="https://simg.open-open.com/show/0e2241909ff508db7257aa21d384befa.png"></p>    <p>本文翻译自 <a href="/misc/goto?guid=4959736805427924602" rel="nofollow,noindex"> 20 best Python questions at stackoverflow in 2016 </a> (需KX上网)。</p>    <h2><a href="/misc/goto?guid=4959736805513095778" rel="nofollow,noindex">1.使用Pythonic的方式避免“if x: return x”这样的语句 </a></h2>    <p>我依次调用4个方法检查特定的条件,当其一返回真值时立刻返回。</p>    <pre>  <code class="language-python">def check_all_conditions():      x = check_size()      if x:          return x        x = check_color()      if x:          return x        x = check_tone()      if x:          return x        x = check_flavor()      if x:          return x      return None</code></pre>    <p>以上看起来有些冗余,与其使用两行的if语句,我更愿意这样做:</p>    <pre>  <code class="language-python">x and return x</code></pre>    <p>但这在Python中是非法的,我是否错失了一种简单、优雅的解决方法?此外,在上述情况中,这四种检查方法可能带来开销,所以我不想多次调用它们。</p>    <p><strong><回答></strong></p>    <p><strong>1)使用循环</strong></p>    <pre>  <code class="language-python">conditions = (check_size, check_color, check_tone, check_flavor)  for condition in conditions:      result = condition()      if result:          return result</code></pre>    <p>这还有额外的好处,能使条件数可变。</p>    <p><strong>2)使用 <a href="/misc/goto?guid=4959736805597949653" rel="nofollow,noindex"> map() </a> + <a href="/misc/goto?guid=4959736805685902444" rel="nofollow,noindex"> filter() </a> (在Python2中使用 <a href="/misc/goto?guid=4959736805771542407" rel="nofollow,noindex"> future_builtins versions </a> 以获得Python3版本) 获得第一个匹配值 </strong></p>    <pre>  <code class="language-python">try:      # Python 2      from future_builtins import map, filter  except ImportError:      # Python 3      pass    conditions = (check_size, check_color, check_tone, check_flavor)  return next(filter(None, map(lambda f: f(), conditions)), None)</code></pre>    <p>不过这种方式的可读性是有争议的。</p>    <p><strong>3)使用生成器表达式</strong></p>    <pre>  <code class="language-python">conditions = (check_size, check_color, check_tone, check_flavor)  checks = (condition() for condition in conditions)  return next((check for check in checks if check), None)</code></pre>    <p><strong>4)使用 or 连接,这会返回第一个真值或者None(如果没有真值)</strong></p>    <pre>  <code class="language-python">def check_all_conditions():      return check_size() or check_color() or check_tone() or check_flavor() or None</code></pre>    <p>Demo:</p>    <pre>  <code class="language-python">>>> x = [] or 0 or {} or -1 or None  >>> x  -1  >>> x = [] or 0 or {} or '' or None  >>> x is None  True</code></pre>    <h2><a href="/misc/goto?guid=4959736805854893228" rel="nofollow,noindex">2.如何理解Python循环中的“else”子句? </a></h2>    <p>许多Python程序员可能不知道 while 循环和 for 循环的语法包括可选的 else 子句:</p>    <p>else 子句的主体是进行某些种类的清楚动作的好地方,并且在循环正常终止时执行:即,使用 return 或 break 退出循环时则跳过 else 子句;在 continue 后退出则执行它。我知道这个只是因为我在永远想不起来何时执行 else 子句时(再次) <a href="/misc/goto?guid=4959736805933429182" rel="nofollow,noindex"> 查阅了它 </a> 。</p>    <p>总是?顾名思义般在循环的“失败”的时?在正常终止时?即使循环以 return 结束?如果不查的话,我永远不能完全确定。</p>    <p>我怪在关键字选择上持续的不确定性:我发现 else 这个语义简直难以记忆。我的问题不是“为什么是这个关键字用于这个目的”(虽然只阅读了答案和评论,我可能会投票关闭),而是, <strong>我怎样思考 else 关键字以明白其语义,</strong> <strong>那样我就能记住它了?</strong></p>    <p>我相信有相当多关于这一点的讨论。我也可以想象,为了一致性以及不添加Python保留字,选择使用 try 语句和 else 子句(我也必须查找)。也许选择 else 的原因将使它的作用清晰、容易记忆。</p>    <p><strong><回答></strong></p>    <p>一个 if 语句在条件为假的时候运行其 else 语句。同样的,一个 while 循环在条件为假的时候运行其 else 语句。</p>    <p>这条规则匹配了你描述的规则:</p>    <ul>     <li>在正常执行中, while 循环重复运行直至条件为假,因此很自然的退出循环并进入 else 子句。</li>     <li>当执行 break 语句时,会不经条件判断直接退出循环,所以条件就不能为假,也就永远不会执行 else 子句。</li>     <li>当执行 continue 语句时,会再次进行条件判断,然后在循环迭代的开始处正常执行。所以如果条件为真,就接着循环,如果条件为假就运行 else 子句。</li>     <li>其他退出循环的方法,比如说 return ,不会经过条件判断,所以就不会运行 else 子句。</li>    </ul>    <p>for loops behave the same way. Just consider the condition as true if the iterator has more elements, or false otherwise.</p>    <p>for 循环也是一个道理。就是去考虑下如果迭代器还有元素,条件就是真,反之亦然。</p>    <h2><a href="/misc/goto?guid=4959736806022862670" rel="nofollow,noindex">3.如何避免 __init__中 “self.x = x; self.y = y; self.z = z” 这样的模式?</a></h2>    <pre>  <code class="language-python">def __init__(self, x, y, z):      ...      self.x = x      self.y = y      self.z = z      ...</code></pre>    <p>上述这种模式很常见,经常有很多参数。是否有一种好的方式,能否避免这种重复?我应该从 namedtuple 继承吗?</p>    <p><strong><回答></strong></p>    <p>显示地将参数复制到属性中的方式并没什么错误。如果定义了太多参数要这么做,有时意味着代码坏味,你可能需要将这些参数组合成更少的对象。其他时候,这是必要的,而且并无不妥。 <strong>不管怎么说,显示地这么做是对的。</strong></p>    <p>不过,既然问了如何去避免(而不是它是否必要避免),以下是一些解决方案:</p>    <p><strong>1)针对只有关键字参数的情况,可简单使用setattr</strong></p>    <pre>  <code class="language-python">class A:      def __init__(self, **kwargs):          for key in kwargs:            setattr(self, key, kwargs[key])    a = A(l=1, d=2)  a.l # will return 1  a.d # will return 2</code></pre>    <p><strong>2)针对同时有位置参数和关键字参数,使用装饰器</strong></p>    <pre>  <code class="language-python">import decorator  import inspect  import sys      @decorator.decorator  def simple_init(func, self, *args, **kws):      """      @simple_init      def __init__(self,a,b,...,z)          dosomething()        behaves like        def __init__(self,a,b,...,z)          self.a = a          self.b = b          ...          self.z = z          dosomething()      """        #init_argumentnames_without_self = ['a','b',...,'z']      if sys.version_info.major == 2:          init_argumentnames_without_self = inspect.getargspec(func).args[1:]      else:          init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]        positional_values = args      keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)      attribute_values = positional_values + keyword_values_in_correct_order        for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):          setattr(self,attribute_name,attribute_value)        # call the original __init__      func(self, *args, **kws)      class Test():      @simple_init      def __init__(self,a,b,c,d=4):          print(self.a,self.b,self.c,self.d)    #prints 1 3 2 4  t = Test(1,c=2,b=3)  #keeps signature  #prints ['self', 'a', 'b', 'c', 'd']  if sys.version_info.major == 2:      print(inspect.getargspec(Test.__init__).args)  else:      print(inspect.signature(Test.__init__))</code></pre>    <h2><a href="/misc/goto?guid=4959736806107799468" rel="nofollow,noindex">4.为什么Python3中浮点值4*0.1看起来是对的,但是3*0.1则不然? </a></h2>    <p>我知道绝大部分小数没有精确的浮点表示( <a href="/misc/goto?guid=4959736806187324734" rel="nofollow,noindex"> Is floating point math broken? </a> )。</p>    <p>但是我不知道问什么4*0.1能被很好地打印出0.4,但是3*0.1就不行,这两个值用decimal表示时也很丑:</p>    <pre>  <code class="language-python">>>> 3*0.1  0.30000000000000004  >>> 4*0.1  0.4  >>> from decimal import Decimal  >>> Decimal(3*0.1)  Decimal('0.3000000000000000444089209850062616169452667236328125')  >>> Decimal(4*0.1)  Decimal('0.40000000000000002220446049250313080847263336181640625')</code></pre>    <p><strong><回答></strong></p>    <p>简单地说,因为由于量化(舍入)误差的存在,3*0.1 != 0.3(而4*0.1 == 0.4是因为2的幂的乘法通常是一个“精确的”操作)。</p>    <p>你可以在Python中使用 .hex 方法来查看数字的内部表示(基本上,是确切的二进制浮点值,而不是十进制的近似值)。 这可以帮助解释下面发生了什么。</p>    <pre>  <code class="language-python">>>> (0.1).hex()  '0x1.999999999999ap-4'  >>> (0.3).hex()  '0x1.3333333333333p-2'  >>> (0.1*3).hex()  '0x1.3333333333334p-2'  >>> (0.4).hex()  '0x1.999999999999ap-2'  >>> (0.1*4).hex()  '0x1.999999999999ap-2'</code></pre>    <p>0.1是0x1.999999999999a 乘以 2^-4。结尾处的“a”表示数字10 —— 换句话说,二进制浮点中的0.1比“精确”值0.1稍大(因为最终的0x0.99向上舍入为0x0.a)。 当乘以4,也就是2的幂,指数向上移动(从2^-4到2^-2),但是数字不变,所以4*0.1 == 0.4。</p>    <p>但是,当乘以3时,0x0.99和0x0.a0(0x0.07)之间的微小差异放大为0x0.15的错误,在最后一个位置显示为一位错误。 这使得0.1*3大于整值0.3。</p>    <p>Python 3中浮点数的repr设计为可以往返的,也就是说,显示的值应该可以精确地转换为原始值。 因此,它不能以完全相同的方式显示0.3和0.1 * 3,或者两个不同的数字在往返之后是相同的。 所以,Python 3的repr引擎选择显示有轻微的有明显错误的结果。</p>    <h2><a href="/misc/goto?guid=4959736806275783205" rel="nofollow,noindex">5.当前行的Python代码能否知道它的缩进嵌套级别吗? </a></h2>    <p>比如下面这样的:</p>    <pre>  <code class="language-python">print(get_indentation_level())        print(get_indentation_level())            print(get_indentation_level())</code></pre>    <p>我想获取到这样的结果:</p>    <pre>  <code class="language-python">1  2  3</code></pre>    <p>代码能否通过这种方式读取自身?</p>    <p>我想要的是更多的嵌套部分的代码的输出更多的嵌套。 用同的方式,这使得代码更容易阅读,也使输出更容易阅读。</p>    <p>当然我可以手动实现,使用例如 .format(),但我想到的是一个自定义 print 函数,它将print(i*' ' + string),其中i是缩进级别。这会是一个在终端中产生可读输出的快速方法。</p>    <p>有没有更好的、能避免辛苦的手动格式化的方法来做到这一点?</p>    <p><strong><回答></strong></p>    <p>如果你想要嵌套级别的缩进,而不是空格和制表符,事情变得棘手。 例如,在下面的代码中:</p>    <pre>  <code class="language-python">if True:      print(  get_nesting_level())</code></pre>    <p>对get_nesting_level的调用实际上是嵌套1级,尽管事实上在get_nesting_level的调用行前没有空格。同时,在下面的代码中:</p>    <pre>  <code class="language-python">print(1,        2,        get_nesting_level())</code></pre>    <p>对get_nesting_level的调用是嵌套0级,尽管在它所在行前存在空格。</p>    <p>在下面的代码中:</p>    <pre>  <code class="language-python">if True:    if True:      print(get_nesting_level())    if True:      print(get_nesting_level())</code></pre>    <p>对get_nesting_level的两次调用处于不同的嵌套级别,尽管空格数是一样的。</p>    <p>在下面的代码中:</p>    <pre>  <code class="language-python">if True: print(get_nesting_level())</code></pre>    <p>是嵌套0级,还是1级? 在正式语法中,对于INDENT和DEDENT符号,它是0级,但你可能不会有同样的感觉。</p>    <p>如果你想这样做,你将必须符号化整个文件,并为INDENT和DEDENT符号计数。tokenize模块对于这样的函数非常有用的:</p>    <pre>  <code class="language-python">import inspect  import tokenize    def get_nesting_level():      caller_frame = inspect.currentframe().f_back      filename, caller_lineno, _, _, _ = inspect.getframeinfo(caller_frame)      with open(filename) as f:          indentation_level = 0          for token_record in tokenize.generate_tokens(f.readline):              token_type, _, (token_lineno, _), _, _ = token_record              if token_lineno > caller_lineno:                  break              elif token_type == tokenize.INDENT:                  indentation_level += 1              elif token_type == tokenize.DEDENT:                  indentation_level -= 1          return indentation_level</code></pre>    <h2><a href="/misc/goto?guid=4959736806347604641" rel="nofollow,noindex">6.为什么Python的array很慢? </a></h2>    <p>我以为 array.array 比 list 要快,因为array看起来是未装箱的(unboxed)。</p>    <p>然后,我得到了下面的结果:</p>    <pre>  <code class="language-python">In [1]: import array    In [2]: L = list(range(100000000))    In [3]: A = array.array('l', range(100000000))    In [4]: %timeit sum(L)  1 loop, best of 3: 667 ms per loop    In [5]: %timeit sum(A)  1 loop, best of 3: 1.41 s per loop    In [6]: %timeit sum(L)  1 loop, best of 3: 627 ms per loop    In [7]: %timeit sum(A)  1 loop, best of 3: 1.39 s per loop</code></pre>    <p>这种区别的原因是什么?</p>    <p><strong><回答></strong></p>    <p>其存储是“未装箱的”,但每当你访问一个元素的时候,Python必须将它“装箱”(将之嵌入在一个普通的Python对象中),以便做任何事情。 例如,sum(A)遍历了array,并且一次一个地把每个证书装箱到一个普通的Python int对象中。这要花费时间。而在sum(L)中,所有的装箱都已在创建列表时完成了。</p>    <p>所以最后,数组通常较慢,但是相较需要相当少的内存。</p>    <p>----------------------------------------------------------------------------------------------------------------</p>    <p>这是Python 3最近版本的相关代码,但是相同的基本思想适用于所有CPython实现。</p>    <p>以下是访问列表项的代码:</p>    <pre>  <code class="language-python">PyObject *  PyList_GetItem(PyObject *op, Py_ssize_t i)  {      /* error checking omitted */      return ((PyListObject *)op) -> ob_item[i];  }</code></pre>    <p>它做的事很少:somelist [i] 仅仅返回列表中的第i个对象(CPython中的所有Python对象都是指向一个结构体的指针,其初始段符合一个PyObject结构体的结构)。</p>    <p>下面是具有类型代码 l 的 array 的__getitem__实现:</p>    <pre>  <code class="language-python">static PyObject *  l_getitem(arrayobject *ap, Py_ssize_t i)  {      return PyLong_FromLong(((long *)ap->ob_item)[i]);  }</code></pre>    <p>原始内存被视为本地平台的元素为C long(长整型)的向量;第 i 个C long 被读出;然后调用PyLong_FromLong() 将本地的C long 包装(“装箱”)成Python long 对象(在Python 3中,它消除了Python 2中 int 和 long 之间的区别,实际上显示为int)。</p>    <p>这个装箱必须为Python int对象分配新的内存,并将本地的C long的位写入其中。在原例的上下文中,这个对象的生命周期非常短暂(只是足够让sum()将内容添加到总数中),然后需要更多的时间来释放新的int对象。</p>    <p>这就是速度差异的来源,总是来自于,而且总将来自于CPython的实现。</p>    <h2><a href="/misc/goto?guid=4959736806441081801" rel="nofollow,noindex">7.乘以2比移位快? </a></h2>    <p>我本来在看 <a href="/misc/goto?guid=4959736806523923788" rel="nofollow,noindex"> sorted_containers </a> 的源码,然后被 <a href="/misc/goto?guid=4959736806610482145" rel="nofollow,noindex"> 这行 </a> 惊到了:</p>    <pre>  <code class="language-python">self._load, self._twice, self._half = load, load * 2, load >> 1</code></pre>    <p>这里的 load 是一个整数。 为什么在一个位置使用移位,在另一个位乘? 合理的解释似乎是,比特移位可能比整数除以2快,但是为什么不用移位替换乘法呢? 我对以下情况做了基准测试:</p>    <p>#1 (乘法,除法)</p>    <p>#2 (移位,移位)</p>    <p>#3 (乘法,移位)</p>    <p>#4 (移位,除法)</p>    <p>并发现#3 始终比其他方式更快:</p>    <pre>  <code class="language-python"># self._load, self._twice, self._half = load, load * 2, load >> 1    import random  import timeit  import pandas as pd    x = random.randint(10 ** 3, 10 ** 6)    def test_naive():      a, b, c = x, 2 * x, x // 2      def test_shift():      a, b, c = x, x << 1, x >> 1      def test_mixed():      a, b, c = x, x * 2, x >> 1      def test_mixed_swaped():      a, b, c = x, x << 1, x // 2      def observe(k):      print(k)      return {          'naive': timeit.timeit(test_naive),          'shift': timeit.timeit(test_shift),          'mixed': timeit.timeit(test_mixed),          'mixed_swapped': timeit.timeit(test_mixed_swaped),      }      def get_observations():      return pd.DataFrame([observe(k) for k in range(100)])</code></pre>    <p><img src="https://simg.open-open.com/show/8d3e3a202587ce3102a7416ce0f9cdb5.png"> <img src="https://simg.open-open.com/show/69ac688b23451c5e672564f9415ef6e8.png"></p>    <p>问题:</p>    <p>我的测试有效吗? 如果是,为什么(乘法,移位)比(移位,移位)快?我是在Ubuntu 14.04上运行Python 3.5。</p>    <p>以上是问题的原始声明。 Dan Getz在他的回答中提供了一个很好的解释。</p>    <p>为了完整性,以下是不应用乘法优化时,用更大x的示例说明。</p>    <p><img src="https://simg.open-open.com/show/c93d9fe8bba321a143eb03bfa2bf92ff.png"> <img src="https://simg.open-open.com/show/974a2f4d353ca189b2803e2e8310909d.png"></p>    <p><strong><回答></strong></p>    <p>这似乎是因为小数字的乘法在CPython 3.5中得到优化,而小数字的左移则没有。正左移总是创建一个更大的整数对象来存储结果,作为计算的一部分,而对于测试中使用的排序的乘法,特殊的优化避免了这一点,并创建了正确大小的整数对象。这可以在 <a href="/misc/goto?guid=4959736806691931123" rel="nofollow,noindex"> Python的整数实现的源代码 </a> 中看到。</p>    <p>因为Python中的整数是任意精度的,所以它们被存储为整数“数字(digits)”的数组,每个整数数字的位数有限制。所以在一般情况下,涉及整数的操作不是单个操作,而是需要处理多个“数字”。在 <em>pyport.h</em> 中,该位限制在64位平台上 <a href="/misc/goto?guid=4959736806769693512" rel="nofollow,noindex"> 定义为 </a> 30位,其他的为15位。 (这里我将使用30,以使解释简单。但是请注意,如果你使用的Python编译为32位,你的基准的结果将取决于如果 x 是否小于32,768。</p>    <p>当操作的输入和输出保持在该30位限制内时,会以优化的方式而不是通常的方式来处理操作。整数乘法实现的开头如下:</p>    <pre>  <code class="language-python">static PyObject *  long_mul(PyLongObject *a, PyLongObject *b)  {      PyLongObject *z;        CHECK_BINOP(a, b);         / *单位乘法的快速路径* /      if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {          stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);  #ifdef HAVE_LONG_LONG          return PyLong_FromLongLong((PY_LONG_LONG)v);  #else          / *如果没有long long,我们几乎肯定             使用15位数字,所以 v 将适合 long。在             不太可能发生的情况中,没有long long,             我们在平台上使用30位数字,一个大 v              会导致我们使用下面的一般乘法代码。 * /          if (v >= LONG_MIN && v <= LONG_MAX)              return PyLong_FromLong((long)v);  #endif      }</code></pre>    <p>因此,当乘以两个整数(每个整数适用于30位数字)时,这会由CPython解释器进行的直接乘法,而不是将整数作为数组。(对一个正整数对象调用的 <a href="/misc/goto?guid=4959736806850360818" rel="nofollow,noindex"> MEDIUM_VALUE() </a> 会得到其前30位数字。)如果结果符合一个30位数字, <a href="/misc/goto?guid=4959736806935555211" rel="nofollow,noindex"> PyLong_FromLongLong() </a> 将在相对较少的操作中注意到这一点,并创建一个单数字整数对象来存储它。</p>    <p>相反,左移位不是这样优化的,每次左移位会把整数当做一个数组来处理。特别地,如果你阅读 <a href="/misc/goto?guid=4959736807027089157" rel="nofollow,noindex"> long_lshift() </a> 的源码,在一个小且正的左移位的情况下,如果只需把它的长度截断成1,总会创建一个2位数的整数对象:</p>    <pre>  <code class="language-python">static PyObject *  long_lshift(PyObject *v, PyObject *w)  {      /*** ... ***/        wordshift = shiftby / PyLong_SHIFT;   /*** 对于小w,是0 ***/      remshift  = shiftby - wordshift * PyLong_SHIFT;   /*** 对于小w,是w ***/        oldsize = Py_ABS(Py_SIZE(a));   /*** 对于小v > 0,是1 ***/      newsize = oldsize + wordshift;      if (remshift)          ++newsize;   /*** 对于 w > 0, v > 0,newsize至少会变成2 ***/      z = _PyLong_New(newsize);        /*** ... ***/  }</code></pre>    <p><strong>整数除法</strong></p>    <p>你没有问整数整除相比于右位移哪种性能更差,因为这符合你(和我)的期望。但是将小的正数除以另一个小的正数并不像小乘法那样优化。每个 // 使用函数 <a href="/misc/goto?guid=4959736807111898306" rel="nofollow,noindex"> long_divrem() </a> 计算商和余数。这个余数是通过小除数的 <a href="/misc/goto?guid=4959736807198533208" rel="nofollow,noindex"> 乘法 </a> 得到的,并存储在新分配的整数对象中。在这种情况下,它会立即被丢弃。</p>    <h2><a href="/misc/goto?guid=4959736807276665668" rel="nofollow,noindex">8.Python中 "(1,) == 1," 的意思是什么? </a></h2>    <p>我在测试元组结构,然后发现像下面这样使用 == 操作符时很奇怪:</p>    <pre>  <code class="language-python">>>>  (1,) == 1,  Out: (False,)</code></pre>    <p>当我把这两个表达式赋值给变量,结果又是真值:</p>    <pre>  <code class="language-python">>>> a = (1,)  >>> b = 1,  >>> a==b  Out: True</code></pre>    <p>这个问题在我看来不同于 <a href="/misc/goto?guid=4959736807351358967" rel="nofollow,noindex"> Python元组尾逗号的语法规则 </a> ,我是问 == 操作符之间的表达式组。</p>    <p><strong><回答></strong></p>    <p>这是操作符优先级导致的,可阅读 <a href="/misc/goto?guid=4959736807437554235" rel="nofollow,noindex"> 此文档 </a> 。</p>    <p>我来告诉你在下次遇到类似问题的是否怎么查找答案。你可以使用 <a href="/misc/goto?guid=4959736807525597820" rel="nofollow,noindex"> ast </a> 模块解构,来了解表达式怎么解析的:</p>    <pre>  <code class="language-python">>>> import ast  >>> source_code = '(1,) == 1,'  >>> print(ast.dump(ast.parse(source_code), annotate_fields=False))  Module([Expr(Tuple([Compare(Tuple([Num(1)], Load()), [Eq()], [Num(1)])], Load()))])</code></pre>    <p>从这里我们看到代码解析成如 <a href="/misc/goto?guid=4959736807616583133" rel="nofollow,noindex"> Tim Peters解释 </a> 的那样:</p>    <pre>  <code class="language-python">Module([Expr(      Tuple([          Compare(              Tuple([Num(1)], Load()),               [Eq()],               [Num(1)]          )      ], Load())  )])</code></pre>    <h2><a href="/misc/goto?guid=4959736807685974459" rel="nofollow,noindex">9.Python中什么时候 hash(n) == n? </a></h2>    <p>我一直在玩Python的 <a href="/misc/goto?guid=4959736807774463993" rel="nofollow,noindex"> hash函数 </a> 。对于小整数,hash(n)== n 总是成立的。但是对于大数字则不然:</p>    <pre>  <code class="language-python">>>> hash(2**100) == 2**100  False</code></pre>    <p>我不感到惊讶,我理解hash取有限范围的值。这个范围是多少呢?</p>    <p>我试图使用 <a href="/misc/goto?guid=4959736807855632047" rel="nofollow,noindex"> 二分查找 </a> 找出使 hash(n) != n 的最小数字</p>    <pre>  <code class="language-python">>>> import codejamhelpers # pip install codejamhelpers  >>> help(codejamhelpers.binary_search)  Help on function binary_search in module codejamhelpers.binary_search:    binary_search(f, t)      Given an increasing function :math:`f`, find the greatest non-negative integer :math:`n` such that :math:`f(n) \le t`. If :math:`f(n) > t` for all :math:`n \ge 0`, return None.    >>> f = lambda n: int(hash(n) != n)  >>> n = codejamhelpers.binary_search(f, 0)  >>> hash(n)  2305843009213693950  >>> hash(n+1)  0</code></pre>    <p>2305843009213693951有什么特别之处?我注意到它小于sys.maxsize == 9223372036854775807</p>    <p>编辑:我使用的是Python 3。我在Python 2上运行相同的二分查找,得到的是不同的结果2147483648,我注意到这等于 sys.maxint + 1。</p>    <p>我也使用了 [hash(random.random()) for i in range(10 ** 6)] 来估计hash函数的范围。最大值始终低于上面的n。对比min,Python 3的hash值似乎总是正的,而Python 2的hash值可以是负的。</p>    <p><strong><回答></strong></p>    <p>2305843009213693951是2^61-1,它是最大的Mersenne素数,适合64位。</p>    <p>如果你必须通过使用hash产生一个数,并除以某个数来取余,那么大的Mersenne素数是一个好的选择 —— 它很容易计算,并确保均匀分布的可能性。 (虽然我个人不会这样做hash)</p>    <p>用它来计算浮点数的模量特别方便。 它们具有将整数乘以2^x的指数分量。 由于2^61 = 1 mod 2^61-1,你只需要考虑 "(指数) mod 61"。</p>    <p>阅读: <a href="/misc/goto?guid=4959736807939391587" rel="nofollow,noindex"> Mersenne prime - Wikipedia </a></p>    <h2><a href="/misc/goto?guid=4959736808027954596" rel="nofollow,noindex">10.Python的字符串中为什么3个反斜杠和4个反斜杠是相等的? </a></h2>    <p>能告诉我为什么 '?\\\?'=='?\\\\?' 的结果是 True 吗?</p>    <pre>  <code class="language-python">>>> list('?\\\?')  ['?', '\\', '\\', '?']  >>> list('?\\\\?')  ['?', '\\', '\\', '?']</code></pre>    <p><strong><回答></strong></p>    <p>基本上是因为Python在反斜杠处理略微宽松。 引自 <a href="/misc/goto?guid=4959736808106947817" rel="nofollow,noindex"> 2.4.1 String literals </a> :</p>    <p>与标准C不同,所有无法识别的转义序列在字符串中保持不变,比如,反斜杠会留在字符串中。</p>    <p>(原文强调)</p>    <p>因此,在python中,不是3个反斜杠等于4个。而是当你在反斜杠后跟类似“?”这样的字符,这两个会一起作为两个字符。因为 “\?” 不是可识别的转义序列。</p>    <p> </p>    <p>来自:https://zhuanlan.zhihu.com/p/25020763</p>    <p> </p>