Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)

目录

  • 1. 面向对象
  • 2. 类
    • 2.1 类的概述
    • 2.2 类的定义
    • 2.3 属性和方法
      • 2.3.1 小引
      • 2.3.2 属性
      • 2.3.3 方法
    • 2.4 self参数
  • 3. 特殊方法
  • 4.封装
    • 4.1封装,私有属性,隐藏属性
    • 4.2 getter()与setter()
  • 5. property装饰器
  • 6. 继承
    • 6.1 继承概述 与 object类
    • 6.2 方法重写
    • 6.3 super()方法
    • 6.4 多重继承
  • 7. 多态
  • 8. 属性和方法
    • 8.1 属性
    • 8.2 方法:
      • 8.2.1 实例方法
      • 8.2.2 类方法
      • 8.2.3 静态方法
  • 9.单例模式
    • 9.1 __new()__方法
    • 9.2 单例模式的实现

1. 面向对象

python是一门面型对象的的编程语言,所谓面向对象,简单地来理解就是语言中所有的操作都是通过对象来进行的。
使用面向对象的编程语言,我们关注的是对象,而不注重过程;对于面向对象,一切皆对象。

比如“对于如何把大象放进冰箱这个问题”,在面向对象编程中,我们只需要考虑大象和冰箱两个对象就足够了,然后步骤如下:①打开冰箱。②把大象放进去。③关上冰箱门。至于怎么放进去的,这不是我们关注的重点。
因为冰箱和大象两个对象所持有的属性和方法,或者说是我们通过调用封装好的函数,足以满足我们的需求。甚至python也许就已经存在过了形如:

def 把大象放冰箱() :
      bulabulabula...
      return('已经放进去了。') 

的函数,我们只要找到大象和冰箱两个参数,然后直接调用就可以把问题解决了。而我们不用过于关心这个函数是怎么执行的。

与之相对的另一种编程思想,即面向过程,这个问题在面向过程的编程语言中,我们则会将程序分解为一个一个的步骤,通过对每个步骤的抽象来完成程序。面向过程的方式更符合人类的思维,编写起来比较容易。

面向对象的编程思想,将所有的功能都保存到对应的变量中。在python中,你想使用一个东西,你必须先创建一个这样的对象。要使用某个功能,直接找到对应的对象即可。这种编码方式比较容易阅读,并且易于维护,容易复用。但是编写过程不太符合常规思维,编写相对麻烦。
(比如对于一个第三方库模块导入的函数对象,你如果不了解其功能或使用方法,你的编程就很难继续。同时面向对象编程也是因为这些复杂的函数对象的组合才显得更方便更强大。)

2. 类

2.1 类的概述

python的内置对象并不都能满足我们的需求,所以我们在开发中经常要自定义一些对象。类可以简单理解为是一张图纸,是对象的图纸。在程序中我们需要根据类来创建对象。
比如我们用list()函数创建出一个列表,用tuple()函数创建出一个元组,这些都是在python内置库中已经定义好的内置对象,所以我们才能直接使用。而不能单纯的理解为这是平台的设定。
我们也称对象为类的实例(instance),如果多个对象是通过一个类来创建的,我们就成这些对象是一类对象。
此外,类本身也就是一个对象,类是用来创建对象的对象。

2.2 类的定义

# 语法
class MyClass():  # 定义一个类,类名命名为MyClass,也即创建了一个类对象。
	pass

mc = MyClass()  # 创建一个实例对象(类的实例化)
mc1 = MyClass()

类和对象都是对我们生活中现实事物的抽象

  • isinstance() 用来检测实例对象是否是由给定类对象创建出来的
print(isinstance(mc, MyClass))

输出结果:
在这里插入图片描述

2.3 属性和方法

2.3.1 小引

  • 一个对象可以分为两部分:①属性②方法
  • 在类代码快中,我们可以定义变量和函数:
    变量会成为该类实例的公共属性,所有的该类的实例都可以通过 对象.属性名的形式访问。
    函数会成为该类实例的公共方法,所有的该类的实例都可以通过 对象.方法名的形式访问。

2.3.2 属性

  • 类对象是可以添加属性的,语法为:对象.属性名 = 属性值
  • id和type是每个对象自带的属性,即存储。对象一被创建,就有id和type属性。
print(id(mc), id(mc1))

输出结果如下:
在这里插入图片描述

print(type(mc))

输出结果如下:
在这里插入图片描述

2.3.3 方法

  • 调用方法语法:对象.方法名()
  • 方法调用 和 函数调用 的区别:
    如果是函数调用,调用的时候有几个形参,就会传入几个实参。
    如果是方法调用,默认传递一个参数。所以方法中至少得有一个形参。

2.4 self参数

  • self 在定义的时候需要定义,但是在调用时会自动传入。
  • self 的名字并不是规定死的,但是是约定俗成的。所以一般最好使用self,而不是别的。
  • self总是指调用时类的实例。

现举代码实例如下:

class Person():
    name = '张三'
    age = 18
    identity = '学生'

    def speak(self):  # self就等于任何调用我这个方法的对象本身
        print('你好,我是{},{}岁的{}'.format(self.name, self.age, self.identity))

p1 = Person()

p1.name = "马保国"
p1.age = 65
p1.identity = '老同志'
p1.speak()

输出结果:
Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第1张图片

3. 特殊方法

在类中可以定义一些形如__xxx__的方法,称为特殊方法,也称魔术方法。特殊方法不需要我们调用,会在特定的时候自动调用。

  • 掌握下边这种定义类写法,方可实现在创建类的实例对象的时候直接传入属性。
class Person():
    name = '张三'
    age = 18
    identity = '学生'
    
	# 特殊方法 __init__,类的对象被创建时, __init__就会被调用
    def __init__(self, name, age, identity):  
        self.name = name
        self.age = age
        self.identity = identity
        print('hello')    # 实例对象一被创建,"hello"就被输出。
    # 这样写,再创建类的对象的时候,name和age都将成为必传参数。缺少则 __init__会无法运行,导致报错。
    # 这里不能用不定长参数,那样没有任何意义。
    
    def speak(self):  # self就等于任何调用我这个方法的对象本身
        print('你好,我是{},{}岁的{}'.format(self.name, self.age, self.identity))

p1 = Person("马保国", 65,"老同志")
p1.speak()

在这里插入图片描述

4.封装

4.1封装,私有属性,隐藏属性

封装是面向对象三大特征之一。我们需要一种方式来增强数据的安全性,这种方式就是封装。
封装是指隐藏对象中一些不希望被外部访问到的属性或方法。具体方式为,在属性名前加入一个下划线,表示不可直接访问,但仍可手动访问,只是提醒不可修改;在属性名前加两个下划线表示隐藏属性。

示例:

class Person():
    
    def __init__(self, name, age, identity):  
        self.name = name
        self._age = age
        self.__identity = identity
# 这样写,再创建类的对象的时候,name和age都将成为必传参数。缺少则 __init__会无法运行,导致报错。      # 这里不能用不定长参数,那样没有任何意义。
    
    def speak(self):  # self就等于任何调用我这个方法的对象本身
        print('你好,我是{},{}岁的{}'.format(self.name, self._age, self.__identity))
p1 = Person("马保国", 65,"老同志")
print(p1.name)
print(p1.age)
print(p1.identity)

运行结果如下:
Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第2张图片
我们可以看到,name依然可以直接访问,但是前边加了一个下划线的age和加了两个下划线的identity属性已经不能直接访问了。


  • 对于前边加了一个下划线的属性age,我们使用p1._age的方式依然可以访问age属性的。
    在这里插入图片描述
    但是对于前边加了两个下划线的属性identity,我们却不能再使用p1.__identity来访问了。否则会有如下报错。
    Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第3张图片
  • 但是对于这样的属性,并不是没有办法访问。
    我们可以使用固定语法:_类名__属性名 的形式进行访问。
    如 p1._Person__identity
    (类名前加一个下划线,类名与属性名之间加了两个下划线)
    即:
    在这里插入图片描述
    这样就访问成功了。
  • 该如何解释这一系列情况呢?
    可以这样来理解。
  • 一般情况下,加_的都是私有属性。没有特殊情况下不要修改私有属性。封装就是这样保障数据安全的。这种”不要修改“是一种约定俗成,不意味着不能修改,只是对编程者的一种提醒。毕竟所有的属性,只要你想修改,都还是受人为控制的。
  • 加一个_后,只是相当于传入属性值后,把原先的属性名前加了一个下划线,故可以通过“_属性名”的形式访问。
  • 而对于加了__(两个下划线)的隐藏属性,本质上只不过是Python自动为属性改了一个名字,即 _类名__属性名 。这样我们就常常把它描述为“私有属性不能通过对象来获取,只能在类的内部访问。” (一句常见的概述)但是,上边的例子我们也看到了,使用p1._Person_identity的方式确实可以说是通过对象访问到了属性值。但是这句话本身是没有问题的,之所以这么说,就是我们对于封装这一概念的一种认同与使用,我们约定俗成不乱操作封装过的属性值,以保障数据安全,我们只是不再可以通过原来的对象名字来获取该对象了。
  • 协定开发我们一般使用私有属性(一个下划线),而一般不用隐藏属性。

4.2 getter()与setter()

我们也可以给类提供上getter()和setter()方法
getter() 用于获取对象中指定的属性
setter() 用于设置对象中的属性
示例如下:

class Person():
    
    def __init__(self, name, age, identity):  
        self.name = name
        self.__age = age
        self._identity = identity
    
    # getter方法  提供给你访问这个属性的方法  # 协定开发一般用 私有属性
    def get_identity(self):
        return self._identity
    
    # setter方法  提供给你修改这个属性的方法
    def set_identity(self, identity):
        self._identity = identity
    
    def speak(self):  
        print('你好,我是{},{}岁的{}'.format(self.name, self.__age, self._identity))

# 逐句输入下边代码,观察结果,以感受其用法逻辑。
p1 = Person("马保国", 65,"老同志")
p1.get_identity()
p1.speak()
p1.set_identity('武林高手')
p1.get_identity()
p1.speak()

结果展示如下:
Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第4张图片

  • 增加getter()和setter()方法,可以很好地控制属性是否是只读的。

5. property装饰器

我们可以用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改。
具体示例如下:

class Person():
    
    # 这里,name属性是私有属性,不可读也不可改
    def __init__(self, name):  
        self._name = name 
    
    # getter方法  提供给你访问这个属性的方法
    @property
    def name(self):
        return self._name
    # 这样,name属性就转为了只读,不可改。可以通过相同名称的方法:name来查看name属性。
	
	# 如果想再把不可改的属性改为可改属性,则:
    # setter方法  提供给你修改这个属性的方法
    @name.setter
    def name(self, name):
        self._name = name

# 下边 逐句进行测试
p1 = Person('马保国')
print(p1.name)    # 可以通过p1.name 读取name属性了。
p1.name = '蔡徐坤'  # 可以这样修改p1的name属性了。 
print(p1.name) # name属性已被更改


6. 继承

6.1 继承概述 与 object类

继承也是面向对象的三大特征之一,通过继承我们可以使一个类获取到其他类中的属性和方法。
在定义类的时候,可以在类名后边的括号中指定当前类的父类
继承提高了类的复用性,让类与类之间有了关系,有了这个关系,才有了多态的特性。

# 定义一个父类
class Person(object):  
    def __init__(self, name):  
        self.name = name
# 定义一个子类
class boy(Person):
	def speak(self):
		print('He has a dream.')
# 这就实现了一个简单的类的继承。

# issubclass()方法 用于检测一个类是否是一个类的父类
print(issubclass(boy, Person))    # True
print(issubclass(Person, object))   # True

对于父类,或者说是不继承其他类的独立的类,定义的时候我们约定俗成继承object这个类,object类中有很多高级特性可以使用。不过即使不传入object也没有关系,我们创建类的时候python3已经默认帮我们传入了object。object类是所有类中最大的父类。
对于子类,则必须要在其括号中填入要继承的父类。

6.2 方法重写

  • 方法重写要建立在继承的基础之上。

  • 如果在子类中有和父类同名的方法,则通过子类实例对象区调用方法时,会调用的是子类的方法,而不是父类的方法,这个特点我们称之为方法的重写(覆盖)。

  • 当我们调用一个对象的方法时,会优先在当前对象的类中寻找,有则直接调用,没有则去当前对象的类的父类中寻找,有则调用,没有则继续往上边的父类中寻找,以此类推。直到找到object类,如果还没有就报错了。

可以通过这段示例代码来体会其逻辑:

class A(object):
    name = 'A'

    def test(self):
        print('A.....')

class B(A):
    def test(self):
        print('B,...')

class C(B):
    pass

c = C()
# 逐句测试
c.test()
print(c.name)

6.3 super()方法

  • super这个方法的使用时建立在两个基础之上的
  1. 必须要有父类的继承
  2. 必须要有方法的重写

super()方法可以获取到当前类的父类的方法。且通过super()方法返回对象调用父类方法时,不需要调用self。

class A():    # 默认继承object
    def test(self):
        print('A.....')
class B(A):
    def test(self):
        print('B,...')
    # super的作用: 将被覆盖了的父类方法,重新调用
    def test2(self):
        super().test()

# 进行逐行测试
b = B()
b.test()
b.test2()

结果如下:
Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第5张图片
这样,就既能使用子类中的test方法,也能调用父类中的test方法了。


6.4 多重继承

  • python是支持多重继承的,也就是说我们可以为一个类同时指定多个父类。只要在子类后边的括号里添加多个类即可实现多重继承。
  • 多重继承可以使子类同时拥有多个父类,并且会获取到所有父类中的方法,如果遇到调用父类中的同名方法,则按照括号内传入的顺序,会先在第一个父类中寻找,然后是第二个,第三个…以此类推,前边的会覆盖后便的。
  • 在开发者如果没有特殊要求,应尽量避免使用多重继承。多重继承会使得我们的代码更加复杂。

简要写法示例:

class A():    
    def test(self):
        print('A.....')
class B():
    def test(self):
        print('B.....')
class C(A, B):    # 这样就实现了C类对A类和B类的多重继承。
	def speak(self):
		print('A和B都是我爹。')

# 调用test方法的话,按先后顺序会调用A中的,B中的test()方法会被覆盖。
c = C()
c.test()

输出结果:
Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第6张图片


7. 多态

多态是面向对象三大特性之一。从字面来理解,就是指多种形态。
python中多态的特点:

  1. python中的多态只关心对象的实例方法是否同名,不关心对象所属的类型。
  2. 对象所属的类之间,继承关系也是可有可无的。
  3. 多态可以增加代码外部调用的灵活度,让代码更通用,兼容性比较强。
  4. 多态是调用方法的技巧,不会影响到类的内部设计。

代码示例如下:

class A():    
    def test(self):
        print('A.....')
class B():
    def test(self):
        print('B.....')
class C():
	def test(self):
		print('C.....')

def test(obj):
    obj.test()

a = A()
b = B()
c = C()

a.test()
b.test()
c.test()

Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第7张图片


8. 属性和方法

8.1 属性

  • 类属性: 直接在类中定义的属性是类属性。
    类属性可以通过类或类的示例访问到,但是类属性只能通过类对象来修改,无法通过实例对象修改。
  • 实例属性: 通过示例对象添加的属性属于实例属性。
    实例属性只能通过实例对象来访问和修改,类对象无法访问修改。

示例:

class A():
	num = 0    # 这个属性就是类属性,无法通过实例对象更改    
    def __init__(self, name):
        self.name = name   # 这个属性即是实例属性
        print(self)

8.2 方法:

8.2.1 实例方法

  • 在类的定义中,以self为第一个参数的方法都是实例方法,实例方法调用的时候,python会将调用对象以self传入。
  • 实例方法可以通过实例和类去调用。
    当通过实例调用时,会自动将当前调用对象以self传入。
    当通过类调用时,不会自动传递self,我们必须手动传递self。

8.2.2 类方法

  • 类方法 在类的内容中,以 @classmethod 来修饰的方法就是类方法,类方法的第一个参数是cls,即当前类对象,会被自动传递(这一点类似self)。即可以说:第一个参数是cls的方法是类方法。这也是类方法与实例方法的不同之处。
  • 类方法可以通过类去调用,也可以通过实例调用。

示例:

class A():
    
    def __init__(self, name):
        self.name = name  
        print(self)
    
    def speak(self):  # 实例方法
        print('大家好,我是%s' % self.name)

    @classmethod     # 类方法
    def run(cls):
        print('我会唱、跳、rap、篮球。')
 

a = A('张三') # 创建实例对象
a.speak()  # 实例调用实例方法
A.speak(a)  # 类调用实例方法,需要传入手动传入实例对象
a.run()      # 实例调用类方法
A.run()      # 类调用类方法

8.2.3 静态方法

  • 在类中用**@staticmethod**来修饰的方法属于静态方法。
  • 静态方法不需要指定任何默认参数,静态方法可以通过类和实例调用。
  • 静态方法基本上是一个与当前类无关的方法,只是一个保存在当前类中的函数,是工具方法。

示例如下:

class A():
    @staticmethod
    def static():     # 功能方法,工具方法
        print('我是静态方法')

9.单例模式

9.1 __new()__方法

__new__方法用于创建和返回一个对象。在类准备将自身实例化是调用。

class A():
	def __new__(self):
		print('实例对象创建好了。')
a = A()

在这里插入图片描述

9.2 单例模式的实现

  • 单例模式是一种常用的软件设计模式,也就是说该类只包含一个实例。通过通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
    通常在一些资源管理器中,比如日志记录等。

  • 实现思路:
    当对象不存在时,创建对象;
    当对象存在时,永久返回当前已创建的对象。

代码示例:

class Single(object):
    obj = None

    def __new__(cls, *args, **kwargs):
        if cls.obj is None:
            cls.obj = super().__new__(cls)
            return cls.obj
        else:
            return cls.obj
# 这里也用到了,通过类对象的内部方法来修改类属性obj的思想。

s = Single()
s1 = Single()
print(id(s))
print(id(s1))

Python面向对象专题(面向对象,类,特殊方法,封装,继承,多态,单例模式等)_第8张图片
从结果我们可以看出,s和s1的id相同,即s和s1是同一个对象。
其内在逻辑是这样的:
我们创建s对象的时候,类属性obj默认为None, if条件成立,所以通过父类中的__new__方法,以cls(Single类)为实例对象名,创建了一个父类object的实例对象,然后把这个实例对象赋值给Single类的obj属性。第二次再创建s1实例对象的时候,Single的obj属性就不再是None了,即i条件f不再成立。所以就直接返回非None的cls.obj。


感谢大家的访问与支持,您的支持将是博主坚持更新的最大动力!如果博主的文章对您的学习能够有所帮助,别忘了小手指点一下界面的 收藏+关注。更多精彩内容敬请关注!

你可能感兴趣的:(python基础学习,python,类,面向对象编程,单例模式)