面向对象高阶语法

                  高阶语法

再谈继承

单继承&多继承

在python中,一个子类可以继承一个类,也可以继承多个父类。

class A():
    def __init__(self,name):
        self.name =name
        print('from A')
    def talk(self):
        print('I`m %s'%self.name)

class B():
    def __init__(self):
        print('from B')
    def run(self):
        print('is running')

class C(A):   # 单继承
    pass

class D(A,B):    # 多继承
    pass

在上述代码中,子类C继承了父类A,子类D继承了父类A,B.对于这样的继承就称为单继承和多继承

派生

在子类继承父类的基础属性外,子类还可以自己定义和创建独有的特征属性。

class A():
    def __init__(self,name):
        self.name =name
        print('from A')
    def talk(self):
        print('I`m %s'%self.name)

class B():
    def __init__(self):
        print('from B')
    def run(self):
        print('is running')

class C(A):
    def __init__(self,name):
        self.name = name
        print('from C')
    def talk(self):           # 重新定义父类的方法talk
        print('hello boy!')

class D(A,B):
    def __init__(self,name):
        self.name =name
        print('From D')
    def sleep(self):        # 创建本身独有的方法sleep
        print('%s is sleeping'%self.name)
s1 =C('Tom')
s1.talk()
s2 =D('Jack')
s2.run()
s2.talk()
s2.sleep()

输出:

from C
hello boy!
From D
is running
I`m Jack
Jack is sleeping

 查看继承

print(C.__bases__)   # 查看C的基类
print(D.__bases__)    # 查看D的基类
输出:
(<class '__main__.A'>,)
(<class '__main__.A'>, <class '__main__.B'>)

 继承与抽象(先抽象在继承)

抽象即抽取类似或者说比较像的部分。

抽象分为两个层次:

  1. 将对象比较像的部分抽取成类
  2. 将类中比较像的部分抽取成父类

 抽象最主要的作用是划分类别(隔离关注点,降低复杂度)

继承:是基于抽象的结果,通过编程语言去实现它。所以说先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构

继承与重用性

在我们开发 程序的过程中,先定义一个类C1,在定义 一个类C2,如果他们中有很大部分的代码重复性,就不能从头在开始写C2.。这就要用到类的继承概念

通过继承类C1的方式去创建新类C2,让C2继承C1,这样就大大的减少的代码量,提高的代码的重用性!

class People():
    def __init__(self,name,sex,age):
        self.name =name
        self.sex =sex
        self.age =age
class Teacher(People):
    # 使用super方法继承People的__init__方法已有的属性
    def __init__(self,name,sex,age,job_title,):
        super(Teacher, self).__init__(name,sex,age)   
        self.job_title = job_title
        self.course =[]
        self.students =[]

继承的实现原理

python是怎么实现继承的呢?对于你定义的每一个类,python会计算出一个方法解析顺序(mro)列表,

这个MRO列表就是死一个简单的所有基类的线性顺序列表,例如:

print(F.__mro__) # 只有新式才有这个属性可以查看线性列表,经典类没有这个属性

(<class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.A'>, <class 'object'>)

为了实现继承,python会在MRO列表从左到右开始查找基类,直到找到匹配这个属性的类为止。

MRO列表遵循以下三个准则:

  1. 子类先于父类被查找
  2. 多个父类会根据它们所在列表的位置被检查
  3. 如果两个父类都有合法的属性选择,会选择第一个父类

在python中如果继承了多个类,那么属性的查找有两种方式:

  1. 深度优先:当类是经典类时,多继承情况下,在查找的属性不存在时,会按照深度优先的方式去查找
  2. 广度优先:当类是新式类时,多继承情况下,在查找的属性不存在时,会按照广度优先的方式去查找

如下示例中:

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

class B(A):
    def __init__(self):
        print('from B')

class C(B):
    def __init__(self,name):
        self.name = name
        print('from C')

class D(B):
    def __init__(self,name):
        self.name =name
        print('From D')

class E(A):
    def __init__(self,name):
        self.name =name
        print('From E')

class F(C,D,E):
    def __init__(self,name):
        self.name =name
        print('From F')

类F的mro列表:

(<class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.A'>, <class 'object'>)

查找方式:

新式类:  F-->  C-->  B-->  D-->  B-->  E-->  A
 
经典类:F-> C--> B--> A-->  D --> B-->  E

再谈父类属性调用

调用父类属性的方法,一共有两种:

指名道姓法(不依赖于继承)

class Bird:
    def __init__(self):
        self.hungry = True

    def eat(self):
        if self.hungry:
            print('该为食了')
            self.hungry= False
        else:
            print('活力四射')

class SongBird(Bird):
    def __init__(self):
        print('嗨!')
    def say(self):
        print('hello')
s= SongBird()
s.say()     # 调用方法say
输出:
嗨!
hello

那现在调用方法eat,结果是什么?

s.eat()
输出:
AttributeError: 'SongBird' object has no attribute 'hungry'

为什么报错呢?那是因为子类中没有属性hungry,并且子类SongBird中未关联方法eat。现在我们关联方法eat

class SongBird(Bird):
    def __init__(self):
        print('嗨!')
        Bird.__init__(self)       # 调用父类调用其方法init
    def say(self):
        print('hello')

s.eat()  # 再次调用方法eat

输出:
嗨!
该为食了

这种调用父类属性方式成为指名道姓法,因为其是通过父类本身去调用的方法init,所以不会依赖于继承

super方法(依赖于继承)

class A:
    def f1(self):
        print('from A')
        super().f1()  #1

class B():
    def f1(self):
        print('from B')

class C(A,B):
    pass

print(C.__mro__)   # 查看C的属性查找列表
c =C()
c.f1()

输出:

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
from A
from B

对于结果是不是很诧异?很多人会问为什么会出来个 from B?还有1处的super是怎么继承的?

那我们来分析下,当打印出 " from A "时,根据MRO列表的查找顺序,现在处于class A处,之后根据 “ super ” 再次调用方法 f1。

那问题来了,你说会不会再次调用类A中的 f1?

class A:
    def f1(self):
        print('from A')
        super().f1()

class B():
    pass
    # def f1(self):
    #     print('from B')

class C(A,B):
    pass

c =C()
c.f1()
输出:
from A

AttributeError: 'super' object has no attribute 'f1'

从结果来看显而易见。那我们来看下此时的super是什么,先做一点改动

class A:
    def f1(self):
        print('from A')
        print(super())     # 查看 super 的继承状态
        print(super().__dict__)     # 查看super的属性空间
        super().f1()

class B():
    pass

class C(A,B):
    pass

c =C()
c.f1()
输出:
<super: <class 'A'>, <C object>>
dict {}   

由结果可以看出,此时的super不是A继承了哪个类,它是表示的是C继承了A.。并且super的属性字典为空,所以根本就不会调用类A中的 f1。

那这个super是怎么执行的呢?之前已经说了属性查找是根据mro列表顺序来进行的,所以它还会返回到MRO列表中进行查找,下一个为类B,内有方法 f1,所以就会打印出 from B !

总之,super时根据继承的实现原理(MRO列表)执行的 ,所以它依赖于继承。

组合与重用性

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

先来看一下示例

class People:
    school ='蓝天'
    count =0
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
        People.count +=1
        print('It has %s user to be enrolled!'% self.count)

class Teacher(People):
    def __init__(self,name,age,sex,salary):
        super().__init__(name,age,sex)
        self.salary =salary
        # self.course = []
    def teach(self):
        print('%s is teaching'% self.name)

class Students(People):
    def __init__(self,name,age,sex,class_time):
        super().__init__(name,age,sex)
        self.class_time =class_time

    def learn(self):
        print('%s is learning'% self.name)

对于以上代码,现在增加一个需求,为老师和学生添加一个课程属性,怎么写呢?

# 在创建一个类Course
class Course:
    def __init__(self,course_name,course_price,course_period):
        self.course_name = course_name
        self.course_price = course_price
        self.course_period = course_period

    def tell_info(self):
        print('课程名称:%s  课程价格:%s  学习周期:%s'%(self.course_name,self.course_price,self.course_period))

s1=Teacher('jack',28,'meal',3000)
s2=Teacher('join',38,'meal',6000)
t1 = Students('Tom',18,'meal','08:30:00')
# 获取两个Course对象
python = Course('Python',8999,6)
linux = Course('linux',6999,6)

# 将两个课程对象分别关联到教师对象
s1.course = python
s2.course = linux

# 调用课程对象的方法 tell_info,此时s1.course相当于 python ; s2.course相当于linux
s1.course.tell_info()
s2.course.tell_info()

输出:
课程名称:Python  课程价格:8999  学习周期:6
课程名称:linux  课程价格:6999  学习周期:6

以上这种将课程类Course关联到对象的方法称为组合!那对于多个课程怎么添加呢?

class Teacher(People):
    def __init__(self,name,age,sex,salary):
        super().__init__(name,age,sex)
        self.salary =salary
        self.course = []     # 增加一个课程列表
    def teach(self):
        print('%s is teaching'% self.name)
        print('%s teacher` course is %s'%(self.name,self.course))

s1.course.append(python)
s1.course.append(linux)

for obj in s1.course:   # 循环取出每一个课程对象
    obj.tell_info()         # 在用课程对象调用其方法 tell_info

输出:
课程名称:Python  课程价格:8999  学习周期:6
课程名称:linux  课程价格:6999  学习周期:6

组合和继承的区别

组合和继承都是有效的利用了已有类属性的重要方式,但二者的概念和使用场景皆不同

继承的方式:

通过继承建立了派生类与基类之间的关系,他们是一种 “ 是 “的关系,比如:白猫是猫

当类之间有很多相同的功能时,提取这些共同 的功能作为基类,使用继比较好承,比如:老师是人,学生也是人

组合的方式 :

用组合的方式建立了类与组合之间的关系,它是一种 “ 有 ” 的关系。比如:学生有课程、生日

抽象类

接口与归一化

接口是提取了一群类共同的函数,可以把接口当做一个函数的集合

在JAVA中有一个模块interface,很好的诠释了接口的含义

 1 =================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java
 2 package com.oo.demo;
 3 public interface IAnimal {
 4     public void eat();
 5     public void run(); 
 6     public void sleep(); 
 7     public void speak();
 8 }
 9 
10 =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 
11 package com.oo.demo;
12 public class Pig implements IAnimal{ //如下每个函数都需要详细实现
13     public void eat(){
14         System.out.println("Pig like to eat grass");
15     }
16 
17     public void run(){
18         System.out.println("Pig run: front legs, back legs");
19     }
20 
21     public void sleep(){
22         System.out.println("Pig sleep 16 hours every day");
23     }
24 
25     public void speak(){
26         System.out.println("Pig can not speak"); }
27 }
28 
29 =================第三部分:Person2.java
30 /*
31 *实现了IAnimal的“人”,有几点说明一下: 
32 * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样
33 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */
34 
35 package com.oo.demo;
36 public class Person2 implements IAnimal { 
37     public void eat(){
38         System.out.println("Person like to eat meat");
39     }
40 
41     public void run(){
42         System.out.println("Person run: left leg, right leg");
43     }
44 
45     public void sleep(){
46         System.out.println("Person sleep 8 hours every dat"); 
47     }
48 
49     public void speak(){
50         System.out.println("Hellow world, I am a person");
51     } 
52 }
53 
54 =================第四部分:Tester03.java
55 package com.oo.demo;
56 
57 public class Tester03 {
58     public static void main(String[] args) {
59         System.out.println("===This is a person==="); 
60         IAnimal person = new Person2();
61         person.eat();
62         person.run();
63         person.sleep();
64         person.speak();
65 
66         System.out.println("\n===This is a pig===");
67         IAnimal pig = new Pig();
68         pig.eat();
69         pig.run();
70         pig.sleep();
71         pig.speak();
72     } 
73 }
74 
75  java中的interface
接口 interface示例

Java的 Interface 接口的特征

  1. 是一个功能的集合,而不是一个 功能
  2. 接口的功能只用于交互,所有的功能都是public,即所有的对象都能操作
  3. 接口只定义,而不涉及函数的实现

归一化是指只要是基于这个接口实现的类,那么所有的 类产生的对象在使用时,用法都是一样的

归一化的好处在于

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

在python中是没有接口的的概念的,但我们可以通过第三方模块(abc)去模拟实现它

也可以使用继承,其实继承的用途有两种:

  1. 继承基类的方法,并做出自己的改变或扩展(代码重用):实践中,继承这种用途意义并不是很大,甚至常常是有害的。因为它使得子类和基类出现强耦合。
  2. 声明子类兼容某个基类,定义一个接口类(模仿interface),接口中定义了一些接口名(函数名)且并未实现 接口的功能,子类继承基类,并实现接口中的功能
 1 class Interface:  # 定义接口nterface类来模仿接口的概念
 2 
 3     def read(self):     # 定义read方法
 4         pass
 5     def write(self):    # 定义write方法
 6         pass
 7 
 8 class Txt(Interface):
 9 
10     def read(self):     # 实现read方法
11         print('文本数据的读取方法')
12 
13     def write(self):    # 实现write方法
14         print('文本数据的写入方法')
15 
16 class Sata(Interface):
17 
18     def read(self):  # 实现read方法
19         print('硬盘数据的读取方法')
20 
21     def write(self):  # 实现write方法
22         print('硬盘数据的写入方法')
23 
24 class Process(Interface):
25 
26     def read(self):  # 实现read方法
27         print('进程数据的读取方法')
28 
29     def write(self):  # 实现write方法
30         print('进程数据的写入方法')
31 
32 p =Process()
33 p.write()
34 p.read()
35 
36 输出:
37 进程数据的写入方法
38 进程数据的读取方法

上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类

什么是抽象类?

抽象类是一个特殊的类,其特殊之处在于只能被继承,不能被实例化

为什么要有抽象类?

如果说类是从一堆对象中抽取相同的 内容而来的,那么抽象类就是从一堆类中抽取相同 的内容而来的,内容包括数据属性和函数属性

  • 从设计角度讲,如果类是从现实对象抽象而来的,那么抽象类就是从类中抽象而来的
  • 从实现角度讲,抽象类和普通类的不同之处在于:抽象类只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现其抽象方法。

在python中实现抽象类:

import abc  # 利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):  # 定义接口nterface类来模仿接口的概念
    all_type = 'file'   # 一切皆文件
    @abc.abstractmethod
    def read(self):     # 定义read方法
        '''
        子类实现其功能
        '''
        pass

    @abc.abstractmethod
    def write(self):    # 定义write方法
        '''
        子类实现其功能
        '''
        pass

class Txt(All_file):    # 子类继承抽象类并实现功能

    def read(self):     # 实现read方法
        print('文本数据的读取方法')

    def write(self):    # 实现write方法
        print('文本数据的写入方法')

class Sata(All_file):   # 子类继承抽象类并实现功能

    def read(self):  # 实现read方法
        print('硬盘数据的读取方法')

    def write(self):  # 实现write方法
        print('硬盘数据的写入方法')

class Process(All_file):    # 子类继承抽象类并实现功能

    def read(self):  # 实现read方法
        print('进程数据的读取方法')

    def write(self):  # 实现write方法
        print('进程数据的写入方法')

实例化调用方法:

p =Process()
s =Sata()
t =Txt()
# 方法名称同一化,即归一化 方便对象调用
p.write()
s.write()
t.write()

print(p.all_type)
print(s.all_type)
print(t.all_type)

输出:

进程数据的写入方法
硬盘数据的写入方法
文本数据的写入方法
file
file
file

抽象类和接口的区别:

抽象类的本质还是类,指的是一组类的相似性,包括数据属性(all_type) 和函数属性(read、write),而接口只强调函数属性的相似性。

抽象类是一个介于类和接口之间的一个概念,同时具备接口和类的部分特性,可以用来实现归一化设计

对象的魔法特性:多态 & 多态性

多态和方法:

多态就是对象的多种形态(即对象的类),让你无需知道对象所属的类就能调用其方法。

方法:与对象属性相关联的方法

下面我们定义一个函数来看一下多态

def length_message(x):
    print("The length of ",repr(x),"is" ,len(x))

length_message('Hello word!')

length_message('[1,2,3,4]')
length_message('')

res:

The length of  'Hello word!' is 11
The length of  '[1,2,3,4]' is 9
The length of  '' is 0

 上述代码中,函数length_message对参数的唯一 要求是有长度,可对其调用函数len。此外,代码中还是用了repr。repr是多态的集大成者之一,可用于任何对象

函数分别传入字符串、列表和空格都能调用函数len。函数length_message的多态就在于不管参数是什么类型,只要有长度即可

多态是指一类事物的多种形态,那多态性是什么呢?

多态性

多态性是指在不考虑实例类型 的情况下使用实例,多态性分为静态多态性和动态多态性

静态多态性:如任何类型都可以用运算符+进行运算

动态多态性:

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

多态性的好处

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程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

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

示例一:

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

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

示例二:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
print(s.__len__())
print(l.__len__())
print(t.__len__())

print(len(s))
print(len(l))
print(len(t))

输出:
5
3
3

5
3
3
函数len为内置函数,也就是抽象类序列的函数属性,由于python崇尚鸭子类型,所以在未继承的情况下,我们也可以直接使用函数len

对象的魔法特性:封装

封装就是对外部隐藏有关对象工作原理的细节(无需知道对象的内部细节就可使用它),是不是和多态类似?因为他们都是抽象的原则。

他们都像函数一样,可以帮助你处理程序的组成部分,而你无需关心不必要的细节。

封装不同于多态,封装让你无需知道对象的构造就能使用它。但是封装不等于隐藏

如何隐藏

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

# 其实隐藏仅仅这是一种变形操作
# 类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

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

#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

这种变形的特点:

  1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果
  2. 这种变形其实正是针对外部的变形,在外部是无法通过__x来访问到的
  3. 在子类中定义的__x不会覆盖父类中的__x,因为子类中变成了:_子类名__x;而父类中变成了:_父类名__x。即双下划綫开头的属性继承给子类时,子类是无法覆盖的。

这种变形需要注意的问题是:

  1. 这种机制并没有彻底的限制我们从外部访问属性,知道了类名和属性名就可以拼出名字进而访问属性:_类名__属性名。如:a._A__x
  2. 这种属性变形只在类定义时变形一次,类定义之后的赋值操作是不会变形的
  3. 在继承中如果父类的属性不想被子类覆盖,那么可以设为私有属性
class A:
    def __init__(self):
        pass
    # def talk(self):  # 未加__
    def __talk(self):     # 加__
        print(' is talking')

# print('加__之前:',A.__dict__)
print('加__之后:',A.__dict__)

输出:
加__之前: {'__module__': '__main__', '__init__': <function A.__init__ at 0x000001C52A1429D8>,
#注意  'talk': <function A.talk at 0x000001C52A142A60>, '__dict__': <attribute '__dict__' of 'A' objects>,
     '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} 加__之后: {'__module__': '__main__', '__init__': <function A.__init__ at 0x0000012DB7CA29D8>,
# 注意 '_A__talk': <function A.__talk at 0x0000012DB7CA2A60>, '__dict__': <attribute '__dict__' of 'A' objects>,
     '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

我们来访问下属性:

class A:
    def __init__(self):
        pass
    def __talk(self):     # 加__
        print(' is talking')

A.__talk()      # 访问失败

输出:        # 找不到属性__talk
AttributeError: type object 'A' has no attribute '__talk'

# 换一种方式

A._A__talk(A)   # 访问成功

输出: is talking

类定义后再赋值

A.name = 'jack'
print('之后:',A.__dict__)
输出:
之后: {'__module__': '__main__', '__init__': <function A.__init__ at 0x00000233B83A29D8>,
'_A__talk': <function A.__talk at 0x00000233B83A2A60>, '__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'name': 'jack'} # 未变形

子类继承父类带有属性,父类属性没有被覆盖

class B(A):        # B继承父类A的talk属性
    def __talk(self):
        print('B is talking')

b =B()
b._B__talk()
输出:           # 没有没覆盖
B is talking 

封装不是单纯意义上的隐藏

1. 封装数据:

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

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,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须是整型')
        self.__name=name
        self.__age=age

t=Teacher('Join',18)
t.tell_info()
输出:姓名:Join,年龄:18

t.set_info('jack',19)
t.tell_info()
输出:姓名:jack,年龄:19

2. 封装方法:隔离复杂度

#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性

class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印账单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

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

 特性:property

什么是特性property

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

成人的BMI数值:

过轻:低于18.5

正常:18.5-23.9

过重:24-27

肥胖:28-32

非常肥胖, 高于32

体质指数(BMI)=体重(kg)÷身高^2(m)

EX:70kg÷(1.75×1.75)=22.86

class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.__weight = weight
        self.__height = height

    def hmi(self):
        return self.__weight/(self.__height**2)

p = People('Jakc',65,1.65)
print(p.hmi())

输出:23.875114784205696

那现在我们不想让使用者这样调用,给他一个假象的属性调用,那该如何做?

class People:
    def __init__(self,name,weight,height):
        self.name = name
        self.__weight = weight
        self.__height = height
    @property   # 给函数hmi加一个装饰器property
    def hmi(self):
        return self.__weight/(self.__height**2)

p = People('Jakc',65,1.65)
print(p.hmi)

输出:
23.875114784205696

通过装饰器property将函数hmi装饰成一个数据属性,但其真实性还是函数属性。只不过装饰器property将函数调用执行返回一个结果。

例二:圆的面积、周长

import math
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.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586

注意:此时的特性area和perimeter不能被赋值

c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

除此之外:

ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类)公开
【private】
这种封装对谁都不公开

python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现

class Foo:
    def __init__(self,val):
        self.__NAME = va    l# 将所有的数据属性都隐藏起来
    
    @property
    def name(self):
        return self.__NAME   # obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):   #在设定值之前进行类型检查
            raise TypeError('名字必须是字符串')
        self.__NAME =value  # 通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError('无法删除')

f = Foo('Jack')
print(f.name)        # 返回 Jack
f.name = 8           # 返回 TypeError: 名字必须是字符串
f.name = 'Tina'     #  返回 Tina

封装与扩展性

封装在于明确区分内外,使得设计者可以修改封装内的东西而不影响外部调用者的代码。而外部使用者只需要知道一个接口(函数),一个接口名、参数不变,使用者的代码永远不用变

# 设计者
class Room:
    def __init__(self,name,owner,width,lenght,high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = lenght
        self.__high = high

    def area(self):    # 求面积
        return self.__length * self.__width

# 使用者
r1 =Room('WC','Jack',10,12,18)
print(r1.area())

# 对内部代码进行改动

class Room:
    def __init__(self,name,owner,width,lenght,high):
        self.name = name
        self.owner = owner
        self.__width = width
        self.__length = lenght
        self.__high = high

    def area(self):    # 求体积
        return self.__length * self.__width * self.__high
r1 =Room('WC','Jack',10,12,18)
print(r1.area())    # 对代码修改后,使用者仍然使用此接口获取结果

 

posted @ 2018-08-06 18:04  繁华无殇  阅读(135)  评论(0编辑  收藏  举报