[Python学习日记-67] 封装

[Python学习日记-67] 封装

简介

如何隐藏类中的属性

封装并不是单纯意义的隐藏

封装与扩展性

特性(property)

简介

        从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫、小狗、小王八和小猪一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装起来的麻袋相当于就是把里面的内容隐藏了起来,这样是不是就可以把封装与“隐藏”划上等号呢?其实这种理解是相当片面的。

如何隐藏类中的属性

        在 Python 中用双下划线开头的方式将属性隐藏起来(设置成私有的属性),这里需要与前后都加双下划线的属性作区分,前后都加双下划线的属性是 Python 的内置属性,并不是隐藏属性,下面的代码就是类中把属性隐藏起来的做法

class A:__x = 1def __init__(self,name):self.__name = namedef __foo(self):print('run foo')

        在上面的代码中,在属性前面加上__就是隐藏属性了,那它是怎么把这个属性隐藏起来的呢?我们不妨先试一下用常规方法调用一下看看是什么效果吧,如下

# 尝试一下用常规的调用方法是否可以调用
print(A.__x)
print(A.__foo)# 使用实例化后的对象是否可以呢?
a = A('jove')
print(a.__name)

代码输出如下:

        从输出可以看出,我们使用常规的方法来调用类当中的隐藏属性都是失败报错的,说是找不到对应的属性,这到底是怎么一回事呢?我们回想一下前面学习类和对象的知识,我们知道调用这些属性其实都是调用类和对象的命名空间中的数据而已,那我们直接来看看在命名空间中到底发生了什么,如下

print(A.__dict__)
print(a.__dict__)

代码输出如下:

{'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x000001EB109A8AE0>, '_A__foo': <function A.__foo at 0x000001EB109A99E0>, 'bar': <function A.bar at 0x000001EB109A9EE0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}


{'_A__name': 'jove'}

        可以看出命名空间中原本应该是 __x、__foo、__name 的属性全都加上了 _A,这个 _A 的组合到底是什么意思呢?其实这个是 _+ 该属性所属类的类名。这个时候我们是不是只要在原来调用属性的基础上加上 _类名 就可以正常调用了呢?如下

print(A._A__x)
print(A._A__foo)a = A('jove')
print(a._A__name)

代码输出如下:

        到这里可以得知在 Python 当中,隐藏属性其实仅仅这是一种变形操作而已,类中所有双下划线开头的名称如 __x 都会自动变形成:_类名__x 的形式。而我们可以通过这种形式来访问到隐藏属性,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形。

        而在类当中调用自身的函数的时候也需要这样操作吗?好像并没有,如下

class A:__x = 1def __init__(self,name):self.__name = namedef __foo(self):print('run foo')def bar(self):self.__foo()print('from bar')a = A('jove')
a.bar()

代码输出如下:

        从输出可以看出,bar 方法在调用 __foo 的时候并没有加上 _A 就可以成功调用,这是为什么呢?这是因为 Python 在执行程序的时候 A 类中的代码除了函数代码不会执行外,其它代码都会在定义阶段执行,而且 Python 解释器也会过一遍进行词法分析,所以在类的定义阶段执行代码的时候遇到 __ 开头的属性时解释器就会把它进行变形,如下

class A:__x = 1    # 类的数据属性 _A__x = 1def __init__(self,name):self.__name = name    # self._A__name = namedef __foo(self):    # 类的函数属性 def _A__foo(self):print('run foo')def bar(self):self.__foo()    # self._A__foo()print('from bar')

        从上面代码的注释当中可以看出为什么在类当中调用隐藏属性时并不需要加上 _类名,这是因为在类的定义阶段时就会一起进行变形 。那隐藏属性存不存在子类覆盖父类属性的情况呢?我们先看一段代码,如下

class Foo:def __func(self):  # def _Foo__func(self):print('from foo')class Bar(Foo):def __func(self):  # def _Bar__func(self):print('from bar')b = Bar()
b._Bar__func()
b._Foo__func()

代码输出如下:

        输出的结果来看,在隐藏属性的情况下,由于在类定义中的词法分析阶段已经变形了,所以实际上两个函数属性并没有重名。

一、这种自动变形的特点

  1. 类中定义的 _x 只能在内部使用,例如 self.__x,引用的就是变形的结果
  2. 这种变形其实正是针对外部的变形,在外部是无法通过 __x 这个名字访问到该属性
  3. 在子类定义的 __x 不会覆盖在父类定义的 __x,因为子类中变形成了 _子类名__x,而父类中变形成了 _父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的

二、这种变形需要注意的问题

1、 这种机制并没有真正意义上限制我们从外部直接访问属性,只要知道了类名和属性名就可以拼出名字:_类名__属性名,然后就可以访问了,例如 a._A__x

2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,并不会变形,如下

class B:__x = 1def __init__(self, name):self.__name = nameB.__y = 2
print(B.__dict__)
b = B('jove')
print(b.__dict__)
b.__age = 18
print(b.__dict__)

代码输出如下:

{'__module__': '__main__', '_B__x': 1, '__init__': <function B.__init__ at 0x000001C57BC48CC0>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__y': 2}

{'_B__name': 'jove'}
{'_B__name': 'jove', '__age': 18}

3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

        之前学习的集成当中,如果父类和子类当中都有同名的方法,将会先去对象命名空间找,然后再去子类当中找,最后才去父类中找,如下

# 正常情况
class A:def foo(self):print('A.foo')def bar(self):print('A.bar')self.foo()  # b.foo()class B(A):def foo(self):print('B.foo')b = B()
b.bar()

代码输出如下:

        但是总有想要直接使用父类的情况出现,这个时候我们就需要将父类中的方法定义为私有的了,如下

# 把 foo 定义成私有的,即 __foo
class A:  # 实现了只在自己类里面找对应的函数def __foo(self):    # 在定义时就变形为 _A__fooprint('A.foo')def bar(self):print('A.bar')self.__foo()    # 只会与自己所在的类为准,即调用 self._A__fooclass B(A):def __foo(self):    # _B__fooprint('B.foo')b = B()
b.bar()

代码输出如下:

封装并不是单纯意义的隐藏

一、封装数据

        其实将属性隐藏起来这并不是真正的目的。真正的目的是,将属性隐藏起来后,对外提供操作该属性的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

# 一: 封装数据属性: 明确的区分内外,控制外部对隐藏的属性的操作行为
class People:def __init__(self,name,age):self.__name = nameself.__age = agedef tell_info(self):print('Name:<%s> Age:<%s>' % (self.__name,self.__age))def set_info(self,name,age):if not isinstance(name,str):  # 通过封装可以在自己的函数接口当中进行逻辑判断等操作print('名字必须是字符串类型')elif not isinstance(age,int):print('年龄必须是数字类型')else:self.__name = nameself.__age = agep = People('jove',18)
p.tell_info()  # 获取Name Age
p.set_info('JOVE',38)  # 通过定义的接口来修改Name Age
p.tell_info()
p.set_info(123,38)
p.set_info('JOVE','38')

代码输出如下:

二、封装方法

        封装方法的目的是为了隔离复杂度,我们举个银行取款的例子来看一下,如下

# 二: 封装方法的目的 --> 隔离复杂度
class ATM:def __card(self):print('插卡')def __auth(self):print('用户认证')def __input(self):print('输入取款金额')def __print_bill(self):print('打印账单')def __take_money(self):print('取款')def withdraw(self):self.__card()self.__auth()self.__input()self.__print_bill()self.__take_money()a = ATM()
a.withdraw()  # 使用者只需要调用withdraw()就可以按照流程执行,并不需要理内部的其他流程方法

代码输出如下:

        上面的代码中,取款是功能,而这个功能有很多其他功能组成:插卡、用户认证、输入取款金额、打印账单、取款;对于使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做,即隔离了复杂度,同时也提升了安全性。

封装方法的其他举例:

  1. 你的身体没有一处不体现着封装的概念,例如你的身体把膀胱尿道等等,这些排尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的...),你总不能用你的意识来操控膀胱,然后控制怎么尿的吧
  2. 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏
  3. 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了

注意:在编程语言里,对外提供的接口(可理解为了一个入口),可以是函数(接口函数),但这与接口的概念还不一样的,接口是代表一组接口函数的集合体

封装与扩展性

        封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。 

# 类的设计者
class Room:def __init__(self,name,owner,width,length,high):self.name = nameself.owner = ownerself.__width = widthself.__length = lengthself.__high = highdef tell_area(self):  # 对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积return self.__width * self.__length# 使用者
r1 = Room('卧室','jove',20,20,20)
print(r1.tell_area())  # 使用者调用接口tell_area

代码输出如下:

        当类需要扩展功能时,只需要类的设计者直接扩展就好了,使用者无须改变自己的代码,如下

# 类的设计者
class Room:def __init__(self,name,owner,width,length,high):self.name = nameself.owner = ownerself.__width = widthself.__length = lengthself.__high = highdef tell_area(self):  # 对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了return self.__width * self.__length * self.__high# 使用者
r1 = Room('卧室','jove',20,20,20)
print(r1.tell_area())  # 使用者调用接口tell_area

代码输出入下:

        对于仍然在使用 tell_area 接口的使用者来说,根本无需改动自己的代码,就可以用上新功能了。

特性(property)

        下面我们以 BMI 指数为例来演示特性(property)。

        BMI 指数是计算而来的,是一种用来衡量一个人是否处于健康体重范围的指标。下面是它的一些指标和计算方法:

成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖,高于32
体质指数(BMI)=体重(kg) ÷ 身高^2(m)
例如:70kg÷(1.75x1.75) =22.86

常规代码实现:

class People:def __init__(self,name,weight,height):self.name = nameself.weight = weightself.height = heightp = People('jove',75,1.81)
p.bmi = p.weight / (p.height ** 2)
print(p.bmi)

代码输出如下:

        使用常规方法成功实现了一个对象的 BMI 指数,但是如果多个对象需要计算的话那我们就需要写多次的 BMI 指数的计算公式赋值给各个对象的 bmi 数据属性,于是我们把这部分计算公式放到类里的一个函数属性里面,从而减少重复代码,如下

class People:def __init__(self,name,weight,height):self.name = nameself.weight = weightself.height = heightdef bmi(self):return self.weight / (self.height ** 2)p = People('jove',75,1.81)
print(p.bmi())  # 改变了调用方式

代码输出如下:

        但是这就出现了新的问题,这样改变了使用者的调用方式,从原来的数据属性调用不用加括号,变成了函数属性的调用需要加括号了,不过好在功能的实现是没问题的。但很明显它听起来像是一个数据属性而非函数属性,如果我们将其做成一个属性,更便于理解,这个时候就到我们的 property 出场了。

使用 property 实现:

        property 也是一个装饰器,它是一种特殊的属性,访问它时会执行一个功能(函数)然后返回值。使用方法如下

class People:def __init__(self,name,weight,height):self.name = nameself.weight = weightself.height = height@property  # 可以让使用者像访问数据属性那样去访问bim(),让使用者感知不到是在调用一个函数def bmi(self):return self.weight / (self.height ** 2)p = People('jove',75,1.81)
print(p.bmi)  # 触发方法bmi的执行,将p自动传给self,执行后返回值作为本次引用的结果
# p.bmi = 3333 不能直接赋值,因为实际上是一个方法,会报错: AttributeError: can't set attribute

代码输出如下:

        使用 property 有效地保证了属性访问的一致性。另外 property 还提供设置和删除属性的功能,如下

class People:def __init__(self,name):self.__name = name  # 将属性隐藏起来@property  # 相当于伪装def name(self):print('------get------')return self.__name@name.setter  # 修改 @name.setter 必须是def name 被 @property 装饰过了才能使用def name(self,val):print('------set------')if not isinstance(val,str): # 在设定值之前进行类型检查print('名字必须是字符串类型')returnself.__name = val   # 通过类型检查后,将值val存放到真实的位置self.__name@name.deleter  # 删除 同上def name(self):print('------del------')print('不允许删除')p = People('jove')
print(p.name)   # 获取值 会触发 @property 下的函数
p.name = 'JOVE'  # 修改值 会触发 @name.setter 下的函数name(p,'JOVE')
p.name = 123    # 触发name.setter对应的的函数name(p,123),不会进行修改,并进行提示
print(p.name)del p.name  # 删除属性 会触发 @name.deleter 下的函数name(p),不会进行删除,并进行提示

代码输出如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/472348.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

@Autowired 和 @Resource思考(注入redisTemplate时发现一些奇怪的现象)

1. 前置知识 Configuration public class RedisConfig {Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template new RedisTemplate<>();template.setConnectionFactory(facto…

MongoDB分布式集群搭建----副本集----PSS/PSA

MongoDB分布式集群 Replication 复制、Replica Set 复制集/副本集 概念 一、 副本集的相关概念 1.概念 “ A replica set is a group of mongod instances that maintain the same data set. ” 一组MongoDB服务器&#xff08;多个mongod实例&#xff09;&#xff08;有不…

五、函数封装及调用、参数及返回值、作用域、匿名函数、立即执行函数

1. 函数基本使用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style&…

数据分析-48-时间序列变点检测之在线实时数据的CPD

文章目录 1 时间序列结构1.1 变化点的定义1.2 结构变化的类型1.2.1 水平变化1.2.2 方差变化1.3 变点检测1.3.1 离线数据检测方法1.3.2 实时数据检测方法2 模拟数据2.1 模拟恒定方差数据2.2 模拟变化方差数据3 实时数据CPD3.1 SDAR学习算法3.2 Changefinder模块3.3 恒定方差CPD3…

第八节 如何结合AAA实现用户远程登录-路由基础

关于调试设备的登录方式&#xff0c;一共有三种&#xff1a; 第一个&#xff1a;console&#xff1a;需要工程师在现场&#xff0c;进行登录&#xff0c;设备开局的时候使用 第二个&#xff1a;telnet ssh&#xff1a;基于网络互通的前提下进行登录的&#xff0c;远程登录 第三…

【Conda】Windows下conda的安装并在终端运行

下载 在官网下载 https://www.anaconda.com/download/success 安装 双击 一直下一步安装 配置环境变量 为了在终端运行&#xff0c;需配置环境变量 进入到安装conda的目录并复制路径 设置高级环境变量 在终端运行 输入&#xff1a; conda list表明可以正常运行 参考…

LogViewer NLog, Log4Net, Log4j 文本日志可视化

LogViewer 下载 示例&#xff1a;NLog文本日志可视化软件&#xff0c;并且能够实时监听输出最新的日志 nlog.config 通过udp方式传输给LogViewer (udp://ip:port) <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"http://www.nlog-…

MuMu模拟器安卓12安装Xposed 框架

MuMu模拟器安卓12安装Xposed 框架 当开启代理后,客户端会对代理服务器证书与自身内置证书展开检测,只要检测出两者存在不一致的情况,客户端就会拒绝连接。正是这个原因,才致使我们既没有网络,又抓不到数据包。 解决方式: 通过xposed框架和trustmealready禁掉app里面校验…

Python Web 应用开发基础知识

Python Web 应用开发基础知识 引言 随着互联网的快速发展&#xff0c;Web 应用程序的需求日益增加。Python 作为一种简单易学且功能强大的编程语言&#xff0c;已经成为 Web 开发中广受欢迎的选择之一。本文将深入探讨 Python Web 开发的基础知识&#xff0c;包括常用框架、基…

CSS Module:告别类名冲突,拥抱模块化样式(5)

CSS Module 是一种解决 CSS 类名冲突的全新思路。它通过构建工具&#xff08;如 webpack&#xff09;将 CSS 样式切分为更加精细的模块&#xff0c;并在编译时将类名转换为唯一的标识符&#xff0c;从而避免类名冲突。本文将详细介绍 CSS Module 的实现原理和使用方法。 1. 思…

动力商城-03 Idea集成apifox Mybatis-Plus字段策略

1.Idea下载apifox插件 2.新建令牌放入Idea 3.右键上传到对应接口 4.设置前置url 插件能够自动识别swagger注解 Mybatis-Plus字段策略 1、FieldStrategy作用 Mybatis-Plus字段策略FieldStrategy的作用主要是在进行新增、更新时&#xff0c;根据配置的策略判断是否对实体对…

使用 npm 安装 Yarn

PS E:\WeChat Files\wxid_fipwhzebc1yh22\FileStorage\File\2024-11\spid-admin\spid-admin> yarn install yarn : 无法将“yarn”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后…

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件

Springboot 使用EasyExcel导出含图片并设置样式的Excel文件 Excel导出系列目录&#xff1a;★★★★尤其注意&#xff1a;引入依赖创建导出模板类逻辑处理controllerservice 导出效果总结 Excel导出系列目录&#xff1a; 【Springboot 使用EasyExcel导出Excel文件】 【Springb…

深入理解 source 和 sh、bash 的区别

1 引言 在日常使用 Linux 的过程中&#xff0c;脚本的执行是不可避免的需求之一&#xff0c;而 source、sh、bash 等命令则是执行脚本的常用方式。尽管这些命令都能运行脚本&#xff0c;但它们之间的执行方式和效果却有着显著的区别。这些区别可能会影响到脚本的环境变量、工作…

CC6学习记录

&#x1f338; cc6 cc6和cc1的国外链其实后半条链子是一样的&#xff0c;但是cc6的不局限于jdk的版本和commons-collections的版本。 回忆一下cc1的后半条链子&#xff1a; LazyMap.get()->InvokerTransformer.transform() 这里我们就结合了URLDNS链的思路&#xff0c;在…

飞凌嵌入式RK3576核心板已适配Android 14系统

在今年3月举办的RKDC2024大会上&#xff0c;飞凌嵌入式FET3576-C核心板作为瑞芯微RK3576处理器的行业首秀方案重磅亮相&#xff0c;并于今年6月率先量产发货&#xff0c;为客户持续稳定地供应&#xff0c;得到了众多合作伙伴的认可。 FET3576-C核心板此前已提供了Linux 6.1.57…

路漫漫其修远兮,吾将上下而求索---第一次使用github的过程记录和个人感受

文章目录 1.仓库位置2.新建仓库3.配置仓库4.克隆和上传5.推荐文章和我的感受 1.仓库位置 这个仓库的位置就是在我们的这个个人主页的右上角&#xff1b;如果是第一次注册账号的话&#xff0c;这个主页里面肯定是不存在仓库的&#xff0c;需要我们自己手动的进行创建&#xff1…

docker与大模型(口语化原理和实操讲解)

文章目录 一、镜像images1&#xff09;下载安装2&#xff09;docker images相关命令(保存、删除、上传、别名、搜索镜像) 二、容器container1&#xff09;展现所有在跑的容器服务ps2&#xff09;start /restart / kill / stop /rm 三、dockerfile四、volume五、network六、dock…

《基于Oracle的SQL优化》读书笔记

查看执行计划set autotrace traceonly explain在当前session中将优化器模式改为RULE。alter session set optimizer_modeRULE;统计信息存储在oracle的数据字典里&#xff0c;且从多个维度描述了oracle数据库里相关对象的实际数据量&#xff0c;实际数据分布等详细信息。 -- 对…

css:浮动

网页的本质上就是摆放盒子&#xff0c;把盒子摆到相应的位置上 css提供了三种传统的布局方式&#xff1a; 普通流&#xff08;标准流&#xff09;&#xff1a;标签按默认方式排列&#xff0c;最基本的布局方式 浮动 定位 实际开发中&#xff0c;一个网页基本包含了三种这种布局…