作用域和命名空间示例
以下示例演示了如何引用不同的作用域和命名空间,以及 global 和 nonlocal 如何影响变量绑定:
def scope_test():
def do_local():
namedemo = "local namedemo"
def do_nonlocal():
nonlocal namedemo
namedemo = "nonlocal namedemo"
def do_global():
global namedemo
namedemo = "global namedemo"
namedemo = "test namedemo"
do_local()
print("local 赋值后:", namedemo)
do_nonlocal()
print("nonlocal 赋值后:", namedemo)
do_global()
print("global 赋值后:", namedemo)
scope_test()
print("在 global 作用域:", namedemo)
复制
运行结果:
local 赋值后: test namedemo
nonlocal 赋值后: nonlocal namedemo
global 赋值后: nonlocal namedemo
在 global 作用域: global namedemo
复制
请注意,局部赋值(默认)没有改变 scope_test 对 namedemo 的绑定。nonlocal 赋值更改了 scope_test 对 namedemo 的绑定,global 赋值更改了模块级绑定。
类引入了少量新语法、三种新对象类型和一些新语义。
类定义语法
类定义的最简单形式如下所示:
class ClassName:
<statement-1>
.
.
.
<statement-N>
复制
类定义必须在应用之前执行,如同函数定义(def 语句)。(可以将类定义放在 if 语句的分支中或函数中。)
在实践中,类定义中的语句通常是函数定义,但是其他语句也是允许的,有时也是有用的——我们将在后面讨论。类中的函数定义通常有一种特殊形式的参数列表,由方法的调用约定决定——同样,这将在后面解释。
当进入一个类定义时,将创建一个新的命名空间,并将其用作局部作用域-因此,所有对局部变量的赋值都将进入这个新命名空间。特别是,函数定义在这里绑定新函数的名称。
当类定义正常结束时,将创建一个类对象。这基本上是类定义创建的命名空间内容的包装器。原始的局部作用域(在进入类定义之前生效的作用域)被恢复,在这里类对象被绑定到类定义头中给出的类名(示例中的 ClassName)。
类对象
类对象支持两种操作:属性引用和实例化。
属性引用使用 Python 中的标准语法: obj.name。有效属性名称是创建类对象时类命名空间中的所有名称。如果类定义如下所示:
class MyClass:
"""简单类示例"""
i = 12345
def fun(self):
return 'Hi'
复制
MyClass.i 和 MyClass.fun 是有效的属性引用,分别返回整数和函数对象。可以对类属性赋值,因此可以更改 MyClass.i 的值。__doc__ 也是一个有效属性,返回属于类的文档字符串:“简单类示例”。
类实例化使用函数表示法。假设类对象是一个无参数函数,它返回类的一个新实例。例如:
x = MyClass()
复制
创建类的新实例,并将此对象指定给局部变量 x。
实例化操作(“调用”类对象)创建一个空对象。许多类在创建对象时会指定特定的初始状态。因此,类可以定义一个名为 __init__() 的特殊方法,如下所示:
def __init__(self):
self.data = []
复制
当类定义了 __init__() 方法时,类实例化会自动为新创建的类实例调用 __init__()。因此,在本例中,可以通过以下方式获得新的初始化实例:
x = MyClass()
复制
当然,__init__() 方法可以带有参数,使方法更具灵活性。在这种情况下,提供给类实例化运算符的参数将传递给 __init__()。例如:
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)
复制
运行结果:
3.0 -4.5
复制
实例对象
现在可以用实例对象做什么?实例对象理解的唯一操作是属性引用。有两种有效的属性名称:数据属性和方法。
数据属性对应于 Simultalk 中的“实例变量”和 C++ 中的“数据成员”。数据属性不需要声明;与局部变量一样,它们在第一次分配给时就开始存在。例如,如果 x 是上面创建的 MyClass 的实例,下面的代码将打印出 16,而不留下任何其他痕迹:
class MyClass:
"""简单类示例"""
i = 12345
def fun(self):
return 'Hi'
x = MyClass()
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
复制
另一种实例属性引用是方法。方法是“属于”对象的函数。(在 Python 中,术语“方法”并不是类实例独有的:其他对象类型也可以有方法。例如,列表对象有 append、insert、remove、sort 等方法。但是,在下面的讨论中,除非另有明确说明,否则我们将专门使用术语“方法”来表示类实例对象的方法.)
实例对象的有效方法名取决于其类。根据定义,类的所有属性中的函数对象定义对应其实例的相应方法。因此,在我们的示例中,x.fun 是一个有效的方法引用,因为 MyClass.fun 是一个函数,但 x.i 不是,因为 MyClass.i 不是函数。但是 x.fun 和 MyClass.fun 不是一回事。它是一个方法对象,而不是函数对象。
方法对象
通常,方法在绑定后立即调用:
x.f()
复制
在 MyClass 示例中,这将返回字符串 'Hi'。但是,没有必要立即调用方法:x.fun 是一个方法对象,可以存储起来,以后再调用。例如:
class MyClass:
"""简单类示例"""
i = 12345
def fun(self):
return 'Hi'
x = MyClass()
xf = x.fun
while True:
print(xf())
复制
将持续打印 Hi。
调用一个方法时会发生什么?你可能已经注意到,上述调用 x.fun() 时没有使用任何参数,即使 fun() 的函数定义指定了参数。调用有必须参数的函数,但是调用时却没有提供,Python 会引发一个异常,即使该参数没有实际使用......
实际上,你可能已经猜到了答案:方法的特殊之处在于实例对象作为函数的第一个参数传递。在我们的示例中,调用 x.fun() 与 MyClass.fun(x) 完全等效。通常,调用带有 n 个参数的方法,和在该参数列表第一个参数之前插入方法的实例对象是等价的。
如果仍然不了解方法是如何工作的,那么看看实现可能会澄清问题。引用实例的非数据属性时,将搜索该实例的类。如果名称是作为函数对象的有效类属性,则通过打包(指向)实例对象和刚刚在抽象对象中找到的函数对象来创建方法对象:这就是方法对象。使用参数列表调用方法对象时,将从实例对象和参数列表构造新的参数列表,并使用此新参数列表调用函数对象。
类和实例变量
一般来说,实例变量用于每个实例特有的数据,类变量用于类的所有实例共享的属性和方法:
class Book:
kind = '小说' # 所有实例共享的类变量
def __init__(self, name):
self.name = name # 独属于实例的变量
d = Book('玄幻')
e = Book('穿越')
print(d.kind)
print(e.kind)
print(d.name)
print(e.name)
复制
运行结果:
小说
小说
玄幻
穿越
复制
正如之前曾经介绍过的,共享数据可能会对涉及列表和字典等可变对象产生令人吃惊的影响。例如,以下代码中的 tricks 列表不应用作类变量,因为所有 Dog 实例共享一个列表:
class Dog:
tricks = [] # 错误使用类变量
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
black = Dog('小黑')
white = Dog('小白')
black.add_trick('打滚儿')
white.add_trick('装死')
print(black.tricks) # 被所有其他实例共享了
复制
运行结果:
['打滚儿', '装死']
复制
类的正确设计应改为使用实例变量:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] #创建空列表
def add_trick(self, trick):
self.tricks.append(trick)
black = Dog('小黑')
white = Dog('小白')
black.add_trick('打滚儿')
white.add_trick('装死')
print(black.tricks)
print(white.tricks)
复制
运行结果:
['打滚儿']
['装死']
复制
官方文档:
https://docs.python.org/3.9/tutorial/classes.html