python面对对象

基础知识

面对对象:

核心是“对象”二字

对象就是将程序高度整合

对象是“容器”,用来存放数据和功能

类也是容器:该容器用来存放同类对象的数据与功能,类的对象是类似于数据和功能的集合体,所以类中最常见的是变量和函数的定义,但是类体中可以包含其他代码

优点:提升程序的解耦合度,进而增强程序的可扩展性

缺点:设计复杂

定义类:

class 类名(继承)# 没有可以去除

类名使用驼峰命名法

注意:

1.``类中可以有任意``python``代码,这些代码在类定义阶段便会执行
2.``因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看
3.``对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
 4.``点是访问属性的语法,类中定义的名字,都是类的属性

中的调用

调用类中的属性或者方法,可以通过类的实例化去调用,也可以通过类名去直接调用

img

在``obj.name``会先从``obj``自己的名称空间里找``name``,找不到则去类中找,类也找不到就找父类``...``最后都找不到就抛出异常``  
 

__init__总结:

1.会在调用类时自动触发执行,用来为对象初始化自己独有的数据

2.__init__内应该存放是为对象初始化属性的功能,但是是可以存放任意其他代码,想要在类调用时就要立刻执行的代码都可以放到该方法中

  1. __init__方法必须返回None
 

#python为类内置的特殊属性

类名``.__name__# ``类的名字``(``字符串``)
类名``.__doc__# ``类的文档字符串
类名``.__base__# ``类的第一个父类``(``在讲继承时会讲``)
类名``.__bases__# ``类所有父类构成的元组``(``在讲继承时会讲``)
类名``.__dict__# ``类的字典属性
类名``.__module__# ``类定义所在的模块
类名``.__class__# ``实例对应的类``(``仅新式类中``)

类有两种属性:数据属性和函数属性

1. ``类的数据属性是所有对象共享的
2. ``类的函数属性是绑定给对象用的``,
但是其实类中定义的函数主要是给对象使用的,而且是绑定给对象使用的,虽然所有对象指向的都是相同的功能但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同头,同时会把对象传递到第一个参数中去
绑定方法的特殊之处就是:哪个对象调用这个方法就会把那个对象传入第一个参数,说以绑定方法一般都要保证最少一个参数,这里默认使用self

封装

封装特性

(``1``)封装是将内容封装到某个地方,以后再去调用被封装在某处的内容。在使用面向对象的封装特性时,需要:
1). ``将内容封装到某处
2). ``从某处调用被封装的内容
通过对象直接调用被封装的内容: 对象``.``属性名
通过``self``间接调用被封装的内容:`` self.``属性名
通过``self``间接调用被封装的内容``: self.``方法名``()
(``2``)构造方法``__init__``与其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法。自动执行构造方法里面的内容
(``3``)对于面向对象的封装来说,其实就是使用构造方法将内容封装到`` ``对象`` ``中,然后通过对象直接或者``self``间接获取被封装的内容

封装是啥

从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的

怎么封装:

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

#其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
#类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:

class A:`
 `  __N=0 
#``类的数据属性就应该是共享的``,``但是语法上是可以把类的数据属性设置成私有的如``__N,``会变形为``_A__N
def __init__(self):`
 `    self.__X=10 #``变形为``self._A__Xdef__foo(self): 
#``变形为``_A__fooprint('from A')
def bar(self):`
 `    self.__foo() #``只有在类内部才可以通过``__foo``的形式访问到``.#A._A__N``是可以访问到的,
#``这种,在外部是无法通过``__x``这个名字访问到。

注意:

1.``这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:``_``类名``__``属性,然后就可以访问了,如``a._A__N``,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.``变形的过程只在类的定义时发生一次``,``在定义后的赋值操作,不会变形

img

3.``在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
4 .``这种隐藏是对外不对内,因为__开头的属性会在类定义,类检测语法时发生变形

为什么要封装

封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???

1:封装数据:

将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

2:封装方法:目的是隔离复杂度

封装方法举例:

\1. 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。

\2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!

\3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

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):`
 `    returnself.__NAME#obj.name``访问的是``self.__NAME(``这也是真实值的存放位置``)`
 `  @name.setter`
 `  def name(self,value):`
 `    ifnotisinstance(value,str): #``在设定值之前进行类型检查``raiseTypeError('%s must be str'%value)`
 `    self.__NAME=value #``通过类型检查后``,``将值``value``存放到真实的位置``self.__NAME`
 `  @name.deleter`
 `  def name(self):`
 `    raiseTypeError('Can not delete')
f=Foo('egon')`
 `print(f.name)`
 `# f.name=10 #``抛出异常``'TypeError: 10 must be str'delf.name #``抛出异常``'TypeError: Can not delete'
class Foo:`
 `  def__init__(self,val):`
 `    self.__NAME=val #``将所有的数据属性都隐藏起来``def getname(self):`
 `    returnself.__NAME#obj.name``访问的是``self.__NAME(``这也是真实值的存放位置``)def setname(self,value):`
 `    ifnotisinstance(value,str): #``在设定值之前进行类型检查``raiseTypeError('%s must be str'%value)`
 `    self.__NAME=value #``通过类型检查后``,``将值``value``存放到真实的位置``self.__NAMEdef delname(self):`
 `    raiseTypeError('Can not delete')
name=property(getname,setname,delname) #``不如装饰器的方式清晰

封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

继承

继承特性

继承描述的是事物之间的所属关系,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类、扩展类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。

子类在继承的时候,在定义类时,小括号()中为父类的名字

(1)继承的工作机制

父类的属性、方法,会被继承给子类。 举例如下: 如果子类没有定义__init__方法,父类有,那么在子类继承父类的时候这个方法就被继承了,所以只要创建对象,就默认执行了那个继承过来的__init__方法。

class Cat(): ## ``父类
  def __init__(self):
    print("``正在运行``Cat``的``__init__``方法``")
class BlueCat(Cat): ## ``子类,``BlueCat``是``Cat``的字类
  pass
tom=BlueCat()
输出:
正在运行``Cat``的``__init__``方法

(2)重写父类方法:

就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。

(3)调用父类的方法:

需求: 先执行父类的eat()方法, 再个性化执行自己的方法

父类名.父类的方法名()

super(): py2.2+的功能

什么是继承

继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。

类名.__bases__查看继承的父类

python中类的继承分为:单继承和多继承

class ParentClass1: #``定义父类
  pass
 
class ParentClass2: #``定义父类
  pass
 
class SubClass1(ParentClass1): #``单继承,基类是``ParentClass1``,派生类是``SubClass
  pass
 
class SubClass2(ParentClass1,ParentClass2): #python``支持多继承,用逗号分隔开多个继承的类
  pass

多继承的优缺点:

好处:子类可以同时继承多个父类的属性,最大限度的重用

坏处:1.违背了人的思维习惯:继承表达的是什么是什么的过程

2.代码可读性变差

二 继承与抽象(先抽象再继承)

继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:`` 
1.``将奥巴马和梅西这俩对象比较像的部分抽取成类;`` 
2.``将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

img

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

img

多继承: 新式类与经典类

在Python 2及以前的版本中,由任意内置类型派生出的类,都属于“新式

类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类,则称之为“经典类

新式类: 广度优先算法

经典类: 深度优先算法(py2中的部分类属于经典类)

在这里插入图片描述

在Python 3之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承object类型),

即所有的类都是“新式类”。新式类的继承算法是广度优先

经典类和新式类最明显的区别在于继承搜索的顺序不同,即:

经典类多继承搜索顺序(深度优先算法):先深入继承树左侧查找,然后再返回,开始查找右侧。

新式类多继承搜索顺序(广度优先算法):先在水平方向查找,然后再向上查找

在这里插入图片描述

3.3 私有属性与私有方法

(1)python中的私有属性和私有方法

实例的变量名如果以`` _ _ ``开头``,``就变成了一个私有变量``/``属性``(private)
实例的函数名如果以 ``_ _ ``开头``,``就变成了一个私有函数``/``方法``(private)
只有内部可以访问``,``外部不能访问

确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护, 代码更加健壮。默认情况下,属性在 Python 中都是“public“

(2)私有属性的使用

class Student:
  """``父类``Student"""
  def __init__(self, name, age, score):
    self.name = name
    self.age = age
    # ``私有属性,以双下划线开头。
    # ``工作机制``: ``类的外部``(``包括子类``)``不能访问和操作,类的内部可以访问和操作。
    self.__score = score
 
s1=Student('tom',12,80)
print(s1.score) ## AttributeError: 'Student' object has no attribute 'score'
class Student:
  """``父类``Student"""
  def __init__(self, name, age, score):
    self.name = name
    self.age = age
    # ``私有属性,以双下划线开头。
    # ``工作机制``: ``类的外部``(``包括子类``)``不能访问和操作,类的内部可以访问和操作。
    self.__score = score
 
  def get_score(self): ## ``私有属性只能在类内部访问
    return self.__score
s1=Student('tom',12,80)
score=s1.get_score()
print(score)

私有方法的使用

  def __init__(self, name, age, score):
    self.name = name
    self.age = age
    # ``私有属性,以双下划线开头。
    # ``工作机制``: ``类的外部``(``包括子类``)``不能访问和操作,类的内部可以访问和操作。
    self.__score = score
 
  def get_score(self):
    self.__modify_score() ## ``私有方法只能在类内部调用
    return self.__score
 
  def __modify_score(self):
    self.__score += 20
     
s1=Student('tom',12,80)
#score=s1.get_score() ``报错``AttributeError: 'Student' object has no attribute '__modify_score'
score=s1.get_score()
print(score)
输出:
100

继承实现的原理

1 继承顺序

在``Java``和``C#``中子类只能继承一个父类,而``Python``中子类可以同时继承多个父类,如``A(B,C,D)
如果继承关系为非菱形结构,则会按照先找``B``这一条分支,然后再找``C``这一条分支,最后找``D``这一条分支的顺序直到找到我们想要的属性
如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

img

img

继承原理(python如何实现的继承)

python``到底是如何实现继承的,对于你定义的每一个类,``python``会计算出一个方法解析顺序``(MRO)``列表,这个``MRO``列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #``等同于``F.__mro__`
 `[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
 
为了实现继承``,python``会在``MRO``列表上从左到右开始查找基类``,``直到找到第一个匹配这个属性的类为止。
而这个``MRO``列表的构造是通过一个``C3``线性化算法来实现的。我们不去深究这个算法的数学原理``,``它实际上就是合并所有父类的``MRO``列表并遵循如下三条准则``:
1.``子类会先于父类被检查
2.``多个父类会根据它们在列表中的顺序被检查
3.``如果对下一个类存在两个合法的选择``,``选择第一个父类

子类中调用父类的方法

方法一:指名道姓,即父类名``.``父类方法``()
方法二:``super()

强调:二者使用哪一种都可以,但最好不要混合使用

区别:

当你使用``super()``函数时``,Python``会在``MRO``列表上继续搜索下一个类。只要每个重定义的方法统一使用``super()``并只调用它一次``,``那么控制流最终会遍历完整个``MRO``列表``,``每个方法也只会被调用一次(注意注意注意:使用``super``调用的所有属性,都是从``MRO``列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看``MRO``列表)

super:

调用父类提供给自己的方法-》严格遵循继承关系
调用``super``方法会得到一个特殊对象给对象会参照发起属性查找那个类的``mro``,去当前类的父类中去找

组合:

软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
1.``继承的方式
通过继承建立了派生类与基类之间的关系,它是一种``'``是``'``的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.``组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种``‘``有``’``的关系``,``比如教授有生日,教授教``python``和``linux``课程,教授有学生``s1``、``s2``、``s3...
 
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

接口:

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
 
归一化的好处在于:
1. ``归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. ``归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
2.1``:就好象``linux``的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出``“``字符设备``”``和``“``块设备``”``,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.2``:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

mixins机制

机制:在多继承的机制背景下,尽可能的提升代码可读性

多态

一 什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)

多态性是指在不考虑实例类型的情况下使用实例

在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!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``本身就是支持多态性的,这么做的好处是什么呢?
 
1.``增加了程序的灵活性
 
  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如``func(animal)
 
2.``增加了程序额可扩展性
 
  通过继承``animal``类创建了一个新的类,使用者无需更改自己的代码,还是用``func(animal)``去调用     
 
 
>>> class Cat(Animal): #``属于动物的另外一种形态:猫
...   def talk(self):
...     print('say miao')
... 
>>> def func(animal): #``对于使用者来说,自己的代码根本无需改动
...   animal.talk()
... 
>>> cat1=Cat() #``实例出一只猫
>>> func(cat1) #``甚至连调用方式也无需改变,就能调用猫的``talk``功能
say miao
 
'''
这样我们新增了一个形态``Cat``,由``Cat``类产生的实例``cat1``,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用``cat1``的``talk``方法,即``func(cat1)
'''

三 鸭子类型

逗比时刻:

  Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用

class TxtFile:
  def read(self):
    pass
 
  def write(self):
    pass
 
class DiskFile:
  def read(self):
    pass
  def write(self):
    pass

例2:其实大家一直在享受着多态性带来的好处,比如Python的序列类型有多种形态:字符串,列表,元组,多态性体现如下

#str,list,tuple``都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
 
#``我们可以在不考虑三者类型的前提下使用``s,l,t
s.__len__()
l.__len__()
t.__len__()
 
len(s)
len(l)
len(t)

绑定和非绑定方法

一 绑定方法与非绑定方法

类中定义的函数分为两大类:

绑定方法

非绑定方法

其中绑定方法又分为

绑定到对象的对象方法

绑定到类的类方法。

绑定方法:

特殊之处在于将调用者本身传入当中作为定义个参数=自动传入

1,绑定对象的方法:调用者是对象,自动传入的是对象

2,绑定类的方法:调用者是类,自动传入的是类:@classmethod

在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。

非绑定方法:@staticmethod

没有绑定任何东西,调用者可以是类可以是对象,没有自动传入

为类中某个函数加上装饰器@staticmethod后,该函数就变成了非绑定方法,也称为静态方法。该方法不与类或对象绑定,类与对象都可以来调用它,但它就是一个普通函数而已,因而没有自动传值那么一说

反射

python是动态语言,而反射(reflection)机制被视为动态语言的关键。

反射指的是程序运行过程可以动态获取对象信息

反射机制指的是在程序的运行状态中

对于任意一个类,都可以知道这个类的所有属性和方法;

对于任意一个对象,都能够调用他的任意方法和属性。

这种动态获取程序信息以及动态调用对象的功能称为反射机制。

好处:

实现可插拔机制

动态导入模块(基于反射当前模块成员)

在python中实现反射非常简单,在程序运行过程中,如果我们获取一个不知道存有何种属性的对象,若想操作其内部属性,可以先通过内置函数dir来获取任意一个类或者对象的属性列表,列表中全为字符串格式

接下来就是想办法通过字符串来操作对象的属性了,这就涉及到内置函数hasattr、getattr、setattr、delattr的使用了(Python中一切皆对象,类和对象都可以被这四个函数操作,用法一样

# hasattr(object,'name')
判断是否存在
hasattr(t,'full_name') # ``按字符串``'full_name'``判断有无属性``t.full_name
 
# getattr(object, 'name', default=None)
获取属性
getattr(t,'full_name',None) # ``等同于``t.full_name,``不存在该属性则返回默认值``None
 
# setattr(x, 'y', v)
赋值
setattr(t,'age',18) # ``等同于``t.age=18
 
# delattr(x, 'y')
删除
delattr(t,'age') # ``等同于``del t.age
 

二次加工标准类型(包装)

包装:``python``为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增``/``改写方法,这就用到了我们刚学的继承``/``派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid

  def append(self, p_object):

​    ' 派生自己的append:加上类型检查'

​    if not isinstance(p_object,int):

​      raise TypeError('must be int')

​    super().append(p_object)

 

  @property

  def mid(self):

​    '新增自己的属性'

​    index=len(self)//2

​    return self[index]

 

l=List([1,2,3,4])

print(l)

l.append(5)

print(l)

\# l.append('1111111') #报错,必须为int类型

 

print(l.mid)

 

\#其余的方法都继承list的

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法

class Foo:

  def __init__(self,x):

​    self.x=x

 

  def __getattr__(self, item):

​    print('执行的是我')

​    \# return self.__dict__[item]

 

f1=Foo(10)

print(f1.x)

f1.xxxxxx #不存在的属性访问,触发__getattr__

描述符:

描述符是什么:

描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),set(),delete()中的一个,这也被称为描述符协议

get():调用一个属性时,触发

set():为一个属性赋值时,触发

delete():采用del删除属性时,触发

class Foo: #``在``python3``中``Foo``是新式类``,``它实现了三种方法``,``这个类就被称作一个描述符
  def __get__(self, instance, owner):
    pass
  def __set__(self, instance, value):
    pass
  def __delete__(self, instance):
    pass

描述符是干什么的:

描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行

描述符应用之何时?何地?

描述符分两种

一 数据描述符:至少实现了__get__()和__set__()

class Foo:
  def __set__(self, instance, value):
     print('set')
  def __get__(self, instance, owner):
     print('get')

二 非数据描述符:没有实现__set__()

class Foo:
  def __get__(self, instance, owner):
    print('get')

注意事项:

一 描述符本身应该定义成新式类``,``被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级``,``优先级由高到底分别是
1.``类属性
2.``数据描述符
3.``实例属性
4.``非数据描述符
5.``找不到的属性触发``__getattr__()

描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述父是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

类的内置方法

内置方法

定义。定义在类内部,以__开头__结尾的方法

特定。会在满足某种条件时自动触发

Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类,这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发,我们以常用的__str__和__del__为例来简单介绍它们的使用。

str

__str__``方法会在对象被打印时自动触发,``print``功能打印的就是它的返回值,我们通常基于方法来定制对象的打印信息,该方法必须返回字符串类型
 
>>> class People:
...   def __init__(self,name,age):
...     self.name=name
...     self.age=age
...   def __str__(self):
...     return '<Name:%s Age:%s>' %(self.name,self.age) #``返回类型必须是字符串
... 
>>> p=People('lili',18)
>>> print(p) #``触发``p.__str__()``,拿到返回值后进行打印
<Name:lili Age:18>

del

__del__``会在对象被清理时自动触发。由于``Python``自带的垃圾回收机制会自动清理``Python``程序的资源,所以当一个对象只占用应用程序级资源时,完全没必要为对象定制``__del__``方法,但在产生一个对象的同时涉及到申请系统资源(比如系统打开的文件、网络连接等)的情况下,关于系统资源的回收,``Python``的垃圾回收机制便派不上用场了,需要我们为对象定制该方法,用来在对象被删除时自动触发回收系统资源的操作
 
class MySQL:
  def __init__(self,ip,port):
    self.conn=connect(ip,port) # ``伪代码,发起网络连接,需要占用系统资源
  def __del__(self):
    self.conn.close() # ``关闭网络连接,回收系统资源
 
obj=MySQL('127.0.0.1',3306) # ``在对象``obj``被删除时,自动触发``obj.__del__()

getattr

getattr 函数的功能:当调用的属性或方法不存在的时候,会返回该方法或函数的定义信息

def __getattr__(self, key):
  print(something)
  
# >>> key : ``为调用的任意不存在的属性名
# >>> ``返回值可以是任意类型,也可以不返回
 
 
class Test(object):
  
  def __getattr__(self, key):
    print('``这个`` key : {} ``不存在``'.format(key))
    
test = Test()
test.a            # ``调用一个不存在的属性
 
# >>> ``执行结果如下:
# >>> ``这个`` key : a ``不存在
# >>> ``通过定义`` __getattr__ ``这样一个函数,会使得我们的返回结果更加友好,而不是直接报错。

setattr

setattr 函数的功能:拦截当前类中不存在的属性和值,对它们可以进行一些业务处理。

def __setattr__(self, key, value):
  self.__dict__[key] = value
  
# >>> key ``为当前的属性名
# >>> value ``为当前的参数对应的值
# >>> ``返回值 :无
 
 
class Test(object):
  
  def __setattr__(self, key, value):
    if key not in self.__dict__:
      # print(self.__dict__)                # ``每一个类中都有这样一个空的字典
      self.__dict__[key] = value
      
      
test = Test()
test.name = 'Neo'
 
print(test.name)
 
# >>> ``执行结果如下:
# >>> Neo

call

call 函数的功能:本质上是将一个实例化后的类变成一个函数

def __call_(self, *args, **kwargs):
  print('call will start')
  
# >>> ``可传任意参数
# >>> ``返回值与函数情况相同,可有可无
 
 
class Test(object):
def __call__(self, *args, **kwargs):
    print('__call__ func will start')
    # print(self.__dict__)                # ``每一个类中都有这样一个空的字典
    print('args is {}'.format(kwargs))
    
  
test = Test()
test(name = 'Neo')
 
# >>> ``执行结果如下:
# >>> __call__ func will start
# >>> args is {'name': 'Neo'}

__ iter__

如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。 比如以斐波那契数列为例,写一个Fib类,可以作用于for循环:

class Fib(object):
  def __init__(self):
    self.a, self.b = 0, 1
 
  def __iter__(self):
    return self
  def __next__(self):
    self.a, self.b = self.b, self.a + self.b
    if self.a > 10000:
      raise StopIteration()
    return self. a
 
for n in Fib():
  print(n)

__ getitem__

Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,它不能像List那样按下标取出元素,要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:

class Fib(object):
  def __getitem__(self, n):
    a, b = 1, 1
    for x in range(n):
      a, b = b, a + b
    return a

getitem()方法也可以实现list的切片操作,不过要加一个判断:

class Fib(object):
  def __getitem__(self, n):
    if isinstance(n, int): # n``是索引
      a, b = 1, 1
      for x in range(n):
        a, b = b, a + b
      return a
    if isinstance(n, slice): # n``是切片
      start = n.start
      stop = n.stop
      if start is None:
        start = 0
      a, b = 1, 1
      L = []
      for x in range(stop):
        if x >= start:
          L.append(a)
        a, b = b, a + b
      return L

枚举类

当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份、

JAN = 1

FEB = 2

MAR = 3

...

NOV = 11

DEC = 12

好处是简单,缺点是类型是int,并且仍然是变量。更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。

可以通过Enum来实现:

from enum import Enum
 
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
 

枚举它的所有成员:

for name, member in Month.__members__.items():
  print(name, '=>', member, ',', member.value)

value的值默认从1开始,要自定义枚举类型,可以从Enum派生出自定义类:

from enum import Enum, unique

class Weekday(Enum):

Sun = 0 # Sun的value被设定为0

Mon = 1

Tue = 2

Wed = 3

Thu = 4

Fri = 5

Sat = 6

既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。

元类

元类就是用来实例化产生类的类

关系:元类-》实例化-》类-》实例化-》对象

查看内置的元类:

type是内置的元类

我们用class定义的所有类以及内置的类都是由内置的元类tyoe实例化产生的

class关键字创建类的步骤:

类的三大特征:

1.类名

2.类的基类

3.执行类体代码==exec

4.调用元类

A = type(类名,类的基类,类体代码)

#exec:三个参数

#参数一:包含一系列python代码的字符串

#参数二:全局作用域(字典形式),如果不指定,默认为globals()

#参数三:局部作用域(字典形式),如果不指定,默认为locals()

#可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

自定义元类

一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

class Mymeta(type): #``只有继承了``type``类才能称之为一个元类,否则就是一个普通的自定义类
  pass
 
# StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta): 
  school='Stanford'
 
  def __init__(self,name,age):
    self.name=name
    self.age=age
 
  def say(self):
    print('%s says welcome to the Stanford to learn Python' %self.name)
 

发生三件事:

1。造一个空对象=》StanfordTeacher,调用了类内的__new__

2.调用Mymeta中的init方法,完成初始化

3.返回初始化对象

call

#1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发

#2、调用obj的返回值就是__call__方法的返回值

 
调用自定义元类就是发生三件事,调用自定义元类的``__call__``方法

1。造一个空对象=》StanfordTeacher,调用了类内的__new__

2.调用Mymeta中的init方法,完成初始化

3.返回初始化对象

对象()->触发的是类的__call__
类()->触发的是元类的__call__
自定义元类->触发的是内置的__call__
 
StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})==``》
Type.__call__``干了三件事:
#1``、``Type.__call__``函数``调用``Mymeta``中``__new__``产生一个空对象``obj
#2``、``Type.__call__``函数``调用``Mymeta``中``__init__``初始化空对象``obj
#3``、``Type.__call__``函数``返回初始化好的对象``obj
 
classMymeta(type):#``只有继承了``type``类才能称之为一个元类,否则就是一个普通的自定义类
def__call__(self,*args,**kwargs):
#self=<class'__main__.StanfordTeacher'>
#1``、调用``__new__``产生一个空对象``obj
obj=self.__new__(self)#``此处的``self``是类``StanfordTeacher``,必须传参,代表创建一个``StanfordTeacher``的对象``obj
 
#2``、调用``__init__``初始化空对象``obj
self.__init__(obj,*args,**kwargs)
 
#``在初始化之后,``obj.__dict__``里就有值了
obj.__dict__={'_%s__%s'%(self.__name__,k):vfork,vinobj.__dict__.items()}
#3``、返回初始化好的对象``obj
 
return obj
 
 
#StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta):
"""StanfordTeacher"""
school='Stanford'
 
def __init__(self,name,age):
self.name=name
self.age=age
 
def say(self):
print('%ssayswelcometotheStanfordtolearnPython'%self.name)
 
def __call__(self,*args,**kwargs):
print(self,*args,**kwargs)

属性查找

以类的对象为起始:对象-》类-》父类

切记是父类不是元类

img

以类为起始:对象-》类-》父类-》元类-》type

切记是父类不是元类

img

python中关于OOP****的常用术语

抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。

对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。

封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

合成

合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。

派生/继承/继承结构

派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。

继承描述了子类属性从祖先类继承这样一种方式

继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。

泛化/特化

基于继承

泛化表示所有子类与其父类及祖先类有一样的特点。

特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。

多态与多态性

多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气

多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

自省/反射

自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,name__及__doc

posted @ 2022-11-17 12:42  小符玩代码  阅读(41)  评论(0)    收藏  举报