一、封装

封装 :
  广义上的 :把一堆东西装在一个容器里
  狭义上的 :会对一种现象起一个专门属于它的名字

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

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

【好处】

  1. 将变化隔离;

  2. 便于使用;

  3. 提高复用性;

  4. 提高安全性;

【封装原则】

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

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

私有变量和私有方法

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

class A:
    __N = 'aaa'
print(A.__N)

执行报错:

AttributeError: type object 'A' has no attribute '__N'

这个是__N就是私有属性

python
  pulic 公有的
  private 私有的

java完全面向对象的语言
  public 公有的
  protect 保护的
  private 私有的

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

定义一个私有的名字 : 就是在私有的名气前面加两条下划线 __N = 'aaa'
所谓私有,就是不能在类的外面去引用它

私有类的静态变量

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

执行输出: aaa

那么__N真的私有的吗?

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

print(A.__dict__)

执行输出: 

{'func': <function A.func at 0x0000026F8EB9AA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '_A__N': 'aaa', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, '__module__': '__main__'}

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

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

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

#print(A.__dict__)  # python就是把__名字当成私有的语法
print(A._A__N)

执行输出: aaa

为什么敲A._A__N 没有提示呢?
这是Pycharm做的功能,防止你调用私有变量
虽然这样可以调用,但是在以后的工作中,禁止调用,这样是不规范的。

一个私有的名字 在存储的过程中仍然会出现在A.__dict__中,所以我们仍然可以调用到。
python对其的名字进行了修改: _类名__名字
只不过在类的外部调用 :需要"_类名__名字"去使用
在类的内部可以正常的使用名字

那么为什么类外部是_A__N,内部是__N
那么内存中,存的还是_A__N。请看一下__dict__就知道了

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

私有属性

class B:
    def __init__(self,name):
        self.__name = name

b = B('alex')
print(b.__name)

执行输出:

AttributeError: 'B' object has no attribute '__name'

私有属性变形了

class B:
    def __init__(self,name):
        self.__name = name

b = B('alex')
print(b._B__name)

执行输出: alex

私有方法

class C:
    def __wahaha(self):
        print('wahaha')

c = C()
c.__wahaha()

执行报错:

AttributeError: 'C' object has no attribute '__wahaha'

方法也变形了

class C:
    def __wahaha(self):
        print('wahaha')

c = C()
c._C__wahaha()

执行输出: wahaha

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

class C:
    def __wahaha(self):
        print('wahaha')
    def ADCa(self):
        self.__wahaha()

c = C()
#c._C__wahaha()
c.ADCa()

执行输出:

wahaha

总结:

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

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

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

面试题

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

class D:
    def __func(self):
        print('in func')
class E(D):
    def __init__(self):
        self.__func()

e = E()

执行报错

AttributeError: 'E' object has no attribute '_E__func' 

为什么呢?

代码分析:

class D:
    def __func(self):  # 变形为 _D__func
        print('in func')
class E(D):  # E继承了D
    def __init__(self):  # 执行初始化方法
        self.__func()  # 变形为 _E__func

e = E()

执行到self.__func()时,变形为_E__func

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

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

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

class D:
    def __func(self):  # 变形为 _D__func
        print('in func')
class E(D):
    def __init__(self):
        pass

e = E()
print(e.__dir__())

执行输出:

['__ge__', '__gt__', '__new__', '__reduce_ex__', '__reduce__', '__dict__', '__le__', '__eq__', '__sizeof__', '__format__', '__getattribute__', '_D__func', '__hash__', '__str__', '__init__', '__module__', '__subclasshook__', '__weakref__', '__repr__', '__lt__', '__setattr__', '__ne__', '__class__', '__dir__', '__delattr__', '__doc__']

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

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

class D:
    def __func(self):  # 变形为 _D__func
        print('in func')
class E(D):
    def __init__(self):
        self._D__func()

e = E()

执行输出: in func

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

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

class D:
    def __init__(self):
        self.__func()
    def __func(self):
        print('in D')

class E(D):
    def __func(self):
        print('in E')

e = E()

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

但是结果其实是in D

what?

python 全栈开发,Day22(封装,property,classmethod,staticmethod)-LMLPHP

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

所以结果输出in D

私有的名字,在类内使用的时候,就是会变形成_该类名__方法名
以此为例 :没有双下换线会先找E中的func
但是有了双下划线,会在调用这个名字的类D中直接找_D__func

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

执行输出:alex

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

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

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

变形只在类的内部发生

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

java中的对比
  public 公有的 在类的内部可以使用,子类可以使用,外部可以使用 python中所有正常的名字
  protect 保护的 在类的内部可以使用,子类可以使用,外部不可以使用 python中没有
  private 私有的 只能在类的内部使用,子类和外部都不可以使用 python中的__名字

python私有的用法
  当一个方法不想被子类继承的时候
  有些属性或者方法不希望从外部被调用,只想提供给内部的方法使用

描述一个房子
  单价
  面积
  长宽高

class Room:
    def __init__(self,name,price,length,width,height):
        self.name = name
        self.price = price
        self.__length = length  # 隐藏长
        self.__width = width  # 隐藏宽
        self.__height = height  # 隐藏高

    def area(self):
        return self.__length*self.__width

r = Room('鹏鹏',100,2,1,0.5)
print(r.name)
print(r.price)
print(r.area())

执行输出:

鹏鹏
100
2

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

用户名和密码问题

将密码保护起来

class Person:
    def __init__(self,name,pwd):
        self.name = name
        self.__pwd = pwd
    def __show_pwd(self):
        li = []
        for i in self.__pwd:
            i = ord(str(i))  # 查看ascii码对应的顺序
            li.append(str(i))
        my_secret_pwd = ''.join(li)
        return my_secret_pwd
a = Person('xiao','42423')
ret = a._Person__show_pwd()
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

class Person(object):
    def __init__(self,name,weight,height):
        self.name = name
        self.__weight = weight
        self.__height = height
    def cal_BMI(self):
        return self.__weight / self.__height **2

a = Person('xiao',65,1.75)
print(a.cal_BMI())

执行输出:

21.224489795918366

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

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

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

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

    @property
    def cal_bmi(self):
        return self.__weight / self.__height **2

a = Person('xiao',65,1.75)
print(a.cal_bmi)

执行输出:

21.224489795918366

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

class Person(object):
    def __init__(self,name,weight,height):
        self.name = name
        self.__weight = weight
        self.__height = height
        #self.bmi = self.__weight / self.__height **2
        #self.bmi = cal_BMI() 

但是计算的逻辑,不能放到init里面
初始化,不要做计算

举例:

class Person(object):
    def __init__(self,name,weight,height):
        self.name = name
        self.__weight = weight
        self.__height = height
        self.bmi = self.__weight / self.__height **2

p = Person('xiao',65,1.75)
print(p.bmi)
p._Person__weight = 70  # 1周之后,增加体重
print(p.bmi)

执行输出:

21.224489795918366
21.224489795918366

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

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

看之前的例子

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

    @property
    def cal_bmi(self):
        return self.__weight / self.__height **2

看着,有点问题,cal_bmi是动词。
但是bmi应该是一个属性
为了更美观,将方法名变成名词

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

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

class Person(object):
    def __init__(self,name,weight,height,bmi):
        self.name = name
        self.__weight = weight
        self.__height = height
        self.bmi = bmi

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

a = Person('xiao',65,1.75)
print(a.bmi())

执行报错:

TypeError: __init__() missing 1 required positional argument: 'bmi'

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

那么bmi是否可以修改呢?

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

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

p = Person('xiao',65,1.75)
p.bmi = 2

执行输出:

AttributeError: can't set attribute

总结:

@property 能够将一个方法伪装成一个属性
从原来的的对象名.方法名(),变成了对象名.方法名
只是让代码变的更美观

被property装饰的bmi仍然是一个方法 存在Person.__dict__
对象的.__dict__中不会存储这个属性

在一个类加载的过程中,会先加载这个类的名字,包括被property装饰的
在实例化对象的时候,python解释器会先到类的空间里看看有没有这个被装饰的属性,
如果有就不能再在自己对象的空间中创建这个属性了

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

圆形类

有半径,面积,周长

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

from math import pi
class Circle:  # 圆形
    def __init__(self, r):
        self.r = r
    @property
    def area(self):  # 面积
        return pi * self.r ** 2
    @property
    def perimeter(self):  # 周长
        return pi * self.r * 2

c = Circle(10)
print(c.area)
print(c.perimeter)
c.r =15  # 修改半径
print(c.area)
print(c.perimeter)

执行输出:

314.1592653589793
62.83185307179586
706.8583470577034
94.24777960769379

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

执行输出: alex

这样是获取一个属性name
和下面的代码,效果是一样的

class Person0:
    def __init__(self,name):
        self.name = name
p = Person0('alex')
print(p.name)
p.name = 'sb'
p.name = 123

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

在C++里面,喜欢把所有的属性,变成私有属性
那么上面这2个例子,和直接定义name属性有什么区别?

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name
    def set_name(self,new_name):
        if type(new_name) is str:
            self.__name = new_name
        else:
            print('您提供的姓名数据类型不合法')

p = Person('alex')
print(p.name)
p.set_name('alex_sb')
print(p.name)
p.set_name(123)

执行输出:

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

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

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

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,new_name):
        print('---',new_name)

p = Person('alex')
p.name = 'alex_sb'

  

@property可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式,但是有时候setter/deleter也是需要的。
1》只有@property表示只读。
2》同时有@property和@x.setter表示可读可写。
3》同时有@property和@x.setter和@x.deleter表示可读可写可删除。

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,new_name):
        print('---',new_name)

p = Person('alex')
p.name = 'alex_sb'  # 修改name属性

执行输出:

--- alex_sb

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

python 全栈开发,Day22(封装,property,classmethod,staticmethod)-LMLPHP

alex_sb对应方法里面的new_name

@name.settet
有且并只有一个参数

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,new_name):
        print('---',new_name)

p = Person('alex')
print(p.name)
p.name = 'alex_sb'  # 修改name属性
print(p.name)

执行输出:

alex
--- alex_sb
alex

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

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

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,new_name):
        self.__name = new_name  # 更改__name属性

p = Person('alex')
print(p.name)
p.name = 'alex_sb'  # 修改name属性
print(p.name)

执行输出:

alex
alex_sb

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

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,new_name):
        if type(new_name) is str:
            self.__name = new_name
        else:
            print('您提供的姓名数据类型不合法')

p = Person('alex')
print(p.name)
p.name = 'alex_sb'  # 修改name属性
print(p.name)
p.name = 123  # 不合法
print(p.name)

执行输出:

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

非法类型,不允许修改
这样就可以保护属性的类型

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

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name

p = Person('alex')
print(p.name)
del p.name

执行报错:

AttributeError: can't delete attribute

为什么不能删除
因为name被@property伪装了,此时name是只读的。

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

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name
    @name.deleter
    def name(self):
        print('name 被删除了')

p = Person('alex')
print(p.name)
del p.name
print(p.name)

执行输出:

alex
name 被删除了
alex

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

如何真正删除呢?

class Person:
    def __init__(self,name):
        self.__name = name  # 私有的属性
    @property
    def name(self):
        return self.__name
    @name.deleter
    def name(self):
        del self.__name

p = Person('alex')
print(p.name)
del p.name
print(p.__dict__)  # 查看属性

执行输出:

alex
{}

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

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

✴✴✴ @name.setter

✴✴✴✴ @property

✴ @name.deleter

总结:

@property --> func 将方法伪装成属性,只观看的事儿
@func.setter --> func 对伪装的属性进行赋值的时候调用这个方法 一般情况下用来做修改
@func.deleter --> func 在执行del 对象.func的时候调用这个方法 一般情况下用来做删除 基本不用

再讲一个列子:

商品的 折扣
有一个商品 : 原价 折扣
当我要查看价格的时候 我想看折后价

class Goods:
    def __init__(self,name,origin_price,discount):
        self.name = name
        self.__price = origin_price  # 原价
        self.__discount = discount  # 折扣价

    @property
    def price(self):  # 价格
        return self.__price * self.__discount

apple = Goods('apple',5,0.8)
print(apple.price)

执行输出:

4.0

修改苹果的原价

class Goods:
    def __init__(self,name,origin_price,discount):
        self.name = name
        self.__price = origin_price  # 原价
        self.__discount = discount  # 折扣价

    @property
    def price(self):  # 价格
        return self.__price * self.__discount

    @price.setter
    def price(self,new_price):
        if type(new_price) is int or type(new_price) is float:
            self.__price = new_price

apple = Goods('apple',5,0.8)
print(apple.price)
# 修改苹果的原价
apple.price = 8
print(apple.price)

执行输出:

4.0
6.4

property的作用

将一些需要随着一部分属性的变化而变化的值的计算过程 从方法 伪装成属性
将私有的属性保护起来,让修改的部分增加一些约束,来提高程序的稳定性和数据的安全性

三、classmethod

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

class Goods:
    __discount = 0.8  # 折扣
    def __init__(self,name,origin_price):
        self.name = name
        self.__price = origin_price  # 原价

    @property
    def price(self):  # 价格
        return self.__price * Goods.__discount

apple = Goods('apple',5)
banana = Goods('banana',8)
print(apple.price)
print(banana.price)

执行输出:

4.0
6.4

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

如何修改__discount变量呢?
不能这么写

Goods._Goods__discount = 1

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

class Goods:
    __discount = 0.8  # 折扣
    def __init__(self,name,origin_price):
        self.name = name
        self.__price = origin_price  # 原价

    @property
    def price(self):  # 价格
        return self.__price * Goods.__discount

    def change_discount(self,new_discount):  # 修改折扣
        Goods.__discount = new_discount

apple = Goods('apple',5)
banana = Goods('banana',8)
apple.change_discount(1)  #修改折扣为1
print(apple.price)
print(banana.price)

执行输出:

5
8

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

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

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

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

class Goods:
    __discount = 0.8  # 折扣
    def __init__(self,name,origin_price):
        self.name = name
        self.__price = origin_price  # 原价

    @property
    def price(self):  # 价格
        return self.__price * Goods.__discount

    @classmethod
    def change_discount(self,new_discount):  # 类方法 可以直接被类调用 不需要默认传对象参数 只需要传一个类参数就可以了
        Goods.__discount = new_discount

Goods.change_discount(1)  # 不依赖对象的方法 就应该定义成类方法 类方法可以任意的操作类中的静态变量
apple = Goods('apple',5)
banana = Goods('banana',8)
print(apple.price)
print(banana.price)

执行输出:

5
8

看下面一段代码

def login():pass
    #username
    #password
    #身份 -- 实例化

class Student:
    def __init__(self,name):pass
    def login(self):pass

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

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

完全面向对象编程
先登录 后 实例化
还没有一个具体的对象的时候 就要执行login方法,这样是不合理的。需要将login()变成静态方法。

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

class Student:
    def __init__(self,name):pass

    @staticmethod
    def login(a):  # login就是一个类中的静态方法 静态方法没有默认参数 就当成普通的函数使用即可
        user = input('user :')
        if user == 'alex':
            print('success')
        else:
            print('faild')

Student.login(1)

总结:

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

04-05 05:03