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

  • 相关阅读:
    C# 不用添加WebService引用,调用WebService方法
    贪心 & 动态规划
    trie树 讲解 (转载)
    poj 2151 Check the difficulty of problems (检查问题的难度)
    poj 2513 Colored Sticks 彩色棒
    poj1442 Black Box 栈和优先队列
    啦啦啦
    poj 1265 Area(pick定理)
    poj 2418 Hardwood Species (trie树)
    poj 1836 Alignment 排队
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/8867014.html
Copyright © 2020-2023  润新知