【人生苦短,我学 Python】(14)面向对象、类


Python 所有文章传送门
【Python】所有文章传送门

简述 / 前言

python是一门面向对象的语言,它可以更客观更自然地描述现实世界,方便继承已有或已经完成的工作以及便于维护。

1. 基本概念

  • 类:是对一组具有相同特性的对象的抽象描述。
  • 实例化:是在类的基础上构造对象的过程。
  • 继承:一个子类从父类那里获得已有特性和功能。
  • 多态:不同的对象可以以不同的方式响应相同的消息。

2. 类

2.1 定义

class 类名:
    def __init__(self):		# 初始化类
        pass

    def 方法(self[, 参数]):
        [方法体]

比如下面这个代码就是一个简单的类,其构造了一个羊(Sheep)的类:

class Sheep:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f'{self.name} 在这里咩~咩~咩~')

sheep = Sheep('小羊')	# 创建对象
sheep.bark()			# 调用对象sheep的bark方法

# 输出如下:
# 小羊 在这里咩~咩~咩~

我们可以通过 类名() 来创建对象,通过 对象.方法 调用这个类里面的方法。

2.2 成员

在类里面分为私有成员公有成员

  • 私有成员:不能直接在类的外部访问,一般是在类的内部进行访问和操作,或者在类的外部通过调用对象的公有成员方法来访问。
  • 公有成员:既可以在类的内部进行访问,也可以在外部中使用。

在Python中,以下划线(_)开头的变量名和方法名有特殊的含义:

  • _x (一个下划线):受保护的成员;
  • __x (前面有两个下划线):私有成员(只有类对象自己可以访问,子类对象不能访问,不过可以通过 对象名._类名__x 来访问这个私有成员,因此不存在严格意义上的私有成员);
  • __x__ (前后分别有两个下划线):系统定义的特殊成员。

具体可看下面的代码示例:

class Sheep:
    def __init__(self, name):
        self.__name = name
        self.name = name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('小羊')
# sheep.bark()
print(sheep._Sheep__name)   # 可以通过特殊方法: `对象名._类名__x` 来访问这个私有成员
print(sheep.name)       # 公有成员可以随意访问
print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问

其输出如下:

小羊
小羊
Traceback (most recent call last):
  File "D:\xxx\CSDN.py", line 13, in <module>
    print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问
AttributeError: 'Sheep' object has no attribute '__name'. Did you mean: 'name'?

在上述代码中可以看到用到了构造函数__init__()),下面将介绍几个简单的系统定义的特殊成员。

  • 新建函数__new__ 方法是一个类方法,创建对象时调用,返回当前对象的一个实例,一般无需重载该方法(该方法会在构造函数前被调用)。

  • 构造函数__init__(),在创建对象时被自动调用和执行,一般用来为数据成员设置初值或进行其他必要的初始化工作。如果没有定义构造函数,Python将提供一个默认的构造函数进行必要的工作。

  • 析构函数__del__(),在删除对象和收回对象空间时被自动调用和执行,一般用来释放对象占用的资源。如果没有定义析构函数,Python将提供一个默认的析构函数进行必要的工作。

2.3 属性

属性又分为:实例属性类属性私有属性共有属性。同样属性也分为私有属性共有属性

  • 私有属性:两个下划线开头,但是不以两个下划线结束的属性是私有属性,私有类属性不能直接访问,私有实例属性原则上不能直接访问,但可以通过特殊方法直接访问
  • 公有属性:不是私有属性的属性就是公有属性。

2.3.1 实例属性

实例属性:通过 self.变量名 定义。

class Sheep:
    age = 18
    def __init__(self, name):
        self.__name = name
        self.name = name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('小羊')
# sheep.bark()
print(sheep._Sheep__name)   # 可以通过特殊方法: `对象名._类名__x` 来访问这个私有成员
print(sheep.name)       # 通过对象来访问实例属性
# print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问
print(Sheep.age)        # 通过类来访问类属性

其输出如下:

小羊
小羊
18

上述代码中的 self.name 就是实例属性。

2.3.2 类属性

类属性:类本身的变量。

class Sheep:
    age = 18	# 公有类属性
    def __init__(self, name):
        self.__name = name
        self.name = name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('小羊')
# sheep.bark()
# print(sheep._Sheep__name)   # 可以通过特殊方法: `对象名._类名__x` 来访问这个私有成员
# print(sheep.name)       # 通过对象来访问实例属性
# print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问
print(Sheep.age)        # 通过类来访问类属性
print(sheep.age)        # 因为类属性名和实例属性名不同,所以可以通过对象名访问

其输出如下:

18
18

上述给出的代码中的 age = 18 就是类属性(公有类属性)。因为类属性名和实例属性名不同,所以可以通过对象名访问


但如果是下述代码,则必须通过类名才能正确访问类属性:

class Sheep:
    name = '我是类属性'
    def __init__(self, name):
        self.__name = name
        self.name = name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('我是实例属性')
# sheep.bark()
# print(sheep._Sheep__name)   # 可以通过特殊方法: `对象名._类名__x` 来访问这个私有成员
# print(sheep.name)       # 通过对象来访问实例属性
# print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问
print(Sheep.name)        # 通过类来访问类属性
print(sheep.name)        # 此时对象名访问的同名属性是实例属性,不是类属性

其输出如下:

我是类属性
我是实例属性

但是如果是私有类属性,结果会是什么样呢?

class Sheep:
    __name = '我是类属性(私有类属性)'
    def __init__(self, name):
        self.name = name

    @classmethod
    def getname(cls):
        return cls.__name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('我是实例属性')
# sheep.bark()
# print(sheep._Sheep__name)   # 可以通过特殊方法: `对象名._类名__x` 来访问这个私有成员
# print(sheep.name)       # 通过对象来访问实例属性
# print(sheep.__name)     # 私有成员不能直接通过对象访问,但可以通过第一种方法访问
print(sheep.getname())     # 通过类方法或者静态方法获取私有类属性,下面的两种方法都无法获取私有类属性
# print(Sheep.__name)        # 通过类来访问类属性
# print(sheep.__name)        # 此时对象名访问的同名属性是实例属性,不是类属性

其输出如下:

我是类属性(私有类属性)

可见私有类属性只能在类方法中访问私有类属性!上述代码中用到了装饰器 @classmethod,这会在后面的小节中进行介绍。


类属性的一个好处是所有属于这个类的对象都可以共享类属性,这样我们实现一个功能,比如限制某个类中对象的数量,比如下述代码:

class Sheep:
    SheepName = "羊"
    cnt = 0

    def __new__(cls, *args, **kwargs):  # 该方法在__init__()之前被调用
        if cls.cnt >= 2:
            raise Exception("最多只能有2只" + cls.SheepName)
        else:
            return object.__new__(cls)

    def __init__(self, name):
        self.name = name
        Sheep.cnt += 1

    def bark(self):
        print(f'{self.name} 在这里咩~咩~咩~')

sheep_y = Sheep('小黄')
sheep_b = Sheep('小黑')
sheep_r = Sheep('小红')   # 无法创建此对象,因为只能创建2个对象

其输出如下:

Traceback (most recent call last):
  File "D:\xxx\CSDN.py", line 20, in <module>
    sheep_r = Sheep('小红')
  File "D:\xxx\CSDN.py", line 7, in __new__
    raise Exception("最多只能有2只" + cls.SheepName)
Exception: 最多只能有2只羊

2.4 方法

方法又分为:特殊方法实例方法类方法静态方法

方法同样分为私有方法公有方法特殊方法

  • 特殊方法:以双下划线开始和结束的方法;
  • 私有方法:两个下划线开头,但不以两个下划线结束的方法;
  • 公有方法:不属于上面两种的方法就是共有方法。

2.4.1 特殊方法

此方法在小节 2.2 成员 的最后有介绍,此处不再重复赘述,即:新建函数__new__())、构造函数__init__())、析构函数__del__())。

还有很多特殊方法,可见 Python特殊方法与运算符重载-菜鸟教程 以及 Python入门基础篇 No.82 —— 特殊方法和运算符重载_特殊属性

2.4.2 实例方法

所有实例方法都必须至少有一个名为 self 的参数,并且是方法的第一个形参,self 参数代表当前对象。比如上述代码中的方法 def bark(self) 就是实例方法。

2.4.3 类方法

类方法一般以 cls 作为类方法的第一个参数表示该类自身,在调用类方法时不需要为该参数传递值。定义类方法需要用到装饰器 @classmethod。比如下述代码:

class Sheep:
    __name = '我是类属性(私有类属性)'
    def __init__(self, name):
        self.name = name

    @classmethod
    def getname(cls):
        return cls.__name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('我是实例属性')
print(sheep.getname())     # 通过类方法或者静态方法获取私有类属性

其输出如下:

我是类属性(私有类属性)

2.4.4 静态方法

静态方法可以不接收任何参数。定义静态方法需要用到装饰器 @staticmethod。像上述代码,可以把类方法改成静态方法:

class Sheep:
    __name = '我是类属性(私有类属性)'
    def __init__(self, name):
        self.name = name

    @staticmethod
    def getname():
        return Sheep.__name

    def bark(self):
        print(f'{self.__name} 在这里咩~咩~咩~')

sheep = Sheep('我是实例属性')
print(sheep.getname())     # 通过类方法或者静态方法获取私有类属性

其输出如下:

我是类属性(私有类属性)

2.5 继承

  • 继承是用来实现代码复用的机制,是面向对象程序设计的重要特性之一。Python支持多重继承,即一个派生类可以继承多个基类!
  • 派生类可以继承父类的公有成员,但是不能继承其私有成员。
  • 声明派生类时,必须在其构造函数中调用基类的构造函数,即在构造函数 __init__() 中需要加入 基类名.__init__(self, 参数列表) 或者 super().__init__(参数列表)
  • object 是根类(祖先类)

参考代码(一) 基类名.__init__(self, 参数列表)

class Sheep(object):		# 也可以写成 class Sheep:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f'{self.name} 在这里咩~咩~咩~')

class MySheep(Sheep):
    def __init__(self, name):
        Sheep.__init__(self, name)

sheep = MySheep('小羊')
sheep.bark()    # MySheep类继承了父类Sheep的方法bark()

参考代码(二) super().__init__(参数列表)

class Sheep(object):		# 也可以写成 class Sheep:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f'{self.name} 在这里咩~咩~咩~')

class MySheep(Sheep):
    def __init__(self, name):
        super().__init__(name)

sheep = MySheep('小羊')
sheep.bark()    # MySheep类继承了父类Sheep的方法bark()

上述两个代码的输出一致,输出如下:

小羊 在这里咩~~~

我们还可以通过类的方法 mro() 或类的属性 __mro__ 可以输出其继承的层次关系:

print(MySheep.mro())
print(MySheep.__mro__)

其输出如下:

[<class '__main__.MySheep'>, <class '__main__.Sheep'>, <class 'object'>]
(<class '__main__.MySheep'>, <class '__main__.Sheep'>, <class 'object'>)

2.6 方法重载

方法重载可以在别人的基础上进行二次修改,比如你用了某位大佬写的类,但是对其类中的某个方法想进行修改,比如改进他的方法以适应你的业务要求,那么方法重载就给了你这个机会。比如看下面这个例子:

  1. 新建一个py文件(Sheep.py),假设这是别人写好的类,代码如下:
    class Sheep:
        def __init__(self, name):
            self.name = name
    
        def bark(self):
            print(f'{self.name} 在这里咩~咩~咩~')
    
  2. 然后我们现在要修改这个类中的 bark() 方法,那么我们需要先继承这个类,然后重载其中的 bark() 方法:
    from Sheep import Sheep
    
    
    class MySheep(Sheep):
        def bark(self):
            print(f'{self.name} 在咩~咩~咩~,咩~咩~咩~')
    
    
    sheep = MySheep('小羊')
    sheep.bark()
    
    其输出如下:
    小羊 在咩~~~,咩~~~
    

重载的时候,我们要保证其方法签名一致,即:方法名参数数量参数类型都一样!

当然在python中,由于其自由性,用户也可以不遵守这个规矩,但是这样操作很容易引起其他的问题,可能会导致程序出现错误,因此不建议这种做法,下面也给出这种不遵守规矩的示例代码:

from Sheep import Sheep


class MySheep(Sheep):
    def bark(self, age):
        print(f'{self.name}{age} 在咩~咩~咩~,咩~咩~咩~')


sheep = MySheep('小羊')
sheep.bark(15)

其输出如下:

小羊15 在咩~~~,咩~~~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值