枚举其实是一个类

在很多语言中,枚举都是一个类型,Enum作为关键字;但是Python中枚举是一个类,如下定义:

from enum import Enum

class VIP(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = '1'
    RED = '2'

print(VIP.YELLOW)        #并不会打印出数字,如果是数字,与一般类中的变量定义没有区别

#VIP.YELLOW

关注的是它的标签不是数字

枚举和普通类相比有什么优势

目前,对于枚举类型,我们有如下三种方式表示:

yellow = 1        #变量赋值
green = 2

{'yellow':1, 'green':2}    #字典

class TypeDiamond():        #普通类
    yellow = 1
    green = 2

字典、普通类与枚举类相比缺陷:

  1. 可变
  2. 没有防止相同值的功能

但是枚举类可以不可变、防止相同

from enum import Enum

class VIP(Enum):
    YELLOW = 1
    YELLOW= 2   #不可重复,报错
    BLACK = 3
    RED = 4

print(VIP.YELLOW)
VIP.YELLOW = 6    #不可更改,报错

枚举类型、枚举名称与枚举值

样例如下:

from enum import Enum

class VIP(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = 3
    RED = 4

print(VIP.YELLOW.value)     #value属性得到数字
print(VIP.YELLOW)           #代表枚举下面的一个类型
print(VIP.YELLOW.name)      #name属性得到一个名字,字符串
print(type(VIP.YELLOW))     #<enum 'VIP'>
print(type(VIP.YELLOW.name))    #<class 'str'>
print(VIP['YELLOW'])

#1                #枚举的值
#VIP.YELLOW       #枚举类型
#YELLOW           #枚举的名字
#<enum 'VIP'>
#<class 'str'
#VIP.YELLOW        #枚举类型

枚举可以遍历:

from enum import Enum

class VIP(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = 3
    RED = 4

for x in VIP:
    print(x)

#VIP.YELLOW
#VIP.GREEN
#VIP.BLACK
#VIP.RED

枚举的比较运算 

同一个枚举类中的关系运算符==、身份运算符is如下:

from enum import Enum

class VIP(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = 3
    RED = 4

result = VIP.YELLOW == VIP.GREEN    
print(result)   #False
result = VIP.YELLOW == VIP.YELLOW
print(result)   #True
result = VIP.YELLOW == 2
print(result)   #False
result = VIP.YELLOW is VIP.YELLOW       #支持身份运算符
print(result)   #True

#result = VIP.YELLOW > VIP.GREEN     #报错

不同枚举类的比较:

from enum import Enum

class VIP(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = 3
    RED = 4

class VIP1(Enum):        #所有的枚举类都是Enum的子类
    YELLOW = 1          #枚举的意义重在前面的标签,而不再后面的数字
    GREEN = 2
    BLACK = 3
    RED = 4

result = VIP.YELLOW == VIP1.YELLOW  
print(result)   #False数值相同,但是不是同一个枚举类

枚举注意事项

枚举类中两个类型的值相同时,第二个是第一个的别名:

from enum import Enum

class VIP(Enum):        
    YELLOW = 1          #枚举类中允许两个类型的值相等
    GREEN = 1           #这里的GREEN 就是 YELLOW的别名,最好写成 YELLOW_ALIAS = 1
    BLACK = 3
    RED = 4

print(VIP.GREEN)        

#VIP.YELLOW

对于有别名的枚举类,for循环时,不会显示别名:

from enum import Enum

class VIP(Enum):        
    YELLOW = 1          #枚举类中允许两个类型的值相等
    YELLOW_ALIAS = 1          #这里的GREEN 就是 YELLOW的别名,最好写成 YELLOW_ALIAS = 1
    BLACK = 3
    RED = 4

for v in VIP:
    print(v)

#VIP.YELLOW
#VIP.BLACK
#VIP.RED

Python中有一种机制,可以在for遍历中打印别名:

from enum import Enum

class VIP(Enum):        
    YELLOW = 1          #枚举类中允许两个类型的值相等
    YELLOW_ALIAS = 1          #这里的GREEN 就是 YELLOW的别名,最好写成 YELLOW_ALIAS = 1
    BLACK = 3
    RED = 4

for v in VIP.__members__:        #打印别名,简洁版
    print(v)

for v in VIP.__members__.items():        #完整版
    print(v)

#YELLOW
#YELLOW_ALIAS
#BLACK
#RED
# ('YELLOW', <VIP.YELLOW: 1>)
# ('YELLOW_ALIAS', <VIP.YELLOW: 1>)
# ('BLACK', <VIP.BLACK: 3>)
# ('RED', <VIP.RED: 4>)

枚举转换

在数据库里一般存储数值或者标签名字来代表枚举类型,推荐存储数值,数字占用的空间更小。

但是不建议在代码中用数值代表枚举,可读性不强。

如何将数字转化为枚举类型:

from enum import Enum

class VIP(Enum):        
    YELLOW = 1       
    YELLOW_ALIAS = 1      
    BLACK = 3
    RED = 4

a = 1
print(VIP(a))        #枚举类VIP() 就可以转换

#VIP.YELLOW

枚举小结

Enum 与 IntEnum 区别:

from enum import Enum
from enum import IntEnum

class VIP(Enum):         #不会强制要求数值必须为int   
    YELLOW = 1     
    GREEN = 'str'       #不会报错
    BLACK = 3
    RED = 4

class VIP1(IntEnum):         #强制要求数值必须为int   
    YELLOW = 1     
    GREEN = 'str'       #报错
    BLACK = 3
    RED = 4

限制不同的枚举类型不能取相同的值:unique装饰器

from enum import Enum
from enum import IntEnum,unique

class VIP(Enum):          
    YELLOW = 1     
    GREEN = 1  
    BLACK = 3
    RED = 4

@unique         #添加装饰器:不允许枚举类中的数值相同
class VIP1(Enum):          
    YELLOW = 1     
    GREEN = 1       #报错
    BLACK = 3
    RED = 4

枚举类不可实例化,也是没有意义的,枚举类属于单例模式(23种设计模式之一)

进阶内容开场白

业务逻辑的开发者:不考虑太多的封装性

包和类库的开发者:要考虑封装性,需要进阶知识的学习

一切皆对象

函数式编程 并没有标准定义,如果代码非常繁琐则考虑使用。

学习闭包的概念,不是python独有的。

函数:其他大多数语言中的函数只是一段可执行的代码,并不是对象,不可以实例化。但是在 python中的函数是对象,一切皆对象

可以把函数赋值给变量;甚至可以把函数当做另外一个函数的参数传递或者当做一个返回值返回

def a():
    pass

print(type(a))

#<class 'function'>

什么是闭包

闭包只是函数式编程的体现之一

引例1:

def curve_pre():
    def curve():
        print('This is a function')
    return curve    #函数可以作为返回值返回

f = curve_pre()     #函数可以赋值给一个变量
f()                 #调用函数

#This is a function

 引例2:

def curve_pre():
    a = 25
    def curve(x):
        return a * x *x     #返回值
    return curve    #返回函数curve

f = curve_pre()     #将curve_pre函数的返回值赋给变量f
print(f(2))                 #调用f,即就是调用 curve函数

#100

闭包现象:

def curve_pre():
    a = 25          #环境变量,闭包情况下,a 的赋值必须在 curve_pre 中
    def curve(x):
        return a * x *x     
    return curve   #返回的是闭包函数,就是环境变量+curve 函数一起返回了

a = 10
f = curve_pre()    
print(f(2))   

#100        #注意这里并没有返回40

闭包 = 函数 + 环境变量(函数及其外部环境变量所构成的整体叫做闭包)

同时 环境变量必须在函数外部,但不能是全局变量:

a = 25    #a定义为了全局变量
def curve_pre():
    def curve(x): #接受抛物线的x值
        return a * x * x
    return curve #返回一个函数

a = 10
f = curve_pre()
print(f(2)) #调用curve()函数

#40    #a的值被改变了

查看闭包:

def curve_pre():
    a = 25 #局部变量在curve的外部
    def curve(x): #接受抛物线的x值
        return a * x * x
    return curve #返回一个函数

a = 10
f = curve_pre()
print(f.__closure__)        #查看闭包函数
print(f.__closure__[0].cell_contents)        #查看闭包的环境变量
print(f(2)) #调用curve()函数

#(<cell at 0x0031AAF0: int object at 0x0FF93A80>,)
#25    #这里是a的值
#100

一个事例看看闭包 

闭包的意义:保存函数当时执行时的环境,也就是保存现场

因此环境变量:必须是函数定义时的外部变量,但不是全局的

局部变量与外部变量区别:

def f1():
    a = 10
    def f2():
        a = 20  #被python认为是局部变量,不可能影响外部变量
        print(a)
    print(a)        #10
    f2()            #20
    print(a)        #10

f1()

#10
#20
#10

闭包的环境变量理解:

def f1():
    a = 10
    def f2():
        a = 20     #如果在 f2 内部定义a,a会被python认为是一个局部变量,此时就不能构成闭包
        return a
    return f2         

f = f1()
print(type(f))
print(f.__closure__) #查看时返回None

#<class 'function'>
#None

应该改为:

def f1():
    a = 10
    def f2():
        #a = 20     #如果在 f2 内部定义a,a会被python认为是一个局部变量,此时就不能构成闭包
        return a
    return f2         

f = f1()
print(type(f))
print(f.__closure__)

#<class 'function'>
#(<cell at 0x0000022D66295678: int object at 0x0000000066D16C20>,)

环境变量不能当作一个变量去赋值,而是一定要去引用外部。

用闭包解决问题

闭包不是必不可少的东西,只是可以使你的代码架构更加合理。

旅行者:计算旅行者的路径长度;一维情况下,起点 x = 0, 走3步,则 result = 3,继续走5步, result = 8;注意要保存上一次的结果

非闭包方法:

origin = 0    #这里origin是全局变量

def go(step):
    new_pos = origin + step    #报错
    origin = new_pos    #这里把 origin 定义为局部变量
    return origin

print(go(2))
print(go(3))
print(go(6))

错误原因:在函数内部等号左边出现变量名时,python就认为是它是一个局部变量,此时执行时不会再去外部寻找全局变量

如果取消局部变量的定义,就不会报错,但此时逻辑不符合题意:

origin = 0

def go(step):
    new_pos = origin + step
    #origin = new_pos
    return origin

print(go(2))
print(go(3))
print(go(6))

#0
#0
#0

因此,关键问题在于:函数内部定义的 origin 也要视为全局变量

global 关键字申明即可实现:

origin = 0

def go(step):
    global origin
    new_pos = origin + step
    origin = new_pos
    return origin

print(go(2))
print(go(3))
print(go(6))

#2
#5
#11

闭包方法解决:

origin = 0

def factory(pos):        #工厂模式
    def go(step):
        nonlocal pos            #强制定义 pos 不是一个本地变量
        new_pos = pos + step    #如果没有上一步,这里的 pos 依旧会报错,原因和非闭包方法一样
        pos = new_pos    
        return new_pos
    return go

tourist = factory(origin)
print(tourist(2))
print(tourist(3))
print(tourist(6))

#2
#5
#11

闭包方法的优势:并没有改变全局变量origin的值

小谈函数式编程

python和Javascript都会使用闭包。

python中使用闭包强调的是环境变量;JavaScript 中对于闭包的切入点在于 在函数外部间接调用函数内部的变量。

闭包的使用 极易造成内存泄漏。

对于这个问题,也可以使用面向对象编程的思想:类变量来存储旅行者的路程