面向过程的编程方式更注重步骤,不注重分工,在需求复杂时开发难度大,比如游戏方向,太多内容和逻辑,拿比较简单的植物大战僵尸为例,如果考虑不同状态下所有植物和僵尸的动作,很难基于过程分析,开发难度是很大的,由此产生了面向对象的编程思想,相较函数,对象是更大的封装,编程时先确定职责,产生对象,再确定具体方法。
基本操作
类
类是具有相同特征或行为的事务的统称,是抽象的,并不能直接使用,用class
定义,内部包含属性和方法,是创建对象的模板。
构造函数为def __init__(参数):
用于初始化内部属性,添加属性可在任意函数中实现,只是在init
方法中时可在调用时自动执行,与Java和C不同,属性无需提前声明。
可以通过__dict__[属性名]
和.
方法查看成员,添加也可以使用.
方法,但添加属性最好在类的内部定义,便于维护。
对象
对象是由类创造出来的具体存在,创建方法为对象=类名()
,实例化时先开辟对象空间,自动执行构造方法,并将内存地址传给init
方法的第一个位置的参数self
,对象可以通过.
方法查看和修改变量。
对象找属性的顺序为对象空间=>类空间=>父类。
类的关系
关联、依赖、聚合、组合,实质都一样,是类对其他类进行操作,主要形式是将类作为参数在内部方法中传递。
类的成员
由静态字段和动态方法构成,__
双下划线修饰的是私有成员,只能在类的内部访问,公有成员任何位置都能访问。
类的方法
就是写在类内的函数,可以通过装饰器进行修饰:
@classmethod
修饰将类本身作为对象操作的方法,示例代码如下:
class animal():
num=1
def eat(self,obj):
obj.eat()
@classmethod
def add(self):
self.num=self.num+1
print("num added already")
animal.add()
print(animal.num)
此时类中的num
已经变为2,对象生成后并不影响类,但通过类方法装饰的方法可以直接对类本身进行修改,让类具有记忆力,让对对象的操作持久化到类中。
@staticmethod
装饰器修饰的方法为静态方法,该方法不涉及类中的属性和方法,是单纯独立的函数,仅仅托关于类的命名空间中,便于使用和维护,本质和定义在类外的函数没有区别。
函数是显式传递数据,方法是隐式传递数据,Java中只有方法,C中只有函数。
特殊属性
@property
修饰的方法,在执行函数后只返回值,将方法作为一个成员返回,如:
class animal():
a=3
b=2
@property
def add(self):
return self.a+self.b
a=animal()
print(a.add)
如此装饰的方法,使得用户不知道其内部是由函数运算的,但现在是没想到有啥用处。
也可以使用@成员名.setter
和@成员名.deleter
定义修改和删除的方法,在触发时自动执行。
isinstance(a,b)
判断a是否是b类派生类中实例化的对象
sisubclass(a,b)
判断a是否是b的派生类或派生类的派生类,继承关系更深的判断
三大特征
继承,封装,多态是面向对象的三大特征,功能分别为:
继承:实现代码的复用
封装:将属性和方法封装到一个抽象的类中
多态:不同对象调用相同的方法而产生不同的执行结果
封装
这里的封装没啥好说的,是面向对象的基本要求,抽象类内定义属性和方法,实例化后才能使用,主要介绍一下继承和多态,其中多态最为重要。
但也有一个方法要学习,为了统一命名,限制派生类必须重写该方法时,可让基类方法抛出NotlmplementError
异常,如果派生类未重写时则直接报错,用于限制类中命名规范,或引入抽象类,借助第三方模块from abc import ABCMeta,abctractmethod
实现,设置抽象方法,要求子类必须重写,与Java中的抽象类和接口类似。
继承
继承方法为class 类名(父类名):
,此时新定义的类就具备了父类的所有方法和属性,生成的对象可以直接调用,基类不继承时python规定默认继承object
类,重写后若要使用父类方法,通过父类名.方法
或super().方法
实现。
一个子类继承多个父类为多继承,实现方法为class 类名(父类名,父类名)
,多个父类有相同方法时,按继承顺序优先继承,具体确定顺序由一套mro
序列实现,在类创建时计算。
私有属性或方法,不希望被子类继承或外部访问的成员使用该方法,具体实现为__方法名
,子类和外界不可直接访问,可通过父类公用方法间接访问。
多态
在这我首先复习了一下Java中学习的多态,当时的原话是允许父类的引用指向子类的对象,这次学的比较不是开发课程,但核心是一致的,就是不同的子类调用相同的方法产生不同的结果 ,也有可以通过父类方法的参数中有对象来实现,比如def work(self,obj):obj.work()
就可以实现调用obj
对象的方法,通过对象进参数的手段,实现调用相同方法而产生不同的结果,各种手段形式不同,而目标相同,就是同一个方法产生不同的结果,那这里有一个疑问,重载算不算多态呢?
这里我又去查了一下,感觉我上面说的多态并不严谨,一般的多态是必须要有继承关系,即子类继承父类,子类覆盖父类,父类指向子类这样一个过程,重载和上面提到的实现手段是面向过程的,而非严格意义上的多态,故一般的多态,还应该是父类对象接受子类实例实现的,可在这里我又有疑问了,python是弱类型的语言,父类子类全看实例化对象本身,无法人为规定,该怎么用父类去接收子类对象呢?经过学习,用如下代码示例:
class animal():
def eat(self,obj):
obj.eat()
class dog(animal):
def eat(self):
print("dog eats")
class cat(animal):
def eat(self):
print("cat eat")
dog=dog()
a=animal()
a.eat(dog) # 多态调用
看来还是实现的手段不同,针对弱类型的python,通过将对象传入基类方法作为参数,即可实现多态,严格意义上的多态只存在于强类型的Java和C中 ,python使用鸭子类型,即一只鸟看着像鸭子,叫起来像鸭子,那它就是鸭子,所以我们尽可以使用函数来接收鸭子对象,调用他走和叫的方法,如:def eat(obj): obj.eat()
此时传入对象即可调用不同的方法。
这样的话好像没有继承关系,直接在不同的类中调用其他类的方法就可以实现多态了,经过尝试,删除dog
对animal
的继承后,函数照样可以执行正确,真是只要能干鸭子的活那他就是鸭子,到底是谁的子类python并不在乎。
反射
通过字符串操作对象相关属性的方法,以往对相关属性操作是通过.
或__dict__
方法获取,通过反射可以使用如下方法:
hasatter(obj,name) # 判断对象中是否有name的属性
getatter(obj,name) # 获取名为name的成员
setatter(obj,name,value) # 设置成员的值
delatter(obj,name) # 删除名为name的属性 上述方法的名称均通过字符串形式传递
用该方法可以实现字符串本身与对象的传递,比如菜单的选择,用输入的字符串直接与成员相匹配,减少代码量的同时确保完整覆盖逻辑,避免逻辑缺失,简单示例如下:
class animal():
a=3
b=2
a=animal()
print(getattr(a,'a'))
双下方法
重写针对对象的操作。
__len__ len()时触发
__str__ 打印对象时触发
__call__ 对象()时执行
__del__ 内存释放时自动触发
__init__ 实例化对象时触发
__eq__ 判断相等时触发
__new__ 创建时开辟内存空间触发,限于init方法
上述方法类似于对象操作时对操作符的重载,也表明了python在执行方法和操作符时本质上还是调用的底层方法。
总结
python是一门面向对象的语言,本章才刚刚接触到,最重要的多态的概念,但在python中,因为其弱类型的语言特质,缺少了一定形式而没有那么优雅,转而使用鸭子类型,只要将对象实例传入方法中就可以实现同名称不同结果的效果,并不在乎其具体继承关系。
此外都是Java和C++的低配版,确实开发效率高,基本只要考虑本身的逻辑,而很少无关的硬性规定,优雅优雅。