Design and History FAQ

Why does Python use indentation for grouping of statements?

Guido van Rossum认为,使用缩进进行分组是非常优雅的,有助于平均Python程序的清晰度。大多数人学会爱一段时间后这个功能。

由于没有开始/结束括号,所以在解析器和人类读者感知的分组之间不能存在不一致。偶尔C程序员会遇到一个像这样的代码片段:

if (x <= y)
        x++;
        y--;
z++;

如果条件为true,则仅执行x++语句,但缩进导致您相信。即使有经验的C程序员有时会盯着它很长时间想知道为什么y即使对于x > y

因为没有开始/结束括号,Python不太容易出现编码风格冲突。在C中有许多不同的方法来放置大括号。如果你习惯于阅读和编写使用一种风格的代码,你在阅读(或被要求写)另一种风格时会觉得至少有些不安。

许多编码风格将开始/结束括号放在一行上。这使得程序相当长,并浪费宝贵的屏幕空间,使得更难以获得良好的程序概述。理想情况下,一个函数应该适合一个屏幕(例如,20-30行)。20行的Python可以做比C的20行更多的工作。这不是完全由于缺乏开始/结束括号 - 缺乏声明和高级数据类型也负责 - 但基于缩进语法肯定有帮助。

Why am I getting strange results with simple arithmetic operations?

请参阅下一个问题。

Why are floating-point calculations so inaccurate?

用户常常对这样的结果感到惊讶:

>>> 1.2 - 1.0
0.19999999999999996

并认为它是Python中的一个错误。不是。这与Python没有什么关系,还有更多的关于底层平台如何处理浮点数。

CPython中的float类型使用C double来存储。float对象的值以固定精度(通常为53位)存储在二进制浮点中,Python使用C操作,而C操作依赖于处理器中的硬件实现来执行浮点操作。这意味着,就浮点运算而言,Python的行为就像许多流行的语言,包括C和Java。

许多可以用十进制表示法容易地写入的数字不能精确地表示为二进制浮点。例如,之后:

>>> x = 1.2

x存储的值是对十进制值1.2的(非常好的)近似,但不完全等于它。在典型的机器上,实际存储的值为:

1.0011001100110011001100110011001100110011001100110011 (binary)

这正是:

1.1999999999999999555910790149937383830547332763671875 (decimal)

53位的典型精度为Python浮点数提供15-16位精度的十进制数字。

有关更全面的解释,请参阅Python教程中的floating point arithmetic章节。

Why are Python strings immutable?

有几个优点。

一个是性能:知道字符串是不可变的意味着我们可以在创建时为其分配空间,存储要求是固定的和不变的。这也是元组和列表之间区分的原因之一。

另一个优点是Python中的字符串被认为是“元素”的数字。没有任何数量的活动会将值8更改为其他值,在Python中,没有任何活动会将字符串“8”更改为其他值。

Why must ‘self’ be used explicitly in method definitions and calls?

这个想法是从Modula-3借来的。事实证明是非常有用的,出于各种原因。

首先,更明显的是,您使用的是方法或实例属性,而不是局部变量。读取self.xself.meth()使得绝对清楚的是,即使你不知道类定义的心脏,使用实例变量或方法。在C ++中,可以通过缺少局部变量声明(假设全局变量很少或很容易识别)来排序 - 但在Python中,没有局部变量声明,因此您必须查找类定义为当然。一些C ++和Java编码标准要求实例属性具有一个m_前缀,因此这种显式在这些语言中仍然有用。

第二,这意味着如果你想从一个特定的类中显式地引用或调用该方法,则不需要特殊的语法。在C ++中,如果你想使用一个在派生类中重载的基类的方法,你必须使用::操作符 - 在Python中你可以写baseclass.methodname(self, &lt; argument list&gt;)这对于__init__()方法特别有用,一般来说,在派生类方法想要扩展同名的基类方法,因此必须以某种方式调用基类方法的情况下。

最后,对于实例变量,它解决了一个具有赋值的句法问题:因为Python中的局部变量是(根据定义!)在函数体中赋值的那些变量(并且没有被显式声明为全局变量),必须有一些方法告诉解释器赋值是为了赋给一个实例变量而不是局部变量变量,并且它应当优选地是语法的(出于效率的原因)。C ++通过声明来做到这一点,但是Python没有声明,很可惜只是为了这个目的而引入它们。使用显式self.var很好地解决了这个问题。类似地,对于使用实例变量,必须写入self.var意味着对方法中未限定名称的引用不必搜索实例的目录。换句话说,局部变量和实例变量存在于两个不同的命名空间中,你需要告诉Python要使用哪个命名空间。

Why can’t I use an assignment in an expression?

很多人习惯C或者Perl抱怨他们想使用这个C成语:

while (line = readline(f)) {
    // do something with line
}

在Python中你被迫写这个:

while True:
    line = f.readline()
    if not line:
        break
    ...  # do something with line

不允许在Python表达式中赋值的原因是这些其他语言中常见的,难以找到的错误,由此结构引起:

if (x = 0) {
    // error handling
}
else {
    // code that only works for nonzero x
}

The error is a simple typo: x = 0, which assigns 0 to the variable x, was written while the comparison x == 0 is certainly what was intended.

已经提出了许多替代方案。大多数是保存一些打字,但使用任意或神秘的语法或关键字,并且不符合语言改变建议的简单标准的黑客:它应该直观地为尚未被介绍到构造的人类读者建议适当的意义。

一个有趣的现象是,大多数有经验的Python程序员认识到 True成语,似乎没有在表达式构造中缺少赋值;它只是新来的人表达强烈的愿望添加到语言。

有一种替代方法拼写这似乎有吸引力,但通常不如“真正的”解决方案健壮:

line = f.readline()
while line:
    ...  # do something with line...
    line = f.readline()

这样做的问题是,如果你改变主意关于如何得到下一行(例如。你想把它改成sys.stdin.readline())你必须记住改变程序中的两个地方 - 第二次出现被隐藏在循环的底部。

最好的方法是使用迭代器,使用for语句可以循环访问对象。例如,file objects支持迭代器协议,因此您可以简单地写:

for line in f:
    ...  # do something with line...

为什么Python使用某些功能的方法(例如,list.index()),但函数为其他(例如。len(list))?

主要原因是历史。函数用于那些对于一组类型是通用的操作,并且意图用于即使对于没有方法的对象也是如此。元组)。当使用Python的功能特性(map()zip()等)时,还可以方便地使用一个可以很容易应用于对象的非结构容器的函数。 a1)。

事实上,作为内建函数实现len()max()min()实际上比将它们实现为方法为每种类型。人们可以讨论个别案例,但它是Python的一部分,现在太晚了,不能进行这样的根本改变。这些功能必须保留以避免大量代码损坏。

注意

对于字符串操作,Python已经从外部函数(string模块)移动到方法。但是,len()仍然是一个函数。

Why is join() a string method instead of a list or tuple method?

字符串变得更像其他标准类型从Python 1.6开始,当方法添加了相同的功能,一直使用字符串模块的功能。大多数这些新的方法已被广泛接受,但似乎使一些程序员感到不舒服的是:

", ".join(['1', '2', '4', '8', '16'])

其结果是:

"1, 2, 4, 8, 16"

有两个常见的论据反对这种用法。

第一个沿着以下行:“它看起来真的很丑,使用字符串面值(字符串常量)”的方法,这是答案是,它可能,但字符串字面值只是一个固定的值。如果允许对绑定到字符串的名称的方法,则没有逻辑原因使它们在字面值上不可用。

第二个异议通常被强制转换为:“我真的告诉​​序列连接它的成员与字符串常量”。可悲的是,你不是。由于某种原因,将split()作为字符串方法的难度似乎要小得多,因为在这种情况下很容易看到

"1, 2, 4, 8, 16".split(", ")

是指向字符串字面值返回由给定分隔符(或默认为任意空白空格)分隔的子字符串的指令。

join()是一个字符串方法,因为在使用它时,您要告诉分隔符字符串对一系列字符串进行迭代,并在相邻元素之间插入。这个方法可以用于任何遵守序列对象规则的参数,包括你自己定义的任何新类。类似的方法存在字节和bytearray对象。

How fast are exceptions?

如果没有引发异常,try / except块是非常有效的。实际捕获异常是昂贵的。在Python之前的版本2.0,它是常见的使用这个成语:

try:
    value = mydict[key]
except KeyError:
    mydict[key] = getvalue(key)
    value = mydict[key]

这只有当你期望dict有几乎所有的时间键时才有意义。如果不是这样,你编码如下:

if key in mydict:
    value = mydict[key]
else:
    value = mydict[key] = getvalue(key)

对于这种特定情况,您还可以使用 = dict.setdefault(key, getvalue(key) / t4>,但只有当getvalue()调用足够便宜时,因为它在所有情况下都是计算。

Why isn’t there a switch or case statement in Python?

You can do this easily enough with a sequence of if... elif... elif... else. 已经有一些关于switch语句语法的建议,但是关于是否以及如何做范围测试还没有一致意见。有关完整的详细信息和当前状态,请参见 PEP 275

对于需要从大量可能性中进行选择的情况,您可以创建一个字典,将案例值映射到要调用的函数。例如:

def function_1(...):
    ...

functions = {'a': function_1,
             'b': function_2,
             'c': self.method_1, ...}

func = functions[value]
func()

对于调用对象上的方法,可以通过使用getattr()内建来检索具有特定名称的方法,从而进一步简化:

def visit_a(self, ...):
    ...
...

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

建议您对方法名称使用前缀,例如本示例中的visit_没有这样的前缀,如果值来自不受信任的源,攻击者将能够调用对象上的任何方法。

Can’t you emulate threads in the interpreter instead of relying on an OS-specific thread implementation?

答案1:不幸的是,解释器为每个Python堆栈帧推送至少一个C堆栈帧。此外,扩展可以在几乎随机的时刻回调到Python。因此,一个完整的线程实现需要线程支持C.

答案2:幸运的是,有Stackless Python,它有一个完全重新设计的解释器循环,避免了C堆栈。

Why can’t lambda expressions contain statements?

Python lambda表达式不能包含语句,因为Python的句法框架无法处理嵌套在表达式中的语句。但是,在Python中,这不是一个严重的问题。与其他语言中的lambda表单不同,在它们添加功能的地方,Python lambdas只是一个简化的符号,如果你太懒了定义一个函数。

函数已经是Python中的第一类对象,并且可以在本地作用域中声明。因此,使用lambda而不是局部定义的函数的唯一好处是,你不需要为函数发明一个名字 - 但这只是一个局部变量,函数对象(它是完全相同的对象类型一个lambda表达式yield)被赋值!

Can Python be compiled to machine code, C or some other language?

实际答案:

CythonPyrex使用可选的注解将修改后的Python编译成C扩展。Weave可以方便地以各种方式混合Python和C代码,以提高性能。Nuitka是一个即将到来的Python编译器,成为C ++代码,目的是支持完整的Python语言。

理论答案:

不简单。Python的高级数据类型,对象的动态类型和解释器的运行时调用(使用eval()exec())意味着一个天真的“ “Python程序可能主要包括对Python运行时系统的调用,即使是像x+1这样的看似简单的操作。

在Python新闻组或者Python会议中描述的几个项目已经表明,这种方法是可行的,虽然到目前为止的加速只是适度的。2x)。Jython使用相同的策略来编译到Java字节码。(Jim Hugunin已经证明,结合全程序分析,1000x的加速对于小演示程序是可行的。有关详细信息,请参阅1997年Python大会中的会议记录。)

How does Python manage memory?

Python内存管理的细节取决于实现。Python的标准实现CPython使用引用计数来检测不可访问的对象,另一种机制来收集引用循环,周期性地执行循环检测算法,该算法寻找不可访问的循环并删除所涉及的对象。gc模块提供了执行垃圾容器,获取调试统计信息和调整收集器参数的函数。

然而,其他实现(例如JythonPyPy)可以依赖于不同的机制,例如完全垃圾回收器。如果你的Python代码依赖于引用计数实现的行为,这种差异可能导致一些微妙的移植问题。

在一些Python实现中,以下代码(在CPython中很好)可能会用完文件描述器:

for file in very_long_list_of_files:
    f = open(file)
    c = f.read(1)

事实上,使用CPython的引用计数和析构方案,每个新的赋值f都会关闭上一个文件。然而,对于传统的GC,这些文件对象将仅以不同的和可能长的间隔被收集(和关闭)。

如果你想编写代码来使用任何Python实现,你应该显式关闭文件或使用with语句;这将工作,不管内存管理方案:

for file in very_long_list_of_files:
    with open(file) as f:
        c = f.read(1)

Why doesn’t CPython use a more traditional garbage collection scheme?

一方面,这不是C标准功能,因此它不可移植。(是的,我们知道Boehm GC库。它具有最常用的平台的汇编代码位,而不是所有的,尽管它大部分是透明的,但它不是完全透明的;需要补丁才能使Python使用它。)

当Python嵌入到其他应用程序中时,传统GC也成为一个问题。在独立Python中,用GC库提供的版本替换标准malloc()和free()是很好的,嵌入Python的应用程序可能希望用自己的替换malloc()和free (),并且可能不想要Python的。现在,CPython使用任何实现malloc()和free()的东西。

Why isn’t all memory freed when CPython exits?

当Python退出时,从Python模块的全局命名空间引用的对象不总是被释放。如果有循环引用,可能会发生这种情况。还有由C库分配的不可能释放的某些存储器位。像Purify的工具会抱怨这些)。然而,Python对于在退出时清理内存非常积极,并且试图销毁每一个对象。

如果你想强制Python删除某些事物在deallocation使用atexit模块运行一个函数,将强制这些删除。

Why are there separate tuple and list data types?

列表和元组在许多方面类似,通常以基本不同的方式使用。元组可以被认为是类似于Pascal记录或C结构体;它们是相关数据的小容器,它们可以是作为一组操作的不同类型。例如,笛卡尔坐标适当地表示为两个或三个数字的元组。

另一方面,列表更像是其他语言的数组。它们倾向于持有不同数量的对象,所有这些对象具有相同类型并且逐个操作。例如,os.listdir('.')返回表示当前目录中文件的字符串列表。如果您向目录添加了另一个或两个文件,对此输出操作的函数通常不会中断。

元组是不可变的,这意味着一旦创建了元组,就不能用新值替换它的任何元素。列表是可变的,这意味着您可以随时更改列表的元素。只有不可变元素可以用作字典键,因此只有元组而不是列表可以用作键。

How are lists implemented?

Python的列表实际上是可变长度的数组,而不是Lisp风格的链表。实现使用对其他对象的引用的连续数组,并保持指向此数组的指针和列表头结构中的数组的长度。

这使得索引列表a[i]操作的成本独立于列表的大小或索引的值。

当附加或插入项目时,将调整引用数组的大小。应用一些聪明来反复提高附加项目的性能;当数组必须生长时,会分配一些额外的空间,因此接下来的几次不需要实际调整大小。

How are dictionaries implemented?

Python的字典被实现为可调整大小的哈希表。与B树相比,这在大多数情况下给查找提供了更好的性能(目前最常见的操作),并且实现更简单。

字典通过使用hash()内建函数计算存储在字典中的每个密钥的哈希码。散列码根据密钥和每进程种子而广泛变化;例如,“Python”可以散列到-539294296,而“python”,一个单一位不同的字符串可以散列到1142331976。然后,哈希码用于计算将存储值的内部数组中的位置。假设你存储所有具有不同哈希值的键,这意味着字典在计算机科学记数法中采用常数时间 - O(1)来检索键。这也意味着没有保持键的排序顺序,并且遍历数组,因为.keys().items() do会输出字典的内容任意混杂的顺序,可以随着程序的每次调用而改变。

Why must dictionary keys be immutable?

字典的哈希表实现使用根据键值计算的哈希值来查找键。如果密钥是可变对象,则其值可能改变,因此其散列也可以改变。但是,因为无论谁改变键对象不能告诉它被用作字典键,它不能移动字典中的条目。然后,当您尝试在字典中查找相同的对象时,将不会找到它,因为它的哈希值不同。如果你试图查找旧的值,它也不会被发现,因为在该哈希仓中找到的对象的值会不同。

如果你想要用列表索引的字典,只需先将列表转换为元组;函数tuple(L)创建具有与列表L相同条目的元组。元组是不变的,因此可以用作字典键。

提出了一些不可接受的解决方案:

  • 哈希列表按其地址(对象ID)。这不工作,因为如果你构造一个新的列表具有相同的值,将不会找到;例如

    mydict = {[1, 2]: '12'}
    print(mydict[[1, 2]])
    

    将引发KeyError异常,因为在第二行中使用的[1, 2]的id不同于第一行中的。换句话说,应当使用==来比较字典键,而不是使用is

  • 使用列表作为键时进行复制。这不工作,因为作为可变对象的列表可能包含对自身的引用,然后复制代码将运行到无限循环。

  • 允许列表作为键,但要求用户不要修改它们。这将允许程序中的一类难以跟踪的错误,当你忘记或修改列表偶然。它还使字典的重要不变量无效:d.keys()中的每个值都可用作字典的键。

  • 将列表标记为只读,一旦它们用作字典键。问题是,它不只是可以改变其价值的顶级对象;您可以使用包含列表作为键的元组。将任何东西作为键输入到字典中需要将所有可到达的对象标记为只读 - 并且自我参照对象可能导致无限循环。

如果你需要,有一个窍门,但使用它自己的风险:你可以包装一个可变结构在一个类实例,它有一个__eq__()__hash__()方法。然后,必须确保驻留在字典(或其他基于散列的结构)中的所有这样的包装器对象的哈希值在对象在字典(或其他结构)中时保持固定。

class ListWrapper:
    def __init__(self, the_list):
        self.the_list = the_list

    def __eq__(self, other):
        return self.the_list == other.the_list

    def __hash__(self):
        l = self.the_list
        result = 98767 - len(l)*555
        for i, el in enumerate(l):
            try:
                result = result + (hash(el) % 9999999) * 1001 + i
            except Exception:
                result = (result % 7777777) + i * 333
        return result

注意,散列计算由于列表的一些成员可能是不可散列的并且也可能由于算术溢出的可能性而变得复杂。

此外,必须总是如果o1 == o2(即o1 .__ eq __(o2) is True)then hash(o1) == hash(o2)(即o1 .__ hash __() == o2 .__ hash __()),无论对象是否在字典中。如果你不能满足这些限制,字典和其他基于哈希的结构将会不正确。

在ListWrapper的情况下,每当包装器对象在字典中时,包装列表不能改变以避免异常。不要这样做,除非你准备好对这些要求和不正确地满足他们的后果的思考。考虑自己警告。

Why doesn’t list.sort() return the sorted list?

在性能问题的情况下,制作一份清单的副本只是为了排序会浪费。因此,list.sort()对列表进行排序。为了提醒你这个事实,它不返回排序的列表。这样,当你需要一个排序的副本,但也需要保持未排序的版本周围,你不会被欺骗意外覆盖列表。

如果要返回新列表,请改用内建sorted()函数。此函数从提供的iterable中创建一个新的列表,对其进行排序并返回。例如,下面是如何按照排序顺序遍历字典的键:

for key in sorted(mydict):
    ...  # do whatever with mydict[key]...

How do you specify and enforce an interface spec in Python?

由诸如C ++和Java之类的语言提供的模块的接口规范描述了模块的方法和功能的原型。许多人认为编译时执行接口规范有助于大型程序的构建。

Python 2.6添加了一个abc模块,允许您定义抽象基类(ABC)。然后,您可以使用isinstance()issubclass()来检查实例或类是否实现特定的ABC。collections.abc模块定义了一组有用的ABCs,例如IterableContainerMutableMapping

对于Python,接口规范的许多优点可以通过组件的适当测试规程获得。还有一个工具,PyChecker,可以用于查找问题,由于子类。

一个模块的好的测试套件可以提供一个回归测试,并作为一个模块接口规范和一组示例。许多Python模块可以作为脚本运行以提供简单的“自我测试”。即使使用复杂外部接口的模块通常也可以使用外部接口的简单“存根”仿真来隔离测试。doctestunittest模块或第三方测试框架可用于构建详尽的测试套件,用于练习模块中的每行代码。

适当的测试规则可以帮助在Python中构建大型复杂应用程序以及具有接口规范。事实上,它可以更好,因为接口规范不能测试程序的某些属性。例如,append()方法可以在一些内部列表的末尾添加新元素;一个接口规范不能测试您的append()实现是否会正确地做到这一点,但是在测试套件中检查这个属性并不重要。

编写测试套件是非常有帮助的,你可能想要设计你的代码,使眼睛容易测试。一种越来越流行的技术,即面向测试的开发,首先需要编写测试套件的一部分,然后再编写任何实际代码。当然Python允许你马虎,不写测试用例。

Why is there no goto?

您可以使用异常提供一个“结构化goto”,甚至可以跨函数调用。许多人认为异常可以方便地模拟C,Fortran和其他语言的“go”或“goto”结构的所有合理使用。例如:

class label(Exception): pass  # declare a label

try:
    ...
    if condition: raise label()  # goto label
    ...
except label:  # where to goto
    pass
...

这不允许你跳进一个循环的中间,但这通常被认为是滥用goto反正。谨慎使用。

Why can’t raw strings (r-strings) end with a backslash?

更确切地说,它们不能以奇数个反斜杠结束:末尾的不成对反斜杠转义结束引号字符,留下未终止的字符串。

原始字符串旨在为处理器(主要是正则表达式引擎)创建输入,以便自己执行反斜杠转义处理。这样的处理器认为不匹配的尾反斜杠是一个错误,所以原始字符串不允许。作为回报,它们允许你通过使用反斜杠转义来传递字符串引号字符。当r字符串用于它们的预期目的时,这些规则工作得很好。

如果您正在尝试构建Windows路径名,请注意,所有Windows系统调用也接受正斜杠:

f = open("/mydir/file.txt")  # works fine!

如果您尝试为DOS命令创建路径名,请尝试之一

dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"

Why doesn’t Python have a “with” statement for attribute assignments?

Python有一个'with'语句,它包含一个块的执行,调用入口处的代码和从块中退出。一些语言有一个如下所示的结构:

with obj:
    a = 1               # equivalent to obj.a = 1
    total = total + 1   # obj.total = obj.total + 1

在Python中,这样的构造将是模糊的。

其他语言,如Object Pascal,Delphi和C ++,使用静态类型,因此可以以明确的方式知道正在分配什么成员。这是静态类型的主要要点 - 编译器始终知道编译时每个变量的范围。

Python使用动态类型。不可能提前知道在运行时将引用哪个属性。成员属性可以在运行中从对象中添加或删除。这使得从简单的读取不可能知道什么属性正在被引用:本地,一个全局,或成员属性?

对于实例,请采取以下不完整的代码段:

def foo(a):
    with a:
        print(x)

该代码段假定“a”必须有一个称为“x”的成员属性。然而,在Python中没有告诉解释器这个。如果“a”是一个整数,应该怎么办?如果有一个名为“x”的全局变量,它将在with块中使用吗?正如你所看到的,Python的动态性使得这样的选择更加困难。

然而,“with”和类似语言特性(减少代码量)的主要好处可以通过赋值在Python中轻松实现。代替:

function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63

写这个:

ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63

这也具有提高执行速度的副作用,因为在Python中的运行时解析了名称绑定,第二个版本只需要执行一次解析。

Why are colons required for the if/while/def/class statements?

冒号主要用于增强可读性(实验性ABC语言的结果之一)。考虑这个:

if a == b
    print(a)

if a == b:
    print(a)

注意第二个稍微更容易阅读。进一步注意一个冒号如何在这个常见问题答案的例子;它是英语的标准用法。

另一个小的原因是冒号让编辑器更容易使用语法高亮;他们可以查找冒号来决定何时需要增加缩进,而不必对程序文本进行更精细的解析。

Why does Python allow commas at the end of lists and tuples?

Python允许在列表,元组和字典的末尾添加一个尾随逗号:

[1, 2, 3,]
('a', 'b', 'c',)
d = {
    "A": [1, 5],
    "B": [6, 7],  # last trailing comma is optional but good style
}

允许这个有几个原因。

当你有一个字面值值的列表,元组或字典分布在多行,更容易添加更多的元素,因为你不必记住添加逗号到上一行。这些行也可以重新排序,而不会产生语法错误。

意外省略逗号可能会导致难以诊断的错误。例如:

x = [
  "fee",
  "fie"
  "foo",
  "fum"
]

这个列表看起来像有四个元素,但它实际上包含三个:“费用”,“fiefoo”和“fum”。始终添加逗号可以避免此错误源。

允许尾随逗号也可以使得编程代码生成更容易。