从变量开始
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 也将函数名看作是函数的引用。所以,可以像对待变量一样对待函数。将一个变量(函数名)赋给另一个变量,它们都指向函数所在的内存地址:
def hello(): print("Hello World ") lianhuasheng = hello print(id(lianhuasheng), id(hello)) >>> 21004616 21004616
由于 python 是动态解释型语言,在执行到函数定义处,即在内存中给 hello 函数开辟内存,所以这里引用 lianhuasheng 和 hello 都指向了 hello 函数的内存地址 21004616。
回过头来,再看这句话“内函数使用外函数的临时变量,外函数引用内函数的函数引用”。意思已经很明显了,内函数可以返回函数名给外函数,外函数获取该函数名,将它赋给外部变量,外部变量即成为该内函数的引用。重写改写 hello 函数为:
def inputName(name): hostname = name + ".local" def hello(): print(hostname) return hello name = inputName("lianhuasheng") print(id(name), name, name.__name__) >>> 3637576 <function inputName.<locals>.hello at 0x00378148> hello
从打印结果可以看到,引用 name “指向”的是函数名为 hello 的内存地址。
进一步的,现在需要打印 hostname, 那么我们可以通过 hello 引用来调用 hello 函数:
def inputName(name): hostname = name + ".local" def hello(): print(hostname) return hello name = inputName("lianhuasheng") print(id(name), name, name.__name__) name() >>> 55148872 <function inputName.<locals>.hello at 0x03498148> hello >>> lianhuasheng.local
可以看到通过闭包机制临时变量 hostname 被保存了起来(事实上是和内函数绑定在一起了),等需要调用的时候才使用临时变量的值。
类似于在函数中修改全局变量,如果在内函数中修改绑定的外部临时变量,需要使用关键字 nonlocal 显示指明该变量来自外部(外函数):
def inputName(name): hostname = name + ".local" def hello(): hostname = hostname + ".fullname" print(hostname) return hello name = inputName("lianhuasheng") print(id(name), name, name.__name__) name() >>> UnboundLocalError: local variable 'hostname' referenced before assignment def inputName(name): hostname = name + ".local" def hello(): nonlocal hostname hostname = hostname + ".fullname" print(hostname) return hello name = inputName("lianhuasheng") print(id(name), name, name.__name__) name() >>> 46760264 <function inputName.<locals>.hello at 0x02C98148> hello lianhuasheng.local.fullname
从闭包到装饰器
前面在演示闭包的时候,修改了 hello 函数,那么能否在不需要修改 hello 函数的情况下实现闭包呢?
可以的,可以使用装饰器来实现这一功能。顾名思义,装饰器是起装饰作用的东西,它并不改动装饰体的内容。给 hello 函数加个装饰器,如下:
def hello(): print("Hello World") def helloDecorator(func): print("This is a demo of decorator") def wrapper(*args, **kw): return func(*args, **kw) return wrapper lianhuasheng = helloDecorator(hello) print(lianhuasheng.__name__) >>> This is a demo of decorator >>> wrapper
通过向 helloDecorator 函数传入函数名 hello 来调用 hello 函数,实际的 hello 函数并未改动。
值得注意的是,引用 lianhuasheng “指向”的函数是 wrapper 函数,所以它的函数名是 wrapper。
对于这句 lianhuasheng = helloDecorator(hello) 也可将它写成 hello = helloDecorator(hello),python 在函数定义处加上 @helloDecorator 来表示这条语句,即 helloDecorator 是个装饰器。
def helloDecorator(func): print("This is a demo of decorator") def wrapper(*args, **kw): return func(*args, **kw) return wrapper @helloDecorator def hello(): print("Hello World") print(hello.__name__) >>> This is a demo of decorator >>> wrapper
注意引用 hello 的函数名是 wrapper!
类似的还有带参数的装饰器,这里不介绍了。
装饰器在类里是什么样呢?
装饰器可以用在函数中。同样的,它也可以用在类里。在类中的装饰器叫做静态方法和类成员方法。
静态方法和类成员方法:
class Demo: name = "None" def __init__(self): self.name = Demo.name print("A demo of staticmethod and classmethod") @staticmethod def printName(name): print("My name is {}".format(name)) @classmethod def inputName(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 staticmethod and classmethod My name is lianhuasheng <class '__main__.Demo'> None lianhuasheng <__main__.Demo object at 0x00AF4E80> <class '__main__.Demo'> lianhua lianhuasheng <__main__.Demo object at 0x00AF4E80> <class '__main__.Demo'> My name is lianhuasheng <class '__main__.Demo'> lianhua lianhuasheng <__main__.Demo object at 0x00AF4E80> <class '__main__.Demo'> My name is lianhuasheng My name is lianhuasheng
从上例可以看出:
-
类中,在定义前分别加上 @staticmethod 和 @classmethod 表示静态方法和类成员方法。
-
不管是静态方法和类成员方法都能被类实例和类访问。
-
静态方法不能修改类变量和类实例变量,且它接受的参数非 self /非 cls。相当于是定义在类中的函数。
-
类成员方法可以修改类变量,但是不能访问类实例变量。它传入的 cls 参数实际上是类,在上例中是 <class '__main__.Demo'>。
-
修改类实例变量的值并不会改变类变量,同样的修改类变量也不会改变类实例变量的值。
(完)