一、什么是继承?
继承是一种创建类的方式,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
二、新式类和经典类
python2中类分为:新式类和经典类
class Foo(object): #继承与object及其子类都是新式类
pass
class Bar: #没有继承关系的都叫经典类
pass
python3中的类全部为新式类:即便定义时不设定继承关系,默认也继承于object类
查看类的继承关系:__bases__
class Foo1: pass class Foo2(Foo1): pass print(Foo1.__bases__) #(<class 'object'>,) print(Foo2.__bases__) #(<class '__main__.Foo1'>,)
三、继承
继承的好处:减少冗余代码
在子类定义新的属性,覆盖掉父类的属性,称为派生
class Animal: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def talk(self): print('%s is talking' % self.name) class People(Animal): def __init__(self,name,age,sex,education): Animal.__init__(self,name,age,sex) self.education=education def talk(self): Animal.talk(self) #类直接调用方法,不能自动传值,所以这里需要手写self来传递对象 print('%s saying 哈哈哈' % self.name) P1 = People('alex',18,'man','小学') # 类实例化首先调用该类的__init__方法,如果找不到就往父类中找。 P1.talk() #alex is talking #alex saying 哈哈哈
四、isinstance判断某个对象是否是某个类的实例
class Animal: def __init__(self): pass class People(Animal): def __init__(self,name,age,sex): self.name= name self.age=age self.sex=sex def walk(self): print('%s is walking' % self.name) p1 = People('alex',18,'femail') print(isinstance(p1,People)) #isinstance判断某个对象是否是某个类的示例 True print(isinstance(p1,Animal)) #True 对象同样是该类的父类的实例
五、组合:
继承反应的是一种什么是什么的关系 组合也可以解决代码冗余问题
但是组合反映的是一种什么有什么的关系
class People: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex class Date: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def tell(self): print('%s-%s-%s' % (self.year,self.mon,self.day)) class Teacher(People): def __init__(self,name,age,sex,salary,year,mon,day): self.name=name self.age=age self.sex=sex self.salary = salary self.birth=Date(year,mon,day) class Student(People): def __init__(self,name,age,sex,year,mon,day): self.name=name self.age=age self.sex=sex self.birth=Date(year,mon,day) t = Teacher('alex',18,0,1000,1990,12,12) t.birth.tell() #通过对象的属性直接调用父类的方法
六、抽象类:在父类中需要子类实现的方法上面装饰一个abc.abstractmethod则,子类必须实现该方法,否则报错
#实践中,继承的第一种含义意义并不很大,甚至常常是有害的,因为它使得子类与基类出现强耦合。
#继承的第二种含义非常重要。它又叫“接口继承”
#接口继承实质上是要求“做出一个良好的抽象,这个抽象规定类一个兼容接口,使得外部使用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”----这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合--就好像Linux的范文件概念一样,
#所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分 #出“字符设备”和“块设备”,然后做出针对性的设计;细致到什么程度,视需求而定)。
import abc class File(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Txt(File): #文件,具体实现方法 # def read(self): # print('文本数据的读取方法') def write(self): print('文本数据的读取方法') t1 = Txt() #TypeError: Can't instantiate abstract class Txt with abstract methods read
七、对于有多重继承关系的类的对象调用属性时的查找顺序:
1、各分支间没有交集:class F(A,B,C,D) ,class A(E) ,class B(G) ,class C(H) ,class E(I) ,则定义一个类F的实例f,属性的查找顺序就是f自己的存储空间--》类F--》
类A--》E---》I---》B---》G---》C ---》H---》D,不管新式类还是经典类都是这样
2、对于有交集的情况:
新式类:class F(D,E) , class D(B) ,class E(C) , class B(A) , class C(A) , class A(object)
不管python2还是python3都是按照上面的情况,广度优先,最后再找A
经典类:class F(D,E) , class D(B) ,class E(C) , class B(A) , class C(A) 即最上面的类A不继承于object
这种情况下是深度优先,查找顺序:F---》D---》B---》A----》E---》C
八、super的用法,mro:查看类的继承关系
class Foo1: def walk(self): print('from foo1') class Foo2: def walk(self): print('from foo2') class Bar(Foo1,Foo2): def test(self): Foo1.walk(self) #需要定义在函数内 ,需要传值self super().walk() #super()就相当于一个对象,该对象可以直接使用walk()方法,不应传值,推荐 super(Bar,self).walk() #python2中的用法,需要传值,第一个参数是该类自己,第二个传self b = Bar() b.test() print(Bar.mro()) #查看类的继承关系, #[<class '__main__.Bar'>, <class '__main__.Foo1'>, <class '__main__.Foo2'>, <class 'object'>]
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
九、类的多态及多态性:
多态:多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承) #可以理解为:动物类可以有多种形态,包括人、猪、狗等等 # 序列类型有多种形态:字符串,列表,元组等等。 # 文件有多种形态,文本文件、可执行文件 #多态性:(注意多态与多态性是两种概念) # 多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同功能的函数。 # 在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是 # 调用类obj的方法func,又称为向obj发送类一条消息func),不同的对象在接收时会产生不同的 # 行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数, # 不同的行为就是指不同的实现,即执行不同的函数。 class Animal: def talk(self): print('hello') class People(Animal): pass class Dog(Animal): pass def func(x): x.talk() p1 = People() d1 = Dog() func(p1) #向不同的对象发送同一条消息 func(d1) #多态性是一个接口(函数func),多种实现 #多态性的好处:1、增加类程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(x) # 2、增加类程序的可扩展性:通过继承animal类创建类一个新的类,使用者无需更改自己的代码,还是用func(x)q去调用。
十、封装、raise、property、math 、math.pi
######################################################## #封装:分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口, # 有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现 # 上附加更多的处理逻辑,从而严格控制使用者的访问) #第一层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名. 或者obj.的方式去访问 # 里面的名字,这本身就是一种封装 # class Animal: # name = 'JUGG' # def walk(self): # print('哈哈哈') # # print(Animal.name) #JUGG 第一层次的封装 #第二层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用,外部无法直接访问 # 或者留下少量接口(函数)供外部访问。 #在python中用双下划线的方式实现隐藏属性(设置成私有的) #类中所有双下划线开头的名称如__x 在定义阶段都会自动变形成:_类名__x的形式: # class A: # __N = 0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的,在定义时就自动变形为_A__n,存放在————dict__中 # def __init__(self): # self.__x = 10 #变形为self._A__x # # def __foo(self): #变形为_A__foo # print('from A ') # def bar(self): # self.__foo() #只有在类的内部才可以通过__foo的形式直接访问到,因为只有在定义时会自动变形,即在检查语法时会自动的变形为_A__foo # print(self.__N) # # a1 = A() # print(a1._A__N) # 0 变形后是将_A__N的形式存放在__dict__字典中,在类的外部要访问时需要变形后的形式才能访问 # a1.bar() # from A # a1._A__foo() # from A #使用raise限定类型 # class Teacher: # def __init__(self,name,age): # self.__name=name # self.__age=age # def tell_info(self): # print('姓名:%s,年龄:%s' %(self.__name,self.__age)) # def set_info(self,x,y): # if not isinstance(x,str): # raise TypeError('姓名必须是字符串类型') # if not isinstance(y,int): # raise TypeError('年龄必须是整型') # self.__name=x # self.__age=y # # t1 = Teacher('alex',1000) # t1.tell_info() #姓名:alex,年龄:1000 # t1.set_info('alex',10000) # t1.tell_info() #姓名:alex,年龄:10000 ''' 这种自动变形的特点: 1、类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。 2、这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的 3、在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成类 _父类名__x,即双下划线开头的属性在继承给子类时,子类时无法覆盖的。 4、注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被 隐藏的属性,然后外部就可以使用类 ''' ''' 这种变形需要注意的问题是: 1、这种机制并没有真正意义上限制我们从外部直接访问属性,知道类类名和属性名就可以拼出名字: _类名__属性,然后就可以访问类,如a._A__N 2、变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形 3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的 ''' ''' 了解:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头, 那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的 其实很多时候你去调用一个模块的功能时会遇到但下划线开头的 socket._socket,sys._home,sys._clear_type_cache这些都是私有的,原则上是供内部调用的,作为外部的你 一意孤行也是可以用的,只不过显得稍微傻逼一点点 ''' ################################# #特性:property:是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 # 将一个类的函数定义成特性以后,对象再去使用的使用obj.name,根本无法察觉自己的name是执行类一个 # 函数然后计算出来的,这种特性的使用方式遵循类统一访问的原则 import math print(math.pi) #3.141592653589793 #property用法示例:计算圆的周长和面积 #因为周长和面积对与圆来说只是它的属性,而不是它的技能,因此如果在实际中采用area()的方式得到面积的话,会给人一种误解 #使用property后,可以直接调用该方法名即可执行。外人根本不会察觉到实际上是执行类一个计算的函数。 # class Circle: # def __init__(self,radius): #圆的半径radius # self.radius = radius # # @property # def area(self): # return math.pi * self.radius**2 #计算面积 # # @property # def perimeter(self): # return 2*math.pi *self.radius #计算周长 # # c = Circle(10) # print(c.area) #314.1592653589793 # print(c.perimeter) #62.83185307179586
#使用property来实现隐藏变量的直接访问、修改和删除 class Foo: def __init__(self,name,permission=False): self.__name = name self.permission=permission #控制删除权限,如果为False则不能删除,报异常,为True则能删 @property def name(self): #访问name属性直接返回__name的值 return self.__name @name.setter def name(self,value): if not isinstance(value,str): raise TypeError('名字必须是字符串类型') self.__name=value @name.deleter def name(self): if not self.permission: raise PermissionError('不允许的操作') del self.__name p1 = Foo('alex') print(p1.name) #alex p1.name = 'agen' print(p1.name) #'agen p1.permission=True del p1.name #print(p1.name) #AttributeError: 'Foo' object has no attribute '_Foo__name'
十一、绑定方法
######################################## #绑定方法和非绑定方法 ''' 一、绑定方法(绑定给谁,谁来调用就自动将它本身当做第一个参数传入): 1、绑定到类的方法:用classmetod装饰器装饰的方法。 为类量身定制 类.boud_method(),自动将类当做第一个参数传入 (其实对象也可调用,但仍将类当做第一个参数传入) 2、绑定到对象的方法:没有被任何装饰器装饰的方法。 为对象量身定制 对象.boud_method(),自动将对象当做第一个参数传入 (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说) 二、非绑定方法:用staticmethod装饰器装饰的方法 1、不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说,就是一个普通工具而已 注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法 ,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说 ''' # class Foo: # def test1(self): # print('test1') # @classmethod # def test2(cls): # print('class') # @staticmethod # def test3(x,y): # print(x,y) # # print(Foo.test1) #<function Foo.test1 at 0x0000000000D1F840> 绑定到对象的方法 # print(Foo.test2) #绑定到类的方法 <bound method Foo.test2 of <class '__main__.Foo'>> # f1 = Foo() # print(Foo.test3) #未绑定的方法 <function Foo.test3 at 0x0000000000D1F950> # print(f1.test3) #<function Foo.test3 at 0x0000000000D1F950> # import setting # class MySQL: # def __init__(self,host,port): # self.host=host # self.port=port # @classmethod # def from_conf(cls): # return cls(setting.HOST,setting.PORT) # # #两种给MySQL类传值,第一种手动传值,第二种是通过配置文件自动获取,利用绑定到类的方法 # y1 = MySQL('10.10.82.117',3306) # y2 = MySQL.from_conf() #实现一个自动产生hash值得类 import hashlib import time class MySQL: def __init__(self,host,port): self.host=host self.port=port self.id=self.create_id() @staticmethod def create_id(): m = hashlib.md5(str(time.clock()).encode('utf-8')) #time.clock()产生的时间比time.time更精确 return m.hexdigest() conn= MySQL('10.10.82.117',3306) print(conn.id) # 7c5d6c44dfc59fa5e9ed335742bb3554 print(MySQL.create_id) #<function MySQL.create_id at 0x0000000000A8FA60> print(MySQL.create_id()) #ef40402e3534844eb31f228c24d3ee6e print(conn.create_id()) #77bd2682608773af6585cfa28141abba
#staticmethod和classmethod实际应用中的区别
###################################### #比较staticmethod和classmethod的区别 # class Foo: # def __init__(self,name,age): # self.name=name # self.age=age # def __str__(self): # # return self.age #TypeError: __str__ returned non-string (type int) return后面这里需要返回一个字符串类型 # return self.name # # p = Foo('alex',18) # print(p.__dict__) #{'age': 18, 'name': 'alex'} # print(Foo.__dict__) #{'__str__': <function Foo.__str__ at 0x00000000007171E0>, '__dict__': <attribute '__dict__' of ' # print(p) #alex 在执行打印对象时,会到该对象的类下面找到__str__方法,获取返回的值打印,这样就能返回一些有用的信息,而不只是一个内存地址,没有任何用处 #示例:主要体现在有继承关系的时候,如果子类继承自父类,子类的对象调用父类的方法,那么该对象就是绑定与父类的名, # 打印该对象,打印的也是父类的__str__返回的值。如果定义成classmethod方法,就会打印子类自己的_-str__方法返回的内容 import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port # @staticmethod # def from_conf(): # return MySQL(settings.HOST,settings.PORT) @classmethod def from_conf(cls): return cls(settings.HOST,settings.PORT) def __str__(self): return '来自父类' class Mariadb(MySQL): def __str__(self): return '主机:%s 端口:%s' % (self.host,self.port) #staticmethod方法的使用,classmethod方法注释掉 # m = Mariadb.from_conf() # print(m) #来自父类 如果把__str__注释掉,打印出来的就是<__main__.MySQL object at 0x0000000000A755C0>, #即虽然m看起来是Mariadb该子类的对象,但实际是MSSQL的对象,因此执行的是父类的__str__方法 #用classmethod方法,因为有自动传值,将子类名Mariadb进行传递,因此产生的对象还是子类的 m = Mariadb.from_conf() print(m) #主机:10.10.82.117 端口:3306
练习
''' 定义MySQL类 1.对象有id、host、port三个属性 2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一 3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化 4.为对象定制方法,save和get,save能自动将对象序列化到文件中,文件名为id号,文件路径为配置文件中DB_PATH;get方法用来从文件中反序列化出对象 ''' import settings,hashlib,time,os,pickle class MySQL: def __init__(self,host,port): self.host=host self.port=port self.id = self.create_id() def save(self): file_path = r'%s%s%s' % (settings.DB_PATH,os.sep,self.id) pickle.dump(self,open(file_path,'wb')) def get(self): file_path = r'%s%s%s' % (settings.DB_PATH,os.sep,self.id) pickle.load(open(file_path,'rb')) @staticmethod def create_id(): m = hashlib.md5(str(time.clock()).encode('utf-8')) return m.hexdigest() @classmethod def from_conf(cls): return cls(settings.HOST,settings.PORT) m1 = MySQL('10.10.82.117',3306) m2 = MySQL.from_conf() print(m1.host,m2.port,m1.id) m1.save() f = m1.get() print(f) #__init__和__str__的区别 #__init__ 方法不能有返回值,只是负责初始化属性的,不能创建对象 #__str__必须有返回值,且必须是字符串 class Foo: def __init__(self): return 'hhh' f1 = Foo() #TypeError: __init__() should return None, not 'str'
###反射
####反射 class Chinese: county='China' def __init__(self,name,age): self.name=name self.age=age print(Chinese.county) #Chinese.__dict__['county'] p=Chinese('egon',18) # print(p.name) #p.__dict__['name'] #python面向对象中的反射:通过字符串的形式操作对象相关的属性。 #python中的一切事物都是对象(都可以使用反射)(一切皆对象,类本身也是一个对象) #1、hasattr(obj,name) 某个对象是否有某个属性,name必须是字符串形式的,跟存储在dict中的一样 #print(hasattr(p,'name')) #True 以字符串的形式来查看是否有该属性 #2、setattr(p,name,value) # setattr(p,'x',1222) #这里的name也需要是字符串类型的 # print(p.__dict__) #{'age': 18, 'name': 'egon', 'x': 1222} #3、getattr(p,name,若不存在需要返回的值) 若存在直接返回该属性的值,不存在,则返回最后一个参数的值) #print(getattr(p,'x','不存在')) #不存在 若不设置第三个参数,不存在就会报错 # if hasattr(p,'x'): # res = getattr(p,'x') # print(res) #4、delattr(obj,'name') #delattr(Chinese,'county') ##使用,获取当前模块是否有某个属性 # import sys # m = sys.modules[__name__] #获取当前模块名 # print(m) #<module '__main__' from 'D:/Python_OldBoy/day07/s1.py'> # if hasattr(m,'Chinese'): # res=getattr(m,'Chinese') # obj=res('agen',19) # print(obj.name) #agen #反射实例,导入的ftpclient模块中,需要使用该模块中的get()方法 # import ftpclient # f1 = ftpclient.FtpClient('10.10.82.117') # if hasattr(f1,'get'): #不管另一个功能是否实现了,我的程序都能往下执行 # func=getattr(f1,'get') # func() # print('哈哈哈,程序写完了') # class FtpClient: # def __init__(self,host): # self.host=host # print('connect....') # def run(self): # while True: # inp = input('>>: ').strip() # inp_l=inp.split() #实现用空格分隔命令和文件名的操作放入列表 # if hasattr(self,inp_l[0]): # func=getattr(self,inp_l[0]) # func(inp_l) #将列表作为参数传入,则调用的时候用索引使用即可 # def get(self,arg): # print('download file',arg[1]) # # f = FtpClient('10.10.82.117') # f.run() #只要输入get,则会执行 #以字符串的方式导入一个模块 #import sys #方法1 # m = __import__('sys') #方法2 # print(m.path) import importlib #方法3 importlib.import_module('sys')