[Python]-9-对象与类

引言

这篇文章介绍Python如何将函数与数据整合成类,并通过一个对象名称来访问它们。

文章目录

0×1.什么是python中的对象

Python中每条数据都是对象,每个对象都由三部分组成:标识,数据,类型;

标识是对象的名称,也储存了该对象的内存地址(对象引用),类型规定了这个对象所能储存的值的类型,内存中的某块区域保存了对象的数据值;

在之前的文章中,我们定义的字符串,列表,字典等,都是对象,每个对象都有其内置的一些方法,例如对字符串对象使用len()方法可以求出字符串的长度,我们可以在IDLE中使用dir()方法来查看某个对象可以使用的方法,如下:

					>>> s="www.qingsword.com"
					>>> dir(s)
					['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

					#上面中括号中的所有输出,以逗号分隔,每一个都是字符串对象可以使用的方法,其中带双下划线的为"内部方法",不带下划线的为"外部方法"也叫"接口函数","内部方法"通常对用户是不可见的,但这并不意味着内部方法就是不可调用的,实际上我们对某个字符串对象使用len()函数的时候,就是调用了其内部的__len__方法,通过下面的实例可以看到,两者的输出是相同的
					>>> print(len(s))
					17
					>>> print(s.__len__())
					17
					

在定义一个字符串对象的时候,python在其内部初始化了一个str类,这个类中包含了python提供的对字符串处理的内置方法,等大家看完本文第2部分,自己手动创建了一个类之后,就自然会明白上面这些内部方法和接口函数的工作原理了。

0×2.python中如何创建类

python中的类,实际上是完成某个工作的对象和函数的集合,当一个类被调用时,它创建了绑定到一个名称的对象,请看下面的实例,这是一个经典的电冰箱实例,我们假设要创建一个类,用于将食物放入冰箱,查询冰箱中有哪些食物,然后提供取出食物的一系列方法:

					#!/usr/bin/env python3
					#coding=utf-8

					########
					class Fridge:
					    """电冰箱类实例"""

					    #--------
					    def __init__(self,items={}):
					        """类初始化"""
					        if type(items)!=type({}):
					            raise TypeError("传入参数错误:%s"%items)
					        self.items=items
					########

					#python使用class关键字来定义一个类,本例定义了一个Fridge类,在这个类中有一个内部方法__init__,每个类都可以包含这个方法,也可以不包含,这个方法的作用是,当使用名称初始化一个Fridge对象时,自动执行__init__函数中的代码,相当于类的初始化操作

					#注意def __init__(self,items={})部分中的self参数,这是python独有的语法,self总是代表类对象本身,这段初始化代码初始化了一个空字典对象,如果不好理解self,可以尝试着将代码中的self省去,然后观察这段函数,实际上__init__就接收一个参数,这个参数必须是一个字典对象,如果省去这个参数,那么默认初始化一个空自字典,并且在Fridge内部使用items对象保存这个字典的数据,但实际情况下self参数不能省略

					#现在我们已经有了一个Fridge类,可以通过下面的方法创建这个类的对象实例,下面的语法没有给Fridge传递参数,所以将初始化一个空的字典,如果想要传递参数,可以这么写p=Fridge({"apple":1,"orange":3}),如果这样,那么Fridge中的items对象就将保存{"apple":1,"orange":3}这个字典
					p=Fridge()

					#现在有一个新的对象p,他是一个完整的Fridge类对象,在上面的创建过程中,Fridge类执行了__init__方法,这将创建一个空的字典对象,可以通过"类实例名称.类对象名称"来访问到这些数据
					print(p.items)

					#输出
					{}
					

现在,可以给Fridge类添加一系列方法来提供必要的功能了,在Fridge类中补充下面的内容,用于添加查询或删除食物:

					#!/usr/bin/env python3
					#coding=utf-8

					########
					class Fridge:
					    """电冰箱类实例"""

					    #--------
					    def __init__(self,items={}):
					        """类初始化"""
					        if type(items)!=type({}):
					            raise TypeError("传入参数错误:%s"%items)
					        self.items=items
					    
					    #--------
					    def __add_multi(self,food_name,quantity):
					        """内部方法,向items字典添加键值,如果food_name不在items字典的键列表中,说明这是一个新添加的食物,将数量初始化为0,然后加上传递给这个函数的quantity的值(可能是一个或者多个)"""
					        if (not food_name in self.items.keys()):
					            self. items[food_name]=0
					        self.items[food_name]+=quantity
					    
					    #--------
					    def add_one(self,food_name):
					        """向items字典添加单个食物,这就是一个接口函数,通过调用内部方法__add_multi完成添加食物到冰箱的工作"""
					        if type(food_name)!=type(""):
					            raise TypeError("食物名称类型错误:%s,正确的数据类型:%s"\
					                            %(type(food_name),type("")))
					        else:
					            self.__add_multi(food_name,1)
					        return True
					    
					    #--------
					    def add_many(self,food_dict):
					        """像items字典添加多个食物,这个接口函数接收一个字典参数food_dict,使用for循环遍历这个传递过来的字典中的键名,然后将名称和数量传递给__add_multi函数"""
					        if type(food_dict)!=type({}):
					            raise TypeError("食物名称和数量必须使用字典,错误的数据输入:%s"%food_dict)
					        else:
					            for food_name in food_dict.keys():
					                self.__add_multi(food_name,food_dict[food_name])
					                
					    #--------
					    def has(self,food_name,quantity=1):
					        """查看items中是是否还有某个食物,如果只向这个接口函数传递一个食物名称,那么quantity默认为1,他将调用下面的has_various函数,将食物名称和食物数量传递过去,用于判断冰箱中还有没有这么多的食物"""
					        return self.has_various({food_name:quantity})
					    
					    #--------
					    def has_various(self,foods):
					        """查看食物是否低于输入值,这个接口函数接受一个字典类型的传入参数,通过遍历这个传入的字典中的键名,判断Fridge的items字典中对应键的值是否小于传递进来的这个列表中的每个食物的值,如果小于就返回False,代表冰箱中没有那么多食物了,如果传入的食物名称不存在,会产生一个KeyError异常,同样返回False"""
					        try:
					            for food in foods.keys():
					                if self.items[food]<foods[food]:
					                    return False
					                return True
					        except KeyError:
					            return False
					            
					    #--------
					    def __get_multi(self,food_name,quantity):
					        """取出食物的内部方法,这个方法接受两个值,一个为食物名称,一个为食物数量,首先if判断冰箱中是否存在这种食物,如果有将其从类实例的items字典中减去对应数量,减去数量后再判断这种食物库存是否为0,如果为0,将其从items字典中删除,如果冰箱中没有这种食物或者库存不足(这些都在has_various方法中判断),跳转到else语句,判断是库存不足还是根本没有这种食物"""
					        if self.has(food_name,quantity):
					            self.items[food_name]-=quantity
					            print("成功取出%s,数量%s"%(food_name,quantity))
					            if self.items[food_name]==0:
					                self.items.pop(food_name)    
					        else:
					            if not food_name in self.items.keys():
					                print("冰箱中并没有这种食物:%s"%food_name)
					            else:
					                print("食物'%s'库存不足,现有数量:%s"%(food_name,self.items[food_name]))
					     
					    #--------
					    def get_one(self,food_name):
					        """取出某个食物,调用内部方法__get_multi取出单个食物"""
					        if type(food_name)!=type(""):
					            raise TypeError("食物名称类型错误:%s,正确的数据类型:%s"\
					                                        %(type(food_name),type("")))
					        else:
					            self.__get_multi(food_name, 1)
					        return True
					     
					    #--------
					    def get_many(self,food_dect):
					        """取出多个食物,这个函数接收一个字典参数,for循环遍历这个传入的字典中的键列表,调用__get_multi取出这些食物和对应的数量"""
					        if type(food_dect)!=type({}):
					            raise TypeError("食物名称和数量必须使用字典,错误的数据输入:%s"%food_dict)
					        for food in food_dect.keys():
					            self.__get_multi(food,food_dect[food])
					########       
					
					#初始化Fridge类
					p=Fridge()

					#添加食物
					p.add_one("orange")
					p.add_many({"apple":2,"banana":5})
					print(p.items)
					#输出
					{'apple': 2, 'orange': 1, 'banana': 5}

					#判断食物是否存在
					print(p.has("orange"))  #True
					print(p.has("apple",3)) #False

					#取出食物
					if p.has("orange"):
					    p.get_one("orange")
					p.get_many({"banana":4,"apple":4})
					p.get_one("www.qingsword.com")
					print(p.items)
					#输出
					成功取出orange,数量1
					成功取出banana,数量4
					食物'apple'库存不足,现有数量:2
					冰箱中并没有这种食物:www.qingsword.com
					{'apple': 2, 'banana': 1}
					

0×3.类私有属性访问限制

在上面的电冰箱实例中,我们已经创建过了私有方法,python中所谓的私有方法和属性,就是在这些方法或属性名前添加两个"下划线"(例如__items),这样的属性或方法是不能用正常的方法访问到的,但不包括前后双下划线的那些(例如__len__),下面用一个实例来演示这些私有属性和方法的特性:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class Kitty(object):
					    """私有属性__name和__age的类"""
					    #--------
					    def __init__(self,name="hello",age=1):
					        """初始化"""
					        self.__name=name
					        self.__age=age
					    #私有属性可以通过接口函数来调用
					    #--------
					    def get_age(self):
					        """返回年龄"""
					        return self.__age
					    #--------
					    def set_age(self,age):
					        """设置年龄"""
					        self.__age=age
					    #--------
					    def get_name(self):
					        """返回名字"""
					        return self.__name
					    #--------
					    def set_name(self,name):
					        """设置名字"""
					        self.__name=name
					    #前后双下划线的方法,不是私有方法,外部可以直接访问,只有前置双下划线的属性或方法才是私有的,自能通过内部接口函数调用
					    #--------
					    def __get_name_len__(self):
					        return len(self.__name)
					
					#实例化      
					kt=Kitty()
					#通过接口函数设置年龄和姓名
					kt.set_age(2)
					kt.set_name("www.qingsword.com")
					#通过接口函数打印姓名和年龄
					print(kt.get_name())
					print(kt.get_age())
					#打印出名字长度
					print(kt.__get_name_len__())

					#程序输出
					www.qingsword.com
					2
					17

					#从这个实例可以看出,前后带双下划线的方法或属性,都能直接访问到,而如果我们输入下面这3句,发现并没有报错,不是说私有属性不能直接访问吗,为什么可以直接kt.__name设置私有属性的值呢?这是因为,实际上我们使用实例kt.__name这种语法的时候,是对kt这个类实例,创建了一个新的属性__name,这个属性和Kitty内部的私有属性并不是一个属性,内部的__name属性已经被python解释器自动改名为_Kitty__name了
					kt.__name="qingsword.com"
					print(kt.__name)
					print(kt._Kitty__name)

					#从下面的程序输出可以看到,两者保存的数据并不相同
					qingsword.com
					www.qingsword.com

					#这并不意味着外部就真的无法调用这些私有属性或方法,只是python解释器自动对这些私有数据添加了类名前缀,可以给私有属性添加带但下划线的类名前缀来直接访问他们,但并不推荐这么做
					kt._Kitty__name="www.qingsword.com"

					#可能大家会觉得很奇怪,为什么要添加这些私有的属性或方法,直接提供访问不好吗?实际上,可以通过创建接口函数,对私有数据进行过滤处理,例如
					    #--------
					    def set_age(self,age):
					        """设置年龄"""
					        if 0<=age<=150:
					            self.__age=age
					        else:
					            print("年龄不科学")
					

0×4.继承与多态

我们在创建类的时候,默认情况下都是继承了python的基类object,除此之外,我们还能让新建的类继承现有的类,继承后子类将拥有被继承的父类所有的属性和方法,下面用一个实例来演示这一特性:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class Parent(object):
					    """父类,继承了object类,如果一个类是继承基类object,可以省略(object)不写,但写上看起来更加的规范"""
					    #--------
					    def print_website(self):
					        """打印网址"""
					        print("www.qingsword.com")
					    #--------
					    def print_author(self):
					        """打印作者名称"""
					        print("QingSword")
					########
					class Sub_A(Parent):
					    """继承Parent的子类,将父类写在子类后面的括号中,表示继承其所有的方法和属性,Sub_A重写了父类的print_website方法"""
					    #--------
					    def print_website(self):
					        """打印网址"""
					        print("qingsword.com")
					########
					class Sub_B(Parent):
					    """继承Parent的子类,仅仅是一个空类,但却可以使用父类所有属性或方法(注意,对空类最好添加一个这样的注释,否则在某些编辑器下可能会出现缩进IndentationError错误)"""
					########
					class Sub_C(object):
					    """继承object的子类"""
					    #--------
					    def print_author(self):
					        print("qingsword")
					        
					#--------
					def print_info(x):
					    """打印作者信息"""
					    x.print_author()
					
					#首先实例化四个类
					p=Parent()
					a=Sub_A()
					b=Sub_B()
					c=Sub_C()

					#访问父类的两个方法
					p.print_website()
					p.print_author()
					#程序输出
					www.qingsword.com
					QingSword

					#访问子类A的两个方法,如果一个子类中有与父类同名的属性或方法,那么它将覆盖父类的属性或方法,A类中重写了父类的print_website(),所以会优先被调用,虽然A类并没有print_author()方法,但是它将从父类Parent中继承这个方法,除非重写这个方法,否则就将直接调用父类的方法打印出作者信息
					a.print_website()
					a.print_author()
					#程序输出
					qingsword.com
					QingSword

					#B类中什么都没写,但他继承了父类Parent
					b.print_author()
					b.print_website()
					#程序输出
					www.qingsword.com
					QingSword
					
					#再来看看这些对象他们的类型都是什么

					#Parent类是继承object的,所以它可以是object类型,也可以是Parent类型,但不能是其子类型
					print(isinstance(p,Parent))
					print(isinstance(p,object))
					print(isinstance(p,Sub_A))
					#程序输出
					True
					True
					False

					#所有的子类型,可以是其父类型或Python基类object,也可以是其自身,所以下面的输出都是True
					print(isinstance(a,Parent))
					print(isinstance(a,Sub_A))
					print(isinstance(b,Parent))
					print(isinstance(b,Sub_B))

					#像print_info()方法传递四个类,四个类可以是不同类型,甚至可以像print_info()传递其他方法名称,这是高级动态语言的一个最强大的地方——多态:print_info()方法接收一个变量,它不管接收的变量是什么类型,只关心这个变量能不能访问print_author()方法,这就是为什么C类虽然是继承了object,但它也有print_author()方法,所以程序不会报错
					print_info(p)
					print_info(a)
					print_info(b)
					print_info(c)
					#程序输出
					QingSword
					QingSword
					QingSword
					qingsword
					

0×5.获取对象信息

除了使用isinstance来匹配对象类型外,还能使用type来获取这些对象的类型,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					#types模块中定义的常量可用于判断函数类型
					import types
					
					#输出对象的类型
					print(type("www.qingsword.com"))
					#判断对象是否为字符串类型
					print(type("qingsword.com")==str)
					#程序输出
					<class 'str'>
					True

					#如果对象是一个函数,就需要借助types模块中定义的常量来判断了
					#--------
					def fx():
					    return 0

					#判断fx是否为函数类型
					print(type(fx)==types.FunctionType)

					#判断是否为lambda类型
					print(type(lambda :0)==types.LambdaType)

					#判断len是否为内置的函数类型
					print(type(len)==types.BuiltinFunctionType)

					#判断表达式是否为列表生成器类型
					print(type((x for x in range(10)))==types.GeneratorType)

					#程序输出都为True

					#isinstance同样能够完成这些操作,例如将判断fx函数的语句改成下面这样,返回值同样是True
					print(isinstance(fx,types.FunctionType))
					

除了上面的两种对象类型判断外,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					class Ob_A(object):
					    #---------
					    def __init__(self,x=0):
					        if type(x)==int:
					            self.a=x
					        else:
					            raise TypeError("传入参数类型(%s)错误,正确的参数类型int"%type(x))
					    #---------
					    def sq(self):
					        return self.a*self.a

					oba=Ob_A(12)

					#判断oba对象中是否有属性(或方法)a,返回True
					print(hasattr(oba,"a"))

					#获取属性(或方法)a,本例中a是一个属性值12,所以被print直接打印了出来
					print(getattr(oba,"a"))

					#是否有属性(或方法)b,返回False
					print(hasattr(oba,"b"))

					#给对象设置一个属性b,值为13
					setattr(oba,"b",13)

					#因为已经给对象添加了属性b,再次判断,返回True,并且打印出b的值13
					print(hasattr(oba,"b"))
					print(getattr(oba,"b"))

					#判断oba对象中是否存在sq属性(或方法)
					print(hasattr(oba,"sq"))

					#使用sq对象指向oba对象的sq方法
					sq=getattr(oba,"sq")

					#从type可以看到sq是一个方法类型
					print(type(sq))

					#执行sq方法,返回144
					print(sq())

					#程序输出
					True
					12
					False
					True
					13
					True
					<class 'method'>
					144
					

这三个函数可用于探测对象中的信息,只有在不知道对象信息的时候,才会使用这三个函数,如果已经明确知道了一个对象中包含了哪些属性和方法,请直接调用他们。

0×6.实例属性与类属性

python是动态语言,在实例化后,还能给实例添加新的属性,但最好不要与类中的属性同名,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					#Ob_A类中定义了一个类属性x,并且配置了其值为2
					class Ob_A(object):
					    x=2
					
					#实例化Ob_A类
					oba=Ob_A()

					#给oba实例创建一个新属性x,因为和Ob_A类中的属性同名,所以会覆盖原有属性值
					oba.x=1

					#打印出1
					print(oba.x)

					#类中的属性保持不变,打印出2
					print(Ob_A.x)

					#此时如果手动删除oba实例的x对象,再次打印oba实例的x对象,就会打印出原类中绑定的属性x值,打印出2
					del oba.x
					print(oba.x)

					#程序输出
					1
					2
					2
					

类在实例化后,如果需要给实例新增属性,建议属性名不要使用与原类中已经存在的属性同名,因为相同名称的实例属性将屏蔽掉类属性。

0×7.属性和方法的动态绑定

Python属于高级动态语言,与静态语言不同,当我们实例化一个类对象后,可以动态的给这个类实例绑定属性或方法,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					from types import MethodType

					#定义一个类,仅包含一个age属性
					class Ob_A(object):
					    def __init__(self,age=0):
					        self.age=age
					
					#实例化Ob_A类
					a=Ob_A(23)
					#给Ob_A类的实例a新增一个name属性
					a.name="qingsword"

					#定义一个外部方法,稍后将它绑定到a实例
					def get_sex(self):
					    return "male"
					
					#使用types模块中的MethodType方法,将get_sex这个外部方法绑定到a实例的getsex变量上
					a.getsex=MethodType(get_sex,a)

					#执行a实例的getsex()就相当于执行了get_sex()
					print(a.getsex())

					#程序输出
					male

					#getsex名称仅限于a实例调用,如果此时再实例化一个Ob_A类,除非再次使用MethodType绑定一个新的get_sex方法,否则这个新的类实例是不能使用get_sex这个外部方法的
					

从上面这个实例可以看出,Python不会限制一个对象实例绑定多少外部属性和方法,但如果要限制他们,可以在类中使用__slots__属性,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					from types import MethodType
					########
					class Ob_A(object):
					    def __init__(self,age=0):
					        self.age=age
					    #定义了 __slots__属性后,Ob_A自身和所有实例,都只能使用这个元组中所包含的元素作为属性或方法名称
					    __slots__=("name","age","get_sex")
					########
					#但__slots__属性对继承它的子类无效,除非这个子类中也包含一个__slots__属性,那么这个子类可以定义的属性或方法名就是自己的__slots__元组中包含的元素加上父类__slots__元组中包含的元素的集合
					class Ob_B(Ob_A):
					    x=0
					########
					def get_sex(self):
					    return "male"
					a=Ob_A(23)
					a.name="qingsword"
					a.get_sex=MethodType(get_sex,a)
					print(a.get_sex())

					#Ob_B类虽然继承Ob_A,但它没有包含__slots__属性,所以实例b可以添加任何属性或方法名,本例添加了一个web属性,指向了本站网址这段字符串地址
					b=Ob_B()
					b.web="www.qingsword.com"
					print(b.web)
					

0×8.如何将类方法封装成属性

这一部分将使用python内置的@property装饰器,将类中的方法转换成属性,在外部看来,这个方法就像一个普通属性那样使用,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					from datetime import datetime 
					########
					class Ob_A(object):
					    def __init__(self,birth=2016):
					        """初始化配置出身年份"""
					        self.__birth=birth
					    #使用@property装饰器将birth方法转换成属性
					    @property
					    def birth(self):
					        """获取出身年份"""
					        return self.__birth
					    #使用@方法名.setter将这个方法设置成可写属性(一个方法名称必须先使用过property装饰器,才能使用setter)
					    @birth.setter
					    def birth(self,birth):
					        """设置出身年份"""
					        self.__birth=birth
					    #仅有一个@property没有setter的方法,相当于一个只读属性
					    @property
					    def age(self):
					        """返回年龄"""
					        y=datetime.now()
					        return y.year-self.__birth

					x=Ob_A(2008)
					print(x.birth)
					#可以像设置属性值那样复制(如果是方法语法应该是x.birth(2000))
					x.birth=2000
					print(x.birth)
					print(x.age)

					#程序输出
					2008
					2000
					16
					

虽然上面的实例中,并没有对年龄做进一步的检测(比如年龄范围,输入类型等),但也不难看出,将函数封装成属性的样式可以让调用者写出更直观代码(比如x.birth=2000,而不是x.birth(2000)),而对封装者而言,可以在函数中去做各种条件判断来保证逻辑的合理。

0×9.类的多重继承

python中的类提供了多重继承的功能,比如C类可以继承B类同时又能继承A类,C类能够使用B和A类所有属性和方法,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def _Print_website(self):
					        print("www.qingsword.com")
					########
					class B(object):
					    def _Print_HelloWorld(self):
					        print("Hello World")
					########
					#多重继承只需要在子类括号中用逗号分隔需要继承的类即可
					class C(A,B):
					    def __init__(self):
					        self.name="qingsword"
					
					#因为C类同时继承了A和B类,所以它的实例可以使用A和B类所有方法和属性
					x=C()
					x._Print_website()
					x._Print_HelloWorld()
					print(x.name)

					#程序输出
					www.qingsword.com
					Hello World
					qingsword
					

0×10.定制类

当我们实例化一个类对象后,使用print打印这个类对象,往往会打印出一个类名称和内存地址,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A:
					    #--------
					    def __init__(self,name="qingsword"):
					        self.name=name

					x=A("www.qingsword.com")
					print(x)

					#程序输出
					<__main__.A object at 0x7f41d6039f28>
					

为了让打印输出更加的友好,python提供了两个函数__str__和__repr__,前者可以让print打印一个对象时,输出这个函数下定制的返回信息;后者是在调试过程中让调试程序(IDLE提示符环境等)在直接输入对象时,能够打印出我们定制的信息,这两者通常写入相同的返回信息,例如,将上面的实例更改如下:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A:
					    #--------
					    def __init__(self,name="qingsword"):
					        self.name=name
					    #--------
					    def __str__(self):
					        """定制对象返回信息"""
					        return "Class A Object self.name=%s"%self.name
					    #直接让repr函数等于str
					    __repr__=__str__

					x=A("www.qingsword.com")
					print(x)

					#程序输出
					Class A Object self.name=www.qingsword.com
					

除了定制对象的输出信息外,如果一个类要用作for循环,Python提供了一个__iter__方法,该方法返回一个迭代对象,可使用for循环迭代这个对象,for会不断调用该对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误(或break)退出循环,可以将一个类定制成可迭代类,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class Fib(object):
					    #--------
					    def __init__(self):
					        self.a,self.b=0,1
					    #实例本身就是迭代对象,所以iter函数返回自己
					    def __iter__(self):
					        return self
					    #如果对象中的a属性值大于20,就停止循环,否则返回每次循环中得到的a值
					    def __next__(self):
					        self.a,self.b=self.b,self.a+self.b
					        if self.a>20:
					            raise StopIteration()
					        return self.a 

					x=Fib()
					for y in x:
					    print(y)

					#这是一个斐波拉契迭代类,程序输出如下
					1
					1
					2
					3
					5
					8
					13

					#如果删除类中的self.a>20的判断,可以将for循环更改成
					for y in x:
					    #可以手动指定一个迭代范围
					    if y<20:
					        print(y)
					    else:
					        break
					

上面的可迭代类看起来更像是一个生成器,但现在我们还无法像列表那样使用一个索引来取得单个值,为此python提供了一个__getitem__方法,用于实现取得单个值的类,修改上面的程序如下:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class Fib(object): 
					    #根据输入的n,返回x的值
					    def __getitem__(self,n):
					        #x和y只是函数中的一个局部变量,每次调用函数,都会被重新设置成1
					        x,y=1,1
					        while n>0:
					            x,y=y,x+y
					            n-=1
					        return x
					        
					x=Fib()
					print(x[2])
					print(x[12])

					#程序输出
					2
					233
					

如果想要给这个类添加一个切片功能,可以修改上面的代码如下:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class Fib(object):
					    def __getitem__(self,n):
					        #判断传入的是一个分片类型还是一个索引值
					        if isinstance(n,int):
					            x,y=1,1
					            while n>0:
					                x,y=y,x+y
					                n-=1
					            return x
					        elif isinstance(n,slice):
					            #获取分片的起始值
					            start=n.start
					            stop=n.stop
					            if start is None:
					                start=0
					            x,y=1,1
					            L=[]
					            for a in range(stop):
					                if a>=start:
					                    L.append(x)
					                x,y=y,x+y
					            return L
					        
					x=Fib()
					print(x[12]) 
					print(x[:12])
					print(x[6:12])

					#程序输出
					233
					[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
					[13, 21, 34, 55, 89, 144]
					

上面这个简单的分片器并没有对负数和分片步长做处理,仅仅是提供一个思路,告诉大家,通过这种定制函数,可以将一个类定制成任何数据类型,可以是列表字典元组等等。

通常情况下,我们调用一个对象的某个属性和方法时,如果这个属性和方法不存在,就会报错,如下所示:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def __init__(self):
					        self._path=""

					x=A()
					#调用_path没有问题
					print(x._path)
					#但调用一个不存在的sub属性就会报错,提示sub属性不存在
					print(x.sub)
					

python提供了一个__getattr__函数,它接收一个不存在的属性或方法名称,然后返回一个我们设定的值,如果我们没有定义返回值,它将返回一个None,这比直接抛出一个错误要友好的多,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def __init__(self):
					        self._path=""
					    def __getattr__(self,attr):
					        if attr=="blog":
					            return "www.qingsword.com"
					        elif attr=="doit":
					            return "ok"
					        #一个函数返回值
					        elif attr=="age":
					            return lambda :23

					x=A()
					print(x.blog)
					print(x.doit)
					print(x.age())
					print(x.nothing)

					#程序输出
					www.qingsword.com
					ok
					23
					None
					

请注意__getattr__函数只有当类中没有定义这些属性值时才接收他们,并且根据我们自己的判断返回值,如果类中有这些属性或方法,则会优先使用它们,另外,如果这个函数接收到了一个没有定义返回值的名称,默认返回None,除非手动抛出一个AttributeError,比如,在if的末尾添加下面的代码,再次运行就会抛出一个异常,提示nothing不存在:

					else:
					raise AttributeError("错误的属性或方法名:%s"%attr)	
					

__getattr__最常见的应用就是链式调用,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def __init__(self,path=""):
					        self._path=path
					        print('self._path is %s' % self._path)
					    def __getattr__(self,attr):
					        return A("%s/%s"%(self._path,attr))
					    def __str__(self):
					        return self._path
					    __repr__=__str__

					x=A()
					print(x.www.qingsword.com)

					#程序输出
					#x=A()初始化时的输出
					self._path is
					#x.www时因为没有www属性,调用了__getattr__函数,并且传递了一个www,然后递归调用A,这一步相当于A("/www")
					self._path is /www
					#x.www.qingsword会再次激活__getattr__函数,因为www并没有qingsword属性,以此类推
					self._path is /www/qingsword 
					self._path is /www/qingsword/com
					/www/qingsword/com
					

我们在实例化一个对象的时候,可以通过"对象名.属性名"来使用其中的属性,或者通过"对象名.方法名()"来调用其中的方法,除此之外,python提供了一个__call__函数,这个函数允许对象调用自身,请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def __init__(self,name="qingsword"):
					        self.name=name
					    def __call__(self):
					        print("My name is %s"%self.name)

					x=A()
					#直接调用对象自身,就相当于执行了对象内的__call__函数,就好像x本身是一个函数而不是类对象一样
					x()  #输出"My name is qingsword"

					#我们可以通过callable()函数来判断一个对象是否可调用,这个函数返回一个布尔值,如果我们注释掉A类中的call函数,那么下面的第一句就会返回False,大家可以尝试一下
					print(callable(x))  #True
					print(callable(str))  #True
					print(callable([1,2,3]))  #False
					

0×11.枚举类

Python3.0之后,提供了一个枚举类型,弥补了Python2没有枚举的不足,首先来看一个简单的枚举实例:

					#!/usr/bin/env python
					#coding=utf-8
					#导入枚举模块
					from enum import Enum
					
					#定义一个枚举变量m
					m = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
					
					#使用不同的方法获取枚举中的名称和值,和列表不同,枚举的索引是从1开始的
					print(m(1))  #Month.Jan
					print(m.Mar)  #Month.Mar
					print(m["Oct"])  #Month.Oct
					print(m.Mar.value)  #3

					#打印出枚举中所有的名称
					x=len(m.__members__) #获得枚举个数
					for i in range(1,x+1):
					    print(m(i))

					#程序输出
					Month.Jan
					Month.Feb
					Month.Mar
					Month.Apr
					Month.May
					Month.Jun
					Month.Jul
					Month.Aug
					Month.Sep
					Month.Oct
					Month.Nov
					Month.Dec

					#获取枚举成员名称和全名,m.__members__.items()会将枚举分割成一个列表,每个元素都为一个元组,每个元组中又包含一个字符串和一个字典类型,分割后的列表大概是这个样子[("Jan",<Month.Jan:1>),("Feb",<Month.Feb:2>)....],然后使用name接收第一个参数(Jan),member接收第二个参数(<Month.Jan:1>),member可以访问键,用member.value可以访问值
					for name, member in m.__members__.items():
					    print(name,'=>',member,',',member.value)

					#程序输出
					Jan => Month.Jan , 1
					Feb => Month.Feb , 2
					Mar => Month.Mar , 3
					Apr => Month.Apr , 4
					May => Month.May , 5
					Jun => Month.Jun , 6
					Jul => Month.Jul , 7
					Aug => Month.Aug , 8
					Sep => Month.Sep , 9
					Oct => Month.Oct , 10
					Nov => Month.Nov , 11
					Dec => Month.Dec , 12
					

了解了枚举的创建和访问方法后,再来看看枚举类的创建方法:

					#!/usr/bin/env python
					#coding=utf-8
					from enum import Enum,unique
					@unique  #unique装饰器可以确保枚举中没有重复值
					class Week(Enum):
					    """继承枚举类,可以指定每个名称的值"""
					    Sun=0 #将起始索引设定成0
					    Mon=1
					    Tue=2
					    Wed=3
					    Thu=4
					    Fri=5
					    Sat=6
					
					#访问方法同枚举
					print(Week(0))
					print(Week.Sat.value)
					print(Week["Thu"])

					#程序输出
					Week.Sun
					6
					Week.Thu
					

在Python3中,枚举类经常用于定义常量,因为枚举中每个名称对应的值是不可变的,例如:

					#!/usr/bin/env python
					#coding=utf-8
					from enum import Enum,unique
					@unique
					class const(Enum):
					    """常量枚举类"""
					    website="www.qingsword.com"
					    author="qingsword"
					    pi=3.1415

					print(const.website.value)
					print(const("qingsword"))
					print(const["pi"].value)

					#程序输出
					www.qingsword.com
					const.author
					3.1415
					

0×12.动态类

动态语言和静态语言在创建类的时候也有所不同,动态语言类的创建是在程序执行后才依次执行类中的语句,动态化创建一个类,而静态语言是在编译时就已经创建了这些类;我们在使用type判断一个类的类型时,往往看到类的类型是type,这又与动态类的创建有什么关系呢?请看下面的实例:

					#!/usr/bin/env python
					#coding=utf-8
					########
					class A(object):
					    def Website(self):
					        print("www.qingsword.com")

					x=A()
					print(type(A))
					print(type(x))

					#程序输出,从输出中可以看到,x是A类的一个实例,但A类本身却是一个class type
					<class 'type'>
					<class '__main__.A'>
					

实际上type方法不仅可以判断对象的类型,python还可以使用它来动态创建类,而使用type动态创建的类与直接使用class创建的类效果是一样的,这就是为什么上面type判断A类的类型是class type的原因,实际上两者可以看做同一种类型,下面的实例演示了如何通过type动态创建一个类:

					#!/usr/bin/env python
					#coding=utf-8
					def Website(self):
					    print("www.qingsword.com")
					def SayHello(self,name="qingsword"):
					    print("Hello %s"%name)
					#B指向一个Type动态创建的类,所以B就代表这个类(A)本身
					#type的语法:
					#type("类名",(继承类列表,...),dict(类方法名=绑定的方法名,...))
					#使用这种方法我们就可以动态的将外部的方法绑定到一个类中,从而在运行时创建一个类
					B=type("A",(object,),dict(web=Website,hello=SayHello))
					x=B()
					print(x)
					print(type(B))
					x.web()
					x.hello("xxx")

					#程序输出
					<__main__.A object at 0x7f7d70821048>
					<class 'type'>
					www.qingsword.com
					Hello xxx