什么时候用组合什么时候用继承: 老师类 和 生日类 老师的生日 老师.生日 用组合

老师类 和 人类 老师是人 class老师类(人类) 用继承 另外,有相同属性也可判断要用继承

组合

一个对象的属性值是另外一个类的对象 一个类的属性 用另一个类的对象来描述 两连点:alex.weapon.hand18(jin) alex的weapon属性,因为是一个另类的对象,weapon也有自己的方法,故两连点

# 人狗大战
class Dog:
   def __init__(self,name,aggr,hp,kind):
       self.name = name
       self.aggr = aggr   #武力值
       self.hp = hp    #血
       self.kind = kind
   #咬
   def bite(self,person):
       person.hp -= self.aggr

class Person:
   def __init__(self,name,aggr,hp,sex):
       self.name = name
       self.aggr = aggr
       self.hp = hp
       self.sex = sex
       self.money = 0

   def attack(self,dog):
       dog.hp -= self.aggr

   #获取装备
   def get_weapon(self,weapon):
       if self.money >= weapon.price:
           self.money -= weapon.price
           self.weapon = weapon
           self.aggr += weapon.aggr
       else:
           print("余额不足,请先充值")
#装备
class Weapon:        
   def __init__(self,name,aggr,njd,price):
       self.name = name  
       self.aggr = aggr  #武力值
       self.njd = njd    #耐久度
       self.price = price   #价格

   #技能:18掌
   def hand18(self,person):
       if self.njd > 0:
           person.hp -= self.aggr * 2
           self.njd -= 1

alex = Person('alex',0.5,100,'不详')
jin = Dog('金老板',100,500,'teddy')
w = Weapon('打狗棒',100,3,998)  #创造武器w
# alex装备打狗棒
alex.money += 1000
alex.get_weapon(w)  #alex获取武器w
print(alex.weapon)  
#self.weapon = weapon即传入的w Alex的武器属性是武器的对象
# alex.weapon 是 Weapon类的对象
print(alex.aggr)
alex.attack(jin)
print(jin.hp)
alex.weapon.hand18(jin) #武器的大招,传入jin jin掉血
print(jin.hp)

面向对象三大特性:继承 多态 封装

继承

class A:pass   # 父类,基类,超类
class B(object):pass   # python3中,不写(类)默认继承object 里面很多双下方法
class A_son(A):pass # 子类,派生类
class AB_son(A,B):pass # 子类,派生类 多继承
# 一个类 可以被多个类继承
# 一个类 可以继承多个父类 —— python里

print(A_son.__bases__) #查看继承自谁
print(AB_son.__bases__)
print(A.__bases__) # >>>object     python3 -新式类# 没有继承父类默认继承object

避免大量重复代码 动物定义所有动物的共性 人猪狗继承自动物,各自有各自的方法,这些方法是人猪狗都有的 具体的实例继承自人或猪或狗,再添加自己的独特属性

面试题:当子类没有init,但父类子类都有func方法,若子类实例化,调用的是谁的func

# 狗类 吃 喝 看门(guard)
# 鸟类 吃 喝 下蛋(lay)
class Animal:
   def __init__(self):
       print('执行Animal.__init__')
       self.func()
   def eat(self):
       print('%s eating'%self.name)
   def drink(self):
       print('%s drinking'%self.name)
   def func(self):
       print('Animal.func')

class Dog(Animal):
   def guard(self):
       print('guarding')
   def func(self):
       print('Dog.func')
dog = Dog() #自己没有__init__ 就用父类的
#Dog实例化触发父类__init__时,执行了self.func(),而父类子类都有func
#因为在Dog实例化时,先触发__new__生成Dog的self,然后才去找__init__,进而找父类
#父类要求执行self.func,而dog里是有func的,自然调用自己的

单继承 后 派生新属性和方法

父类 子类都有初始化方法,但是子类的初始化里 会让父类执行init,进而让子类的self获取父类中的属性和方法 除此之外,子类中初始化的其他属性和新定义方法就是派生的属性和方法 方法1 父类名.init(self, 属性1,属性2)

class Animal:
   def __init__(self,name,aggr,hp):
       self.name = name
       self.aggr = aggr
       self.hp = hp

   def eat(self):
       print('吃药回血')
       self.hp+=100

class Dog(Animal):
   def __init__(self,name,aggr,hp,kind):
       Animal.__init__(self,name,aggr,hp)  #这里的self全程都是Dog的self
                                           #若没有这句,狗就只有kind属性,因为自己有__init__
                                           #不会去找父类,进而无法初始化出self.name = name等
       
       self.kind = kind  # 派生属性:原来属性基础上添加的属性
   def eat(self):
       Animal.eat(self)   # 如果想在父类eat方法基础上添加新内容,需要在子类中调用父类,再加
       self.teeth = 2
   def bite(self,person):   # 派生方法:原来属性基础上添加的方法
       person.hp -= self.aggr

jin = Dog('金老板',100,500,'吉娃娃')
jin.eat()
print(jin.hp)

class Person(Animal):
   def __init__(self,name,aggr,hp,sex):
       Animal.__init__(self,name,aggr,hp)
       self.sex = sex       # 派生属性
       self.money = 0       # 派生属性

   def attack(self,dog):
       dog.hp -= self.aggr

   def get_weapon(self,weapon):
       if self.money >= weapon.price:
           self.money -= weapon.price
           self.weapon = weapon
           self.aggr += weapon.aggr
       else:
           print("余额不足,请先充值")
alex = Person('alex',1,2,None)
alex.eat()
print(alex.hp)

jin.bite(alex)
print(alex.hp)
# 父类中没有的属性 在子类中出现 叫做派生属性
# 父类中没有的方法 在子类中出现 叫做派生方法
# 只要是子类的对象调用,子类中有的名字 一定用子类的,子类中没有才找父类的,若父类爷爷类也没有则报错
# 如果父类 子类都有的 用子类的

   # 如果想用父类的再加新内容,单独调用父类的:
   #       方法1: 父类名.方法名 需要自己传self参数
   #       方法2: super().方法名 不需要自己传self
  (父类实例化后调用父类__init__方法,当然不用再传self)
# 正常的代码中 单继承 === 减少了代码的重复
# 继承表达的是一种 子类是父类的关系

方法2 super().init(属性1,属性2) super单继承很好用

class Animal:
   def __init__(self,name,aggr,hp):
       self.name = name
       self.aggr = aggr
       self.hp = hp
   def eat(self):
       print('吃药回血')
       self.hp+=100

class Dog(Animal):
   def __init__(self,name,aggr,hp,kind):
       super().__init__(name,aggr,hp)  # 只在新式类中有,python3中所有类都是新式类
       #super(Dog, self).__init__(name,aggr,hp)     ()中默认传了本类即Dog的self
       #父类实例化后调用父类__init__方法时,不用传self,只需传其他参数
       
       self.kind = kind       # 派生属性
   def eat(self):print('dog eating')

# jin = Dog('金老板',200,500,'teddy')
# print(jin.name)
# jin.eat()
super(Dog,jin).eat()  #super在类外面也能用,调用的是父类的eat方法

多继承

以下全是python3的 mro: 能直接查看找的顺序,新式类才有mro 就连super() 也是严格按照mro顺序查找的 总结:有共同父类则广度优先,没有共同父类则深度 钻石继承:广度优先情况 (两条路都能找到A则先找BC) B(A) , C(A) , D(B,C) D.func时,D没有func时,优先找B,B没有找C,C没有最好找A D先找左边父类,再找右边父类,再找爷爷类

漏斗问题 只有B才是A的子类,B找了不去找A ,去找C的话就找不到了

小乌龟问题(两条路都能找到F,且是太爷爷,则F最后找)

# 
# 新式类: 继承object类的才是新式类 广度优先 python3全是新式类
# 经典类: 在2.7中直接创建一个类就是经典类 深度优先  

# 新式类才有mro ,能直接查看顺序
# print(D.mro())
# D.mro()

# 单继承 : 子类有的用子类 子类没有用父类
# 多继承中,我们子类的对象调用一个方法,默认是就近原则,找的顺序是什么?
# 经典类中 深度优先
# 新式类中 广度优先
# python2.7 新式类和经典类共存,新式类要继承object
# python3   只有新式类,默认继承object
# 经典类和新式类还有一个区别 mro方法只在新式类中存在
# super 只在新式类存在
# super的本质 :不是单纯找父类 而是根据调用者的节点位置的广度优先顺序来的

加上super的多继承问题 证明:super的本质 不是单纯找父类 而是根据调用者的节点位置的广度优先顺序来的 结果为:打印A C B D

多继承用super()时,super()是按照mro列表的顺序来查找父类 按道理图中A类里super()应该去找object了,但不是,是按照C.mro的顺序找的

 

多态

其他强类型语言的多态: 定义A类 并有一个x方法 B类继承自A类 有x方法,并写了x的具体实现方式 C类继承自A类 有x方法,并写了x的具体实现方式 B或者C的实例虽然调用的都是x方法,但是不同实例调用结果不同 其他语言需要有个A类,来让B类C类知道是一家子,x是关联的

python的多态:不需要先定义A类,但需要定义x方法,且 x(obj) 里面会调用 真实实例的x方法,即B或C中具体的x方法

封装 的思想

封装:打包(类)+保密(私有__属性) 把一个包含了很多私有属性和私有方法的类 写好后,可在别的.py文件中把它导入进来

单下划线_和双下划线__的约定(仅仅是约定,实在带下划线调用其实可以调到的):

  1. 单表示隐藏起来的属性,类的外部不能用(封装要明确区分内外)

  2. 双下滑线开头的属性,python会重命名属性;例如AAA类中的xxx,会重命名为_AAAxxx 只是原来的属性名无法调用,并不是真的无法访问

例1

# 广义上面向对象的封装 :代码的保护,面向对象的思想本身就是一种
# 只让自己的对象能调用自己类中的方法

# 狭义上的封装 —— 面向对象的三大特性之一
# 属性 和 方法都藏起来 不让你看见
class Person:
   __key = 123  # 私有静态属性
   def __init__(self,name,passwd):
       self.name = name
       self.__passwd = passwd   # 私有属性

   def __get_pwd(self):         # 私有方法
       return self.__passwd   #只要在类的内部使用私有属性,就会自动的带上_类名

   def login(self):          # 正常的方法调用私有的方法
       self.__get_pwd()

alex = Person('alex','alex3714')
print(alex._Person__passwd)   # _类名__属性名   alex.__dict__中也能看到
print(alex.get_pwd())
# 所有的私有 都是在变量的左边加上双下划綫
   # 对象的私有属性
   # 类中的私有方法
   # 类中的静态私有属性
# 所有的私有的 都不能在类的外部使用

例2


__author__ = 'Linhaifeng'

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

   def tell_width(self):
       return self.__width


r1=Room('卫生间','alex',100,100,10000)

# arear=r1.__width * r1.__length #会报错,只能在类里才能用双下__直接访问该函数
print(r1.tell_area())

例3

#为外部修改私有属性 提供方法
class Room:
   def __init__(self,name,length,width):
       self.__name = name
       self.__length = length
       self.__width = width
   def get_name(self):
       return self.__name
   def set_name(self,newName):
       if type(newName) is str and newName.isdigit() == False:
           self.__name = newName
       else:
           print('不合法的姓名')
   def area(self):
       return self.__length * self.__width

jin = Room('金老板',2,1)
print(jin.area())
jin.set_name('2')
print(jin.get_name())

私有属性补充 父类的私有属性 不能被子类调用

class Foo:
   __key='123'
   
class Son(Foo):
   print(Foo.__key)

#用到私有属性的场景
1.隐藏属性,不想被类外部调用
2.想保护这个属性,不想被随意改变
3.保护此属性,不被子类继承

设计模式(编程思想):接口类 抽象类

共有目的:规范子类,都是面向对象的开发规范 python中原生没有接口类说法,因为自带多继承,可用class模拟实现接口类

java不支持多继承,但借助Interface 可以实现多继承概念,故有了接口类的说法

首先看类的多继承

接口类和接口不是一回事

接口类 (多继承不好控制) 每个类要实现不同功能,即便是相同功能,每个类的实现方式也不同 默认多继承,接口类中的所有的方法都必须不能实现 @abstractmethod : 规定子类必须有下句的方法,方法里内容pass就行了,具体实现 在具体的Tiger类里实现即可

例如tiger类继承自 必须有走方法的类 和 必须有 游方法的类,那么tiger类必须定义走方法和游方法,否则,当tiger实例化时,会报错提醒 少写了某个必要方法

接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些不需要的接口

抽象类 设计模式:抽象类 不支持多继承,抽象类中方法可以有一些代码的实现 解决 多个 实现功能很相近的类

两种思想的比较: 一般情况下 单继承 能实现的功能都是一样的,所以在父类中可以有一些简单的基础实现 多继承的情况 由于功能比较复杂,所以不容易抽象出 相同功能的具体实现 去写在父类中

#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
   all_type='file'
   @abc.abstractmethod #定义抽象方法,无需实现功能
   def read(self):
       '子类必须定义读功能'
       with open('filaname') as f:
           pass

   @abc.abstractmethod #定义抽象方法,无需实现功能
   def write(self):
       '子类必须定义写功能'
       pass

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
   def read(self):
       print('文本数据的读取方法')
   def write(self):
       print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
   def read(self):
       print('硬盘数据的读取方法')

   def write(self):
       print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
   def read(self):
       print('进程数据的读取方法')

   def write(self):
       print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
# 抽象类 : 规范
# 一般情况下 单继承 能实现的功能都是一样的,所以在父类中可以有一些简单的基础实现
# 多继承的情况 由于功能比较复杂,所以不容易抽象出 相同功能的具体实现 去写在父类中

# 抽象类还是接口类 : 面向对象的开发规范 所有的接口类和抽象类都不能实例化
# java :
# java里的所有类的继承都是单继承,所以抽象类完美的解决了单继承需求中的规范问题
# 但对于多继承的需求,由于java本身语法的不支持,所以创建了接口Interface这个概念来解决多继承的规范问题

# python
# python中没有接口类 :
 # python中自带多继承 所以我们直接用class来实现了接口类
# python中支持抽象类 : 一般情况下 单继承 不能实例化
 # 且可以实现python代码

内置方法

内置方法:property(静态属性) classmethod(类方法) staticmethod(静态方法) 1.@property 作用:obj.方法() 得出结果 上一行加了@property后,可将方法伪装成属性,以后调用直接用 obj.方法

但有限制:这个方法() 中,不能跟参数 且不能直接改:obj.方法=值 会报错 有时候明明是个方法 动词,却得到的是个数值之类的 名词 可伪装成静态属性 如果想改,加@name(self, new_name)且只能是一个参数

删除由方法伪装的属性 del 触发deleter方法 ,deleter下面是打印就是打印,有删除就删除,结果由写的代码定

2.staticmethod(静态方法) 类中的方法需要传self,若需要在实例化之前不传任何对象,即可完成该函数功能,可以用静态方法 直接 类名.函数()即可完成功能的实现

3.classmethod(类方法) 一般函数传的是self,self为实例,直接去改改的是实例属性,此时需要类方法 不需要对象即可操作值 当这个方法的操作只涉及静态属性使时,就应该使用classmethod来装饰此方法

总结: 类方法和静态方法 都是类调用的 对象可以调用类方法和静态方法,一般情况下 推荐用类名调用 类方法 有一个默认的参数 cls 代表这个类 cls 静态方法 没有默认的参数 就像函数一样

自省 反射

#自省 反射
hasattr(obj,'属性') #obj.属性 是否存在
getattr(obj,'属性') #获取obj.属性   不存在则报错
getattr(obj,'属性''默认值') #获取obj.属性   不存在不会报错,返回那个默认值
setattr(obj,'属性''属性的值') #obj.属性=属性的值
delattr(obj,'属性') #del obj.属性
#注意:反射可以反射类的属性,
hasattr(cls,'属性')等等   一切皆对象,类也是对象
内置模块也能用
反射自己当前模块的变量:print(getattr(sys.module['__main__'],'属性'))
反射自己当前模块的函数:getattr(sys.module['__main__'],'函数'))()
如果是被导入,又想反射被导入模块中的属性:getattr(sys.module[__name__],'属性') 防止被写死
模块在不被导入时叫'__main__' , 如果是被导入,则叫这个模块的名字,而sys.module可以查看所有导入进来的模块,且为字典

 

双下getattr,双下setattr,双下delattr

#__getattr__,__setattr__,__delattr__
obj点的方式去操作属性时触发的方法
__getattr__: obj.属性 不存在时触发
__setattr__: obj.属性=属性的值 时触发
__delattr__: del obj.属性 时触发

 

 

继承里的包装 与 组合方式实现授权 对比

继承里的包装:自己定义的方法与父类同名,调用时用的自定制方法,其余方法用的原来类的方法

class List(list):
#List继承自标准类型list

#重写append
   def append(self, p_object):
  #若是str则用继承来的append
       if type(p_object) is str:
           # self.append(p_object)
           super().append(p_object)
       #若不是str,执行自定义内容
       else:
           print('只能添加字符串类型')
   #自定义一个函数:显示 列表实例 的中间值        
   def show_midlle(self):
       mid_index=int(len(self)/2)
       return self[mid_index]

# l2=list('hell oworld')
# print(l2,type(l2)) >>>得到一串 字符串形式的字母 列表

l1=List('helloworld')
# print(l1,type(l1))     >>>type是继承来的方法
# print(l1.show_midlle())   >>>show_midlle是自定义方法
l1.append(1111111111111111111111)   #本来是有append方法,但被覆盖重写
l1.append('SB')
print(l1)

而授权:也是一种包装,但不是通过继承实现的,是通过 双下getattr

import time
class FileHandle:
   def __init__(self,filename,mode='r',encoding='utf-8'):
       # self.filename=filename
       self.file=open(filename,mode,encoding=encoding)
       self.mode=mode
       self.encoding=encoding
   def write(self,line):
       print('------------>',line)
       t=time.strftime('%Y-%m-%d %X')
       self.file.write('%s %s' %(t,line))

   def __getattr__(self, item):
       # print(item,type(item))
       # self.file.read
       return getattr(self.file,item) #调read时触发,变成getattr(self.file,read)
       #因为self.file在__init__初始化成文件句柄,里面是有read方法,即触发文件里的read

f1=FileHandle('a.txt','w+')
# print(f1.file)
# print(f1.__dict__)
# print('==>',f1.read)     #read在FileHandle中找不到,即触发__getattr__
# print(f1.write)           #write有定义,使用类已定义的write方法
f1.write('1111111111111111 ')
f1.write('cpu负载过高 ')
f1.write('内存剩余不足 ')
f1.write('硬盘剩余不足 ')
# f1.seek(0)
# print('--->',f1.read())
#__getitem__,__setitem_,__delitem__
obj[‘属性’]的方式去操作属性时触发的方法

__getitem__:obj['属性'] 时触发
__setitem__:obj['属性']=属性的值 时触发
__delitem__:del obj['属性'] 时触发

#__get__,__set__,__delete__
描述符是一个新式类,这个类至少要实现上述三个方法的一个
class 描述符:
def __get__():
pass
def __set__():
pass
def __delete__():
pass

class 类:
name=描述符()

obj=类()
obj.name #get
obj.name='egon' #set
del obj.name #delete

#__del__:析构方法
垃圾回收时自动触发

双下方法:

内置的类方法 和 内置的函数之间有着千丝万缕的联系

转成字符串
obj.__str__ = str(obj)

repr
print(1)   --->1
print('1')  --->'1'
obj.__repr__ = repr(obj)

obj.__len__ = repr(len)

较重要的:__call__

class A:
   def __init__(self, name):
       pass
   def __call__(self):
       print('1111')
a=A('alex')
a()
实例化的对象+() 即会执行__call__方法
类进行实例化生成实例时候,就是执行__call__方法

还有元类生成类的时候,利用了__call__

构造方法new及单例模式


#__init__初始化方法
#__new__ 构造方法:创建一个对象,init之前就有self,而self是由__new__造出来的
class A:
   def __init__(self):
       self.x = 1
       print('in init function')
   # 利用object类中的__new__方法造出新对象并return,return出来的就是self
   # def __new__(cls, *args, **kwargs): #cls默认参数代表本类,此时还没有self
   #     print('in new function')
   #     return object.__new__(A, *args, **kwargs)
   

#实例出来的是不同的实例(内存地址不同)
a1 = A()
a2 = A()
a3 = A()
print(a1)
print(a2)
print(a3)
print(a.x)


# 设计模式的一种:单例模式
# 一个类 始终 只有 一个 实例
# 当你第一次实例化这个类的时候 就创建一个实例化的对象
# 当你之后再来实例化的时候 就用之前创建的对象(操作的是同一个对象)
class A:
   __instance = False  #私有属性
   def __init__(self,name,age):
       self.name = name
       self.age = age
   def __new__(cls, *args, **kwargs):    
   # __new__为构造方法,__init__初始化之前 先执行的就是构造方法,cls默认参数代表本类,此时还没有self,执行的结果即返回self,
     
     if cls.__instance:    #第二次进来才满足此条件
           return cls.__instance
       cls.__instance = object.__new__(cls)  #第一次进来   ,触发object类里的__new__并传入本类,创造出本类的子类

       return cls.__instance    #返回子类,即self

egon = A('egg',38)
egon.cloth = '小花袄'
nezha = A('nazha',25)
print(nezha)
print(egon)
print(nezha.name)
print(egon.name)
print(nezha.cloth)
#__eq__:__eq__(slef,other) 判断self对象是否等于other对象

class A:
   def __init__(self,name):
       self.name = name

   def __eq__(self, other):
       pass

ob1 = A('egg')
ob2 = A('egg')
print(ob1 == ob2)  
#>>>False 原因:虽然参数相同但还是两个实例,内存地址不相同
#__eq__的self和other是实例的内存地址
# ‘==’ 默认比较的其实是内存地址

class A:
   def __init__(self,name):
       self.name = name

   def __eq__(self, other):
       if self.name == other.name_:
           return True
       else:
           return False
ob1 = A('egg')
ob2 = A('egg')
print(ob1 == ob2)
#>>>Ture
#定义__eq__后,比较的是两个对象的值,而值是相同的,不用的只是对象
只要是可hash的,里面都有__hash__方法

class A:
   def __init__(self,name,sex):
       self.name = name
       self.sex = sex
a = A('egon','男')
b = A('egon','nv')
c = A('egon','男')
print(hash(a))
print(hash(b))
print(hash(c))
#默认对对象的内存地址进行hash
> -9223371859710366888
> 177144408945
> -9223371859710366860


class A:
   def __init__(self,name,sex):
       self.name = name
       self.sex = sex
   def __hash__(self):
       return hash(self.name+self.sex)
a = A('egon','男')
b = A('egon','女')
c = A('egon','男')
print(hash(a))
print(hash(b))
print(hash(c))
#改成对值的hash
> 9213915433563894301
> -546061345136987036
> 9213915433563894301
原文地址:https://www.cnblogs.com/lishuaing/p/11264279.html