Self-study Python Fish-C Note20 P64to65

2024-09-02 11:20

本文主要是介绍Self-study Python Fish-C Note20 P64to65,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

类和对象 (part 3)

本节主要介绍 类和对象的多态和鸭子类型、私有变量和 __slots__(原视频P64-65)\

多态

多态是面向对象编程的三大特征之一,另外两个是封装和继承。多态是指同一个运算符、函数或对象,在不同场景下具有不同作用效果的情况。
Python 是一门动态语言,多态本来就是 Python的一种特性。
比如:
加号:两边都是数字的时候就是执行算术运算(相加),如果两边都是字符串就是得到字符串的拼接。

8+9
17
'aaa'+'bbb'
'aaabbb'

乘号:也类似,如果是字符串则会得到重复拷贝的效果,如果是数字则算术运算

'abc'*3
'abcabcabc'
3*5
15

这些是运算符的多态,除了运算符的多态,Python有一些函数也是支持多态的:
比如:
函数 len() 获取对象长度,如果传入字符串则得到字符串这个对象字符的个数;如果传入列表则得到列表中元素的个数;如果传入字典则得到字典中 键(key) 的个数。

len('abcdefg')
7
len(['aa',1,2])
3
len({'a':1,'b':3})
2

以上,展现了多态的好处,尽管我们的接口是不变的,但是它却可以根据不同的对象执行不同的操作。

类继承的多态

Python 允许我们在子类中定义和父类同名的方法进行覆盖(重写),事实上重写就是实现类继承的多态。

class Shape():def __init__(self,name):self.name=namedef area(self):pass #这里是父类我们直接passclass Square(Shape):def __init__(self,length):super().__init__('正方形')self.length=lengthdef area(self):return self.length * self.lengthclass Circle(Shape):def __init__(self,radius):super().__init__('圆形')self.radius = radiusdef area(self):return 3.14 * self.radius * self.radiusclass Triangle(Shape):def __init__(self,base,height):super().__init__('三角形')self.base = baseself.height = heightdef area(self):return (self.base * self.height)/2s1 = Square(5)
c1 = Circle(6)
t1 = Triangle(3,4)
print(s1.name)
print(c1.name)
print(t1.name)
print(s1.area())
print(c1.area())
print(t1.area())
正方形
圆形
三角形
25
113.03999999999999
6.0

上面的例子中正方形、圆形、三角形都是继承自 Shape 类,但是他们又重写了构造函数和 area() 方法。这就是多态的体现。

自定义函数实现多态接口

class Cat:def __init__(self,name,age):self.name=nameself.age=agedef intro(self):print(f'this is a cat, my name is {self.name}. I am {self.age} years old')def say(self):print('miao')class Dog:def __init__(self,name,age):self.name=nameself.age=agedef intro(self):print(f'this is a dog, my name is {self.name}. I am {self.age} years old')def say(self):print('wang')class Pig:def __init__(self,name,age):self.name=nameself.age=agedef intro(self):print(f'this is a pig, my name is {self.name}. I am {self.age} years old')def say(self):print('hong')c1 = Cat('mao',5)
d1 = Dog('gou',3)
p1 = Pig('zhu',1)

此时我们定义一个叫 animal() 的函数,它会接收一个参数 x(是我们之前定义的动物的对象)。这时候,多态的功效就发挥出来了,当我们给函数传递不同的实例对象的时候,输出不同的结果。
即,该函数接收不同对象作为参数,并且不检查其类型的情况下执行它的方法。

def animal(x):x.intro()x.say()animal(c1)
animal(d1)
animal(p1)
this is a cat, my name is mao. I am 5 years old
miao
this is a dog, my name is gou. I am 3 years old
wang
this is a pig, my name is zhu. I am 1 years old
hong

鸭子类型

在编程中,我们不会关心对象是什么类型,我们关心的是它的行为是否符合要求。
比如之前 animal() 函数,它不关注里面参数 x 是什么,只要 x 里面有 intro()say() 两个方法,他就不会报错。

class Bicycle:def intro(self):print('this is a bicycle')def say(self):print("dddddddd")# 这里 Bicycle 有`intro()`和 `say()` 两个方法就可以被 animal 调用,尽管其不是动物。
b1 = Bicycle()
animal(b1)
this is a bicycle
dddddddd

"私有变量"和 __slots__

“私有变量”

私有变量:是指通过某种手段,使得对象中的属性或方法无法被外部所访问的机制,(一种保护机制)。
但是 Python 的哲学是给程序员极大的自由,所以其实在 Python中,那种仅限从一个对象内部才能够访问的 私有变量 并不存在。这里我们给其加上双引号 “私有变量” 来区分。
但是, Python 中有一个 name mangling 的机制,翻译过来就是 名字改编,名称改写,或名称修饰。
语法就是在 名字的前面 加上两个连续的下横线。

class C:def __init__(self,x):self.__x = x # 设置"私有变量" __xdef set_x(self,x):self.__x=xdef get_x(self):print(self.__x)c1 = C(100)

此时我们是无法直接通过变量名来访问到这个变量的。

c1.__x

after run:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_8456\543364159.py in <module>
----> 1 c1.__xAttributeError: 'C' object has no attribute '__x'

想要访问变量的值,就需要通过指定的接口,比如这里的 set_x()get_x() 这两个方法。

c1.get_x()
100
c1.set_x(200)
c1.get_x()
200

但是,事实上如果我们查看 dic 属性:

c1.__dict__
{'_C__x': 200}
# 虽然 dict 里面没有 __x,但是有 _C__x
# 我们尝试访问一下
c1._C__x
200

这其实就是 名字改编,其实就是 下横线加类名加变量的名字。所以在 Python 中所谓的"私有变量"就是把我们想要私有的变量,按照这个规律改了名字。其实方法名也一样:

class D:def __func(self): # 在方法前面加两个连续的下横线,把方法隐藏起来print('hi')d1 = D()
# now d1.__func() can not run
# but :
d1._D__func()
hi

但是,我们强烈不建议有这种方法去访问。毕竟有意使用双下横线开头就是不希望这个属性或方法被外界所访问到。所以我们应该遵循这个约定俗成的规则。

名字改编是发生在类实例化对象的时候的事情

在对象诞生之后,是不能够通过动态添加属性的方式来添加一个 "私有变量"的

c1.__y=111
c1.__dict__
{'_C__x': 200, '__y': 111}
其他约定俗成的规则

单个下横线开头的变量(_x): 仅供内部使用的变量。属于约定俗成的命名规则,所以当看到这种名字不要随意访问和修改。
单个下横线结尾的变量(x_): 比如 class 是 python 用于定义类的,你非要用这个名,可以加一个 单下横线 结尾。

# 对比
class C:def __init__(self,x,y,z):self._x = xself.__y = yself.z_ = zdef say_x(self):print(self._x)def say_y(self):print(self.__y)def say_z(self):print(self.z_)c1 = C(1,2,3)
# 对比发现只有,双下横线开头的会被修改
print(c1._x)
print(c1._C__y) #c1.__y 不行
print(c1.z_)
print('_'*30)
print(c1.__dict__)
# 但是其他的也 非常强烈不建议 访问
print('_'*30)
# 我们也可以看到,内部是可以访问的,且都没有改名
c1.say_x()
c1.say_y()
c1.say_z()
1
2
3
______________________________
{'_x': 1, '_C__y': 2, 'z_': 3}
______________________________
1
2
3

效率提升之道 以及 __slots__

即舍/得之道:Python 为了对象的灵活性,有时候会牺牲大量的存储空间。比如,动态添加属性很灵活,但其背后实现的原理是字典(即 __dict__ 属性,对象的属性通常都是放在这个 __dict__ 里的)。比如:

class C:def __init__(self,x):self.x=xc1 = C(100)
c1.__dict__
{'x': 100}

我们甚至可以直接通过给字典添加键值对的方式,来创建对象的属性。

c1.__dict__['y']=666
print(c1.y)
c1.__dict__
666{'x': 100, 'y': 666}

但是我们之前讲字典的时候说了,字典的执行效率很高是以牺牲存储空间换来的,以空间换时间(详见之前的课程里,字典和集合高效背后的玄机)。使用字典是比较费内存的。
但是,如果我们明确知道一个类的对象设计出来,就只是需要那么固定的几个属性,并且将来也不会有动态添加属性这种功能的需求。那么利用字典来存放属性这种空间上的牺牲就是纯纯的浪费。
针对这种情况,Python专门设置了一个 __slots__的类属性,避免了利用字典来存放造成空间上的浪费。

__slots__

示例:

class C:__slots__ = ['x','y']  # 注意 slots 别忘了s;赋值一个列表,这个列表就是我们希望这个对象可以使用的属性的名称。# 这里我么 希望类C实例化的对象 只有 x和y两个属性def __init__(self,x): # 写构造函数self.x = x # 给其中一个属性赋值c1=C(100) # 这样我们就创建了一个属性受限制的对象 c1

访问 __slots__ 中列举的属性没有问题:

print(c1.x)
c1.y=666
print(c1.y)
100
666

但是如果,我们现在想要动态添加一个属性:

c1.z = 666 

报错:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_1228\4272346953.py in <module>
----> 1 c1.z = 666AttributeError: 'C' object has no attribute 'z'

这种限制不仅体现在动态添加属性上,如果我们在类内部想要创建一个 __slots__ 不包含的属性,也是不被允许的:

class D:__slots__ = ['x','y']def __init__(self,x,y,z):self.x = x self.y = yself.z = zd1 = D(1,2,3)

报错:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_1228\2058141618.py in <module>6         self.z = z7 
----> 8 d1 = D(1,2,3)~\AppData\Local\Temp\ipykernel_1228\2058141618.py in __init__(self, x, y, z)4         self.x = x5         self.y = y
----> 6         self.z = z7 8 d1 = D(1,2,3)AttributeError: 'D' object has no attribute 'z'

事实上,因为使用了 __slots__ 属性,对象就会划分一个固定大小的空间来存放指定的属性。这时候 __dict__ 属性也就不需要了,空间也就因此被节约了出来。但是这牺牲了灵活性,也有人用 __slots__ 属性防止类属性的滥用。

继承自父类的 __slots__ 属性 是不会在子类中生效的

Python 只会关注各个具体的类中,定义的 __slots__ 属性。

class C:__slots__ = ['x','y']def __init__(self,x):self.x=xclass E(C):passe1=E(100)
print(e1.x)
e1.y=200
print(e1.y)
e1.z = 300
print(e1.z) # 可以看到这里不受限制
100
200
300

我们看一下 e1.__slots__ 是有的,因为 类E 是继承自 类C 的。虽然 e1 有 __slots__ 属性,但是这是类C的,这是继承下来的。同时 e1 也会有一个 __dict__ 属性。

e1.__slots__
['x', 'y']
e1.__dict__
{'z': 300}

但是 类C 实例化的对象是没有 __dict__ 属性的

c1 = C(1)
c1.__dict__

报错:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_1228\4180053359.py in <module>1 c1 = C(1)
----> 2 c1.__dict__AttributeError: 'C' object has no attribute '__dict__'

所以说,继承自父类的 __slots__ 属性 是不会在子类中生效的,Python 只会关注各个具体的类中,定义的 __slots__ 属性。

附言:
题目:Self-study Python Fish-C Note-20 P64-P65
本文为自学B站上鱼C的python课程随手做的笔记。一些概念和例子我个人为更好的理解做了些查询和补充
因本人水平有限,如有任何问题,欢迎大家批评指正!
原视频链接:https://www.bilibili.com/video/BV1c4411e77t?p=8

这篇关于Self-study Python Fish-C Note20 P64to65的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129807

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

nudepy,一个有趣的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - nudepy。 Github地址:https://github.com/hhatto/nude.py 在图像处理和计算机视觉应用中,检测图像中的不适当内容(例如裸露图像)是一个重要的任务。nudepy 是一个基于 Python 的库,专门用于检测图像中的不适当内容。该

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

HTML提交表单给python

python 代码 from flask import Flask, request, render_template, redirect, url_forapp = Flask(__name__)@app.route('/')def form():# 渲染表单页面return render_template('./index.html')@app.route('/submit_form',

Python QT实现A-star寻路算法

目录 1、界面使用方法 2、注意事项 3、补充说明 用Qt5搭建一个图形化测试寻路算法的测试环境。 1、界面使用方法 设定起点: 鼠标左键双击,设定红色的起点。左键双击设定起点,用红色标记。 设定终点: 鼠标右键双击,设定蓝色的终点。右键双击设定终点,用蓝色标记。 设置障碍点: 鼠标左键或者右键按着不放,拖动可以设置黑色的障碍点。按住左键或右键并拖动,设置一系列黑色障碍点

Python:豆瓣电影商业数据分析-爬取全数据【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】

**爬取豆瓣电影信息,分析近年电影行业的发展情况** 本文是完整的数据分析展现,代码有完整版,包含豆瓣电影爬取的具体方式【附带爬虫豆瓣,数据处理过程,数据分析,可视化,以及完整PPT报告】   最近MBA在学习《商业数据分析》,大实训作业给了数据要进行数据分析,所以先拿豆瓣电影练练手,网络上爬取豆瓣电影TOP250较多,但对于豆瓣电影全数据的爬取教程很少,所以我自己做一版。 目

【Python报错已解决】AttributeError: ‘list‘ object has no attribute ‘text‘

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一:检查属性名2.2 步骤二:访问列表元素的属性 三、其他解决方法四、总结 前言 在Python编程中,属性错误(At