Python语法及语义

Python语法及语义(Python syntax and semantics),Python编程语言语法是一组Python的运作编程规则,用于定义Python程序如何来编写与编译(由运行时系统程式师来操作)。[1]Python语言与PerlC,以及Java有许多相似之处,不过在这些语言之间仍存在着一些明确的差异[1]

设计理念

Python被设计为一种高度可读的编程语言。[2]它具有相对排列有致的视觉布局,并且在其他编程语言使用标点符号的情况下经常使用英文关键字。Python的目标是在其语法设计中保持简单与一致性,在“Python之禅”一书中的“宣示”也提到“应该有一种——最好只有一种——明显的方法来达成Python简单与一致性的目标”。[3]

如上述、Python的“宣示”即“应该有一种——最好只有一种——明显的方法来达成Python简单与一致性的目标”;而这个宣示故意跟“Perl”及“Ruby”的宣示“不止只有一种方法可以达成目标”唱反调。

关键字

Python有35个关键字或保留字;这些关键字或保留字不能用作标识符[4][5]

  • and
  • as
  • assert
  • async[note 1]
  • await[note 1]
  • break
  • class
  • continue
  • def
  • del
  • elif
  • else
  • except
  • False[note 2]
  • finally
  • for
  • from
  • global
  • if
  • import
  • in
  • is
  • lambda
  • None
  • nonlocal[note 3]
  • not
  • or
  • pass
  • raise
  • return
  • True[note 2]
  • try
  • while
  • with
  • yield
注解
  1. ^ 1.0 1.1 asyncawait自Python 3.5启用[6]
  2. ^ 2.0 2.1 TrueFalse在Python 3.0中成为保留字,此前为全局变量。
  3. ^ nonlocal自Python 3.0启用。

首行缩排

Python 使用空白字元来分隔控制流程区块(遵循越位规则)。 Python 借鉴了其前身ABC 的这一特性:它使用首行缩排来表示代码块的运作,而不是标点符号关键字

在所谓的"自由格式"(free-format)的编程语言中——使用源自ALGOL的区块结构——代码区块会使用大括号({ })或关键字来区分隔开。在这些编程语言的大多数代码约定中,程序师通常会在区块内缩进代码,使其在视觉上能与周围的代码区明白的区分开来。

一个名为foo递回函数,它传递一个参数x,如果参数为0,仅呼叫bar这个函数;否则将呼叫不同的名为baz的函数,传递参数x,并递回呼叫自身,传递 x-1为参数 , 这都可以在 Python 中实现的演算:

def foo(x):
    if x == 0:
        bar()
    else:
        baz(x)
        foo(x - 1)

并且可以在C中用K&R缩排样式写成如下程式:

void foo(int x)
{
    if (x == 0) {
        bar();
    } else {
        baz(x);
        foo(x - 1);
    }
}

不正确的缩排代码可能会被人误读,而不是被编译器直译器解译。 比如,如果上例中最后一行的函数呼叫 foo(x - 1)会被错误地缩排到if/else区块之外:

def foo(x):
    if x == 0:
        bar()
    else:
        baz(x)
    foo(x - 1)

造成上例编程无论如何最后一行都会被执行,即使x0亦是如此,结果会导致无限递回的现象。

虽然空格制表键都被接受作为缩排的形式,并且可以使用任意倍数的空格,也建议使用空格作为不同的架构区分[7];并且在标准上建议区分不同的巢状结构使用 4 个空格的形式(如上例所示),这也是迄今为止最常用的空格布局形态。[8][9]从 Python 3[10]开始,不允许在同一源代码文件中的连续行上混合空格和制表符,这会导致产生难以发现的错误;因为有许多工具无法在视觉上区分空格与制表键。

数据结构

由于 Python 是一种动态类型的编程语言,因此 Python 值不是一种变量携带资料类型信息。 Python 中的所有变量都持有对对象参照,这些参照被传递给函数; 函数不能在其呼叫函数中更改变量参照的值(但请参阅下面的例外情况)。有些人(包括吉多·范罗苏姆本人)将这种参数传递方案称为"依物件参照呼叫"。"对象参照"意味着一个名称,"传递参照"是一个"别名",即是对同一物件的参照副本,就像在 C/C++ 中一样。物件的值可以在被呼叫的函数中用“别名”改变,比如:

>>> alist = ['a', 'b', 'c']
>>> def my_func(al):
...     al.append('x')
...     print(al)
...
>>> my_func(alist)
['a', 'b', 'c', 'x']
>>> alist
['a', 'b', 'c', 'x']

函数my_func用形式引数al改变alist的值,它即是alist的别名。然而、任何对别名本身进行操作的尝试都不会对原始对象产生影响。在Python中,非最内部局部(non-innermost-local)以及未声明全面(not-declared-global)可攫取的名称都是别名。

在动态类型语言中,Python 进行了适度的类型检查。 隐式转换是为数字类型(以及布尔值)定义的,因此可以有效地将复数乘以整数(例如)而没有显式转换。 但是,例如数字和字符串之间没有隐式转换; 字符串是需要数字之数学函数的无效参数。

基本类型

Python 具有广泛的基本数据类型。 除了传统的整数以及浮点数算术外,它透明地支持高精度计算复数,及十进制数英语Decimal data type

集合类型

Python非常有用的方面之的是集合(或容器)类型的概念。 一般来说,集合是一个以易于引用、或则索引的方式包含其他对象的物件。 集合有两种基本形式:序列与映射。

物件系统

在Python中,一切都是物件,甚至是类属。类属,作为物件,有一个类属,称为它们的元类。Python亦支持多重继承以及Mixin

Python语言支持对类型以及类属的广泛自检。 可以读取与比较类型——而所谓的类型是指类型的实例。 物件的属性可以提取为字典。

文字

字符串

Python有各种字符串文字英语String literal

普通字符串文字

单引号双引号均可用于引用字符串。 与Unix shell语言、Perl,或受Perl影响的语言(诸如RubyGroovy)不同,单引号和双引号的功能相同,即$foo表达式没有字符串插值。然而,可以通过多种方式进行插值:使用“f-strings”(自Python 3.6[11]开始)、使用格式方法或者旧的%字符串格式的运算符。

比如,所有这些 Python 的宣告语句:

print(f"I just printed {num} pages to the printer {printer}")

print("I just printed {} pages to the printer {}".format(num, printer))
print("I just printed {0} pages to the printer {1}".format(num, printer))
print("I just printed {num} pages to the printer {printer}".format(num=num, printer=printer))

print("I just printed %s pages to the printer %s" % (num, printer))
print("I just printed %(num)s pages to the printer %(printer)s" % {"num": num, "printer": printer})

等价于Perl的宣告语句:

print "I just printed $num pages to the printer $printer\n"

语句使用变量num以及printer构建一个字符串。

多行字符串文字

还有多行字符串,它们以一系列三个单引号或双引号开头及结尾,而其功能类似于PerlRuby中的Here文档

字符串内插值英语String interpolation的一个简单示例(使用格式的方法)比如:

print("""Dear {recipient},

I wish you to leave Sunnydale and never return.

Not Quite Love,
{sender}
""".format(sender="Buffy the Vampire Slayer", recipient="Spike"))

原始字符串

范例包括:

>>> # A Windows path, even raw strings cannot end in a backslash
>>> r"C:\Foo\Bar\Baz\"
  File "<stdin>", line 1
    r"C:\Foo\Bar\Baz\"
                     ^
SyntaxError: EOL while scanning string literal

>>> dos_path = r"C:\Foo\Bar\Baz\ " # avoids the error by adding
>>> dos_path.rstrip()              # and removing trailing space
'C:\\Foo\\Bar\\Baz\\'

>>> quoted_dos_path = r'"{}"'.format(dos_path)
>>> quoted_dos_path
'"C:\\Foo\\Bar\\Baz\\ "'

>>> # A regular expression matching a quoted string with possible backslash quoting
>>> re.match(r'"(([^"\\]|\\.)*)"', quoted_dos_path).group(1).rstrip()
'C:\\Foo\\Bar\\Baz\\'

>>> code = 'foo(2, bar)'
>>> # Reverse the arguments in a two-arg function call
>>> re.sub(r'\(([^,]*?),([^ ,]*?)\)', r'(\2, \1)', code)
'foo(2, bar)'
>>> # Note that this won't work if either argument has parens or commas in it.

相邻字符串文字的串联

因此

title = "One Good Turn: " \
        'A Natural History of the Screwdriver and the Screw'

相当于

title = "One Good Turn: A Natural History of the Screwdriver and the Screw"

Unicode

从Python 3.0开始,源代码与直译器的默认字符集都是UTF-8。在UTF-8中,Unicode字符串的处理方式及传统的字节字符串类似。这个例子将起如下作用:

s = "Γειά" # Hello in Greek
print(s)

数字

Python中的数字文字是属于普通类型,例如:0-13.43.5e-8

列表、多元组、集合、字典

Python具有支持创建容器类型的语法。

列表(类属列表)是任意类型项目的可变序列,可以使用特殊语法来创建。

a_list = [1, 2, 3, "a dog"]

或使用普通物件来创建

a_second_list = list()
a_second_list.append(4)
a_second_list.append(5)

多元组(类属元组)是任意类型项目的不可变序列。还有一个特殊的语法来创建多元组

a_tuple = 1, 2, 3, "four"
a_tuple = (1, 2, 3, "four")

运算符

算术

在算术上Python包括+-*/("真值除法")、//(下限除法)、%(模数)以及**(取幂)运算符,以及它们通常的数学优先级别。在Python 3中,x/y执行"真值除法"(true division),这表示它总是会回返一个浮点数,即使x与y都是整除的整数

>>> 4 / 2
2.0

接着//执行整数除法、或下限除法,将商数的下限作为整数回返。

在Python 2(及大多数其他编程语言)中,除非明确要求,否则x/y在执行整数除法时,仅当任一输入值为浮点数时才回返浮点数。但是,因为Python是一种动态类型的语言;并不总是能够分辨出正在执行的是哪一个操作,这往往会导致细微的错误。从而促使Python 3引入//运算符,并改变/运算符的语义。

比较运算符

比较运算符(comparison operator),比如:==, !=, <, >, <=, >=, is, is not, innot in[12]可用于各种运算值的使用上。数字、字符串、序列,以及映射都可以进行比较。在Python 3中,不同的类型(比如strint)没有一致的相对顺序。虽然在Python 2中可以比较某个字符串是大于、还是小于某个整数;但这被认为是历史上的"设计怪癖",最终在Python 3中被删除。

诸如a < b < c之类的"炼式比较表达式"(chained comparison expressions)大致具有它们在数学中的含义,而不是在C与类似语言中发现的不寻常含义。这些程式术语是依据顺序进行评估与比较。这个操作具有短路求值语义,这表示著一旦判断明确,就保证评估停止:如果a < b为假,则永远不会评估到c,因为表达式不可能再为真。

对于没有副作用的表达式,a < b < c为等价于a < b 與 b < c。然而,当表达式具有副作用时,也是存在很大差异。a < f(x) < b将只计算f(x)一次,而a < f(x) 以及 f(x) < b;如果a的值小于f(x)将计算两次,否则只计算一次。

逻辑运算符

在所有版本的Python里面,布尔运算符处理零值或空值、诸如""0None0.0[],以及{}将被视为”假“;而通常则将非空(non-empty)、非零值(non-zero value)视为"真"。布尔值在Python 2.2.1中是作为常数(从10产生的子类化)添加到编程语言里,并在Python 3中变成为完整的关键字。二进制"比较运算符"、诸如==以及>则回返的信息。

布尔运算符(boolean operator)andor使用最小化求值。例如,y == 0 或則 x/y > 100永远不会引发被零除之异常错误信息。这些"运算符"回返最后操作元评估之值,而不是或是的情形。因此,表达式(4 及 5)的计算结果为 5,而(4 或 5)的计算结果为 4。因此,表达式(4 及 5)的计算结果为 5,而(4 或 5)的计算结果为4

函数式编程

如上所述,Python的另一个优势是函数式编程风格的可用性。正如所料,这使得使用列表及其他集合更加简单。

理解

L = [mapping_expression for element in source_list if filter_expression]

第一级函数

在Python中,函数是可以动态创建以及传递的第一级物件。

Python对匿名函数的有限支持是lambda建构式。下列范例是对输入值求平方的匿名函数,使用引数5来呼叫(调用)的:

f = lambda x: x**2
f(5)

Lambda仅限于包含表达式英语Expression (computer science)而不是语句陈述式,尽管"控制流"(control flow)仍可以通过使用短路在lambda中不太优雅地实现编程,[13]并且更习惯地使用"条件表达式"(conditional expression)。[14]

闭包

def derivative(f, dx):
    """Return a function that approximates the derivative of f
    using an interval of dx, which should be appropriately small.
    """
    def function(x):
        return (f(x + dx) - f(x)) / dx
    return function

生成器

from itertools import count

def generate_primes(stop_at=None):
    primes = []
    for n in count(start=2):
        if stop_at is not None and n > stop_at:
            return # raises the StopIteration exception
        composite = False
        for p in primes:
            if not n % p:
                composite = True
                break
            elif p ** 2 > n:
                break
        if not composite:
            primes.append(n)
            yield n

生成器表达式

在Python 2.4中引入的生成器表达式是列表推导式的惰性求值等价式。使用上一节中提供的质数生成器,我们可以定义一个惰性求值方式但不是无限的集合。

from itertools import islice

primes_under_million = (i for i in generate_primes() if i < 1000000)
two_thousandth_prime = islice(primes_under_million, 1999, 2000).next()
primes_under_million = [i for i in generate_primes(2000000) if i < 1000000]
two_thousandth_prime = primes_under_million[1999]

字典与集合推导式

>>> dict((n, n*n) for n in range(5))
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Python 2.7及3.0通过引入字典与集合推导式来统一所有集合类型,类似于列表推导式:

>>> [n*n for n in range(5)]  # regular list comprehension
[0, 1, 4, 9, 16]
>>>
>>> {n*n for n in range(5)}  # set comprehension
{0, 1, 4, 9, 16}
>>>
>>> {n: n*n for n in range(5)}  # dict comprehension
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

物件

Python支持大多数面向物件编程(OOP)技术。

With 宣告

关于with语句可以处理资源的使用,并允许使用者使用"上下文管理器协议"(Context Manager protocol)。[15]在进入作用域时呼叫一个函数(__enter__()),而自离开时呼叫另一个函数(__exit__())。如此可以防止忘记释放资源,而且还可以处理更复杂的情形、比如在资源使用过程中发生异常时可以释放资源。"上下文管理器"(Context Managers)通常与文件、数据库连接、测试用例等情况一起使用。

属性

属性允许使用及属性揭取相同的语法在物件实例上调用专门定义的方法。 定义一些属性的类属的一个例子是:

class MyClass:
    def __init__(self):
        self._a = None

    @property
    def a(self):
        return self._a

    @a.setter  # makes the property writable
    def a(self, value):
        self._a = value

描述符

定义三个特殊方法__get__(self, instance, owner)__set__(self, instance, value)__delete__(self, instance)) 中的一个,或多个的类属可以用作描述符。创建描述符的实例作为第二个类的类成员,使该实例成为第二个类之属性。[16]

类属与静态法

Python允许通过使用@cla @classmethod@staticmethod修饰模式来创建类方法和静态方法。方法的第一个参数是类物件,而不是对实例的自引用。静态方法没有特殊的第一个参数。实例及类物件都不会传递给静态方法

异常

在第一个代码的示例中,遵循LBYL的方法,在揭取之前对属性进行了外显的检查:

if hasattr(spam, 'eggs'):
    ham = spam.eggs
else:
    handle_missing_attr()

第二个示例遵循EAFP范式:

try:
    ham = spam.eggs
except AttributeError:
    handle_missing_attr()

说明与文档字符串

说明一段代码:

import sys

def getline():
    return sys.stdin.readline()  # Get one line and return it

用多行说明一段代码:

def getline():
    return sys.stdin.readline()    """this function
                                      gets one line
                                      and returns it"""

函数注解

函数注解(类型提示)在PEP3107中定义。[17]函数注解允许将数据附加到函数的参数以及返回原址。注释的特性不是由编程语言所定义的,而是留给第三方框架的来进行的。例如,可以编写一个数据库来处理静态类型的编程语言架构:[17]

def haul(item: Haulable, *vargs: PackAnimal) -> Distance

修饰子

修饰子(装饰器;修饰符)是用于修改函数、方法,或则类定义的任何可调用的 Python 物件。修饰子传递正被定义的原始物件、并返回修改后的物件;然后将其绑定到定义中的名称之上。Python 修饰子部分受到 Java 注释运用的启发,并且具有类似 Java语法;修饰子语法是纯粹的语法糖,使用 @ 作为修饰子的关键字

@viking_chorus
def menu_item():
    print("spam")

相当于

def menu_item():
    print("spam")
menu_item = viking_chorus(menu_item)

修饰子是元编程的一种形式; 它们增强了它们修饰的函数或方法的作用。 比如,在下面的示例中,viking_chorus 可能会导致 menu_item 每次被呼叫时运行 8 次(参见小品喜剧英语Spam (Monty Python)(Spam)):

def viking_chorus(myfunc):
    def inner_func(*args, **kwargs):
        for i in range(8):
            myfunc(*args, **kwargs)
    return inner_func

函数修饰子的典型用途是创建类方法静态方法、添加函数属性、追踪英语Tracing (software)、设置前置后置条件以及同步[18]不过可以用于更多方面,包括尾递归[19]记忆化;甚至改进其他修饰子的编写。[20]

修饰子的作用;即是可以经由在主要编程语言相邻且相关的行之上置放几个修饰子、进而来带动主程式链接修饰子之间的连系:

@invincible
@favourite_colour("Blue")
def black_knight():
    pass

相当于

def black_knight():
    pass
black_knight = invincible(favourite_colour("Blue")(black_knight))

或者,使用中介架构的变数、如下程式所示:

def black_knight():
    pass
blue_decorator = favourite_colour("Blue")
decorated_by_blue = blue_decorator(black_knight)
black_knight = invincible(decorated_by_blue)

在上述的例子中,favourite_colour 修饰子工厂接受一个参数。 修饰子工厂必须返回一个修饰子,然后使用要修饰的物件作为参数来呼叫它:

def favourite_colour(colour):
    def decorator(func):
        def wrapper():
            print(colour)
            func()
        return wrapper
    return decorator

复活节彩蛋

一个隐藏信息,Python之禅Python设计哲学的总结),在尝试import this(导入this) 时会显示出来。

注释

  1. ^ 1.0 1.1 "Readability counts." - PEP 20 - The Zen of Python页面存档备份,存于互联网档案馆
  2. ^ "Readability counts." - PEP 20 - The Zen of Python 互联网档案馆存档,存档日期2014-12-05.
  3. ^ PEP 20 - The Zen of Python. Python Software Foundation. 2004-08-23 [2008-11-24]. (原始内容存档于2008-12-03). 
  4. ^ 2. Lexical analysis. Python 3 documentation. Python Software Foundation. [2021-03-11]. (原始内容存档于2018-01-09). 
  5. ^ 2. Lexical analysis. Python v2.7.18 documentation. Python Software Foundation. [2021-03-11]. (原始内容存档于2022-05-02). 
  6. ^ New Keywords. Python v3.5 documentation. Docs.python.org. [2016-06-01]. (原始内容存档于2016-06-18). 
  7. ^ PEP 8 -- Style Guide for Python Code. Python.org. [2021-03-17]. (原始内容存档于2018-07-13) (英语). 
  8. ^ Hoffa, Felipe. 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?. Medium. 2017-07-26 [2021-03-11]. (原始内容存档于2022-03-18) (英语). 
  9. ^ Tabs or Spaces. ukupat.github.io. [2021-03-11]. (原始内容存档于2022-05-05). 
  10. ^ PEP 8 -- Style Guide for Python Code. Python.org. [2021-03-11]. (原始内容存档于2018-07-13) (英语). 
  11. ^ PEP 498 - Literal String Interpolation. What’s New In Python 3.6. 2016-12-23 [2017-03-29]. (原始内容存档于2017-03-30). 
  12. ^ 6. Expressions — Python 3.9.2 documentation. docs.python.org. [2021-03-17]. (原始内容存档于2022-05-10). 
  13. ^ David Mertz. Functional Programming in Python. IBM developerWorks. [2007-08-27]. (原始内容存档于2007-02-20). 
  14. ^ PEP 308 -- Conditional Expressions. [2016-04-14]. (原始内容存档于2016-03-13). 
  15. ^ PEP 343 -- The "with" Statement. [2014-08-15]. (原始内容存档于2014-12-14). 
  16. ^ Glossary — Python 3.9.2 documentation. docs.python.org. [2021-03-23]. (原始内容存档于2020-06-25). 
  17. ^ 17.0 17.1 PEP 3107 -- Function Annotations. [2014-08-15]. (原始内容存档于2015-01-06). 
  18. ^ Python 2.4 Decorators: Reducing code duplication and consolidating knowledge. Dr. Dobb's. 2005-05-01 [2007-02-08]. (原始内容存档于2007-02-06). 
  19. ^ New Tail Recursion Decorator. ASPN: Python Cookbook. 2006-11-14 [2007-02-08]. (原始内容存档于2007-02-09). 
  20. ^ The decorator module. [2007-02-08]. (原始内容存档于2007-02-10). 

参阅

外部连接