Python面对对象编程
author:Once Day date:2022年2月20日
本文档在于总结相关内容,零散的知识难以记忆学习。
本文档基于windows平台。
全系列文档查看:python基础_CSDN博客。
1.简介
面向对象编程:Object Oriented Programming,简称OOP,是一种程序设计方法。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的方法。Python就是一种面向对象的语言,支持面向对象编程,在其内部,一切都被视作对象。
概念及术语:
- 类(Class): 用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
- 实例:也称对象。通过类定义的初始化方法,赋予具体的值,成为一个"有血有肉的实体"。
- 实例化:创建类的实例的过程或操作。
- 实例变量:定义在实例中的变量,只作用于当前实例。
- 类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
- 数据成员:类变量、实例变量、方法、类方法、静态方法和属性等的统称。
- 方法:类中定义的函数。
- 静态方法:不需要实例化就可以由类执行的方法
- 类方法:类方法是将类本身作为对象进行操作的方法。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对父类的方法进行改写,这个过程也称override。
- 封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制
- 继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
- 多态:根据对象类型的不同以不同的方式进行处理。
2.类和实例
详情参考:python 类和实例 - 刘江的python教程 (liujiangblog.com)
2.1 创建一个简单的类
class 类名(父类列表):
xxxxx#类变量
def xxx(self):
self.xxx#类实例变量
类名通常采用驼峰式命名方式。Python采用多继承机制,一个类可以同时继承多个父类(也叫基类、超类),继承的基类有先后顺序,写在类名后的圆括号里。
即使列表为空,也继承了object类,是Python3中所有类的基类。
2.2 __init__
实例初始化
可以通过调用类的实例化方法(有的语言中也叫初始化方法或构造函数)来创建一个类的实例。
任何一个类中,名字为__init__
的方法就是类的实例化方法,具有__init__
方法的类在实例化的时候,会自动调用该方法,并传递对应的参数。
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
s1=Student('xxx',15)
2.3 实例变量和类变量
实例变量指的是实例本身拥有的变量。每个实例的变量在内存中都不一样。
如下:
class user:
game='射击游戏'#类变量
def __init__(self,name):
self.name=name#类实例变量
每个实例的实例变量名虽然一样,但他们保存的值却是各自独立的。
类变量是所有实例公有的变量,每一个实例都可以访问、修改类变量。
注意,python是从类实例变量---->类变量的方向查找变量。
-
如果使用"实例名.类变量名“访问类变量,不能赋值,否则将创建一个新的实例变量。
-
应用“类名.类变量名”访问类变量
-
应用“实例名.实例变量名”访问实例变量
2.4 实例方法、静态方法、类方法
- 类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。
- 静态方法由类调用,无默认参数。在方法定义上方加上@staticmethod,就成为静态方法。建议只使用类名.静态方法的调用方式。
- 类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。建议只使用类名.类方法的调用方式。
2.5 类的内存视图
类、类的所有方法以及类变量在内存中只有一份,所有的实例共享它们。而每一个实例都在内存中独立的保存自己和自己的实例变量。
创建实例时,实例中除了封装诸如name和age的实例变量之外,还会保存一个类对象指针,该值指向实例所属的类的地址。因此,实例可以寻找到自己的类,并进行相关调用,而类无法寻找到自己的某个实例。
3.封装、继承、多态
推荐阅读:python 封装、继承和多态 - 刘江的python教程 (liujiangblog.com)
3.1 封装
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正是由于封装机制,程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。
class 类名(父类列表):
xxxxx#类变量
def xxx(self):
self.xxx#类实例变量
上面把变量封装在类里面,外部就不能随便访问了。
3.2 继承
继承机制实现了代码的复用,多个类公用的代码部分可以只在一个类中提供,而其他类只需要继承这个类即可。
定义一个新类的时候,新的类称为子类(Subclass),而被继承的类称为基类、父类或超类(Base class、Super class)。
继承最大的好处是子类获得了父类的全部变量和方法的同时,又可以根据需要进行修改、拓展。
Python支持多父类的继承机制,搜索时按从左到右搜索,一旦找到直接调用。
子类在调用某个方法或变量的时候:
- 首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
- 根据父类定义中的顺序(左->右),以深度优先的方式逐一查找父类!
3.3 super()函数,强制调用父类的成员
在子类中如果有与父类同名的成员,那就会覆盖掉父类里的成员。
使用super()函数来强调使用父类方法。
语法:super(子类名, self).方法名()
:
class A:
def __init__(self, name):
self.name = name
class B(A):
def __init__(self, name, age):
super(B, self).__init__(name=name)
self.age = age
3.4 多态
# 这个函数接收一个animal参数,并调用它的kind方法
def show_kind(animal):
animal.kind()
只要传入的animal有一个kind方法,就能正确调用。
动态语言调用实例方法时不检查类型,只要方法存在,参数正确,就可以调用。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
4.成员保护和访问限制
推荐阅读:python 成员保护和访问限制 - 刘江的python教程 (liujiangblog.com)
4.1 __成员
,私有成员(private)
私有成员只能在类的内部访问,外部无法访问。
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
obj = People("jack", 18)
obj.__name
发生异常: AttributeError
'People' object has no attribute '__name'
File "E:\django\mysite\text.py", line 16, in <module>
obj.__name
如果外部想访问私有成员,则需要通过类里面的方法。
注意:本质上Python解释器对外把__age
变量改成了_People__age
,也就是_类名__age
。因此,可以通过_ People__age
在类的外部访问__age
变量。
而像_name_
这种属于建议的私有成员。__name__
是特殊成员。
5.@property装饰器
推荐阅读:python @property装饰器 - 刘江的python教程 (liujiangblog.com)
Python内置的@property
装饰器可以把类的方法伪装成属性调用的方式。
将一个方法伪装成为属性后,就不再使用圆括号的调用方式。而是类似变量的赋值、获取和删除方法。
- @property 提供get返回值方法,只能有self参数。
- @xxx.setter 提供赋值功能
- @xxx.delete 提供删除功能
如果不定义setter方法,该属性就只能只读了。
使用方法如下:
class People:
.......
@property
def age(self):
return self.__age
@age.setter #注意age为属性名称
def age(self, age):
......
@age.deleter
def age(self):
print("删除年龄数据!")
或者使用下面这种方式:
class People:
......
def get_age(self):
......
def set_age(self, age):
......
def del_age(self):
......
#下面使用内置函数
age = property(get_age, set_age, del_age, "年龄")
6.特殊成员和魔法方法
名称 | 描述 |
---|---|
__init__ | 构造函数,在生成对象时调用 |
__del__ | 析构函数,释放对象时使用 |
__repr__ | 打印,转换 |
__setitem__ | 按照索引赋值 |
__getitem__ | 按照索引获取值 |
__len__ | 获得长度 |
__cmp__ | 比较运算 |
__call__ | 调用 |
__add__ | 加运算 |
__sub__ | 减运算 |
__mul__ | 乘运算 |
__div__ | 除运算 |
__mod__ | 求余运算 |
__pow__ | 幂 |
6.1 __doc__
说明性文档和信息。Python自建,无需自定义
class Foo:
""" 描述类信息,可被自动收集 """
6.2 __init__()
实例化方法,通过类创建实例时,自动触发执行
6.3 __module__
和 __class__
当前操作的对象所属于的模块和对象,python自建
6.4 __del__()
析构方法,当对象在内存中被释放时,自动触发此方法。
6.5 __call__()
当实例对象被调用时执行的方法
构造方法的执行是由类加括号执行的,即:对象 = 类名()
,而对于__call__()
方法,是由对象后加括号触发的,即:对象()
或者 类()()
6.6 __dict__
列出类或对象中的所有成员,python自建
6.7 __str__()
打印对象的默认输出
如果一个类中定义了__str__()
方法,那么在打印对象时,默认输出该方法的返回值。
6.8 __getitem__()
、__setitem__()
、__delitem__()
取值、赋值、删除
Python中,标识符后面加圆括号,通常代表执行或调用方法的意思。而在标识符后面加中括号[],通常代表取值的意思。Python设计了__getitem__()
、__setitem__()
、__delitem__()
这三个特殊成员,用于执行与中括号有关的动作。它们分别表示取值、赋值、删除数据。
- a = 标识符[],执行__getitem__方法
- 标识符[] = a ,执行__setitem__方法
- del 标识符[],执行__delitem__方法
6.9 __iter__()
迭代器方法
列表、字典、元组之所以可以进行for循环,是因为其内部定义了 __iter__()
这个方法。
如果用户想让自定义的类的对象可以被迭代,那么就需要在类中定义这个方法,并且让该方法的返回值是一个可迭代的对象。当在代码中利用for循环遍历对象时,就会调用类的这个__iter__()
方法。
可在代码中使用iter生成迭代器或者使用yield生成迭代函数。
6.10 __len__()
获取长度
在Python中,如果你调用内置的len()函数试图获取一个对象的长度,在后台,其实是去调用该对象的__len__()
方法。
6.11 __repr__()
返回程序开发者看到的字符串
这个方法的作用和__str__()
很像,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。通常两者代码一样。
6.12 __author__
代表作者信息
6.13 __slots__
限制实例的变量
Python作为一种动态语言,可以在类定义完成和实例化后,给类或者对象继续添加随意个数或者任意类型的变量或方法,这是动态语言的特性。
可以使__slots__
限制实例的变量,比如,只允许Foo的实例添加name和age属性。
class Foo:
__slots__ = ("name", "age")
pass
__slots__
定义的属性仅对当前类的实例起作用,对继承了它的子类是不起作用的。
7.reflect反射
反射就是通过字符串来调用同名的函数。
7.1 getattr(类,str) 获取函数
通过getattr()函数,从commons模块里,查找到和inp字符串“外形”相同的函数名,并将其返回。
func = getattr(commons,str)
func()
getattr()函数的使用方法:接收2个参数,前面的是一个类或者模块,后面的是一个字符串
7.2 hasattr(类,str) 判断是否有这个属性
可以判断commons中是否具有某个成员,返回True或False。
7.3 delattr(类,str) 删除属性
delattr(x, ‘y’) is equivalent to ``del x.y’’
7.4 setattr(类,str,value)
setattr(x, ‘y’, v) is equivalent to ``x.y = v’’