从冰箱装大象到女娲造人,带你彻底吃透Python面向对象编程

宝剑锋从磨砺出,梅花香自苦寒来。这篇文章主要讲述从冰箱装大象到女娲造人,带你彻底吃透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): """排水""" passdef wash(self): """洗涤""" pass

a = "大象" 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 面向对象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 = name self.gender = genderdef talk(self): print(f"我的名字是:{self.name},我的性别是:{self.gender}")

> > > xiaoming = Person("小明", "男")# 创建一个名叫小明的男孩对象 # 当创建对象的时候,会自动执行这个函数> > > print(xiaoming.name) # 小明> > > xiaoming.talk() # 我的名字是:小明,我的性别是:男----------------------------------------------------------------------- > > > xiaohong = Person("小红", "女") # 创建一个名叫小红的女孩对象 # 当创建对象的时候,会自动执行这个函数> > > print(xiaohong.name) # 小红> > > xiaoming.talk() # 我的名字是:小红,我的性别是:女-------------------------------------------------------------------------- > > > xiaoli = Person("小李", "女") # 创建一个名叫小李的女孩对象 # 当创建对象的时候,会自动执行这个函数> > > print(xiaoli.name) # 小李> > > xiaoli.talk() # 我的名字是:小李,我的性别是:女

这里,我们发现类的实例化过程跟我们的生孩子有点类似,当我们创建一个对象的时候 xiaoming = Person("小明", "男"),我们名字叫做小明的朋友就产生了,他有自己的名字和性别,这个是他的属性。当我们还想要一个名为小李的小姑娘,我们就再创建一个新的对象即可。
3.4 self是什么?首先,我们要明白self不是一个关键字,在类中,你也可以不用self,你也可以使用其他名字。之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性。
那self这个参数在我们的类中指的是什么呢?
self在类中表示的是对象本身。在类的内部,通过这个参数访问自己内部的属性和方法。
# 在这个类中,self表示一个类范围内的全局变量,在这个类中任何方法中,都能访问self绑定的变量 # 同时也能访问self绑定的函数 class Person(object): def __init__(self, name, gender): self.name = name self.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 多态多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
# 爸爸类 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
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,不能返回其他对象。
6.2 __new__(cls, *args, **kwargs)__new __()__init __()之前被调用,是真正的类构造方法,用于产生实例化对象(空属性)。__new__方法必须返回一个对象
【从冰箱装大象到女娲造人,带你彻底吃透Python面向对象编程】这个方法会产生一个实例化对象,然后我们的实例对象才会调用 __init__()方法进行初始化。
__new__一般很少用于普通的业务场景,更多的用于元类之中,因为可以更底层的处理对象的产生过程。而__init__的使用场景更多。有兴趣的小伙伴们可以多去了解了解这个方法有哪些高级的玩法。这里就不做介绍了。
6.3 __del__(self)析构方法,当对象在内存中被释放时,自动触发此方法,往往用来做“清理善后”的工作。
在我们的工作中,基本不会用到这个方法。所以这里就知道有这样一个概念就行了。同时,python解释器已经帮我们做了垃圾回收与内存管理,我们使用 Python 编程不需要再过度优化内存使用,以避免写出 C++ 风格的代码。
6.4 __call__(self, *args, **kwargs)如果,我们在类中实现了这个方法,那个,这个类的实例就可以被执行,执行的就是这个方法。讲的很拗口,看代码吧
class Person(object):def __init__(self, name, age): self.name = name self.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 = name self.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 = name self.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 = name self.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

6.6 __repr__(self)这个方法的作用和str()很像,这两个函数都是将一个实例转成字符串。但是不同的是,两者的使用场景不同,
  • 其中__str__更加侧重展示。所以当我们print输出给用户或者使用str函数进行类型转化的时候,Python都会默认优先调用__str__函数。
  • __repr__更侧重于这个实例的报告,除了实例当中的内容之外,我们往往还会附上它的类相关的信息,因为这些内容是给开发者看的
class Person(object):def __init__(self, name, age): self.name = name self.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 = name self.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 = name self.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 = name self.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 = name self.age = agedef __getitem__(self, key): print("通过 [] 取值时,调用了我") return "hello,world"p = Person("baozi", 26) print(p["name"]) # 通过 [] 取值时,调用了我 # hello,world

当我们给我们的对象加上__getitem__方法的时候,没有报错了!!,但是这个时候,不管取什么值,都是返回hello,world的。这也说明了,我们通过 p[" name" ]取值的时候,拿到的结果就是 __getitem__()的返回值。
举一反三:我们删除、赋值也是一样的
# 赋值 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 = name self.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 = name self.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 = name self.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 实例添加nameage属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性:
没限制前:
class Person(object):def __init__(self, name, age): self.name = name self.age = agep = Person("test", 26) p.sports = "篮球、足球"# 在这里我们给对象新增了一个属性:sports print(p.sports) # 篮球、足球

限制后:
class Person(object): __slots__ = ("name", "age")def __init__(self, name, age): self.name = name self.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 两个属性
  • 这个属性功能看起来很鸡肋啊,那他到底有什么用呢?
    省内存,提升属性的查找速度。通常用在ORM的场景中,因为这些项目中存在特别多大量创建实例的操作,使用 __slots__ 会明显减少内存的使用,提升速度。并且随着实例数目的增加,其效果会更加显著。
  • 它为什么可以节省内存空间呢?
    通常情况下,我们类中的属性是存在 __dict__中,它是一个哈希表结构,并且python的动态性,意味着需要划分更多的内存去保证我们动态的去增减类的属性。但是使用__slots__属性后,编译时期就可以预先知道这个类具有什么属性,以分配固定的空间来存储已知的属性。
7.2 __dict__列出类或对象中的所有成员!
这个属性,我们只看名字就应该能联想到什么了。没错,就是我们字典的结构。
在python的类中,主要是通过字典来存储类与对象的属性。通过__dict__属性,我们可以获得类中包含的属性字典。
class Person(object):def __init__(self, name, age): self.name = name self.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 = name self._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 = name self._age = age@property def 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 = name self._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 = name self._age = age@property def age(self): return self._age@age.setter def age(self, age): if age < = 0: raise ValueError(age must be greater than zero) self._age = age

看到这里,小伙伴可能会有点疑惑了?@age.setter这又是何方神圣?怎么蹦出来的?它也是一个装饰器。这个装饰器在属性赋值的时候会被调用。
使用这两个装饰器,我们就可以做很多事情了。比如:实现密码的密文存储和明文输出、修改属性前判断是否满足条件等等。
为两个同名函数打上@*.setter装饰器和@property装饰器后:
  • 当把类方法作为属性赋值时会触发@*.setter对应的函数
  • 当把类方法作为属性读取时会触发@property对应的函数
8.2 @staticmethod将类中的方法装饰为静态方法,即类不需要创建实例的情况下,可以通过类名直接引用。
class Person(object): def __init__(self, name, age): self._name = name self._age = age# 此方法只能是类的实例调用 def talk(self): print(f"name is {self._name} age is {self._age}")# 此方法没有就像普通的函数一样,直接通过 Person.talk()就可以直接调用 @staticmethod def 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 given

Person.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 = name self._age = age@classmethod def 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@classmethod def init_18_age_person_instance(cls):# 这是一个类方法。这个方法只创建年龄为18岁的的对象。 age = 18 return cls(age)@classmethod def init_30_age_person_instance(cls):# 这是一个类方法。这个方法只创建年龄为30岁的的对象。 age = 30 return 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面向对象编程

文章图片

关注小编公众号:偷偷学习,卷死他们

    推荐阅读