一文入门Python面向对象编程(干货满满)
在开始之前,我一直企图找到一个通俗直观的例子来介绍面向对象。找来找去,发现什么都可以是面向对象,什么又都不是面向对象。后来我发现,人类认识社会的方式更多的就是面向对象的方式。“物以类聚、人以群分”,这句话好像给我们的面向对象有很好的诠释。会飞的是鸟类,会游的是鱼类。人们总是很会捕捉生活中各种事物的特征,并进行分类。这其实就是一种面向对象的思想。
不同的对象总是有着不同的特征,同一类的对象总是有着相似或者相同的特征
有一句话叫做“一切皆对象“,这就意味着编程要进阶,面向对象是我们绕不过去的坎。
1. 面向过程与面向对象
目标:把大象装进冰箱
1.1 面向过程
面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,并且按步骤实现。
在这里面:我们的事件是把大象装进冰箱,所以我们需要把这件事情拆成各个小步骤,并且实现每个小步骤
a = "大象"
open_ice_door() # 开冰箱门,需要自己实现开冰箱门的函数
push(a) # 推大象进入
close_ice_door() # 关冰箱门,需要自己实现关冰箱门的函数
那如果是把大象装进洗衣机呢?
a = "大象"
open_washer _door() # 开洗衣机门,需要自己实现开洗衣机门的函数
push(a) # 推大象进入
close_washer_door() # 关洗衣机门,需要自己实现关洗衣机门的函数
那如果是把大象装进铁笼呢?
a = "大象"
open_hot_door() # 开铁笼门,需要自己实现开铁笼门的函数
push(a) # 推大象进入
close_hot_door() # 关铁笼门,需要自己实现关铁笼门的函数
那我要是想,今天关冰箱、明天关洗衣机、后天关铁笼呢?我要是想关狮子、老虎呢?我要是想冰箱关大象、洗衣机关狮子、笼子关老虎呢?
我们发现,需求会越来越复杂,代码量越来越多,重复代码也越来越多,而且真正复杂的场景下,我们是没办法写出完整的面向过程的代码的。
这个时候,聪明的开发者们,就开始发挥自己的聪明才智了。
他们发现,这件事情本质就是:把一个动物关进一个容器里面,这个容器可以开门也可以关门,开门和关门这个动作是一样的,而且这个容器是可以复用的。
2.2 面向对象
上面的任务中:我们需要自己创造冰箱、洗衣机、笼子,并且实现开关门方法。
于是,我们就可以把通用的方法封装起来
class Box():"""盒子类,实现了开门、关门方法"""def open_door(self):passdef close_door(self):passclass IceBox(Box):"""冰箱"""def ice(self):"""制冷"""passclass WaterBox(Box):"""洗衣机"""def add_water(self):"""加水"""passdef sub_water(self):"""排水"""pass def wash(self):"""洗涤"""passa = "大象"
ice_box = IceBox() # 冰箱对象
ice_box.open_door() # 通知冰箱开门
push(a) # 推大象进入
ice_box.close_door() # 通知冰箱关门# 那我想关老虎呢?
b = "老虎"
ice_box.open_door() # 通知冰箱开门
push(b) # 推老虎进入
ice_box.close_door() # 通知冰箱关门
面向对象的思想主要是以对象为主,将一个问题抽象出具体的对象,并且将抽象出来的对象和对象的属性和方法封装成一个类。
例如上面,我们可以把冰箱、洗衣机、铁笼子抽象成一个盒子对象,这个盒子可以开门、也可以关门。
任何脱离面向过程空谈面向对象的都是耍流氓!
面向对象的方法,本质上还是为面向过程服务的,因为计算机解决问题的方法永远都是面向过程的。面向对象只是人类的狂欢,只是为了让程序看起来更符合人的思考方式。
2. 类与对象
2.1 类
类是一组相关属性和行为的集合
最常见的类是什么?人类!
人类的属性:有两个眼睛、一个鼻子、一个嘴巴、两个耳朵、一个头、两只手、两条腿
人类的行为:走、跑、跳、呼吸、吃饭
2.2 对象
类的实例,由类创造。
人类是人吗?不是
你是人吗?是
所以。人类是一个抽象的类。你是具体的一个人类对象。
人类是女娲娘娘画的图纸。对象是女娲娘娘根据图纸一个一个捏出来的小人。
3. python 面向对象
程序员,你有对象吗?没有?那就自己new一个吧
3.1 类的定义
其实我们前面已经举了很多例子,也定义了很多类
在Python中可以使用class关键字定义类。
关键字class后面跟着类名,类名通常是大写字母开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承下来的类。
# 类名 Person 通常是大写字母开头的单词
# (object) 表示继承自object这个类,暂时不知道继承的可以先跳过
class Person(object):pass
这里我们就定义了一个最基本的类。写在类中的函数,我们通常称之为(对象的)方法
3.2 创建对象
person = Person() # person 是 Person 类的实例对象
3.3 对象的方法
写在类中的函数,我们通常称之为(对象的)方法
class Person(object):def talk(self):print("我是一个对象的方法")person = Person()
person.talk() # 使用 . 访问对象的属性或者方法
3.3 对象的属性
class Person(object):def __init__(self, name, gender):print("当创建对象的时候,会自动执行这个函数")self.name = nameself.gender = genderdef talk(self):print(f"我的名字是:{self.name},我的性别是:{self.gender}")
__init__是一个特殊方法,在创建对象时进行初始化操作,它会自动执行,他的第一个参数永远都是self,代表实例本身。
>>> xiaoming = Person("小明", "男") # 创建一个名叫小明的男孩对象
# 当创建对象的时候,会自动执行这个函数>>> print(xiaoming.name)
# 小明>>> xiaoming.talk()
# 我的名字是:小明,我的性别是:男-----------------------------------------------------------------------
>>> xiaohong = Person("小红", "女") # 创建一个名叫小红的女孩对象
# 当创建对象的时候,会自动执行这个函数>>> print(xiaohong.name)
# 小红>>> xiaoming.talk()
# 我的名字是:小红,我的性别是:女--------------------------------------------------------------------------
>>> xiaoli = Person("小李", "女") # 创建一个名叫小李的女孩对象
# 当创建对象的时候,会自动执行这个函数>>> print(xiaoli.name)
# 小李>>> xiaoli.talk()
# 我的名字是:小李,我的性别是:女
这里,我们发现类的实例化过程跟我们的生孩子有点类似,当我们创建一个对象的时候 xiaoming = Person("小明", "男"),我们名字叫做小明的朋友就产生了,他有自己的名字和性别,这个是他的属性。当我们还想要一个名为小李的小姑娘,我们就再创建一个新的对象即可。
这就是,大名鼎鼎的,没有对象,我自己 new 一个。
new在其他语言里面是创建对象的关键字。
3.4 self是什么?
首先,我们要明白self不是一个关键字,在类中,你也可以不用self,你也可以使用其他名字。之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性。
那self这个参数在我们的类中指的是什么呢?
self,英文单词意思很明显,表示自己,本身。
self在类中表示的是对象本身。在类的内部,通过这个参数访问自己内部的属性和方法。
# 在这个类中,self表示一个类范围内的全局变量,在这个类中任何方法中,都能访问self绑定的变量
# 同时也能访问self绑定的函数
class Person(object):def __init__(self, name, gender):self.name = nameself.talk() # 访问self绑定的方法def talk(self): # 参数为self,这个函数是对象的方法print(self.name)
4. 面向对象三大特性
4.1 封装
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。
概念很拗口,但是思想却很简单。
回到我们的冰箱、洗衣机,他们的共同特征是什么呢?能装东西、能开门、能关门。这些是他们的共性,我们就可以向上封装。把能装东西、关门、开门封装起来。并且给他一个统称叫做:可开关盒子。可开关盒子就是一个类。这个类的所有对象都可以装东西、开门、关门。
封装可以把计算机中的数据跟操作这些数据的方法组装在一起,把他们封装在一个模块中,也就是一个类中。
class Box():"""盒子类,实现了开门、关门方法"""def open_door(self):passdef close_door(self):pass
4.2 继承
继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。
继承的思想也很简单。你有没有想过一个问题,你为什么长得像人,而不像猪?
首先,你爸是人,你妈也是人,你爸妈都有人的模样,你继承他们,就会继承他们的所有这些属性。你一出生就会有人类共有的这些属性。并且你可以对这些属性进行拓展,比如,你爸只会说中文,但是你会说中文、你拓展了这个方法,你还会说英文。
继承简单地说就是一种层次模型,这种层次模型能够被重用。层次结构的上层具有通用性,但是下层结构则具有特殊性。在继承的过程中类则可以从最顶层的部分继承一些方法和变量。类除了可以继承以外同时还能够进行修改或者添加。通过这样的方式能够有效提高工作效率
class Father:def talk(self):print("我会讲话")def breathe(self):print("我能呼吸")class Me(Father):passme = Me() # 我们的 Me 类,并没有实现下面两个方法,而是继承了 Father 类的方法
me.talk()
me.breathe()
我会讲话
我能呼吸
组合继承:
class P1():def talk(self):print("我是p1")class P2():def talk(self):print("我是p2")class Person(P1, P2): # P1排在第一位,调用P1的talk()passp = Person()
p.talk()
# 我是p1
class P1():def talk(self):print("我是p1")class P2():def talk(self):print("我是p2")class Person(P2, P1): # P2排在第一位,调用P2的talk()passp = Person()
p.talk()
# 我是p2
这里注意一个小细节,当我继承自多个父类,多个父类都有相同的方法。那我调用的时候会调用谁的呢?
其实,是按照继承参数的顺序来的,谁排在第一个就调用谁的方法
4.3 多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
你爸有一个talk()方法,也就是说话,你继承了你爸的talk()方法,对于同样的talk()方法,你爸讲中文,你讲英语,你弟弟讲俄语、你妹妹讲韩语,这就是多态
# 爸爸类
class Father:def talk(self):print("我会讲话,我讲的是中文")# 继承自爸爸类
class Me(Father):def talk(self):print("我是哥哥,我讲英语:hello,world")# 继承自爸爸类
class Son(Father):def talk(self):print("我是弟弟,我讲俄语:Всем привет")# 继承自爸爸类
class Sister(Father):def talk(self):print("我是妹妹,我讲韩语:전 세계 여러분 안녕하세요")me = Me()
son = Son()
sister = Sister()me.talk()
son.talk()
sister.talk()
我是哥哥,我讲英语:hello,world
我是弟弟,我讲俄语:Всем привет
我是妹妹,我讲韩语:전 세계 여러분 안녕하세요
多态存在的三个必要条件
- 要有继承
- 要有重写;
- 父类引用指向子类对象。
5. 属性访问权限
有的时候,在类中的属性不希望被外部访问。如果想让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以双下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
5.1 前置单下划线 _xx
前置单下划线只有约定含义。程序员之间的相互约定,对Python解释器并没有特殊含义。
class Person(object):def __init__(self, name):self._name = "我是一个伪私有变量">>> p = Person()
>>> print(p._name)
我是一个私有变量
我们看见,类并没有阻止我们访问变量 _name
所以:以单下划线开头的名称只是Python命名中的约定,表示供内部使用。它通常对Python解释器没有特殊含义,仅仅作为对程序员的提示。意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
5.2 前置双下划线 __xx
实例的变量名如果以双下划线开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Person(object):def __init__(self):self.__name = "我是一个私有变量">>> p = Person()
>>> print(p.__name)
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 6, in <module>print(p.__name)
AttributeError: 'Person' object has no attribute '__name'
但我们访问 __name 的时候,报错了,阻止了我们在实例外部访问私有变量。这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮
class Person(object):def __init__(self):self.__name = "我是一个私有变量"def __talk(self):print("sdsd")p = Person()
p.__talk()
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 9, in <module>p.__talk()
AttributeError: 'Person' object has no attribute '__talk'
那是真的彻底不能访问了吗?其实不是的
print(p._Person__name)
我是一个私有变量
不能直接访问__name是因为Python解释器对外把__name变量改成了_Person__name,所以,仍然可以通过_Person__name来访问__name变量:
但是,最好不要这样做,Python的访问限制其实并不严格,主要靠自觉。
6. 内置的特殊方法
Python中的类提供了很多双下划线开头和结尾 __xxx__ 的方法。这些内置方法在object类中已经定义,子类可以拿来直接使用。
__xxx__是系统定义的名字,前后均有一个“双下划线” 代表python里特殊方法专用的标识。
6.1 __init__(self, ...)
__init__方法在类的一个对象被建立时,会自动执行,无需用户去调用它。可以使用这个方法来对你的对象做一些初始化。
class Person(object):def __init__(self, name):self.name = namep = Person("test")
print(p.name)
# test
相当于构造函数,我们向类中传递的参数,就在这个函数接受。并且这个方法只能返回None,不能返回其他对象。
但是其实这个方法只是一个伪构造函数,生成对象的过程并不是它来完成的,它只是对生成的实例进行初始化。
举个例子的话:我们把创建实例比作生孩子。这个函数并没有承担妈妈生孩子的责任,而是等妈妈把孩子生出来以后,给这个孩子起了个名字。真正生孩子的是下面的__new__方法。
6.2 __new__(cls, *args, **kwargs)
__new __()在__init __()之前被调用,是真正的类构造方法,用于产生实例化对象(空属性)。__new__方法必须返回一个对象
这个方法会产生一个实例化对象,然后我们的实例对象才会调用 __init__()方法进行初始化。
__init__和__new__区别:
__init__通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。__new__通常用于控制生成一个新实例的过程。它是类级别的方法,这个方法产生的实例其实也就是__init__里面的self
__new__一般很少用于普通的业务场景,更多的用于元类之中,因为可以更底层的处理对象的产生过程。而__init__的使用场景更多。有兴趣的小伙伴们可以多去了解了解这个方法有哪些高级的玩法。这里就不做介绍了。
6.3 __del__(self)
析构方法,当对象在内存中被释放时,自动触发此方法,往往用来做“清理善后”的工作。
此方法一般无须自定义,因为Python自带内存分配和释放机制,除非你需要在释放的时候指定做一些动作。析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
在我们的工作中,基本不会用到这个方法。所以这里就知道有这样一个概念就行了。同时,python解释器已经帮我们做了垃圾回收与内存管理,我们使用 Python 编程不需要再过度优化内存使用,以避免写出 C++ 风格的代码。
6.4 __call__(self, *args, **kwargs)
注意:这个方法并不是内置的方法,需要我们去实现这个方法
如果,我们在类中实现了这个方法,那个,这个类的实例就可以被执行,执行的就是这个方法。讲的很拗口,看代码吧
class Person(object):def __init__(self, name, age):self.name = nameself.age = agep = Person("test", 26)
p() # 抛出异常:类对象是不可调用的
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 12, in <module>p()
TypeError: 'Person' object is not callable
通过__call__使类对象可调用:
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __call__(self, *args, **kwargs):print("执行实例方法call方法")p = Person("test", 26)
p() # 可以直接调用类的对象,因为我们在类中实现了__call__(),调用的也是我们__call__()
# 执行实例方法call方法
6.5 __str__(self)
返回对象的字符串表达式。
我们之前在学习python的字符串的时候,应该都很熟悉一个方法:str(),使用这个方法可以把一个对象转换成字符串。实际上,执行的就是对象的__str__方法。
没有实现__str__方法:
# 在这里面我们没有实现 __str__ 方法
class Person(object):def __init__(self, name, age):self.name = nameself.age = age
p = Person("baozi", 20)
print(str(p))
# <__main__.Person object at 0x7fad74a98f28>
# 默认返回的是解释器在执行的时候这个实例的一些相关信息,没有什么参考意义
实现__str__方法:
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __str__(self):return f"my name is {self.name} age is {self.age}"p = Person("baozi", 26)
print(str(p))
# my name is baozi age is 26print(p) # 直接打印这个对象,也会先执行 str(p)
# my name is baozi age is 26
__str__主要是用于 str(对象) 的时候,返回一个字符串
6.6 __repr__(self)
这个方法的作用和str()很像,这两个函数都是将一个实例转成字符串。但是不同的是,两者的使用场景不同,
- 其中
__str__更加侧重展示。所以当我们print输出给用户或者使用str函数进行类型转化的时候,Python都会默认优先调用__str__函数。 - 而
__repr__更侧重于这个实例的报告,除了实例当中的内容之外,我们往往还会附上它的类相关的信息,因为这些内容是给开发者看的
若定义了
__repr__没有定义__str__,那么本该由__str__展示的字符串会由__repr__代替。
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __str__(self):return f"my name is {self.name} age is {self.age}"def __repr__(self):return "Person('%s', %s)" % (self.name, self.age)p = Person("baozi", 26)
print(p)
# my name is baozi age is 26print(repr(p))
# Person('baozi', 26)
6.7 __eq__
当判断两个对象的值是否相等时,触发此方法
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __eq__(self, other):print("判断两个对象是否相等,触发此函数")return Truep1 = Person("baozi", 26)
p2 = Person("baozi", 33)
print(p1 == p2)
# 判断两个对象是否相等,触发此函数
# True
上面的结果显示两个对象是相等的。但其实这两个对象属性值是不一样的,理论上不应该是相等。但是我们重写了__eq__,无论如何什么情况都返回True。
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __eq__(self, other):return self.__dict__ == other.__dict__p1 = Person("baozi", 26)
p2 = Person("baozi", 33)
print(p1 == p2)
# Falsep2.age = 26
print(p1 == p2)
# True
6.8 其他比较方法
与6.7类似,下面这些运算符执行的时候,也会执行相应的特殊方法,这里就不一一展开了。
__lt__():大于(>)__gt__():小于(<)__le__():大于等于( >= )__ge__():小于等于( <= )__eq__():等于( == )__ne__():不等于 ( != )
6.9 __getitem__()、__setitem__()、__delitem__()
为什么要它们仨放在一起呢?因为它们是字典的取值、赋值、删除三剑客。
特别注意:但是它们并不是对象内建的方法。
我们回忆一下,字典取值的方式:dict["key"]。在python中,字典的取值是通过 [] 实现的
class Person(object):def __init__(self, name, age):self.name = nameself.age = agep = Person("baozi", 26)
print(p["name"])
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 9, in <module>print(p["name"])
TypeError: 'Person' object is not subscriptable
我们发现,直接通过 [] 取值的时候,报错了。我们试试加上三剑客
添加__getitem__():
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __getitem__(self, key):print("通过 [] 取值时,调用了我")return "hello,world"p = Person("baozi", 26)
print(p["name"])
# 通过 [] 取值时,调用了我
# hello,world
当我们给我们的对象加上__getitem__方法的时候,没有报错了!!,但是这个时候,不管取什么值,都是返回hello,world的。这也说明了,我们通过 p[“name”]取值的时候,拿到的结果就是 __getitem__()的返回值。
结论:如果一个对象没有实现
__getitem__方法,就不能使用形如 p[“name”] 的格式取值
举一反三:我们删除、赋值也是一样的
# 赋值
p["name"] = "baozi2"
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 14, in <module>p["name"] = "baozi2"
TypeError: 'Person' object does not support item assignment# 删除
del p["name"]
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 15, in <module>del p["name"]
TypeError: 'Person' object does not support item deletion
上面还是一如既往的双双报错。
添加__setitem__()、__delitem__:
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __getitem__(self, key):print("通过 [] 取值时,调用了我")return "hello,world"def __setitem__(self, key, value):print("通过 [] 赋值时,调用了我")return "hello,world"def __delitem__(self, key):print("通过 [] 删除值时,调用了我")return "hello,world"p = Person("baozi", 26)
name = p["name"]
# 通过 [] 取值时,调用了我p["name"] = "baozi2"
# 通过 [] 赋值时,调用了我del p["name"]
# 通过 [] 删除值时,调用了我
搞定收工!上面我们的对象就可以像字典一样的工作了。当然了,上面的方法什么都没有干,这里主要讲用法哈!要学会触类旁通。
6.10 __setattr__() 、 __delattr__()、__getattribute__():
上面我们了解了__getitem__()、__setitem__()、__delitem__()。他们操作属性的方式形如:obj["key"]
在对象中,我们操作属性的方式是形如:obj.key。这种操作方式,取值、赋值、删除也有三剑客。就是__setattr__() 、 __delattr__()、__getattribute__()。
这三个属性是内建的方法,平时没事我们不会去重写它
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __getattribute__(self, key):print("对象通过 . 取值时,调用了我")return "hello,world"def __setattr__(self, key):print("对象赋值时,调用了我")def __delattr__(self, key):print("删除对象属性时,调用了我")p = Person("baozi", 26)
print(p.name)
# 对象通过 . 取值时,调用了我
# hello,worldp.name = "change"
# 对象赋值时,调用了我del p.name
# 删除对象属性时,调用了我
6.11 __getattr__
我们这个方法跟 __getattribute__()非常类似。可能有小伙伴可能会把它当成取值的时候,调用的函数了。
不过确实是取值的时候会调用它,但是有个条件:只有当访问不存在的属性的时候,才会调用它
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __getattr__(self, key):return "hello,world"p = Person("baozi", 26)
print(p.cname) # 访问不存在的属性,调用 __getattr__
# hello,world
而 __getattribute__ 在访问任意属性时都会被调用。
7. 内置特殊属性
7.1 __slots__
使用这个特性可以限制class的属性,比如,只允许对 Person 实例添加name和age属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性:
没限制前:
class Person(object):def __init__(self, name, age):self.name = nameself.age = agep = Person("test", 26)
p.sports = "篮球、足球" # 在这里我们给对象新增了一个属性:sports
print(p.sports)
# 篮球、足球
限制后:
class Person(object):__slots__ = ("name", "age")def __init__(self, name, age):self.name = nameself.age = agep = Person("test", 26)
p.sports = "篮球、足球"
print(p.sports)
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 12, in <module>p.sports = "篮球、足球"
AttributeError: 'Person' object has no attribute 'sports'
抛出了异常, 因为使用了__slots__属性,只能添加 name 和 age 两个属性
需要注意的是
__slots__的限定只对当前类的对象生效,对子类并不起任何作用。
- 这个属性功能看起来很鸡肋啊,那他到底有什么用呢?
省内存,提升属性的查找速度。通常用在ORM的场景中,因为这些项目中存在特别多大量创建实例的操作,使用 __slots__ 会明显减少内存的使用,提升速度。并且随着实例数目的增加,其效果会更加显著。
- 它为什么可以节省内存空间呢?
通常情况下,我们类中的属性是存在 __dict__中,它是一个哈希表结构,并且python的动态性,意味着需要划分更多的内存去保证我们动态的去增减类的属性。但是使用__slots__属性后,编译时期就可以预先知道这个类具有什么属性,以分配固定的空间来存储已知的属性。
尽管
__slots__可以节省内存空间,提高属性的访问速度,但也存在局限性和副作用,在使用前,我们需要根据我们的业务实例规模来确定。
7.2 __dict__
列出类或对象中的所有成员!
这个属性,我们只看名字就应该能联想到什么了。没错,就是我们字典的结构。
在python的类中,主要是通过字典来存储类与对象的属性。通过__dict__属性,我们可以获得类中包含的属性字典。
class Person(object):def __init__(self, name, age):self.name = nameself.age = agep = Person("baozi", 26)
print(Person.__dict__) # 一个包含所有类属性的字典
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x7f9dd88b49d8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>,'__doc__': None
}print(p.__dict__) # 一个包含实例对象所有属性的字典
{'name': 'baozi', 'age': 26}
7.3 __doc__
返回类的注释描述信息
class Person(object):passp = Person()
print(p.__doc__)
# None
class Person(object):"""这是一个类的注释""" # 就是返回这里的注释描述信息passp = Person()
print(p.__doc__)
# 这是一个类的注释
7.4 __class__
返回当前对象是哪个类的实例
class Person(object):passp = Person()
print(p.__class__)
# <class '__main__.Person'>
7.5 __module__
返回当前操作的对象在属于哪个模块
class Person(object):passp = Person()
print(p.__module__)
# __main__
8. 内置装饰器
8.1 @property
通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
修饰方法,使方法可以像属性一样访问。
未加装饰器:
class Person(object):def __init__(self, name, age):self._name = nameself._age = agedef name(self):return self._namep = Person("baozi", 26)
print(p.name)
# <bound method Person.name of <__main__.Person object at 0x7fb728643dd8>>print(p.name()) # 没加装饰器,必须调用函数
# baozi
加装饰器:
class Person(object):def __init__(self, name, age):self._name = nameself._age = age@propertydef name(self):return self._namep = Person("baozi", 26)
print(p.name) # 加了装饰器,像访问属性一样,直接访问方法,不用再加()调用
# baozi
通过这个装饰器,我们可以像访问属性一样,直接访问方法。
那么,这个装饰器有什么用处呢?那我直接 p.name()不行吗?也能实现我的需求啊
确实是这样的。但是从代码可读性而言,我们想访问对象的属性,使用p.name()肯定是没有 p.name这么直观的。
他的使用场景是:我们想访问对象属性,又不想属性被修改的时候,就可以使用这个装饰器。
拓展一下:如果,我想改年龄,并且年龄需要一些限制条件该怎么办呢?
class Person(object):def __init__(self, name, age):self._name = nameself._age = agedef set_age(self, age):if age <= 0:raise ValueError('age must be greater than zero')self._age = agedef get_age(self):return self._age
有问题吗?没有问题,那我能不能通过刚才那个装饰器来玩呢?也可以
class Person(object):def __init__(self, name, age):self._name = nameself._age = age@propertydef age(self):return self._age@age.setterdef age(self, age):if age <= 0:raise ValueError('age must be greater than zero')self._age = age
看到这里,小伙伴可能会有点疑惑了?@age.setter这又是何方神圣?怎么蹦出来的?它也是一个装饰器。这个装饰器在属性赋值的时候会被调用。
@*.setter装饰器必须在@property的后面,且两个被修饰的属性(函数)名称必须保持一致。*即为函数名
使用这两个装饰器,我们就可以做很多事情了。比如:实现密码的密文存储和明文输出、修改属性前判断是否满足条件等等。
为两个同名函数打上@*.setter装饰器和@property装饰器后:
- 当把类方法作为属性赋值时会触发@*.setter对应的函数
- 当把类方法作为属性读取时会触发@property对应的函数
8.2 @staticmethod
将类中的方法装饰为静态方法,即类不需要创建实例的情况下,可以通过类名直接引用。
class Person(object):def __init__(self, name, age):self._name = nameself._age = age# 此方法只能是类的实例调用def talk(self):print(f"name is {self._name} age is {self._age}")# 此方法没有就像普通的函数一样,直接通过 Person.talk()就可以直接调用@staticmethoddef static_talk(name, age): # 这里无需再传递self,函数不用再访问类print(f"name is {name} age is {age}")
p = Person("baozi", 26) # 正常p.static_talk("baozi", 26) # 报错,该方法是个静态方法,不能通过实例访问
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 14, in <module>p.static_talk("baozi", 60)
TypeError: static_talk() takes 2 positional arguments but 3 were givenPerson.static_talk("baozi", 60) # 正常Person.talk() # 报错,这个方法没有被修饰,只能被实例访问,不能被类访问
Traceback (most recent call last):File "/app/util-python/python-module/obj.py", line 15, in <module>Person.talk()
TypeError: talk() missing 1 required positional argument: 'self'
8.3 @classmethod
这个装饰器修饰的方法是类方法,而不是实例方法。这句话是什么意思呢?我们常规定义的方法,都属于实例方法,必须要先创建实例以后,才能调用。但是类方法,无需实例化就可以访问。
类方法的第一个参数是类本身,而不是实例
class Person(object):def __init__(self, name, age):self._name = nameself._age = age@classmethoddef static_talk(cls, name, age):print(f"name is {name} age is {age}")Person.static_talk("baozi", 60)
怎么看起来跟我们的 @staticmethod 功能一样呢?其实注意细节的同学已经发现了。
我们 @classmethod修饰的函数,多了一个参数 cls,这个参数跟我们的self可不一样,self指的是当前实例,而我们的cls指的是当前的类。
- @classmethod修饰的方法需要通过cls参数传递当前类对象,它可以访问类属性,不能访问实例属性
- @staticmethod修饰的方法定义与普通函数是一样的,它不可以访问类属性,也不能访问实例属性
那这个装饰器又有什么用呢?
网上说的最多的就是用来做实现多构造器。什么叫多构造器呢?
class Person(object):def __init__(self, age):self._age = age@classmethoddef init_18_age_person_instance(cls): # 这是一个类方法。这个方法只创建年龄为18岁的的对象。age = 18return cls(age)@classmethoddef init_30_age_person_instance(cls): # 这是一个类方法。这个方法只创建年龄为30岁的的对象。age = 30return cls(age)p = Person(18) # 创建了一个实例,属性age = 18p_18 = Person.init_18_age_person_instance() # 这里也创建了一个实例,属性age = 18p_30 = Person.init_30_age_person_instance() # 这里也创建了一个实例,属性age = 30
当然我这里场景使用得不是很恰当,只是为了简单说明它的功能。通过这个函数,可以模拟出多构造器。具体的业务场景需要你们多多去挖掘。
9. 尝试理解一下一切皆对象
通过我们上面介绍的一些内置方法以后,我们或许对一切皆对象有了更进一步的认识。
此时我们发现,我们的str、int、dict、list、tuple这些其实本质上都是一个对象。针对不同的数据结构,它们自己重写了自己的一套内置方法来实现不同的功能。
比如字典 dict[“key”] 的取值方式就是实现了我们之前介绍的:__setitem__、__getitem__…
比如我们的字符串"hello,world",它不是一个静态的字符串,他也是一个对象,他也有很多内置方法,我们能看到"hello,world",是因为它实现了__str__()
读到这里,相信有一些小伙伴可能已经有所感悟,相信只要永远秉持着这个理念去写代码。你们一定会突飞猛进的。
关于Python学习指南
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、自动化办公等学习教程。带你从零基础系统性的学好Python!
👉Python所有方向的学习路线👈
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(全套教程文末领取)

👉Python学习视频600合集👈
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

温馨提示:篇幅有限,已打包文件夹,获取方式在:文末
👉Python70个实战练手案例&源码👈
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

👉Python大厂面试资料👈
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。


👉Python副业兼职路线&方法👈
学好 Python 不论是就业还是做副业赚钱都不错,但要学会兼职接单还是要有一个学习规划。

👉 这份完整版的Python全套学习资料已经上传,朋友们如果需要可以扫描下方CSDN官方认证二维码或者点击链接免费领取【保证100%免费】
点击免费领取《CSDN大礼包》:Python入门到进阶资料 & 实战源码 & 兼职接单方法 安全链接免费领取

相关文章:
一文入门Python面向对象编程(干货满满)
在开始之前,我一直企图找到一个通俗直观的例子来介绍面向对象。找来找去,发现什么都可以是面向对象,什么又都不是面向对象。后来我发现,人类认识社会的方式更多的就是面向对象的方式。“物以类聚、人以群分”,这句话好…...
qiankun: 关于ElementUI字体图标加载不出来的问题
问题描述: 子应用使用的是vueelementUI,在项目main.js中需要引入elementUI的样式文件。elementUI的样式文件中有字体文件的引用,是以相对路径的形式写在css文件中的, 本来独立部署项目访问是没问题的,问题出现在以qi…...
【智能家居】四、网络服务器线程控制功能点
网络控制 网络线程控制功能点代码 inputCommand.h(输入控制指令)socketControl.c(socket网络控制指令)main.c(主函数)编译运行结果 网络控制 Linux网络编程 “网络控制”(Network Control&a…...
localForage使用 IndexedDB / WebSQL存储
一、什么是 localForage 当我们的存储量比较大的时候,我们一定会想到我们的 indexedDB,让我们在浏览器中也可以 使用数据库这种形式来玩转本地化存储,然而 indexedDB 的使用是比较繁琐而复杂的, 有一定的学习成本,但 …...
Hdoop学习笔记(HDP)-Part.03 资源规划
目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …...
SQL -高阶3
zstarling 字符串拼接与类型转换最大,最小值,提取日期部分的数值日期截断 字符串拼接与类型转换 新语法SQL delete from public.basiclaw_qr_staff_ac ct where batch_date || data_dt || :: date and biz_line || biz_line || ;详解 该 SQL 语句…...
HarmonyOS4.0系列——03、声明式UI、链式编程、事件方法、以及自定义组件简单案例
HarmonyOS4.0系列——03、声明式UI、链式编程、事件方法、以及自定义组件简单案例 声明式 UI ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑。 如果组件的接口定义没有包…...
播放器开发(六):音频帧处理并用SDL播放
目录 学习课题:逐步构建开发播放器【QT5 FFmpeg6 SDL2】 步骤 AudioOutPut模块 1、初始化【分配缓存、读取信息】 2、开始线程工作【从队列读帧->重采样->SDL回调->写入音频播放数据->SDL进行播放】 主要代码 分配缓存 // 对于样本队列 av_audio_…...
Qt 问题记录
问题记录 运行时出现的问题 运行出现的warning QWidget::repaint: Recursive repaint detected在paintEvent中使用painter绘制了线段、图片,移动了QWidget,加入了下面代码导致的 QApplication::processEvents();屏蔽后没有出现该warning QApplicati…...
Go 语言真正有什么用处?
在其十几年的发展过程中,Google 的Go 编程语言已经从 alpha 极客的好奇心发展成为世界上一些最重要的云原生软件项目背后经过考验的编程语言。 为什么Docker、Kubernetes等项目的开发者会选择 Go ?Go 的定义特征是什么?它与其他编程语言有何…...
贪心 55. 跳跃游戏 45.跳跃游戏 II
55. 跳跃游戏 题目: 给定非负数组,初始位置在数组第一格,数组值是可以选择的最大跳跃步数,判断能不能达到数组末尾。 示例 1: * 输入: [2,3,1,1,4] * 输出: true * 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1,…...
为XiunoBBS4.0开启redis缓存且支持密码验证
修改模块文件1 xiunoPHP/cache_redis.class.php: <?phpclass cache_redis {public $conf array();public $link NULL;public $cachepre ;public $errno 0;public $errstr ;public function __construct($conf array()) {if(!extension_loaded(Redis)) {return $thi…...
手把手教你写一个Shell脚本部署你的服务
我们都知道,在开发的过程中,有很多部署自己微服务的方式,其中有各种各样的不同操作,比如使用 docker 打包为镜像的方式,还有基础使用 jar 包的方式进行部署,但是呢?使用 jar 包部署,…...
银行数字化产品方案
在互联网及金融科技公司快速发展的时代背景下,银行客户普遍都意识到了自己在客户体验、客户洞察、产品服务方面受到的来自互联网的挑战 。为了更好地面对各方面的挑战,传统的业务模式必须革新。传统银行都在积极进行数字化转型。同时,也要面对…...
C# datagridview控件 绑定数据库中表中数据的方式-3
1.如下图所示,为数据库中的一张表结构,注意该表中共有11个字段 2.首先在窗体后台代码中拖入一个datagridview控件,并在窗体加载时,给datagridview控件添加列,添加的方式如下所示:请注意,每个列…...
Amazon CodeWhisperer 正式发布可免费供个人使用
文章作者:sunny 亚马逊云科技日前推出了实时 AI 编程助手 Amazon CodeWhisperer,包括个人套餐和专业套餐,所有开发人员均可免费使用个人套餐。Amazon CodeWhisperer 让开发人员能够保持专注、高效,帮助他们快速、安全地编写代码&a…...
el-table根据返回数据回显选择复选框
接口给你返回一个集合,然后如果这个集合里面的status2,就把这一行的复选框给选中 注意: 绑定的ref :row-key"getRowKeys" this.$refs.multiTableInst.toggleRowSelection(this.list[i], true); <el-table :data"list"…...
代码随想录算法训练营第四十二天 _ 动态规划_01背包问题。
学习目标: 动态规划五部曲: ① 确定dp[i]的含义 ② 求递推公式 ③ dp数组如何初始化 ④ 确定遍历顺序 ⑤ 打印递归数组 ---- 调试 引用自代码随想录! 60天训练营打卡计划! 学习内容: 二维数组处理01背包问题 听起来…...
会话 cookie 及隐私的那些事
什么是会话 Cookie? 会话 Cookie 的概念非常简单。 会话 Cookie,也称为临时 Cookie 或内存 Cookie,是网站在浏览会话期间存储在用户计算机或设备上的小数据片段。 它是由网站生成并由您的浏览器存储和使用的多种 Cookie 之一。 常规 Cookie 或“持久”Cookie 是通常在您的…...
前端知识笔记(二十九)———MySQL通配符和正则表达式
一、通配符 1.% 匹配0,1,多个字符,但不匹配NULL 2._ 匹配单个字符 3.[charlist] 匹配字符列中的任何单一字符 4.[^charlist] 或 [!charlist] 匹配不在字符列中的任何单一字符 二、正则表达式 通配符的LIKE替换为REGEXP LIKE 匹配整个列&…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
