一、封装

封装 :

广义上的 :把一堆东西装在一个容器里

狭义上的 :会对一种现象起一个专门属于它的名字

函数和属性装到了一个非全局的命名空间 —— 封装

隐藏对象的属性和实现细节,仅对外提供公共访问方式。

【好处】

  1. 将变化隔离;

  2. 便于使用;

  3. 提高复用性;

  4. 提高安全性;

【封装原则】

  1. 将不需要对外提供的内容都隐藏起来;

  2. 把属性都隐藏,提供公共方法对其访问。

私有变量和私有方法

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

  1. class A:
  2. __N = 'aaa'
  3. print(A.__N)

执行报错:

AttributeError: type object ‘A’ has no attribute ‘__N’

这个是__N 就是私有属性

python

pulic 公有的

private 私有的

java 完全面向对象的语言

public 公有的

protect 保护的

private 私有的

python 之前学的所有属性,都是公有的

定义一个私有的名字 : 就是在私有的名气前面加两条下划线 __N = ‘aaa’

所谓私有,就是不能在类的外面去引用它

私有类的静态变量

  1. class A:
  2. __N = 'aaa' # 静态变量
  3. def func(self):
  4. print(A.__N) # 在类的内部使用正常
  5. a = A()
  6. a.func()
  7. #print(A.__N) # 在类的外部直接用 报错

执行输出:aaa

那么__N 真的私有的吗?

  1. class A:
  2. __N = 'aaa' # 静态变量
  3. def func(self):
  4. print(A.__N) # 在类的内部使用正常
  5. print(A.__dict__)

执行输出:

{‘func’: , ‘dict‘: , ‘AN’: ‘aaa’, ‘weakref‘: <attribute ‘weakref‘ of ‘A’ objects>, ‘doc‘: None, ‘module‘: ‘main_‘}

从输出的结果中,可以看到_A__N

在外部,依然可以调用,只不过,存储的的时候,做了变形

  1. class A:
  2. __N = 'aaa' # 静态变量
  3. def func(self):
  4. print(A.__N) # 在类的内部使用正常
  5. #print(A.__dict__) # python就是把__名字当成私有的语法
  6. print(A._A__N)

执行输出:aaa

为什么敲 A._A__N 没有提示呢?

这是 Pycharm 做的功能,防止你调用私有变量

虽然这样可以调用,但是在以后的工作中,禁止调用,这样是不规范的。

一个私有的名字 在存储的过程中仍然会出现在 A.dict中,所以我们仍然可以调用到。

python 对其的名字进行了修改: 类名_名字

只不过在类的外部调用 :需要”类名_名字”去使用

在类的内部可以正常的使用名字

那么为什么类外部是_AN,内部是N

那么内存中,存的还是AN。请看一下dict_就知道了

_A__N

在类的内部 只要你的代码遇到名字,就会被 python 解释器自动的转换成_类名名字

私有属性

  1. class B:
  2. def __init__(self,name):
  3. self.__name = name
  4. b = B('alex')
  5. print(b.__name)

执行输出:

AttributeError: ‘B’ object has no attribute ‘__name’

私有属性变形了

  1. class B:
  2. def __init__(self,name):
  3. self.__name = name
  4. b = B('alex')
  5. print(b._B__name)

执行输出:alex

私有方法

  1. class C:
  2. def __wahaha(self):
  3. print('wahaha')
  4. c = C()
  5. c.__wahaha()

执行报错:

AttributeError: ‘C’ object has no attribute ‘__wahaha’

方法也变形了

  1. class C:
  2. def __wahaha(self):
  3. print('wahaha')
  4. c = C()
  5. c._C__wahaha()

执行输出:wahaha

在里面定义方法,调用私有方法

  1. class C:
  2. def __wahaha(self):
  3. print('wahaha')
  4. def ADCa(self):
  5. self.__wahaha()
  6. c = C()
  7. #c._C__wahaha()
  8. c.ADCa()

执行输出:

wahaha

总结:

在类中,静态属性,方法,对象属性都可以变成私有的,只需要在这些名字之前加上__

类中所有双下划线开头的名称如x 都会自动变形成:_类名x 的形式

在类中,可以使用 self.__x 的形式调用。外部无法调用,因为变形了。

面试题

下面的代码,执行输出什么?

  1. class D:
  2. def __func(self):
  3. print('in func')
  4. class E(D):
  5. def __init__(self):
  6. self.__func()
  7. e = E()

执行报错

AttributeError: ‘E’ object has no attribute ‘_E__func’

为什么呢?

代码分析:

  1. class D:
  2. def __func(self): # 变形为 _D__func
  3. print('in func')
  4. class E(D): # E继承了D
  5. def __init__(self): # 执行初始化方法
  6. self.__func() # 变形为 _E__func
  7. e = E()

执行到 self.func()时,变形为_Efunc

此时 E 里面找不到这个方法,执行报错

私有的名字不能被子类继承

查看当前范围内的变量、方法和定义的类型列表

  1. class D:
  2. def __func(self): # 变形为 _D__func
  3. print('in func')
  4. class E(D):
  5. def __init__(self):
  6. pass
  7. e = E()
  8. print(e.__dir__())

执行输出:

[‘ge‘, ‘gt‘, ‘new‘, ‘reduce_ex‘, ‘reduce‘, ‘dict‘, ‘le‘, ‘eq‘, ‘sizeof‘, ‘format‘, ‘getattribute‘, ‘Dfunc’, ‘hash‘, ‘str‘, ‘init‘, ‘module‘, ‘subclasshook‘, ‘weakref‘, ‘repr‘, ‘lt‘, ‘setattr‘, ‘ne‘, ‘class‘, ‘dir‘, ‘delattr‘, ‘doc_‘]

从输出结果中,可以看出。_Dfunc 就是 D 里面的func 方法。在内存中存储的值,已经变形了。

既然这样的话,那么就可以调用__func 方法了

  1. class D:
  2. def __func(self): # 变形为 _D__func
  3. print('in func')
  4. class E(D):
  5. def __init__(self):
  6. self._D__func()
  7. e = E()

执行输出:in func

但是不建议这么写,既然是私有属性,不能这么做。很容易被打的哈…

面试题

下面的代码执行输出 in D 还是 in E?

  1. class D:
  2. def __init__(self):
  3. self.__func()
  4. def __func(self):
  5. print('in D')
  6. class E(D):
  7. def __func(self):
  8. print('in E')
  9. e = E()

大部分人猜的结果应该是 in E

但是结果其实是 in D

what?

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597652205989-e76c8213-4cf1-472c-a288-ce6449bd4a9c.png)

执行到第 8 部的时候,它需要找的是_D__func

所以结果输出 in D

私有的名字,在类内使用的时候,就是会变形成该类名_方法名

以此为例 :没有双下换线会先找 E 中的 func

但是有了双下划线,会在调用这个名字的类 D 中直接找_D__func

  1. class F:pass
  2. F.__name = 'alex' # 是不是在创建私有属性?
  3. print(F.__name)

执行输出:alex

what? 它为啥不是私有属性?

在类的外部,它不会发生变形

只有在类的内部,才关心双下划线

变形只在类的内部发生

在类的外部,禁止访问私有属性

java 中的对比

public 公有的 在类的内部可以使用,子类可以使用,外部可以使用 python 中所有正常的名字

protect 保护的 在类的内部可以使用,子类可以使用,外部不可以使用 python 中没有

private 私有的 只能在类的内部使用,子类和外部都不可以使用 python 中的__名字

python 私有的用法

当一个方法不想被子类继承的时候

有些属性或者方法不希望从外部被调用,只想提供给内部的方法使用

描述一个房子

单价

面积

长宽高

  1. class Room:
  2. def __init__(self,name,price,length,width,height):
  3. self.name = name
  4. self.price = price
  5. self.__length = length # 隐藏长
  6. self.__width = width # 隐藏宽
  7. self.__height = height # 隐藏高
  8. def area(self):
  9. return self.__length*self.__width
  10. r = Room('鹏鹏',100,2,1,0.5)
  11. print(r.name)
  12. print(r.price)
  13. print(r.area())

执行输出:

鹏鹏

100

2

这个例子,将长宽高隐藏起来的,外部知道名字,价格,面积就可以了。

用户名和密码问题

将密码保护起来

  1. class Person:
  2. def __init__(self,name,pwd):
  3. self.name = name
  4. self.__pwd = pwd
  5. def __show_pwd(self):
  6. li = []
  7. for i in self.__pwd:
  8. i = ord(str(i)) # 查看ascii码对应的顺序
  9. li.append(str(i))
  10. my_secret_pwd = ''.join(li)
  11. return my_secret_pwd
  12. a = Person('xiao','42423')
  13. ret = a._Person__show_pwd()
  14. print(ret)

执行输出:

5250525051

二、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

查看我的 BMI

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. def cal_BMI(self):
  7. return self.__weight / self.__height **2
  8. a = Person('xiao',65,1.75)
  9. print(a.cal_BMI())

执行输出:

21.224489795918366

bmi 是一个名词,能不能 将 bmi 伪装成属性?

方法执行,需要带括号,而属性则不需要

@property 装饰器就是负责把一个方法变成属性调用的

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. @property
  7. def cal_bmi(self):
  8. return self.__weight / self.__height **2
  9. a = Person('xiao',65,1.75)
  10. print(a.cal_bmi)

执行输出:

21.224489795918366

下面的代码,也可以实现上面的效果

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. #self.bmi = self.__weight / self.__height **2
  7. #self.bmi = cal_BMI()

但是计算的逻辑,不能放到 init 里面

初始化,不要做计算

举例:

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. self.bmi = self.__weight / self.__height **2
  7. p = Person('xiao',65,1.75)
  8. print(p.bmi)
  9. p._Person__weight = 70 # 1周之后,增加体重
  10. print(p.bmi)

执行输出:

21.224489795918366

21.224489795918366

执行结果是一样的,体重增加了,但是 bmi 指数没有变动。

因为init初始化之后,就不会再变动了。

看之前的例子

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. @property
  7. def cal_bmi(self):
  8. return self.__weight / self.__height **2

看着,有点问题,cal_bmi 是动词。

但是 bmi 应该是一个属性

为了更美观,将方法名变成名词

  1. @property
  2. def bmi(self):
  3. return self.__weight / self.__height **2

如果在init里面,也指定同名的 bmi 属性呢?

  1. class Person(object):
  2. def __init__(self,name,weight,height,bmi):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. self.bmi = bmi
  7. def bmi(self):
  8. return self.__weight / self.__height **2
  9. a = Person('xiao',65,1.75)
  10. print(a.bmi())

执行报错:

TypeError: init() missing 1 required positional argument: ‘bmi’

init里面,属性名不能和方法名重复

那么 bmi 是否可以修改呢?

  1. class Person(object):
  2. def __init__(self,name,weight,height):
  3. self.name = name
  4. self.__weight = weight
  5. self.__height = height
  6. @property
  7. def bmi(self):
  8. return self.__weight / self.__height **2
  9. p = Person('xiao',65,1.75)
  10. p.bmi = 2

执行输出:

AttributeError: can’t set attribute

总结:

@property 能够将一个方法伪装成一个属性

从原来的的对象名.方法名(),变成了对象名.方法名

只是让代码变的更美观

被 property 装饰的 bmi 仍然是一个方法 存在 Person.dict

对象的.dict中不会存储这个属性

在一个类加载的过程中,会先加载这个类的名字,包括被 property 装饰的

在实例化对象的时候,python 解释器会先到类的空间里看看有没有这个被装饰的属性,

如果有就不能再在自己对象的空间中创建这个属性了

被 property 装饰的方法,不能修改,只能查看

圆形类

有半径,面积,周长

要求:将方法伪装成属性,方法中一般涉及的都是一些计算过程

  1. from math import pi
  2. class Circle: # 圆形
  3. def __init__(self, r):
  4. self.r = r
  5. @property
  6. def area(self): # 面积
  7. return pi * self.r ** 2
  8. @property
  9. def perimeter(self): # 周长
  10. return pi * self.r * 2
  11. c = Circle(10)
  12. print(c.area)
  13. print(c.perimeter)
  14. c.r =15 # 修改半径
  15. print(c.area)
  16. print(c.perimeter)

执行输出:

314.1592653589793

62.83185307179586

706.8583470577034

94.24777960769379

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self): # 将一个方法伪装成一个属性
  6. return self.__name
  7. p = Person('alex')
  8. print(p.name) #此时执行的是伪装的属性name

执行输出:alex

这样是获取一个属性 name

和下面的代码,效果是一样的

  1. class Person0:
  2. def __init__(self,name):
  3. self.name = name
  4. p = Person0('alex')
  5. print(p.name)<br>p.name = 'sb'<br>p.name = 123

但是它的 name 属性是可以改变的。

在 C++里面,喜欢把所有的属性,变成私有属性

那么上面这 2 个例子,和直接定义 name 属性有什么区别?

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. def set_name(self,new_name):
  8. if type(new_name) is str:
  9. self.__name = new_name
  10. else:
  11. print('您提供的姓名数据类型不合法')
  12. p = Person('alex')
  13. print(p.name)
  14. p.set_name('alex_sb')
  15. print(p.name)
  16. p.set_name(123)

执行输出:

alex

alex_sb

您提供的姓名数据类型不合法

@property 被装饰的方法,是不能传参数的,因为它伪装成属性了。

通过 if 判断,就可以保护属性的类型,必须是字符串

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.setter
  8. def name(self,new_name):
  9. print('---',new_name)
  10. p = Person('alex')
  11. p.name = 'alex_sb'

@property 可以将 python 定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候 setter/deleter 也是需要的。

1》只有@property 表示只读。

2》同时有@property 和@x.setter 表示可读可写。

3》同时有@property 和@x.setter 和@x.deleter 表示可读可写可删除。

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.setter
  8. def name(self,new_name):
  9. print('---',new_name)
  10. p = Person('alex')
  11. p.name = 'alex_sb' # 修改name属性

执行输出:

—- alex_sb

上面的代码,3 个 name 必须是相同的。三位一体

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597652206023-e97f5b33-5f4e-4b60-af36-ccd9f10838b8.png)

alex_sb 对应方法里面的 new_name

@name.settet

有且并只有一个参数

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.setter
  8. def name(self,new_name):
  9. print('---',new_name)
  10. p = Person('alex')
  11. print(p.name)
  12. p.name = 'alex_sb' # 修改name属性
  13. print(p.name)

执行输出:

alex

—- alex_sb

alex

从结果上来看,并没有改变 alex 的值

那么如何改变呢?看下面的代码

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.setter
  8. def name(self,new_name):
  9. self.__name = new_name # 更改__name属性
  10. p = Person('alex')
  11. print(p.name)
  12. p.name = 'alex_sb' # 修改name属性
  13. print(p.name)

执行输出:

alex

alex_sb

但是这样,不能保证修改的数据类型是固定的

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.setter
  8. def name(self,new_name):
  9. if type(new_name) is str:
  10. self.__name = new_name
  11. else:
  12. print('您提供的姓名数据类型不合法')
  13. p = Person('alex')
  14. print(p.name)
  15. p.name = 'alex_sb' # 修改name属性
  16. print(p.name)
  17. p.name = 123 # 不合法
  18. print(p.name)

执行输出:

alex

alex_sb

您提供的姓名数据类型不合法

alex_sb

非法类型,不允许修改

这样就可以保护属性的类型

方法伪装成的属性删除操作

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. p = Person('alex')
  8. print(p.name)
  9. del p.name

执行报错:

AttributeError: can’t delete attribute

为什么不能删除

因为 name 被@property 伪装了,此时 name 是只读的。

那么如何删除呢?看下面的代码

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.deleter
  8. def name(self):
  9. print('name 被删除了')
  10. p = Person('alex')
  11. print(p.name)
  12. del p.name
  13. print(p.name)

执行输出:

alex

name 被删除了

alex

它并没有真正删除,只是执行了被@name.deleter 装饰的函数

如何真正删除呢?

  1. class Person:
  2. def __init__(self,name):
  3. self.__name = name # 私有的属性
  4. @property
  5. def name(self):
  6. return self.__name
  7. @name.deleter
  8. def name(self):
  9. del self.__name
  10. p = Person('alex')
  11. print(p.name)
  12. del p.name
  13. print(p.__dict__) # 查看属性

执行输出:

alex

{}

p 对象返回的是空字典,说明删除成功了!

标注一下 3 个装饰器的重要程度

✴✴✴** @name.setter**

✴✴✴✴** @property**

✴ @name.deleter

总结:

@property —> func 将方法伪装成属性,只观看的事儿

@func.setter —> func 对伪装的属性进行赋值的时候调用这个方法 一般情况下用来做修改

@func.deleter —> func 在执行 del 对象.func 的时候调用这个方法 一般情况下用来做删除 基本不用

再讲一个列子:

商品的 折扣

有一个商品 : 原价 折扣

当我要查看价格的时候 我想看折后价

  1. class Goods:
  2. def __init__(self,name,origin_price,discount):
  3. self.name = name
  4. self.__price = origin_price # 原价
  5. self.__discount = discount # 折扣价
  6. @property
  7. def price(self): # 价格
  8. return self.__price * self.__discount
  9. apple = Goods('apple',5,0.8)
  10. print(apple.price)

执行输出:

4.0

修改苹果的原价

  1. class Goods:
  2. def __init__(self,name,origin_price,discount):
  3. self.name = name
  4. self.__price = origin_price # 原价
  5. self.__discount = discount # 折扣价
  6. @property
  7. def price(self): # 价格
  8. return self.__price * self.__discount
  9. @price.setter
  10. def price(self,new_price):
  11. if type(new_price) is int or type(new_price) is float:
  12. self.__price = new_price
  13. apple = Goods('apple',5,0.8)
  14. print(apple.price)
  15. # 修改苹果的原价
  16. apple.price = 8
  17. print(apple.price)

执行输出:

4.0

6.4

property 的作用

将一些需要随着一部分属性的变化而变化的值的计算过程 从方法 伪装成属性

将私有的属性保护起来,让修改的部分增加一些约束,来提高程序的稳定性和数据的安全性

三、classmethod

还是上面的例子,店庆 全场八折,代码怎么改?

  1. class Goods:
  2. __discount = 0.8 # 折扣
  3. def __init__(self,name,origin_price):
  4. self.name = name
  5. self.__price = origin_price # 原价
  6. @property
  7. def price(self): # 价格
  8. return self.__price * Goods.__discount
  9. apple = Goods('apple',5)
  10. banana = Goods('banana',8)
  11. print(apple.price)
  12. print(banana.price)

执行输出:

4.0

6.4

现在折扣变了,店庆结束,恢复原价

如何修改__discount 变量呢?

不能这么写

  1. Goods._Goods__discount = 1

怎么办呢?定义一个方法,修改属性

  1. class Goods:
  2. __discount = 0.8 # 折扣
  3. def __init__(self,name,origin_price):
  4. self.name = name
  5. self.__price = origin_price # 原价
  6. @property
  7. def price(self): # 价格
  8. return self.__price * Goods.__discount
  9. def change_discount(self,new_discount): # 修改折扣
  10. Goods.__discount = new_discount
  11. apple = Goods('apple',5)
  12. banana = Goods('banana',8)
  13. apple.change_discount(1) #修改折扣为1
  14. print(apple.price)
  15. print(banana.price)

执行输出:

5

8

但是修改类静态变量,不需要实例化才对啊

如果要改变折扣 是全场的事情 不牵扯到一个具体的物品 所以不应该使用对象来调用这个方法

类函数(@classmethod):即类方法, 更关注于从类中调用方法, 而不是在实例中调用方法

不依赖对象的方法 就应该定义成类方法 类方法可以任意的操作类中的静态变量

  1. class Goods:
  2. __discount = 0.8 # 折扣
  3. def __init__(self,name,origin_price):
  4. self.name = name
  5. self.__price = origin_price # 原价
  6. @property
  7. def price(self): # 价格
  8. return self.__price * Goods.__discount
  9. @classmethod
  10. def change_discount(self,new_discount): # 类方法 可以直接被类调用 不需要默认传对象参数 只需要传一个类参数就可以了
  11. Goods.__discount = new_discount
  12. Goods.change_discount(1) # 不依赖对象的方法 就应该定义成类方法 类方法可以任意的操作类中的静态变量
  13. apple = Goods('apple',5)
  14. banana = Goods('banana',8)
  15. print(apple.price)
  16. print(banana.price)

执行输出:

5

8

看下面一段代码

  1. def login():pass
  2. #username
  3. #password
  4. #身份 -- 实例化
  5. class Student:
  6. def __init__(self,name):pass
  7. def login(self):pass

一半是面向过程,一半是面向对象

对于完全面向对象编程而言,不允许出现面向过程的代码

完全面向对象编程

先登录 后 实例化

还没有一个具体的对象的时候 就要执行 login 方法,这样是不合理的。需要将 login()变成静态方法。

python 为我们内置了函数 staticmethod 来把类中的函数定义成静态方法,它不需要实例化

  1. class Student:
  2. def __init__(self,name):pass
  3. @staticmethod
  4. def login(a): # login就是一个类中的静态方法 静态方法没有默认参数 就当成普通的函数使用即可
  5. user = input('user :')
  6. if user == 'alex':
  7. print('success')
  8. else:
  9. print('faild')
  10. Student.login(1)

总结:

staticmethod

当一个方法要使用对象的属性时 就是用普通的方法

当一个方法要使用类中的静态属性时 就是用类方法(classmethod)

当一个方法要既不使用对象的属性也不使用类中的静态属性时,就可以使用 staticmethod 静态方法