Python类和对象

目录

class:定义类

类的定义

__init__()类构造方法

类对象的创建和使用

类的实例化

类对象的使用

self用法详解

类变量和实例变量(类属性和实例属性)

类变量(类属性)

实例变量(实例属性)

局部变量

实例方法、静态方法和类方法详解

类实例方法

类方法

类静态方法

描述符

property()函数:定义属性

@property装饰器详解

封装机制及实现方法

类如何进行封装

Python继承机制及其使用

关于Python的多继承同名类

父类方法重写

调用被重写的方法

super()函数:调用父类的构造方法

super()使用注意事项(包含新式类和旧式类的区别)

__slots__:限制类实例动态添加属性和方法

type()函数:动态创建类

MetaClass元类详解

多态及用法详解

枚举类定义和使用


class:定义类

Python 程序中类的使用顺序是这样的:

  1. 创建(定义)类,也就是制作图纸的过程;
  2. 创建类的实例对象(根据图纸造出实际的物品),通过实例对象实现特定的功能。

类的定义

Python 中定义一个类使用 class 关键字实现,其基本语法格式如下:

class 类名:
    多个(≥0)类属性...
    多个(≥0)类方法...

给类起好名字之后,其后要跟有冒号(:),表示告诉 Python 解释器,下面要开始设计类的内部功能了,也就是编写类属性和类方法。

Python 类是由类头(class 类名)和类体(统一缩进的变量和函数)构成。

class TheFirstDemo:

    '''这是一个学习Python定义的第一个类'''
    # 下面定义了一个类属性

    add = 'www.baidu.com'

    # 下面定义了一个say方法
    def say(self, content):
        print(content)

事实上,我们完全可以创建一个没有任何类属性和类方法的类,换句话说,Python 允许创建空类:

class Empty:
    pass

__init__()类构造方法

在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。

构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。Python 类中,手动添加构造方法的语法格式如下:

def __init__(self,...):
    代码块

注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义.

另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。也就是说,类的构造方法最少也要有一个 self 参数。

class TheFirstDemo:

    '''这是一个学习Python定义的第一个类'''
    #构造方法
    def __init__(self):
        print("调用构造方法")

    # 下面定义了一个类属性
    add = 'www.baidu.com'

    # 下面定义了一个say方法
    def say(self, content):
        print(content)

注意,即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法。

仅包含 self 参数的 __init__() 构造方法,又称为类的默认构造方法。

在上面代码的后面,顶头(不缩进)直接添加如下代码:

zhangsan = TheFirstDemo()

这行代码的含义是创建一个名为 zhangsan 的 TheFirstDemo 类对象。运行代码可看到如下结果:

调用构造方法

显然,在创建 zhangsan 这个对象时,隐式调用了我们手动创建的 __init__() 构造方法。

不仅如此,在 __init__() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割。例如,下面的代码在创建 __init__() 方法时,额外指定了 2 个参数:

class Website:

    '''这是一个学习Python定义的一个类'''
    def __init__(self,name,add):
        print(name,"的网址为:",add)

#创建 add 对象,并传递参数给构造函数
add = Website("百度","www.baidu.com")

结果:
百度 的网址为: www.baidu.com

可以看到,虽然构造方法中有 self、name、add 3 个参数,但实际需要传参的仅有 name 和 add,也就是说,self 不需要手动传递参数。

类对象的创建和使用

类的实例化

对已定义好的类进行实例化,其语法格式如下:

类名(参数)

定义类时,如果没有手动添加 __init__() 构造方法,又或者添加的 __init__() 中仅有一个 self 参数,则创建类对象时的参数可以省略不写。

class Website :

    def __init__(self,name,add):
        #下面定义 2 个实例变量
        self.name = name
        self.add = add
        print(name,"网址为:",add)

    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)

website = Website("谷歌","www.google.com")

结果:
谷歌 网址为: www.google.com

类对象的使用

使用已创建好的类对象访问类中实例变量的语法格式如下:

类对象名.变量名

使用类对象调用类中方法的语法格式如下:

对象名.方法名(参数)

注意,对象名和变量名以及方法名之间用点 "." 连接。

self用法详解

在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。例如,定义一个 Person 类:

class Person:

    def __init__(self):
        print("正在执行构造方法")

    # 定义一个study()实例方法
    def study(self,name):
        print(name,"正在学Python")

对于构造函数中的 self 参数,其代表的是当前正在初始化的类对象。

class Person:

    name = "xxx"

    def __init__(self,name):
        self.name=name

zhangsan = Person("zhangsan")
print(zhangsan.name)

lisi = Person("lisi")
print(lisi.name)

结果:
zhangsan
lisi

除了类对象可以直接调用类方法,还有一种函数调用的方式

class Person:

    def who(self):
        print(self)

zhangsan = Person()

#第一种方式
zhangsan.who()

#第二种方式
who = zhangsan.who
who()#通过 who 变量调用zhangsan对象中的 who() 方法

结果:
<__main__.Person object at 0x0000025C26F021D0>
<__main__.Person object at 0x0000025C26F021D0>

类变量和实例变量(类属性和实例属性)

无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。我们可以将类看做一个独立的空间,则类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。在类体中,根据变量定义的位置不同,以及定义的方式不同,类属性又可细分为以下 3 种类型:

  1. 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量;
  2. 类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量;
  3. 类体中,所有函数内部:以“变量名=变量值”的方式定义的变量,称为局部变量。

类方法也可细分为实例方法、静态方法和类方法。

类变量(类属性)

类变量指的是在类中,但在各个类方法外定义的变量。

class Website :

    # 下面定义了2个类变量
    name = "百度"
    add = "www.baidu.com"

    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)

print(Website.name)
print(Website.add)

site = Website()
site.name = "谷歌"
site.add = "www.google.com"

print(site.name)
print(site.add)

结果:
百度
www.baidu.com
谷歌
www.google.com

上面程序中,name 和 add 就属于类变量。

类变量的特点是,所有类的实例化对象都同时共享类变量,也就是说,类变量在所有实例化对象中是作为公用资源存在的。类方法的调用方式有 2 种,既可以使用类名直接调用,也可以使用类的实例化对象调用。

通过类名修改类变量,会作用到所有的实例化对象。

除了可以通过类名访问类变量之外,还可以动态地为类和对象添加类变量。

clang = CLanguage()
CLanguage.catalog = 13

print(clang.catalog)

结果:
13

实例变量(实例属性)

实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。

class Website :

    def __init__(self):
        self.name = "百度"
        self.add = "www.baidu.com"

    # 下面定义了一个say实例方法
    def say(self):
        self.name = "谷歌"
        self.add = "www.google.com"

site = Website()

print(site.name," ",site.add)

site.say()
print(site.name," ",site.add)

结果:
百度   www.baidu.com
谷歌   www.google.com

类对象是无法修改类变量的值,和类变量不同,通过某个对象修改实例变量的值,不会影响类的其它实例化对象,更不会影响同名的类变量。

class Website :
    defaultname = "defaultname"  #类变量
    defaultadd = "defaultadd"  #类变量
    def __init__(self):
        self.name = "initname"  #实例变量
        self.add = "initadd"  #实例变量
    # 下面定义了一个say实例方法
    def say(self):
        self.name = "sayname"  #实例变量
        self.add = "sayadd"  #实例变量

site1 = Website()
print(site1.name," ",site1.add)

#修改对象的实例变量
site1.name = "initname改变"
site1.add = "initadd改变"
print(site1.name," ",site1.add)
#修改对象的类变量
site1.defaultname = "defaultname改变"
site1.defaultadd = "defaultadd改变"
print(site1.defaultname," ",site1.defaultadd)

site2 = Website()
#不会影响类的其它类对象
print(site2.defaultname," ",site2.defaultadd)
#不会影响类的其它实例化对象
print(site2.name," ",site2.add)

结果:
initname   initadd

initname改变   initadd改变

defaultname改变   defaultadd改变

defaultname   defaultadd
initname   initadd

局部变量

局部变量直接以“变量名=值”的方式进行定义。通常情况下,定义局部变量是为了所在类方法功能的实现。需要注意的一点是,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁。

实例方法、静态方法和类方法详解

和类属性一样,类方法也可以进行更细致的划分,具体可分为类方法、实例方法和静态方法。

采用 @classmethod 修饰的方法为类方法;采用 @staticmethod 修饰的方法为静态方法;不用任何修改的方法为实例方法。

类实例方法

通常情况下,在类中定义的方法默认都是实例方法(上面定义的都是)。不仅如此,类的构造方法理论上也属于实例方法,只不过它比较特殊。

class Test:
    
    # 类构造方法,也属于实例方法
    def __init__(self):
        print("调用的为init构造函数")

    # 下面定义了一个say实例方法
    def say(self):
        print("调用的为say实例方法")

#调用test中的构造函数
test = Test()
print(test)

#调用test中的实力方法
Test.say(test)

#调用test中的实力方法
test.say()
print(test)

结果:
调用的为init构造函数
<__main__.Test object at 0x000002111832EC88>

调用的为say实例方法

调用的为say实例方法
<__main__.Test object at 0x000002111832EC88>

类方法

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。

和实例方法最大的不同在于,类方法需要使用@classmethod修饰符进行修饰

class Test:

    # 类构造方法,也属于实例方法
    def __init__(self):
        print("调用的为init构造函数")

    # 下面定义了一个say实例方法
    def say(self):
        print("调用的为say实例方法")

    #如果没有 @classmethod,则 Python 解释器会将 info() 方法认定为实例方法,而不是类方法。
    #类方法
    @classmethod
    def info(cls):
        print("调用的为info类方法,cls:",cls)

#使用类名直接调用类方法
Test.info()
#使用类对象调用类方法
clang = Test
clang.info()

结果:
调用的为info类方法,cls: <class '__main__.Test'>
调用的为info类方法,cls: <class '__main__.Test'>

类静态方法

静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

静态方法需要使用@staticmethod修饰:

class Test:

    # 类构造方法,也属于实例方法
    def __init__(self):
        print("调用的为init构造函数")

    # 下面定义了一个say实例方法
    def say(self):
        print("调用的为say实例方法")

    #如果没有 @classmethod,则 Python 解释器会将 info() 方法认定为实例方法,而不是类方法。
    #类方法
    @classmethod
    def info(cls):
        print("调用的为info类方法,cls:",cls)

    #类静态方法
    @staticmethod
    def content(name):
        print("调用类静态方法",name)


#使用类名直接调用静态方法
Test.content("test1")
#使用类对象调用静态方法
test = Test
test.content(123)

结果:
调用类静态方法 test1
调用类静态方法 123

在实际编程中,几乎不会用到类方法和静态方法,因为我们完全可以使用函数代替它们实现想要的功能,但在一些特殊的场景中(例如工厂模式中),使用类方法和静态方法也是很不错的选择。

描述符

通过使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。

 

一个类可以将属性管理全权委托给描述符类。

描述符是 Python 中复杂属性访问的基础,它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。

描述符类基于以下 3 个特殊方法,换句话说,这 3 个方法组成了描述符协议:

  • __set__(self, obj, type=None):在设置属性时将调用这一方法(本节后续用 setter 表示);
  • __get__(self, obj, value):在读取属性时将调用这一方法(本节后续用 getter 表示);
  • __delete__(self, obj):对属性调用 del 时将调用这一方法。

其中,实现了 setter 和 getter 方法的描述符类被称为数据描述符;

反之,如果只实现了 getter 方法,则称为非数据描述符。

实际上,在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __getattr__() 弄混)。也就是说,每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:

  1. 验证该属性是否为类实例对象的数据描述符;
  2. 如果不是,就查看该属性是否能在类实例对象的 __dict__ 中找到;
  3. 最后,查看该属性是否为类实例对象的非数据描述符。
#描述符类
class revealAccess:

    def __init__(self, initval = None, name = 'var'):
        self.name = name
        self.val = initval

    def __get__(self, obj, objtype):
        print("__get__",self.name)
        return self.val

    def __set__(self, obj, val):
        print("__set__",self.name)
        self.val = val

class myClass:
    x = revealAccess(10,'var "x"')
    y = 5

m = myClass()

print("m.x:",m.x) #每次查找这个属性时,都会调用描述符的 __get__() 方法
结果:
__get__ var "x"
m.x: 10


m.x = 20 #每次在对该属性赋值时,也会调用 __set__() 方法
结果:
__set__ var "x"


print("m.x:",m.x) #每次查找这个属性时,都会调用描述符的 __get__() 方法
结果:
__get__ var "x"
m.x: 20


print("m.y:",m.y) #每次查找这个属性时,都会调用描述符的 __get__() 方法
结果:
m.y: 5

如果一个类的某个属性有数据描述符,那么每次查找这个属性时,都会调用描述符的 __get__() 方法,并返回它的值;同样,每次在对该属性赋值时,也会调用 __set__() 方法。

注意,虽然上面例子中没有使用 __del__() 方法,但也很容易理解,当每次使用 del 类对象.属性(或者 delattr(类对象,属性))语句时,都会调用该方法。

除了使用描述符类自定义类属性被调用时做的操作外,还可以使用 property() 函数或者 @property 装饰器。

property()函数:定义属性

正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。

因此,在不破坏类封装原则的基础上,为了能够有效操作类中的属性,类中应包含读(或写)类属性的多个 getter(或 setter)方法,这样就可以通过“类对象.方法(参数)”的方式操作属性

class Test:
    #构造函数
    def __init__(self,name):
        self.name = name
    #设置 name 属性值的函数
    def setname(self,name):
        self.name = name
    #访问nema属性值的函数
    def getname(self):
        return self.name
    #删除name属性值的函数
    def delname(self):
        self.name="xxx"

test = Test("测试文字")

#获取name属性值
print(test.getname())

#设置name属性值
test.setname("更改!!")
print(test.getname())

#删除name属性值
test.delname()
print(test.getname())

结果:
测试文字
更改!!
xxx

这种操作类属性的方式比较麻烦

Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。

property() 函数的基本使用格式如下:

属性名 = property ( fget = None ,  fset = None ,  fdel = None ,  doc = None )

其中,fget 参数用于指定获取该属性值的类方法,

fset 参数用于指定设置该属性值的方法,

fdel 参数用于指定删除该属性值的方法,

doc 是一个文档字符串,用于说明此函数的作用。

注意,在使用 property() 函数时,以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个,当前也可以全部指定。也就是说,property() 函数中参数的指定并不是完全随意的。

class Test:
    # 构造函数
    def __init__(self, n):
        self.__name = n

    # 设置 name 属性值的函数
    def setname(self, n):
        self.__name = n

    # 访问nema属性值的函数
    def getname(self):
        return self.__name

    # 删除name属性值的函数
    def delname(self):
        self.__name = "xxx"

    # 为name 属性配置 property() 函数
    name = property(getname, setname, delname, '指明出处')

#调取说明文档的 2 种方式
#print(CLanguage.name.__doc__)
help(Test.name)

test = Test("测试文字")  #init

#调用 getname() 方法
print(test.name)

#调用 setname() 方法
test.name="改变!!!"  #set
print(test.name)

#调用 delname() 方法
del test.name #del
print(test.name)


结果:
Help on property:

    指明出处

测试文字
改变!!!
xxx

注意,在此程序中,由于 getname() 方法中需要返回 name 属性,如果使用 self.name 的话,其本身又被调用 getname(),这将会先入无限死循环。为了避免这种情况的出现,程序中的 name 属性必须设置为私有属性,即使用 __name(前面有 2 个下划线)。

当然,property() 函数也可以少传入几个参数。以上面的程序为例,我们可以修改 property() 函数如下所示

name = property(getname, setname)

这意味着,name 是一个可读写的属性,但不能删除,因为 property() 函数中并没有为 name 配置用于函数该属性的方法。

也就是说,即便 类中设计有 delname() 函数,这种情况下也不能用来删除 name 属性。 

同理,还可以像如下这样使用 property() 函数:

name = property(getname)    # name 属性可读,不可写,也不能删除
name = property(getname, setname,delname)    #name属性可读、可写、也可删除,就是没有说明文档

@property装饰器详解

既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性,除了使用 property() 函数,Python 还提供了 @property 装饰器。

通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。

@property 的语法格式如下:

@property
def 方法名(self)
    代码块

例如,定义一个矩形类,并定义用 @property 修饰的方法操作类中的 area 私有属性,代码如下:

class Rect:
    def __init__(self,area):
        print("init")
        self.__area = area
    @property
    def area(self):
        print("area")
        return self.__area

rect = Rect(30)

#直接通过方法名来访问 area 方法
print("---")

print("矩形的面积是:",rect.area)

结果:
init
---
area
矩形的面积是: 30

上面程序中,使用 @property 修饰了 area() 方法,这样就使得该方法变成了 area 属性的 getter 方法。需要注意的是,如果类中只包含该方法,那么 area 属性将是一个只读属性。

也就是说,在使用 Rect 类时,无法对 area 属性重新赋值,即运行如下代码会报错:

class Rect:
    def __init__(self,area):
        print("init")
        self.__area = area
    @property
    def area(self):
        print("area")
        return self.__area

rect = Rect(30)
#直接通过方法名来访问 area 方法
print("---")
print("矩形的面积是:",rect.area)
rect.area = 90
print("修改后的面积:",rect.area)


结果:
Traceback (most recent call last):
  File "D:/PycharmProjects/pythonProject/main.py", line 14, in <module>
    rect.area = 90
AttributeError: can't set attribute
init
---
area
矩形的面积是: 30

而要想实现修改 area 属性的值,还需要为 area 属性添加 setter 方法,就需要用到 setter 装饰器,它的语法格式如下:

@方法名.setter
def 方法名(self, value):
    代码块

为 Rect 类中的 area 方法添加 setter 方法,代码如下:

class Rect:
    def __init__(self,area):
        print("init")
        self.__area = area
    @property
    def area(self):
        print("area")
        return self.__area

    @area.setter
    def area(self, value):
        print("area.setter")
        self.__area = value

rect = Rect(30)

#直接通过方法名来访问 area 方法

print("---")

print("矩形的面积是:",rect.area)

rect.area = 90

print("修改后的面积:",rect.area)

结果:
init
---
area
矩形的面积是: 30
area.setter
area
修改后的面积: 90

这样,area 属性就有了 getter 和 setter 方法,该属性就变成了具有读写功能的属性。

除此之外,还可以使用 deleter 装饰器来删除指定属性,其语法格式为:

@方法名.deleter
def 方法名(self):
    代码块

给 area() 方法添加 deleter 方法,实现代码如下:

class Rect:
    def __init__(self,area):
        print("init")
        self.__area = area
    @property
    def area(self):
        print("area")
        return self.__area

    @area.setter
    def area(self, value):
        print("area.setter")
        self.__area = value

    @area.deleter
    def area(self):
        print("area.deleter")
        self.__area = 0

rect = Rect(30)

#直接通过方法名来访问 area 方法
print("---")
print("矩形的面积是:",rect.area)

rect.area = 90
print("修改后的面积:",rect.area)

del rect.area
print("删除后的area值为:",rect.area)

结果:
init
---
area
矩形的面积是: 30
area.setter
area
修改后的面积: 90
area.deleter
area
删除后的area值为: 0

封装机制及实现方法

首先,封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。

除此之外,对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,我们只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松实现用户对类中属性或方法的不合理操作。

并且,对类进行良好的封装,还可以提高代码的复用性。

类如何进行封装

Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),这 2 种属性的区别如下:

  • public:公有属性的类变量和类函数,在类的外部、类内部以及子类(后续讲继承特性时会做详细介绍)中,都可以正常访问;
  • private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。


但是,Python 并没有提供 public、private 这些修饰符。为了实现类的封装,Python 采取了下面的方法:

  • 默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前都没有下划线(_)
  • 如果类中的变量和函数,其名称以双下划线“__”开头,则该变量(函数)为私有变量(私有函数),其属性等同于 private。


除此之外,还可以定义以单下划线“_”开头的类属性或者类方法(例如 _name、_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。

注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。

class Format:
    def setname(self, name):
        if len(name) < 3:
            raise ValueError('名称长度必须大于3!')
        self.__name = name

    def getname(self):
        return self.__name

    # 为 name 配置 setter 和 getter 方法
    name = property(getname, setname)

    def setadd(self, add):
        if add.startswith("www.") and add.endswith(".com") :
            self.__add = add
        else:
            raise ValueError('地址必须以 www. 开头 com. 结尾')

    def getadd(self):
        return self.__add

    # 为 add 配置 setter 和 getter 方法
    add = property(getadd, setadd)

    # 定义个私有方法
    def __display(self):
        print(self.__name, self.__add)


format = Format()

format.name = "baidu" #setname
format.add = "www.baidu.com" #setadd
print(format.name)
print(format.add)

结果:
baidu
www.baidu.com

上面程序中, 将 name 和 add 属性都隐藏了起来,但同时也提供了可操作它们的“窗口”,也就是各自的 setter 和 getter 方法,这些方法都是公有(public)的。

不仅如此,以 add 属性的 setadd() 方法为例,通过在该方法内部添加控制逻辑,即通过调用 startswith() 方法,控制用户输入的地址必须以“www.”开头".com"结尾,否则程序将会执行 raise 语句抛出 ValueError 异常。

通过此程序的运行逻辑不难看出,通过对 CLanguage 类进行良好的封装,使得用户仅能通过暴露的 setter() 和 getter() 方法操作 name 和 add 属性,而通过对 setname() 和 setadd() 方法进行适当的设计,可以避免用户对类中属性的不合理操作,从而提高了类的可维护性和安全性。

Format类中还有一个 __display() 方法,由于该类方法为私有(private)方法,且该类没有提供操作该私有方法的“窗口”,因此我们无法在类的外部使用它。换句话说,如下调用 __display() 方法是不可行的:

#尝试调用私有的 display() 方法
clang.__display()

结果:
Traceback (most recent call last):
  File "D:\python3.6\1.py", line 33, in <module>
    clang.__display()
AttributeError: 'CLanguage' object has no attribute '__display'

正确使用私有方法  :

class Format:
    def __init__(self,name):
        self.name=name
        self.__display()

    # 定义个私有方法
    def __display(self):
        print("私有方法:",self.name)


format = Format('zzz')
format.name='111'
print(format.name)

结果:
私有方法: zzz
111

Python继承机制及其使用

继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。也就是说,通过使用继承这种机制,可以轻松实现类的重复使用。

一下为两者的比较:

#两个函数都要实现 draw()
class Shape:
    def draw(self,content):
        print("画",content)

class Form:
    def draw(self,content):
        print("画",content)
    def area(self):
        #....
        print("此图形的面积为...")

继承:

class Shape:
    def draw(self,content):
        print("画",content)

class Form(Shape):
    def area(self):
        #....
        print("此图形的面积为...")

上面代码中,class From(Shape) 就表示 From 继承 Shape。From 是子类,Shape 是父类。

子类继承父类时,只需在定义子类时,将父类(可以是多个)放在子类之后的圆括号里即可。语法格式如下:

class 类名(父类1, 父类2, ...):
    #类定义部分

注意,如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)。另外,Python 的继承是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类。


注意,可能还听说过“派生”这个词汇,它和继承是一个意思,只是观察角度不同而已。换句话话,继承是相对子类来说的,即子类继承自父类;而派生是相对于父类来说的,即父类派生出子类。

class People:
    def say(self):
        print("我是一个人,名字是:",self.name)

class Animal:
    def display(self):
        print("人也是高级动物")

#同时继承 People 和 Animal 类
#其同时拥有 name 属性、say() 和 display() 方法
class Person(People, Animal):
    pass

zhangsan = Person()
zhangsan.name = "张三"

zhangsan.say()
zhangsan.display()

结果:
我是一个人,名字是: 张三
人也是高级动物

可以看到,虽然 Person 类为空类,但由于其继承自 People 和 Animal 这 2 个类,因此实际上 Person 并不空,它同时拥有这 2 个类所有的属性和方法。

子类拥有父类所有的属性和方法,即便该属性或方法是私有(private)的。

关于Python的多继承同名类

对于这种情况,Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。

class People:
    def __init__(self):
        self.name = People
    def say(self):
        print("People类",self.name)

class Animal:
    def __init__(self):
        self.name = Animal
    def say(self):
        print("Animal类",self.name)

#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):
    pass

zhangsan = Person()
zhangsan.name = "张三"
zhangsan.say()

结果:
People类 张三

可以看到,当 Person 同时继承 People 类和 Animal 类时,People 类在前,因此如果 People 和 Animal 拥有同名的类方法,实际调用的是 People 类中的。

父类方法重写

子类继承了父类,那么子类就拥有了父类所有的类属性和类方法。通常情况下,子类会在此基础上,扩展一些新的类属性和类方法。

子类从父类继承得来的类方法中,大部分是适合子类使用的,但有个别的类方法,并不能直接照搬父类的,如果不对这部分类方法进行修改,子类对象无法使用。针对这种情况,我们就需要在子类中重复父类的方法。

举个例子,鸟通常是有翅膀的,也会飞,因此我们可以像如下这样定义个和鸟相关的类:

class Bird:

    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")

    #鸟会飞
    def fly(self):
        print("鸟会飞")

但是,对于鸵鸟来说,它虽然也属于鸟类,也有翅膀,但是它只会奔跑,并不会飞。针对这种情况,可以这样定义鸵鸟类:

class Ostrich(Bird):

    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟是鸟却不会飞")

可以看到,因为 Ostrich 继承自 Bird,因此 Ostrich 类拥有 Bird 类的 isWing() 和 fly() 方法。其中,isWing() 方法同样适合 Ostrich,但 fly() 明显不适合,因此我们在 Ostrich 类中对 fly() 方法进行重写。

重写,有时又称覆盖,是一个意思,指的是对类中已有方法的内部实现进行修改。

在上面 2 段代码的基础上,添加如下代码并运行:

class Bird:

    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")

    #鸟会飞
    def fly(self):
        print("鸟会飞")

class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟是鸟却不会飞")

# 创建Ostrich对象
ostrich = Ostrich()

#调用 Ostrich 类中重写的 fly() 类方法
ostrich.fly()

结果:
鸵鸟是鸟却不会飞

显然,ostrich 调用的是重写之后的 fly() 类方法。

调用被重写的方法

我们在子类中重写了从父类继承来的类方法,那么当在类的外部通过子类对象调用该方法时,Python 总是会执行子类中重写的方法。

这就产生一个新的问题,即如果想调用父类中被重写的这个方法,很简单,前面讲过,Python 中的类可以看做是一个独立空间,而类方法其实就是出于该空间中的一个函数。而如果想要全局空间中,调用类空间中的函数,只需要在调用该函数是备注类名即可。

class Bird:

    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")

    #鸟会飞
    def fly(self):
        print("鸟会飞")

class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟是鸟却不会飞")

# 创建Ostrich对象
ostrich = Ostrich()

#调用 Bird 类中的 fly() 方法
Bird.fly(ostrich)

结果:
鸟会飞

此程序中,需要大家注意的一点是,使用类名调用其类方法,Python 不会为该方法的第一个 self 参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。

通过类名调用实例方法的这种方式,又被称为未绑定方法。

super()函数:调用父类的构造方法

子类会继承父类所有的类属性和类方法。严格来说,类的构造方法其实就是实例方法,因此毫无疑问,父类的构造方法,子类同样会继承。

如果子类继承的多个父类中包含同名的类实例方法,则子类对象在调用该方法时,会优先选择排在最前面的父类中的实例方法。显然,构造方法也是如此。

class People:

    def __init__(self,name):
        self.name = name

    def say(self):
        print("我是人,名字为:",self.name)

class Animal:
    def __init__(self,food):
        self.food = food

    def say(self):
        print("我是动物,我吃",self.food)

    def display(self):
        print("我是动物")

#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People,Animal): #这样Animal中的构造函数__init__不会被调用,food不会有值
    pass

per = Person("zhangsan")
per.say()
per.display()

结果:
我是人,名字为: zhangsan
我是动物

上面程序中,Person 类同时继承 People 和 Animal,其中 People 在前。这意味着,在创建 per 对象时,其将会调用从 People 继承来的构造函数。因此我们看到,上面程序在创建 per 对象的同时,还要给 name 属性进行赋值。

正确的做法是定义 Person 类自己的构造方法(等同于重写第一个直接父类的构造方法)。但需要注意,如果在子类中定义构造方法,则必须在该方法中调用父类的构造方法。

在子类中的构造方法中,调用父类构造方法的方式有 2 种,分别是:

  1. 类可以看做一个独立空间,在类的外部调用其中的实例方法,可以向调用普通函数那样,只不过需要额外备注类名(此方式又称为未绑定方法);
  2. 使用 super() 函数。但如果涉及多继承,该函数只能调用第一个直接父类的构造方法。

也就是说,涉及到多继承时,在子类构造函数中,调用第一个父类构造方法的方式有以上 2 种,而调用其它父类构造方法的方式只能使用未绑定方法。

值得一提的是,Python 2.x 中,super() 函数的使用语法格式如下:

super(Class, obj).__init__(self,...)

其中,Class 指的是子类的类名,obj 通常指的就是 self。

在掌握 super() 函数用法的基础上,我们可以尝试修改上面的程序:

class People:
    def __init__(self,name):
        self.name = name

    def say(self):
        print("我是人,名字为:",self.name)

class Animal:
    def __init__(self,food):
        self.food = food

    def display(self):
        print("我是动物,我吃",self.food)

#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):
    #自定义构造方法
    def __init__(self,name,food):
        super().__init__(name)
        #super(Person,self).__init__(name) #执行效果和上一行相同

        #People.__init__(self,name)#使用未绑定方法调用 People 类构造方法
        #调用其它父类的构造方法,需手动给 self 传值

        Animal.__init__(self,food)

per = Person("zhangsan","食物")
per.say()
per.display()

结果:
我是人,名字为: zhangsan
我是动物,我吃 食物

可以看到,Person 类自定义的构造方法中,调用 People 类构造方法,可以使用 super() 函数,也可以使用未绑定方法。但是调用 Animal 类的构造方法,只能使用未绑定方法。

super()使用注意事项(包含新式类和旧式类的区别)

Python 2 中 super() 函数的用法和 Python 3 大致相同,唯一的区别在于,Python 2 中不能使用零参数形式的格式,必须提供至少一个参数。

对于想要编写跨版本兼容代码的程序员来说,还要注意一件事,即 Python 2 中的 super() 函数只适用于新式类,在旧式类中不能使用 super()。

那么,什么是旧式类和新式类呢?在早期版本的 Python 中,所有类并没有一个共同的祖先 object,如果定义一个类,但没有显式指定其祖先,那么就被解释为旧式类,例如:

 

class oldStyle1:
  pass
class oldStyle2:
  pass

其中,oldStyle1 和 oldStyle2 都属于旧式类。

Python 2.x 版本中,为了向后兼容保留了旧式类。该版本中的新式类必须显式继承 object 或者其他新式类:

class newStyleClass(object):
  pass
class newStyleClass(newStyleClass):
  pass

显然,以上两个类都属于新式类。

而在 Python 3.x 版本中,不再保留旧式类的概念。因此,没有继承任何其他类的类都隐式地继承自 object。

可以说,在 Python 3.x 中,显式声明某个类继承自 object 似乎是冗余的。但如果考虑跨版本兼容,那么就必须将 object 作为所有基类的祖先,因为如果不这么做的话,这些类将被解释为旧式类,最终会导致难以诊断的问题。

__slots__:限制类实例动态添加属性和方法

Python 也允许动态地为类或实例对象添加方法呢

class Test:
    pass

#下面定义了一个实例方法
def info(self):
    print("正在调用实例方法")

#下面定义了一个类方法
@classmethod
def info2(cls):
    print("正在调用类方法")

#下面定义个静态方法
@staticmethod
def info3():
    print("正在调用静态方法")

#类可以动态添加以上 3 种方法,会影响所有实例对象
Test.info = info
Test.info2 = info2
Test.info3 = info3
test = Test()
#如今,clang 具有以上 3 种方法
test.info()
test.info2()
test.info3()
#类实例对象只能动态添加实例方法,不会影响其它实例对象
test1 = Test()
test1.info = info
#必须手动为 self 传值
test1.info(test1)

结果:
正在调用实例方法
正在调用类方法
正在调用静态方法
正在调用实例方法

显然,动态给类或者实例对象添加属性或方法,是非常灵活的。但与此同时,如果胡乱地使用,也会给程序带来一定的隐患,即程序中已经定义好的类,如果不做任何限制,是可以做动态的修改的。

庆幸的是,Python 提供了 __slots__ 属性,通过它可以避免用户频繁的给实例对象动态地添加属性或方法。

再次声明,__slots__ 只能限制为实例对象动态添加属性和方法,而无法限制动态地为类添加属性和方法。

__slots__ 属性值其实就是一个元组,只有其中指定的元素,才可以作为动态添加的属性或者方法的名称。举个例子:

class Test:
    __slots__ = ('name','add','info')

可以看到, Test类中指定了 __slots__ 属性,这意味着,该类的实例对象仅限于动态添加 name、add、info 这 3 个属性以及 name()、add() 和 info() 这 3 个方法。

注意,对于动态添加的方法,__slots__ 限制的是其方法名,并不限制参数的个数。

class Test:
    __slots__ = ('name','add','info')

#Clanguage 的空子类
class SmallTest(Test):
    pass
#定义的实例方法
def info(self,name):
    print("正在调用实例方法:",name)

test1 = SmallTest()

#为子类对象动态添加 say() 方法
test1.say = info

#slot影响不到继承

test1.say.score = "1"
test1.age = 12
test1.say(test1,"testing...")

结果:
正在调用实例方法: testing...


#slot只能作用当前类
test2 = Test()
test2.name = "asd"
test2.score = "123"

结果:
Traceback (most recent call last):
  File "D:/PycharmProjects/pythonProject/main.py", line 22, in <module>
    test2.score = "123"
AttributeError: 'Test' object has no attribute 'score'

如果子类也要限制外界为其实例对象动态地添加属性和方法,必须在子类中设置 __slots__ 属性。

注意,如果为子类也设置有 __slots__ 属性,那么子类实例对象允许动态添加的属性和方法,是子类中 __slots__ 属性和父类 __slots__ 属性的和。

type()函数:动态创建类

type() 函数还有一个更高级的用法,即创建一个自定义类型(也就是创建一个类)。

type() 函数的语法格式有 2 种,分别如下:

type(obj) 
type(name, bases, dict)

以上这 2 种语法格式,各参数的含义及功能分别是:

  • 第一种语法格式用来查看某个变量(类对象)的具体类型,obj 表示某个变量或者类对象。
  • 第二种语法格式用来创建类,其中 name 表示类的名称;bases 表示一个元组,其中存储的是该类的父类;dict 表示一个字典,用于表示类内定义的属性或者方法。


对于使用 type() 函数查看某个变量或类对象的类型,由于很简单,这里不再做过多解释,直接给出一个样例:

#定义一个实例方法
def say(self):
    print("这是一个实例方法")

#使用 type() 函数创建类
Class1 = type("Class1",(object,),dict(say = say, name = "一个类"))

#创建一个 Class1 实例对象
test = Class1()

#调用 say() 方法和 name 属性
test.say()
print(test.name)

结果:
这是一个实例方法
一个类

注意,Python 元组语法规定,当 (object,) 元组中只有一个元素时,最后的逗号(,)不能省略。

可以看到,此程序中通过 type() 创建了类,其类名为 CLanguage,继承自 objects 类,且该类中还包含一个 say() 方法和一个 name 属性。

如何判断 dict 字典中添加的是方法还是属性?很简单,如果该键值对中,值为普通变量(如 "C语言中文网"),则表示为类添加了一个类属性;反之,如果值为外部定义的函数(如 say() ),则表示为类添加了一个实例方法。

可以看到,使用 type() 函数创建的类,和直接使用 class 定义的类并无差别。事实上,我们在使用 class 定义类时,Python 解释器底层依然是用 type() 来创建这个类。

MetaClass元类详解

MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。可以这么说,使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法。

举个例子,根据实际场景的需要,我们要为多个类添加一个 name 属性和一个 say() 方法。显然有多种方法可以实现,但其中一种方法就是使用 MetaClass 元类。

如果想把一个类设计成 MetaClass 元类,其必须符合以下条件:

  1. 必须显式继承自 type 类;
  2. 类中需要定义并实现 __new__() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 __new__() 方法会自动被执行,用来修改新建的类。

定义一个 MetaClass 元类:

#定义一个元类
class FirstMetaClass(type):

    # cls代表动态修改的类
    # name代表动态修改的类名
    # bases代表被动态修改的类的所有父类
    # attr代表被动态修改的类的所有属性、方法组成的字典

    def __new__(cls, name, bases, attrs):

        # 动态为该类添加一个name属性
        attrs['name'] = "name"
        attrs['say'] = lambda self: print("调用 say() 实例方法")
        return super().__new__(cls,name,bases,attrs)

class Test(object,metaclass=FirstMetaClass):
    pass

tests = Test()
print(tests.name)
tests.say()

结果:
name
调用 say() 实例方法

此程序中,首先可以断定 FirstMetaClass 是一个类。其次,由于该类继承自 type 类,并且内部实现了 __new__() 方法,因此可以断定 FirstMetaCLass 是一个元类。

可以看到,在这个元类的 __new__() 方法中,手动添加了一个 name 属性和 say() 方法。这意味着,通过 FirstMetaClass 元类创建的类,会额外添加 name 属性和 say() 方法。

显然,FirstMetaClass 元类的 __new__() 方法动态地为 Test类添加了 name 属性和 say() 方法,因此,即便该类在定义时是空类,它也依然有 name 属性和 say() 方法。

对于 MetaClass 元类,它多用于创建 API,因此我们几乎不会使用到它。

多态及用法详解

Python 是弱类型语言,其最明显的特征是在使用变量时,无需为其指定具体的数据类型。

这会导致一种情况,即同一变量可能会被先后赋值不同的类对象,例如:

class Test:
    def say(self):
        print("赋值的是 Test 类的实例对象")
class OneClass:
    def say(self):
        print("赋值的是 OneClass 类的实例对象")
        
a = Test()
a.say()

a = OneClass()
a.say()

结果:
赋值的是 Test 类的实例对象
赋值的是 OneClass类的实例对象

 

可以看到,a 可以被先后赋值为 Test 类和 OneClass类的对象,但这并不是多态。类的多态特性,还要满足以下 2 个前提条件:

  1. 继承:多态一定是发生在子类和父类之间;
  2. 重写:子类重写了父类的方法。
class Test:
    def say(self):
        print("调用的是 Clanguage 类的say方法")
class OneClass(Test):
    def say(self):
        print("调用的是 OneClass 类的say方法")
class TwoClass(Test):
    def say(self):
        print("调用的是 TwoClass 类的say方法")

a = Test()
a.say()

a = OneClass()
a.say()

a = TwoClass()
a.say()

结果:
调用的是 Clanguage 类的say方法
调用的是 OneClass 类的say方法
调用的是 TwoClass 类的say方法

可以看到,OneClass 和 TwoClass 都继承自 Test类,且各自都重写了父类的 say() 方法。从运行结果可以看出,同一变量 a 在执行同一个 say() 方法时,由于 a 实际表示不同的类实例对象,因此 a.say() 调用的并不是同一个类中的 say() 方法,这就是多态。

继续对上面的程序进行改写:

class WhoSay:
    def say(self,who):
        who.say()
class Test:
    def say(self):
        print("调用的是 Clanguage 类的say方法")
class OneClass(Test):
    def say(self):
        print("调用的是 OneClass 类的say方法")
class TwoClass(Test):
    def say(self):
        print("调用的是 TwoClass 类的say方法")

a = WhoSay()

a.say(Test())
a.say(OneClass())
a.say(TwoClass())

结果:
调用的是 Clanguage 类的say方法
调用的是 OneClass 类的say方法
调用的是 TwoClass 类的say方法

此程序中,通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法。这意味着,当调用 WhoSay 类中的 say() 方法时,我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法。

在其它教程中,Python 这种由多态衍生出的更灵活的编程机制,又称为“鸭子模型”或“鸭子类型”。

枚举类定义和使用

一些具有特殊含义的类,其实例化对象的个数往往是固定的,比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。

针对这种特殊的类,Python 3.4 中新增加了 Enum 枚举类。也就是说,对于这些实例化对象个数固定的类,可以用枚举类来定义。

from enum import Enum

class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 2
    blue = 3

如果想将一个类定义为枚举类,只需要令其继承自 enum 模块中的 Enum 类即可。

Color 类继承自 Enum 类,则证明这是一个枚举类。

在 Color 枚举类中,red、green、blue 都是该类的成员(可以理解为是类变量)。注意,枚举类的每个成员都由 2 部分组成,分别为 name 和 value,其中 name 属性值为该枚举值的变量名(如 red),value 代表该枚举值的序号(序号通常从 1 开始)。

和普通类的用法不同,枚举类不能用来实例化对象,但这并不妨碍我们访问枚举类中的成员。访问枚举类成员的方式有多种,例如以 Color 枚举类为例,如下代码:

from enum import Enum
class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 2
    blue = 3

#调用枚举成员的 3 种方式
print(Color.red)
print(Color['red'])
print(Color(1))

结果:
Color.red
Color.red
Color.red

#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)

结果:
1
red

#遍历枚举类中所有成员的 2 种方式
for color in Color:
    print(color)

结果:
Color.red
Color.green
Color.blue

枚举类成员之间不能比较大小,但可以用 == 或者 is 进行比较是否相等,例如:

print(Color.red == Color.green)
print(Color.red.name is Color.green.name)

结果:
Flase
Flase

枚举类中各个成员的值,不能在类的外部做任何修改,也就是说,下面语法的做法是错误的:

Color.red = 4

结果:
Traceback (most recent call last):
  File "D:/PycharmProjects/pythonProject/main.py", line 8, in <module>
    Color.red = 4
  File "C:\Users\HuYoo\AppData\Local\Programs\Python\Python37\lib\enum.py", line 391, in __setattr__
    raise AttributeError('Cannot reassign members.')
AttributeError: Cannot reassign members.

除此之外,该枚举类还提供了一个 __members__ 属性,该属性是一个包含枚举类中所有成员的字典,通过遍历该属性,也可以访问枚举类中的各个成员。例如:

for name,member in Color.__members__.items():
    print(name,"->",member)

结果:
red -> Color.red
green -> Color.green
blue -> Color.blue

Python 枚举类中各个成员必须保证 name 互不相同,但 value 可以相同(这将重复val的name当成别名)。

from enum import Enum

class Color(Enum):

    # 为序列值指定value值
    red = 1
    green = 1
    blue = 3

print(Color['green'])

结果:
Color.red

可以看到,Color 枚举类中 red 和 green 具有相同的值(都是 1),Python 允许这种情况的发生,它会将 green 当做是 red 的别名,因此当访问 green 成员时,最终输出的是 red。

在实际编程过程中,如果想避免发生这种情况,可以借助 @unique 装饰器,这样当枚举类中出现相同值的成员时,程序会报 ValueError 错误。

#引入 unique
from enum import Enum,unique

#添加 unique 装饰器
@unique
class Color(Enum):
    # 为序列值指定value值
    red = 1
    green = 1
    blue = 3

print(Color['green'])

结果:
Traceback (most recent call last):
  File "D:\python3.6\demo.py", line 3, in <module>
    class Color(Enum):
  File "D:\python3.6\lib\enum.py", line 834, in unique
    (enumeration, alias_details))
ValueError: duplicate values found in <enum 'Color'>: green -> red

除了通过继承 Enum 类的方法创建枚举类,还可以使用 Enum() 函数创建枚举类。例如:

from enum import Enum

#创建一个枚举类
Color = Enum("Color",('red','green','blue'))

#调用枚举成员的 3 种方式
print(Color.red)
print(Color['red'])
print(Color(1))

结果:
Color.red
Color.red
Color.red

#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)

结果:
1
red

#遍历枚举类中所有成员的 2 种方式
for color in Color:
    print(color)

结果:
Color.red
Color.green
Color.blue

 

 

 

 

 

 

 

 

 

 

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌新待开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值