总结不易,希望学习的小伙伴,认真看(内容比较多)
希望对你有帮助
Python 面向对象编程
1.面向过程:
- 核心是"过程"二字
- 过程的终极奥义就是将程序流程化
- 过程是"流水线",用来分步骤解决问题的
2.面向对象:
- 核心是"对象"二字
- 对象的终极奥义就是将程序"整合"
- 对象是"容器",用来盛放数据与功能的
- 类也是"容器",该容器用来存放同类对象共有的数据与功能
比如要实现一个贪吃蛇,那面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏
2、随机生成食物
3、绘制画面
4、接收输入并改变方向
5、判断是否碰到墙壁和食物等
6、…
而用面向对象的思路则是:
- 首先,将整个游戏拆解为一个个的实体:蛇、食物、障碍物、规则系统、动画系统。
- 然后分别去实现这些实体应该具有的功能(即成员函数),然后你还要考虑不同实体之间如何交互和传递消息,说白了就是调用关系和传参。
比如规则系统接收蛇、食物、障碍物作为参数,可以判定是否吃到食物或者碰到墙壁。 - 动画系统则可以接收蛇、食物、障碍物等作为参数,然后在屏幕上动态的显示出来。
- …
这样做的好处便是,可以利用面向对象有封装、继承、多态性的特性,设计出低耦合的系统,使系统更加灵活、更加易于维护。
在程序中认为一切皆为对象,都可以代码化。了解过程序常识的人都知道,程序员总是说没有对象怎么办?没有对象new一个!!这其实是Java语言中实例化对象的一个梗,Python被人称为最优美、最简洁的语言,实例化对象以及类的定义确实比Java简单一点。
简单说点:
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 实例变量:定义在方法中的变量,只作用于当前实例的类。
- 继承:即一个派生类(derived class)继承基类(base
class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待. - 实例化:创建一个类的实例,类的具体对象。
- 方法:类中定义的函数。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
3.什么是类(class)?
- 类是就是对象,也就是数据与功能的集合体
- 所以类体中最常见的是变量与函数的定义,但是类体其实是可以包含任意其他代码的
注意:类体代码是在类定义阶段就会立即执行,会产生类的名称空间
创建类
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) #引用人的走路方法,注意,这里不是在调用
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
实例化的过程就是类——>对象的过程
原本我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。
语法:对象名 = 类名(参数)
egg = Person('egon') #类名()就等于在执行Person.__init__()
#执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
查看属性和调用方法
print(egg.name) #查看属性直接 对象名.属性名
print(egg.walk()) #调用方法,对象名.方法名()
类属性的补充:
一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
所以,面向对象编程就这样子:
class 类名:
def __init__(self,参数1,参数2):
self.对象的属性1 = 参数1
self.对象的属性2 = 参数2
def 方法名(self):
pass
def 方法名2(self):
pass
对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西
#类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
#括号里传参数,参数不需要传self,其他与init中的形参一一对应
#结果返回一个对象
对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
类的专有方法:
- init: 构造函数,在生成对象时调用
- del: 析构函数,释放对象时使用
- repr: 打印,转换
- setitem : 按照索引赋值
- getitem: 按照索引获取值
- len: 获得长度
- cmp: 比较运算
- call: 函数调用
- add: 加运算
- sub: 减运算
- mul: 乘运算
- div: 除运算
- mod: 求余运算
- pow: 乘方
4.类命名空间与对象、实例的命名空间
- 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
静态属性就是直接在类中定义的变量
动态属性就是定义在类中的方法
- 其中类的数据属性是共享给所有对象的
而类的动态属性是绑定到所有对象的
5.面向对象的组合用法
软件重用的重要方式除了继承之外还有另外一种方式,即:组合 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
看代码: 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
class BirthDate:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
class Couse:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
class Teacher:
def __init__(self,name,gender,birth,course):
self.name=name
self.gender=gender
self.birth=birth
self.course=course
def teach(self):
print('teaching')
p1=Teacher('egon','male',
BirthDate('1995','1','27'),
Couse('python','28000','4 months')
)
print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
'''
运行结果:
1 27
python 28000 4 months
'''
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
6.面向对象的三大特性
6.1 继承
什么是继承??
- 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
查看继承
print(SubClass1.__bases__) #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__)
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
print(ParentClass1.__bases__)
(<class 'object'>,)
print(ParentClass2.__bases__)
(<class 'object'>,)
继承与重用性
==========================第一部分
例如
猫可以:吃、喝、爬树
狗可以:吃、喝、看家
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
#猫和狗有大量相同的内容
class 猫:
def 吃(self):
# do something
def 喝(self):
# do something
def 爬树(self):
# do something
class 狗:
def 吃(self):
# do something
def 喝(self):
# do something
def 看家(self):
#do something
==========================第二部分
上述代码不难看出,吃、喝是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝
猫:爬树(猫继承动物的功能)
狗:看家(狗继承动物的功能)
伪代码如下:
class 动物:
def 吃(self):
# do something
def 喝(self):
# do something
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
def 爬树(self):
print '喵喵叫'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
def 看家(self):
print '汪汪叫'
==========================第三部分
#继承的代码实现
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '猫'
def climb(self):
print('爬树')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def look_after_house(self):
print('汪汪叫')
######### 执行
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
- 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
- 我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
派生
- 当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Animal:
'''
人和狗都是动物,所以创造一个Animal基类
'''
def __init__(self, name, aggressivity, life_value):
self.name = name # 人和狗都有自己的昵称;
self.aggressivity = aggressivity # 人和狗都有自己的攻击力;
self.life_value = life_value # 人和狗都有自己的生命值;
def eat(self):
print('%s is eating'%self.name)
class Dog(Animal):
'''
狗类,继承Animal类
'''
def bite(self, people):
'''
派生:狗有咬人的技能
:param people:
'''
people.life_value -= self.aggressivity
class Person(Animal):
'''
人类,继承Animal
'''
def attack(self, dog):
'''
派生:人有攻击的技能
:param dog:
'''
dog.life_value -= self.aggressivity
egg = Person('egon',10,1000)
ha2 = Dog('二愣子',50,1000)
print(ha2.life_value)
print(egg.attack(ha2))
print(ha2.life_value)
注意:像ha2.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
- 在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.
在python3中,子类执行父类的方法也可以直接用super方法.
class A:
def hahaha(self):
print('A')
class B(A):
def hahaha(self):
super().hahaha()
#super(B,self).hahaha()
#A.hahaha(self)
print('B')
a = A()
b = B()
b.hahaha()
super(B,b).hahaha()
结论:
继承的作用:
减少代码的重用
提高代码可读性
规范编程模式
几个名词:
抽象:抽象即抽取类似或者说比较像的部分。是一个从具题到抽象的过程。
继承:子类继承了父类的方法和属性
派生:子类在父类方法和属性的基础上产生了新的方法和属性
抽象类与接口类 (可以学,但是我不想写了)
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口
2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
钻石继承:(可以学,但是我不想写了)
新式类:广度优先 (Python3 版本继承用广度优先)
经典类:深度优先 (Python2 版本继承用深度优先)
6.2 多态
多态指的是一类事物有多种形态
动物有多种形态:人,狗,猪
import abc
#abc 这个模块懂吧? 它是在代码中定义和使用抽象基类进行API检查。可以去百度~
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')
多态性
什么是多态动态绑定?(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
在面向对象方法中一般是这样表述多态性:
向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。
也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用,然而它们并没有直接的关系
class TxtFile: #Txt类有两个与文件类型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class DiskFile:#Disk类也有两个与文件类型同名的方法:read和write
def read(self):
pass
def write(self):
pass
6.3 封装
【封装】
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
- 将变化隔离;
- 便于使用;
- 提高复用性;
- 提高安全性;
【封装原则】
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
- 在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
私有变量
#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
- 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
- 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
- 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
- 变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
私有方法
- 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定义成私有的,即__fa
>>> class A:
... def __fa(self): #在定义时就变形为_A__fa
... print('from A')
... def test(self):
... self.__fa() #只会与自己所在的类为准,即调用_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
封装与扩展性
- 封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()
property属性
什么是特性property?
- property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
为什么要用property?
- 将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')
@AAA.setter
def AAA(self,value):
print('set的时候运行我啊')
@AAA.deleter
def AAA(self):
print('delete的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
---------------上下对比----------------------
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self,value):
print('set的时候运行我啊')
def delete_AAA(self):
print('delete的时候运行我啊')
AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
绑定方法与非绑定方法
类中定义的函数分为两大类:绑定方法和非绑定方法
-
其中绑定方法又分为绑定到对象的对象方法和绑定到类的类方法。
-
在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
-
对象方法之前说过了,这里就。类方法通常用来在__init__的基础上提供额外的初始化实例的方式
# 配置文件settings.py的内容
HOST='127.0.0.1'
PORT=3306
`
# 类方法的应用
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls): # 从配置文件中读取配置进行初始化
return cls(settings.HOST,settings.PORT)
>>> MySQL.from_conf # 绑定到类的方法
<bound method MySQL.from_conf of <class ‘__main__.MySQL'>>
>>> conn=MySQL.from_conf() # 调用类方法,自动将类MySQL当作第一个参数传给cls
- 绑定到类的方法就是专门给类用的,但其实对象也可以调用,只不过自动传入的第一个参数仍然是类,
- 也就是说这种调用是没有意义的,并且容易引起混淆,这也是Python的对象系统与其他面向对象语言对象系统的区别之一,比如Smalltalk和Ruby中,绑定到类的方法与绑定到对象的方法是严格区分开的。
非绑定方法
- 为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它
- 但它就是一个普通函数而已,因而没有自动传值那么一说,遵循函数玩法
import uuid
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id():
return uuid.uuid1()
>>> conn=MySQL(‘127.0.0.1',3306)
>>> print(conn.id) #100365f6-8ae0-11e7-a51e-0088653ea1ec
# 类或对象来调用create_id发现都是普通函数,而非绑定到谁的方法
>>> MySQL.create_id
<function MySQL.create_id at 0x1025c16a8>
>>> conn.create_id
<function MySQL.create_id at 0x1025c16a8>
总结:
绑定方法与非绑定方法的使用:若类中需要一个功能,该功能的实现代码中需要引用对象则将其定义成对象方法、
需要引用类则将其定义成类方法、无需引用类或对象则将其定义成静态方法。