Python 支持面向过程、面向对象、函数式编程等多种编程范式,且不强制我们使用任何一种编程范式,我们可以使用过程式编程编写任何程序,在编写小程序时,基本上不会有问题.但对于中等和大型项目来说,面向对象将给我们带来很多优势.接下来将结合面向对象的基本概念和Python语法的特性讲解面向对象的编程.
## 面向对象编程简介
面向对象,OOP英文全称(Object-Oriented Programming)
是一种高效的编程思路,面向对象技术已经成为当今软件设计和开发领域的主流技术,面向对象编程是一种编程方式,使用 "类" 和 "对象" 来实现,所以面向对象编程其实就是对 "类" 和 "对象" 的使用,类就是一个模板,模板里可以包含多个方法(函数),方法里实现各种各样的功能,对象则是根据模板创建的实例,通过实例对象可以执行类中的方法,每个对象都拥有相同的方法,但各自的数据可能不同.
在面向对象的设计中,程序员可以创建任何新的类型,这些类型可以描述每个对象包含的数据和特征,类是一些对象的抽象,隐藏了对象内部复杂的结构和实现,类由变量和函数两部分构成,类中的变量称为数据成员
,类中的函数称为成员函数
,类是对客观事物的抽象,而对象是类的实例化后的实体.
简单点说,"面向对象"是一种编程范式,而编程范式是按照不同的编程特点总结出来的编程方式,俗话说,条条大路通罗马,也就说我们使用不同的方法都可以达到最终的目的,但是有些办法比较快速、安全且效果好,有些方法则效率低下且效果不尽人意.同样,编程也是为了解决问题,而解决问题可以有多种不同的视角和思路,前人把其中一些普遍适用且行之有效的编程模式归结为"范式",常见的编程范式有:
● 面向过程编程:OPP(Procedure Oriented Programing)
● 面向对象编程:OOP(Object Oriented Programing)
● 面向函数编程:FP(Functional Programing)
面向对象编程有3个目标:重用性、灵活性、扩展性,而目标的几个主要特点:
● 封装:可以隐藏实现细节,使代码模块化,屏蔽代码的实现细节,对外只提供接口
● 继承:可以通过扩展已存在的类来实现代码重用,避免重复编写相同的代码
● 多态:多态是为了实现接口重用,使得多个不同的类之间的灵活调用
说到这里,我们已经把面向对象的基本作用,特性,应用场景介绍完了,这里只是个人笔记,无法做到面面俱到,如果想要深入了解,请自行去看一些学习手册,以上内容大部分摘抄自书籍中的重点,接下来我们将围绕封装,继承,多态
等面向对象核心概念进行说明.
面向对象—>封装
封装是面向对象的主要特征之一,是对象和类概念的主要特性.简单的说,类就是封装了数据以及操作这些数据的方法的逻辑实体,它向外暴露部分数据和方法,屏蔽具体的实现细节,除此之外,在一个对象内部,某些数据或方法可以是私有的,这些私有的数据或方法是不允许外界访问的.通过这种方式,对象对内部数据提供了不同级别的保护.
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏,封装一般是通过在类中封装数据,而通过对象或者self获取,和其他面向对象的语言类似,也是通过构造函数来进行数据封装,说白了就是,将一些功能相近的函数写在一起,方便我们的调用与维护.
◆实现简单的类封装◆
在Python中,定义类是通过class关键字
,class后面紧接着是类名,类名通常是大写开头的单词,紧接着是()小括号
,小括号内可以写(要继承的类名称)
,表示该类是从哪个类继承下来的,可以有多个父类(基类)
,通常如果没有合适的继承类,就使用object类
,这是所有类最终都会继承的类,也可以不写,不写的话默认也是加载的.
>>> import sys
>>> import os
>>>
>>> class lyshark(object):
... def __init__(self,name,age,sex):
... self.name = name
... self.age = age
... self.sex = sex
...
>>>
>>> temp=lyshark("lyshark","22","Man")
>>> print("姓名:%s 年龄:%s 性别:%s"%(temp.name,temp.age,temp.sex))
姓名:lyshark 年龄:22 性别:Man
以上就是创建的一个lyshark
类,如上所示__init__()
叫做初始化方法(或构造方法),在类实例化时,这个方法(虽然它是函数形式,但在类中就不叫函数了叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的__init__(self,name,age,sex)
就是要在创建一个角色时给它设置这些属性,也就是做一些初始化赋值工作.
呈上参数
self
的工作流程是这样的.● 在内存中开辟一块空间指向lyshark这个变量名,也就是相当于一个指针函数
● 实例化这个类首先执行其中的__init__()
,执行后会自动的将参数传递给内部变量
● 然后自动执行__init__()
构造方法,开辟内存空间,此时self.* = *
两个变量数据一致了
创建对象的过程称为实例化,还是看如上代码,temp=lyshark()
这一句话就是将lyshark类
实例化,当一个对象被创建后,包含3个方面的特性:对象的句柄、属性和方法,对象的句柄用于区分不同的对象,当对象被创建后,该对象会获取一块存储空间,存储空间的地址即为对象的标识,对象的属性和方法与类的成员变量和成员函数相对应.
◆使用公有属性封装◆
类由属性和方法组成,类的属性是对数据的封装,而类的方法则表示对象具有的行为,类通常由函数(实例方法)和变量(类变量)组成,如下是使用公有属性封装的数据成员,可以看到,在类的外部,我们是可以使用temp.name="xxoo"
的方式修改这个数据成员的数值的,然后再次调用打印函数,则发现数据被改动了,这样做显然是不够安全的.
>>> import sys
>>> import os
# =====================以下内容是类的定义====================
>>> class lyshark():
... def __init__(self,name,age): #构造函数,初始化数据
... self.name = name #封装的数据成员
... self.age = age
...
... def my_print(self): #封装的成员函数
... print("我的名字是:%s 我的年龄是:%s"%(self.name,self.age))
...
# =========================================================
>>>
>>> temp=lyshark("wangrui","22") #类的实例化,将参数传入类中
>>> temp.my_print() #调用类中的指定方法,打印数据
我的名字是:wangrui 我的年龄是:22
>>>
>>> print(temp.name) #直接调用类中的数据成员
wangrui
# ===============改变数据成员,再次调用看看===================
>>> temp.name="xxoo"
>>> temp.my_print()
我的名字是:xxoo 我的年龄是:22
# =========================================================
小总结: 1.公有属性或者静态属性,可以直接通过类直接访问,也可以直接通过实例进行访问.2.通过类的某个实例对公有属性进行修改,实际上对为该实例添加了一个与类的公有属性名称相同的成员属性,对真正的公有属性是没有影响的,因此它不会影响其他实例获取的该公有属性的值.3.通过类对公有属性进行修改,必然是会改变公有属性原有的值,他对该类所有的实例是都有影响的.
◆使用私有属性封装◆
在上面的小例子中,我们也发现了一些缺陷问题,接下来则看看私有属性封装的技巧,私有属性和成员属性一样,是在__init__
方法中进行声明,但是属性名需要以双下划线__开头
,私有属性是一种特殊的成员属性,它只允许在实例对象的内部(成员方法或私有方法中)访问,而不允许在实例对象的外部通过实例对象或类来直接访问,也不能被子类继承,总之一句话:私有属性只有类的内部可以调用
.
>>> import os
>>> import sys
# =====================以下内容是类的定义====================
class lyshark():
name = "lyshark" #定义公有属性(类变量,可共享数据)
__age = 22 #定义私有属性(类变量)
def __init__(self): #定义构造函数,初始化数据
self.__like = "soccer" #定义私有实例属性(实例变量)
self.hobby = "xxoo" #定义公有实例属性
def my_print(self): #定义公有函数,外部可以调用
print("我的名字: %s"%self.name)
print("我的年龄: %s"%self.__age)
print("我的爱好: %s"%self.__like)
print("其他: %s"%self.hobby)
def __new_test(self): #定义私有函数,只能内部类调用
print("hello world")
def __del__(self): #定义析构函数,清理数据
self.__nobody = "end"
#print("函数执行结束,销毁无用的数据. %s"%self.__nobody)
# =================(公有/私有)方法的调用====================
>>> temp=lyshark() #实例化对象
>>> temp.my_print() #调用类中方法(公有方法)
我的名字: lyshark
我的年龄: 22
我的爱好: soccer
其他: xxoo
>>> temp.__new_test() #调用私有方法,则会报错
# =================(公有/私有)属性的调用====================
>>> print(lyshark.name) #调用公有属性则成功
lyshark
>>> print(lyshark.__age) #调用私有属性,则会报错
>>> print(lyshark.__like)
# =========================================================
小总结: 通过对公有属性
和私有属性
的数据对比,我们可以清晰的看到他们之间的差距,其总结一下就是,1.私有变量不能通过类直接访问,2.私有变量也不能通过实例对象直接访问,3.私有变量可以通过成员方法进行访问.4.类变量一般可以用于共享两个实例化之间的数据,而实例变量则只作用于当前实例.
◆将类封装进对象中◆
除了上面的一些经常用到的封装以外,还有一个比较难理解的封装格式,就是下面这种,它的意思是将一个类实例化后的对象当作一个参数传递到另一个类中,那么在另一个类中我们就可以访问到被传入类中的数据成员以及成员函数的调用啦.
import os
import sys
class main(object):
def __init__(self,name,obj): #OBJ参数用来接收对象
self.name=name
self.obj=obj
class uuid(object):
def __init__(self,uid,age,sex):
self.uid=uid
self.age=age
self.sex=sex
temp=uuid(1001,22,"Man") #首先给UUID类初始化
lyshark=main("lyshark",temp) #将生成的TEMP对象传递给main
# =========================================================
>>> lyshark.name
'lyshark'
>>> lyshark.obj.uid #最后通过多级指针的方式访问数据
1001
>>> lyshark.obj.sex
'Man'
>>> lyshark.obj.age
22
## 面向对象—>继承
面向对象编程(OOP)语言的一个主要功能就是"继承",继承是指这样一种能力,它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展.
继承是面向对象的重要特性之一,通过继承可以创建新类,其目的是使用或修改现有类的行为,原始的类称为父类或超类,新类称为子类或派生类,继承机制可以实现代码的重用,继承的本质是将父类中的方法全部复制一份到子类中,Python里面的继承可以多继承,通过继承,可以获得父类的功能,继承的时候,如果父类中有重复的方法,优先找自己(子类).
◆继承基类普通函数◆
以下是个基本小例子,则可说明继承的关系,首先base()
是一个基类,而expand()
则是一个派生类,派生自base()
,如下虽然派生列没有任何功能,但我们依然可以调用printf()函数打印传入的数据,则可说明,函数是继承的base类里面的.
import os
import sys
class base(): #这个类是基类
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base): #新建类expand继承base基类的方法
pass
# =========================================================
>>> temp=expand("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
◆继承基类构造函数◆
当然我们也可以继承父类的构造函数,以及给相应的子类添加新的属性字段,还可以直接通过super()
方法,直接在子类中的函数体里调用父类里面的函数,并返回执行结果给调用者.
直接继承构造函数: 新建expand()
子类,并继承base()
基类的构造函数,并能够数据在子类中打印父类属性.
import os
import sys
class base():
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base):
def __init__(self,name,age):
super(expand,self).__init__(name,age) #推荐使用本功能实现继承
#base.__init__(self,name,age) #此处和上面实现的功能相等
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
# =========================================================
>>> temp=base("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
>>> temp=expand("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
添加新字段: 在父类base()
的原始字段的基础上重写,给子类expand()
添加一个新的字段sex
,并能够传递参数.
import os
import sys
class base(): #定义的父类
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base): #定义的子类
def __init__(self,name,age,sex):
super(expand,self).__init__(name,age) #继承父类的属性
self.sex = sex #新添加的一个属性
def printf(self):
print("姓名:%s 年龄:%s 性别:%s "%(self.name,self.age,self.sex))
# =========================================================
>>> temp=base("lyshark","22") #原始基类,没有第三个字段
>>> temp.printf()
姓名:lyshark 年龄:22
>>> temp=expand("lyshark","22","Man") #在不影响父类情况下,重写新的字段
>>> temp.printf()
姓名:lyshark 年龄:22 性别:Man
继承父类函数(强制): 如果想在子类中使用父类的其中一个方法,可以使用以下的方式来实现.
import os
import sys
class base(object):
def printf(self):
print("================================")
print("执行函数....")
print("================================")
return 0
class expand(base):
def fun(self):
ret=super(expand,self).printf() #强制调用父类中的printf方法
return ret #将结果返回给调用者
# =========================================================
>>> temp=base()
>>> temp.printf() #调用基类的方法
================================
执行函数....
================================
>>> obj=expand() #在子类中调用基类方法
>>> ret=obj.fun() #将返回值付给ret并打印
>>> print(ret)
================================
执行函数....
================================
0
◆多类继承与多继承◆
简单的多继承: 此处我们实现一个简单的多继承,这里我们会在代码中说明他们的执行顺序,废话不多说,看下图.
import os
import sys
class A:
def fun(self):
print("我是A类里面的函数")
class B:
def fun1(self):
print("我是B类里面的函数1")
def fun2(self):
print("我是B类里面的函数2")
class C(A,B):
def fun(self):
print("我是C类里面的函数")
# =========================================================
>>> temp=C()
>>> temp.fun() #默认调用C类,如果C里面有fun()函数则默认执行自身
我是C类里面的函数 #如果自身没有,才会去基类里面去找fun()函数的存在
>>> temp.fun1() #由于C类中没有这个方法,它会去B或A类里面去找
我是B类里面的函数1 #这也为我们重写函数提供了可能性,我们只需修改C类且名称相同即可实现重写
复杂的多继承: 下面是重点和难点,在其他源码都是这么干的,属于嵌套继承如果开发新程序,则最好不要这么干,最后连自己都懵逼了.
import os
import sys
class A:
def bar(self):
print('bar')
self.f1()
class B(A):
def f1(self):
print('b')
class C():
def f1(self):
print('c')
class D(B):
def f1(self):
print('d')
class E(C,D):
pass
temp=D()
temp.bar()
多类继承(实例): 多继承也是一种解决问题的方式,这里我们通过例子,来演示一下多继承的应用场景,如下我们将添加三个类分别是Person(人类)
作为父类使用,在创建两个派生类,一个是Teacher(老师)
,另一个是Student(学生)
两个类,这两个类派生于Person(人类)
,都属于人类都有共同属性.
1.我们首先创建一个基类Person()
,来描述人类这个范围,当然人类具有一些公共属性如姓名,年龄无论老师学生都有.
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def walk(self):
print('%s is walking...' % self.name)
def talk(self):
print('%s is talking...' % self.name )
2.在以上基类的基础上派生一个Teacher
子类,使用父类的几个属性和方法.
class Teacher(Person):
def __init__(self, name, age, level, salary):
super(Teacher, self).__init__(name, age)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching...' % self.name)
3.最后创建一个学生类Student
,同样和父类公用一些基本属性和方法.
class Student(Person):
def __init__(self, name, age, class_):
Person.__init__(self, name, age)
self.class_ = class_
def study(self):
print('%s is studying...' % self.name)
4.最后直接实例化,并传递两个参数,打印看结果,很好理解.
>>> t1 = Teacher('张老师', 33, '高级教师', 20000)
>>> s1 = Student('小明', 13, '初一3班')
>>> t1.talk()
>>> t1.walk()
>>> t1.teach()
>>> s1.talk()
>>> s1.walk()
>>> s1.study()
张老师 is talking...
张老师 is walking...
张老师 is teaching...
小明 is talking...
小明 is walking...
小明 is studying...
## 面向对象—>多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作,多态的作用:我们知道,封装可以隐藏实现细节,使得代码模块化,继承可以扩展已存在的代码模块,它们的目的都是为了实现代码的重用
,而多态则是为了实现另一个目的那就是接口的重用
,多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用,一句话概括,就是为了实现接口的重用.
import os
import sys
class Animal(object):
def __init__(self, name):
self.name = name
class Cat(Animal):
def talk(self):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' %self.name)
def func(obj): #这里制定一个接口,后期直接调用它
obj.talk()
# =========================================================
>>> c1 = Cat("catcat")
>>> d1 = Dog("dogdog")
>>> func(c1) #这里调用相同的方法,传入的数据不同
catcat: 喵喵喵!
>>> func(d1) #这里调用相同的方法,传入的数据不同
dogdog: 汪!汪!汪!
## 面向对象—>包装
在上面的面向对象概念中提到过,通常类中封装的是数据(数据成员)
和操作数据的方法(成员函数)
这两种东西组成形成了一个类的雏形,数据就是属性,且上面已经介绍过了属性无外乎这几种分类,公有属性/类变量
、成员属性/实例变量
和私有属性
,除了这些属性以外,现在我们来说说类中的方法,类中的方法分为以下几种:
● 成员方法:通常情况下,它们与成员属性相似,是通过类的实例对象去访问,成员方法的第一个参数通常写成
self
,以标明这是一个成员方法
● 私有方法:以双下划线(__)
开头的成员方法就是私有方法,与私有属性类似,私有只能在实例对象内部访问,且不能被子类继承
● 类的方法:使用@classmethod
来装饰的成员方法就叫做类方法,定义为类方法后,只能访问类变量,而不能访问实例变量
● 静态方法:使用@staticmethod
来装饰的成员方法就叫做静态方法,静态方法已经与这个类没有任何关联了,通常情况下用来编写工具包
● 属性方法:把一个方法变成静态属性,可以像访问成员属性那样去访问这个方法,且无法通过()
小括号,传递参数
◆类的方法◆
上面的简单介绍也说明了,通常情况下我们如果在成员函数的上方加上@classmethod
来装饰的成员方法就叫做类方法,它要求第一次参数必须是当前类,与公有属性/静态属性
相似,除了可通过实例对象进行访问,还可以直接通过类名去访问,且第一个参数表示的是当前类,通常写为cls,另外需要说明的是,类方法只能访问公有属性,不能访问成员属性,因此第一个参数传递的是代表当前类的cls,而不是表示实例对象的self.
import os
import sys
class lyshark(object):
name="wangrui" #赋值等待被调用
age="22"
sex="Man"
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
@classmethod #声明下方的函数为类方法
def printf(cls): #此函数只能调用类变量,而不能调用成员属性
print("姓名:%s 年龄:%s 性别:%s"%(cls.name,cls.age,cls.sex))
# =========================================================
>>> temp=lyshark("100","100")
>>> temp.printf()
姓名:wangrui 年龄:22 性别:Man
如果我们将以上的打印函数变量修改调用实例变量,则会出现错误,这是因为装饰器classmethod
的存在,如果屏蔽掉装饰器代码,则就可是成功调用啦,只不过调用的不再是类变量中的数据,而是实例变量中的数据.
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
#@classmethod #此装饰器存在,不允许调用实例变量
def printf(self):
print("姓名:%s 年龄:%s 性别:%s"%(self.x,self.y,self.z))
# =========================================================
>>> temp=lyshark("lyshark","33","Man")
>>> temp.printf()
姓名:lyshark 年龄:22 性别:Man
◆静态方法◆
上面的简单介绍也说明了,通常情况下我们如果在成员函数的上方加上@staticmethod
来装饰的成员方法就叫做静态方法,静态方法是类中的函数,不需要实例.静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作.可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护,通常情况下我们可以用它来实现一个私有的工具包.
import os
import sys
class lyshark(object):
@staticmethod
def sum(x,y):
return x+y
@staticmethod
def sub(x,y):
return x-y
# =========================================================
>>> #temp=lyshark() #这里无需实例化
>>> text=lyshark.sum(10,20)
>>> print("两数之和:%s"%text)
两数之和:30
>>> text=lyshark.sub(100,50)
>>> print("两数之差:%s"%text)
两数之差:50
以上的小例子就是一个典型的封装,因为我们无法调用类中的其他数据,所以直接将它作为一个工具箱使用是最恰当不过的啦,当然这个功能至今为止在开发中也没怎末用到过.
◆属性方法◆
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值,他可以把一个方法变成静态属性,可以像访问成员属性那样去访问这个方法,且无法通过()
小括号,传递参数,它的第一个参数也必须是当前实例对象,且该方法必须要有返回值.
import os
import sys
class lyshark(object):
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
@property
def foo(self):
return self.weight + self.height
# =========================================================
>>> temp=lyshark("wangrui",75,1.88)
>>> print(temp.foo) #此处我们直接调用这个属性,并没有传递参数
76.88
上面的小例子我们可以看到,实例化一个类以后,在调用内部的foo函数的时候,并没有添加括号,然而还是调用成功了,这种特性的使用方式遵循了统一访问的原则,即对外屏蔽细节,只需要像调用变量一样的使用它即可,给用户的感觉就是调用了一个类中的变量.
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除,看下面的代码:
import os
import sys
class lyshark(object):
@property
def get(self):
print("get 函数运行了我...")
@get.setter
def get(self,value):
print("set 设置参数运行了我,传递的参数是: %s"%value)
@get.deleter
def get(self):
print("工作结束了,可以删除数据了")
# =========================================================
>>> temp = lyshark() #实例化
>>> temp.get #调用get属性,则会执行get()函数
get 函数运行了我...
>>> temp.get = 'hello' #设置属性则会执行get.setter函数
set 设置参数运行了我,传递的参数是: hello
>>> del temp.get #删除工作,则会走get.deleter函数
工作结束了,可以删除数据了
下面来看一个具体例子,我们可以直接调用obj.price
来获取商品价格,也可以通过赋值语句修改商品的价格等.
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deltter
def price(self, value):
del self.original_price
# =========================================================
>>> obj = Goods()
>>> obj.price # 获取商品价格
>>> obj.price = 200 # 修改商品原价
>>> del obj.price # 删除商品原价