标签归档:interview-questions

Interactive-coding-challenges-120+交互式Python编码面试挑战(算法和数据结构)

交互式编码-挑战

Binder

120多项持续更新、交互式和测试驱动的编码挑战,具有Anki flashcards

挑战集中在算法数据结构在以下位置找到编码面试

每个挑战都有一个或多个参考解决方案,它们是:

  • 全功能
  • 单元测试
  • 通俗易懂

Challenges将很快提供按需服务incremental hints以帮助您获得最佳解决方案

笔记本还详细介绍了:

  • 约束条件
  • 测试用例
  • 算法
  • 大O时空复杂性

还包括单元测试的参考实现各式各样的data structuresalgorithms

挑战解决方案

Anki抽认卡:编码与设计

提供的Anki flashcard deck使用间隔重复来帮助您记住关键概念

非常适合在旅途中使用

设计资源:系统设计入门

寻找资源来帮助您准备系统设计面向对象的设计访谈

查看姊妹回购The System Design Primer,其中包含其他Anki甲板:

笔记本结构

每个挑战有两个笔记本,一个挑战笔记本有要解决的单元测试和解决方案笔记本以供参考

问题陈述

  • 陈述要解决的问题

约束条件

  • 描述任何约束或假设。

测试用例

  • 描述将在单元测试中评估的常规测试用例和边缘测试用例。

算法

  • [挑战笔记本]空,如果需要提示,请参阅解决方案笔记本算法部分
  • [解决方案笔记本]一个或多个算法方案讨论,时间和空间复杂度大O

提示

  • [挑战笔记本]提供按需服务incremental hints来帮助你找到最优的解决方案。马上就来!

代码(挑战:实现我!)

  • [挑战笔记本]供您实现的骨架代码
  • [解决方案笔记本]一个或多个参考解决方案

单元测试

  • [挑战笔记本]代码的单元测试。预计在你解决挑战之前都会失败
  • [解决方案笔记本]参考解决方案的单元测试

索引

挑战类别

格式化:挑战类别-挑战数量

挑战总数:120项

参考实现:数据结构

以下数据结构经过单元测试、功能齐全的实现:

参考实现:算法

以下算法经过单元测试、功能齐全的实现:

参考实现:TODO

安装和运行挑战

杂项

挑战

Image Credits

数组和字符串

Binder

挑战 静电笔记本
确定字符串是否包含唯一字符 ChallengeSolution
确定一个字符串是否为另一个字符串的排列 ChallengeSolution
确定一个字符串是否为另一个字符串的旋转 ChallengeSolution
压缩字符串 ChallengeSolution
颠倒字符串中的字符 ChallengeSolution
给定两个字符串,找到单个不同的字符 ChallengeSolution
查找两个总和为特定值的索引 ChallengeSolution
实现哈希表 ChallengeSolution
实施冒泡嗡嗡声 ChallengeSolution
查找字符串中的第一个非重复字符 ContributeContribute
删除字符串中的指定字符 ContributeContribute
颠倒字符串中的单词 ContributeContribute
将字符串转换为整数 ContributeContribute
将整数转换为字符串 ContributeContribute
添加质询 ContributeContribute

链表

Binder

挑战 静电笔记本
从链接列表中删除重复项 ChallengeSolution
查找链表最后一个元素的第k个元素 ChallengeSolution
删除链表中间的节点 ChallengeSolution
围绕给定值对链表进行分区 ChallengeSolution
将数字存储在链表中的两个数字相加 ChallengeSolution
查找链接列表循环的起点 ChallengeSolution
确定链表是否为回文 ChallengeSolution
实现链表 ChallengeSolution
确定列表是循环的还是非循环的 ContributeContribute
添加质询 ContributeContribute

堆栈和队列

Binder

挑战 静电笔记本
使用单个数组实施n个堆栈 ChallengeSolution
实现跟踪其最小元素的堆栈 ChallengeSolution
实现包装容量受限堆栈列表的一组Stacks类 ChallengeSolution
使用两个堆栈实现一个队列 ChallengeSolution
使用另一个堆栈作为缓冲区对堆栈进行排序 ChallengeSolution
实现堆栈 ChallengeSolution
实现一个队列 ChallengeSolution
实现由数组支持的优先级队列 ChallengeSolution
添加质询 ContributeContribute

图形和树

Binder

挑战 静电笔记本
在树上实现深度优先搜索(前、中、后顺序) ChallengeSolution
实现树的广度优先搜索 ChallengeSolution
确定树的高度 ChallengeSolution
从排序数组创建高度最小的二叉搜索树 ChallengeSolution
为二叉树的每个级别创建链表 ChallengeSolution
检查二叉树是否平衡 ChallengeSolution
确定树是否为有效的二叉搜索树 ChallengeSolution
在二叉搜索树中查找给定节点的有序后继节点 ChallengeSolution
查找二叉树中的第二大节点 ChallengeSolution
找到最底层的共同祖先 ChallengeSolution
反转二叉树 ChallengeSolution
实现二叉搜索树 ChallengeSolution
实现最小堆 ChallengeSolution
实现Trie ChallengeSolution
在图上实现深度优先搜索 ChallengeSolution
实现图的广度优先搜索 ChallengeSolution
确定图中的两个节点之间是否存在路径 ChallengeSolution
实现图形 ChallengeSolution
在给定项目和依赖项列表的情况下查找构建顺序 ChallengeSolution
在加权图中查找最短路径 ChallengeSolution
在未加权图中查找最短路径 ChallengeSolution
添加质询 ContributeContribute

排序

Binder

挑战 静电笔记本
实现选择排序 ChallengeSolution
实现插入排序 ChallengeSolution
实现快速排序 ChallengeSolution
实现合并排序 ChallengeSolution
实现基数排序 ChallengeSolution
对字符串数组进行排序,以便所有字谜都相邻 ChallengeSolution
在排序的旋转数组中查找项 ChallengeSolution
在排序矩阵中搜索项目 ChallengeSolution
在n个整数的输入中查找不是的整数 ChallengeSolution
给定排序数组A、B,按排序顺序将B合并到A中 ChallengeSolution
实现稳定选择排序 ContributeContribute
使不稳定排序稳定 ContributeContribute
实现高效的就地版本的快速排序 ContributeContribute
给定两个排序的数组,按排序顺序将一个合并到另一个 ContributeContribute
在经过旋转和排序的整数数组中查找元素 ContributeContribute
添加质询 ContributeContribute

递归与动态规划

Binder

挑战 静电笔记本
递归、动态和迭代地实现Fibonacci ChallengeSolution
最大化放置在背包中的物品 ChallengeSolution
最大化放置在背包中的无界项目 ChallengeSolution
查找最长的公共子序列 ChallengeSolution
找出最长的递增子序列 ChallengeSolution
最小化矩阵乘法的成本 ChallengeSolution
在给定k个交易的情况下最大化股票价格 ChallengeSolution
在给定一组硬币的情况下,找出表示n美分的最小方法数 ChallengeSolution
在给定一组硬币的情况下,找出表示n美分的唯一数量的方法 ChallengeSolution
打印n对括号的所有有效组合 ChallengeSolution
在迷宫中导航 ChallengeSolution
打印集合的所有子集 ChallengeSolution
打印字符串的所有排列 ChallengeSolution
在数组中查找魔术索引 ChallengeSolution
找出运行n个步骤的方式的数量 ChallengeSolution
用3个塔和N个圆盘实现河内之塔 ChallengeSolution
递归、动态和迭代地实现阶乘 ContributeContribute
对已排序的整数数组执行二进制搜索 ContributeContribute
打印字符串的所有组合 ContributeContribute
实现绘画填充功能 ContributeContribute
给出1、5、10、25美分硬币,找出代表n美分的所有排列 ContributeContribute
添加质询 ContributeContribute

数学与概率

Binder

挑战 静电笔记本
生成素数列表 ChallengeSolution
找到数字根 ChallengeSolution
在O(1)中创建支持插入、最大、最小、平均、模式的类 ChallengeSolution
确定一个数字是否为2的幂 ChallengeSolution
将不带+或-号的两个数字相加 ChallengeSolution
减去两个不带+或-号的数字 ChallengeSolution
检查数字是否为质数 ContributeContribute
确定笛卡尔平面上的两条直线是否相交 ContributeContribute
仅使用整数的加法、减法和除法实现乘法、减法和除法 ContributeContribute
找出第k个数,使其唯一的素因数为3、5和7 ContributeContribute
添加质询 ContributeContribute

位操作

Binder

挑战 静电笔记本
实现常见的位操作操作 ChallengeSolution
确定要翻转以将a转换为b的位数 ChallengeSolution
在屏幕上画一条线 ChallengeSolution
翻转一位以最大化最长的1序列 ChallengeSolution
获取下一个最大和下一个最小的数字 ChallengeSolution
合并两个二进制数 ChallengeSolution
交换整数中的奇数位和偶数位 ChallengeSolution
打印介于0和1之间的数字的二进制表示法 ChallengeSolution
确定给定整数的二进制表示中的1的个数 ContributeContribute
添加质询 ContributeContribute

网上评委

Binder

挑战 静电笔记本
查找最多包含k个不同字符的最长子字符串 ChallengeSolution
找出三个数字的最高乘积 ChallengeSolution
通过1次买入和1次卖出最大化股票利润 ChallengeSolution
将列表中的所有零移动到末尾 ChallengeSolution
找出所有其他整数的乘积 ChallengeSolution
给出进场和出场的列表,找出最繁忙的时段 ChallengeSolution
确定岛屿的周长 ChallengeSolution
格式化许可证密钥 ChallengeSolution
查找最长的绝对文件路径 ChallengeSolution
合并元组范围 ChallengeSolution
分配Cookie ChallengeSolution
确定您是否可以在NIM中获胜 ChallengeSolution
检查一本杂志是否可能被用来制作赎金字条 ChallengeSolution
找出一个句子可以在屏幕上显示的次数 ChallengeSolution
乌托邦之树 ChallengeSolution
最大化异或 ChallengeSolution
添加质询 ContributeContribute

回购结构

interactive-coding-challenges        # Repo
├─ arrays_strings                    # Category of challenges
│  ├─ rotation                       # Challenge folder
│  │  ├─ rotation_challenge.ipynb    # Challenge notebook
│  │  ├─ rotation_solution.ipynb     # Solution notebook
│  │  ├─ test_rotation.py            # Unit test*
│  ├─ compress
│  │  ├─ compress_challenge.ipynb
│  │  ├─ compress_solution.ipynb
│  │  ├─ test_compress.py
│  ├─ ...
├─ linked_lists
│  ├─ palindrome
│  │  └─ ...
│  ├─ ...
├─ ...

*笔记本(.ipynb)读/写关联的单元测试(.py)文件

笔记本安装

零安装

Binder

本自述包含指向以下内容的链接Binder,哪些主机动态笔记本不需要安装的在线回购内容

木星笔记本

运行:

pip install jupyter

有关更优化地设置开发环境的详细说明、脚本和工具,请参阅dev-setup回购

有关笔记本安装的更多详细信息,请按照说明操作here

有关IPython/Jupyter笔记本的更多信息,请访问here

跑步挑战

笔记本电脑

挑战以以下形式提供IPython/Jupyter笔记本电脑一直以来都是使用Python 2.7和Python 3.x进行测试

如果您需要安装IPython/Jupyter笔记本,请参阅Notebook Installation部分

  • 本自述包含指向以下内容的链接nbviewer,哪些主机静电笔记本回购的内容
  • 中的元素进行交互或修改动态笔记本,请参阅以下说明

运行挑战笔记本:

$ git clone https://github.com/donnemartin/interactive-coding-challenges.git
$ cd interactive-coding-challenges
$ jupyter notebook

这将启动包含质询类别列表的Web浏览器:

  • 导航到挑战笔记本你想要解决
  • 运行挑战笔记本中的单元格(单元格->全部运行)
    • 这将导致预期的单元测试错误
  • 解决挑战并验证它是否通过了单元测试
  • 查看随附的解决方案笔记本以供进一步讨论

调试您的PDB解决方案,请参阅以下内容ticket

注意:如果您的解决方案与解决方案笔记本中列出的解决方案不同,请考虑提交Pull请求,以便其他人可以从您的工作中受益。回顾Contributing Guidelines有关详细信息,请参阅

未来发展方向

挑战、解决方案和单元测试以IPython/Jupyter笔记本电脑

  • 笔记本目前主要包含Python解决方案(在Python2.7和Python3.x上都进行了测试),但可以进行扩展以包括40+ supported languages
  • 回购将是不断更新新的解决方案和挑战
  • Contributions欢迎光临!

贡献

欢迎投稿!

回顾Contributing Guidelines有关如何执行以下操作的详细信息,请执行以下操作:

  • 提交问题
  • 为现有挑战添加解决方案
  • 添加新的挑战

学分

资源

图像

联系信息

请随时与我联系,讨论任何问题、问题或评论

我的联系信息可以在我的GitHub page

许可证

我在开放源码许可下向您提供此存储库中的代码和资源。因为这是我的个人存储库,您获得的我的代码和资源的许可证来自我,而不是我的雇主(Facebook)

Copyright 2015 Donne Martin

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Wtfpython – Python中的各种坑与违反直觉的奇怪问题

翻译:Chinese 中文|Vietnamese Tiếng Việt|Spanish Español|Add translation

其他模式:Interactive|CLI

Python是一种设计精美的高级和基于解释器的编程语言,它为程序员提供了许多舒适的特性。但有时,Python代码片段的结果乍一看可能并不明显

下面是一个有趣的项目,它试图解释Python中一些违反直觉的代码片段和鲜为人知的功能背后到底发生了什么

虽然您在下面看到的一些示例可能不是真正意义上的WTF,但它们将揭示您可能不知道的Python的一些有趣部分。我发现这是学习编程语言内部的一个很好的方法,我相信您也会发现它很有趣!

如果您是一名经验丰富的Python程序员,那么您可以将其视为一次尝试就能正确完成大部分操作的挑战。你以前可能已经经历过其中的一些,我也许能唤起你甜蜜的旧回忆!😅

PS:如果你是回头客,你可以了解到新的修改here(标有星号的例子为最新一次主要修订中增加的例子)

所以,我们开始吧

目录

示例的结构

所有示例的结构如下:

▶一些花哨的标题

# Set up the code.
# Preparation for the magic...

输出(Python版本):

>>> triggering_statement
Some unexpected output

(可选):一行描述意外输出

💡说明:

  • 简要说明正在发生的事情以及发生的原因

# Set up code
# More examples for further clarification (if necessary)

输出(Python版本):

>>> trigger # some example that makes it easy to unveil the magic
# some justified output

注:所有示例都在Python 3.5.2交互式解释器上进行了测试,除非在输出之前明确指定,否则它们应该适用于所有Python版本

用法

在我看来,最大限度地利用这些例子的一个很好的方法是按时间顺序阅读它们,并针对每个例子:

  • 仔细阅读设置示例的初始代码。如果您是一名经验丰富的Python程序员,您将在大多数情况下成功预测接下来会发生什么
  • 阅读输出片段,
    • 检查输出是否与您预期的相同
    • 如果您知道输出背后的确切原因,请确保它是这样的
      • 如果答案是否定的(这完全没问题),深呼吸,然后阅读解释(如果你仍然不明白,就大声喊出来!)然后制造一个问题here)
      • 如果是,轻轻拍一下你的背,你就可以跳到下一个例子了

PS:您也可以在命令行中使用pypi package

$ pip install wtfpython -U
$ wtfpython

👀示例

部分:开动脑筋!

▶当务之急!*

由于某些原因,Python3.8的“Walrus”运算符(:=)已经变得相当流行了。我们去看看吧,

1个

# Python version 3.8+

>>> a = "wtf_walrus"
>>> a
'wtf_walrus'

>>> a := "wtf_walrus"
File "<stdin>", line 1
    a := "wtf_walrus"
      ^
SyntaxError: invalid syntax

>>> (a := "wtf_walrus") # This works though
'wtf_walrus'
>>> a
'wtf_walrus'

2个

# Python version 3.8+

>>> a = 6, 9
>>> a
(6, 9)

>>> (a := 6, 9)
(6, 9)
>>> a
6

>>> a, b = 6, 9 # Typical unpacking
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oops
  File "<stdin>", line 1
    (a, b = 16, 19)
          ^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # This prints out a weird 3-tuple
(6, 16, 19)

>>> a # a is still unchanged?
6

>>> b
16

💡解释

海象操作员快速刷新器

海象操作员(:=)是在Python3.8中引入的,因此在需要为表达式中的变量赋值的情况下会很有用

def some_func():
        # Assume some expensive computation here
        # time.sleep(1000)
        return 5

# So instead of,
if some_func():
        print(some_func()) # Which is bad practice since computation is happening twice

# or
a = some_func()
if a:
    print(a)

# Now you can concisely write
if a := some_func():
        print(a)

输出(>3.8):

5
5
5

这节省了一行代码,并且隐式地阻止了调用some_func两次

  • 不带括号的“赋值表达式”(使用walrus运算符)在顶层受到限制,因此SyntaxErrora := "wtf_walrus"第一个代码段的语句。加上括号后,它按预期工作并分配给a
  • 与往常一样,包含以下内容的表达式的括号=不允许使用操作员。因此,中的语法错误(a, b = 6, 9)
  • Walrus运算符的语法为NAME:= expr,在哪里NAME是有效的标识符,并且expr是有效的表达式。因此,不支持迭代打包和解包,这意味着,
    • (a := 6, 9)相当于((a := 6), 9)最终(a, 9) (其中a的值为6‘)

      >>> (a := 6, 9) == ((a := 6), 9)
      True
      >>> x = (a := 696, 9)
      >>> x
      (696, 9)
      >>> x[0] is a # Both reference same memory location
      True
    • 同样,(a, b := 16, 19)相当于(a, (b := 16), 19)它只不过是一个3元组

▶字符串有时可能很棘手

1个

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Notice that both the ids are same.
140420665652016

2个

>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False

3个

>>> a, b = "wtf!", "wtf!"
>>> a is b # All versions except 3.7.x
True

>>> a = "wtf!"; b = "wtf!"
>>> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False

# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print(a is b)

# prints True when the module is invoked!

4.

输出(<Python3.7)

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

很有道理,对吧?

💡说明:

  • 第一个和第二个代码段中的行为是由于CPython优化(称为字符串插入),在某些情况下会尝试使用现有的不可变对象,而不是每次都创建新对象
  • 在“内嵌”之后,许多变量可能会在内存中引用相同的字符串对象(从而节省内存)
  • 在上面的代码片断中,字符串是隐式驻留的。何时隐式内嵌字符串的决定取决于实现。有一些规则可用于猜测字符串是否会被扣留:
    • 所有长度为0和长度1的字符串都会被插入
    • 字符串在编译时驻留('wtf'会被拘留,但是''.join(['w', 't', 'f'])不会被拘留)
    • 不包含由ASCII字母、数字或下划线组成的字符串。这就解释了为什么'wtf!'没有被拘留是因为!可以找到此规则的CPython实现here
      image
  • 什么时候ab设置为"wtf!"在同一行中,Python解释器创建一个新对象,然后同时引用第二个变量。如果您在单独的行上执行,它不会“知道”已经有"wtf!"作为对象(因为"wtf!"根据上述事实,未被默示拘留)。它是编译时优化。此优化不适用于CPython的3.7.x版本(选中此选项issue有关更多讨论,请参见)
  • 在像IPython这样的交互式环境中,编译单元由单个语句组成,而如果是模块,则由整个模块组成。a, b = "wtf!", "wtf!"是单个语句,而a = "wtf!"; b = "wtf!"是一行中的两个语句。这就解释了为什么a = "wtf!"; b = "wtf!",并解释为什么它们在中调用时是相同的some_file.py
  • 第四个代码段的输出突然更改是由于peephole optimization一种称为恒定折叠的技术。这意味着表达式'a'*20被替换为'aaaaaaaaaaaaaaaaaaaa'以在运行时节省几个时钟周期。常量折叠仅发生在长度小于21的字符串中。(为什么?想象一下……的大小.pyc作为表达式的结果生成的文件'a'*10**10)。Here’s相同的实现源
  • 注意:在Python3.7中,常量折叠从窥视优化器移到了新的AST优化器,但在逻辑上也做了一些更改,因此第四个代码片段不适用于Python3.7。您可以阅读有关更改的更多信息here

▶注意链式操作

>>> (False == False) in [False] # makes sense
False
>>> False == (False in [False]) # makes sense
False
>>> False == False in [False] # now what?
True

>>> True is False == False
False
>>> False is False is False
True

>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False

💡说明:

按规定https://docs.python.org/3/reference/expressions.html#membership-test-operations

形式上,如果a、b、c、…、y、z是表达式,并且op1、op2、…、opn是比较运算符,则a op1b、op2c。y opn z等同于a op1b和b op2c。y opn z,只是每个表达式最多求值一次

虽然在上面的示例中,这样的行为在您看来可能很愚蠢,但对于像这样的东西来说,它是非常棒的a == b == c0 <= x <= 100

  • False is False is False相当于(False is False) and (False is False)
  • True is False == False相当于True is False and False == False由于声明的第一部分(True is False)的计算结果为False,则整个表达式的计算结果为False
  • 1 > 0 < 1相当于1 > 0 and 0 < 1,它的计算结果为True
  • 表达式(1 > 0) < 1相当于True < 1

    >>> int(True)
    1
    >>> True + 1 #not relevant for this example, but just for fun
    2

    所以,1 < 1计算结果为False


▶如何不使用is操作员

下面是一个在互联网上流传的非常著名的例子。

1个

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

2个

>>> a = []
>>> b = []
>>> a is b
False

>>> a = tuple()
>>> b = tuple()
>>> a is b
True

3个输出

>>> a, b = 257, 257
>>> a is b
True

输出(特别是Python 3.7.x)

>>> a, b = 257, 257
>> a is b
False

💡说明:

两者之间的区别is==

  • is运算符检查两个操作数是否引用同一对象(即,它检查操作数的标识是否匹配)
  • ==运算符比较两个操作数的值并检查它们是否相同
  • 所以is是为了引用相等和==是为了价值平等。这是一个澄清问题的例子,

    >>> class A: pass
    >>> A() is A() # These are two empty objects at two different memory locations.
    False

256是现有对象,但257不是吗

当您启动python时,来自-5256将会被分配。这些数字用得很多,所以只要准备好就行了

报价自https://docs.python.org/3/c-api/long.html

当前的实现为-5到256之间的所有整数保留了一个整数对象数组,当您在该范围内创建一个int时,您只会得到对现有对象的引用。所以应该可以更改1的值。我怀疑Python的行为(在本例中)是未定义的。:-)

>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344

在这里,解释器在执行时不够聪明y = 257要认识到我们已经创建了一个值的整数257,因此,它继续在内存中创建另一个对象

类似的优化也适用于其他不可变的对象也喜欢空元组。由于列表是可变的,这就是为什么[] is []会回来的False() is ()会回来的True这解释了我们的第二个片段。让我们继续第三个问题,

两者都有ab在同一行中使用相同的值初始化时引用相同的对象

输出

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488
  • 当a和b设置为257在同一行中,Python解释器创建一个新对象,然后同时引用第二个变量。如果您在单独的行上执行,它不会“知道”已经有257作为一个对象
  • 它是一种编译器优化,特别适用于交互式环境。当您在实时解释器中输入两行时,它们被单独编译,因此分别进行了优化。如果您要在.py文件时,您将不会看到相同的行为,因为该文件是一次性编译的。这种优化并不局限于整数,它也适用于其他不可变的数据类型,如字符串(请查看“字符串是棘手的示例”)和浮点数。

    >>> a, b = 257.0, 257.0
    >>> a is b
    True
  • 为什么这不适用于Python3.7?抽象原因是因为这样的编译器优化是特定于实现的(即可能随版本、OS等而改变)。我还在找出是什么具体的实现更改导致了这个问题,您可以查看以下内容issue用于更新

▶哈希布朗尼

1个

some_dict = {}
some_dict[5.5] = "JavaScript"
some_dict[5.0] = "Ruby"
some_dict[5] = "Python"

输出:

>>> some_dict[5.5]
"JavaScript"
>>> some_dict[5.0] # "Python" destroyed the existence of "Ruby"?
"Python"
>>> some_dict[5] 
"Python"

>>> complex_five = 5 + 0j
>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"

那么,为什么到处都是Python呢?

💡解释

  • Python字典中键的唯一性由等价性,而不是身份。所以即使是这样55.0,以及5 + 0j是不同类型的不同对象,因为它们是相等的,所以它们不可能都在同一个dict(或set)。一旦您插入其中的任何一个,尝试查找任何不同但等价的键将使用原始映射值成功(而不是使用KeyError):

    >>> 5 == 5.0 == 5 + 0j
    True
    >>> 5 is not 5.0 is not 5 + 0j
    True
    >>> some_dict = {}
    >>> some_dict[5.0] = "Ruby"
    >>> 5.0 in some_dict
    True
    >>> (5 in some_dict) and (5 + 0j in some_dict)
    True
  • 这在设置项目时也适用。所以当你这么做的时候some_dict[5] = "Python"时,Python会查找具有等效键的现有项5.0 -> "Ruby",在原地覆盖其值,而不使用原始键。

    >>> some_dict
    {5.0: 'Ruby'}
    >>> some_dict[5] = "Python"
    >>> some_dict
    {5.0: 'Python'}
  • 那么我们如何更新密钥以5(而不是5.0)?我们实际上不能就地进行此更新,但我们可以做的是首先删除密钥(del some_dict[5.0]),然后设置它(some_dict[5])以获取整数5作为密钥,而不是浮动5.0,尽管在极少数情况下应该需要这样做
  • Python是如何找到5在包含以下内容的词典中5.0?Python在固定时间内完成此操作,而不必使用散列函数扫描每一项。当Python查找密钥时foo在字典中,它首先计算hash(foo)(它以恒定时间运行)。因为在Python中要求比较相等的对象也具有相同的散列值(docs这里),55.0,以及5 + 0j具有相同的哈希值

    >>> 5 == 5.0 == 5 + 0j
    True
    >>> hash(5) == hash(5.0) == hash(5 + 0j)
    True

    注:反之亦然:散列值相等的对象本身可能不相等。(这导致了所谓的hash collision,并且降低了散列通常提供的恒定时间性能。)


▶在内心深处,我们都是一样的

class WTF:
  pass

输出:

>>> WTF() == WTF() # two different instances can't be equal
False
>>> WTF() is WTF() # identities are also different
False
>>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well
True
>>> id(WTF()) == id(WTF())
True

💡说明:

  • 什么时候id时,Python创建了一个WTF类对象,并将其传递给id功能。这个id函数将其id(其内存位置),并丢弃该对象。该对象将被销毁
  • 当我们连续两次执行此操作时,Python也会将相同的内存位置分配给第二个对象。自(在CPython中)id使用内存位置作为对象ID,两个对象的ID相同
  • 因此,对象的id只在对象的生存期内是唯一的。在销毁对象之后,或在创建对象之前,其他对象可以具有相同的id
  • 但是为什么is运算符的计算结果为False?让我们看看这个片段

    class WTF(object):
      def __init__(self): print("I")
      def __del__(self): print("D")

    输出:

    >>> WTF() is WTF()
    I
    I
    D
    D
    False
    >>> id(WTF()) == id(WTF())
    I
    D
    I
    D
    True

    正如你可能注意到的,这些物品被销毁的顺序是造成这里的所有不同之处的原因


▶秩序中的混乱*

from collections import OrderedDict

dictionary = dict()
dictionary[1] = 'a'; dictionary[2] = 'b';

ordered_dict = OrderedDict()
ordered_dict[1] = 'a'; ordered_dict[2] = 'b';

another_ordered_dict = OrderedDict()
another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';

class DictWithHash(dict):
    """
    A dict that also implements __hash__ magic.
    """
    __hash__ = lambda self: 0

class OrderedDictWithHash(OrderedDict):
    """
    An OrderedDict that also implements __hash__ magic.
    """
    __hash__ = lambda self: 0

输出

>>> dictionary == ordered_dict # If a == b
True
>>> dictionary == another_ordered_dict # and b == c
True
>>> ordered_dict == another_ordered_dict # then why isn't c == a ??
False

# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...

>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
>>> dictionary = DictWithHash()
>>> dictionary[1] = 'a'; dictionary[2] = 'b';
>>> ordered_dict = OrderedDictWithHash()
>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
>>> another_ordered_dict = OrderedDictWithHash()
>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
>>> len({dictionary, ordered_dict, another_ordered_dict})
1
>>> len({ordered_dict, another_ordered_dict, dictionary}) # changing the order
2

这里发生什么事情?

💡说明:

  • 不及物性平等不成立的原因dictionaryordered_dictanother_ordered_dict是因为这种方式__eq__方法是在OrderedDict班级。从docs

    OrderedDict对象之间的相等性测试是顺序敏感的,并且实现为list(od1.items())==list(od2.items())之间的相等性测试OrderedDict对象和其他映射对象与常规字典一样不区分顺序

  • 这种行为平等的原因是它允许OrderedDict在使用常规字典的任何地方都要直接替换的对象
  • 好的,那么为什么更改订单会影响生成的set反对吗?答案只有一个,那就是缺乏不及物平等。由于集合是唯一元素的“无序”集合,因此插入元素的顺序应该无关紧要。但在这种情况下,这确实很重要。让我们把它分解一下,

    >>> some_set = set()
    >>> some_set.add(dictionary) # these are the mapping objects from the snippets above
    >>> ordered_dict in some_set
    True
    >>> some_set.add(ordered_dict)
    >>> len(some_set)
    1
    >>> another_ordered_dict in some_set
    True
    >>> some_set.add(another_ordered_dict)
    >>> len(some_set)
    1
    
    >>> another_set = set()
    >>> another_set.add(ordered_dict)
    >>> another_ordered_dict in another_set
    False
    >>> another_set.add(another_ordered_dict)
    >>> len(another_set)
    2
    >>> dictionary in another_set
    True
    >>> another_set.add(another_ordered_dict)
    >>> len(another_set)
    2

    所以不一致是因为another_ordered_dict in another_set存在False因为ordered_dict已经出现在another_set正如之前观察到的,ordered_dict == another_ordered_dictFalse


▶继续努力。*

def some_func():
    try:
        return 'from_try'
    finally:
        return 'from_finally'

def another_func(): 
    for _ in range(3):
        try:
            continue
        finally:
            print("Finally!")

def one_more_func(): # A gotcha!
    try:
        for i in range(3):
            try:
                1 / i
            except ZeroDivisionError:
                # Let's throw it here and handle it outside for loop
                raise ZeroDivisionError("A trivial divide by zero error")
            finally:
                print("Iteration", i)
                break
    except ZeroDivisionError as e:
        print("Zero division error occurred", e)

输出:

>>> some_func()
'from_finally'

>>> another_func()
Finally!
Finally!
Finally!

>>> 1 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

>>> one_more_func()
Iteration 0

💡说明:

  • 当一个returnbreakcontinue语句在try一套“Try…Finally”语句,finally子句也在退出时执行。
  • 函数的返回值由最后一个return语句已执行。由于finally子句始终执行,则会引发return中执行的语句finally子句将始终是最后执行的子句。
  • 这里需要注意的是,如果Finally子句执行returnbreak语句,则会丢弃临时保存的异常。

▶为了什么?

some_string = "wtf"
some_dict = {}
for i, some_dict[i] in enumerate(some_string):
    i = 10

输出:

>>> some_dict # An indexed dict appears.
{0: 'w', 1: 't', 2: 'f'}

💡说明:

  • 一个for语句在Python grammar作为:

    for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
    

    哪里exprlist是分配目标。这意味着相当于{exprlist} = {next_value}为每个项目执行在可迭代中。一个有趣的例子说明了这一点:

    for i in range(4):
        print(i)
        i = 10

    输出:

    0
    1
    2
    3
    

    您是否希望循环只运行一次?

    💡说明:

    • 赋值语句i = 10由于for循环在Python中的工作方式,永远不会影响循环的迭代。在每次迭代开始之前,迭代器提供的下一项(range(4)在本例中)被解包并分配给目标列表变量(i在这种情况下)
  • 这个enumerate(some_string)函数会产生一个新值。i(计数器向上)和来自some_string在每一次迭代中。然后它设置(刚刚分配的)i词典的关键字some_dict给那个角色。循环的展开可以简化为:

    >>> i, some_dict[i] = (0, 'w')
    >>> i, some_dict[i] = (1, 't')
    >>> i, some_dict[i] = (2, 'f')
    >>> some_dict

▶评估时间差异

1个

array = [1, 8, 15]
# A typical generator expression
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

输出:

>>> print(list(gen)) # Where did the other values go?
[8]

2个

array_1 = [1,2,3,4]
gen_1 = (x for x in array_1)
array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4]
gen_2 = (x for x in array_2)
array_2[:] = [1,2,3,4,5]

输出:

>>> print(list(gen_1))
[1, 2, 3, 4]

>>> print(list(gen_2))
[1, 2, 3, 4, 5]

3个

array_3 = [1, 2, 3]
array_4 = [10, 20, 30]
gen = (i + j for i in array_3 for j in array_4)

array_3 = [4, 5, 6]
array_4 = [400, 500, 600]

输出:

>>> print(list(gen))
[401, 501, 601, 402, 502, 602, 403, 503, 603]

💡解释

  • 在一个generator表达式,则in子句在声明时求值,但条件子句在运行时求值
  • 所以在运行之前,array被重新分配到列表中[2, 8, 22],并且由于不在1815,只有8大于0,发电机只会产生8
  • 其产量的不同之处在于g1g2第二部分是因应方式变量array_1array_2是重新赋值的
  • 在第一种情况下,array_1绑定到新对象[1,2,3,4,5]而且由于in子句在声明时求值,它仍然引用旧对象。[1,2,3,4](未销毁)
  • 在第二种情况下,将切片分配给array_2更新相同的旧对象[1,2,3,4][1,2,3,4,5]因此,这两个g2array_2仍然有对同一对象的引用(该对象现在已更新为[1,2,3,4,5])
  • 好的,按照到目前为止讨论的逻辑,不应该是list(gen)在第三个片段中[11, 21, 31, 12, 22, 32, 13, 23, 33]?(因为array_3array_4会表现得就像array_1)。原因(仅限)array_4有关更新的值的说明,请参阅PEP-289

    仅立即计算最外层的for-expression,其他表达式将推迟到生成器运行


is not ...不是is (not ...)

>>> 'something' is not None
True
>>> 'something' is (not None)
False

💡解释

  • is not是单个二元运算符,其行为与使用isnot分开的
  • is not计算结果为False如果运算符两侧的变量指向同一对象,并且True否则
  • 在该示例中,(not None)计算结果为True因为它的价值NoneFalse在布尔上下文中,因此表达式变为'something' is True

▶一个X在第一次尝试中就赢了的井字棋(tic-tac-toe)!

# Let's initialize a row
row = [""] * 3 #row i['', '', '']
# Let's make a board
board = [row] * 3

输出:

>>> board
[['', '', ''], ['', '', ''], ['', '', '']]
>>> board[0]
['', '', '']
>>> board[0][0]
''
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['X', '', ''], ['X', '', '']]

我们没有分配三个"X"S,我们有吗?

💡说明:

当我们初始化时row变量,这种可视化解释了内存中发生的事情

image

而当board通过将row,这就是内存中发生的事情(每个元素board[0]board[1]board[2]是对由引用的同一列表的引用row)

image

我们可以在这里避免这种情况,方法是不使用row要生成的变量board(被问及this问题)

>>> board = [['']*3 for _ in range(3)]
>>> board[0][0] = "X"
>>> board
[['X', '', ''], ['', '', ''], ['', '', '']]

▶薛定谔变量*

funcs = []
results = []
for x in range(7):
    def some_func():
        return x
    funcs.append(some_func)
    results.append(some_func())  # note the function call here

funcs_results = [func() for func in funcs]

输出(Python版本):

>>> results
[0, 1, 2, 3, 4, 5, 6]
>>> funcs_results
[6, 6, 6, 6, 6, 6, 6]

的价值x在追加之前的每个迭代中都是不同的some_funcfuncs,但是在循环完成后对所有函数求值时,所有函数都返回6

>>> powers_of_x = [lambda x: x**i for i in range(10)]
>>> [f(2) for f in powers_of_x]
[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

💡说明:

  • 在循环内定义在其主体中使用循环变量的函数时,循环函数的闭包将绑定到变量,而不是ITS价值该函数查找x在周围的上下文中,而不是使用x在创建函数时。因此,所有函数都使用分配给变量的最新值进行计算。我们可以看到它正在使用x从周围的上下文(即局部变量),具有:

>>> import inspect
>>> inspect.getclosurevars(funcs[0])
ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())

因为x是全局值,我们可以更改funcs将通过更新查找并返回x

>>> x = 42
>>> [func() for func in funcs]
[42, 42, 42, 42, 42, 42, 42]
  • 要获得所需的行为,可以将循环变量作为命名变量传递给函数。为什么这个管用呢?因为这将定义变量内部函数的作用域。它将不再转到周围的(全局)作用域来查找变量值,而是创建一个局部变量来存储x在那个时间点上

funcs = []
for x in range(7):
    def some_func(x=x):
        return x
    funcs.append(some_func)

输出:

>>> funcs_results = [func() for func in funcs]
>>> funcs_results
[0, 1, 2, 3, 4, 5, 6]

它不再使用x在全局范围内:

>>> inspect.getclosurevars(funcs[0])
ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())

▶先有鸡还是先有蛋的问题**

1个

>>> isinstance(3, int)
True
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True

那么,哪个是“终极”基类呢?顺便说一下,念力还有更多内容,

2个

>>> class A: pass
>>> isinstance(A, A)
False
>>> isinstance(type, type)
True
>>> isinstance(object, object)
True

3个

>>> issubclass(int, object)
True
>>> issubclass(type, object)
True
>>> issubclass(object, type)
False

💡解释

  • type是一种metaclass在Python中
  • 所有的一切是一种object在Python中,包括类及其对象(实例)
  • 班级type是类的元类object,以及每个班级(包括type)直接或间接继承自object
  • 中没有真正的基类。objecttype上述片段中的念力之所以出现,是因为我们正在考虑这些关系(issubclassisinstance)在Python类方面。两国之间的关系objecttype不能用纯python复制。更准确地说,以下关系不能在纯Python中重现,
    • 类A是类B的实例,类B是类A的实例
    • A类是其自身的一个实例
  • 这些关系之间的关系objecttype(两者既是彼此的实例,也是自己的实例)存在于Python中,因为在实现级别上存在“欺骗”

▶子类关系

输出:

>>> from collections import Hashable
>>> issubclass(list, object)
True
>>> issubclass(object, Hashable)
True
>>> issubclass(list, Hashable)
False

子类关系应该是可传递的,对吗?(即,如果A是的子类B,以及B是的子类C,即A应该的子类C)

💡说明:

  • 在Python中,子类关系不一定是可传递的。任何人都可以定义他们自己的,武断的__subclasscheck__在元类中
  • 什么时候issubclass(cls, Hashable)被调用,它只是简单地查找非Falsey“__hash__“中的方法cls或它继承的任何东西
  • 因为object是可以哈希的,但是list是不可散列的,它打破了传递性关系
  • 可以找到更详细的解释here

▶方法的等价性和同一性

class SomeClass:
    def method(self):
        pass

    @classmethod
    def classm(cls):
        pass

    @staticmethod
    def staticm():
        pass

输出:

>>> print(SomeClass.method is SomeClass.method)
True
>>> print(SomeClass.classm is SomeClass.classm)
False
>>> print(SomeClass.classm == SomeClass.classm)
True
>>> print(SomeClass.staticm is SomeClass.staticm)
True

访问classm两次,我们得到一个相等的对象,但不是相同的一?让我们看看如何处理以下实例SomeClass

o1 = SomeClass()
o2 = SomeClass()

输出:

>>> print(o1.method == o2.method)
False
>>> print(o1.method == o1.method)
True
>>> print(o1.method is o1.method)
False
>>> print(o1.classm is o1.classm)
False
>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
True
>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
True

访问 classmmethod两次,创建相等但不相等相同的对象的同一实例的SomeClass

💡解释

  • 函数有descriptors无论何时将函数作为属性访问,都会调用描述符,从而创建一个方法对象,该对象将函数与拥有该属性的对象“绑定”在一起。如果被调用,该方法调用函数,将绑定对象作为第一个参数隐式传递(这就是我们如何获取self作为第一个参数,尽管没有显式传递)

>>> o1.method
<bound method SomeClass.method of <__main__.SomeClass object at ...>>
  • 多次访问该属性每次都会创建一个方法对象!因此,o1.method is o1.method从来都不是真实的。但是,将函数作为类属性访问(与实例相反)并不会创建方法;因此SomeClass.method is SomeClass.method是真实的吗?

>>> SomeClass.method
<function SomeClass.method at ...>
  • classmethod将函数转换为类方法。类方法是描述符,当访问这些描述符时,会创建一个方法对象,该对象将班级对象的(类型),而不是对象本身

>>> o1.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
  • 与函数不同,classmethod在作为类属性访问时,也将创建一个方法(在这种情况下,它们绑定类,而不是绑定到类的类型)。所以SomeClass.classm is SomeClass.classm是假的

>>> SomeClass.classm
<bound method SomeClass.classm of <class '__main__.SomeClass'>>
  • 当两个函数相等且绑定对象相同时,方法对象会比较相等。所以o1.method == o1.method是真实的,尽管在内存中不是同一对象
  • staticmethod将函数转换为“no-op”描述符,该描述符按原样返回函数。不会创建任何方法对象,因此与is是真实的吗?

>>> o1.staticm
<function SomeClass.staticm at ...>
>>> SomeClass.staticm
<function SomeClass.staticm at ...>
  • 每次Python调用实例方法时都必须创建新的“方法”对象,并且每次都必须修改参数才能插入self严重影响了性能。CPython3.7solved it通过引入新的操作码来处理调用方法,而无需创建临时方法对象。这仅在实际调用所访问的函数时使用,因此此处的代码段不受影响,并且仍会生成方法:)

▶全真*

>>> all([True, True, True])
True
>>> all([True, True, False])
False

>>> all([])
True
>>> all([[]])
False
>>> all([[[]]])
True

为什么会有这种真假的改变呢?

💡说明:

  • 该计划的实施all函数等效于
  • def all(iterable):
        for element in iterable:
            if not element:
                return False
        return True
  • all([])退货True由于迭代器为空
  • all([[]])退货False因为传递的数组有一个元素,[],在python中,空列表是虚假的。
  • all([[[]]])更高的递归变体总是True这是因为传递的数组的单个元素([[...]])不再为空,带有值的列表为真

▶令人惊讶的逗号

输出(<3.6):

>>> def f(x, y,):
...     print(x, y)
...
>>> def g(x=4, y=5,):
...     print(x, y)
...
>>> def h(x, **kwargs,):
  File "<stdin>", line 1
    def h(x, **kwargs,):
                     ^
SyntaxError: invalid syntax

>>> def h(*args,):
  File "<stdin>", line 1
    def h(*args,):
                ^
SyntaxError: invalid syntax

💡说明:

  • 在Python函数的形参列表中,尾随逗号并不总是合法的
  • 在Python中,参数列表部分使用前导逗号定义,部分使用尾随逗号定义。此冲突会导致逗号被困在中间的情况,并且没有规则接受它
  • 注:后面的逗号问题是fixed in Python 3.6中的评论this简要讨论Python中尾随逗号的不同用法

▶字符串和反斜杠

输出:

>>> print("\"")
"

>>> print(r"\"")
\"

>>> print(r"\")
File "<stdin>", line 1
    print(r"\")
              ^
SyntaxError: EOL while scanning string literal

>>> r'\'' == "\\'"
True

💡解释

  • 在通常的python字符串中,反斜杠用于转义可能具有特殊含义的字符(如单引号、双引号和反斜杠本身)。

    >>> "wt\"f"
    'wt"f'
  • 在原始字符串文字中(由前缀指示r),则反斜杠会按原样传递自身,同时转义以下字符的行为

    >>> r'wt\"f' == 'wt\\"f'
    True
    >>> print(repr(r'wt\"f')
    'wt\\"f'
    
    >>> print("\n")
    
    >>> print(r"\\n")
    '\\n'
  • 这意味着当解析器在原始字符串中遇到反斜杠时,它会期待后面跟着另一个字符。在我们的情况下(print(r"\")),则反斜杠转义尾部引号,使解析器没有终止引号(因此SyntaxError)。这就是原始字符串末尾不能使用反斜杠的原因

▶不是结!

x = True
y = False

输出:

>>> not x == y
True
>>> x == not y
  File "<input>", line 1
    x == not y
           ^
SyntaxError: invalid syntax

💡说明:

  • 运算符优先级影响表达式的求值方式,并且==运算符的优先级高于notPython中的运算符
  • 所以not x == y相当于not (x == y)这相当于not (True == False)最终评估为True
  • x == not y引发一个SyntaxError因为它可以被认为等同于(x == not) y而不是x == (not y)这可能是你第一眼看到的
  • 解析器期望not令牌作为not in运算符(因为两者==not in运算符具有相同的优先级),但在无法找到in标记后跟在not令牌,则会引发SyntaxError

▶半个三引号字符串

输出:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # The following statements raise `SyntaxError`
>>> # print('''wtfpython')
>>> # print("""wtfpython")
  File "<input>", line 3
    print("""wtfpython")
                        ^
SyntaxError: EOF while scanning triple-quoted string literal

💡说明:

  • Python支持隐式string literal concatenation,例如,

    >>> print("wtf" "python")
    wtfpython
    >>> print("wtf" "") # or "wtf"""
    wtf
    
  • '''"""也是Python中的字符串分隔符,这会导致语法错误,因为Python解释器在扫描当前遇到的三重引号字符串文字时期望使用终止的三重引号作为分隔符

▶布尔人有什么问题吗?

1个

# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [False, 1.0, "some_string", 3, True, [], False]
integers_found_so_far = 0
booleans_found_so_far = 0

for item in mixed_list:
    if isinstance(item, int):
        integers_found_so_far += 1
    elif isinstance(item, bool):
        booleans_found_so_far += 1

输出:

>>> integers_found_so_far
4
>>> booleans_found_so_far
0

2个

>>> some_bool = True
>>> "wtf" * some_bool
'wtf'
>>> some_bool = False
>>> "wtf" * some_bool
''

3个

def tell_truth():
    True = False
    if True == False:
        print("I have lost faith in truth!")

输出(<3.x):

>>> tell_truth()
I have lost faith in truth!

💡说明:

  • bool是的子类int在Python中

    >>> issubclass(bool, int)
    True
    >>> issubclass(int, bool)
    False
  • 因此,TrueFalse是以下对象的实例int

    >>> isinstance(True, int)
    True
    >>> isinstance(False, int)
    True
  • 的整数值True1那就是False0

    >>> int(True)
    1
    >>> int(False)
    0
  • 查看此StackOverflowanswer以了解其背后的理论基础
  • 最初,Python过去没有bool类型(人们使用0表示FALSE,使用非零值1表示TRUE)。TrueFalse,和一个bool类型是在2.x版本中添加的,但是为了向后兼容,TrueFalse不能成为常量。它们只是内置变量,可以重新赋值
  • Python3向后不兼容,该问题最终被修复,因此最后一个代码片段不能与Python3.x一起工作!

▶类属性和实例属性

1个

class A:
    x = 1

class B(A):
    pass

class C(A):
    pass

输出:

>>> A.x, B.x, C.x
(1, 1, 1)
>>> B.x = 2
>>> A.x, B.x, C.x
(1, 2, 1)
>>> A.x = 3
>>> A.x, B.x, C.x # C.x changed, but B.x didn't
(3, 2, 3)
>>> a = A()
>>> a.x, A.x
(3, 3)
>>> a.x += 1
>>> a.x, A.x
(4, 3)

2个

class SomeClass:
    some_var = 15
    some_list = [5]
    another_list = [5]
    def __init__(self, x):
        self.some_var = x + 1
        self.some_list = self.some_list + [x]
        self.another_list += [x]

输出:

>>> some_obj = SomeClass(420)
>>> some_obj.some_list
[5, 420]
>>> some_obj.another_list
[5, 420]
>>> another_obj = SomeClass(111)
>>> another_obj.some_list
[5, 111]
>>> another_obj.another_list
[5, 420, 111]
>>> another_obj.another_list is SomeClass.another_list
True
>>> another_obj.another_list is some_obj.another_list
True

💡说明:

  • 类变量和类实例中的变量作为类对象的字典在内部处理。如果在当前类的字典中找不到变量名,则会在父类中搜索该变量名
  • 这个+=运算符就地修改可变对象,而不创建新对象。因此,更改一个实例的属性会影响其他实例和类属性

▶一无所获

some_iterable = ('a', 'b')

def some_func(val):
    return "something"

输出(<=3.7.x):

>>> [x for x in some_iterable]
['a', 'b']
>>> [(yield x) for x in some_iterable]
<generator object <listcomp> at 0x7f70b0a4ad58>
>>> list([(yield x) for x in some_iterable])
['a', 'b']
>>> list((yield x) for x in some_iterable)
['a', None, 'b', None]
>>> list(some_func((yield x)) for x in some_iterable)
['a', 'something', 'b', 'something']

💡说明:


▶屈服于。回来!*

1个

def some_func(x):
    if x == 3:
        return ["wtf"]
    else:
        yield from range(x)

输出(>3.3):

>>> list(some_func(3))
[]

那辆车在哪里呢?"wtf"去?是不是因为有一些特殊的效果yield from?我们来验证一下,

2个

def some_func(x):
    if x == 3:
        return ["wtf"]
    else:
        for i in range(x):
          yield i

输出:

>>> list(some_func(3))
[]

同样的结果,这也不管用

💡说明:

  • 从Python3.3开始,可以使用return在生成器内具有值的语句(请参见PEP380)。这个official docs这么说吧,

“。”return expr在发电机中引起StopIteration(expr)在离开发电机时提升。“

  • 在以下情况下some_func(3)StopIteration是在一开始就提出的,因为return声明。这个StopIteration异常会自动捕获到list(...)包装器和for循环。因此,上述两个代码段将产生一个空列表
  • 为了得到["wtf"]从发电机some_func我们需要赶上StopIteration例外,

    try:
        next(some_func(3))
    except StopIteration as e:
        some_string = e.value

    >>> some_string
    ["wtf"]

▶NaN-自反性*

1个

a = float('inf')
b = float('nan')
c = float('-iNf')  # These strings are case-insensitive
d = float('nan')

输出:

>>> a
inf
>>> b
nan
>>> c
-inf
>>> float('some_other_string')
ValueError: could not convert string to float: some_other_string
>>> a == -c # inf==inf
True
>>> None == None # None == None
True
>>> b == d # but nan!=nan
False
>>> 50 / a
0.0
>>> a / a
nan
>>> 23 + b
nan

2个

>>> x = float('nan')
>>> y = x / x
>>> y is y # identity holds
True
>>> y == y # equality fails of y
False
>>> [y] == [y] # but the equality succeeds for the list containing y
True

💡说明:

  • 'inf''nan'是特殊字符串(不区分大小写),当显式类型转换为float类型,分别用于表示数学上的“无穷大”和“非数字”
  • 因为根据IEEE标准 NaN != NaN遵守此规则,则打破了Python中集合元素的自反性假设,即如果x是像这样的集合的一部分list,类似比较的实现是基于以下假设的x == x由于这一假设,在比较两个元素时,首先比较标识(因为这样更快),只有当标识不匹配时才比较值。下面的片段会让事情变得更清楚,

    >>> x = float('nan')
    >>> x == x, [x] == [x]
    (False, True)
    >>> y = float('nan')
    >>> y == y, [y] == [y]
    (False, True)
    >>> x == y, [x] == [y]
    (False, False)

    因为他们的身份xy是不同的,则考虑这些值,这些值也是不同的;因此比较返回False这一次

  • 有趣的阅读:Reflexivity, and other pillars of civilization

▶变异不变的东西!

如果您知道引用在Python中的工作方式,这可能看起来微不足道

some_tuple = ("A", "tuple", "with", "values")
another_tuple = ([1, 2], [3, 4], [5, 6])

输出:

>>> some_tuple[2] = "change this"
TypeError: 'tuple' object does not support item assignment
>>> another_tuple[2].append(1000) #This throws no error
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000])
>>> another_tuple[2] += [99, 999]
TypeError: 'tuple' object does not support item assignment
>>> another_tuple
([1, 2], [3, 4], [5, 6, 1000, 99, 999])

但是我认为元组是不变的

💡说明:

  • 报价自https://docs.python.org/3/reference/datamodel.html

    不可变序列不可变序列类型的对象一旦创建就不能更改。(如果对象包含对其他对象的引用,则这些其他对象可以是可变的,并且可以修改;但是,由不可变对象直接引用的对象集合不能更改。)

  • +=操作员就地更改列表。项目分配不起作用,但当异常发生时,项目已更改到位
  • 这里面也有一个解释official Python FAQ

▶外部作用域中正在消失的变量

e = 7
try:
    raise Exception()
except Exception as e:
    pass

输出(Python 2.x):

>>> print(e)
# prints nothing

输出(Python 3.x):

>>> print(e)
NameError: name 'e' is not defined

💡说明:

  • 来源:https://docs.python.org/3/reference/compound_stmts.html#except

    在使用以下命令分配异常时as目标,则在except条款。这就好像

    except E as N:
        foo

    被翻译成

    except E as N:
        try:
            foo
        finally:
            del N

    这意味着必须为异常指定不同的名称,才能在EXCEPT子句之后引用它。异常之所以被清除,是因为附加了回溯后,它们与堆栈帧形成一个引用循环,使该帧中的所有局部变量保持活动状态,直到下一次垃圾回收发生

  • 这些子句在Python中没有作用域。示例中的所有内容都在相同的作用域中,并且变量e由于执行except条款。对于具有独立内部作用域的函数则不是这样。下面的示例说明了这一点:

    def f(x):
        del(x)
        print(x)
    
    x = 5
    y = [5, 4, 3]

    输出:

    >>>f(x)
    UnboundLocalError: local variable 'x' referenced before assignment
    >>>f(y)
    UnboundLocalError: local variable 'x' referenced before assignment
    >>> x
    5
    >>> y
    [5, 4, 3]
  • 在Python 2.x中,变量名称e分配给Exception()实例,因此当您尝试打印时,它不打印任何内容

    输出(Python 2.x):

    >>> e
    Exception()
    >>> print e
    # Nothing is printed!

▶神秘的钥匙类型转换

class SomeClass(str):
    pass

some_dict = {'s': 42}

输出:

>>> type(list(some_dict.keys())[0])
str
>>> s = SomeClass('s')
>>> some_dict[s] = 40
>>> some_dict # expected: Two different keys-value pairs
{'s': 40}
>>> type(list(some_dict.keys())[0])
str

💡说明:

  • 这两个对象s和那根弦"s"散列为相同的值,因为SomeClass继承__hash__一种方法str班级
  • SomeClass("s") == "s"计算结果为True因为SomeClass还继承了__eq__方法来自str班级
  • 由于这两个对象散列为相同的值并且相等,因此它们在字典中由相同的键表示
  • 对于所需的行为,我们可以重新定义__eq__中的方法SomeClass

    class SomeClass(str):
      def __eq__(self, other):
          return (
              type(self) is SomeClass
              and type(other) is SomeClass
              and super().__eq__(other)
          )
    
      # When we define a custom __eq__, Python stops automatically inheriting the
      # __hash__ method, so we need to define it as well
      __hash__ = str.__hash__
    
    some_dict = {'s':42}

    输出:

    >>> s = SomeClass('s')
    >>> some_dict[s] = 40
    >>> some_dict
    {'s': 40, 's': 42}
    >>> keys = list(some_dict.keys())
    >>> type(keys[0]), type(keys[1])
    (__main__.SomeClass, str)

▶让我们看看你能不能猜到这个?

a, b = a[b] = {}, 5

输出:

>>> a
{5: ({...}, 5)}

💡说明:

赋值语句计算表达式列表(请记住,这可以是单个表达式或逗号分隔的列表,后者生成一个元组),并将单个结果对象从左到右分配给每个目标列表

  • 这个+在……里面(target_list "=")+意味着可以有一个或多个目标列表。在本例中,目标列表为a, ba[b](请注意,表达式列表正好是一个,在我们的示例中是{}, 5)
  • 对表达式列表求值后,将其值解压缩到目标列表中从左到右因此,在我们的案例中,首先{}, 5将元组解包为a, b现在我们有了a = {}b = 5
  • a现在分配给{},它是一个可变对象
  • 第二个目标列表是a[b](您可能认为这会抛出一个错误,因为ab在以前的语句中没有定义。但请记住,我们刚刚分配了a{}b5)
  • 现在,我们正在设置密钥5在字典中设置为元组({}, 5)创建循环引用({...}在输出中引用的对象与a已在引用)。循环引用的另一个更简单的示例可以是

    >>> some_list = some_list[0] = [0]
    >>> some_list
    [[...]]
    >>> some_list[0]
    [[...]]
    >>> some_list is some_list[0]
    True
    >>> some_list[0][0][0][0][0][0] == some_list
    True

    我们示例中的情况与此类似(a[b][0]是与以下对象相同的对象a)

  • 因此,总而言之,您可以将该示例分解为

    a, b = {}, 5
    a[b] = a, b

    循环引用可以用以下事实来证明a[b][0]是与以下对象相同的对象a

    >>> a[b][0] is a
    True


剖面:湿滑斜坡

▶在迭代字典时修改字典

x = {0: None}

for i in x:
    del x[i]
    x[i+1] = None
    print(i)

输出(Python 2.7-Python 3.5):

0
1
2
3
4
5
6
7

是的,它的运行时间正好是时间和停靠站

💡说明:

  • 不支持对同时编辑的字典进行迭代
  • 它运行8次,因为这是字典调整大小以容纳更多键的时间点(我们有8个删除条目,因此需要调整大小)。这实际上是一个实现细节
  • 对于不同的Python实现,处理已删除键的方式和调整大小的时间可能会有所不同
  • 因此,对于除Python2.7-Python3.5之外的Python版本,计数可能不同于8(但无论计数是多少,每次运行它都是一样的)。你可以找到一些关于这方面的讨论here或在this堆栈溢出线程
  • 从Python 3.7.6开始,您将看到RuntimeError: dictionary keys changed during iteration如果您尝试执行此操作,则会出现异常

▶固执的del运营

class SomeClass:
    def __del__(self):
        print("Deleted!")

输出:1个

>>> x = SomeClass()
>>> y = x
>>> del x # this should print "Deleted!"
>>> del y
Deleted!

哎呀,终于删掉了。你可能已经猜到是什么救了你__del__在我们第一次尝试删除时被调用x让我们在这个示例中添加更多的曲折

2个

>>> x = SomeClass()
>>> y = x
>>> del x
>>> y # check if y exists
<__main__.SomeClass instance at 0x7f98a1a67fc8>
>>> del y # Like previously, this should print "Deleted!"
>>> globals() # oh, it didn't. Let's check all our global variables and confirm
Deleted!
{'__builtins__': <module '__builtin__' (built-in)>, 'SomeClass': <class __main__.SomeClass at 0x7f98a1a5f668>, '__package__': None, '__name__': '__main__', '__doc__': None}

好的,现在它被删除了😕

💡说明:

  • del x不会直接调用x.__del__()
  • 什么时候del x时,Python将删除该名称x从当前作用域开始,并将对象的引用计数减1x已引用。__del__()仅当对象的引用计数达到零时才调用
  • 在第二个输出片段中,__del__()未调用,因为前面的语句(>>> y)创建了对同一对象的另一个引用(具体地说,_魔术变量,它引用最后一个非None表达式),从而防止在以下情况下引用计数达到零del y遇到了
  • 呼叫globals(或者实际上,执行任何将具有非None结果)导致_若要引用新结果,请删除现有引用。现在引用计数达到0,我们可以看到“已删除!”正在打印中(终于!)

▶超出作用域的变量

1个

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

2个

def some_closure_func():
    a = 1
    def some_inner_func():
        return a
    return some_inner_func()

def another_closure_func():
    a = 1
    def another_inner_func():
        a += 1
        return a
    return another_inner_func()

输出:

>>> some_func()
1
>>> another_func()
UnboundLocalError: local variable 'a' referenced before assignment

>>> some_closure_func()
1
>>> another_closure_func()
UnboundLocalError: local variable 'a' referenced before assignment

💡说明:

  • 当您为作用域中的变量赋值时,它将变为该作用域的局部变量。所以a成为本地化的作用域another_func,但是它以前没有在相同的作用域中初始化,这会引发错误
  • 修改外部作用域变量a在……里面another_func,我们必须使用global关键字

    def another_func()
        global a
        a += 1
        return a

    输出:

    >>> another_func()
    2
  • 在……里面another_closure_funca成为本地化的作用域another_inner_func,但是它以前没有在相同的作用域中初始化,这就是它抛出错误的原因。
  • 修改外部作用域变量a在……里面another_inner_func,请使用nonlocal关键字。非本地语句用于引用在最近的外部(不包括全局)作用域中定义的变量

    def another_func():
        a = 1
        def another_inner_func():
            nonlocal a
            a += 1
            return a
        return another_inner_func()

    输出:

    >>> another_func()
    2
  • 关键字globalnonlocal告诉python解释器不要声明新变量,并在相应的外部作用域中查找它们。
  • 朗读this这是一本简短但令人敬畏的指南,可帮助您详细了解Python中的名称空间和作用域解析是如何工作的

▶迭代时删除列表项

list_1 = [1, 2, 3, 4]
list_2 = [1, 2, 3, 4]
list_3 = [1, 2, 3, 4]
list_4 = [1, 2, 3, 4]

for idx, item in enumerate(list_1):
    del item

for idx, item in enumerate(list_2):
    list_2.remove(item)

for idx, item in enumerate(list_3[:]):
    list_3.remove(item)

for idx, item in enumerate(list_4):
    list_4.pop(idx)

输出:

>>> list_1
[1, 2, 3, 4]
>>> list_2
[2, 4]
>>> list_3
[]
>>> list_4
[2, 4]

你能猜出为什么输出是[2, 4]

💡说明:

  • 更改正在迭代的对象从来都不是一个好主意。这样做的正确方法是迭代对象的副本,并且list_3[:]就是这么做的吗?

    >>> some_list = [1, 2, 3, 4]
    >>> id(some_list)
    139798789457608
    >>> id(some_list[:]) # Notice that python creates new object for sliced list.
    139798779601192

两者之间的差异delremove,以及pop

  • del var_name只是移除了var_name从本地或全局命名空间(这就是为什么list_1不受影响)
  • remove移除第一个匹配值,而不是特定索引,将引发ValueError如果找不到该值
  • pop移除特定索引处的元素并将其返回,引发IndexError如果指定的索引无效

为什么输出是[2, 4]

  • 列表迭代是逐个索引完成的,当我们删除1从…list_2list_4,列表的内容现在是[2, 3, 4]剩余的元素被下移,即,2位于索引0,并且3由于下一次迭代将查看索引1(它是3)、2完全跳过了。列表序列中的每个备用元素都会发生类似的情况
  • 请参阅此StackOverflowthread解释示例
  • 另请参见这个不错的StackOverflowthread查看与Python中的字典相关的类似示例

▶迭代程序的有损压缩*

>>> numbers = list(range(7))
>>> numbers
[0, 1, 2, 3, 4, 5, 6]
>>> first_three, remaining = numbers[:3], numbers[3:]
>>> first_three, remaining
([0, 1, 2], [3, 4, 5, 6])
>>> numbers_iter = iter(numbers)
>>> list(zip(numbers_iter, first_three)) 
[(0, 0), (1, 1), (2, 2)]
# so far so good, let's zip the remaining
>>> list(zip(numbers_iter, remaining))
[(4, 3), (5, 4), (6, 5)]

DID元素位于何处3numbers名单?

💡说明:

  • 来自Pythondocs,这里是zip函数的大致实现,

    def zip(*iterables):
        sentinel = object()
        iterators = [iter(it) for it in iterables]
        while iterators:
            result = []
            for it in iterators:
                elem = next(it, sentinel)
                if elem is sentinel: return
                result.append(elem)
            yield tuple(result)
  • 因此,该函数接受任意数量的可迭代对象,并将它们的每个项添加到result列表,方法是调用next函数,并在任何迭代量耗尽时停止
  • 这里需要注意的是,当耗尽任何可迭代时,result列表将被丢弃。这就是发生在3numbers_iter
  • 执行上述操作的正确方法是使用zip会是,

    >>> numbers = list(range(7))
    >>> numbers_iter = iter(numbers)
    >>> list(zip(first_three, numbers_iter))
    [(0, 0), (1, 1), (2, 2)]
    >>> list(zip(remaining, numbers_iter))
    [(3, 3), (4, 4), (5, 5), (6, 6)]

    zip的第一个参数应该是元素最少的那个


▶循环变量泄漏!

1个

for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

输出:

6 : for x inside loop
6 : x in global

x从未在for循环的作用域之外定义

2个

# This time let's initialize x first
x = -1
for x in range(7):
    if x == 6:
        print(x, ': for x inside loop')
print(x, ': x in global')

输出:

6 : for x inside loop
6 : x in global

3个

输出(Python 2.x):

>>> x = 1
>>> print([x for x in range(5)])
[0, 1, 2, 3, 4]
>>> print(x)
4

输出(Python 3.x):

>>> x = 1
>>> print([x for x in range(5)])
[0, 1, 2, 3, 4]
>>> print(x)
1

💡说明:

  • 在Python中,for循环使用它们所在的作用域,并将其定义的循环变量留在后面。如果我们之前在全局名称空间中显式定义了for-loop变量,这也适用。在这种情况下,它将重新绑定现有变量
  • 列表理解示例的Python 2.x和Python 3.x解释器的输出差异可通过中记录的以下更改进行解释What’s New In Python 3.0更改日志:

    “列表理解不再支持语法形式[... for var in item1, item2, ...]使用[... for var in (item1, item2, ...)]取而代之的是。还要注意,列表理解具有不同的语义:它们更接近于list()构造函数,特别是循环控制变量不再泄漏到周围的作用域。“


▶注意默认的可变参数!

def some_func(default_arg=[]):
    default_arg.append("some_string")
    return default_arg

输出:

>>> some_func()
['some_string']
>>> some_func()
['some_string', 'some_string']
>>> some_func([])
['some_string']
>>> some_func()
['some_string', 'some_string', 'some_string']

💡说明:

  • Python中函数的默认可变参数并不是在您每次调用函数时都真正初始化的。取而代之的是,使用最近分配给它们的值作为默认值。当我们显式地传递[]some_func作为参数,default_arg未使用变量,因此函数按预期返回

    def some_func(default_arg=[]):
        default_arg.append("some_string")
        return default_arg

    输出:

    >>> some_func.__defaults__ #This will show the default argument values for the function
    ([],)
    >>> some_func()
    >>> some_func.__defaults__
    (['some_string'],)
    >>> some_func()
    >>> some_func.__defaults__
    (['some_string', 'some_string'],)
    >>> some_func([])
    >>> some_func.__defaults__
    (['some_string', 'some_string'],)
  • 避免由于可变参数导致的错误的常见做法是将None作为默认值,稍后检查是否有任何值传递给与该参数对应的函数。示例:

    def some_func(default_arg=None):
        if default_arg is None:
            default_arg = []
        default_arg.append("some_string")
        return default_arg

▶捕捉异常

some_list = [1, 2, 3]
try:
    # This should raise an ``IndexError``
    print(some_list[4])
except IndexError, ValueError:
    print("Caught!")

try:
    # This should raise a ``ValueError``
    some_list.remove(4)
except IndexError, ValueError:
    print("Caught again!")

输出(Python 2.x):

Caught!

ValueError: list.remove(x): x not in list

输出(Python 3.x):

  File "<input>", line 3
    except IndexError, ValueError:
                     ^
SyntaxError: invalid syntax

💡解释

  • 要向EXCEPT子句添加多个异常,需要将它们作为带括号的元组作为第一个参数传递。第二个参数是一个可选名称,当提供该名称时,它将绑定已引发的异常实例。例如,

    some_list = [1, 2, 3]
    try:
       # This should raise a ``ValueError``
       some_list.remove(4)
    except (IndexError, ValueError), e:
       print("Caught again!")
       print(e)

    输出(Python 2.x):

    Caught again!
    list.remove(x): x not in list
    

    输出(Python 3.x):

      File "<input>", line 4
        except (IndexError, ValueError), e:
                                         ^
    IndentationError: unindent does not match any outer indentation level
  • 不建议使用逗号将异常与变量分开,这在Python3中不起作用;正确的方法是使用as例如,

    some_list = [1, 2, 3]
    try:
        some_list.remove(4)
    
    except (IndexError, ValueError) as e:
        print("Caught again!")
        print(e)

    输出:

    Caught again!
    list.remove(x): x not in list
    

▶同样的操作数,不同的故事!

1个

a = [1, 2, 3, 4]
b = a
a = a + [5, 6, 7, 8]

输出:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4]

2个

a = [1, 2, 3, 4]
b = a
a += [5, 6, 7, 8]

输出:

>>> a
[1, 2, 3, 4, 5, 6, 7, 8]
>>> b
[1, 2, 3, 4, 5, 6, 7, 8]

💡说明:

  • a += b并不总是以相同的方式表现为a = a + b班级可能实施op=运算符不同,列表就是这样做的
  • 表达式a = a + [5,6,7,8]生成新列表并设置a对新列表的引用,离开b不变
  • 表达式a += [5,6,7,8]实际上映射到在列表上操作的“扩展”函数,以便ab仍然指向已就地修改的同一列表

▶忽略类作用域的名称解析

1个

x = 5
class SomeClass:
    x = 17
    y = (x for i in range(10))

输出:

>>> list(SomeClass.y)[0]
5

2个

x = 5
class SomeClass:
    x = 17
    y = [x for i in range(10)]

输出(Python 2.x):

>>> SomeClass.y[0]
17

输出(Python 3.x):

>>> SomeClass.y[0]
5

💡解释

  • 嵌套在类定义内的作用域忽略类级别绑定的名称
  • 生成器表达式有其自己的作用域
  • 从Python3.x开始,列表理解也有自己的作用域

▶像银行家一样圆滑*

让我们实现一个朴素的函数来获取列表的中间元素:

def get_middle(some_list):
    mid_index = round(len(some_list) / 2)
    return some_list[mid_index - 1]

Python 3.x:

>>> get_middle([1])  # looks good
1
>>> get_middle([1,2,3])  # looks good
2
>>> get_middle([1,2,3,4,5])  # huh?
2
>>> len([1,2,3,4,5]) / 2  # good
2.5
>>> round(len([1,2,3,4,5]) / 2)  # why?
2

看起来Python似乎将2.5舍入为2

💡说明:

  • 这不是浮点精度错误,事实上,此行为是故意的。从Python3.0开始,round()用途banker’s rounding其中0.5个分数四舍五入到最接近的甚至编号:

>>> round(0.5)
0
>>> round(1.5)
2
>>> round(2.5)
2
>>> import numpy  # numpy does the same
>>> numpy.round(0.5)
0.0
>>> numpy.round(1.5)
2.0
>>> numpy.round(2.5)
2.0
  • 这是对0.5小数进行舍入的推荐方式,如中所述IEEE 754然而,另一种方式(从零开始四舍五入)大部分时间都是在学校教授的,所以银行家的舍入可能不是那么出名。此外,一些最流行的编程语言(例如:JavaScript、Java、C/C++、Ruby、Rust)也不使用银行家取整。因此,这对于Python语言来说仍然非常特殊,并且在对分数进行舍入时可能会导致念力
  • 请参阅round() docsthis stackoverflow thread了解更多信息
  • 请注意,get_middle([1])仅返回1,因为索引为round(0.5) - 1 = 0 - 1 = -1,返回列表中的最后一个元素

▶干草堆里的针*

到目前为止,我还没有遇到过一位体验过Pythonist的人,他没有遇到过以下一个或多个场景,

1个

x, y = (0, 1) if True else None, None

输出:

>>> x, y  # expected (0, 1)
((0, 1), None)

2个

t = ('one', 'two')
for i in t:
    print(i)

t = ('one')
for i in t:
    print(i)

t = ()
print(t)

输出:

one
two
o
n
e
tuple()

3个

ten_words_list = [
    "some",
    "very",
    "big",
    "list",
    "that"
    "consists",
    "of",
    "exactly",
    "ten",
    "words"
]

输出

>>> len(ten_words_list)
9

4.主张不够有力

a = "python"
b = "javascript"

输出:

# An assert statement with an assertion failure message.
>>> assert(a == b, "Both languages are different")
# No AssertionError is raised

5个

some_list = [1, 2, 3]
some_dict = {
  "key_1": 1,
  "key_2": 2,
  "key_3": 3
}

some_list = some_list.append(4) 
some_dict = some_dict.update({"key_4": 4})

输出:

>>> print(some_list)
None
>>> print(some_dict)
None

6个

def some_recursive_func(a):
    if a[0] == 0:
        return
    a[0] -= 1
    some_recursive_func(a)
    return a

def similar_recursive_func(a):
    if a == 0:
        return a
    a -= 1
    similar_recursive_func(a)
    return a

输出:

>>> some_recursive_func([5, 0])
[0, 0]
>>> similar_recursive_func(5)
4

💡说明:

  • 对于%1,预期行为的正确语句为x, y = (0, 1) if True else (None, None)
  • 对于2,预期行为的正确语句为t = ('one',)t = 'one',(缺少逗号)否则口译员会认为t成为一名str并逐个字符对其进行迭代
  • ()是一个特殊标记,表示为空tuple
  • 在3中,正如您可能已经知道的那样,第5个元素后面缺少逗号("that")。因此,通过隐式字符串文字连接,

    >>> ten_words_list
    ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
  • 不是的AssertionError在第四个代码段中引发,因为不是断言单个表达式a == b,我们断言整个元组。下面的代码片断将澄清问题,

    >>> a = "python"
    >>> b = "javascript"
    >>> assert a == b
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
    AssertionError
    
    >>> assert (a == b, "Values are not equal")
    <stdin>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
    
    >>> assert a == b, "Values are not equal"
    Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
    AssertionError: Values are not equal
  • 对于第五个代码段,大多数修改序列/映射对象项的方法,如list.appenddict.updatelist.sort等,就地修改对象并返回None这背后的基本原理是,如果操作可以就地完成,则可以通过避免复制对象来提高性能(请参阅here)
  • 最后一个应该是相当明显的可变对象(如list)可以在函数中更改,并且重新分配不可变的(a -= 1)不是对价值的更改
  • 从长远来看,意识到这些吹毛求疵可以为您节省数小时的调试工作

▶分裂*

>>> 'a'.split()
['a']

# is same as
>>> 'a'.split(' ')
['a']

# but
>>> len(''.split())
0

# isn't the same as
>>> len(''.split(' '))
1

💡说明:

  • 最初可能显示分割的默认分隔符是单个空格' ',但根据docs

    如果未指定SEP或None,则应用不同的拆分算法:连续的空格串被视为单个分隔符,如果字符串具有前导空格或尾随空格,则结果的开头或结尾处将不包含空字符串。因此,拆分空字符串或仅由空格组成的字符串(使用NONE分隔符)将返回[]如果给定了SEP,则连续的分隔符不会组合在一起,并被视为分隔空字符串(例如,'1,,2'.split(',')退货['1', '', '2'])。使用指定的分隔符拆分空字符串将返回['']

  • 注意以下代码片段中前导空格和尾随空格的处理方式会让事情变得清晰起来,

    >>> ' a '.split(' ')
    ['', 'a', '']
    >>> ' a '.split()
    ['a']
    >>> ''.split(' ')
    ['']

▶野生进口**

# File: module.py

def some_weird_name_func_():
    print("works!")

def _another_weird_name_func():
    print("works!")

输出

>>> from module import *
>>> some_weird_name_func_()
"works!"
>>> _another_weird_name_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_another_weird_name_func' is not defined

💡说明:

  • 通常建议不要使用通配符导入。第一个显而易见的原因是,在通配符导入中,不会导入带有前导下划线的名称。这可能会导致运行时出错
  • 如果我们用了from ... import a, b, c语法,以上NameError就不会发生

    >>> from module import some_weird_name_func_, _another_weird_name_func
    >>> _another_weird_name_func()
    works!
  • 如果您真的想使用通配符导入,那么您必须定义列表__all__在您的模块中,它将包含在执行通配符导入时可用的公共对象列表

    __all__ = ['_another_weird_name_func']
    
    def some_weird_name_func_():
        print("works!")
    
    def _another_weird_name_func():
        print("works!")

    输出

    >>> _another_weird_name_func()
    "works!"
    >>> some_weird_name_func_()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'some_weird_name_func_' is not defined

▶都整理好了吗*

>>> x = 7, 8, 9
>>> sorted(x) == x
False
>>> sorted(x) == sorted(x)
True

>>> y = reversed(x)
>>> sorted(y) == sorted(y)
False

💡说明:

  • 这个sorted方法始终返回列表,比较列表和元组始终返回False在Python中
  • >>> [] == tuple()
    False
    >>> x = 7, 8, 9
    >>> type(x), type(sorted(x))
    (tuple, list)
  • 不像sorted,即reversed方法返回迭代器。为什么?因为排序需要就地修改迭代器或使用额外的容器(列表),而反转只需从最后一个索引迭代到第一个索引即可
  • 所以在比较的时候sorted(y) == sorted(y),第一次调用sorted()将使用迭代器y,下一次调用将只返回一个空列表

    >>> x = 7, 8, 9
    >>> y = reversed(x)
    >>> sorted(y), sorted(y)
    ([7, 8, 9], [])

▶午夜时间不存在吗?

from datetime import datetime

midnight = datetime(2018, 1, 1, 0, 0)
midnight_time = midnight.time()

noon = datetime(2018, 1, 1, 12, 0)
noon_time = noon.time()

if midnight_time:
    print("Time at midnight is", midnight_time)

if noon_time:
    print("Time at noon is", noon_time)

输出(<3.5):

('Time at noon is', datetime.time(12, 0))

未打印午夜时间

💡说明:

在Python 3.5之前的版本中,datetime.time对象被认为是False如果它代表协调世界时的午夜。在使用if obj:语法,以检查是否obj为NULL或与“Empty”等价物。



部分:隐藏的宝藏!

这一节包含一些像我这样的初学者不知道的关于Python的鲜为人知和有趣的事情(好吧,现在不知道了)

▶好的,python,能给我做飞翔吗?

好的,给你

import antigravity

输出:嘘。这是个超级秘密

💡说明:

  • antigravity模块是Python开发人员发布的为数不多的复活节彩蛋之一
  • import antigravity打开Web浏览器,指向classic XKCD comic关于Python
  • 嗯,还有更多的原因。那里有复活节彩蛋里面的另一个复活节彩蛋如果你看一下code中定义了一个函数,该函数旨在实现XKCD’s geohashing algorithm

goto但是为什么呢?

from goto import goto, label
for i in range(9):
    for j in range(9):
        for k in range(9):
            print("I am trapped, please rescue!")
            if k == 2:
                goto .breakout # breaking out from a deeply nested loop
label .breakout
print("Freedom!")

输出(Python 2.3):

I am trapped, please rescue!
I am trapped, please rescue!
Freedom!

💡说明:

  • 的工作版本goto在Python中是announced作为2004年4月1日的愚人节笑话
  • 当前版本的Python没有此模块
  • 虽然有效,但请不要使用。这是reason为什么goto在Python中不存在

▶振作起来!

如果您不喜欢在Python中使用空格来表示作用域,您可以使用C样式{},方法是导入

from __future__ import braces

输出:

  File "some_file.py", line 1
    from __future__ import braces
SyntaxError: not a chance

牙套?不行!如果您认为这令人失望,可以使用Java。好的,另一件令人惊讶的事,你能找到SyntaxError成长于__future__模块code

💡说明:

  • 这个__future__模块通常用于提供未来版本的Python的功能。然而,在这一特定背景下的“未来”是具有讽刺意味的。
  • 这是一个复活节彩蛋,关注社区在这个问题上的感受
  • 代码实际上是存在的here在……里面future.c文件
  • 当CPython编译器遇到future statement,它首先在future.c在将其视为普通导入语句之前

▶让我们来见见友好的终生语言大叔

输出(Python 3.x)

>>> from __future__ import barry_as_FLUFL
>>> "Ruby" != "Python" # there's no doubt about it
  File "some_file.py", line 1
    "Ruby" != "Python"
              ^
SyntaxError: invalid syntax

>>> "Ruby" <> "Python"
True

好了,我们走吧

💡说明:

  • 这与以下内容相关PEP-4012009年4月1日上映(现在你知道这意味着什么了)
  • 引用PEP-401

    认识到Python3.0中的!=不等式运算符是一个可怕的、会导致手指疼痛的错误,FLUFL恢复了<>菱形运算符作为唯一拼写

  • 巴里叔叔在PEP中有更多的东西要分享;你可以阅读它们here
  • 它在交互环境中工作得很好,但它会引发SyntaxError当您通过python文件运行时(请参阅此issue)。但是,您可以将语句包装在evalcompile为了让它运转起来,

    from __future__ import barry_as_FLUFL
    print(eval('"Ruby" <> "Python"'))

▶即使是python也明白爱情是复杂的

import this

等等,这是什么this就是爱❤️

输出:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

这是巨蟒的禅宗!

>>> love = this
>>> this is love
True
>>> love is True
False
>>> love is False
False
>>> love is not True or False
True
>>> love is not True or False; love is love  # Love is complicated
True

💡说明:

  • thisPython中的模块是Python禅宗的复活节彩蛋(PEP 20)
  • 如果您认为这已经足够有趣,请查看this.py有趣的是,禅宗的密码违背了它自己(这可能是唯一发生这种情况的地方)
  • 关于这份声明love is not True or False; love is love,具有讽刺意味,但这是不言而喻的(如果不是,请参阅与以下内容相关的示例isis not操作员)

▶是的,它确实存在!

这个elseFOR循环子句一个典型的示例可能是:

  def does_exists_num(l, to_find):
      for num in l:
          if num == to_find:
              print("Exists!")
              break
      else:
          print("Does not exist")

输出:

>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist

这个else异常处理中的子句举个例子,

try:
    pass
except:
    print("Exception occurred!!!")
else:
    print("Try block executed successfully...")

输出:

Try block executed successfully...

💡说明:

  • 这个else循环后的子句仅在没有显式break在所有的迭代之后。你可以把它看作是“不中断”条款。
  • else挡路试水后条款又称“补全条款”,即到达else子句中的子句try语句表示试用挡路实际已成功完成

▶省略号*

def some_func():
    Ellipsis

输出

>>> some_func()
# No output, No Error

>>> SomeRandomString
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'SomeRandomString' is not defined

>>> Ellipsis
Ellipsis

💡解释

  • 在Python中,Ellipsis是全局可用的内置对象,它等效于...

    >>> ...
    Ellipsis
  • 省略可以用于几个目的,
    • 作为尚未编写的代码的占位符(就像pass声明)
    • 在切片语法中表示剩余方向上的完整切片

    >>> import numpy as np
    >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
    array([
        [
            [0, 1],
            [2, 3]
        ],
    
        [
            [4, 5],
            [6, 7]
        ]
    ])

    所以我们的three_dimensional_array是由数组数组组成的数组。假设我们要打印第二个元素(index1)在所有最里面的数组中,我们可以使用省略号绕过前面的所有维度

    >>> three_dimensional_array[:,:,1]
    array([[1, 3],
       [5, 7]])
    >>> three_dimensional_array[..., 1] # using Ellipsis.
    array([[1, 3],
       [5, 7]])

    注意:这适用于任何数量的维度。您甚至可以选择第一个和最后一个维度中的切片,而忽略中间维度(n_dimensional_array[firs_dim_slice, ..., last_dim_slice])

    • 在……里面type hinting仅表示该类型的一部分(如(Callable[..., int]Tuple[str, ...]))
    • 您还可以使用省略号作为默认函数参数(在需要区分“没有传递参数”和“没有传递值”的情况下)

▶纯洁

这个拼写是有意的。请不要为此提交补丁

输出(Python 3.x):

>>> infinity = float('infinity')
>>> hash(infinity)
314159
>>> hash(float('-inf'))
-314159

💡说明:

  • 无穷大的散列是10⁵xπ
  • 有趣的是,float('-inf')在Python3中为“-10⁵xπ”,而在Python2中为“-10⁵x e

▶让我们毁了它吧

1个

class Yo(object):
    def __init__(self):
        self.__honey = True
        self.bro = True

输出:

>>> Yo().bro
True
>>> Yo().__honey
AttributeError: 'Yo' object has no attribute '__honey'
>>> Yo()._Yo__honey
True

2个

class Yo(object):
    def __init__(self):
        # Let's try something symmetrical this time
        self.__honey__ = True
        self.bro = True

输出:

>>> Yo().bro
True

>>> Yo()._Yo__honey__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Yo' object has no attribute '_Yo__honey__'

为什么要Yo()._Yo__honey工作?

3个

_A__variable = "Some value"

class A(object):
    def some_func(self):
        return __variable # not initialized anywhere yet

输出:

>>> A().__variable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__variable'

>>> A().some_func()
'Some value'

💡说明:

  • Name Mangling用于避免不同命名空间之间的命名冲突。
  • 在Python中,解释器修改(损坏)以开头的类成员名称__(双下划线,也称为“下划线”),并且不能以多个尾部下划线结尾,方法是添加_NameOfTheClass在前面
  • 因此,要访问__honey属性,我们必须在第一个代码段中追加_Yo添加到前面,这样可以防止与任何其他类中定义的相同名称属性发生冲突
  • 但是为什么它在第二个片段中不起作用呢?因为名称损坏会排除以双下划线结尾的名称
  • 第三个代码片段也是名称损坏的结果。名字__variable在声明中return __variable被弄得残缺不全_A__variable,恰好也是我们在外部作用域中声明的变量的名称
  • 此外,如果损坏的名称超过255个字符,则会发生截断


部分:外表是有欺骗性的!

▶跳过台词?

输出:

>>> value = 11
>>> valuе = 32
>>> value
11

无精打采的?

注:要再现这一点,最简单的方法是简单地从上面的代码片段复制语句,并将它们粘贴到文件/shell中

💡解释

有些非西方字符看起来与英语字母表中的字母相同,但口译员认为它们是不同的

>>> ord('е') # cyrillic 'e' (Ye)
1077
>>> ord('e') # latin 'e', as used in English and typed using standard keyboard
101
>>> 'е' == 'e'
False

>>> value = 42 # latin e
>>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
>>> value
42

内置的ord()函数返回字符的Unicodecode point,并且西里尔文‘e’和拉丁文‘e’的不同代码位置证明了上述示例的行为


▶隐形传态

# `pip install numpy` first.
import numpy as np

def energy_send(x):
    # Initializing a numpy array
    np.array([float(x)])

def energy_receive():
    # Return an empty numpy array
    return np.empty((), dtype=np.float).tolist()

输出:

>>> energy_send(123.456)
>>> energy_receive()
123.456

诺贝尔奖在哪里?

💡说明:

  • 请注意,在energy_send函数不返回,因此内存空间可以自由重新分配。
  • numpy.empty()返回下一个可用内存插槽,而不重新初始化它。这个内存点恰好与刚刚释放的内存点相同(通常,但不总是)

▶嗯,有些事很可疑

def square(x):
    """
    A simple function to calculate the square of a number by addition.
    """
    sum_so_far = 0
    for counter in range(x):
        sum_so_far = sum_so_far + x
  return sum_so_far

输出(Python 2.x):

>>> square(10)
10

不是应该是100吗?

注:如果无法重现此文件,请尝试运行该文件mixed_tabs_and_spaces.py通过外壳

💡解释

  • 不要将制表符和空格混为一谈!紧接在回车之前的字符是“制表符”,并且在示例中的其他地方,代码以“4个空格”的倍数缩进
  • 以下是Python处理选项卡的方式:

    首先,制表符被替换(从左到右)1到8个空格,这样替换之前(包括替换)的字符总数是8的倍数<.>

  • 所以最后一行的“制表符”square函数被替换为8个空格,并进入循环
  • Python3非常友好,可以在这种情况下自动抛出错误

    输出(Python 3.x):

    TabError: inconsistent use of tabs and spaces in indentation


部分:其他

+=速度更快

# using "+", three strings:
>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.25748300552368164
# using "+=", three strings:
>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
0.012188911437988281

💡说明:

  • +=比我们的速度要快得多+用于连接两个以上的字符串,因为第一个字符串(例如,s1s1 += s2 + s3)在计算完整字符串时不会被销毁

▶让我们做一根巨大的绳子吧!

def add_string_with_plus(iters):
    s = ""
    for i in range(iters):
        s += "xyz"
    assert len(s) == 3*iters

def add_bytes_with_plus(iters):
    s = b""
    for i in range(iters):
        s += b"xyz"
    assert len(s) == 3*iters

def add_string_with_format(iters):
    fs = "{}"*iters
    s = fs.format(*(["xyz"]*iters))
    assert len(s) == 3*iters

def add_string_with_join(iters):
    l = []
    for i in range(iters):
        l.append("xyz")
    s = "".join(l)
    assert len(s) == 3*iters

def convert_list_to_string(l, iters):
    s = "".join(l)
    assert len(s) == 3*iters

输出:

# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())

>>> NUM_ITERS = 1000
>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> l = ["xyz"]*NUM_ITERS
>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

让我们将迭代次数增加10倍

>>> NUM_ITERS = 10000
>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase
6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase
645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase
1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> l = ["xyz"]*NUM_ITERS
>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase
86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

💡解释

  • 您可以阅读更多关于timeit%timeit在这些链接上。它们用于度量代码片段的执行时间
  • 不要使用+为了生成长字符串-在Python中,str是不可变的,因此对于每对串联,必须将左字符串和右字符串复制到新字符串中。如果连接四个长度为10的字符串,您将复制(10+10)+((10+10)+10)+(10+10)+10)+10)=90个字符,而不仅仅是40个字符。随着字符串的数量和大小的增加,情况会变得平方恶化(这与add_bytes_with_plus功能)
  • 因此,建议您使用.format.%语法(但是,它们比+对于非常短的字符串)
  • 或者更好的是,如果您已经有了可迭代对象形式的内容,那么使用''.join(iterable_object)它的速度要快得多
  • 不像add_bytes_with_plus因为+=上一个示例中讨论的优化,add_string_with_plus没有表现出执行时间的二次增长。如果这份声明是s = s + "x" + "y" + "z"而不是s += "xyz",那么增长将是平方的。

    def add_string_with_plus(iters):
        s = ""
        for i in range(iters):
            s = s + "x" + "y" + "z"
        assert len(s) == 3*iters
    
    >>> %timeit -n100 add_string_with_plus(1000)
    388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    >>> %timeit -n100 add_string_with_plus(10000) # Quadratic increase in execution time
    9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  • 因此,格式化和创建巨型字符串的许多方法与Zen of Python,根据它的说法,

    应该有1个,最好只有一个–显而易见的方法


▶减速dict查找*

some_dict = {str(i): 1 for i in range(1_000_000)}
another_dict = {str(i): 1 for i in range(1_000_000)}

输出:

>>> %timeit some_dict['5']
28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> some_dict[1] = 1
>>> %timeit some_dict['5']
37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>> %timeit another_dict['5']
28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> another_dict[1]  # Trying to access a key that doesn't exist
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 1
>>> %timeit another_dict['5']
38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

为什么相同的查找速度变慢了?

💡说明:

  • CPython有一个通用字典查找函数,可以处理所有类型的键(strint,任何对象。),还有一个专门的,用于由以下内容组成的字典的常见情况str-仅密钥
  • 专用函数(名为lookdict_unicode在CPython的source)知道所有现有键(包括查找的键)都是字符串,并使用更快、更简单的字符串比较来比较键,而不是调用__eq__方法
  • 第一次dict实例使用非str键,则会对其进行修改,以便将来的查找使用泛型函数
  • 此过程对于特定情况是不可逆的dict实例,并且该键甚至不必存在于字典中。这就是为什么尝试失败的查找会产生同样的效果

▶膨胀的实例dict%s*

import sys

class SomeClass:
    def __init__(self):
        self.some_attr1 = 1
        self.some_attr2 = 2
        self.some_attr3 = 3
        self.some_attr4 = 4


def dict_size(o):
    return sys.getsizeof(o.__dict__)

输出:(Python 3.8,其他Python 3版本可能会稍有不同)

>>> o1 = SomeClass()
>>> o2 = SomeClass()
>>> dict_size(o1)
104
>>> dict_size(o2)
104
>>> del o1.some_attr1
>>> o3 = SomeClass()
>>> dict_size(o3)
232
>>> dict_size(o1)
232

我们再试一次。在新的口译器中:

>>> o1 = SomeClass()
>>> o2 = SomeClass()
>>> dict_size(o1)
104  # as expected
>>> o1.some_attr5 = 5
>>> o1.some_attr6 = 6
>>> dict_size(o1)
360
>>> dict_size(o2)
272
>>> o3 = SomeClass()
>>> dict_size(o3)
232

是什么让那些字典变得臃肿呢?为什么新创建的物体也会膨胀呢?

💡说明:

  • CPython能够在多个字典中重用相同的“键”对象。这是在PEP 412有减少内存使用的动机,特别是在实例字典中-其中键(实例属性)往往对所有实例都是通用的
  • 这种优化对于例如字典来说是完全无缝的,但是如果某些假设被打破,它将被禁用
  • 密钥共享字典不支持删除;如果实例属性被删除,则字典是“非共享”的,并且对同一类的所有未来实例禁用密钥共享
  • 此外,如果字典键已调整大小(因为插入了新键),则它们将保持共享仅限如果它们正好由单个字典使用(这允许在__init__第一个创建的实例的属性,而不会导致“取消共享”)。如果在调整大小时存在多个实例,则对同一类的所有未来实例禁用密钥共享:CPython无法知道您的实例是否再使用相同的属性集,因此决定放弃尝试共享它们的密钥
  • 如果您的目标是降低程序的内存占用量,那么给您一个小提示:不要删除实例属性,并确保初始化__init__好了!

▶次要的*

  • join()是字符串操作,而不是列表操作。(第一次使用时有点违反直觉)

    💡说明:如果join()是字符串上的方法,那么它可以操作任何可迭代的(列表、元组、迭代器)。如果它是列表上的方法,则必须由每种类型单独实现。此外,将特定于字符串的方法放在泛型list对象API

  • 一些看起来奇怪但语义正确的陈述:
    • [] = ()是语义上正确的语句(解包一个空的tuple变得空荡荡的list)
    • 'a'[0][0][0][0][0]与字符串一样,也是语义上正确的语句sequencesPython中的(支持使用整数索引访问元素的迭代数)
    • 3 --0-- 5 == 8--5 == 5都是语义上正确的语句,并且求值为True
  • 考虑到这一点a是一个数字,++a--a都是有效的Python语句,但行为方式与C、C++或Java等语言中的类似语句不同

    >>> a = 5
    >>> a
    5
    >>> ++a
    5
    >>> --a
    5

    💡说明:

    • 没有++Python语法中的运算符。实际上是两个+操作员
    • ++a解析为+(+a)这意味着a同样,语句的输出--a可以证明是合理的
    • 此堆栈溢出thread讨论Python中没有递增和递减运算符的原因
  • 您一定知道Python中的Walrus操作符。但是你有没有听说过太空入侵者操作员

    >>> a = 42
    >>> a -=- 1
    >>> a
    43

    它与另一个递增运算符一起用作另一个递增运算符

    >>> a +=+ 1
    >>> a
    >>> 44

    💡说明:这个恶作剧来自于Raymond Hettinger’s tweet空间入侵者操作符实际上只是一个格式错误的a -= (-1)这相当于a = a - (- 1)类似于a += (+ 1)案例

  • Python有一个未记录的converse implication操作员

    >>> False ** False == True
    True
    >>> False ** True == False
    True
    >>> True ** False == True
    True
    >>> True ** True == True
    True

    💡说明:如果你替换掉FalseTrue用0和1相乘并做数学运算,真值表等价于一个逆蕴涵运算符。(Source)

  • 既然我们说的是运营商,还有@矩阵乘法运算符(别担心,这次是实数)

    >>> import numpy as np
    >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
    46

    💡说明:这个@Python3.5中添加了运算符,将科学界考虑在内。任何对象都可以重载__matmul__定义此运算符行为的神奇方法

  • 从Python3.8开始,您可以使用典型的f-string语法,如下所示f'{some_var=}用于快速调试。例如,

    >>> some_string = "wtfpython"
    >>> f'{some_string=}'
    "some_string='wtfpython'"
  • Python使用2个字节存储函数中的局部变量。理论上,这意味着一个函数中只能定义65536个变量。但是,Python内置了一个方便的解决方案,可用于存储超过2^16个变量名。下面的代码演示了当定义了超过65536个局部变量时堆栈中会发生什么(警告:此代码打印大约2^18行文本,因此请做好准备!)

    import dis
    exec("""
    def f():
       """ + """
       """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
    
    f()
    
    print(dis.dis(f))
  • 多个Python线程不会运行您的Python代码同时(是的,你没听错!)产生多个线程并让它们并发执行Python代码似乎很直观,但是由于Global Interpreter Lock在Python中,您所要做的就是让您的线程轮流在同一内核上执行。Python线程适用于IO受限的任务,但要在Python中实现CPU受限任务的实际并行化,您可能需要使用Pythonmultiprocessing模块
  • 有时候,print方法可能不会立即打印值。例如,

    # File some_file.py
    import time
    
    print("wtfpython", end="_")
    time.sleep(3)

    这将打印wtfpython3秒后,由于end参数,因为输出缓冲区在遇到\n或者当程序完成执行时。我们可以通过传递以下参数来强制刷新缓冲区flush=True论据

  • 索引超出界限的列表切片不会引发错误

    >>> some_list = [1, 2, 3, 4, 5]
    >>> some_list[111:]
    []
  • 对迭代数进行切片并不总是会创建一个新对象。例如,

    >>> some_str = "wtfpython"
    >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
    >>> some_list is some_list[:] # False expected because a new object is created.
    False
    >>> some_str is some_str[:] # True because strings are immutable, so making a new object is of not much use.
    True
  • int('١٢٣٤٥٦٧٨٩')退货123456789在Python 3中。在Python中,十进制字符包括数字字符和所有可用于构成小数基数的字符,例如U+0660,阿拉伯数字0。这是一张interesting story与Python的此行为相关
  • 从Python3开始,可以使用下划线分隔数字文字(以提高可读性

    >>> six_million = 6_000_000
    >>> six_million
    6000000
    >>> hex_address = 0xF00D_CAFE
    >>> hex_address
    4027435774
  • 'abc'.count('') == 4以下是以下内容的大致实现count方法,这将使事情变得更清楚。

    def count(s, sub):
        result = 0
        for i in range(len(s) + 1 - len(sub)):
            result += (s[i:i + len(sub)] == sub)
        return result

    该行为是由于匹配空的子字符串(''),并在原始字符串中包含长度为0的片段



贡献

您可以通过几种方式为wtfpython做贡献,

  • 提出新的例子
  • 帮助翻译(请参见issues labeled translation)
  • 较小的更正,如指出过期的代码片段、打字错误、格式错误等
  • 找出差距(如解释不充分、重复示例等)
  • 有没有让这个项目更有趣、更有用的创造性建议?

请看CONTRIBUTING.md了解更多详细信息。您可以随意创建新的issue讨论事情

PS:请不要联系反向链接请求,不会添加任何链接,除非它们与项目高度相关

确认

这个系列的想法和设计最初的灵感来自Denys Dovhan令人惊叹的项目wtfjsPythonistas的压倒性支持给了它现在的样子

一些不错的链接!

🎓许可证

WTFPL 2.0

©Satwik Kansal

让你的朋友也大吃一惊吧!

如果您喜欢wtfpython,您可以使用这些快速链接与您的朋友分享它,

Twitter|Linkedin|Facebook

需要pdf版本吗?

我收到了一些关于wtfpython的pdf(和epub)版本的请求。您可以添加您的详细信息here一做完就拿到

这就是所有的人!对于即将发布的此类内容,您可以添加您的电子邮件here

System-design-primer 学习如何设计大型系统 为系统设计面试做准备

了解如何设计大型系统

为系统设计面试做准备

为系统设计面试做准备

学习如何设计可伸缩的系统将帮助您成为一名更好的工程师

系统设计是一个宽泛的话题。网上有大量关于系统设计原则的资源。

此repo是一个有组织的资源集合,可帮助您了解如何大规模构建系统

编码资源:交互式编码挑战

这是一个不断更新的开放源码项目

欢迎投稿!

步骤1:概述用例、约束和假设

除了对面试进行编码外,系统设计在许多科技公司的技术面试流程中也是必不可少的组成部分

练习常见的系统设计面试问题,并将您的结果与示例解决方案(讨论、代码和图表)进行比较

面试准备的其他主题:

系统设计主题索引

提供的Anki抽认卡套装使用间隔重复来帮助您记住关键的系统设计概念

非常适合在旅途中使用

步骤2:创建高级设计

正在寻找资源来帮助您准备编码面试吗?

请查看姊妹版repo交互式编码挑战,其中包含额外的Anki幻灯片:

学习指导

向社区学习

请随时提交拉取请求以提供帮助:

  • Fix errors
  • Improve sections
  • Add new sections
  • Translate

需要润色的内容正在开发中

查看投稿指南

如何处理系统设计面试问题

各种系统设计主题的摘要,包括优缺点。每件事都是权衡的

每个部分都包含指向更深入资源的链接

系统设计面试问题及解决方案

根据您的面试时间表(短、中、长)建议复习的主题

问:对于面试,我需要知道这里的一切吗?

A:不,你不需要了解这里的一切来准备面试

你在面试中被问到的问题取决于以下变量:

  • How much experience you have
  • What your technical background is
  • What positions you are interviewing for
  • Which companies you are interviewing with
  • Luck

更有经验的应聘者通常会对系统设计有更多的了解。架构师或团队领导可能会比单个贡献者了解更多。顶级科技公司可能会有一轮或多轮设计面试

从宽泛开始,在几个领域深入研究。它有助于您对各种关键的系统设计主题有所了解。根据您的时间表、经验、您面试的职位以及您面试的公司调整以下指南

  • Short timeline – Aim for breadth with system design topics. Practice by solving some interview questions.
  • Medium timeline – Aim for breadth and some depth with system design topics. Practice by solving many interview questions.
  • Long timeline – Aim for breadth and more depth with system design topics. Practice by solving most interview questions.
Short Medium Long
Read through the System design topics to get a broad understanding of how systems work :+1: :+1: :+1:
Read through a few articles in the Company engineering blogs for the companies you are interviewing with :+1: :+1: :+1:
Read through a few Real world architectures :+1: :+1: :+1:
Review How to approach a system design interview question :+1: :+1: :+1:
Work through System design interview questions with solutions Some Many Most
Work through Object-oriented design interview questions with solutions Some Many Most
Review Additional system design interview questions Some Many Most

面向对象的设计面试问题及其解决方案

如何进行撞击系统设计面试题

系统设计面试是一场开放式的谈话。希望你来领导它

您可以使用以下步骤来指导讨论。要帮助巩固此过程,请使用以下步骤完成系统设计面试问题与解决方案部分

步骤3:设计核心组件

收集需求并确定问题范围。提出问题以澄清用例和约束。讨论假设

  • Who is going to use it?
  • How are they going to use it?
  • How many users are there?
  • What does the system do?
  • What are the inputs and outputs of the system?
  • How much data do we expect to handle?
  • How many requests per second do we expect?
  • What is the expected read to write ratio?

步骤4:调整设计比例

概述包含所有重要组件的高级设计

  • Sketch the main components and connections
  • Justify your ideas

粗略计算

深入了解每个核心组件的详细信息。例如,如果您被要求设计一个url缩短服务,请讨论:

  • Generating and storing a hash of the full url
    • MD5 and Base62
    • Hash collisions
    • SQL or NoSQL
    • Database schema
  • Translating a hashed url to the full url
    • Database lookup
  • API and object-oriented design

来源和进一步阅读

在给定约束的情况下,确定并解决瓶颈问题。例如,您是否需要以下内容来解决可伸缩性问题?

  • Load balancer
  • Horizontal scaling
  • Caching
  • Database sharding

讨论潜在的解决方案和权衡。每件事都是权衡的。使用可扩展系统设计原则解决瓶颈问题

Design Pastebin.com(或bit.ly)

你可能会被要求手工做一些估算。有关以下资源,请参阅附录:

设计Twitter时间表和搜索(或Facebook提要和搜索)

请查看以下链接,以更好地了解预期内容:

系统设计主题:从此处开始

带有示例讨论、代码和图表的常见系统设计面试问题

链接到解决方案/文件夹中内容的解决方案

Question
Design Pastebin.com (or Bit.ly) Solution
Design the Twitter timeline and search (or Facebook feed and search) Solution
Design a web crawler Solution
Design Mint.com Solution
Design the data structures for a social network Solution
Design a key-value store for a search engine Solution
Design Amazon’s sales ranking by category feature Solution
Design a system that scales to millions of users on AWS Solution
Add a system design question Contribute

设计一个网络爬虫程序

查看练习和解决方案

Design Mint.com

查看练习和解决方案

设计社交网络的数据结构

查看练习和解决方案

为搜索引擎设计键值存储

查看练习和解决方案

按类别功能设计亚马逊的销售排名

查看练习和解决方案

设计可扩展到数百万AWS用户的系统

查看练习和解决方案

第1步:复习可扩展性视频课程

查看练习和解决方案

步骤2:查看可伸缩性文章

查看练习和解决方案

性能与可扩展性

常见的面向对象设计面试问题,带有示例讨论、代码和图表

注:此部分正在开发中。

是系统设计的新手吗?

Question
Design a hash map Solution
Design a least recently used cache Solution
Design a call center Solution
Design a deck of cards Solution
Design a parking lot Solution
Design a chat server Solution
Design a circular array Contribute
Add an object-oriented design question Contribute

延迟与吞吐量

首先,您需要基本了解通用原则,了解它们是什么、如何使用以及它们的优缺点

哈佛大学可伸缩性讲座

下一步

可扩展性

  • Topics covered:
    • Vertical scaling
    • Horizontal scaling
    • Caching
    • Load balancing
    • Database replication
    • Database partitioning

盖子定理

接下来,我们来看看高级权衡:

弱一致性

请记住,每件事都是权衡的。

  • Performance vs scalability
  • Latency vs throughput
  • Availability vs consistency

然后,我们将深入探讨更具体的主题,如DNS、CDN和负载均衡器

如果服务以与添加的资源成正比的方式提高性能,则该服务是可伸缩的。通常,提高性能意味着服务更多的工作单元,但也可以处理更大的工作单元,例如当数据集增长时。1

可用性与一致性

看待性能与可伸缩性的另一种方式:

延迟是执行某种操作或产生某种结果的时间

  • If you have a performance problem, your system is slow for a single user.
  • If you have a scalability problem, your system is fast for a single user but slow under heavy load.

最终一致性

一致性模式

吞吐量是每单位时间内此类操作或结果的数量

通常,您应该以具有可接受延迟的最大吞吐量为目标

资料来源:复习上限定理

强一致性

可用性模式

故障转移

在分布式计算机系统中,您只能支持以下两项保证:

网络不可靠,因此您需要支持分区容错。您需要在一致性和可用性之间进行软件权衡

  • Consistency – Every read receives the most recent write or an error
  • Availability – Every request receives a response, without guarantee that it contains the most recent version of the information
  • Partition Tolerance – The system continues to operate despite arbitrary partitioning due to network failures

等待来自分区节点的响应可能会导致超时错误。如果您的业务需求需要原子读写,CP是一个很好的选择

SQL调优

响应返回任何节点上可用的最容易获得的数据版本,该版本可能不是最新版本。在解析分区时,写入可能需要一些时间才能传播

键值存储

如果业务需要考虑到最终的一致性,或者当系统需要在出现外部错误的情况下继续工作时,AP是一个很好的选择

对于同一数据的多个副本,我们面临着如何对它们进行同步操作的选择,以便客户对数据有一致的看法。回想一下CAP定理中的一致性定义-每次读取都会收到最近的写入或错误

缺点:故障转移

域名系统

写入后,读取可能会也可能看不到它。采取了尽力而为的方法

复制

这种方法可以在memcached等系统中看到。弱一致性适用于VoIP、视频聊天和实时多人游戏等实时用例。例如,如果您正在打电话,并且在几秒钟内失去接收,那么当您重新连接时,您听不到在连接中断期间所说的话

写入之后,读取最终会看到它(通常在毫秒内)。异步复制数据

数量上的可用性

在DNS和电子邮件等系统中可以看到这种方法。最终一致性在高可用性系统中运行良好

写入后,Reads将看到它。同步复制数据

缺点:DNS

这种方法可以在文件系统和RDBMS中看到。强一致性在需要事务的系统中运行良好

支持高可用性有两种互补模式:故障切换和复制

推流CDN

内容交付网络

使用主动-被动故障转移时,会在处于备用状态的主动服务器和被动服务器之间发送心跳。如果心跳中断,被动服务器将接管主动服务器的IP地址并恢复服务

拉取CDN

文档存储

停机时间的长短取决于被动服务器是否已经在“热”待机状态下运行,或者它是否需要从“冷”待机状态启动。只有活动服务器才能处理流量

主动-被动故障切换也可以称为主-从故障切换

在主动-主动模式下,两台服务器都在管理流量,在它们之间分担负载

宽列存储

如果服务器是面向公众的,则DNS需要知道两个服务器的公共IP。如果服务器是面向内部的,则应用程序逻辑需要了解这两个服务器

主动-主动故障切换也可以称为主-主故障切换

此主题将在数据库部分进一步讨论:

劣势:CDN

  • Fail-over adds more hardware and additional complexity.
  • There is a potential for loss of data if the active system fails before any newly written data can be replicated to the passive.

第4层负载均衡

图形数据库

可用性通常通过正常运行时间(或停机时间)作为服务可用时间的百分比来量化。可用性通常用数字9来衡量–99.99%可用性的服务被描述为有四个9

第7层负载均衡

如果服务由多个容易发生故障的组件组成,则服务的总体可用性取决于这些组件是顺序的还是并行的

来源和进一步阅读:NoSQL

Duration Acceptable downtime
Downtime per year 8h 45min 57s
Downtime per month 43m 49.7s
Downtime per week 10m 4.8s
Downtime per day 1m 26.4s

后备缓存

Duration Acceptable downtime
Downtime per year 52min 35.7s
Downtime per month 4m 23s
Downtime per week 1m 5s
Downtime per day 8.6s

直写

当两个可用性<100%的组件按顺序排列时,总体可用性会降低:

In sequence

如果Foo和Bar都有99.9%的可用性,那么它们的总可用性依次为99.8%

Availability (Total) = Availability (Foo) * Availability (Bar)

当两个可用性<100%的组件并行时,总体可用性会提高:

In parallel

如果Foo和BAR都有99.9%的可用性,那么它们并行的总可用性将是99.9999%

Availability (Total) = 1 - (1 - Availability (Foo)) * (1 - Availability (Bar))

来源:DNS安全演示

负载均衡器

域名系统(DNS)将域名(如www.example.com)转换为IP地址

DNS是分层的,顶层有几个权威服务器。您的路由器或ISP提供有关执行查找时联系哪些DNS服务器的信息。较低级别的DNS服务器缓存映射,这些映射可能会因DNS传播延迟而变得陈旧。您的浏览器或操作系统还可以将DNS结果缓存一段时间,具体取决于生存时间(TTL)

CloudFlare和Route 53等服务提供托管DNS服务。某些DNS服务可以通过各种方法路由流量:

  • NS record (name server) – Specifies the DNS servers for your domain/subdomain.
  • MX record (mail exchange) – Specifies the mail servers for accepting messages.
  • A record (address) – Points a name to an IP address.
  • CNAME (canonical) – Points a name to another name or CNAME (example.com to www.example.com) or to an A record.

来源:为什么使用CDN

水平缩放

  • Accessing a DNS server introduces a slight delay, although mitigated by caching described above.
  • DNS server management could be complex and is generally managed by governments, ISPs, and large companies.
  • DNS services have recently come under DDoS attack, preventing users from accessing websites such as Twitter without knowing Twitter’s IP address(es).

缺点:负载均衡器

反向代理(Web服务器)

内容递送网络(CDN)是全球分布的代理服务器网络,提供来自更靠近用户的位置的内容。一般情况下,静电文件(如html/css/js)、照片和视频都是由云服务提供的,但有些云服务(如亚马逊的云前端)支持动态内容。站点的DNS解析将告诉客户端要联系哪个服务器

从CDN提供内容可以通过两种方式显著提高性能:

每当您的服务器发生更改时,推送CDN都会接收新内容。您负责提供内容、直接上传到CDN、重写指向CDN的URL。您可以配置内容何时过期以及何时更新。只有在内容是新的或更改的情况下才会上载内容,从而最大限度地减少流量,但最大限度地提高存储

  • Users receive content from data centers close to them
  • Your servers do not have to serve requests that the CDN fulfills

负载均衡器与反向代理

流量较小的站点或内容不经常更新的站点可以很好地使用推送CDN。内容只放在CDN上一次,而不是定期重新拉取

拉取CDN在第一个用户请求内容时从您的服务器抓取新内容。您将内容保留在服务器上,并重写URL以指向CDN。这会导致请求速度变慢,直到内容缓存在CDN上

缺点:反向代理

生存时间(TTL)确定缓存内容的时间长度。拉取CDN最大限度地减少了CDN上的存储空间,但如果文件过期并在实际更改之前被拉取,则可能会产生冗余流量

流量大的站点可以很好地使用拉式CDN,因为流量分布更均匀,只有最近请求的内容保留在CDN上

来源:可伸缩系统设计模式

微服务

  • CDN costs could be significant depending on traffic, although this should be weighed with additional costs you would incur not using a CDN.
  • Content might be stale if it is updated before the TTL expires it.
  • CDNs require changing URLs for static content to point to the CDN.

服务发现

应用层

负载平衡器将传入的客户端请求分发到应用程序服务器和数据库等计算资源。在每种情况下,负载均衡器都会将来自计算资源的响应返回到相应的客户端。负载均衡器在以下方面有效:

负载均衡器可以通过硬件(昂贵)或软件(如HAProxy)实现

  • Preventing requests from going to unhealthy servers
  • Preventing overloading resources
  • Helping to eliminate a single point of failure

其他优势包括:

为了防止故障,通常在主动-被动或主动-主动模式下设置多个负载均衡器

  • SSL termination – Decrypt incoming requests and encrypt server responses so backend servers do not have to perform these potentially expensive operations
  • Session persistence – Issue cookies and route a specific client’s requests to same instance if the web apps do not keep track of sessions

负载均衡器可以根据各种指标路由流量,包括:

第4层负载均衡器查看传输层的信息,以决定如何分发请求。通常,这涉及报头中的源IP地址、目的IP地址和端口,但不涉及数据包的内容。第4层负载均衡器转发进出上游服务器的网络数据包,执行网络地址转换(NAT)

缺点:应用层

第7层负载均衡器查看应用层以决定如何分发请求。这可能涉及标头、消息和Cookie的内容。第7层负载均衡器终止网络流量,读取消息,做出负载平衡决策,然后打开到所选服务器的连接。例如,第7层负载均衡器可以将视频流量定向到托管视频的服务器,同时将更敏感的用户计费流量定向到经过安全强化的服务器

关系数据库管理系统(RDBMS)

以灵活性为代价,与第7层相比,第4层负载平衡需要更少的时间和计算资源,尽管对现代商用硬件的性能影响可能微乎其微

负载均衡器还可以帮助进行水平扩展,从而提高性能和可用性。与在更昂贵的硬件上纵向扩展单个服务器(称为垂直扩展)相比,使用商用计算机进行横向扩展更具成本效益,并带来更高的可用性。与专门的企业系统相比,在商用硬件上工作的人才也更容易招聘

NoSQL

来源:维基百科

写后(回写)

  • Scaling horizontally introduces complexity and involves cloning servers
    • Servers should be stateless: they should not contain any user-related data like sessions or profile pictures
    • Sessions can be stored in a centralized data store such as a database (SQL, NoSQL) or a persistent cache (Redis, Memcached)
  • Downstream servers such as caches and databases need to handle more simultaneous connections as upstream servers scale out

SQL或NoSQL

  • The load balancer can become a performance bottleneck if it does not have enough resources or if it is not configured properly.
  • Introducing a load balancer to help eliminate a single point of failure results in increased complexity.
  • A single load balancer is a single point of failure, configuring multiple load balancers further increases complexity.

客户端缓存

数据库

反向代理是集中内部服务并向公众提供统一接口的Web服务器。在反向代理将服务器的响应返回给客户端之前,将来自客户端的请求转发到可以实现该请求的服务器

来源:规模架构系统简介

通过将Web层与应用层(也称为平台层)分开,您可以分别扩展和配置这两个层。添加新API会导致添加应用程序服务器,而不必添加额外的Web服务器。单一责任原则主张共同工作的小型自主服务。拥有小型服务的小型团队可以更积极地规划快速增长

  • Increased security – Hide information about backend servers, blacklist IPs, limit number of connections per client
  • Increased scalability and flexibility – Clients only see the reverse proxy’s IP, allowing you to scale servers or change their configuration
  • SSL termination – Decrypt incoming requests and encrypt server responses so backend servers do not have to perform these potentially expensive operations
  • Compression – Compress server responses
  • Caching – Return the response for cached requests
  • Static content – Serve static content directly
    • HTML/CSS/JS
    • Photos
    • Videos
    • Etc

CDN缓存

  • Deploying a load balancer is useful when you have multiple servers. Often, load balancers route traffic to a set of servers serving the same function.
  • Reverse proxies can be useful even with just one web server or application server, opening up the benefits described in the previous section.
  • Solutions such as NGINX and HAProxy can support both layer 7 reverse proxying and load balancing.

Web服务器缓存

  • Introducing a reverse proxy results in increased complexity.
  • A single reverse proxy is a single point of failure, configuring multiple reverse proxies (ie a failover) further increases complexity.

数据库缓存

高速缓存

应用程序层中的工作者也有助于启用异步

与此讨论相关的是微服务,可以将其描述为一套可独立部署的小型模块化服务。每个服务都运行一个独特的流程,并通过定义良好的轻量级机制进行通信,以服务于业务目标。1个

例如,Pinterest可以拥有以下微服务:用户档案、追随者、馈送、搜索、照片上传等

应用程序缓存

诸如Consul、etcd和ZooKeeper这样的系统可以通过跟踪注册的名称、地址和端口来帮助服务找到彼此。运行状况检查有助于验证服务完整性,通常使用HTTP端点来完成。Consul和etcd都有一个内置的键值存储,可用于存储配置值和其他共享数据

来源:向上扩展至您的第一个1000万用户

数据库查询级别的高速缓存

像SQL这样的关系数据库是以表形式组织的数据项的集合

对象级别的缓存

  • Adding an application layer with loosely coupled services requires a different approach from an architectural, operations, and process viewpoint (vs a monolithic system).
  • Microservices can add complexity in terms of deployments and operations.

何时更新缓存

异步

ACID是关系数据库事务的一组属性

缺点:缓存

扩展关系数据库有许多技术:主-从复制、主-主复制、联合、分片、反规范化和SQL调优

主机服务于读取和写入,将写入复制到一个或多个仅服务于读取的从机。从属设备还可以以树状方式复制到其他从属设备。如果主机脱机,系统可以继续以只读模式运行,直到将从属提升为主机或调配新主机

  • Atomicity – Each transaction is all or nothing
  • Consistency – Any transaction will bring the database from one valid state to another
  • Isolation – Executing transactions concurrently has the same results as if the transactions were executed serially
  • Durability – Once a transaction has been committed, it will remain so

来源:可伸缩性、可用性、稳定性、模式

提前刷新

两个主机都提供读写服务,并在写入时相互协调。如果任一主机发生故障,系统可以在读取和写入的情况下继续运行

来源:可伸缩性、可用性、稳定性、模式

Disadvantage(s): master-slave replication
  • Additional logic is needed to promote a slave to a master.
  • See Disadvantage(s): replication for points related to both master-slave and master-master.

来源和进一步阅读:HTTP

来源:向上扩展至您的第一个1000万用户

联合(或功能分区)按功能拆分数据库。例如,您可以拥有三个数据库,而不是一个单一的整体数据库:论坛、用户和产品,从而减少每个数据库的读写流量,从而减少复制延迟。较小的数据库会产生更多可以放入内存的数据,这反过来又会因为改进的高速缓存位置而导致更多的高速缓存命中率。由于没有单个中央主机串行化写入,您可以并行写入,从而增加吞吐量

Disadvantage(s): master-master replication
  • You’ll need a load balancer or you’ll need to make changes to your application logic to determine where to write.
  • Most master-master systems are either loosely consistent (violating ACID) or have increased write latency due to synchronization.
  • Conflict resolution comes more into play as more write nodes are added and as latency increases.
  • See Disadvantage(s): replication for points related to both master-slave and master-master.
Disadvantage(s): replication
  • There is a potential for loss of data if the master fails before any newly written data can be replicated to other nodes.
  • Writes are replayed to the read replicas. If there are a lot of writes, the read replicas can get bogged down with replaying writes and can’t do as many reads.
  • The more read slaves, the more you have to replicate, which leads to greater replication lag.
  • On some systems, writing to the master can spawn multiple threads to write in parallel, whereas read replicas only support writing sequentially with a single thread.
  • Replication adds more hardware and additional complexity.
Source(s) and further reading: replication

来源和进一步阅读:TCP和UDP

来源:可伸缩性、可用性、稳定性、模式

分片将数据分布在不同的数据库中,以便每个数据库只能管理数据的一个子集。以用户数据库为例,随着用户数量的增加,集群中会添加更多的分片

Disadvantage(s): federation
  • Federation is not effective if your schema requires huge functions or tables.
  • You’ll need to update your application logic to determine which database to read and write.
  • Joining data from two databases is more complex with a server link.
  • Federation adds more hardware and additional complexity.
Source(s) and further reading: federation

缺点:RPC

与联合的优点类似,分片可以减少读写流量、减少复制和增加缓存命中率。索引大小也会减小,这通常会通过更快的查询提高性能。如果一个碎片发生故障,其他碎片仍可运行,尽管您需要添加某种形式的复制以避免数据丢失。与联合一样,没有单个串行化写入的中央主机,允许您在提高吞吐量的同时并行写入

共享用户表的常见方式是通过用户的姓氏首字母或用户的地理位置

反规格化试图以牺牲一些写入性能为代价来提高读取性能。数据的冗余副本被写入多个表中,以避免昂贵的联接。一些RDBMS(如PostgreSQL和Oracle)支持物化视图,物化视图处理存储冗余信息和保持冗余副本一致的工作

使用联合和分片等技术分发数据后,管理跨数据中心的联接会进一步增加复杂性。反规格化可能会绕过对这种复杂连接的需要。

Disadvantage(s): sharding
  • You’ll need to update your application logic to work with shards, which could result in complex SQL queries.
  • Data distribution can become lopsided in a shard. For example, a set of power users on a shard could result in increased load to that shard compared to others.
    • Rebalancing adds additional complexity. A sharding function based on consistent hashing can reduce the amount of transferred data.
  • Joining data from multiple shards is more complex.
  • Sharding adds more hardware and additional complexity.
Source(s) and further reading: sharding

劣势:睡觉

在大多数系统中,读取的数量可能远远超过写入的数量100:1甚至1000:1。导致复杂数据库联接的读取可能非常昂贵,会花费大量时间进行磁盘操作

SQL调优是一个涉及面很广的主题,很多书都是作为参考编写的

重要的是要进行基准测试和性能分析,以模拟和发现瓶颈

Disadvantage(s): denormalization
  • Data is duplicated.
  • Constraints can help redundant copies of information stay in sync, which increases complexity of the database design.
  • A denormalized database under heavy write load might perform worse than its normalized counterpart.
Source(s) and further reading: denormalization

来源及进一步阅读:睡觉和rpc

基准测试和性能分析可能会为您提供以下优化

NoSQL是键值存储、文档存储、宽列存储或图形数据库中表示的数据项的集合。数据被反规范化,连接通常在应用程序代码中完成。大多数NoSQL存储缺乏真正的ACID事务,倾向于最终的一致性

  • Benchmark – Simulate high-load situations with tools such as ab.
  • Profile – Enable tools such as the slow query log to help track performance issues.

BASE通常用来描述NoSQL数据库的属性。与CAP定理相比,BASE选择可用性而不是一致性

Tighten up the schema
  • MySQL dumps to disk in contiguous blocks for fast access.
  • Use CHAR instead of VARCHAR for fixed-length fields.
    • CHAR effectively allows for fast, random access, whereas with VARCHAR, you must find the end of a string before moving onto the next one.
  • Use TEXT for large blocks of text such as blog posts. TEXT also allows for boolean searches. Using a TEXT field results in storing a pointer on disk that is used to locate the text block.
  • Use INT for larger numbers up to 2^32 or 4 billion.
  • Use DECIMAL for currency to avoid floating point representation errors.
  • Avoid storing large BLOBS, store the location of where to get the object instead.
  • VARCHAR(255) is the largest number of characters that can be counted in an 8 bit number, often maximizing the use of a byte in some RDBMS.
  • Set the NOT NULL constraint where applicable to improve search performance.
Use good indices
  • Columns that you are querying (SELECT, GROUP BY, ORDER BY, JOIN) could be faster with indices.
  • Indices are usually represented as self-balancing B-tree that keeps data sorted and allows searches, sequential access, insertions, and deletions in logarithmic time.
  • Placing an index can keep the data in memory, requiring more space.
  • Writes could also be slower since the index also needs to be updated.
  • When loading large amounts of data, it might be faster to disable indices, load the data, then rebuild the indices.
Avoid expensive joins
Partition tables
  • Break up a table by putting hot spots in a separate table to help keep it in memory.
Tune the query cache
Source(s) and further reading: SQL tuning

消息队列

除了在SQL或NoSQL之间进行选择之外,了解哪种类型的NoSQL数据库最适合您的用例也很有帮助。在下一节中,我们将回顾键值存储、文档存储、宽列存储和图形数据库

抽象:哈希表

  • Basically available – the system guarantees availability.
  • Soft state – the state of the system may change over time, even without input.
  • Eventual consistency – the system will become consistent over a period of time, given that the system doesn’t receive input during that period.

键值存储通常允许O(1)次读取和写入,并且通常由内存或SSD支持。数据存储可以按字典顺序维护键,从而允许高效地检索键范围。键值存储可允许存储具有值的元数据

来源和进一步阅读

键值存储提供高性能,通常用于简单数据模型或快速变化的数据,如内存缓存层。由于它们只提供有限的操作集,因此如果需要额外的操作,复杂性将转移到应用层

键值存储是更复杂系统(如文档存储,在某些情况下还包括图形数据库)的基础

抽象:键值存储,文档存储为值

文档存储以文档(XML、JSON、二进制等)为中心,文档存储给定对象的所有信息。文档存储提供基于文档本身的内部结构进行查询的API或查询语言。请注意,许多键值存储包括用于使用值的元数据的功能,从而模糊了这两种存储类型之间的界限

Source(s) and further reading: key-value store

可视化的延迟数字

根据底层实现,文档按集合、标签、元数据或目录进行组织。尽管可以将文档组织或分组在一起,但文档的字段可能彼此完全不同

一些文档存储,如MongoDB和CouchDB,也提供了一种类似SQL的语言来执行复杂的查询。DynamoDB同时支持键值和文档

文档存储具有很高的灵活性,通常用于处理偶尔更改的数据

来源:SQL&NoSQL,简史

抽象:嵌套映射ColumnFamily<RowKey,Columns<ColKey,Value,Timestamp>>

Source(s) and further reading: document store

英语∙日本語∙简体中文∙繁體中文|العَرَبِيَّة‎∙বাংলা∙葡萄牙语do Brasil∙Deutsch∙ελληνικά∙עברית∙Italiano∙한국어∙فارسی∙Poliano∙русскийязык∙Español∙ภาษาไทย∙Türkçe∙tiếng Việt∙Français|添加翻译

宽列存储的基本数据单位是列(名称/值对)。列可以按列族分组(类似于SQL表)。超级柱族进一步将柱族分组。您可以使用行键单独访问每列,具有相同行键的列形成一行。每个值都包含一个用于版本化和冲突解决的时间戳

Google引入了Bigtable作为第一个宽列存储,它影响了Hadoop生态系统中经常使用的开源HBase,以及Facebook的Cassandra。Bigtable、HBase和Cassandra等存储按字典顺序维护键,从而允许高效地检索选择性键范围

宽列存储提供高可用性和高可伸缩性。它们通常用于非常大的数据集

来源:图表数据库

抽象:图表

Source(s) and further reading: wide column store

网络不可靠,因此您需要支持分区容错。您需要在一致性和可用性之间进行软件权衡

在图形数据库中,每个节点是一条记录,每条弧是两个节点之间的关系。对图形数据库进行了优化,以表示具有多个外键或多对多关系的复杂关系

图形数据库为具有复杂关系的数据模型(如社交网络)提供高性能。它们相对较新,尚未广泛使用;可能更难找到开发工具和资源。很多图表只能通过睡觉API访问

来源:从RDBMS过渡到NoSQL

SQL的原因:

Source(s) and further reading: graph

请注意,许多键值存储包括用于使用值的元数据的功能,从而模糊了这两种存储类型之间的界限

任务队列

使用NoSQL的原因:

非常适合NoSQL的示例数据:

  • Structured data
  • Strict schema
  • Relational data
  • Need for complex joins
  • Transactions
  • Clear patterns for scaling
  • More established: developers, community, code, tools, etc
  • Lookups by index are very fast

来源:可伸缩系统设计模式

  • Semi-structured data
  • Dynamic or flexible schema
  • Non-relational data
  • No need for complex joins
  • Store many TB (or PB) of data
  • Very data intensive workload
  • Very high throughput for IOPS

缓存可以缩短页面加载时间,并可以减少服务器和数据库的负载。在此模型中,调度程序将首先查找以前是否已发出请求,并尝试查找要返回的前一个结果,以便保存实际执行

  • Rapid ingest of clickstream and log data
  • Leaderboard or scoring data
  • Temporary data, such as a shopping cart
  • Frequently accessed (‘hot’) tables
  • Metadata/lookup tables
Source(s) and further reading: SQL or NoSQL

沟通

数据库通常受益于跨其分区的读和写的统一分布。受欢迎的项目可能会扭曲分布,造成瓶颈。将缓存放在数据库前面有助于吸收不均匀的负载和流量高峰

缓存可以位于客户端(操作系统或浏览器)、服务器端或位于不同的缓存层中

CDN被认为是一种缓存

背压

反向代理和缓存(如varish)可以直接服务于静电和动态内容。Web服务器还可以缓存请求,无需联系应用程序服务器即可返回响应

缺点:异步

您的数据库通常在默认配置中包含某种级别的缓存,针对一般用例进行了优化。针对特定的使用模式调整这些设置可以进一步提高性能

超文本传输协议(HTTP)

内存缓存(如memcached和redis)是应用程序和数据存储之间的键值存储。由于数据保存在RAM中,因此它比数据存储在磁盘上的典型数据库快得多。RAM比磁盘更有限,因此缓存失效算法(如最近最少使用(LRU))可以帮助使“冷”条目无效,并将“热”数据保留在RAM中

传输控制协议(TCP)

Redis具有以下附加功能:

用户数据报协议(UDP)

您可以缓存多个级别,分为两个一般类别:数据库查询和对象:

通常,您应该尽量避免基于文件的缓存,因为这会增加克隆和自动缩放的难度

  • Persistence option
  • Built-in data structures such as sorted sets and lists

无论何时查询数据库,都要将查询作为键进行散列,并将结果存储到缓存中。此方法存在过期问题:

  • Row level
  • Query-level
  • Fully-formed serializable objects
  • Fully-rendered HTML

将数据视为对象,类似于您对应用程序代码所做的操作。让您的应用程序将数据库中的数据集组装成一个类实例或一个或多个数据结构:

远程过程调用(RPC)

缓存内容的建议:

  • Hard to delete a cached result with complex queries
  • If one piece of data changes such as a table cell, you need to delete all cached queries that might include the changed cell

表述性状态转移(睡觉)

由于您只能在缓存中存储有限数量的数据,因此您需要确定哪种缓存更新策略最适合您的用例

  • Remove the object from cache if its underlying data has changed
  • Allows for asynchronous processing: workers assemble objects by consuming the latest cached object

来源:从缓存到内存中数据网格

  • User sessions
  • Fully rendered web pages
  • Activity streams
  • User graph data

远程过程调用与睡觉调用比较

应用程序负责从存储中读取和写入。缓存不直接与存储交互。应用程序执行以下操作:

我在开放源码许可下向您提供此存储库中的代码和资源。因为这是我的个人存储库,您获得的我的代码和资源的许可证来自我,而不是我的雇主(Facebook)

memcached通常以这种方式使用

后续读取添加到高速缓存的数据速度很快。侧缓存也称为惰性加载。仅缓存请求的数据,从而避免使用未请求的数据填满缓存

  • Look for entry in cache, resulting in a cache miss
  • Load entry from the database
  • Add entry to cache
  • Return entry
def get_user(self, user_id):
    user = cache.get("user.{0}", user_id)
    if user is None:
        user = db.query("SELECT * FROM users WHERE user_id = {0}", user_id)
        if user is not None:
            key = "user.{0}".format(user_id)
            cache.set(key, json.dumps(user))
    return user

来源:可伸缩性、可用性、稳定性、模式

应用程序使用高速缓存作为主数据存储,对其进行读写数据,而高速缓存负责对数据库进行读写:

Disadvantage(s): cache-aside
  • Each cache miss results in three trips, which can cause a noticeable delay.
  • Data can become stale if it is updated in the database. This issue is mitigated by setting a time-to-live (TTL) which forces an update of the cache entry, or by using write-through.
  • When a node fails, it is replaced by a new, empty node, increasing latency.

Write-through

应用程序代码:

缓存代码:

  • Application adds/updates entry in cache
  • Cache synchronously writes entry to data store
  • Return

由于写入操作,直写操作的总体速度较慢,但后续读取刚写入的数据会很快。用户在更新数据时通常比读取数据时更能容忍延迟。缓存中的数据未过时

set_user(12345, {"foo":"bar"})

来源:可伸缩性、可用性、稳定性、模式

def set_user(user_id, values):
    user = db.query("UPDATE Users WHERE id = {0}", user_id, values)
    cache.set(user_id, user)

在Write-Back中,应用程序执行以下操作:

Disadvantage(s): write through
  • When a new node is created due to failure or scaling, the new node will not cache entries until the entry is updated in the database. Cache-aside in conjunction with write through can mitigate this issue.
  • Most data written might never be read, which can be minimized with a TTL.

Write-behind (write-back)

来源:从缓存到内存中数据网格

您可以将缓存配置为在任何最近访问的缓存条目到期之前自动刷新

  • Add/update entry in cache
  • Asynchronously write entry to the data store, improving write performance
Disadvantage(s): write-behind
  • There could be data loss if the cache goes down prior to its contents hitting the data store.
  • It is more complex to implement write-behind than it is to implement cache-aside or write-through.

Refresh-ahead

如果缓存可以准确预测将来可能需要哪些项目,则与直读相比,提前刷新可以降低延迟

来源:规模架构系统简介

异步工作流有助于减少代价高昂的操作的请求时间,否则这些操作将以内联方式执行。他们还可以通过提前执行耗时的工作来提供帮助,例如定期聚合数据

Disadvantage(s): refresh-ahead
  • Not accurately predicting which items are likely to be needed in the future can result in reduced performance than without refresh-ahead.

二次幂表

  • Need to maintain consistency between caches and the source of truth such as the database through cache invalidation.
  • Cache invalidation is a difficult problem, there is additional complexity associated with when to update the cache.
  • Need to make application changes such as adding Redis or memcached.

每个程序员都应该知道的延迟数字

安全性

消息队列接收、保存和传递消息。如果操作速度太慢而无法以内联方式执行,则可以将消息队列与以下工作流结合使用:

用户不会被阻止,作业将在后台处理。在此期间,客户端可能会选择性地进行少量处理,使其看起来好像任务已经完成。例如,如果发布一条tweet,该tweet可以立即发布到您的时间表上,但可能需要一段时间才能真正将您的tweet发送给您的所有追随者

其他系统设计面试问题

Redis作为简单的消息代理很有用,但消息可能会丢失

  • An application publishes a job to the queue, then notifies the user of job status
  • A worker picks up the job from the queue, processes it, then signals the job is complete

RabbitMQ很流行,但需要您适应‘AMQP’协议并管理您自己的节点

Amazon SQS是托管的,但可能会有很高的延迟,并且可能会传递两次消息

任务队列接收任务及其相关数据,运行它们,然后交付结果。它们可以支持调度,并可用于在后台运行计算密集型作业

芹菜支持调度,并且主要支持python。

现实世界的建筑

如果队列开始显著增长,队列大小可能会大于内存,从而导致缓存未命中、磁盘读取,甚至会降低性能。反压可以通过限制队列大小来提供帮助,从而为队列中已有的作业保持较高的吞吐率和良好的响应时间。一旦队列填满,客户端就会收到服务器繁忙或HTTP 503状态代码,以便稍后重试。客户端可以稍后重试请求,可能会使用指数回退

来源:OSI 7层模型

公司架构

HTTP是一种在客户端和服务器之间编码和传输数据的方法。它是一种请求/响应协议:客户端发出请求,服务器发出响应,其中包含请求的相关内容和完成状态信息。HTTP是独立的,允许请求和响应流经许多执行负载平衡、缓存、加密和压缩的中间路由器和服务器

公司工程博客

  • Use cases such as inexpensive calculations and realtime workflows might be better suited for synchronous operations, as introducing queues can add delays and complexity.

CP-一致性和划分容错

附录

基本HTTP请求由谓词(方法)和资源(端点)组成。以下是常见的HTTP谓词:

AP-可用性和分区容错

*可以多次调用,没有不同的结果

HTTP是依赖于较低级别协议(如TCP和UDP)的应用层协议

Verb Description Idempotent* Safe Cacheable
GET Reads a resource Yes Yes Yes
POST Creates a resource or trigger a process that handles data No No Yes if response contains freshness info
PUT Creates or replace a resource Yes No No
PATCH Partially updates a resource No No Yes if response contains freshness info
DELETE Deletes a resource Yes No No

来源:如何制作多人游戏

TCP是IP网络上的面向连接的协议。使用握手建立和终止连接。所有发送的数据包都保证按原始顺序到达目的地,并且通过以下方式不会损坏:

Source(s) and further reading: HTTP

主动-被动

如果发送方没有收到正确的响应,它将重新发送数据包。如果存在多个超时,则连接将断开。TCP还实施流量控制和拥塞控制。这些保证会导致延迟,并且通常会导致传输效率低于UDP

为了确保高吞吐量,Web服务器可以保持大量TCP连接处于打开状态,从而导致高内存使用率。在web服务器线程和memcached服务器(比方说)之间具有大量打开的连接可能代价高昂。除了在适用的情况下切换到UDP之外,连接池还可以提供帮助

TCP对于要求高可靠性但对时间要求较低的应用程序很有用。一些示例包括Web服务器、数据库信息、SMTP、FTP和SSH

在以下情况下使用UDP上的TCP:

来源:如何制作多人游戏

UDP是无连接的。数据报(类似于数据包)仅在数据报级别得到保证。数据报可能无序到达目的地,也可能根本没有到达目的地。UDP不支持拥塞控制。如果没有TCP支持的保证,UDP通常更有效

  • You need all of the data to arrive intact
  • You want to automatically make a best estimate use of the network throughput

主动-主动

UDP可以广播,向子网中的所有设备发送数据报。这对于DHCP很有用,因为客户端尚未接收到IP地址,因此阻止了TCP在没有IP地址的情况下流式传输

UDP的可靠性较低,但在VoIP、视频聊天、流和实时多人游戏等实时使用案例中运行良好

在以下情况下使用TCP上的UDP:

来源:破解系统设计访谈

在RPC中,客户端导致过程在不同的地址空间(通常是远程服务器)上执行。该过程的编码就好像它是一个本地过程调用,从客户端程序抽象出如何与服务器通信的细节。远程调用通常比本地调用更慢、更不可靠,因此区分RPC调用和本地调用很有帮助。流行的RPC框架包括Protobuf、Thrift和Avro

  • You need the lowest latency
  • Late data is worse than loss of data
  • You want to implement your own error correction

Source(s) and further reading: TCP and UDP

主-从和主-主

RPC是一种请求-响应协议:

示例RPC调用:

RPC专注于公开行为。RPC通常用于内部通信的性能原因,因为您可以手工创建本地调用以更好地适应您的用例

  • Client program – Calls the client stub procedure. The parameters are pushed onto the stack like a local procedure call.
  • Client stub procedure – Marshals (packs) procedure id and arguments into a request message.
  • Client communication module – OS sends the message from the client to the server.
  • Server communication module – OS passes the incoming packets to the server stub procedure.
  • Server stub procedure – Unmarshalls the results, calls the server procedure matching the procedure id and passes the given arguments.
  • The server response repeats the steps above in reverse order.

在以下情况下选择本机库(也称为SDK):

GET /someoperation?data=anId

POST /anotheroperation
{
  "data":"anId";
  "anotherdata": "another value"
}

睡觉之后的HTTP接口倾向于更常用于公共接口

睡觉是一种实施客户端/服务器模型的体系结构风格,其中客户端作用于由服务器管理的一组资源。服务器提供资源和动作的表示,这些资源和动作既可以操作资源,也可以获得新的资源表示。所有通信必须是无状态和可缓存的

  • You know your target platform.
  • You want to control how your “logic” is accessed.
  • You want to control how error control happens off your library.
  • Performance and end user experience is your primary concern.

REST风格的界面有四个特点:

Disadvantage(s): RPC

  • RPC clients become tightly coupled to the service implementation.
  • A new API must be defined for every new operation or use case.
  • It can be difficult to debug RPC.
  • You might not be able to leverage existing technologies out of the box. For example, it might require additional effort to ensure RPC calls are properly cached on caching servers such as Squid.

99.9%可用性-三个9

睡觉调用示例:

睡觉专注于数据曝光。它最大限度地减少了客户端/服务器之间的耦合,通常用于公共HTTPAPI。睡觉使用一种更通用、更统一的方法,通过URI公开资源,通过头部表示,通过GET、POST、PUT、DELETE和PATCH等动词进行操作。由于是无状态的,睡觉非常适合水平伸缩和分区

  • Identify resources (URI in HTTP) – use the same URI regardless of any operation.
  • Change with representations (Verbs in HTTP) – use verbs, headers, and body.
  • Self-descriptive error message (status response in HTTP) – Use status codes, don’t reinvent the wheel.
  • HATEOAS (HTML interface for HTTP) – your web service should be fully accessible in a browser.

来源:你真的知道为什么你更喜欢睡觉而不是rpc吗?

GET /someresources/anId

PUT /someresources/anId
{"anotherdata": "another value"}

此部分可能需要一些更新。考虑捐款吧!

Disadvantage(s): REST

  • With REST being focused on exposing data, it might not be a good fit if resources are not naturally organized or accessed in a simple hierarchy. For example, returning all updated records from the past hour matching a particular set of events is not easily expressed as a path. With REST, it is likely to be implemented with a combination of URI path, query parameters, and possibly the request body.
  • REST typically relies on a few verbs (GET, POST, PUT, DELETE, and PATCH) which sometimes doesn’t fit your use case. For example, moving expired documents to the archive folder might not cleanly fit within these verbs.
  • Fetching complicated resources with nested hierarchies requires multiple round trips between the client and server to render single views, e.g. fetching content of a blog entry and the comments on that entry. For mobile applications operating in variable network conditions, these multiple roundtrips are highly undesirable.
  • Over time, more fields might be added to an API response and older clients will receive all new data fields, even those that they do not need, as a result, it bloats the payload size and leads to larger latencies.

99.99%可用性-四个9

Operation RPC REST
Signup POST /signup POST /persons
Resign POST /resign
{
“personid”: “1234”
}
DELETE /persons/1234
Read a person GET /readPerson?personid=1234 GET /persons/1234
Read a person’s items list GET /readUsersItemsList?personid=1234 GET /persons/1234/items
Add an item to a person’s items POST /addItemToUsersItemsList
{
“personid”: “1234”;
“itemid”: “456”
}
POST /persons/1234/items
{
“itemid”: “456”
}
Update an item POST /modifyItem
{
“itemid”: “456”;
“key”: “value”
}
PUT /items/456
{
“key”: “value”
}
Delete an item POST /removeItem
{
“itemid”: “456”
}
DELETE /items/456

安全是一个广泛的话题。除非你有相当多的经验,有安全背景,或者正在申请一个需要安全知识的职位,否则你可能不需要知道更多的基础知识:

Source(s) and further reading: REST and RPC

正在开发中

有时你会被要求做“粗略”的估算。例如,您可能需要确定从磁盘生成100个图像缩略图需要多长时间,或者一个数据结构需要多少内存。每个程序员都应该知道的两个表的幂和延迟数字是很方便的参考资料

基于以上数字的便捷指标:

  • Encrypt in transit and at rest.
  • Sanitize all user inputs or any input parameters exposed to user to prevent XSS and SQL injection.
  • Use parameterized queries to prevent SQL injection.
  • Use the principle of least privilege.

并行可用性与顺序可用性

学分

缺点:水平缩放

Power           Exact Value         Approx Value        Bytes
---------------------------------------------------------------
7                             128
8                             256
10                           1024   1 thousand           1 KB
16                         65,536                       64 KB
20                      1,048,576   1 million            1 MB
30                  1,073,741,824   1 billion            1 GB
32                  4,294,967,296                        4 GB
40              1,099,511,627,776   1 trillion           1 TB

Source(s) and further reading

主从复制

Latency Comparison Numbers
--------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy            10,000   ns       10 us
Send 1 KB bytes over 1 Gbps network     10,000   ns       10 us
Read 4 KB randomly from SSD*           150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
HDD seek                            10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from 1 Gbps  10,000,000   ns   10,000 us   10 ms  40x memory, 10X SSD
Read 1 MB sequentially from HDD     30,000,000   ns   30,000 us   30 ms 120x memory, 30X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns

常见的系统设计面试问题,以及有关如何解决每个问题的资源链接

  • Read sequentially from HDD at 30 MB/s
  • Read sequentially from 1 Gbps Ethernet at 100 MB/s
  • Read sequentially from SSD at 1 GB/s
  • Read sequentially from main memory at 4 GB/s
  • 6-7 world-wide round trips per second
  • 2,000 round trips per second within a data center

Latency numbers visualized

有关如何设计真实系统的文章

Source(s) and further reading

主-主复制

来源:Twitter时间表按规模

Question Reference(s)
Design a file sync service like Dropbox youtube.com
Design a search engine like Google queue.acm.org
stackexchange.com
ardendertat.com
stanford.edu
Design a scalable web crawler like Google quora.com
Design Google docs code.google.com
neil.fraser.name
Design a key-value store like Redis slideshare.net
Design a cache system like Memcached slideshare.net
Design a recommendation system like Amazon’s hulu.com
ijcai13.org
Design a tinyurl system like Bitly n00tc0d3r.blogspot.com
Design a chat app like WhatsApp highscalability.com
Design a picture sharing system like Instagram highscalability.com
highscalability.com
Design the Facebook news feed function quora.com
quora.com
slideshare.net
Design the Facebook timeline function facebook.com
highscalability.com
Design the Facebook chat function erlang-factory.com
facebook.com
Design a graph search function like Facebook’s facebook.com
facebook.com
facebook.com
Design a content delivery network like CloudFlare figshare.com
Design a trending topic system like Twitter’s michael-noll.com
snikolov .wordpress.com
Design a random ID generation system blog.twitter.com
github.com
Return the top k requests during a time interval cs.ucsb.edu
wpi.edu
Design a system that serves data from multiple data centers highscalability.com
Design an online multiplayer card game indieflashblog.com
buildnewgames.com
Design a garbage collection system stuffwithstuff.com
washington.edu
Design an API rate limiter https://stripe.com/blog/
Design a Stock Exchange (like NASDAQ or Binance) Jane Street
Golang Implementation
Go Implemenation
Add a system design question Contribute

联盟

在下面的文章中,不要把重点放在具体的细节上,而是:

您面试的公司的架构

您遇到的问题可能来自同一个域

  • Identify shared principles, common technologies, and patterns within these articles
  • Study what problems are solved by each component, where it works, where it doesn’t
  • Review the lessons learned
Type System Reference(s)
Data processing MapReduce – Distributed data processing from Google research.google.com
Data processing Spark – Distributed data processing from Databricks slideshare.net
Data processing Storm – Distributed data processing from Twitter slideshare.net
Data store Bigtable – Distributed column-oriented database from Google harvard.edu
Data store HBase – Open source implementation of Bigtable slideshare.net
Data store Cassandra – Distributed column-oriented database from Facebook slideshare.net
Data store DynamoDB – Document-oriented database from Amazon harvard.edu
Data store MongoDB – Document-oriented database slideshare.net
Data store Spanner – Globally-distributed database from Google research.google.com
Data store Memcached – Distributed memory caching system slideshare.net
Data store Redis – Distributed memory caching system with persistence and value types slideshare.net
File system Google File System (GFS) – Distributed file system research.google.com
File system Hadoop File System (HDFS) – Open source implementation of GFS apache.org
Misc Chubby – Lock service for loosely-coupled distributed systems from Google research.google.com
Misc Dapper – Distributed systems tracing infrastructure research.google.com
Misc Kafka – Pub/sub message queue from LinkedIn slideshare.net
Misc Zookeeper – Centralized infrastructure and services enabling synchronization slideshare.net
Add an architecture Contribute

切分

Company Reference(s)
Amazon Amazon architecture
Cinchcast Producing 1,500 hours of audio every day
DataSift Realtime datamining At 120,000 tweets per second
Dropbox How we’ve scaled Dropbox
ESPN Operating At 100,000 duh nuh nuhs per second
Google Google architecture
Instagram 14 million users, terabytes of photos
What powers Instagram
Justin.tv Justin.Tv’s live video broadcasting architecture
Facebook Scaling memcached at Facebook
TAO: Facebook’s distributed data store for the social graph
Facebook’s photo storage
How Facebook Live Streams To 800,000 Simultaneous Viewers
Flickr Flickr architecture
Mailbox From 0 to one million users in 6 weeks
Netflix A 360 Degree View Of The Entire Netflix Stack
Netflix: What Happens When You Press Play?
Pinterest From 0 To 10s of billions of page views a month
18 million visitors, 10x growth, 12 employees
Playfish 50 million monthly users and growing
PlentyOfFish PlentyOfFish architecture
Salesforce How they handle 1.3 billion transactions a day
Stack Overflow Stack Overflow architecture
TripAdvisor 40M visitors, 200M dynamic page views, 30TB data
Tumblr 15 billion page views a month
Twitter Making Twitter 10000 percent faster
Storing 250 million tweets a day using MySQL
150M active users, 300K QPS, a 22 MB/S firehose
Timelines at scale
Big and small data at Twitter
Operations at Twitter: scaling beyond 100 million users
How Twitter Handles 3,000 Images Per Second
Uber How Uber scales their real-time market platform
Lessons Learned From Scaling Uber To 2000 Engineers, 1000 Services, And 8000 Git Repositories
WhatsApp The WhatsApp architecture Facebook bought for $19 billion
YouTube YouTube scalability
YouTube architecture

反规格化

想要添加博客吗?为避免重复工作,请考虑将您的公司博客添加到以下回购中:

有兴趣添加一个部分或帮助完成一个正在进行的部分吗?贡献自己的力量!

Source(s) and further reading

在整个回购过程中提供积分和来源

联系信息

特别感谢:

  • Distributed computing with MapReduce
  • Consistent hashing
  • Scatter gather
  • Contribute

许可证

请随时与我联系,讨论任何问题、问题或评论

我的联系信息可以在我的GitHub页面上找到

了解如何设计大型系统

我在开放源码许可下向您提供此存储库中的代码和资源。因为这是我的个人存储库,您获得的我的代码和资源的许可证来自我,而不是我的雇主(Facebook)

动机

向开源社区学习

Anki抽认卡

Copyright 2017 Donne Martin

Creative Commons Attribution 4.0 International License (CC BY 4.0)

http://creativecommons.org/licenses/by/4.0/