面向对象编程基础
一 编程的分类:
1 分类
1 面向过程编程
以指令为核心:围绕“正在发生什么”进行编写
面向过程:程序= 算法+ 数据结构
面向过程编程:程序具有一系列线性步骤,主体思想是代码作用于数据,以指令为核心,核心是设计算法,
2 面向函数的编程
3 面向对象的编程成都创新互联公司主营红山网站建设的网络公司,主营网站建设方案,成都app软件开发,红山h5微信小程序开发搭建,红山网站营销推广欢迎红山等地区企业咨询
2 面向对象编程
面向对象编程--object oriented programming ,简称OOP,把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
面向过程把函数继续切分称为子函数,来降低系统的复杂度。
面向对象编程(OOP)
程序 = 指令 + 数据/算法 + 数据结构
以数据为核心: 围绕“将影响谁”进行编写
面向对象编程(OOP):围绕数据以及为数据严格定义的接口来组织程序,用数据控制对代码的访问,面向数据,以及能够对这些数据采取的操作进行,代码执行过成,不能跳过数据
面向对象编程的核心概念:
所有编程语言的最终目的都是提供一种抽象方法
在机器模型(“解空间”或“方案空间”)与实际解决问题的问题模型(“问题空间”)之间,程序员必须建立一种联系
将问题空间中的元素以及他们在解空间中的标识物抽象为对象,并允许通过问题来描述问题而不是通过方案来描述问题
可以把实例(某个类的实例)想象成一种新型变量,他保存这数据,但可以对自身数据执行操作,实例可以对自身数据做操作
二 类和实例概念
1 类:
定义了被多个同一类型对象共享的结构和行为(数据和代码)
类是抽象的,实例是具体的,类是(概念,抽象的概念模型),类本身是不能被操作的,须有实例化的对象才能操作
类内部有两个核心成员:代码和数据,都叫类成员
数据:成员变量或实例变量
成员方法: 简称为方法,是操作数据的代码,用于定义如何使用成员变量,因此一个类的行为和接口是通过方法来定义的
类:将同一种具体的物事的共同特性抽想出来的表现
2 实例:
实例是类的具体表现形式,类存在的目的就是为了实例化。类是抽象的,实例是具体的
三 类基本操作
1 类的定义
calss 类名(父类):
类的内容
类后面有括号的称为新式类,没有括号称为经典类。
括号里面的内容是父类的名称,程序中,所有父类都是object
类的查看:
2 类的数据属性(类变量)
当类的实例化后,类的属性是不变的
类的数据属性是可变的,当在实例处修改类的数据属性时,其类本身的数据属性不变。
当类的数据属性发生改变时,原本已经改变的实例的数据属性不会再次改变,而新创建的实例的数据属性会发生改变
#!/usr/local/bin/python3.6
#coding:utf-8
class MyClass:
'''is example class'''
x='abc' # 类的属性
def foo(self): # 类的属性,也是方法
return (self,'MyClass')
myclass=MyClass() # 进行实例化操作
print (myclass.x) #调用类的属性
print (MyClass.x) # 类调用类属性
print (MyClass.foo(1)) # 类调用类方法
print (myclass.foo()) # 调用类的方法
print (type(myclass)) # 打印类型
print (id(MyClass)) # 打印类的实例地址
print (id(myclass)) # 打印类的内存地址
结果如下
其实例化都的内存地址和类本身的内存地址不同,且实例可以调用类的属性和方法,类自身也可以进行相关的调用处理
3 类的方法===函数
在类中定义的函数教方法,类的方法中,要求第一个形参必须是self,而self实际上是类实例化后的对象本身
实例化后获得的实例,是不同的实例,即使使用相同的参数实例化,也得到不一样的对象,其会先调用new进行实例化,然后调用_init_ 进行初始化
实例化后,会自动调用__init__方法,这个方法的第一个参数必须留给self,其他参数随意
类方法的定义
类方法的调用
#!/usr/local/bin/python3.6
#coding:utf-8
class MyClass:
'''is example class'''
x='abc' # 类的属性
y='cbd'
def __init__(self): # 初始化时进行打印,并显示结果,其__init__方法中不能return,其只能用做初始化
print ('init')
def foo(self):
return (self.x)
def foo1(self):
return (self.y)
myclass=MyClass() # 对象实例化
print (myclass.foo())
print (myclass.foo1())
结果如下
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
def __init__(self,name,age=20):
self.name=name # 此处的name始终和上述的self后面的name相对应,下面相同
self.x=age
def showage(self):
print ('{} is {}'.format(self.name,self.x)) # 此处self.x 始终和上面的相同,其中age相当于形参
tom=Person('tom') #对参数进行实例化操作
jerry=Person('jerry',10) # 对参数进行实例化
tom.showage() #打印其方法
jerry.showage()
a=Person('a') # 其获取到的参数完全相同
b=Person('a')
print (a is b) # 其内存地址不同
print (a==b) # 其获取结果不同,
结果如下
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
def __init__(self,name,age=18):
self.name=name
self.age=age
def show(self,x,y): # 通过此处的参数,可修改类中的属性
self.name=x # 修改name的属性
self.age=y
Person.x=x # 修改类属性
print (self.name,self.age,x)
tom=Person('tom')
tom.show('jerry',20)
print (Person.x) # 其返回结果为修改类属性后的结果
结果如下
4 类的特殊属性
1 简介
1 class._name_ 类的名称显示
2 class._doc_ 类的帮助文档
3 class._base_ 类的父类/基类
4 class._module_ 类的模块,当不是导入的模块的类时,其执行结果为main,当为模块导入时,其执行结果为模块名
5 class._dict_ 对象属性的字典,用于遍历和查找属性,每个对象中保存这自己的属性,类的属性在类字典中,实例的属性在实例的字典中
6 class._class_ 对象的类型,__class__和type中的类型是相同的,在python3中相同,在早期的python2 中有某些事不相同的
7 class._qualname_ 类的限定名
2 操作
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
def __init__(self,name,age=18):
self.name=name
self.age=age
def show(self,x,y): # 通过此处的参数,可修改类中的属性
self.name=x # 修改name的属性
self.age=y
Person.x=x # 修改类属性
print (self.name,self.age,x)
tom=Person('tom')
print (tom.__class__,Person.__class__) # 打印实例类型和类的类型
print (tom.__dict__,Person.__dict__) # 打印实例的属性字典和类的属性字典
print (tom.__qualname__,Person.__qualname__) # 打印实例的限定名和类的限定名,默认的,只有类有限定名
结果如下
如需输出,则需要
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
def __init__(self,name,age=18):
self.name=name
self.age=age
def show(self,x,y): # 通过此处的参数,可修改类中的属性
self.name=x # 修改name的属性
self.age=y
Person.x=x # 修改类属性
print (self.name,self.age,x)
tom=Person('tom')
print (tom.__class__.__qualname__) #此处通过__class__吊起该实例的类,通过类调用其限定名即可
print (isinstance(tom,Person)) #通过此处判断该类是否是该实例的类
print(isinstance(tom,tom.__class__))
结果
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
def __init__(self,name,age=18):
self.name=name
self.age=age
def show(self,x,y): # 通过此处的参数,可修改类中的属性
self.name=x # 修改name的属性
self.age=y
Person.x=x # 修改类属性
print (self.name,self.age,x)
tom=Person('tom')
jerry=Person('jerry',20)
print (tom.__dict__,jerry.__dict__,Person.__dict__,sep='\n')# 打印实例和类的属性字典
结果如下
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
age=3
height=170
def __init__(self,name,age=18):
self.name=name
self.age=age
tom=Person('tom')
jerry=Person('jerry',20)
Person.age=30
print (Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n') #此处打印类和实例对象属性字典
print (Person.age,tom.age,jerry.age) # 结果为30,18,20
Person.height+=30
print (Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
print (Person.height,tom.height,jerry.height) # 结果为200,200,200
tom.height=180 # 当此处给单独的对象添加了类的属性并赋值后,其对象tom的属性字典中的值也会随之变化
print (Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n') # 此处tom的字典属性中也增加了height的key,且其值为180
print (Person.height,tom.height,jerry.height) #结果为200,180,200
jerry.height+=30 # 此处添加了实例的属性后,其对象字典中的对象属性也会随之增加
print (Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n') # 此处jerry 的字典属性中也增加了height的key,且其值为230
print (Person.height,tom.height,jerry.height) # 结果为200,180,230
结果如下
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
x='abc'
age=3
height=170
def __init__(self,name,age=18):
self.name=name
self.age=age
tom=Person('tom')
print (tom.__dict__['name'],tom.__dict__['age']) # 通过key调用对应的value,此处不能调用height,因为其不在tom属性字典中
结果如下
3 结论:
是类的,也是这个类所有实例的,其实例都可以访问到,是实例的,就是这个实例自己的,通过类访问不到。
实例可以动态的给自己增加一个属性,实例.dict[变量名]和实例.变量名都可以访问到
实例的同名变量会隐藏这类变量,或者说是覆盖了这类变量
实例属性的查找顺序
指的是实例使用.来访问属性,会先找到自己的dict,如果没有。则通过属性class找到自己的类,再去类的dict中找注意: 如果实例使用dict[变量名]访问变量,将不会按照上面的顺序查找变量了
5 类方法和静态方法
1 类方法
通过类方法@classmethod 进行将类本身调用,其中cls 关键字就是表示类本身,
可以使用类名.类方法(参数) 的形式进行调用。并使用return 返回结果
1 在定义类中,使用@classmethod 装饰器修饰的方法
2 必须至少传递一个参数,且第一个参数留给了cls,cls指代调用者及类对象自身
3 cls这个标识符可以是任何合法名称,但是为了易读,请不要修改
4 通过cls可以直接操作类的属性,其无法对实例的属性进行修改,因为其第一个传递的是类对象,类中也不能调用实例的方法
相关实现
#!/usr/local/bin/python3.6
#coding:utf-8
class Document: # 父类
def __init__(self,content):
self.content=content
def print(self): # 共有的属性
print (self.content)
class Word(Document):
pass
class PrintableWord(Word):
def print(self): # 子类自己的私有属性
print ("Word pinrt {}".format(self.content))
class Pdf(Document):
pass # 子类自定义私有属性
print (PrintableWord.mro())
word=PrintableWord('test\nabc')
word.print()
结果如下
实例如下
#!/usr/bin/poython3.6
#conding:utf-8
class MyClass:
x='123'
@classmethod # 类方法,此处传入的第一个值是类,cls.__name__调用的是这个类的名称,而cls.x调用的是这个类的私有属性
def my(cls):
cls.pas='456' # 可通过此种方式在类中增加属性
print ('{}.x={}'.format(cls.__name__,cls.x),cls.pas)
MyClass.my() # 此处是类的自调用
a=MyClass() # 实例化,其类的相关属性依然存在
a.my()
结果如下
2 静态方法
1 在定义类中,使用@staticmethod 装饰器装饰的方法
2 调用时,不会隐式传入参数
3 静态方法,只是表明这个方法属于这个名称空间,函数归类在一起,方便管理组织
实例如下
#!/usr/bin/poython3.6
#conding:utf-8
class MyClass:
def char(): #此处无语法错误,但其不建议如此写入
print ('char')
@staticmethod # 此处是静态方法,用于保存类中的静态变量,其本身和实例无任何关系
def char1():
print ('char1')
a=MyClass()
# a.char() # 此处调用会出错,但下面的由于是静态方法,因此其调用不会出错
a.char1()
总结: 静态方法和类方法都属于类的属性
此处不推荐直接使用def char()的方式进行处理,而推荐使用@staticmethod,python中对静态方法的使用较少,
datetime 是在类上调用的方法,进而造出datetime对象而返回这个方法
python中常见的三种函数:
1 和实例相关的函数
2 和类相关的函数
3 一般的函数类的方法在类创建完成后即可进行调用,而实例的方法在类创建完成之后不能调用
3 总结:
类的方法调用
类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错,原因是第一个参数必须是类的实例
实例几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义
类方法:默认第一个参数是类本身,其可以被类调用,不能被实例调用
普通方法:默认第一个参数是实例本身,可以被类和实例进行调用
静态方法:默认第一个参数数传入的参数。只能被类本身进行调用,不能被实例调用
总结:
类除了普通方法外都可以调用,普通方法需要对象的实例作为第一参数
实例可以调用所有类中的方法(包括类方法,静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类后进行相关的处理。
4 在类中的应用:
扩展:
在类中调用类本身的方法:
传统的输入年、月、日、的方法:
通过函数进行格式化的结果,通过在函数中调用类的方式实现
6 访问控制
1 私有属性
使用双下划线开头的属性名,就是私有属性
通过在类的构造时使用self.__x=x 来 获取类的私有属性。
默认的类的私有属性不能访问:
通过将其私有属性包装成方法进行使用,其是可以访问的。
实例如下:
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,name,age):
self.name=name
self.age=age
def char(self):
if 0< self.age < 100:
return self.age
tom=Person('tom',50)
print (tom.char())
jerry=Person('jerry',110)
print (jerry.char()) # 此处的返回为空,因为没有满足的条件
print (jerry.age) # 可通过此种方式进行返回,因此其是无法真实控制的
结果如下
此处python提供了私有属性用于解决此种问题
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,name,age):
self.name=name
self.__age=age # 私有属性,隐藏之后无法使用实例名.__age进行调用,必须将其抽象成一个方法并输出的方式进行处理
def char(self):
if 0< self.__age < 100:
return self.__age #
def charget(self):
return self.__age
tom=Person('tom',50)
print (tom.char())
jerry=Person('jerry',110)
# print (jerry.__age) #此处无法通过__age的方法进行调用吗
print (jerry.charget()) #此处可有方法进行调用处理
print (jerry.__dict__) # 事实上,此处的私有属性在python中是修改了名称,其结果是实例名._类名称__age可进行调用,具体可参见__dict__获取,此处私有属性是在实例字典中存在的
print (jerry._Person__age)
jerry._Person__age=300 #修改私有属性参数值,但其测试,显示中既然是私有属性,则不建议修改其属性值
print (jerry._Person__age) # 修改结果查看
结果如下
私有变量的本质: 类定义的时候,如果声明一个实例变量的时候,使用双下划线,python解释器会将其改名,转换成为实例名._类名__变量名,所以原来的名字访问不到了,私有属性是在实例的字典中的
2 保护变量
在变量名前使用一个下划线,称为保护变量
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,name):
self._name=name # 保护变量,python开发人员约定俗称的,不可修改的,但实际其是可以被实例调用且修改的,但建议其使用方法进行封装处理
def printf(self):
return self._name
tom=Person('tom')
print (tom._name)
tom._name='jerry'
print (tom.printf()) #修改后打印
结果如下
可以看出,这个_name属性根本就没改变名称,和普通的属性一样,解释器不做任何特殊处理,这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用,保护变量也是在实例的字典中的。
3 私有方法
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,name):
self.name=name
def __printf(self): #私有方法,其本质是对其名称做了修改,其方法是类的方法是Person.__printf
return self.name
tom=Person('tom')
print (Person.__dict__)
# print (tom.__printf()) #修改后打印
print (tom.__dict__) # 实例列表中无此方法
print (tom._Person__printf()) # 此处的调用先找到实例,然后没有这方法,然后找到类,找到该方法,
结果如下
在函数上的双下划线,是属于类的属性,方法内部(函数)的双下划线,是属于实例的属性,而不是类的属性
私有方法的本质
单下划线的方法只是开发者之间的约定,解释器不会做任何改变
双下划线的方法,是私有方法,解释器会改名,改变策略和私有变量相同,但其私有变量是在实例字典中,而私有方法却是在类字典中
4 总结
私有成员总结
在python中使用单下划线或者双下划綫来标识一个成员被保护或者私有化隐藏起来,但是,不管使用什么样的方式访问控制,都不能真正的阻止用户修改类的成员属性,python中没有绝对安全的保护成员或者私有成员因此,前面的下划线是一种警告或者提醒,请遵守这个约定,除非真的必要,否则不要修改随意使用成员保护或者私有成员。
7 属性装饰器
属性装饰器的目的: 把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。
一般方法
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
def __init__(self,name,age=18):
self.__name=name
self.age=age
def getname(self): #查看名称的属性
return self.__name
def setname(self,name): # 修改名称属性
self.__name=name
tom=Person('tom')
print (tom.getname())
tom.setname('jerry')
print (tom.getname())
结果如下
属性装饰器
#!/usr/local/bin/python3.6
#coding:utf-8
class Person:
def __init__(self,name,age=18):
self.__name=name
self.age=age
@property
def getname(self): #查看名称的属性
return self.__name
@getname.setter #修改属性值,此处的getname和上面的和下面的必须相同,否则会出现错误
def getname(self,value):
self.__name=value
@getname.deleter # 删除属性值,使用del self.__name 进行对属性的删除操作
def getname(self):
#del self.__name
print ('delete self.__name')
tom=Person('tom')
print (tom.getname)
tom.getname='jerry' # 修改属性值
print (tom.getname)
del tom.getname #外部调用执行删除程序
结果如下
第二种写法
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,chinese,english,history):
self._chinese=chinese
self.__english=english
def seteng(self,value): # 只有getter属性,此中成为只读属性
self.__english=value
#第二种写方式,初始化方法
eng=property(lambda self:self.__english,seteng ) # property 是只读的,此处的传入的值,eng是一个函数,第一个是gettter,第二个是setter
stu=Person(90,100,110)
print (stu.eng)
stu.eng=200 #修改其值
print (stu.eng)
结果如下
通过此装饰器@property: 定义一个类方法为私有属性的名称;让用户可以直接访问, 但不能任意修改;
通过其装饰器的方法进行重新定义一个接受隐藏属性的范围来进行修改其值。
@属性名.seeter: 给属性赋值时先做判断; 当属性名=value会自动调用该函数
通过deleter 方法可以使得当del 属性名, 会自动调用该函数并执行高函数下面的操作~~
特别注意: 这三个方法同名
property 装饰器
后面跟的函数名就是以后的属性名,它就是getter,这个必须有,有了它至少是只读属性setter装饰器
与属性名同名,且接受2个参数,第一个是self,第二个是将要赋值的值,有了它,属性可写deleter 装饰器
可以控制是否删除属性,很少用property装饰器必须在前,setter、deleterious装饰器在后
property 装饰器能通过简单的方式,把对象方法的操作编程对属性的访问,并起到了一定的隐藏作用
@property应用:
用于分页显示,此处传入两个值,一个值是第几页,另一个是每页的数量,通过@property 可以进行对列表的操作。
8 对象的销毁
类中可以定义del方法,称为析构函数(方法)
作用: 销毁类的实例的时候调用,用以释放占用的资源 ,其目标是销毁实例由于python实现了垃圾回收机制,这个方法不能确定何时使用,有必要的时候,请使用del语句删除实例
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,name,age=20):
self.name=name
self.__age=age
def __del__(self): # 设置消亡实例
print ("delete instance {}".format(self.name))
a=Person('tom')
del a # 调用消亡实例
结果如下
9 方法重载 (overload)
在其他面向对象的高级语言中,都有重载的概念,所谓的重载,就是同一个方法名,但是参数数量,类型不一样,就是同一个方法的重载
python 没有重载,python不需要重载
python中,方法(函数)定义中,形参非常灵活,不需要指定类型,参数个数也不固定,一个函数的定义可以实现多种不同的形式,因此python不需要重载。
python中只要标识符相同,则会被完全覆盖,
10 练习
1 随机数生成类,可以指定一批生成的个数,可以指定数值的范围,生成随机数
#!/usr/bin/poython3.6
#conding:utf-8
import random
class randsum:
@classmethod
def getsrandsum(cls,min=1,max=10,num=10):
return [random.randint(min,max) for _ in range(num)]
print (randsum.getsrandsum())
print (randsum.getsrandsum(10,20,5))
结果如下:
2 随机生成随机数,并两两配对形成二维坐标系,把这些坐标组织起来,并进行打印
#!/usr/bin/poython3.6
#conding:utf-8
import random
class RandomNum:
def __init__(self,count=10,min=1,max=10):
self.count=count
self.min=min
self.max=max
self.g=self._generate() # 此处可使用此种方式将内部的方法以属性的形式进行处理,供内部调用
def _generate(self):
while True:
yield [random.randint(self.min,self.max) for _ in range(self.count)] #c此处返回一个列表生成式,用于下面的next()进行调用
def genrate(self,count):
self.count=count
return next(self.g) # 此处通过此种方式进行处理,返回值为随机数的单个个体
a=RandomNum()
for k,v in dict(zip(a.genrate(5),a.genrate(5))).items():
print ("(x={},y={})".format(k,v))
结果如下
3 记录车的品牌mark,颜色color,价格 price,速度等特征,并实现增加车辆信息,显示全部车辆信息的功能。
#!/usr/bin/poython3.6
#conding:utf-8
class Car:
def __init__(self,mark,speed,color,price):
self.mark=mark
self.speed=speed
self.color=color
self.price=price
class CarInfo: # 信息本身也是一个对象,一个容器的对象
def __init__(self):
self.lst = []
def addcar(self,car:Car): #此处可调用上述的类
self.lst.append(car)
def getall(self):
return self.lst # 打印的格式化输出
ci=CarInfo()# 此处完成后,其基本的列表创建已经完成
car=Car('audi',100,'red',1500000) # 此处是初始化
ci.addcar(car) # 调用此初始化数据并增加
print (ci.getall()) # 查看
四 python 补丁
1 概述
可以通过修改或者替换成员,使用者调用方没改变,但是,类提供的功能可能已经修改了,当项目需要修改较多的类时,又着急上线,修改起来比较麻烦时,需要通过在一个文件中指定某个类进行相关的操作来对其进行打补丁
猴子补丁
运行时,对属性进行动态替换
黑魔法,慎用
2 具体使用如下
test 源文件
#!/usr/bin/poython3.6
#conding:utf-8
class Person:
def __init__(self,chinese,english,history):
self.chinese=chinese
self.english=english
self.history=history
def getscore(self):
return (self.history,self.english,self.chinese)
a=Person(70,80,90)
print (a.getscore())
默认结果如下
补丁文件
test1
#!/usr/bin/poython3.6
#conding:utf-8
def getscore(self): # 其名称和相关的类对象名必须一致,否则会报错,此处返回是一个字典
return dict(chi=self.chinese,eng=self.english,his=self.history)
test2
#!/usr/bin/poython3.6
#conding:utf-8
from test import Person
from test1 import getscore
def monkeypatch5Persoon():
Person.getscore=getscore # 对类来改变属性,在类的DICT中有方法,
student = Person(80,91,80)
print (student.getscore())
monkeypatch5Persoon()
student1 = Person(80,91,80)
print (student1.getscore())
结果如下
五 面向对象的三大特性之封装
1 概念
组装: 将数据和操作组装到一起
隐藏数据:对外只暴露一些接口,通过接口访问对象
封装实际上是把数据封装到摸个地方,以后再去调用在某处的内容或者数据
调用封装数据的方式
通过对象直接调用
其中init表示的是一个构造器,当类的实例化过程时会调用其方法,由于其是必选参数,因此在传值时必须与对应的个数相同,当然可以实例化多个对象
2 相关实例
通过self在类内部对类中的数据进行调用
类的实例化
六面向对象的三大特性之继承(inheritance)
1 概念
多复用,继承来的就不用自己写了
多继承少修改,OCP,使用继承来改变,来提现个性
基类和派生类
其中父类也叫基类
子类也叫派生类
父类
子类,其中没有定义类的属性和方法,只是继承了Class1 的属性
子类的实例化和结果,其完全继承和父类的属性和方法:
当子类中有自己的构造函数时,以子类中的构造函数为准
2 基本实践
1 共有属性和方法继承
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name):
self.__name=name
@property
def name(self): # 此处的A使用装饰器将方法封装成属性并进行暴露
return self.__name
def shout(self):
print ('A shout')
class B(A): #此处的B 继承了A的属性
x=456
def shout(self): # 此处的B对A的方法进行了重写
print ('B shout')
class C(B):
pass
tom=C('tom')
print (tom.name)
tom.shout()
结果如下
2 私有属性的继承
默认的,私有属性不能直接调用,需要伪装成方法方可调用。
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.__age=age
def shout(self):
print ('A shount')
@property
def getage(self):
return self.__age
class B(A):
x=456
def shout(self):
print ('B shout')
jerry=A('tom')
print (jerry.__dict__)
tom=B('jerry')
print (tom.__dict__) # 默认的A的私有属性会被B读取,并可进行访问,其方法亦可被访问
print (tom.getage)
结果如下
3 私有方法继承
在类中,以双下划綫开始的方法称为私有方法:私有方法不能继承。
#!/usr/bin/poython3.6
#conding:utf-8
class A:
def __init__(self,name,age):
self.name=name
self.age=age
def __getname(self): #此处定义私有方法
return self.name
class B(A):
pass
b=B('tom',30)
print (b.__dict__)
print (B.__dict__)
a=A('jerry',20)
print (a.__dict__)
print (A.__dict__) # 私有方法是属于类的,不能被继承,
结果如下:
3 查看继承的特殊属性和方法
1 基本含义
特殊属性和方法 | 含义 |
---|---|
_base_ | 类的基类 |
_bases_ | 类的基类元组 |
_mro_ | 显示方法查找顺序,基类的元组 |
mor() | 同上 |
_subclasses_() | 类的子类列表 |
2 基本实例
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name):
self.__name=name
@property
def name(self): # 此处的A使用装饰器将方法封装成属性并进行暴露
return self.__name
def shout(self):
print ('A shout')
class B(A): #此处的B 继承了A的属性
x=456
def shout(self): # 此处的B对A的方法进行了重写
print ('B shout')
class C(B):
pass
print (C.__base__) #显示此类的基类
print (C.__bases__) # 显示此类的基类,以元组呈现
print (C.__mro__) # 显示类的链表,由此类的父类开始查找
print (C.mro()) # 通上
print (A.__subclasses__()) # 显示此类的子类
结果如下
类和实例属性列表打印
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name):
self.__name=name
@property
def name(self): # 此处的A使用装饰器将方法封装成属性并进行暴露
return self.__name
def shout(self):
print ('A shout')
class B(A): #此处的B 继承了A的属性
x=456
def shout(self): # 此处的B对A的方法进行了重写
print ('B shout')
class C(B):
pass
tom=C('tom')
print (tom.__dict__) # 打印实例的字典,其中只有实例化的属性的列表
print (C.__dict__) # 打印C类的字典,C类无任何属性和方法,因为其是来自于B和A的
print (B.__dict__) # 打印B类的字典,其中有x类属性的定义,shout函数的定义
print (A.__dict__)# A中包括初始化函数等信息
如下
3 结论
类和类属性只有一个,而实例和实例属性却有多个
凡是需要分成多个的,应该设计成类的实例属性,凡是需要一份的,应该设计成类的属性
一个子类继承了祖先的特征,但祖先的都是抽象的东西,只有对象才具有继承数据的能力,对于实例,不同的需要设置各个单独的属性,需要放置在自己的属性列表中,如果需要一份,则放置在类属性上。
4 重写父类的构造函数
1 python 2.0 中的写法
A.init(self,args)
1 通过 父类名.init(self,父类参数) 进行重写构造函数
2 通过super对私有方法重写
通过super(自己类名称,self).init(形参)
优点: 不需要明确告诉父类的名称,如果父类改变,只需修改class 语句后面的继承关系即可,不用修改父类名称
实例化调用
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def shout(self):
print ('A shout')
class B(A):
def shout(self): #此处定义覆盖了父类的shout
print ('B')
def shout(self): # 此处定义覆盖了自身的shout
print (super()) # 显示super内容
print (super(B,self))# 此处相当于将B设置为self传输进去。等价与上面的super
super().shout()#此处调用的是父类并调用了父类的shout
a=B()
a.shout()
结果如下
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=0):
self.__name=name # 此处是实例的初始化函数,当然要在其实例化后才能表现出该属性,该类中也找不到,当然在其子类中也不能找到
self.age=age
@property
def getname(self):
return self.__name
def shout(self):
print ('A shout')
class B(A):
x=456
def __init__(self,name,age):
self.age=age # 此处的传入后会修改成50,因为其是在修改之后传入A类中进行处理的
super().__init__(name,age) #新式类推荐使用的方式,此处默认传入self,需要送入指定参数,此处不许找指定self和父类的类名
self.__name=name+" "+'B' # 私有属性的修改是无法生效的,因为其私有属性的key和类名相关,
# self.age=10 #覆盖共有属性,其之前的age是0,现在覆盖成10,因为实例的字典中的key是唯一的。此处因为在a.__init__(self,name)之后定义,因此其会
# 覆盖之前name传入的值
def shout(self):
print ('B shout')
super(B,self).shout() #此处是调用父类的,通过super可以方面的访问自己的祖先类,其查找顺序是找本实例,若通过本实例查找
#其类,通过类查找其父类并进行相关的操作
super().shout() # 与上述相同
x=B('jerry',30) # 此处传入的值是30,由于下面的覆盖,变成了10
x.shout()
print (x.__dict__) #其私有属性的_A__name 仍然存在,_B__name 也存在,
print (x.getname)
结果如下
5 公有属性继承中的初始化
1 公有属性覆盖和父类公有属性调用
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.age=age
def shout(self):
print ('A shount')
class B(A):
x=456
def __init__(self,name): #此处进行初始化,初始化后,其父类的公有属性将不能被调用
self.name=name #此处重新覆盖了共有属性name,由之前的tom修改成了jerry
def shout(self):
print ('B shout')
tom=B('jerry')
print (tom.name)
print (tom.__dict__)
#print (tom.age) # 此处是父类的属性,此处不能被调用
结果如下
2 公有属性重命名
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.age=age
def shout(self):
print ('A shount')
class B(A):
x=456
def __init__(self,name): #此处进行初始化,初始化后,其父类的公有属性将不能被调用
self.cname=name #此处定义了公有属性cname
def shout(self):
print ('B shout')
tom=B('jerry')
print (tom.cname)
# print (tom.name) #此处是父类的属性,不能被调用,因为子类进行了初始化
print (tom.__dict__) # 此处只有cname
#print (tom.age) # 此处是父类的属性,此处不能被调用
3 调用父类A.init(self,args) 来获取父类的共有属性
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom'):
self.name=name
def shout(self):
print ('A shount')
class B(A):
x=456
def __init__(self,name): #此处进行初始化,此处需要调用父类的方法,若不调用,则默认父类的私有方法不能使用,但共有方法可以被调用
self.cname=name #此处重新定义了共有属性cname,
A.__init__(self,name) # 此处调用了父类的name
def shout(self):
print ('B shout')
tom=B('jerry')
print (tom.name) #此处name存在于A类中。而B中调用A.__init__(self,args) 导致其A中存在的属性在B中也同样存在
print (tom.cname)
print (tom.__dict__)# 其共有方法中的name存在,也有自己的初始化的cname
结果如下
4 共有属性中顺序不同导致的问题
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=0):
self.__name=name
self.age=age
@property
def getname(self):
return self.__name
def shout(self):
print ('A shout')
class B(A):
x=456
def __init__(self,name,age):
#self.age=age # 此处的传入后会修改成30,因为其是在修改之后传入A类中进行处理的
A.__init__(self,name,age) #通过父类的调用来调用父类的属性,此中可调用父类的私有属性,
self.__name=name+" "+'B' # 私有属性的修改是无法生效的,因为其私有属性的key和类名相关,
self.age=10 #覆盖共有属性,其之前的age是0,现在覆盖成10,因为实例的字典中的key是唯一的。此处因为在a.__init__(self,name)之后定义,因此其会
# 覆盖之前name传入的值
def shout(self):
print ('B shout')
x=B('jerry',30) # 此处传入的值是30,由于下面的覆盖,变成了10
x.shout()
print (x.__dict__) #其私有属性的_A__name 仍然存在,_B__name 也存在,
print (x.getname)
结果如下
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=0):
self.__name=name # 此处是实例的初始化函数,当然要在其实例化后才能表现出该属性,该类中也找不到,当然在其子类中也不能找到
self.age=age
@property
def getname(self):
return self.__name
def shout(self):
print ('A shout')
class B(A):
x=456
def __init__(self,name,age):
self.age=age # 此处的传入后会修改成50,因为其是在修改之后传入A类中进行处理的
A.__init__(self,name,age) #通过父类的调用来调用父类的属性,此中可调用父类的私有属性,
self.__name=name+" "+'B' # 私有属性的修改是无法生效的,因为其私有属性的key和类名相关,
# self.age=10 #覆盖共有属性,其之前的age是0,现在覆盖成10,因为实例的字典中的key是唯一的。此处因为在a.__init__(self,name)之后定义,因此其会
# 覆盖之前name传入的值
def shout(self):
print ('B shout')
x=B('jerry',30) # 此处传入的值是30,由于下面的覆盖,变成了10
x.shout()
print (x.__dict__) #其私有属性的_A__name 仍然存在,_B__name 也存在,
print (x.getname)
结果如下
5 私有属性中继承中的初始化
1 私有属性未处理和调用
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.__age=age
def shout(self):
print ('A shount')
@property
def getage(self):
return self.__age
class B(A):
x=456
def __init__(self,name,age): # 子类的初始化
self.name=name # 此处进行覆盖共有属性,其私有属性未处理
def shout(self):
print ('B shout')
b=B('jerry',30)
print (b.__dict__) # 此处只获取到了重新定义的name,而私有属性__age 不存在
2 私有属性覆盖
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.__age=age
def shout(self):
print ('A shount')
@property
def getage(self):
return self.__age
class B(A):
x=456
def __init__(self,name,age): # 子类的初始化
self.__age=age # 此处进行覆盖私有属性,其私有属性未处理
def shout(self):
print ('B shout')
b=B('jerry',30)
print (b.__dict__) # 此处只获取到了重新定义的__age,而共有属性name则不存在
结果如下
3 调用父类方法获取共有属性和私有属性列表
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.__age=age
def shout(self):
print ('A shount')
@property
def getage(self):
return self.__age
class B(A):
x=456
def __init__(self,name,age): # 子类的初始化
A.__init__(self,name,age) #调用父类方法,处理获取属性
def shout(self):
print ('B shout')
b=B('jerry',30)
print (b.__dict__) # 此处可获取父类中的共有属性和私有属性
print (b.name,b._A__age) # 已经进行了修改,,因为上面的def __init__(self,name,age): 中的age在实例化后已经被修改
print (b.getage)
结果如下
4 私有属性之重新初始化
#!/usr/bin/poython3.6
#conding:utf-8
class A:
x=123
def __init__(self,name='tom',age=10):
self.name=name
self.__age=age
def shout(self):
print ('A shount')
@property
def getage(self):
return self.__age
class B(A):
x=456
def __init__(self,name,age): # 子类的初始化
A.__init__(self,name,age) #调用父类方法,处理获取属性
self.__age=age # 重覆盖age,但无法完成,因为其私有属性和类名有关,其相当于增加
def shout(self):
print ('B shout')
b=B('jerry',30)
print (b.__dict__) # 此处可获取父类中的共有属性和私有属性
print (b.name,b._A__age) # 已经进行了修改,,因为上面的def __init__(self,name,age): 中的age在实例化后已经被修改
print (b.getage)
结果如下
6 类方法和静态方法继承中的初始化
1 实例
#!/usr/bin/poython3.6
#conding:utf-8
class A:
@classmethod
def class_method(cls):
print (cls.__name__) # 此处打印类的名称
@staticmethod # 此处定义其静态方法
def static_method():
print ('A staticmethod')
class B(A):
@classmethod
def class_method(cls):
print (cls.__name__)
@staticmethod
def static_method():
print ('B staticmethod')
class C(B):
pass
b=C()
b.class_method() # 此处因为调用的是C类,因此其cls.__name__打印的是C而并非是B
b.static_method() # 此处的静态方法是B,因为C中未定义静态方法
print (B.__dict__)
a=A()
a.class_method() # 此处因为调用的时A类,因此cls.__name__打印的是A
a.static_method()
print (A.__dict__)
结果如下
2 结论
静态方法会进行属性查找,找到及截至,类方法因为类的继承而进入该C类中,而C类调用其类方法后得到的名称当然是C自己,此处若只有A配置类属性,则B和C进行调用,则各自cls.name仍然是各个实例的类,相当于自己在调用自己的类。此处大大的复用了,此处就是多态
7 私有方法继承中的初始化
1 私有方法重写
#!/usr/bin/poython3.6
#conding:utf-8
class A:
def __init__(self,name,age):
self.name=name
self.age=age
def __getname(self): #此处定义私有方法
return self.name
class B(A):
def __init__(self,name,age): # 此处进行初始化,方法不能继承
self.name=name
def __getname(self): # 此处属于重定义私有方法
return self.age
b=B('tom',30)
print (b.__dict__)
print (B.__dict__)
a=A('jerry',20)
print (a.__dict__)
print (A.__dict__) # 私有方法是属于类的,不能被继承,
结果如下
8 多继承
1 多重继承简介
原则: OCP 原则,多继承,少修改
继承的用途: 增强基类,实现多态多态:在面向对象环境中,父类,子类通过继承联系到一起,如果可以通过一套方法,就可以实现不同表现,这就是多态
一个类继承多个类就是多继承,它具有多个类的特征
多态是继承+多态
多态的弊端:
1 多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突
多继承的实现会导致编译器设计的复杂度增加,所以很多语言都舍弃了类的多继承。多继承可能带来二义性
解决方案
实现多继承的语言,需要解决二义性和,深度优先或广度优先
左边是多继承,右边是单继承
多继承带来的路径选择问题
python 使用MRO(method resolution order)解决了基类搜索顺序问题
历史原因,MRO有三个搜索算法:
1 经典算法,按照定义从左到右,深度优先策略,2.2之前
左图的MRO是MyClass,D,B,A,C,A
新式类算法,经典算法的升级,重复的只保留最后一个2.2
左图的MRO是MyClass,D,B,C,A,object
C3 算法,在类被创建出来的时候,就计算出一个MRO有序列表,2.3之后,python3唯一支持的算法
左图中的MRO是MyClass,D,B,C,A,object 的列表
C3 算法解决的多继承的二义性
多继承的缺点
当类很多时,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径
Python 语法是允许多继承,但python代码是解释器执行的,只有执行到的时候,才发现错误
团队协作开发,如果引入多继承,代码将不可控
不管编程语言是否支持多继承,都应当避免多继承
python2.x 里面支持经典类和新式类
python3.x 里面仅支持新式类
经典类,其可以不写父类。
新式类,其如果没有继承的父类则直接写object,必须写父类,如果有父类,则直接写父类
只有新式类支持mro() 方法
对于新式类,是广度优先
对于经典类,是深度优先
对于新式类,当调用D实例时,其结果是执行D的输出,当D中为pass 占位,其继承了B的输出,当B中的结果为pass 时,其会继承C中的输出,
对于经典类,当调用D实例时,其结果是执行D的输出,当D中为pass 占位,其继承了B的输出,当B中的结果为pass 时,其会继承A中的输出,因为B继承了A,因为其是深度优先。
9 总结
继承时,共有的,子类和实例都可以随意访问,私有成员被隐藏,子类和实例不可直接访问,当私有变量所在的类内的方法中可以访问这个私有变量,python通过自己一套实现,实现和其他语言一样的面向对象的继承机制
属性查找顺序
实例的_dict_-----类的_dict_ -----父类的_dict_,如果搜索到这些地方后没有找到就会抛异常,先找到就立即返回了。
七 Mixin
1 简介
装饰器: 用装饰器装饰一个类,把功能给类附加上去,那个类需要,就装饰它
Mixin
文档Document 类是其他所有文档类的抽象基类,
Word,PDF类是Document的子类。
此处可抽象为两种类
1 共同的特性抽象成一个类,父类
2 其他不同的特性组成其他的相关的属性,子类
Mxin 本质上是多继承实现的
mixin 提现的是一种组合的设计模式
在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,这些就需要很多组合在一起。
MIxin 类的使用原则:
Mixin 类中不应该显式的出现__init__初始化方法
Mixin 类通常不能独立工作,因为它是准本混入别的类中的部分功能实现
Mixin 类的祖先类也应该是Mixin类
Mixin类和装饰器
这两种方式都是可以使用,看个人喜好
如果需要继承就使用MixIn类的方法
2 装饰器处理代码继承问题如下
1 子类中欲实现自己的功能自己处理,不再父类中进行定义
#!/usr/local/bin/python3.6
#coding:utf-8
class Document: # 父类
def __init__(self,content):
self.content=content
def print(self): # 共有的属性
print (self.content)
class Word(Document):
pass
class PrintableWord(Word):
def print(self): # 子类自己的私有属性
print ("Word pinrt {}".format(self.content))
class Pdf(Document):
pass # 子类自定义私有属性
print (PrintableWord.mro())
word=PrintableWord('test\nabc')
word.print()
结果如下
2 通过父类中实现。子类若需要个性化需求,则自己修改
#!/usr/local/bin/python3.6
#coding:utf-8
def printable(cls): #此处传入的是类函数
def _print(self): # 此处的实例可以看作是这个类函数的参数
print (self.content,'装饰器') # 此处是计算的返回值
cls.print=_print # 此处对返回值进行赋值
return cls # 并返回该函数的执行结果
class Document:
def __init__(self,content):
self.content=content
class Word(Document):
pass
class Pdf(Document):
pass
@printable
class PrintableWord(Word): #此处先继承,后装饰
pass
print (PrintableWord.mro())
pw=PrintableWord('test string')
pw.print()
结果如下
另一种方式,使用lambda 进行处理
#!/usr/local/bin/python3.6
#coding:utf-8
def printable(cls):
# 此处是给对应的类增加相关的属性,使得其更加健壮
cls.print=lambda self: print (self.content)
cls.NAME='name'
return cls
class Document:
def __init__(self,content):
self.content=content
class Word(Document):
pass
class Pdf(Document):
pass
@printable #先继承,后装饰
class PrintableWord(Word):
pass
print (PrintableWord.mro())
pw=PrintableWord('test string')
pw.print()
print (pw.NAME)
结果如下
3 Mixin 方法处理
#!/usr/bin/poython3.6
#conding:utf-8
class Docment:
def __init__(self,content):
self.content=content
def print(self):
print (self.content)
class PrintableMixin:
def print(self):
print ('*'*20)
print ('Pdf print {}'.format(self.content))
class Word(Docment):
pass
class Pdf(Docment):
pass
class PrintablePdf(PrintableMixin,Pdf): # 此处继承了Mixin和Pdf两个类,此处是多继承,Mixin继承了object。而Pdf继承了Docment
# 而Docment 继承了object,此处的作用是使用PrintableMixin覆盖之前的DOcment才是要求,一般的Mixin都是在最前面放的
pass
print (PrintablePdf.mro()) #查看MRO的输出,以MRO的输出为准
Pdf=PrintablePdf('test\nabc'
网页名称:面向对象编程基础
浏览路径:http://pwwzsj.com/article/jcjopp.html