python 全栈开发,Day22(封装,property,classmethod,staticmethod)
一、封装
封装 :
广义上的 :把一堆东西装在一个容器里
狭义上的 :会对一种现象起一个专门属于它的名字
函数和属性装到了一个非全局的命名空间 —— 封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
1. 将变化隔离;
2. 便于使用;
3. 提高复用性;
4. 提高安全性;
【封装原则】
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
1 2 3 | 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'
所谓私有,就是不能在类的外面去引用它
私有类的静态变量
1 2 3 4 5 6 7 | class A: __N = 'aaa' # 静态变量 def func( self ): print (A.__N) # 在类的内部使用正常 a = A() a.func() #print(A.__N) # 在类的外部直接用 报错 |
执行输出: aaa
那么__N真的私有的吗?
1 2 3 4 5 6 | 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
在外部,依然可以调用,只不过,存储的的时候,做了变形
1 2 3 4 5 6 7 | 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解释器自动的转换成_类名__名字
私有属性
1 2 3 4 5 6 | class B: def __init__( self ,name): self .__name = name b = B( 'alex' ) print (b.__name) |
执行输出:
AttributeError: 'B' object has no attribute '__name'
私有属性变形了
1 2 3 4 5 6 | class B: def __init__( self ,name): self .__name = name b = B( 'alex' ) print (b._B__name) |
执行输出: alex
私有方法
1 2 3 4 5 6 | class C: def __wahaha( self ): print ( 'wahaha' ) c = C() c.__wahaha() |
执行报错:
AttributeError: 'C' object has no attribute '__wahaha'
方法也变形了
1 2 3 4 5 6 | class C: def __wahaha( self ): print ( 'wahaha' ) c = C() c._C__wahaha() |
执行输出: wahaha
在里面定义方法,调用私有方法
1 2 3 4 5 6 7 8 9 | class C: def __wahaha( self ): print ( 'wahaha' ) def ADCa( self ): self .__wahaha() c = C() #c._C__wahaha() c.ADCa() |
执行输出:
wahaha
总结:
在类中,静态属性,方法,对象属性都可以变成私有的,只需要在这些名字之前加上__
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式
在类中,可以使用self.__x的形式调用。外部无法调用,因为变形了。
面试题
下面的代码,执行输出什么?
1 2 3 4 5 6 7 8 | 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'
为什么呢?
代码分析:
1 2 3 4 5 6 7 8 | 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里面找不到这个方法,执行报错
私有的名字不能被子类继承
查看当前范围内的变量、方法和定义的类型列表
1 2 3 4 5 6 7 8 9 | 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方法了
1 2 3 4 5 6 7 8 | 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 ?
1 2 3 4 5 6 7 8 9 10 11 | 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
1 2 3 | class F: pass F.__name = 'alex' # 是不是在创建私有属性? print (F.__name) |
执行输出:alex
what? 它为啥不是私有属性?
在类的外部,它不会发生变形
只有在类的内部,才关心双下划线
变形只在类的内部发生
在类的外部,禁止访问私有属性
java中的对比
public 公有的 在类的内部可以使用,子类可以使用,外部可以使用 python中所有正常的名字
protect 保护的 在类的内部可以使用,子类可以使用,外部不可以使用 python中没有
private 私有的 只能在类的内部使用,子类和外部都不可以使用 python中的__名字
python私有的用法
当一个方法不想被子类继承的时候
有些属性或者方法不希望从外部被调用,只想提供给内部的方法使用
描述一个房子
单价
面积
长宽高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
这个例子,将长宽高隐藏起来的,外部知道名字,价格,面积就可以了。
用户名和密码问题
将密码保护起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
1 2 3 4 5 6 7 8 9 10 | 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装饰器就是负责把一个方法变成属性调用的
1 2 3 4 5 6 7 8 9 10 11 12 | 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
下面的代码,也可以实现上面的效果
1 2 3 4 5 6 7 | 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里面
初始化,不要做计算
举例:
1 2 3 4 5 6 7 8 9 10 11 | 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__初始化之后,就不会再变动了。
看之前的例子
1 2 3 4 5 6 7 8 9 | 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应该是一个属性
为了更美观,将方法名变成名词
1 2 3 | @property def bmi( self ): return self .__weight / self .__height * * 2 |
如果在__init__里面,也指定同名的bmi属性呢?
1 2 3 4 5 6 7 8 9 10 11 12 | 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是否可以修改呢?
1 2 3 4 5 6 7 8 9 10 11 12 | 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装饰的方法,不能修改,只能查看
圆形类
有半径,面积,周长
要求:将方法伪装成属性,方法中一般涉及的都是一些计算过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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
1 2 3 4 5 6 7 8 | 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
和下面的代码,效果是一样的
1 2 3 4 5 | class Person0: def __init__( self ,name): self .name = name p = Person0( 'alex' ) print (p.name)<br>p.name = 'sb' <br>p.name = 123 |
但是它的name属性是可以改变的。
在C++里面,喜欢把所有的属性,变成私有属性
那么上面这2个例子,和直接定义name属性有什么区别?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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判断,就可以保护属性的类型,必须是字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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表示可读可写可删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
有且并只有一个参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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的值
那么如何改变呢?看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
但是这样,不能保证修改的数据类型是固定的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
非法类型,不允许修改
这样就可以保护属性的类型
方法伪装成的属性删除操作
1 2 3 4 5 6 7 8 9 10 | 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是只读的。
那么如何删除呢?看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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装饰的函数
如何真正删除呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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的时候调用这个方法 一般情况下用来做删除 基本不用
再讲一个列子:
商品的 折扣
有一个商品 : 原价 折扣
当我要查看价格的时候 我想看折后价
1 2 3 4 5 6 7 8 9 10 11 12 | 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
修改苹果的原价
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
还是上面的例子,店庆 全场八折,代码怎么改?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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变量呢?
不能这么写
1 | Goods._Goods__discount = 1 |
怎么办呢?定义一个方法,修改属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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):即类方法, 更关注于从类中调用方法, 而不是在实例中调用方法
不依赖对象的方法 就应该定义成类方法 类方法可以任意的操作类中的静态变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
看下面一段代码
1 2 3 4 5 6 7 8 | def login(): pass #username #password #身份 -- 实例化 class Student: def __init__( self ,name): pass def login( self ): pass |
一半是面向过程,一半是面向对象
对于完全面向对象编程而言,不允许出现面向过程的代码
完全面向对象编程
先登录 后 实例化
还没有一个具体的对象的时候 就要执行login方法,这样是不合理的。需要将login()变成静态方法。
python为我们内置了函数staticmethod来把类中的函数定义成静态方法,它不需要实例化
1 2 3 4 5 6 7 8 9 10 11 12 | 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静态方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix