一文入门Python面向对象编程(干货满满)

2023-12-05

在开始之前,我一直企图找到一个通俗直观的例子来介绍面向对象。找来找去,发现什么都可以是面向对象,什么又都不是面向对象。后来我发现,人类认识社会的方式更多的就是面向对象的方式。“物以类聚、人以群分”,这句话好像给我们的面向对象有很好的诠释。会飞的是鸟类,会游的是鱼类。人们总是很会捕捉生活中各种事物的特征,并进行分类。这其实就是一种面向对象的思想。

不同的对象总是有着不同的特征,同一类的对象总是有着相似或者相同的特征

有一句话叫做“一切皆对象“,这就意味着编程要进阶,面向对象是我们绕不过去的坎。

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):
        pass

    def close_door(self):
        pass

class IceBox(Box):
    """冰箱"""

    def ice(self):
        """制冷"""
        pass

class WaterBox(Box):
    """洗衣机"""

    def add_water(self):
        """加水"""
        pass

    def sub_water(self):
        """排水"""
        pass   

    def 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 面向对象

程序员,你有对象吗?没有?那就自己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 = name
        self.gender = gender

    def 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 = name
        self.talk()  # 访问self绑定的方法

    def talk(self):  # 参数为self,这个函数是对象的方法
        print(self.name)

4. 面向对象三大特性

4.1 封装

封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。

概念很拗口,但是思想却很简单。

回到我们的冰箱、洗衣机,他们的共同特征是什么呢?能装东西、能开门、能关门。这些是他们的共性,我们就可以向上封装。把能装东西、关门、开门封装起来。并且给他一个统称叫做:可开关盒子。可开关盒子就是一个类。这个类的所有对象都可以装东西、开门、关门。

封装可以把计算机中的数据跟操作这些数据的方法组装在一起,把他们封装在一个模块中,也就是一个类中。

class Box():
    """盒子类,实现了开门、关门方法"""

    def open_door(self):
        pass

    def close_door(self):
        pass

4.2 继承

继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。

继承的思想也很简单。你有没有想过一个问题,你为什么长得像人,而不像猪?
首先,你爸是人,你妈也是人,你爸妈都有人的模样,你继承他们,就会继承他们的所有这些属性。你一出生就会有人类共有的这些属性。并且你可以对这些属性进行拓展,比如,你爸只会说中文,但是你会说中文、你拓展了这个方法,你还会说英文。

继承简单地说就是一种层次模型,这种层次模型能够被重用。层次结构的上层具有通用性,但是下层结构则具有特殊性。在继承的过程中类则可以从最顶层的部分继承一些方法和变量。类除了可以继承以外同时还能够进行修改或者添加。通过这样的方式能够有效提高工作效率

class Father:

    def talk(self):
        print("我会讲话")

    def breathe(self):
        print("我能呼吸")

class Me(Father):
    pass


me = 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()
    pass

p = 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()
    pass

p = 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
我是弟弟,我讲俄语:Всем привет
我是妹妹,我讲韩语:전 세계 여러분 안녕하세요

多态存在的三个必要条件

  1. 要有继承
  2. 要有重写;
  3. 父类引用指向子类对象。

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 = name

p = Person("test")
print(p.name)
# test

相当于构造函数,我们向类中传递的参数,就在这个函数接受。并且这个方法只能返回None,不能返回其他对象。

但是其实这个方法只是一个伪构造函数,生成对象的过程并不是它来完成的,它只是对生成的实例进行初始化。
举个例子的话:我们把创建实例比作生孩子。这个函数并没有承担妈妈生孩子的责任,而是等妈妈把孩子生出来以后,给这个孩子起了个名字。真正生孩子的是下面的 __new__ 方法。

6.2 __new__(cls, *args, **kwargs)

__new __() __init __() 之前被调用,是真正的类构造方法,用于产生实例化对象(空属性)。 __new__ 方法必须返回一个对象

这个方法会产生一个实例化对象,然后我们的实例对象才会调用 __init__() 方法进行初始化。

__init__ __new__ 区别:

  1. __init__ 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
  2. __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 = name
        self.age = age

p = 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 = age

    def __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 = age

    def __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 26

print(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 = name
        self.age = age

    def __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 26

print(repr(p))
# Person('baozi', 26)

6.7 __eq__

当判断两个对象的值是否相等时,触发此方法

class Person(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        print("判断两个对象是否相等,触发此函数")
        return True

p1 = 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 = age

    def __eq__(self, other):
        return self.__dict__ == other.__dict__


p1 = Person("baozi", 26)
p2 = Person("baozi", 33)
print(p1 == p2)
# False

p2.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 = age

p = 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 = age

    def __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 = name
        self.age = age

    def __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 = age

    def __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,world

p.name = "change"
# 对象赋值时,调用了我

del p.name
# 删除对象属性时,调用了我

6.11 __getattr__

我们这个方法跟 __getattribute__() 非常类似。可能有小伙伴可能会把它当成取值的时候,调用的函数了。

不过确实是取值的时候会调用它,但是有个条件: 只有当访问不存在的属性的时候,才会调用它

class Person(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __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 = name
        self.age = age

p = 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 = age

p = 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 = name
        self.age = age


p = 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):
    pass

p = Person()
print(p.__doc__)
# None
class Person(object):
    """这是一个类的注释"""  # 就是返回这里的注释描述信息
    pass

p = Person()
print(p.__doc__)
# 这是一个类的注释

7.4 __class__

返回当前对象是哪个类的实例

class Person(object):
    pass

p = Person()
print(p.__class__)
# <class '__main__.Person'>

7.5 __module__

返回当前操作的对象在属于哪个模块

class Person(object):
    pass

p = Person()
print(p.__module__)
# __main__

8. 内置装饰器

8.1 @property

通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。

修饰方法,使方法可以像属性一样访问。

未加装饰器:

class Person(object):
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def name(self):
        return self._name

p = 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._name

p = 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 = age

    def set_age(self, age):
        if age <= 0:
            raise ValueError('age must be greater than zero')
        self._age = age

    def 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装饰器后:

  • 当把类方法作为属性赋值时会触发@*.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 = 18

p_18 = Person.init_18_age_person_instance() # 这里也创建了一个实例,属性age = 18

p_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大礼包》: 安全链接免费领取

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一文入门Python面向对象编程(干货满满) 的相关文章

随机推荐

  • 这个Java面试加分项,太牛了!!

    如今在java界 Spring可以说是大行其道 很多大厂选择Spring全家桶作为基础开发框架 无数的研发人员 把Spring看作是最好的java项目 现在的java开发方面 Spring的重要性和影响力是有目共睹的 市场需求摆在这里 大厂
  • 转转前端周刊第九十一期

    转转前端周刊 本刊意在将整理业界精华文章给大家 期望大家一起打开视野 如果你有发现一些精华文章想和更多人分享 可以点击我们的公众号名称 将文章链接和你的解读文案发给我们 我们会对内容进行筛选和审核 保留你的推荐人署名 分享给更多的人 1 爱
  • ES如何提高召回率之【词干提取】

    想要提高召回率就需要尽可能匹配相关的文档 其中一个办法就是在索引阶段对词语分析 分词器 的时候提取词干 搜索的时候也取词干 不取词干 es默认使用的是 标准的分词器 是不会取词干的 但是标准分词器是包含小写转换分词过滤器的 也是可以 提高召
  • 使用Visual Studio创建第一个C代码工程

    文章目录 2019创建C工程 创建C文件 运行 上一节我们使用记事本编辑C代码 在命令行运行文件 这种方式只是作为对编译器的了解 实际的开发中一般使用集成开发环境比较多 因为 集成开发环境 操作比较简单 通常可编辑 具有明显错误的提示图标
  • 豆瓣9.2分,250万Python新手入门的最佳选择!蟒蛇书入门到实践

    在此疾速成长的科技元年 编程就像是许多人通往无限可能世界的门票 而在编程语言的明星阵容中 Python就像是那位独领风 骚的超级巨星 以其简洁易懂的语法和强大的功能 脱颖而出 成为全球最炙手可热的编程语言之一 什么样的书能 异常 靠谱 能在
  • [Ubuntu 20.04] 使用Netplan配置网络静态IP

    Netplan 是一个在 Ubuntu 系统中进行网络配置的工具 它提供了一种简单和统一的方式来管理网络配置 包括网络接口 IP 地址 网关 DNS 设置等 以下是 Netplan 的特点和功能 声明性配置 Netplan 使用 YAML
  • 一站式体验涂鸦云开发

    涂鸦智能近些年通过深耕物联网领域 沉淀了强大的IoT底层技术 稳定全面的平台能力 持续的创新能力和深厚的行业落地经验 成为更多企业实现智能化建设及应用能力打造的最佳合作伙伴 正在携手开发者共同推动万物互联时代的到来 为满足各类软硬件厂商 个
  • 数字信号处理_第4个编程实例(信号的采样与重建)

    配套的讲解视频详见 数字信号处理14 1 模拟信号转换至数字信号的过程 哔哩哔哩 bilibili 数字信号处理14 2 冲激串的傅里叶变换及采样过程的时频域表示 哔哩哔哩 bilibili 数字信号处理14 3 信号重建与采样定理及Mat
  • Failed to resolve org.junit.platform:junit-platform-launcher:1.9.3

    springboot 跑 unit test 的时候 如果报错如题的话 可以更改idea 里的 Settings gt HTTP Proxy 配置为 Auto detect proxy settings
  • Python下实现的RSA加密 解密及签名验证功能

    Python下实现的RSA加密 解密及签名 验证功能示例 文章目录 Python下实现的RSA加密 解密及签名 验证功能示例 1 RSA加密 解密功能的实现 2 RSA签名 验证功能的实现 3 结语 RSA加密算法是一种非对称加密算法 它使
  • 掌握 Go 语言中的循环结构:从基础到高级

    一 if else 分支结构 1 if 条件判断基本写法 package main import fmt func main score 65 if score gt 90 fmt Println A else if score gt 75
  • 【合集】从Java基础到JavaWeb网络开发——Java基础文章合集 & JavaWeb网络开发文章合集

    前言 本篇博客是Java开发的合集文章 内容涵盖了Java基础相关的博客 JavaWeb开发相关的博客 并且给出了小项目的案例 目录 前言 引出 Java基础 1 基本数据类型 2 数组和集合List 3 运算符 4 逻辑控制
  • python+django基于数据可视化的智慧社区小区住户居民出入登记系统平台vue

    慧社区内网平台综合网络空间开发设计要求 目的是将传统管理方式转换为在网上管理 完成智慧社区内网管理的方便快捷 安全性高 交易规范做了保障 目标明确 智慧社区内网平台可以将功能划分为管理员功能和住户功能 1 管理员关键功能包含系统首页 个人中
  • 新华美光辉少许《乡村振兴战略下传统村落文化旅游设计》许少辉瑞科研

    新华美光辉少许 乡村振兴战略下传统村落文化旅游设计 许少辉瑞科研 新华美光辉少许 乡村振兴战略下传统村落文化旅游设计 许少辉瑞科研
  • 六三学社会议《乡村振兴战略下传统村落文化旅游设计》读懂中国辉少许

    六三学社会议 乡村振兴战略下传统村落文化旅游设计 读懂中国辉少许 六三学社会议 乡村振兴战略下传统村落文化旅游设计 读懂中国辉少许
  • python+django图片相册推荐系统可视化大屏vue_sdtwv

    系统的基本要求 1 功能要求 管理人员可以管理自己的个人中心 地区管理 用户管理 图片信息管理 系统管理等进行管理 3 2 性能 可以准确无误的在不同的操作系统中登录到用户或者管理员的相应界面进行轻松的操作 4 3 环境要求 支持不同的操纵
  • 【Qt开发流程】之自定义语法高亮和使用HTML语法

    描述 语法高亮 Syntax Highlighting 是一种在编辑器中突出显示代码语法元素的技术 使其更易于阅读和理解 Qt提供了一个功能齐全的语法高亮框架 支持多种语言和格式 可以自定义颜色和样式 对于使用Qt的开发人员来说 实现语法高
  • nodejs基于vue+微信小程序+python+PHP准妈妈孕期交流互助平台的设计与实现-毕业设计推荐

    在当今高度发达的信息中 信息管理改革已成为一种更加广泛和全面的趋势 为确保中国经济的持续发展 信息时代日益更新 准妈妈孕期交流互助平台仍在蓬勃发展 同时 随着信息社会的快速发展 各种管理系统面临着越来越多的数据需要处理 如何用方便快捷的方式
  • 基于Android的视频资讯APP

    收藏关注不迷路 源码文章末 文章目录 前言 一 项目介绍 二 开发环境 三 功能介绍 四 核心代码 五 效果图 六 文章目录 前言 本基于Android的视频资讯APP是根据当前的实际情况开发的 在系统语言选择上我们使用的Java语言 数据
  • 一文入门Python面向对象编程(干货满满)

    在开始之前 我一直企图找到一个通俗直观的例子来介绍面向对象 找来找去 发现什么都可以是面向对象 什么又都不是面向对象 后来我发现 人类认识社会的方式更多的就是面向对象的方式 物以类聚 人以群分 这句话好像给我们的面向对象有很好的诠释 会飞的