从变量开始

python 中全局变量在函数作用域内只能读,不能“写”。如果要在函数作用域内实现修改全局变量值操作,需要使用关键字 global 显示指明该变量是全局变量。

但是,在函数中的变量是即时的,调用的时候才被用到,调用完变量就会销毁。变量是临时的,状态不能保存。

那么,如果想保存临时变量的值,等需要用的时候才使用该变量,该怎么做呢?

python 引入一种称为“闭包”的机制来处理这种情况。

所谓闭包,既是函数的嵌套,在一个函数(外函数)中嵌套另一个函数(内函数)。内函数使用外函数的临时变量,外函数引用内函数的函数引用。

让我们放下这句看起来很玄的话,先看看为什么 python 可以实现这种函数闭包机制:

python 将一切都视为对象,变量,函数,类等都是对象。简单的赋值操作,即将对象和变量名绑定在一起,变量名是对象的引用。比如,对于变量 1 来说,它可以赋值给 a ,也可以赋值给 b。那么, a 和 b 就是 1 的引用,类似于 C/C++ 中的指针,它们都“指向”变量 1 所在的内存地址:

>>> a = b = 1

>>> print(id(a), id(b))265086896 265086896

同样的,python 也将函数名看作是函数的引用。所以,可以像对待变量一样对待函数。将一个变量(函数名)赋给另一个变量,它们都指向函数所在的内存地址:

defhello():print("Hello World\n")

lianhuasheng=helloprint(id(lianhuasheng), id(hello))>>> 21004616 21004616

由于 python 是动态解释型语言,在执行到函数定义处,即在内存中给 hello 函数开辟内存,所以这里引用 lianhuasheng 和 hello 都指向了 hello 函数的内存地址 21004616。

回过头来,再看这句话“内函数使用外函数的临时变量,外函数引用内函数的函数引用”。意思已经很明显了,内函数可以返回函数名给外函数,外函数获取该函数名,将它赋给外部变量,外部变量即成为该内函数的引用。重写改写 hello 函数为:

definputName(name):

hostname= name + ".local"

defhello():print(hostname)returnhello

name= inputName("lianhuasheng")print(id(name), name, name.__name__)>>> 3637576 .hello at 0x00378148> hello

从打印结果可以看到,引用 name “指向”的是函数名为 hello 的内存地址。

进一步的,现在需要打印 hostname, 那么我们可以通过 hello 引用来调用 hello 函数:

definputName(name):

hostname= name + ".local"

defhello():print(hostname)returnhello

name= inputName("lianhuasheng")print(id(name), name, name.__name__)

name()>>> 55148872 .hello at 0x03498148>hello>>> lianhuasheng.local

可以看到通过闭包机制临时变量 hostname 被保存了起来(事实上是和内函数绑定在一起了),等需要调用的时候才使用临时变量的值。

类似于在函数中修改全局变量,如果在内函数中修改绑定的外部临时变量,需要使用关键字 nonlocal 显示指明该变量来自外部(外函数):

definputName(name):

hostname= name + ".local"

defhello():

hostname= hostname + ".fullname"

print(hostname)returnhello

name= inputName("lianhuasheng")print(id(name), name, name.__name__)

name()>>> UnboundLocalError: local variable 'hostname'referenced before assignmentdefinputName(name):

hostname= name + ".local"

defhello():

nonlocal hostname

hostname= hostname + ".fullname"

print(hostname)returnhello

name= inputName("lianhuasheng")print(id(name), name, name.__name__)

name()>>> 46760264 .hello at 0x02C98148>hello

lianhuasheng.local.fullname

从闭包到装饰器

前面在演示闭包的时候,修改了 hello 函数,那么能否在不需要修改 hello 函数的情况下实现闭包呢?

可以的,可以使用装饰器来实现这一功能。顾名思义,装饰器是起装饰作用的东西,它并不改动装饰体的内容。给 hello 函数加个装饰器,如下:

defhello():print("Hello World")defhelloDecorator(func):print("This is a demo of decorator")def wrapper(*args, **kw):return func(*args, **kw)returnwrapper

lianhuasheng=helloDecorator(hello)print(lianhuasheng.__name__)>>> This isa demo of decorator>>> wrapper

通过向 helloDecorator 函数传入函数名 hello 来调用 hello 函数,实际的 hello 函数并未改动。

值得注意的是,引用 lianhuasheng “指向”的函数是 wrapper 函数,所以它的函数名是 wrapper。

对于这句 lianhuasheng = helloDecorator(hello) 也可将它写成 hello = helloDecorator(hello),python 在函数定义处加上 @helloDecorator 来表示这条语句,即 helloDecorator 是个装饰器。

defhelloDecorator(func):print("This is a demo of decorator")def wrapper(*args, **kw):return func(*args, **kw)returnwrapper

@helloDecoratordefhello():print("Hello World")print(hello.__name__)>>> This isa demo of decorator>>> wrapper

注意引用 hello 的函数名是 wrapper!

类似的还有带参数的装饰器,这里不介绍了。

装饰器在类里是什么样呢?

装饰器可以用在函数中。同样的,它也可以用在类里。在类中的装饰器叫做静态方法和类成员方法。

静态方法和类成员方法:

classDemo:

name= "None"

def __init__(self):

self.name=Demo.nameprint("A demo of staticmethod and classmethod")

@staticmethoddefprintName(name):print("My name is {}".format(name))

@classmethoddefinputName(cls, name):

cls.name=name

Demo.printName(cls.name)print(cls)

student=Demo()

student.inputName("lianhuasheng")print(student.name, Demo.name, student, Demo)

student.name= "lianhua"

print(student.name, Demo.name, student, Demo)

Demo.inputName("lianhuasheng")print(student.name, Demo.name, student, Demo)

student.printName("lianhuasheng")

Demo.printName("lianhuasheng")>>>A demo of staticmethodandclassmethod

My nameislianhuashengNone lianhuasheng<__main__.demo object at> lianhua lianhuasheng<__main__.demo object at> My nameislianhuashenglianhua lianhuasheng<__main__.demo object at> My nameislianhuasheng

My nameis lianhuasheng

从上例可以看出:

类中,在定义前分别加上 @staticmethod 和 @classmethod 表示静态方法和类成员方法。

不管是静态方法和类成员方法都能被类实例和类访问。

静态方法不能修改类变量和类实例变量,且它接受的参数非 self /非 cls。相当于是定义在类中的函数。

类成员方法可以修改类变量,但是不能访问类实例变量。它传入的 cls 参数实际上是类,在上例中是 。

修改类实例变量的值并不会改变类变量,同样的修改类变量也不会改变类实例变量的值。

(完)

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐