跳到主要内容

中英名词对照:

  • 名称 name
  • 命名空间 namespace
  • 属性 attribute
  • 作用域 scope

名称和对象

对象之间相互独立,可以有多个名称指向到同一对象上。对于基本类型可以忽视这一特性。

作用域和命名空间

命名空间 是名称到对象的映射。大多数命名空间都使用字典实现。有如下几种命名空间的例子:

  • 内置名称集合(包括 abs() 以及内置异常的名称等);
  • 一个模块的全局名称;
  • 函数调用的局部名称;
  • 一个对象的属性集合也可视为命名空间。

对模块中名称的引用是属性引用。不同命名空间中的名称之间没有任何关系。

属性可以是只读或者可写。模块属性是可写的:

m.the_answer = 42   # 修改属性
del m.the_answer # 删除属性

命名空间有自己的创建时间及生命周期,比如:

  • 内置名称的命名空间(即 builtins 模块)是在解释器启动时创建,且永远不会被删除;
  • 模块的全局命名空间在读取定义时被创建,通常会持续到解释器退出;
  • 从脚本文件读取或交互式读取的,由解释器顶层调用执行的语句是 __main__ 模块调用的一部分,也拥有自己的全局命名空间。

函数的局部命名空间会在函数被调用时被创建,并在结束调用后被删除。

一个命名空间的 作用域 是代码中的一段文本区域,在这段区域可直接访问该命名空间。

作用域在代码定义就被确定下来,但使用时是动态的,存在嵌套关系。在执行期间,都会有 3 到 4 个嵌套作用域:

  • 最内层作用域,包含局部名称,会最先被搜索;
  • 外层闭包函数的作用域,包含非局部非全局的名称,名称会从内到外搜索;
  • 倒数第二层作用域,包含当前模块的全局名称;
  • 最外层作用域,是内置名称的命名空间。
name = "talaxy"

def try_rename():
name = "allay" # 局部名称,不会影响全局名称
print(name) # 打印 "allay"

try_rename()
print(name) # 打印 "talaxy"

在上述的例子中,如果要让函数中的 name 与全局的 name 绑定,需要使用 global 语句:

def try_rename():
global name
name = "allay" # 修改全局名称
print(name) # 打印 "allay"

还有一个类似的 nonlocal 语句,它会在外层闭包函数的作用域中由内向外搜索名称并绑定:

def test():
count = 20 # `try_modify` 中的 `count` 会绑定到这里

def try_modify():
nonlocal count
count = 30

注意,nonlocal 无法绑定到全局名称。

初探类

class 关键字来定义类:

class MyClass: # 这里的 `MyClass` 称之为一个类对象。
pass

当进入类定义时,将创建一个新的命名空间,并将其用作局部作用域。

类属性的定义:

class MyClass:
"""A simple example class"""
i = 12345

def hello():
return "hello world"

print(MyClass.i) # => "12345"
print(MyClass.hello()) # => "hello world"
print(MyClass.__doc__) # => "A simple example class"

实例化方法:

class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y

def output(self):
print(self.x, self.y)

p = Point()
p.output() # => 0 0
f = p.output # 可以直接将方法赋值给变量
f() # => 0 0

虽然在定义方法时第一个参数是 self,但在调用时不需要传入 self,这区别于类对象的直接调用:

Point.output(p) # => 0 0

self 只是一个约定俗成的名称,可以使用其他名称代替,但最好不要违背这个约定。

方法也可以在类定义外部定义:

def output(self):
print(self.x, self.y)

class Point:
# ...

output = output

注意区别类变量和实例变量:

class Dog:
kind = "canine" # 类变量

def __init__(self, name):
self.name = name # 实例变量

Dog.kind # => "canine"
Dog("Fido").name # => "Fido"

可以通过实例来访问类变量:

class Dog:
kind = "canine"

def __init__(self, name):
self.name = name

def what_kind(self):
return self.kind

d = Dog("Fido")
d.what_kind() # => "canine"
d.kind # => "canine"

如果类对象和实例对象有同名的属性,实例对象的属性会覆盖类对象的属性。

可以访问实例对象的 __class__ 属性来访问类对象:

print(Dog)          # => <class '__main__.Dog'>
print(d.__class__) # => <class '__main__.Dog'>

继承

在定义的类后面加上一个类名,表示继承自该类:

class Cat(Animal):  # 这里 `Animal` 可称为父类或基类
# ...

在访问类对象或者实例对象的属性时,如果没有找到,则会沿着基类继承链逐步向下查找。派生类也可以重写基类的方法。

两个继承机制相关的函数:

  • isinstance(obj, cls):判断一个对象是否是一个类的实例;
  • issubclass(c1, c2):判断一个类是否是另一个类的子类,如 issubclass(bool, int)True

Python 支持多重继承,对继承属性的搜索顺序是从左到右,深度优先(真实情况会更复杂,但会确保每个基类只被访问一次):

class Light(Wave, Particle):
# ...

私有变量

Python 中没有对实例属性的访问控制,即没有真正的私有变量。但大多数遵循一个约定:以下划线开头的名称(如 _name )应该被视为非公开的。

为了能够实现私有变量,Python 存在这样的机制:可以在属性名称前加上双下划线 __ ,如 __name ,在实际执行时 Python 会将其替换为 _类名__name ,这样就可以避免子类与基类之间对属性的使用冲突。

class Person:
def __init__(self, name):
self.name = name
self.__greeting() # 会始终调用 `Person.__greeting`

def greeting(self):
print("Hello, I'm " + self.name)

__greeting = greeting

class Student(Person):
def greeting(self):
return print("Hello, I'm " + self.name + ". I'm a student.")

student = Student("Talaxy") # => "Hello, I'm Talaxy"

杂项说明

可以对类使用 @dataclass 装饰器,可以实现类似 C 语言中结构体的功能:

from dataclasses import dataclass

@dataclass
class Point:
x: int
y: int

p = Point(1, 2) # 会自动实现 `__init__` 方法

迭代器

为了让一个对象可迭代,需要实现 __iter__ 方法,该方法返回一个迭代器对象,该对象实现了 __next__ 方法,该方法返回下一个元素,如果没有元素了则抛出 StopIteration 异常。

class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)

def __iter__(self):
return self

def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]

for char in Reverse("spam"):
print(char) # => "m", "a", "p", "s"

可以用全局函数 iter()next() 直接调用迭代器:

# 该例子模拟了上段代码的 for 语句的运行过程
it = iter(Reverse("spam"))
next(it) # => "m"
next(it) # => "a"
next(it) # => "p"
next(it) # => "s"
next(it) # => StopIteration

生成器

可以在函数中使用 yield 语句来实现生成器:

def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]

for char in reverse("golf"):
print(char) # => "f", "l", "o", "g"

生成器会自动创建 __iter____next__ 方法,且会自动抛出 StopIteration 异常。

生成器表达式

生成器表达式语法类似于列表推导式,但使用圆括号:

sum(i*i for i in range(10)) # => 285

参考

类 - python.org