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 方法重载
方法重载可以在别人的基础上进行二次修改,比如你用了某位大佬写的类,但是对其类中的某个方法想进行修改,比如改进他的方法以适应你的业务要求,那么方法重载就给了你这个机会。比如看下面这个例子:
- 新建一个py文件(
Sheep.py
),假设这是别人写好的类,代码如下:class Sheep: def __init__(self, name): self.name = name def bark(self): print(f'{self.name} 在这里咩~咩~咩~')
- 然后我们现在要修改这个类中的
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 在咩~咩~咩~,咩~咩~咩~