zoukankan      html  css  js  c++  java
  • python 全栈开发,Day22(封装,property,classmethod,staticmethod)

    一、封装

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

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

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

    【好处】

      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?

    执行到第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必须是相同的。三位一体

    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静态方法

  • 相关阅读:
    CCF 201712-4
    图论_最短路径
    图论_查并集
    let和const
    Promise
    实现表单label两端对齐
    始终让footer在底部
    react——使用this.setState({ })修改state状态值
    react——css样式
    react脚手架
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/8867014.html
Copyright © 2011-2022 走看看