Python之路【第11章】:Python面向对象
Python面向对象
-
初级篇:
程序设计的由来
面向对象介绍以及诞生原因
面向对象的程序的构成
面向对象的三大特性
-
进阶篇:
类的成员
成员修饰符
类的特殊成员
类的组合用法
主动调用其他类的成员
-
其他:
和类相关的几个内置函数,issubclass、type、isinstance
函数和方法区别以及如何判断
反射
约束
继承中的c3算法
一、面向对象之初级篇
1.程序设计的由来;
转自http://www.cnblogs.com/linhaifeng/articles/6428835.html
2.面向对象介绍以及诞生原因;
1)编程的几种方式
我们知道编程方式分为面向过程编程、函数式编程、以及面向对象编程,刚开始我们接触编程时所写的代码都是面向过程;
面向过程的程序设计:核心是过程,指的是解决问题的步骤,比如先干什么再干什么;这种设计好比精心设计好的一条流水线,是一种机械式的思维方式;
优点:复杂度的问题流程化,进而简单化;
缺点:几乎没有可扩展性,程序只要稍微改动则牵制全身;
函数式编程的设计:把问题分解为一个函数的集合,函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生同样的结果。
优点:逻辑可证;模块化;易于调试;代码重复利用;
缺点:所有的数据都是不可以改变的,严重占用运行资源,导致运行速度也不够快
2)关于面向对象编程;
面向对象的程序设计:核心是对象,对象是特征和技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向对象机械式的思维方式形成鲜明对比。
优点:解决了程序的可扩展性,对某一个对象单独修改,会立刻反应到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易;
缺点:编程的复杂度高;无法像面向过程的程序设计流水线式的可以很精确的预测问题的处理流程和结果;面向对象的程序一旦开始就是对象之间的交互解决问题,即便是上帝也无法精确预测最终结果;
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层、互联网应用、企业内部软件、游戏等都是面向对象设置的好地方;
对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性的;
3)面向对象诞生的原因:
请参考程序设计的由来。
3.面向对象的程序构成;
面向对象程序包含了类和对象;
1)什么是类?如何写一个类?
类的定义:类就是类别、种类的意思,是面向对象设计最重要的概念,类就像是一个模板,类是一系列对象相似的特征与技能的结合体;比如:我们每个人都有相同的结构(身体构造),相同的功能(吃喝拉撒跑),那么,我们可以说都是由人的这个类所创建出来的。
类的编写:
#格式: class 类名: def __init__(self): pass def func1(): pass def func2(): pass . . . . . . #类里面的函数,我们称之为方法; #__init__方法意思为对象定制自己独有的特征
类的作用:属性引用和实例化;
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name): self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化的过程其实就是在创建对象,我们稍后介绍对象;
关于类方法中的self参数:
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做;
2)什么是对象,对象如何产生,如何使用?
对象的定义:对象是特征和技能的结合体,比如,每个人都可以作为人类的一个对象,世间万物皆如此,所以,世间万物皆为对象;
在现实中:现有对象,由对象的基本特征总结出了类;
在程序中:现有类,定义类之后,在根据类实例出一个个具体的对象;
对象的产生:
对象是由类实例化产生的;也可以说类是虚拟的,对象是实实在在存在的;
class person(): #定义一个类 def __init__(self,name,age): 类的函数属性 self.name = name self.age = age def like(self,love): 类的数据属性 print("%s今年%s岁了,喜欢%s" % (self.name,self.age,love)) ren = person("小张",20) #根据person实例化一个对象,也就是创建一个对象 ren.like("看书") #对象调用方法; --------------------- 输出结果 --------------------------------------------------- 小张今年20岁了,喜欢看书 #类的函数属性只的是每个对象的特殊属性,比如每个人的名字,年龄都是不一样的,这就是他们的特殊属性,而类的数据属性,是公共属性,也就是说大家都可以看书;
4.面向对象的三大特性;
面向对象有三大特征:封装、继承、多态;
1)封装;
定义:封装,从本身意思去理解就是将某些东西放到一个容器里装起来。函数的作用就是将某个功能进行封装到函数里,方便调用;而面向对象的封装就是将某些共性的函数(在类里我们称为方法)封装在一个类里,方便查找使用,我们称为归类;还有一层意义就是隐藏;通过__下划线方式将属性隐藏起来,设置成私有的。
核心:将相关功能封装到一个类中,将数据封装到对象中;
2)继承;
定义:继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又称为基类或超类,新建的类称为派生类或子类。
子类会遗传父类的属性,也就是说当类在本身的类中找不到某方法或变量是,回去父类进行寻找,这样就解决了代码重用的问题;
继承分为单继承和多继承:
class zjk(): #person类的父类 def sport(self,sport): print("%s喜欢%s" % (self.name,sport)) class person(zjk): #定义zjk类为自己的父类 def __init__(self,name,age): self.name = name self.age = age def like(self,love): print("%s今年%s岁了,喜欢%s" % (self.name,self.age,love)) ren = person("小张",20) ren.sport("打球") #当ren这个对象调用sport时会先从自身的类(person)进行查找,如果自身的类中没有这个方法,则回去父类(zjk)中查找,并调用。
class zjk(): def sport(self,sport): print("%s喜欢%s" % (self.name,sport)) class zjk1(): def eat(self,eat): print("%s喜欢%s" % (self.name,eat)) class person(zjk,zjk1): def __init__(self,name,age): self.name = name self.age = age def like(self,love): print("%s今年%s岁了,喜欢%s" % (self.name,self.age,love)) ren = person("小张",20) ren.eat("吃水果") ------------------- 打印结果 ------------------------------------------------ 小张喜欢吃水果
由此,我们可以总结一下:多继承时的顺序,由左至右,一次查找;
3)多态;
定义:多态指的是一类事物有多种形态;python中原生多态的概念指出了对象如何通过他们共同的属性和方法来操作及访问,并且不需要考虑它们具体是哪个类。
比如:动物有多种形态:人、狗、猪;这样实例出的对象也是不同的;
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print('say hello') class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang') class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
比如:文件有多种形态:文本文件、可执行文件;
import abc class File(metaclass=abc.ABCMeta): #同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print('open file') class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print('execute file')
鸭子类型:
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
a:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用 class TxtFile: def read(self): pass def write(self): pass class DiskFile: def read(self): pass def write(self): pass
b.其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下
#str,list,tuple都是序列类型 s=str('hello') l=list([1,2,3]) t=tuple((4,5,6)) #我们可以在不考虑三者类型的前提下使用s,l,t s.__len__() l.__len__() t.__len__() len(s) len(l) len(t)
二、面向对象之进阶篇
1.类的成员
类的成员可以分为三大类:字段、方法、属性;
需要注意的是:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段,而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
1.1字段(变量)
字段包括:普通字段和静态字段,它们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同;
普通字段属于对象,静态字段属于类;
class Province: country = '中国' # 静态字段 def __init__(self, name): self.name = name # 普通字段 # 直接访问普通字段 obj = Province('河北省') print(obj.name) # 直接访问静态字段 Province.country
根据上述代码得出:【普通字段需要通过对象来访问】【静态字段通过类访问】,并且在使用上也可以看出普通字段和静态字段的归属是不同的,在内容的存储方式类似如下图:
由上图可是:静态字段在内存中只保存一份;普通字段在每个对象中都要保存一份;
小结:
- 静态字段属于类,普通字段属于对象;
- 静态字段也称类变量,普通字段也称实例变量;
- 访问静态字段时使用类方法进行访问,实在不方便时再使用对象去访问静态字段;
- 每个对象之间都是互不干扰的,也就是说当一个对象自我本身进行了修改,是不会影响另一个对象的,
应用场景:通过类创建对象时,如果每个对象都具有相同的字段且操作都相同的话,那么就使用静态字段
2.方法
方法包括:普通方法、静态方法、类方法,这三种方法在内存中都归属于类,区别在于调用方式不同。
普通方法:由对象调用,方法中至少一个self默认参数;执行普通方法时,自动将调用该方法的对象赋值给self参数;
静态方法:由类调用,无默认参数;
类方法:由类调用,至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls参数;
class Foo: def __init__(self, name): self.name = name def ord_func(self): """ 定义普通方法,至少有一个self参数 """ # print self.name print '普通方法' @classmethod def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print '类方法' @staticmethod def static_func(): """ 定义静态方法 ,无默认参数""" print '静态方法' # 调用普通方法 f = Foo() f.ord_func() # 调用类方法 Foo.class_func() # 调用静态方法 Foo.static_func()
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份;
不同点:方法调用者不同、调用方法时自动传入的参数不同;
小结:
- 普通方法:
普通方法也称实例方法,默认有一个self参数;
场景:放方法中需要使用对象中的一些参数时可以使用该方法;
调用:该方法由对象调用;
- 静态方法:
编写:在方法上方写 @staticmethod,方法的参数可有可无;
调用:类名.方法名() 或者 对象.方法名() 建议:静态方法使用类进行调用;
场景:当方法中无需调用对象中已封装的值的时候,可以使用这种静态方法;
- 类方法:
编写:在方法上写 @classmethod ,方法的参数至少有一个cls参数;
调用:类名.方法名(),默认会将当前类传到参数中;
场景:如果在方法中会使用到当前类中的一些数据,那么就可以使用类方法;
3.属性
python中的属性就是普通方法的变种;对于属性,有两个知识点:属性的基本使用、属性的两种定义方式。
1)属性的基本使用;
# ############### 定义 ############### class Foo: def func(self): pass # 定义属性 @property def prop(self): pass # ############### 调用 ############### foo_obj = Foo() foo_obj.func() foo_obj.prop #调用属性
属性的定义:在普通方法的基础上添加 @property 装饰器;属性仅有一个self参数
属性的调用:无需括号;就像调用字段一样;对象.方法 即可
属性的意义:访问属性时可以制造出和访问字段完全相同的假象;
应用场景:对于简单的方法,当无需传参且有返回值时,可以使用 @property
实例:
对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
a.根据用户请求的当前页和总数据条数计算出 m 和 n
b.根据m 和 n 去数据库中请求数据
class Pagenation(object): """ 处理分页相关的代码 """ def __init__(self,data_list,page,per_page_num=10): """ 初始化 :param data_list: 所有的数据 :param page: 当前要查看的页面 :param per_page_num: 每页默认要显示的数据行数 """ self.data_list = data_list self.page = page self.per_page_num = per_page_num @property def start(self): """ 计算索引的起始位置 :return: """ return (self.page-1) * self.per_page_num @property def end(self): """ 计算索引的结束位置 :return: """ return self.page * self.per_page_num def show(self): """ 通过索引获取列表内容,并通过for循环打印 """ result = self.data_list[self.start:self.end] for row in result: print(row) data_list = [] for i in range(1, 901): data_list.append('alex-%s' % i) while True: # 1. 要查看的页面 page = int(input('请输入要查看的页码:')) obj = Pagenation(data_list,page) obj.show()
从上述可见,Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回;
2)属性的两种定义方法
属性的定义有两种方式:
装饰器,即在方法上应用装饰器;
静态字段,即在类中定义值为property对象的静态字段;
2.类成员的修饰符
类的所有成员在上一步中已经做了介绍,对于每一个类的成员而言都有两种形式:
公有成员,在任何地方都可以访问;
私有成员,只有在类的内部才能访问;
注意:私有成员比较严格,只有在类的内部才能访问,就算是这个类的派生类也是无法方法基类的私有成员的;
两种成员的区别:
定义不同:私有成员命名时,前两个字符是下划线(特殊成员除外,比如:__init__、__call__、__dict__等);
注意:如果成员名称前只有一个下划线或者没有下划线,表示的是公有成员;
访问限制不同:我们拿字段来举例,每个字段都有公有成员和私有成员;
静态字段:
公有静态字段:类可以访问,类内部可以访问,派生类可以访问,对象可以访问,谁都可以访问;
私有静态字段:仅类内部可以访问;外部可以通过中间人进行访问,比如:在内存定义一个方法,这个方法里写入访问这个私有静态字段,然后在外部访问这个公有方法,通过公有方法访问私有静态字段;
class C: name = "公有静态字段" def func(self): print C.name class D(C): def show(self): print C.name C.name # 类访问 obj = C() obj.func() # 类内部可以访问 obj_son = D() obj_son.show() # 派生类中可以访问
class C: __name = "公有静态字段" def func(self): print C.__name class D(C): def show(self): print C.__name C.__name # 类访问 ==> 错误 obj = C() obj.func() # 类内部可以访问 ==> 正确 obj_son = D() obj_son.show() # 派生类中可以访问 ==> 错误
普通字段:
公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问;谁都可以访问;
私有普通字段:仅类内部可以访问;外部可以通过中间人进行访问,比如:在内存定义一个方法,这个方法里写入访问这个私有普通字段,然后在外部访问这个公有方法,通过公有方法访问私有普通字段;
注意:非要访问私有属性的话,可以通过 对象._类__属性名
class C: def __init__(self): self.foo = "公有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.foo # 通过对象访问 obj.func() # 类内部访问 obj_son = D(); obj_son.show() # 派生类中访问
class C: def __init__(self): self.__foo = "私有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.__foo # 通过对象访问 ==> 错误 obj.func() # 类内部访问 ==> 正确 obj_son = D(); obj_son.show() # 派生类中访问 ==> 错误
各成员私有和公有的实例:
class Foo: def __init__(self,name): self.name = name self.age = 123 def func(self): print(self.name) obj = Foo('zjk') print(obj.name) print(obj.age) obj.func()
class Foo: def __init__(self,name): # 私有实例变量(私有字段) self.__name = name self.age = 123 def func(self): print(self.__name) obj = Foo('zjk') print(obj.age) #obj.__name # 无法访问 obj.func() # 找一个内部人:func, 让func帮助你执行内部私有 __name
class Foo: country = "中国" def __init__(self): pass def func(self): # 内部调用 print(self.country) print(Foo.country) # 推荐 # 外部调用 print(Foo.country) obj = Foo() obj.func()
class Foo: __country = "中国" def __init__(self): pass def func(self): # 内部调用 print(self.__country) print(Foo.__country) # 推荐 # 外部无法调用私有类变量 # print(Foo.country) obj = Foo() obj.func()
class Base(object): __secret = "受贿" def zt(self): print(Base.__secret) class Foo(Base): def func(self): print(self.__secret) print(Foo.__secret) obj = Foo() obj.zt() #只能通过中间人进行访问,直接访问是行不通的;
class Foo(object): def __init__(self): pass def __display(self,arg): print('私有方法',arg) def func(self): self.__display(123) obj = Foo() # obj.__display(123) # 无法访问 obj.func()
class Foo(object): def __init__(self): pass @staticmethod def __display(arg): print('私有静态 方法',arg) def func(self): Foo.__display(123) @staticmethod def get_display(): Foo.__display(888) # Foo.__display(123) 报错 obj = Foo() obj.func() Foo.get_display()
3.类的特殊成员
1. __doc__
说明:表示类的描述信息
class Foo: """ 描述类信息,这是用于看片的神奇 """ def func(self): pass print Foo.__doc__ #输出:类的描述信息
class UserInfo: """ 这是一个用户信息类 """ pass print(UserInfo.__doc__) ------------------- 打印输出 ----------------------------------------------------- 这是一个用户信息类
2. __module__和__class__
说明:__module__ 表示当前操作的对象在那个模块;__class__ 表示当前操作的对象的类是什么
class C: def __init__(self): self.name = 'zjk'
from lib.aa import C obj = C() print obj.__module__ # 输出 lib.aa,即:输出模块 print obj.__class__ # 输出 lib.aa.C,即:输出类
3. __init__
说明:初始化方法,通过类创建对象时,自动触发执行。一般我们称之为用来初始化一个对象;
class Foo: def __init__(self, name): self.name = name self.age = 18 obj = Foo('wupeiqi') # 自动执行类中的 __init__ 方法
4. __del__
说明:析构方法,当对象在内存中被释放时,自动触发执行;
注意:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Foo: def __del__(self): pass
5. __call__
说明:对象后面加括号,触发执行;
注意:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的
例如:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print '__call__' obj = Foo() # 执行 __init__ obj() # 执行 __call__
class UserInfo: def __call__(self, *args, **kwargs): print(args,kwargs) obj = UserInfo() obj.__call__(1,2,3,4,5,a=6,b=7,c=8) --------------------- 输出结果 -------------------------------------------------- (1, 2, 3, 4, 5) {'a': 6, 'b': 7, 'c': 8}
6. __dict__
说明:类或对象中的所有成员;
class Province: country = 'China' def __init__(self, name, count): self.name = name self.count = count def func(self, *args, **kwargs): print 'func' # 获取类的成员,即:静态字段、方法、 print Province.__dict__ # 输出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None} obj1 = Province('HeBei',10000) print obj1.__dict__ # 获取 对象obj1 的成员 # 输出:{'count': 10000, 'name': 'HeBei'} obj2 = Province('HeNan', 3888) print obj2.__dict__ # 获取 对象obj1 的成员 # 输出:{'count': 3888, 'name': 'HeNan'}
7. __str__
说明:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值;
class Foo: def __str__(self): return 'zjk' obj = Foo() print obj # 输出:zjk
8. __getitem__ __setitem__ __delitem__
说明:用于索引操作,如字典、列表等等,以上分别表示获取、设置、删除数据。
class Foo(object): def __getitem__(self, key): print '__getitem__',key def __setitem__(self, key, value): print '__setitem__',key,value def __delitem__(self, key): print '__delitem__',key obj = Foo() result = obj['k1'] # 自动触发执行 __getitem__ obj['k2'] = 'wupeiqi' # 自动触发执行 __setitem__ del obj['k1'] # 自动触发执行 __delitem__
class UserInfo: def __init__(self,dic): self.dic = dic def __getitem__(self,item): print(item) return self.dic[item] def __setitem__(self,key,value): print(key,value) self.dic[key] = value print(self.dic) def __delitem__(self,key): print(key) del self.dic[key] print(self.dic) dic = {"K1":"V1","K2":"V2","K3":"V3"} zjk = UserInfo(dic) print(zjk["K1"]) zjk["K4"] = "V4" del zjk["K4"] ------------------------------ 打印输出 --------------------------------------- K1 V1 K4 V4 {'K1': 'V1', 'K2': 'V2', 'K3': 'V3', 'K4': 'V4'} K4 {'K1': 'V1', 'K2': 'V2', 'K3': 'V3'}
class UserInfo: def __init__(self,dic): self.dic = dic def __getitem__(self,item): print(item) return self.dic[item] def __setitem__(self,key,value): print(key,value) self.dic[key] = value print(self.dic) def __delitem__(self,key): print(key) del self.dic[key] print(self.dic) li = [1,2,3,4,5,6,7,7,8,9] zjk = UserInfo(li) print(zjk[0]) zjk[4] = "V4" del zjk[9] -------------------------------- 打印输出 ------------------------------------ 0 1 4 V4 [1, 2, 3, 4, 'V4', 6, 7, 7, 8, 9] 9 [1, 2, 3, 4, 'V4', 6, 7, 7, 8]
9. __getslice__ __setslice__ __delslice__
说明:该三个方法用于分片操作,如列表,以上分别表示获取、设置、删除数据
注意:__getslice__ ,使用slice对象作为__getitem__()的参数。Python 3.0以上已废弃该方法。
class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自动触发执行 __getslice__ obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__ del obj[0:2] # 自动触发执行 __delslice__
10. __iter__
说明:用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了__iter__;
三步整明白这个iter.
## 第一步:如果一个类中没有定义__iter__,那么在for循环这个类的对象时,会发生什么? class Foo(object): pass obj = Foo() for i in obj: print(i) ------------------ 打印结果 ------------------------------------------------------ TypeError: 'Foo' object is not iterable #提示这个Foo的对象不是一个可迭代对象;
## 第二步,在类中添加__iter__方法,但不设置参数,然后继续打印结果; class Foo(object): def __iter__(self): pass obj = Foo() for i in obj: print(i) ------------------------------- 打印结果 --------------------------------------- TypeError: iter() returned non-iterator of type 'NoneType' #提示 类型错误:iter()返回类型为“NoneType”的非迭代器
##第三步,在类中添加__iter__方法,且让这个方法返回一些数据; class Foo(object): def __init__(self,sq): self.sq = sq def __iter__(self): return iter(self.sq) obj = Foo([1,2,3,4,5]) for i in obj: print(i) ----------------------------- 打印结果 ------------------------------------------- 1 2 3 4 5
以上三步可以看出,for循环迭代的其实是iter([1,2,3,4,5]),所以可以变更一下执行流程
obj = iter([1,2,3,4,5]) for i in obj: print(i) ------------------------------ 输出结果 ------------------------------------------ 1 2 3 4 5
11. __new__ 和 __init__
说明:上面我们已经说过__init__用来初始化对象,那么对象是由谁来创建的呢?对象是由__new__来创建的。
class Foo(object): def __init__(self, a1, a2): # 初始化方法 """ 为空对象进行数据初始化 :param a1: :param a2: """ self.a1 = a1 self.a2 = a2 def __new__(cls, *args, **kwargs): # 构造方法 """ 创建一个空对象 :param args: :param kwargs: :return: """ return object.__new__(cls) # Python内部创建一个当前类的对象(初创时内部是空的.). obj1 = Foo(1,2) print(obj1) obj2 = Foo(11,12) print(obj2)
如何验证__new__是用来创建空对象的;
class Foo(object): def __init__(self, a1, a2): # 初始化方法 """ 为空对象进行数据初始化 :param a1: :param a2: """ self.a1 = a1 self.a2 = a2 def __new__(cls, *args, **kwargs): # 构造方法 """ 创建一个空对象 :param args: :param kwargs: :return: """ v = object.__new__(cls) # Python内部创建一个当前类的对象(初创时内部是空的.). print(v) print(v.__dict__) return v obj1 = Foo(1,2) print(obj1) print(obj1.__dict__) ---------------------------- 打印输出 ----------------------------------------- <__main__.Foo object at 0x000000000250FCC0> {} <__main__.Foo object at 0x000000000250FCC0> {'a1': 1, 'a2': 2}
说明:
我们在__new__方法中创建一个v用来接收对象的空间地址,也就是用V来引用对象的内存地址,然后我们使__new__方法返回这个空对象,当外部进行实例化时,先调用__new__生成一个空对象然后调用__init__方法对这个空对象进行初始化操作,通过结果对比两个对象的值是一致的,说明是__new__创建对象的,__init__用来初始化这个对象。
12. __add__
说明:两个对象进行计算时,比如: 对象 + 对象 时,会调用第一个对象的类里的__add__方法,并将第二个对象作为参数传入到__add__ 方法中进行计算;
class Foo: def __init__(self,a1,a2): self.a1 = a1 self.a2 = a2 def __add__(self, other): return self.a1 + other.a2 obj1 = Foo(12,34) obj2 = Foo(56,89) ret = obj1 + obj2 print(ret) ---------------------------------- 输出结果 -------------------------------------- 101
class Foo: def __init__(self,a1,a2): self.a1 = a1 self.a2 = a2 def __add__(self, other): return self.a1 + other.a2 class F1: def __init__(self,a2): self.a2 = a2 obj1 = Foo(12,34) f1 = F1(100) ret = obj1 + f1 print(ret) --------------------------------- 输出结果 ------------------------------------ 112
13. with 对象
说明:类似装饰器那样的效果,在某段代码的前后执行一个对象的__enter__ 和 __exit__方法;具体看例子。
class Foo: def __init__(self,a,b): self.a = a self.b = b def __enter__(self): print(self.a) return 888 def __exit__(self, exc_type, exc_val, exc_tb): print(self.b) obj = Foo(1,2) with obj as f: print(f) print("hello world") -------------------------------------- 输出结果 ------------------------------ 1 888 hello world 2
4.类的组合
软件重用的重要方式除了继承之外还有另外一种方式,-- 组合;
组合是指,在一个类中以另一个类的对象作为数据属性,称之为组合;
举个例子:学校有很多,老师也有很多,不同的学校、老师都是一个个体,请用类创建出三个学校和三个老师,并将老师和学校进行组合。
""" 创建三个学校且三个学校的设施内容等都是一致. """ class School(object): def __init__(self, name, address): self.name = name self.address = address def speech(self): print('讲课') obj1 = School('老男孩北京校区', '美丽富饶的沙河') obj2 = School('老男孩上海校区', '浦东新区') obj3 = School('老男孩深圳校区', '南山区') class Teacher(object): def __init__(self, name, age, salary): self.name = name self.age = age self.__salary = salary self.school = None #刚开始老师还没有找到学校呢 t1 = Teacher('李杰', 19, 188888) t2 = Teacher('艳涛', 18, 60) t3 = Teacher('女神',16, 900000) # ############## 老师分配校区 t1.school = obj1 t2.school = obj1 t3.school = obj2 # #################################### # 查看t1老师,所在的校区名称/地址 print(t1.school.name) print(t1.school.address) print(t1.name) print(t1.age) t1.school.speech() -----------------输出结果------------------------------------------------------ 老男孩北京校区 美丽富饶的沙河 李杰 19 讲课 #当然,我们也可以让学校去选择老师;
class UserInfo(object): pass class Department(object): pass class StarkConfig(object): def __init__(self,num): self.num = num def changelist(self,request): print(self.num,request) def run(self): self.changelist(999) class RoleConfig(StarkConfig): def changelist(self,request): print(666,self.num) class AdminSite(object): def __init__(self): self._registry = {} def register(self,k,v): self._registry[k] = v(k) site = AdminSite() site.register(UserInfo,StarkConfig) site.register(Department,StarkConfig) print(len(site._registry)) # 3 for k,row in site._registry.items(): row.run() -----------------------打印输出 -------------------------------------------- 2 <class '__main__.UserInfo'> 999 <class '__main__.Department'> 999
5.主动调用其他类的成员
如何在每个类里主动的去调用另一个类呢;看例子说明;
class Base(object): def f1(self): print('5个功能') class Foo(object): def f1(self): print('3个功能') obj = Foo() obj.f1()
那么,如果我现在既想执行自身类Foo的f1,又想执行Base类中的f1,那么代码该如何实现呢?
## 方法一:直接使用Base类进行调用Base类中的成员 class Base(object): def f1(self): print('5个功能') class Foo(object): def f1(self): print('3个功能') Base.f1(self) obj = Foo() obj.f1() --------------------------------打印结果------------------------------------------ 3个功能 5个功能
### 方法二:super,按照类的继承顺序,找下一个; class Base(object): def f1(self): print('5个功能') class Foo(Base): def f1(self): super().f1() print('3个功能') obj = Foo() obj.f1()
那么,我们在看一道题,自己琢磨一下;
class Foo(object): def f1(self): super().f1() print('3个功能') class Bar(object): def f1(self): print('6个功能') class Info(Foo,Bar): pass obj = Info() obj.f1() ##打印结果你知道了吗?
三、其他
1.和类相关的几个内置函数
1)issubclass
格式:issubclass(class1,class2)
说明:判断class1是不是class2的子类或子子类;
class Base(object): pass class Foo(Base): pass class Bar(Foo): pass print(issubclass(Bar,Base)) ---------------- 输出结果 -------------------------------------------------------- True
2)type
格式:type(obj)
说明:获取当前对象是由哪个具体的类创建的;
class Foo(object): pass obj = Foo() print(obj,type(obj)) # 获取当前对象是由那个类创建。 if type(obj) == Foo: print('obj是Foo类型') --------------- 打印输出 --------------------------------------------------------- obj是Foo类型
class Foo(object): pass class Bar(object): pass def func(*args): foo_counter =0 bar_counter =0 for item in args: if type(item) == Foo: foo_counter += 1 elif type(item) == Bar: bar_counter += 1 return foo_counter,bar_counter v1,v2 = func(Foo(),Bar(),Foo()) print(v1,v2) -----------------------------打印输出 -------------------------------------- 2 1
3)isinstance
格式:isinstance(obj,class)
说明:判断obj对象是不是class这个类或父类的实例;
class Base(object): pass class Foo(Base): pass obj1 = Foo() print(isinstance(obj1,Foo)) print(isinstance(obj1,Base)) obj2 = Base() print(isinstance(obj2,Foo)) print(isinstance(obj2,Base)) ------------------------ 打印输出 ------------------------------------------------ True True False True
2.函数和方法区别以及如何判断
我们平时所说,如果一个def在类中,我们称之为方法,否则我们称之为函数,其实这样的说法不太精确;那么应该如果的科学的判断是函数还是方法呢?
在python中有一个模块中的两个数值专门用来判断是数据是函数还是方法,那就是 types模块中的MethodType以及FunctionType.
MethodType用来判断数据是不是方法,FunctionType用来判断数据是不是函数。
from types import MethodType,FunctionType def check(arg): """ 检查arg是方法还是函数 :param arg: :return: """ if isinstance(arg,MethodType): print("%s是一个方法" % arg) elif isinstance(arg,FunctionType): print("%s是一个函数" % arg) else: print("不是函数也不是方法!") def func(): pass class Foo(object): def detail(self): pass @staticmethod def kkk(): pass obj = Foo() check(func) check(obj.detail) check(Foo.detail) check(obj.kkk) check(Foo.kkk) ------------------------------------ 输出结果 -------------------------------- <function func at 0x00000000025470D0>是一个函数 <bound method Foo.detail of <__main__.Foo object at 0x00000000022B3470>>是一个方法 <function Foo.detail at 0x0000000002547048>是一个函数 <function Foo.kkk at 0x00000000025476A8>是一个函数 <function Foo.kkk at 0x00000000025476A8>是一个函数
由上面例子我们得出结论:判断某个数据是不是函数或方法,主要就看这个数据的调用者,如果数据的调用者是一个对象,那么这个数据就是一个方法;否则则是一个函数;
class Foo(object): def f1(self): pass def f2(self): pass def f3(self): pass list_display = [f1,f2] def __init__(self): pass for item in Foo.list_display: print(type(item)) ---------------------------------- 打印输出 ----------------------------------- <class 'function'> <class 'function'>
class Foo(object): def f1(self): pass def f2(self): pass def f3(self): pass list_display = [f1,f2] obj = Foo() Foo.list_display.append(obj.f3) for item in Foo.list_display: print(item) ----------------------- 输出结果 ------------------------------------------- <function Foo.f1 at 0x00000000025A60D0> <function Foo.f2 at 0x00000000025A6268> <bound method Foo.f3 of <__main__.Foo object at 0x000000000252FCC0>>
判断一个对象是否可以被调用;使用callable内置函数;
注意:这里的对象指的是python中所有事物,因为世间万物一切皆对象,所以这里的对象表示的是一切的意思
def func(): pass class Foo(object): def __call__(self, *args, **kwargs): pass def func(self): pass obj = Foo() print(callable(func)) print(callable(Foo)) print(callable(obj)) print(callable(obj.func)) ------------------------ 打印输出 ----------------------------------------------- True True True True
3.反射
反射的定义:主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省),这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
python面向对象中的反射:
通过字符串的形式操作对象相关的属性,python中的一切事物都是对象(都可以使用反射)。
关于四个可以实现自省的函数:hasattr(obj,name) getatter(obj,name,default=None) setattr(x,y,v) delattr(x,y)
1)hasattr(obj,name)
说明:判断对象中有没有一个name字符串对应的方法或属性;
注意:此处对象泛指一切,一切皆对象,下文的对象也是如此。
class Foo: def kkk(self): pass obj = Foo() print(hasattr(obj,"kkk")) print(hasattr(Foo,"kkk")) ------------------ 输出结果 ----------------------------------------------------- True True
2)getatter(obj,name,default=None)
说明:用于返回一个对象的属性值;
class Foo: def kkk(self): pass obj = Foo() print(getattr(obj,"kkk")) print(getattr(Foo,"kkk")) -------------------------- 输出打印 --------------------------------------------- <bound method Foo.kkk of <__main__.Foo object at 0x000000000250FCC0>> <function Foo.kkk at 0x00000000025850D0>
3)setattr(obj,name,value)
说明:根据字符串的形式动态的设置一个对象成员(仅在内存中修改,不会改变文件)
class Foo: def kkk(self): pass obj = Foo() setattr(obj,"aaa",456) print(getattr(obj,"aaa")) ------------------ 输出结果 ----------------------------------------------------- 456
4)delattr(obj,name)
说明:根据字符串的形式,动态的删除一个成员(仅在内存中修改,不会改变文件)
class Foo: def kkk(self): pass obj = Foo() setattr(obj,"aaa",456) print(getattr(obj,"aaa")) delattr(obj,"aaa") print(hasattr(obj,"aaa")) ------------------------- 输出结果 ---------------------------------------------- 456 False
关于这四个函数的一个练习题:用户在选择了一个函数后,如何快速找到此函数
class Account(object): func_list = ['login', 'logout', 'register'] def login(self): """ 登录 :return: """ print('登录111') def logout(self): """ 注销 :return: """ print('注销111') def register(self): """ 注册 :return: """ print('注册111') def run(self): """ 主代码 :return: """ print(""" 请输入要执行的功能: 1. 登录 2. 注销 3. 注册 """) choice = int(input('请输入要执行的序号:')) func_name = Account.func_list[choice-1] func = getattr(self, func_name) # self.login func() obj1 = Account() obj1.run() obj2 = Account() obj2.run() ----------------------- 输出结果 --------------------------------------------- 请输入要执行的功能: 1. 登录 2. 注销 3. 注册 请输入要执行的序号:3 注册111 请输入要执行的功能: 1. 登录 2. 注销 3. 注册 请输入要执行的序号:2 注销111
反射的好处:
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr
f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑')
好处二:动态导入模块(基于反射当前模块成员)
4.约束;
什么叫约束:
字面意思就是,限制使其不越出范围;
python中的约束:用于约束其派生类,保证派生类中必须要编写我们指定的方法,不然的话执行就会报错;用于完成具体业务逻辑;
为什么要有约束,约束产生的背景:
背景一:比如公司要开发一个告警功能,当服务器的内存使用率达到80%时,可以选择通过邮箱、短信或者微信进行告警发送,面对这三个发送功能,里面必须要有一个信息发送方法,如果我们不进行约束,那么可能,每个人的发送方法都不一样,比如微信定义了send方法,邮箱定义了sen方法,短信定义了upload功能,尽管这三种方法可能对于开发者来说都是发送信息用于,可是当这几个功能在加入总项目中就会差生问题,因为每个人定义的方法名称不一样,因此,我们有必要约束一下,我们写一个父类,里面加入一个send方法,方法下面写上主动触发异常代码,然后让这三个类,短信类,微信类,邮箱类都继承这个父类,这样的话,只要谁的类里没有send放法在执行时就会报错。这样就约束了大家必须使用send方法来作为发送信息的方法名称;
背景二:当有新员工入职时,我们的代码使用了约束功能后,就可以不用去对新员工进行培训了,员工一看就知道这是约束的什么方法,在哪个类里必须要使用这个方法等等。
python中使用约束的两种方式:
a.通过主动发出异常来约束,建议使用此方法; b.通过抽象类以及抽象方法来进行约束;
class BaseMessage(object): def send(self): """ 必须继承BaseMessage,然后其中必须编写send方法。用于完成具体业务逻辑。 """ raise NotImplementedError(".send() 必须被重写.") class Email(BaseMessage): def send(self): print("发送邮件") # 发送邮件 def f1(self): pass def f2(self): pass class Wechat(BaseMessage): # def send(self): # print("发送微信") # 发送微信 def f1(self): pass def f2(self): pass class Msg(BaseMessage): def send(self): print("发送短信") # 发送短信 def f1(self): pass def f2(self): pass def func(arg): """ 报警通知的功能 """ arg.send() obj = Msg() func(obj) obj1 = Wechat() func(obj1) ---------------------------------------- 输出结果 --------------------------- Traceback (most recent call last): File "E:/python/s15/面向对象.py", line 654, in <module> func(obj1) File "E:/python/s15/面向对象.py", line 648, in func arg.send() File "E:/python/s15/面向对象.py", line 608, in send raise NotImplementedError(".send() 必须被重写.") NotImplementedError: .send() 必须被重写. 发送短信 #### #可以看出,这几个类都继承了BaseMessage类,BaseMessage类中定义了send方法并且主动异常,如果派生类中没有send方法则执行时会报错。
from abc import ABCMeta,abstractmethod class BaseMessage(metaclass=ABCMeta): def zjk(self): print(666) @abstractmethod def send(self): """ 抽象方法中是不能写代码的,所以是pass """ pass class Email(BaseMessage): def send(self): print("发送邮件") # 发送邮件 def f1(self): self.zjk() class Wechat(BaseMessage): # def send(self): # print("发送微信") # 发送微信 def f1(self): pass def f2(self): pass class Msg(BaseMessage): def send(self): print("发送短信") # 发送短信 def f1(self): self.zjk() def func(arg): """ 报警通知的功能 """ arg.f1() arg.send() obj = Msg() func(obj) obj1 = Wechat() func(obj1) --------------------------------- 输出结果 ------------------------------------ 666 Traceback (most recent call last): 发送短信 File "E:/python/s15/面向对象.py", line 652, in <module> obj1 = Wechat() TypeError: Can't instantiate abstract class Wechat with abstract methods send #类型错误:不能用抽象方法send实例化抽象类微信
两种方法的区别:
主动抛异常这个简单,无非是都继承父类,在父类中定义必须要编写的指定方法,如果派生类中没有的话就会调用父类中的方法,就会引发异常。
抽象类抽象方法,则是引用了一个模块,设置父类为抽象类,并在类中指定一个派生类必须编写的方法为抽象方法,如果派生类中没有此方法就会报错,关键是在抽象类中也可以定义普通类来供派生类的正常调用;
#这两种语言规定了:接口中不允许在方法内部写代码,只能约束继承它的类必须实现接口中定义的所有方法。就好比python中的抽象类一样; interface IFoo: def f1(self,x1):pass def f2(self,x1):pass interface IBar: def f3(self,x1):pass def f4(self,x1):pass class Foo(IFoo,IBar):# 实现了2个接口 def f1(self,x1):pass def f2(self,x1):pass def f3(self,x1):pass def f4(self,x1):pass ## #java没有多继承。可以通过接口方式进行多个类的调用;
小结:
- 接口时一种数据类型,主要用于约束派生类中必须实现指定的方法,Python中不存在,Java和C#中是存在的。
- python中可以使用 抽象类+抽象方法来进行约束,编写上比较麻烦;建议使用人为主动抛出异常方法来进行约束;
- 约束时主动抛出异常使用专业术词:raise NotImplementedError(".send() 必须被重写.");当然其他的异常信号也可以写,只不过不专业而已,比如:raise Exception(".send() 必须被重写.")
- 当有多个类时,内部都必须有某些方法时,需要使用基类+异常进行约束;
抽象类概念:
抽象类是不完整的,并且它只能用作基类。它与非抽象类的不同:
1、抽象类不能直接实例化,并且对抽象类使用 new 运算符是编译时错误。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为 null,或者含有对非抽象类的实例的引用(此非抽象类是从抽象类派生的)。
2、允许(但不要求)抽象类包含抽象成员。
3、抽象类不能被密封。
5.继承中的c3算法
先说说python中类的种类,python中有经典类和新式类之分;
python2中有经典类和新式类,python3中只有新式类;
经典类和新式类的区别:
经典类,一条道走到黑(深度优先);
新式类,C3算法实现(python2.3更新时c3算法);
注意:super是遵循__mro__执行顺序。
C3算法怎么用?
获取第一个表头 和 其他表位进行比较;不存在则拿走;如果存在,则放弃,然后获取第二个表的表头再次和其他表的表尾进行比较。
class A(object): pass class B(A): pass class C(B): pass class D(object): pass class E(D,C): pass class F(object): pass class G(F): pass class H(C,G): pass class Foo(E,H): pass # print(E.__mro__) # print(H.__mro__) """ L(Foo + L(E) + L(H) ) L(E) = E,D,C,B,A,object L(H) = H,C,B,A,G,F,object Foo = (object) + (G,F,object) Foo,E,D,H,C,B,A,G,F,object """ print(Foo.__mro__)