目录
2.2.5 获取数据(__getitem__)、删除数据(__delitem__)、设置数据(__setitem__)
6.2 各种方法的区别(@staticmethod、@classmethod)
1、什么是面向对象
1.1 面向过程
面向过程就是分析出实现需求所需要的步骤,通过函数(方法)一步一步实现这些步骤,接着依次调用即可,不适合迭代。
1.2 面向对象
面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成类(类实例化后才是对象),创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为。
1.3 面向对象的优点
面向对象的编程--迭代更新方便,结构清晰,方便管理,面向对象的编程带来的主要好处之一是代码的重用。
2、类
2.1 什么是类、对象,方法和属性
类:用来描述具有相同属性和方法(能做的事)的对象集合。
对象:具体的某个事物,实实在在的一个例子。
方法:能做的事,对象的行为---类中定义的函数(如:车能载人,对象的描述信息)。
属性:对象的描述信息--变量,(特点、特征)。
举个例子:类(动物)子类(人类)对象(你本人)
2.2 类的定义和使用
2.2.1 类名规范和类的写法
定义的关键字为class,一般首字母大写(大驼峰),如:Person, GoodsInfo,驼峰命名法:UserName,AtmJsrm。
class Atm():
pass
class Atm:
pass
class Atm(object):
pass
2.3 魔术方法
2.3.1 __init__方法:实例初始化
这个方法可以用来对你的对象做一些你希望的初始化任务,__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,它在类的一个对象被建立后,自动运行。.__init__的self就表示__new__返回的实例,__init__对这个实例进行初始化工作,如果实例化对象和本身class不一致,__init__就不执行。
2.3.2 __new__:创建一个实例
这个方法用来创建一个实例,一般情况下都不会重写,父类object有,object创建一个实例。__new__必须要传入一个参数(cls),代表当前类,__new__必须返回一个实例化对象。子类没有定义__new__,就会去找父类的__new__,新式才有__new__。
2.2.3 析构函数(__del__)
在实例释放、销毁的时候自动执行的,通常用于做一些收尾工作, 如关闭一些数据库连接,关闭打开的临时文件。
2.2.4 调用方法(__call__)
是callable对象,类实例化后的对象当做函数来调用的时候自动被调用。
class A:
def __call__(self,name):
print(f"i am A.call ,my name is {name}")
a1 = A()
a1("sc")
2.2.5 获取数据(__getitem__)、删除数据(__delitem__)、设置数据(__setitem__)
a[key]来访问对象,a[1:3]切片时调用__getitem__ 。
删除数据(__delitem__)。
r['e']=213时调用__setitem__ 获取可迭代(__iter__)
2.2.6 其他魔术方法
- __eq__(self, other) 定义了等号的行为,==
- __ne__(self, other) 定义了不等号的行为, !=
- __lt__(self, other) 定义了小于号的行为, <
- __gt__(self, other) 定义了大于等于号的行为, >=
- __add__(self, other) +运算
- __mul__(self, other) *运算
- __len__(self, other) 获得长度
class A:
def __init__(self,num):
self.num = num
def __add__(self, x):
print("this is add")
return self.num + x
a1 = A(4)
print(a1 + 6)
2.2.7 查看类属性和方法 dir
dir(Person):了解Person类里的变量和方法。
2.2.8 内置属性及其功能
- __dict__:类的属性(包含一个字典,由类的数据属性组成)
- __doc__:类的文档字符串
- __name__:类名
- __module__:类定义所在的模块
- __bases__:类的所有父类构成元素
2.3 类的三基本特征
三大特点:封装、继承、多态。
2.3.1 封装(Encapsulation)
在类中对数据的赋值、内部调用对外部用户是透明的。把一些功能的实现细节不对外暴露。
2.3.2 继承(Inheritance)
继承:即一个派生类(derived class)继承基类(base class)的字段和方法,为实现代码的重用, 一个类可以派生出子类,继承也允许把一个派生类的对象作为一个基类对象对待。
2.3.3 多态(Polymorphism)
接口重用, 一个接口,多种实现(重写),多态(Polymorphism)按字面的意思就是“多种状态”,在面向对象语言中,接口的多种不同的实现方式即为多态。)
1.Python里不支持多态:
语法上的多态,不需要额外实现多态的代码,按照多态的严格语法,不属于多态(父类作为参数,传递子类对象)。
2.Python处处是多态:
Python是一种动态语言,崇尚鸭子类型,本身就实现了多态,不关心是什么类型,到底是不是鸭子,只关心行为。
3.其他语言的多态要通过继承来实现
• Python是一种多态语言,崇尚鸭子类型。
• 在程序设计中,鸭子类型是动态类型的一种风格。
• 鸭子模型是指:"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就
可以被称为鸭子。"
• 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
参考文章:
浅谈Python鸭子类型 - 腾讯云开发者社区-腾讯云 (tencent.com)
2.4 super
super()#父子关系时使用,子类访问父类的方法属性,
class Pig(Animal):
count = 0
def __init__(self):
# super().__init__()#父子关系是使用,子类访问父类的方法属性
#相当于在此处调用父类__init__
# self.name = "animal"
# Animal.count += 1
self.name = "pig"
Pig.count += 1
print("初始化pig...")
2.5 self 详解
self只是个形参名,可以随便取不必是self,但是self是约定俗成的,一般写成self。 self代表类的实例,而非类,self不必非写成self,改成this试试。
class Person():
name = "sanchuang"
def info(slef):
print(f"i am {slef.name}")
print(slef)
print(slef.__class__)
p = Person()
p.info() #==> Person.info(p)
print(p.__class__)
底层编译p = Person(),p.info() ==> Person.info(p) p作为参数传给info,传入参数的第一个默认会传入给self,不想要第一个参数变成self使用 @staticmethod 静态方法。
2.6 范例
#封装函数:
class ATM():
money = 50000 #属性
def get_money(self,money): #方法(函数)
print(f"get money .. {money}")
#子函数:
class HunanAtm(ATM):
area = "湖南"
#实例化
a1 = ATM() #对象a1
a2 = ATM() #对象a2
a1.bank = "长沙银行" #添加特殊属性,两个对象的公共属性可以创建一个父类或基类
hunan_a1 = HunanAtm()
print(a1.money,a1.bank)
a1.get_money(1000) #使用某个属性 变量名.属性
print(hunan_a1.money,hunan_a1.area)
2.7 类空间和实例空间
类创建的时候会生成类空间,实例化会产生实例空间,不同实例之间,空间都是独立的 ,实例查找属性方法的时候,会先去实例空间查找,找不到再去类空间,类空间找不到,就去父类空间找,实例可以(通过指针去访问类空间)访问到类以及父类空间,类访问不到到实例空间。
实例空间内有个指针,实例空间通过指针去访问类空间,实例可以访问类属性,但是改变不了类属性。
2.8 面向过程VS面向对象
面向过程编程 (函数):根据操作数据的语句块来实现功能。
面向对象编程 (OOP-Object Oriented Programming):把数据和功能结合起来,用称为对象的东西包裹起来组织程序的方法。
• 面向过程是一件事"该怎么做", 着重于做什么。
• 面向对象是一件事"该让谁来做",着重于谁去做。
Python面向对象有两种关系
1.继承关系 object是所有类的父类,是最顶层的类
2.创建关系 实例与类的关系,type是最顶层的类
type创建了object,type又继承了object, object = type(object)
2.9 单例模式
2.9.1 定义
单例是一种设计模式,应用该模式的类只会生成一个实例。单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。
2.9.2 例子
class Danli:
instance = None
def __new__(cls, *args, **kwargs):
if cls.instance is None:#if not cls.instance
cls.instance = super().__new__(cls)#super相当于object
return cls.instance
dan1 =Danli()
print(dan1)
dan2 = Danli()
print(dan2)
# 通过重写new方法,把实例对象放到类属性保存,每次调用都是同一个
3、经典类与新式类
3.1 定义
在Python 2.x及以前的版本中,由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”。
不由任意内置类型派生出的类,则称之为“经典类”。
“新式类”和“经典类”的区分在Python 3.x之后就已经不存在,在Python 3.x之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承object类型),即所有的类都是“新式类”。
建议使用新式类。
3.2 区别
3.2.1 经典类与新式类在类型上的区别
经典类:
所有的类都是classobj类型,而类的实例都是instance类型,类与实例只有通过__class__属性进行关联。
新式类:
类实例的类型是这个实例所创建自的类(通常是和类实例的__class__相同)。
3.2.2 最大区别
经典类与新式类的继承顺序:
1.经典类:深度优先
2.新式类:c3算法
Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和c3算法。
深度优先一条路走到黑,走完再去广度(另一条路)。
3.2.3 经典类与新式类的继承继承原理
MRO(Method Resolution Order):方法解析顺序
MRO是在Python类的多重继承中,解决当定义了多个同名的方法/属性时,为避免产生歧义,保证用户找到正确的对象所实现的一种算法。对于你定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
Python2.2以前的版本:经典类(classic class)时代,MRO的方法为DFS(深度优先搜索(子节点顺序:从左到右)),Python2.2版本:新式类(new-style class)诞生,这时有两种MRO的方法,经典类MRO为DFS(深度优先搜索(子节点顺序:从左到右))。
新式类MRO为BFS(广度优先搜索(子节点顺序:从左到右)),Python2.3到Python2.7:经典类、新式类和平发展,从Python2.3开始新式类的MRO取而代之的是C3算法,Python3到至今:新式类一统江湖,Python3开始就只存在新式类了,采用的MRO也依旧是C3算法。
3算法:
1.首先将自身类加入本序列,然后对继承序列的元素依次判断,
2.若某个元素不在其它序列,或者所有继承序列的第一个,那么就把这个元素提取到本序列
3.条件:不在所有此类继承类的其他序列,且是所有继承序列的第一个
C3算法:它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
• 子类会先于父类被检查
• 多个父类会根据它们在列表中的顺序被检查
• 如果对下一个类存在两个合法的选择,选择第一个父类
4、元类
元类就是用来创建类的类。
创建类:
MyClass = type('MyClass', (), {}),type是最上层的元类,所有的类基本上都是有type创建的
#第一个是参数 类名
#第二个参数元组 显示继承关系
#第三个参数字典 设置属性和方法
# 元类一般用来拦截类的创建
#自定义元类
class MyMate(type):
def __new__(cls, name, bases, attrs):
if "foo" not in attrs:#实例化添加属性
raise TypeError("必须设置foo属性")
attrs["test"] = "mymate"#实例化添加属性
return type.__new__(cls, name, bases, attrs)
class A(metaclass=MyMate):
foo = "foo!"
5、抽象基类
5.1 什么是抽象基类
抽象基类它提供了接口,但是又没有去把接口实现的类,需要由子类完成。感觉它就是老板,只告诉你要完成项目A, 你接到项目A后(继承),你自己去把它完成。抽象基类不能实例化。
参考文章:
5.2 抽象基类特点
1.继承类必须实现抽象基类的方法
2.抽象基类无法实例化
5.3 Abstract Base Class(抽象基类)
python中并没有提供抽象类与抽象方法,但是提供了内置模块abc(abstract base class)来模拟实现抽象类。
ABC,A bstract Base Class(抽象基类),主要定义了基本类和最基本的抽象方法,可以为子类定义共有的API,不需要具体实现。
抽象基类提供了逻辑和实现解耦的能力,即在不同的模块中通过抽象基类来调用,ABC类实际上是一个元类。
from abc import ABC, abstractmethod
class A(ABC):
@abstractmethod #定义抽象方法
def eat(self):
pass
6、静态方法、类方法、实例方法
6.1 定义
静态方法(无需使用实例封装的内容@staticmethod)。
类方法(会自动加当前类的类名 @classmethod) => cls表示类本。
6.2 各种方法的区别(@staticmethod、@classmethod)
实例方法不能通过类名调用,但是静态方法和类方法是可以(不实例化访问)。
实例方法:可以通过self访问实例属性。
类方法:可以通过cls访问类属性(希望取的值不受实例影响时使用)
静态方法:不可以访问,通过传值的方式。
6.3 属性包装(@propert)
把函数包装成属性,使用的时候用 对象名.属性名,Person类中有一个属性为age,控制age的范围只能在0~150之间,属性包装的应用,property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
@propert会自动产生两个装饰器 .setter 和 .del。
class Person():
def __init__(self):
self.age = 10
@property # 会自动产生两个装饰器age.setter(xx.setter)和 age.del(xx.del)
def age(self):
return self._age
@age.setter # 进行设置 age 的时候自动调用
def age(self,_age):
if 0 <_age<100:
self._age = _age
else:
raise ValueError("年龄不在范围内")
@age.deleter
def age(self):
print("okook")
p = Person()
p.age = 20
print(p.age)
p.age = 1
print(p.age)
del p.age
7、Python类中的下划线
7.1 定义
标识符是用来标识某种对象的名称。在命名标识符时,需要遵循一定规则。标识符的第一个字符必须是字母(大小写均可),或者是一个下划线("_")。以下划线开头的标识符是有特殊意义的
7.2 以单下划线开头的(_foo)
保护属性,私有属性。只有类内部自己访问,子类不能访问,对象也不能访问。
以单下划线开头的(_foo):
类:这类成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量
模块:如果你写了代码“from <模块/包名> import *”,那么以“_”开头的模块和包都不会被导入,除非模块或包中的“__all__”列表显式地包含了模块和包。
这有点类似于惯例,为了使其他人(或你自己)使用这些代码时将会知道以“_”开头的名称只供内部使用。正如Python文档中所述:以下划线“_”为前缀的名称(如_spam)应该被视为API中非公开的部分(不管是函数、方法还是数据成员)。此时,应该将它们看作是一种实现细节,在修改它们时无需对外部通知。
7.3 以双下划线开头的(__foo)
私有属性,Python中私有是一种伪私有,在类的内部对双下划线开头的变量做了一层转换--》__max转换成 --》_类名__max。
以双下划线开头的(__foo)
类:只有类对象自己能访问,连子类对象也不能访问到这个数据。强行访问“对象名._类名
__xxx“这样的方式。
模块:不能用“from xxx import *“导入包/模块。
名称(具体为一个方法名)前双下划线(“__“)的用法并不是一种惯例,对解释器来说它有特定的意义。Python中的这种用法是为了避免与子类定义的名称冲突。
Python文档指出,“__spam”这种形式(至少两个前导下划线,最多一个后续下划线)的任何标识符将会“_classname__spam”这种形式原文取代,在这里“classname”是去掉前导下划线的当前类名。
7.4 以双下划线开头和结尾的( __foo__ )
以双下划线开头和结尾的( __foo__ )属性在Python中是有特殊含义的。
- __dict__ :查看命名空间--字典
- __name__ :查看类名
- __class__ :查看对象属于哪个类
- __model__ :查看所在模块
- __doc__ :文档注释(在类里面第一个用三引号引起来的部分,自动被__doc__调用)
8、自省
8.1 自省是什么
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。有时我们要访问某个变量或是方法时并不知道到底有没有这个变量或方法,所以就要做些判断。判断是否存在字符串对应的变量及方法。
8.2 自省有4个方法
- getattr(obj,'name'): 获取成员,根据字符串去获取obj对象里的对应的方法的内存地址
- hasattr(obj,'name'): 检查是否含有成员,判断一个对象obj里是否有对应的name_str字符串的方法
- setattr(obj,'age', 18): 设置成员
- delattr(obj,'name'): 删除成员
class A:
name = "sc"
def func1(self):
print("this is A.func1")
a = A()
print(hasattr(A,'name'))
print(hasattr(A,'name1'))
#获得属性方法
getattr(a,'func1')()