名称空间
本文是Python通用编程系列教程,已全部更新完成,实现的目标是从零基础开始到精通Python编程语言。本教程不是对Python的内容进行泛泛而谈,而是精细化,深入化的讲解,共5个阶段,25章内容。所以,需要有耐心的学习,才能真正有所收获。虽不涉及任何框架的使用,但是会对操作系统和网络通信进行全局的讲解,甚至会对一些开源模块和服务器进行重写。学完之后,你所收获的不仅仅是精通一门Python编程语言,而且具备快速学习其他编程语言的能力,无障碍阅读所有Python源码的能力和对计算机与网络的全面认识。对于零基础的小白来说,是入门计算机领域并精通一门编程语言的绝佳教材。对于有一定Python基础的童鞋,相信这套教程会让你的水平更上一层楼。
一 类的实例化
调用类 ====>产生类的对象,该对象也可以成为类的一个实例,调用类的过程也称为类的实例化
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
最后一行代码就是类的实例化,那么这行代码做了哪些事,我们再来回顾下
- 先造一个空对象obj,和所有对象都一样
- 连同这个对象和三个参数一块传递给init函数,
- 执行DeepshareStudent.init(obj,'王二小',18,'male')
二 名称空间
我们可以使用类名加.___dict___方法,查看类的名称空间,那么对象的名称空间能不能查看呢?
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name # 把name这个属性放进对象的名称空间中
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
print(stu1.__dict__)
在定义类的阶段产生类的名称空间,那么什么时候产生对象的名称空间呢?你要先告诉我什么时候产生对象,只有在调用类的时候才会产生对象,这个时候就会产生出对象的名称空间,有了名称空间就是把对象存好了,但是存不是目的,我们目的是取
print(stu1.__dict__['name'])
毫无疑问,这样肯定是可以的,但是我们还有更好的方法
print(stu1.name)
现在我们定义的类做一下修改
class DeepshareStudent:
school = 'deepshare'
name = 'aaaaaaaaaa'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
现在有两个name,我再执行以下代码,打印的结果是什么呢
print(stu1.name)
很明显我用对象找他的独有的name属性 就应该从___init___中找name
但是,如果___init___函数中没有呢?
print(stu1.school)
这个对象stu1会先从他自己对象的名称空间中找school这个属性,但是他发现没有这个属性,那就后退一级,往类的名称空间中找,注意对象的名称空间和类的名称空间不是一个概念,接下来我们会说明这个问题
如果类的名称空间中,他还会往上找吗?
school = 'deepshare'
class DeepshareStudent:
name = 'aaaaaaaaaa'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
print(stu1.school)
肯定是不能的,写代码的时候Pycharm就会有错误的提示了,现在school是定义在全局,与对象stu1没有任何关系
我们再来看一下代码
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
print(DeepshareStudent.__dict__)
print(id(DeepshareStudent.__dict__))
print(stu1.__dict__)
print(id(stu1.__dict__))
print(id(stu2.__dict__))
我现在造了两个对象stu1和stu2,打印结果可以说明,他们分别有自己的内存空间,类也有自己的内存空间,那么这之间有什么关系呢?
他们三部分是完全独立的,没有包含与被包含的关系,只不过用对象点属性做属性查找的时候先从对象的名称空间中查找,如果能够找到,就是使用对象的名称空间存的,如果找不到就取类的名称空间中找,再找不到就要报错了,错误提示:对象没有这个属性
三 绑定方法
前面我们研究是对象的特征(用变量表示的,接下来我们来研究对象的技能(用函数来表示的),不管是特征还是技能这些都是对象的属性,这是我们前面已经证明过的,那么接下来我们就来调用调用对象的属性
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
print(DeepshareStudent.learn)
print(stu1.learn)
print(stu2.learn)
print(id(DeepshareStudent.school))
print(id(stu1.school))
print(id(stu2.school))
输出
<function DeepshareStudent.learn at 0x1067bd488>
<bound method DeepshareStudent.learn of <__main__.DeepshareStudent object at 0x104215128>>
<bound method DeepshareStudent.learn of <__main__.DeepshareStudent object at 0x104215208>>
4550599216
4550599216
4550599216
从输出结果我们可以看到用类来调learn属性拿到的是一个普通方法,而用对象去调learn属性拿到的是
绑定方法,这个方法是绑定到类属性上的
我们再来理解一下,类是直接从自己的名称空间中拿到learn属性,而对象在自己的名称空间中找learn属性没有找到,就要到类的名称空间中去找,相当于是间接拿到了learn属性。类内部定义的函数自己能使用,但主要是是给对象用的。再来看一下内存地址,上面三个函数属性的是完全不同的,而下面三个变量属性是完全一样的。这是因为类内部的变量是直接给对象使用,而类内部的函数是绑定给对象使用,这怎么理解呢
你们大家都是Deepshare的学生,都有一个相似的技能叫做学习,但是你学习能学到小明身上了,你学习并不能代表小明学习,虽然你们都具有学习的功能。这就叫绑定方法,大家用的是同一个功能,但是绑定给谁,就是谁在执行
那么这在程序中怎么体现呢?(其实我们前面的代码已经用过了,只是我还没讲)
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('is learning')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
DeepshareStudent.learn('albert')
stu1.learn()
执行代码,我们会发现,类调用learn方法必须要传一个参数,而stu1调用learn方法不需要传参数,但是learn确实是需要一个参数的,那就说明他是自动传了一个参数,我们怎么验证呢
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('is learning',self) # 打印一下self就知道了
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
DeepshareStudent.learn('albert')
stu1.learn()
print(stu1)
输出
is learning albert
is learning <__main__.DeepshareStudent object at 0x1014d1128>
<__main__.DeepshareStudent object at 0x1014d1128>
仔细看看是不是一样的
这就说明stu1调用learn方法本质原理就是把它自己传进来
stu1.learn() # DeepshareStudent.learn(stu1)
这就是绑定方法,类内部定义的函数,类可以使用,但是类来使用的时候就是一个普通函数,普通函数有几个参数就穿几个参数
print(DeepshareStudent.learn)
DeepshareStudent.learn('albert')
但是类内部定义的函数其实是为了给对象用的,而且是绑定给对象用的,绑定给不同的对象,就是不同的绑定方法
print(stu1.learn)
print(stu2.learn)
绑定方法的特殊之处在于谁来调用,就会把谁当作第一个参数自动传入
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('is learning',self)
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
stu1.learn()
stu2.learn()
接下来我们单独用stu1来说明
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
# self=stu1
def learn(self):
print('%s is learning' % self.name) # self就是stu1,stu1有name方法
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
stu1.learn()
stu2.learn()
输出
王二小 is learning
王三小 is learning
综上所述
类内部的变量是给所有对象共享,所有对象指向的都是同一个内存地址; 类内部定义的函数其实是为了给对象用的,而且是绑定给对象用的,绑定给不同的对象,就是不同的绑定方法
类内部定义的函数必须要有self这个参数,但也可以有表的参数
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self.name)
def choose(self,course): # 在添加一个函数
print('%s is choosing %s' %(self.name, course)) # 传一个course参数
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
stu1.choose('Python') # 传一个实参
stu2.choose('AI')
输出
王二小 is choosing Python
王三小 is choosing AI
我们在使用Pycharm工具的时候,遇到括号里面需要传参,Pycharm回自动给我们提示参数的形参,当我们遇到self这个参数的,就当成没有这参数,只需要传其他的参数就可以了,现在你理解了,因为self是会自动传参值的,而且是自动把它本身当作第一个参数传入。
四 一切皆对象
在Python中有一个一切皆对象的说法,面向对象讲到这里我们就可以解释了
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self.name)
def choose(self,course):
print('%s is choosing %s' %(self.name, course))
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
print(stu1)
print(type(stu1))
print(id(stu1))
输出
<__main__.DeepshareStudent object at 0x106776390>
<class '__main__.DeepshareStudent'>
4403454864
一个等号表示赋值,stu1其实就是我们定义的一个变量,第一天我们就说过了,一个变量有id,type和值,他的值没什么好说的,就是一个Deepshare类的对象,id就是表示它的内存地址,这也没什么好说的,我们来看stu1的类型是什么 ,忽略___main___,stu1的类型就是Deepshare,stu1的类也是Deepshare,所以在 Python中
类与类型是一个概念
明确了这个概念之后,我们再来看下面代码
l1 = [1,2,3,4] # list([1,2,3,4])
print(type(l1))
输出
<class 'list'>
l1 写一个列表其本身就是# 后面的代码,我们可以看到 l1 的类型是list,那也就是说 l1的类就是list,所以list其实就是一个类,只不过它为了好看,没有首字母大写。那么我们之前所学的定义各种数据类型的变量(str,int,float,list,tuple,set等等)其实都是在调用类,来进行类的实例化,造出一个对象
l1 = [1,2,3,4] # list([1,2,3,4])
print(l1.append)
输出
<built-in method append of list object at 0x1077560c8>
在数据类型那一章我们就学过了一个列表有append方法,可以给列表添加元素,那么现在我们再来看,l1是一个对象,根据打印结果我们可以看到append其实就是一个方法,他虽然现在不叫绑定方法了,但他其实就是一个绑定方法。
其实Pycharm做的挺好的,像self这种不需要传的参数他就会用灰色标记,真正要传的参数用黄色,绑定方法的特征是什么
l1 = [1,2,3,4] # list([1,2,3,4])
l1.append(5) # 其实就是在执行list.append(l1,5)
既然是一样的,那我们就试一试
l1 = [1,2,3,4]
list.append(l1,5)
print(l1)
输出
[1, 2, 3, 4, 5]
绑定方法的精髓是什么,大家都是同一种功能,但问题是,我和你都具有吃饭的功能,我吃完了,吃到你肚子里去了吗?
l1 = [1,2,3,4]
l2 = ['a','b','c']
l2.append('d')
print(l2)
print(l1)
这就是绑定方法,l2 append的东西肯定不会添加到l1里面去,我们也可以换一种方式添加
这个时候必须要传self参数
l1 = [1,2,3,4]
l2 = ['a','b','c']
list.append(l2,'d')
print(l2)
print(l1)
我们最后再来看一下我们自己写的类和Python内置的类
class DeepshareStudent:
school = 'deepshare'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print('%s is learning' % self.name)
def choose(self,course):
print('%s is choosing %s' %(self.name, course))
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
stu1 = DeepshareStudent('王二小',18,'male')
stu2 = DeepshareStudent('王三小',18,'male')
print(int)
print(str)
print(list)
print(tuple)
print(set)
print(dict)
print(DeepshareStudent)
输出
<class 'int'>
<class 'str'>
<class 'list'>
<class 'tuple'>
<class 'set'>
<class 'dict'>
<class '__main__.DeepshareStudent'>
都叫class,出去___main___,他们看起来是一样的,当然他们用法的原理也是一样的。
五 对象属性的增删改查
先在这里补充一点,我们初始化用的___init__函数,不是int函数_
查看
class Bar:
n = 1111
def __init__(self,x):
self.x = x
obj = Bar('abc')
print(obj.__dict__)
print(obj.x)
print(obj.n)
添加
class Bar:
n = 1111
def __init__(self,x):
self.x = x
obj = Bar('abc')
print(obj.__dict__)
print(obj.x)
print(obj.n)
obj.abc = 'abc'
print(obj.abc)
修改
class Bar:
n = 1111
def __init__(self,x):
self.x = x
obj = Bar('abc')
print(obj.__dict__)
print(obj.x)
print(obj.n)
obj.abc = 'abc'
print(obj.abc)
obj.abc = '123'
print(obj.abc)
删除
class Bar:
n = 1111
def __init__(self,x):
self.x = x
obj = Bar('abc')
print(obj.__dict__)
print(obj.x)
print(obj.n)
obj.abc = 'abc'
print(obj.abc)
obj.abc = '123'
print(obj.abc)
del obj.abc
# print(obj.abc)
练习一:
实现这样一个功能,有一个对象,他有一个count属性,count属性是统计他所在的类产生了多少个对象,即print(obj.count)能打印出对象的个数
分析:
既然要统计有多少个对象,那么只要有一个对象就要执行一次类的调用,调用一次类,其实就要执行类内部的___init___函数,所以我们在init函数里面写上相应的逻辑就好了
class Foo:
count = 0
def __init__(self):
self.count += 1
obj1 = Foo()
obj2 = Foo()
obj3 = Foo()
print(obj1.count)
是不是这样写呢?如果这样写就错了,我们可以执行程序,打印一下看看效果
1
这是什么原因呢?
self是对象自己,obj1,obj2和obj3是互不相通的,虽然每次count都会加一,但是其实每个对象
都有自己的独一份的count,初始值永远是0,每次来都是加1,所以永远是1
我们应该对他们共享的那个属性来做修改,什么属性是共享的呢?
class Foo:
count = 0
def __init__(self):
Foo.count += 1
obj1 = Foo()
obj2 = Foo()
obj3 = Foo()
print(obj1.count)
虽然这次我们没有在对象自己的名称空间中添加count属性,但是类里面有啊,对象在做属性查找的时候先从自己的名称空间中找,没有就去他所在的类里面找,在类里面,这个count变量属性是所有对像共用的。
练习二:
实现一个人狗大战的程序,人可以咬狗,狗也可以咬人,人和狗都有自己的生命值,被咬了之后会掉血,当生命值为0时,人或者狗就死了
按照步骤我们先总结出现实中的对象
现实中的人类
"""
人1
特征:
名字='张二炮'
攻击力=60
生命值=100
技能:
咬
人2
特征:
名字='刘老三'
攻击力=50
生命值=100
技能:
咬
现实中的人类
相同的特征 无
相同的技能 咬
"""
现实中的狗类
"""
狗1
特征:
名字='旺财'
品种="京巴"
攻击力=80
生命值=50
技能:
咬
狗2
特征:
名字='小黑'
品种="藏獒"
攻击力=200
生命值=200
技能:
咬
现实中的狗类
相同的特征 无
相同的技能 咬
"""
注意:
两个人虽然有相同的特征的,都有名字,攻击力,和生命值,但是这是他们每个人独立的,不如章二炮被咬了之后,他的生命值下降,而刘老三没有被咬,还是原来的值,所以我们定义现实中的人类是没有相同的特征的,同理,现实中的狗类也是一样的。
代码实现
class People:
def __init__(self, name, aggressivity, life_value=100):
self.name = name
self.aggressivity = aggressivity
self.life_value = life_value
# 注意:咬肯定不能咬自己,一定是咬敌人,所以要传一个敌人参数
def bite(self, enemy): #self=p1 enemy=d1
enemy.life_value-=self.aggressivity
print("""
人[%s] 咬了一口狗 [%s]
狗掉血[%s]
狗还剩血量[%s]
""" %(self.name,enemy.name,self.aggressivity,enemy.life_value)
)
class Dog:
def __init__(self, name, dog_type, aggressivity, life_value):
self.name = name
self.dog_type = dog_type
self.aggressivity = aggressivity
self.life_value = life_value
def bite(self, enemy): #self = d1 enemy= p1
enemy.life_value-=self.aggressivity
print("""
狗[%s] 咬了一口人 [%s]
人掉血[%s]
人还剩血量[%s]
""" %(self.name,enemy.name,self.aggressivity,enemy.life_value)
)
p1 = People('张二炮', 60)
d1=Dog('小黑',"藏獒",200,200)
p1.bite(d1)
d1.bite(p1)
这段代码把程序的基本逻辑和功能实现了,但是还有一些明显的bug,大家如果有兴趣,可以自己完善。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?