Python 学习笔记: 从变量到装饰器


从变量开始

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'>。
  • 修改类实例变量的值并不会改变类变量,同样的修改类变量也不会改变类实例变量的值。
 
 
 
(完)
原文地址:https://www.cnblogs.com/xingzheanan/p/13040858.html