面向对象高级

一、 特性

特性是指的 property .

property 这个词的翻译一直都有问题, 很多人把它翻译为属性, 其实是不恰当和不准确的. 在这里翻译成特性是为了和属性区别开来.

属性是指的 attribute , 我们以前学习的实例变量和类变量是 attribute , 所以也可以叫做实例属性和类属性.

property (特性)到底是个什么东西?

我们前面学习类属性和实例属性的时候知道, 访问他们的时候就可以直接获取到这些属性的值.

而特性可以看成一种特殊的属性, 为什么呢?

但从访问方式来看, 特性和属性看不出来差别, 但是特性实际上会经过计算之后再返回值. 所以每一个特性都始终与一个方法相关联.

1.1 定义特性

定义特性和定义实例方法类似, 只需要另外在方法上面添加一个内置装饰器: @property

访问特性和访问实例变量完全一样, 不需要使用添加括号去调用.

import mathclass Circle:def __init__(self, r):self.r = r@propertydef area(self):"""定义特性这个特性是计算出来圆的面积:return:"""return math.pi * (self.r ** 2)c = Circle(10)print(c.area)

很明显, 特性背后的本质是一个方法的存在, 所以你不可能在外面去修改这个特性的值!

试图修改特性的值只会抛出一个异常.

c.area = 100

1.2 使用特性的设计哲学

这种特性使用方式遵循所谓的 统一访问原则 .

实际上, 定义一个类总是保持接口的统一总是好的.

有了特性, 把访问属性和访问方法统一了, 都像在访问属性一样, 省得去考虑到底什么时候需要添加括号,什么时候不用添加括号.

1.3 特性的拦截操作

python 还提供了设置和删除属性.

通过给方法添加其他内置装饰器来实现

设置: @特性名.setter

删除: @特性名.deleter

class Student:def __init__(self, name):self._name = name    # name 是特性了, 所以用实例变量存储特性的值的是换个变量名!!!@propertydef name(self):return self._name@name.setterdef name(self, name):if type(name) is str and len(name) > 2:self._name = nameelse:print("你提供的值" + str(name) + "不合法!")@name.deleterdef name(self):print("对不起, name 不允许删除")s = Student("李四")
print(s.name)s.name = "彩霞"
print(s.name)s.name = "张三"
print(s.name)del s.name

二、三大特性之一-封装性

面向对象的三大特征:封装, 继承, 多态

2.1什么是封装性

1.封装是面向对象编程的一大特点2.面向对象编程的第一步,就是讲属性和方法封装到一个抽象的类中3.外界使用类创建对象,然后让对象调用方法4.对象方法的细节都被封装在类的内部

在类中定义属性, 定义方法就是在封装数据和代码.

2.2 私有化属性

首先先明确一点, python 不能真正的对属性(和方法)进行私有, 因为 python 没有想 java 那样的 private 可用.

python 提供的"私有", 是为了怕在编程的过程中对对象属性不小心"误伤"提供的一种保护机制! 这种级别的私有稍微只要知道了规则, 是很容易访问到所谓的私有属性或方法的.

2.2.1 为什么需要私有

封装和保护数据的需要.

默认情况下, 类的所有属性和方法都是公共的, 也就意味着对他们的访问没有做任何的限制.

意味着, 在基类中定义的所有内容都可以都会被派生类继承, 并可从派生类内部进行访问.

在面向对象的应用程序设计中, 我们通常不希望这种行为, 因为他们暴露基类的内部实现, 可能导致派生类中的使用的私有名称与基类中使用的相同的私有名称发生冲突.

属性或方法私有后就可以避免这种问题!

2.2.2 "私有"机制

为了解决前面说的问题, python 提供了一种叫做 名称改写(name mangling) 的机制

如果给属性或者方法命名的时候, 使用两个下划线开头( __ )的属性和方法名会自动变形为 _类名__方法名 , 这样就避免了在基础中命名冲突的问题.

class Student:def __init__(self):passdef __say(self):print("我是私有方法你信吗?")s = Student()s.__say()    # 双下划线开头的方法已经被形变, 此处访问不到

s._Student__say()

2.2.3 不是真正的私有

尽管这种方案隐藏了数据, 但是并没有提供严格的机制来限制对私有属性和方法的访问.

虽然这种机制好像多了一层处理, 但是这种变形是发生在类的定义期间, 并不会在方法执行期间发生, 所以并没有添加额外的开销.

2.2.4 不同的声音

有部分人认为这种使用双 __ 的机制好辣鸡, 写两个下划线影响效率. 他们使用一个下划线, 并把这个作为一个约定.

好吧, 你喜欢哪种呢?

三、面向对象三大特性-继承性(Inheritance)

这一节我们来学习面向的对象的再一个特征: 继承

3.1继承性的概念

继承( extends )是创建新类的一种机制, 目的是专门使用和修改先有类的行为.

原有类称为超类( super class ), 基类( base class )或父类.

新类称为子类或派生类.

通过继承创建类时, 所创建的类将继承其基类所有的属性和方法, 派生类也可以重新定义任何这些属性和方法, 并添加自己的新属性和方法

3.2 继承性的意义

继承实现代码的重用,相同的代码不需要重复的编写

从子类的角度来看,避免了重复的代码。(子类继承父类后,子类可以直接使用父类的属性和方法)

从父类的角度来看,子类扩展了父类的功能。(因为子类也是一个特殊的父类)

  1. 子类可以直接访问父类的属性和方法。
  2. 子类可以新增自己的属性和方法。
  3. 子类可以重写父类的方法。

3.3 继承的语法和具体实现

继承的语法如下:

class 父类名:passclass 子类名(父类名):pass

3.3.1最简单的继承

python 的继承是在类名的后面添加括号, 然后在括号中声明要继承的父类.

class Father:def speak(self):print("我是父类中的 speak 方法")# Son继承 Father 类
class Son(Father):passs = Son()
s.speak()

说明:

  1. 从字面上我们看到 Son 没有定义任何的方法, 但是由于 Son 继承自 Father , 则 Son 会继承 Father 的所有属性和方法
  2. 调用方法时, 方法的查找规则: 先在当前类中查找, 当前类找不到想要的方法, 则去父类中查找, 还找不到然后继续向上查找. 一旦找到则立即执行. 如果找到最顶层还找不到, 则会抛出异常

示例代码

# 创建人类
class Person:# 定义吃东西方法def eat(self):print("吃窝窝头。。")# 定义睡觉方法def sleep(self):print("睡着啦。。")# 创建学生类
class Student(Person):# 子类新增方法:学习def study(self):print("学生学习啦。。。把你爸乐坏了。。。。。")# 创建父类对象,访问父类的方法
zhangsan = Person();
zhangsan.eat()
zhangsan.sleep()# 创建子类对象,访问父类的方法和子类的方法
ergou = Student();
ergou.eat()  # 访问父类的方法
ergou.sleep()  # 访问父类的方法
ergou.study()  # 访问子类的新增方法

3.3.2 继承中的 __init__() 的调用规则

如果子类没有手动 __init__() 方法, 则 python 自动调用子类的 __init__() 的时候, 也会自动的调用基类的 __init()__ 方法.

class Father:def __init__(self):print("基类的 init ")# Son继承 Father 类
class Son(Father):def speak(self):passs = Son()

如果子类手动添加了 __init__() , 则 python 不会再自动的去调用基类的 __init__()

class Father:def __init__(self):print("基类的 init ")# Son继承 Father 类
class Son(Father):def __init__(self):print("子类的 init ")def speak(self):passs = Son()

如果想通过基类初始化一些数据, 则必须显示的调用这个方法, 调用语法是:

基类名.__init__(self, 参数...)

class Father:def __init__(self, name):print("基类的 init ")self.name = namedef speak(self):print("我是父类中的 speak 方法" + self.name)# Son继承 Father 类
class Son(Father):def __init__(self, name, age):# name 属性的初始化应该交给基类去完成, 手动调用基类的方法. 一般放在首行Father.__init__(self, name)  # 调动指定类的方法, 并手动绑定这个方法的 selfprint("子类的 init ")self.age = ages = Son("李四", 20)
s.speak()
print(s.name)
print(s.age)

3.4方法的重写(override)

3.4.1重写的概念

我们已经了解了调用方法时候的查找规则, 先在子类中查找, 子类查找不到再去父类中查找.

如果父类的方法不满足子类的需求, 利用这个查找规则, 我们就可以在子类中添加一个与父类的一样的方法, 那么以后就会直接执行子类的方法, 而不会再去父类中查找.

这就叫方法的覆写.( override )

>重写,就是子类将父类已有的方法重新实现。

父类封装的方法,不能满足子类的需求,子类可以重写父类的方法。在调用时, 调用的是重写的方法,而不会调用父类封装的方法。

3.4.2重写父类方法的两种情况

  1. 覆盖父类 的方法

    父类的方法实现和子类的方法实现,完全不同,子类可以重新编写父类的方法实现。

    具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现

  2. 对父类方法 进行扩展

    子类的方法实现中包含父类的方法实现。(也就是说,父类原本封装的方法实现是子类方法的一部分)。

    在子类中重写父类的方法

    在需要的位置使用 super().父类方法 来调用父类的方法

    代码其他的位置针对子类的需求,编写子类特有的代码实现。

如果在覆写的方法中, 子类还需要执行父类的方法, 则可以手动调用父类的方法:

父类名.方法(self, 参数...)

class Father:def __init__(self, name):self.name = namedef speak(self):print("我是父类中的 speak 方法" + self.name)# Son继承 Father 类
class Son(Father):def __init__(self, name, age):Father.__init__(self, name)self.age = age# 子类中覆写了父类的方法def speak(self):Father.speak(self)print("我是子类的 speak 方法" + self.name + " 年龄:" + str(self.age))s = Son("李四", 20)
s.speak()

3.4.3关于super

在Python中super是一个特殊的类(Python 3.x以后出现)

super()就是使用super类创建出来的对象

最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现

3.5、父类的私有属性和方法

  • 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
  • 子类对象可以通过父类的共有方法间接访问到私有属性或私有方法

私有属性和方法是对象的隐私,不对外公开,外界以及子类都不能直接访问

私有属性和方法通常用于做一些内部的事情

3.6、多继承

3.6.1多继承的概念

多继承:子类可以拥有多个父类,并且具有所有父类的属性和方法

​ 比如:孩子会继承自己的父亲和母亲的特性

3.6.2多继承的语法

class 子类名(父类名1, 父类名2...):pass

示例代码:

# 父类A
class A:def test1(self):print("A类中的test1方法。。")# 父类B
class B:def test2(self):print("B类中的test2方法。。")# 子类C同时继承A和B
class C(A,B):pass# 创建C对象
c1 = C()
c1.test1()
c1.test2()

3.6.3多继承的注意事项

提问:如果不同的父类中存在同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?

开发时,应该尽量避免这种容易产生混淆的情况。如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承

3.6.4 Python中的 MRO (方法搜索顺序)[扩展]

python中针对类提供了一个内置属性, ___mro__ 可以查看方法搜索顺序

MRO是method resolution order,主要用于在多继承时判断方法,属性的调用路径

print(C.__mro__)

输出结果:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
  • 在搜索方法时,是按照__mro_-的输出结果从左至右的顺序查找
  • 如果当前类中找到方法,就直接执行,不再搜索
  • 如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索
  • 如果找到最后一个雷,还没有对应的方法,程序报错

3.6.5 python 中的上帝类型

python 中有个类比较特殊, 所有的类都直接和间接的继承自这个类.

这个类就是: object . 他是所有类的基类.

如果一个类没有显示的去继承一个类, 则这个类默认就继承 object , 也可以去显示的继承这个类.

class Student(object):pass

3.6.6 新式类和旧式(经典)类[扩展]

object 是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用

dir 函数查看

新式类:以object为基类的类,推荐使用

经典类:不以object为基类的类,不推荐使用

  • 在python 3.x中定义类时,如果没有指定父类,会默认使用object作为该类的父类。所以python 3.x中定义的类都是新式类

  • 在python 2.x中定义类时,如果没有指定父类,则不会以object作为父类

    新式类和经典类在多继承时,会影响到方法的搜索顺序

提示:为了保证编写的代码能够同时在python 2.x 和python 3.x 运行,在定义类的时候,如果没有父类,建议统一继承自object

class 类名(object):pass

四、面向对象三大特性-多态性(Polymorphism)

4.1多态性的概念

  • 封装性,根据职责将属性和方法封装到一个抽象的类中

    ​ 定义类的准则

  • 继承性,实现代码的重用,相同的代码不需要重复的编 写

    ​ 设计类的技巧

    ​ 子类针对自己特有的书需求,编写特定的代码

  • 多态性,不同的子类对象,调用相同的父类方法,产生不同的执行结果

    ​ 多态可以增加代码的灵活性

    ​ 以继承和重写父类方法为前提

    ​ 是调用方法的技巧,不会影响到类的内部设计

示例代码:

"""
多态性:继承和重写为前提,创建不同的对象执行的具体方法不同
"""
class Father(object):def __init__(self, name):print('父类的init方法')self.name = namedef say(self):print('父类的say方法' + self.name)# Son类继承于Father类,python中是类继承于类的
class Son(Father):def __init__(self, name, age):Father.__init__(self, name)self.age = ageprint('子类的init方法')def say(self):Father.say(self)print('子类的say方法:' + self.name + ',' + str(self.age))# 以下程序会体现出多态性def mytest(obj):obj.say()f1 = Father("张爸爸")
mytest(f1)print("---------------")
f2 = Son("小头儿子",5)
mytest(f2)

4.2属性和方法查找顺序

多态性(多态绑定)是在有继承背景情况下使用的一种特性.

是指在不考虑实例背景的情况下使用实例

多态的理论根据是属性和方法的查找过程. 只要使用 obj.attr 的方式使用属性和方法, 则查找顺序一定是: 对象本身, 类定义, 基类定义...

关于先查找对象本身的说明:因为 python 是一门动态语言, 允许我们在代码执行的过程中去动态的给对象添加属性和方法, 所以先从对象本身查找.

class Father:def __init__(self, name):self.name = namedef speak(self):print("我是父类中的 speak 方法" + self.name)# Son继承 Father 类
class Son(Father):def __init__(self, name, age):Father.__init__(self, name)self.age = agedef speak(self):Father.speak(self)print("我是子类的 speak 方法" + self.name + " 年龄:" + str(self.age))def foo():print("我是动态添加上去的...")s = Son("李四", 20)
s.speak = foo
s.speak()

4.3 鸭子类型

python 的多态有的时候很多人把它称之为 鸭子类型

鸭子类型是指: 看起来像鸭子, 叫起来像鸭子, 走起来像鸭子, 那么它既是鸭子, 你就可以把它当鸭子来用.

换成编程语言的说法就是: 对象属性和方法的时候完成时和类型分开的.

class A:def speak(self):print("a 类中的方法")class B:def speak(self):print("b 类中的方法")def foo(obj):obj.speak()a = A()
b = B()foo(a)
foo(b)

说明:

  1. foo 接受一个对象, 只要这个对象中有 speak() 方法, 就可以正常执行, 我们并不关注他的类型
  2. A, B 这两个类没有任何的关系, 但是他们都有 speak 方法, 所以传递过去都没有任何的问题.
  3. 这就是鸭子模型, 只要你看起来有 speak 就可以了

五、其他

5.1 特殊属性__slot__

5.1.1动态添加属性的问题

通过前面的学习中我们知道, 由于 python 的动态语言的特性, 我们可以动态的给对象添加属性和方法.

但是这种方式添加的属性和方法, 只在当前对象上有用, 在其他对象上是没用.

class A:passa1 = A()
a1.name = "李四"  #给 a1 对象添加一个属性
print(a1.name)a2 = A()
print(a2.name)    # a2中没有 name 属性, 所以抛异常

5.1.2 __slot__ 的基本使用

添加属性和方法最好直接在类中添加, 这样所有的对象都可以拥有了.

如果我想避免把某些属性直接添加到实例对象上, 可以使用一个特殊属性: __slot__ 类实现.

给 __slot__ 定义一个元组, 则元组内的属性名允许在实例对象上直接添加, 其他的都不允许.

class A:__slots__ = ("name", )a1 = A()
a1.name = "李四"  # 给 a1 对象添加一个属性   name 属性是允许的
print(a1.name)
a1.age = 20    # age 不允许, 所以抛异常
print(a1.age)

注意:

  1. 我们的 __init__() 中添加属性是在 self 上添加的, 其实也是直接在对象上添加, 所以没有在元组中的属性名, 也是不允许的.
  2. 对于我们直接在类中添加方法是没有任何的影响的.
class A:__slots__ = ("name",)def __init__(self):self.age = 30  # 也是不允许的a = A()

5.1.3 继承中的 __slot__

__slot__ 只对当前类有用, 对他的子类不起作用. 所以子类也要有自己的 __slot__

class A:__slots__ = ("name",)def __init__(self):self.age = 30  # 也是不允许的class B:def __init__(self):self.age = 30b = B()
print(b.age)

5.1.4 __slot__ 对性能上的提升

一些人把 __slot__ 作为一种安全的特性来实现, 然后实际上他对内存和执行速度上的性能优化才是最重要的.

不使用 __slot__ , python 使用字典的方式去存储实例数据的, 如果一个程序使用大量的实例, 测内存占用和执行效率都会影响比较大.

使用 __slot__ 后, python 存储实例数据的时候, 不再使用字典, 而是使用一种更加高效的基于数组的数据结构. 可以显著减少内存占用和执行时间.

5.2 实例的测试类型

任何一个类都可以做为类型!

创建类的实例时, 该实例的类型是这个类本身, 如果有继承存在, 则父类型也是这个实例的类型.

有些情况下, 我们需要先测试实例的类型然后再写相应的代码.

python 支持 2 种测试方法:

5.2.1 内置函数: type(实例)

class A:passclass B(A):passclass C:passa = A()
b = B()
c = C()print(type(a))
print(type(b))
print(type(c))

说明:

type 返回的是这个实例的所属类的类对象.

补充一下:

其实我们经常接触到的有两种对象:1. 实例对象 2. 类对象

类对象就是: 表示类本身的那个对象!

5.2.2 内置函数: isinstance(实例, 类型)

class A:passclass B(A):passclass C:passa = A()
b = B()
c = C()print(isinstance(a, A))  # True
print(isinstance(b, B))  # True
print(isinstance(b, A))  # True    继承关系
print(isinstance(c, C))  # True
print(isinstance(c, A))  # False

说明:

  1. 这个函数返回的是布尔值, 使用起来方便, 所以以后测试类型建议用这个函数
  2. 这个函数继承关系也可以测试出来. b 是 B 类创建出来的, B 继承自 A , 所以 b 也算是类 A的实例.
  3. 对一个实例也可以同时测试多个类型, 有一个满足就返回 True , isinstance(实例, (类 a, 类 b, ...))) . 需要把多个类封装到一个 tuple 中.
print(isinstance(c, (A, B, C)))     # True

5.2.3 类与类的关系: issubclass(类1, 类2)

用来测试 类1 是不是 类2 的子类.

class A:passclass B(A):passclass C:passprint(issubclass(B, A))  # True
print(issubclass(C, A))  # False
print(issubclass(A, B))  # False
print(issubclass(A, object))  # True
print(issubclass(C, (object, A)))  # True  第二个参数也是可以 class 组成的元组

Python编程教程:面向对象之高级特性!相关推荐

  1. python是人都能学会_人人都能学会的python编程教程15:高级特性2

    生成器 如果你想要一百万个数,而这些数里只有一百个数是你经常要用的,剩下的都几乎不怎么会用到,那么如果直接把这一百万个数全部放在list中是不明智的因为这会浪费较多存储空间,生成器就是为了解决这个问题 ...

  2. 人人都能学会的python编程教程15:高级特性2

    生成器 如果你想要一百万个数,而这些数里只有一百个数是你经常要用的,剩下的都几乎不怎么会用到,那么如果直接把这一百万个数全部放在list中是不明智的因为这会浪费较多存储空间,生成器就是为了解决这个问题 ...

  3. 人人都能学会的python编程教程14:高级特性1

    切片 取一个list或tuple的部分元素是非常常见的操作.比如,一个list如下: L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] 第一个元素的索引 ...

  4. 人人都能学会的python编程教程(基础篇)完整版

    人人都能学会的python编程教程1:第一行代码 人人都能学会的python编程教程2:数据类型和变量 人人都能学会的python编程教程3:字符串和编码 人人都能学会的python编程教程4:关系运 ...

  5. 人人都能学会的python编程教程1:第一行代码

    前言 众所周知,现在IT行业很火,行业薪酬也很高,国家在2017年也发布了人工智能教育的推广计划,人人会编程的时代将要到来.不会编程.不懂编程可能有些跟不上时代的节奏,普通人不懂技术也许会觉得这个很难 ...

  6. php抽象类继承抽象类,PHP面向对象程序设计高级特性详解(接口,继承,抽象类,析构,克隆等)...

    本文实例讲述了PHP面向对象程序设计高级特性.分享给大家供大家参考,具体如下: 静态属性 class StaticExample { static public $aNum = 0; // 静态共有属 ...

  7. 零基础新手自学Python编程教程入门精通学习资料网站大全

    零基础新手自学Python编程教程入门精通学习资料网站大全 今天说下关于Python的一些普及知识,以及学习资料,这一节我来跟大家分享下. 1 为什么要学习Python? 1 Python是一个脚本语 ...

  8. Python面向对象的高级特性

    一.类方法与静态方法 类方法是类对象所拥有的方法,需要用修饰器一般以@classmethod来标识其为类方法, 1). 对于类方法,第一个参数必须是类对象,作为第一个参数 (cls是形参, 可以修改为 ...

  9. python编程教程第九讲_Python入门学习视频,最全面讲解视频无偿分享,这些基础知识你都懂了吗?...

    2020最新Python零基础到精通资料教材,干货分享,新基础Python教材,看这里,这里有你想要的所有资源哦,最强笔记,教你怎么入门提升!让你对自己更加有信心,重点是资料都是免费的,免费!!! 如 ...

  10. Python 进阶:全面解读高级特性之切片!

    作者 | 豌豆花下猫 责编 | 胡巍巍 众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串.列表.元组-)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢? 切片(slice) ...

最新文章

  1. 办公室自动化系统_大队举办办公自动化系统培训班
  2. 太极团队内部邮件曝光:iOS8完美越狱重大突破
  3. 制作ubuntu 18.04 U盘启动盘
  4. netty系列之:netty对SOCKS协议的支持
  5. 线性序列机与串行接口ADC驱动设计与验证
  6. 运动会加油稿计算机学院,信息工程学院运动会加油稿
  7. __declspec(dllexport)、__declspec(dllimport)详解
  8. python3可以运行python2的代码吗_Python同时兼容python2和python3的8个技巧分享
  9. 淘宝主营类目占比对店铺有哪些影响
  10. 如何制作学术Poster?
  11. C语言图形库——EasyX基本贴图
  12. Linux-脚本、tailf
  13. 牛课-跳跃游戏1,2,3
  14. 数字科技陪伴企业成长|突破封锁,庚顿数据助力中国名牌全球瞩目
  15. Error occurred when installing package 'qcloud_cos'
  16. 树莓派CM4_5G扩展板
  17. 今天第一次解决了程序在未装VS和XP下运行的问题
  18. ffmypeg 视频处理类库使用方法
  19. 【网络篇】第十七篇——IP协议详解
  20. 【S0020】【素材】梦幻抽象设计感海报PSD源文件

热门文章

  1. javascript-网页换肤案例
  2. Redis:Cannot assign requested address的解决办法
  3. 1. 性能测试学习指南
  4. Python爬虫urllib库的使用
  5. 计科14-1 140201125 王振禹 作业三
  6. OAuth和OpenID的区别(转)
  7. 获取客户端网卡MAC地址和IP地址的几种方法(一)
  8. Vuex 状态管理的工作原理
  9. 科普文:为什么不能在服务器上 npm install ? #30
  10. Nginx 动态模块