类
中英名词对照:
- 名称 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