Python之路第11章:Python面向对象
Posted 重启试试
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 = F以上是关于Python之路第11章:Python面向对象的主要内容,如果未能解决你的问题,请参考以下文章