7.0 Python 面向对象编程
python是一种面向对象的编程语言,面向对象编程(Object-Oriented Programming,OOP)是一种编程思想,其核心概念是“对象”。对象是指一个具有特定属性和行为的实体,而面向对象编程就是通过对这些实体进行抽象、分类、封装和继承等操作,来实现程序的结构和逻辑。在python中,我们可以通过定义类、创建实例和调用方法等方式,来实现面向对象编程的思想,从而编写出更加灵活、可扩展、易维护的程序。
7.1 面向对象之封装
封装是面向对象三大特性之一,是指把对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过内部提供的接口来实现对对象的操作和访问。
在python中,通过定义类和实现类的属性和方法来实现封装。类中的属性和方法可以分为公有和私有两种,公有属性和方法可以被外部访问,而私有属性和方法只能在类的内部访问。可以通过在属性和方法名前加上双下划线来实现私有化。
封装可以使对象的状态信息被保护,不会被随意篡改,从而提高了程序的可靠性和安全性。同时,封装还可以隐藏对象的内部实现细节,使得对象可以更加灵活地进行修改和优化,而不会对外部产生影响。
定义类是通过class关键字
,class后面紧接着是类名,类名通常是大写开头的单词,紧接着是()小括号
,小括号内可以写(要继承的类名称)
,表示该类是从哪个类继承下来的,可以有多个父类(基类)
,通常如果没有合适的继承类,就使用object类
,这是所有类最终都会继承的类,也可以不写,不写的话默认也是加载的.
>>> import sys
>>> import os
>>>
>>> class lyshark(object):
... def __init__(self,name,age,sex):
... self.name = name
... self.age = age
... self.sex = sex
...
>>>
>>> temp=lyshark("lyshark","22","Man")
>>> print("姓名:%s 年龄:%s 性别:%s"%(temp.name,temp.age,temp.sex))
姓名:lyshark 年龄:22 性别:Man
以上就是创建的一个lyshark
类,如上所示__init__()
叫做初始化方法(或构造方法),在类实例化时,这个方法(虽然它是函数形式,但在类中就不叫函数了叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的__init__(self,name,age,sex)
就是要在创建一个角色时给它设置这些属性,也就是做一些初始化赋值工作.
- 呈上参数
self
的工作流程是这样的. - 在内存中开辟一块空间指向lyshark这个变量名,也就是相当于一个指针函数
- 实例化这个类首先执行其中的
__init__()
,执行后会自动的将参数传递给内部变量 - 然后自动执行
__init__()
构造方法,开辟内存空间,此时self.* = *
两个变量数据一致了
创建对象的过程称为实例化,还是看如上代码,temp=lyshark()
这一句话就是将lyshark类
实例化,当一个对象被创建后,包含3个方面的特性:对象的句柄、属性和方法,对象的句柄用于区分不同的对象,当对象被创建后,该对象会获取一块存储空间,存储空间的地址即为对象的标识,对象的属性和方法与类的成员变量和成员函数相对应.
使用公有属性封装: 类由属性和方法组成,类的属性是对数据的封装,而类的方法则表示对象具有的行为,类通常由函数(实例方法)和变量(类变量)组成,如下是使用公有属性封装的数据成员,可以看到,在类的外部,我们是可以使用temp.name="xxoo"
的方式修改这个数据成员的数值的,然后再次调用打印函数,则发现数据被改动了,这样做显然是不够安全的.
- 1.公有属性或者静态属性,可以直接通过类直接访问,也可以直接通过实例进行访问.
- 2.通过类的某个实例对公有属性进行修改,实际上对为该实例添加了一个与类的公有属性名称相同的成员属性,对真正的公有属性是没有影响的,因此它不会影响其他实例获取的该公有属性的值.
- 3.通过类对公有属性进行修改,必然是会改变公有属性原有的值,他对该类所有的实例是都有影响的.
>>> import sys
>>> import os
# =====================以下内容是类的定义====================
>>> class lyshark():
... def __init__(self,name,age): #构造函数,初始化数据
... self.name = name #封装的数据成员
... self.age = age
...
... def my_print(self): #封装的成员函数
... print("我的名字是:%s 我的年龄是:%s"%(self.name,self.age))
...
# =========================================================
>>>
>>> temp=lyshark("wangrui","22") #类的实例化,将参数传入类中
>>> temp.my_print() #调用类中的指定方法,打印数据
我的名字是:wangrui 我的年龄是:22
>>>
>>> print(temp.name) #直接调用类中的数据成员
wangrui
# ===============改变数据成员,再次调用看看===================
>>> temp.name="xxoo"
>>> temp.my_print()
我的名字是:xxoo 我的年龄是:22
# =========================================================
使用私有属性封装: 私有属性和成员属性一样,是在__init__
方法中进行声明,但是属性名需要以双下划线__开头
,私有属性是一种特殊的成员属性,它只允许在实例对象的内部(成员方法或私有方法中)访问,而不允许在实例对象的外部通过实例对象或类来直接访问,也不能被子类继承,总之一句话:私有属性只有类的内部可以调用
.
- 1.私有变量不能通过类直接访问.
- 2.私有变量也不能通过实例对象直接访问.
- 3.私有变量可以通过成员方法进行访问.
- 4.类变量一般可以用于共享两个实例化之间的数据,而实例变量则只作用于当前实例.
>>> import os
>>> import sys
# =====================以下内容是类的定义====================
class lyshark():
name = "lyshark" #定义公有属性(类变量,可共享数据)
__age = 22 #定义私有属性(类变量)
def __init__(self): #定义构造函数,初始化数据
self.__like = "soccer" #定义私有实例属性(实例变量)
self.hobby = "xxoo" #定义公有实例属性
def my_print(self): #定义公有函数,外部可以调用
print("我的名字: %s"%self.name)
print("我的年龄: %s"%self.__age)
print("我的爱好: %s"%self.__like)
print("其他: %s"%self.hobby)
def __new_test(self): #定义私有函数,只能内部类调用
print("hello world")
def __del__(self): #定义析构函数,清理数据
self.__nobody = "end"
#print("函数执行结束,销毁无用的数据. %s"%self.__nobody)
# =================(公有/私有)方法的调用====================
>>> temp=lyshark() #实例化对象
>>> temp.my_print() #调用类中方法(公有方法)
我的名字: lyshark
我的年龄: 22
我的爱好: soccer
其他: xxoo
>>> temp.__new_test() #调用私有方法,则会报错
# =================(公有/私有)属性的调用====================
>>> print(lyshark.name) #调用公有属性则成功
lyshark
>>> print(lyshark.__age) #调用私有属性,则会报错
>>> print(lyshark.__like)
# =========================================================
将类封装进对象中: 将类实例化后的对象当作参数传递到另一个类中,那么在另一个类中我们就可以访问到被传入类中的数据成员以及成员函数的调用啦.
import os
import sys
class main(object):
def __init__(self,name,obj): #OBJ参数用来接收对象
self.name=name
self.obj=obj
class uuid(object):
def __init__(self,uid,age,sex):
self.uid=uid
self.age=age
self.sex=sex
temp=uuid(1001,22,"Man") #首先给UUID类初始化
lyshark=main("lyshark",temp) #将生成的TEMP对象传递给main
# =========================================================
>>> lyshark.name
'lyshark'
>>> lyshark.obj.uid #最后通过多级指针的方式访问数据
1001
>>> lyshark.obj.sex
'Man'
>>> lyshark.obj.age
22
7.2 面向对象之继承
继承是面向对象编程中的重要特性之一。通过继承,可以创建一个新类,目的是使用或修改现有类的行为。原始的类称为父类或超类,新类称为子类或派生类。继承机制可以实现代码的重用。其本质是在子类中创建一个父类的实例,从而将父类中的方法和属性全部复制一份到子类中。python中支持多继承,通过继承,子类可以获得父类的功能。在继承的过程中,如果父类和子类中有重复的方法,优先使用子类中的方法。
继承基类普通函数: 首先base()
是一个基类,而expand()
则是一个派生类,派生自base()
,如下虽然派生列没有任何功能,但我们依然可以调用printf()
函数打印传入的数据,则可说明,函数是继承的base
类里面的.
import os
import sys
class base(): #这个类是基类
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base): #新建类expand继承base基类的方法
pass
# =========================================================
>>> temp=expand("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
直接继承构造函数: 新建expand()
子类,并继承base()
基类的构造函数,并能够数据在子类中打印父类属性.
import os
import sys
class base():
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base):
def __init__(self,name,age):
super(expand,self).__init__(name,age) #推荐使用本功能实现继承
#base.__init__(self,name,age) #此处和上面实现的功能相等
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
# =========================================================
>>> temp=base("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
>>> temp=expand("lyshark","22")
>>> temp.printf()
姓名:lyshark 年龄:22
在父类base()
的原始字段的基础上重写,给子类expand()
添加一个新的字段sex
,并能够传递参数.
import os
import sys
class base(): #定义的父类
def __init__(self,name,age):
self.name = name
self.age = age
def printf(self):
print("姓名:%s 年龄:%s"%(self.name,self.age))
class expand(base): #定义的子类
def __init__(self,name,age,sex):
super(expand,self).__init__(name,age) #继承父类的属性
self.sex = sex #新添加的一个属性
def printf(self):
print("姓名:%s 年龄:%s 性别:%s "%(self.name,self.age,self.sex))
# =========================================================
>>> temp=base("lyshark","22") #原始基类,没有第三个字段
>>> temp.printf()
姓名:lyshark 年龄:22
>>> temp=expand("lyshark","22","Man") #在不影响父类情况下,重写新的字段
>>> temp.printf()
姓名:lyshark 年龄:22 性别:Man
强制继承父类函数: 如果想在子类中使用父类的其中一个方法,可以使用以下的方式来实现.
import os
import sys
class base(object):
def printf(self):
print("================================")
print("执行函数....")
print("================================")
return 0
class expand(base):
def fun(self):
ret=super(expand,self).printf() #强制调用父类中的printf方法
return ret #将结果返回给调用者
# =========================================================
>>> temp=base()
>>> temp.printf() #调用基类的方法
================================
执行函数....
================================
>>> obj=expand() #在子类中调用基类方法
>>> ret=obj.fun() #将返回值付给ret并打印
>>> print(ret)
================================
执行函数....
================================
0
简单的多继承: 此处我们实现一个简单的多继承,这里我们会在代码中说明他们的执行顺序,废话不多说,看下图.
import os
import sys
class A:
def fun(self):
print("我是A类里面的函数")
class B:
def fun1(self):
print("我是B类里面的函数1")
def fun2(self):
print("我是B类里面的函数2")
class C(A,B):
def fun(self):
print("我是C类里面的函数")
# =========================================================
>>> temp=C()
>>> temp.fun() #默认调用C类,如果C里面有fun()函数则默认执行自身
我是C类里面的函数 #如果自身没有,才会去基类里面去找fun()函数的存在
>>> temp.fun1() #由于C类中没有这个方法,它会去B或A类里面去找
我是B类里面的函数1 #这也为我们重写函数提供了可能性,我们只需修改C类且名称相同即可实现重写
复杂的多继承: 下面是重点和难点,在其他源码都是这么干的,属于嵌套继承如果开发新程序,则最好不要这么干,最后连自己都懵逼了.
import os
import sys
class A:
def bar(self):
print('bar')
self.f1()
class B(A):
def f1(self):
print('b')
class C():
def f1(self):
print('c')
class D(B):
def f1(self):
print('d')
class E(C,D):
pass
temp=D()
temp.bar()
多类继承(实例): 多继承也是一种解决问题的方式,这里我们通过例子,来演示一下多继承的应用场景,如下我们将添加三个类分别是Person(人类)
作为父类使用,在创建两个派生类,一个是Teacher(老师)
,另一个是Student(学生)
两个类,这两个类派生于Person(人类)
,都属于人类都有共同属性.
1.我们首先创建一个基类Person()
,来描述人类这个范围,当然人类具有一些公共属性如姓名,年龄无论老师学生都有.
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def walk(self):
print('%s is walking...' % self.name)
def talk(self):
print('%s is talking...' % self.name )
2.在以上基类的基础上派生一个Teacher
子类,使用父类的几个属性和方法.
class Teacher(Person):
def __init__(self, name, age, level, salary):
super(Teacher, self).__init__(name, age)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching...' % self.name)
3.最后创建一个学生类Student
,同样和父类公用一些基本属性和方法.
class Student(Person):
def __init__(self, name, age, class_):
Person.__init__(self, name, age)
self.class_ = class_
def study(self):
print('%s is studying...' % self.name)
4.最后直接实例化,并传递两个参数,打印看结果,很好理解.
>>> t1 = Teacher('张老师', 33, '高级教师', 20000)
>>> s1 = Student('小明', 13, '初一3班')
>>> t1.talk()
>>> t1.walk()
>>> t1.teach()
>>> s1.talk()
>>> s1.walk()
>>> s1.study()
张老师 is talking...
张老师 is walking...
张老师 is teaching...
小明 is talking...
小明 is walking...
小明 is studying...
7.3 面向对象之包装
在上面的面向对象概念中提到过,通常类中封装的是数据(数据成员)
和操作数据的方法(成员函数)
这两种东西组成形成了一个类的雏形,数据就是属性,且上面已经介绍过了属性无外乎这几种分类,公有属性/类变量
、成员属性/实例变量
和私有属性
,除了这些属性以外,现在我们来说说类中的方法,类中的方法分为以下几种:
- 成员方法:通常情况下,它们与成员属性相似,是通过类的实例对象去访问,成员方法的第一个参数通常写成
self
,以标明这是一个成员方法 - 私有方法:以双下划线
(__)
开头的成员方法就是私有方法,与私有属性类似,私有只能在实例对象内部访问,且不能被子类继承 - 类的方法:使用
@classmethod
来装饰的成员方法就叫做类方法,定义为类方法后,只能访问类变量,而不能访问实例变量 - 静态方法:使用
@staticmethod
来装饰的成员方法就叫做静态方法,静态方法已经与这个类没有任何关联了,通常情况下用来编写工具包 - 属性方法:把一个方法变成静态属性,可以像访问成员属性那样去访问这个方法,且无法通过
()
小括号,传递参数
类的方法: 通常情况下我们如果在成员函数的上方加上@classmethod
来装饰的成员方法就叫做类方法,它要求第一次参数必须是当前类,与公有属性/静态属性
相似,除了可通过实例对象进行访问,还可以直接通过类名去访问,且第一个参数表示的是当前类,通常写为cls,另外需要说明的是,类方法只能访问公有属性,不能访问成员属性,因此第一个参数传递的是代表当前类的cls,而不是表示实例对象的self.
import os
import sys
class lyshark(object):
name="wangrui" #赋值等待被调用
age="22"
sex="Man"
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
@classmethod #声明下方的函数为类方法
def printf(cls): #此函数只能调用类变量,而不能调用成员属性
print("姓名:%s 年龄:%s 性别:%s"%(cls.name,cls.age,cls.sex))
# =========================================================
>>> temp=lyshark("100","100")
>>> temp.printf()
姓名:wangrui 年龄:22 性别:Man
如果我们将以上的打印函数变量修改调用实例变量,则会出现错误,这是因为装饰器classmethod
的存在,如果屏蔽掉装饰器代码,则就可是成功调用啦,只不过调用的不再是类变量中的数据,而是实例变量中的数据.
def __init__(self,x,y,z):
self.x=x
self.y=y
self.z=z
#@classmethod #此装饰器存在,不允许调用实例变量
def printf(self):
print("姓名:%s 年龄:%s 性别:%s"%(self.x,self.y,self.z))
# =========================================================
>>> temp=lyshark("lyshark","33","Man")
>>> temp.printf()
姓名:lyshark 年龄:22 性别:Man
静态方法: 上面的简单介绍也说明了,通常情况下我们如果在成员函数的上方加上@staticmethod
来装饰的成员方法就叫做静态方法,静态方法是类中的函数,不需要实例.静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作.可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护,通常情况下我们可以用它来实现一个私有的工具包.
import os
import sys
class lyshark(object):
@staticmethod
def sum(x,y):
return x+y
@staticmethod
def sub(x,y):
return x-y
# =========================================================
>>> #temp=lyshark() #这里无需实例化
>>> text=lyshark.sum(10,20)
>>> print("两数之和:%s"%text)
两数之和:30
>>> text=lyshark.sub(100,50)
>>> print("两数之差:%s"%text)
两数之差:50
以上的小例子就是一个典型的封装,因为我们无法调用类中的其他数据,所以直接将它作为一个工具箱使用是最恰当不过的啦,当然这个功能至今为止在开发中也没怎末用到过.
属性方法: property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值,他可以把一个方法变成静态属性,可以像访问成员属性那样去访问这个方法,且无法通过()
小括号,传递参数,它的第一个参数也必须是当前实例对象,且该方法必须要有返回值.
import os
import sys
class lyshark(object):
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
@property
def foo(self):
return self.weight + self.height
# =========================================================
>>> temp=lyshark("wangrui",75,1.88)
>>> print(temp.foo) #此处我们直接调用这个属性,并没有传递参数
76.88
上面的小例子我们可以看到,实例化一个类以后,在调用内部的foo函数的时候,并没有添加括号,然而还是调用成功了,这种特性的使用方式遵循了统一访问的原则,即对外屏蔽细节,只需要像调用变量一样的使用它即可,给用户的感觉就是调用了一个类中的变量.
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除,看下面的代码:
import os
import sys
class lyshark(object):
@property
def get(self):
print("get 函数运行了我...")
@get.setter
def get(self,value):
print("set 设置参数运行了我,传递的参数是: %s"%value)
@get.deleter
def get(self):
print("工作结束了,可以删除数据了")
# =========================================================
>>> temp = lyshark() #实例化
>>> temp.get #调用get属性,则会执行get()函数
get 函数运行了我...
>>> temp.get = 'hello' #设置属性则会执行get.setter函数
set 设置参数运行了我,传递的参数是: hello
>>> del temp.get #删除工作,则会走get.deleter函数
工作结束了,可以删除数据了
下面来看一个具体例子,我们可以直接调用obj.price
来获取商品价格,也可以通过赋值语句修改商品的价格等.
class Goods(object):
def __init__(self):
# 原价
self.original_price = 100
# 折扣
self.discount = 0.8
@property
def price(self):
# 实际价格 = 原价 * 折扣
new_price = self.original_price * self.discount
return new_price
@price.setter
def price(self, value):
self.original_price = value
@price.deltter
def price(self, value):
del self.original_price
# =========================================================
>>> obj = Goods()
>>> obj.price # 获取商品价格
>>> obj.price = 200 # 修改商品原价
>>> del obj.price # 删除商品原价
7.4 面向对象之反射
在程序开发中,常常会遇到需要在执行对象的某个方法或者调用对象的某个变量时,由于一些原因无法确定或者并不知道该方法或者变量是否存在的情况。这时我们需要一个特殊的机制来访问或操作该未知的方法或变量,这种机制就被称之为反射。
反射机制是指通过字符串的形式来操作对象模块中的成员,例如通过字符串的形式导入模块、寻找模块中的指定函数并对其进行操作。反射机制是一种基于字符串的事件驱动机制,可以根据字符串的形式来访问对象中的成员。
python 中的反射功能是由以下四个内置函数提供:hasattr、getattr、setattr、delattr
,这四个函数分别用于在对象内部执行:检查是否含有某成员、获取成员、设置成员、删除成员、导入模块以字符串方式导入,接下来我们将具体介绍它们的应用场景.
hasattr: 检查指定类中是否有指定成员,也就是检查是否含有指定成员函数.
import os
import sys
class dog(object):
def __init__(self,name):
self.name=name
def eat(self):
print("%s 在吃东西..."%self.name)
d = dog("dogging")
choice = input("输入数据:").strip()
# (d=类的实例名称) (choice=数据保存位置)
print(hasattr(d,choice))
#--输出结果-----------------------------------
输入数据:eat
True
getattr: 获取指定类中是否有指定的成员,结果打印出1个字符串,映射出函数所在内存地址.
import os
import sys
class dog(object):
def __init__(self,name):
self.name=name
def eat(self):
print("%s 在吃东西...",self.names)
d= dog("dogging")
choice = input("输入数据:").strip()
# (d=类的实例名称) (choice=数据保存位置)
print(getattr(d,choice))
# 同样的,在getattr后面加上括号,则可调用指定方法.
getattr(d,choice)()
#--输出结果-----------------------------------
输入数据:eat
<bound method dog.eat of <__main__.dog object at 0x000001D71FD47128>>
dogging 在吃东西..
getattr: getattr一般的通用写法,映射出函数所在内存地址后,给函数传递参数.
import os
import sys
class dog(object):
def __init__(self,name):
self.name=name
def eat(self,food):
print("%s 在吃东西..."%self.name,food)
d= dog("dogging")
choice=input("输入数据:").strip()
func=getattr(d,choice)
func("调用传递参数..")
#--输出结果-----------------------------------
输入数据:eat
dogging 在吃东西... 调用传递参数..
setattr: 动态装配函数,在外部创建函数,然后将外部函数,动态的装配到指定类的内部.
import os
import sys
def bulk(self): #定义一个外部函数.
print("%s 在大叫..."%self.name)
class dog(object):
def __init__(self,name):
self.name=name
def eat(self,food):
print("%s 在吃东西..."%self.name,food)
d= dog("dogging")
choice=input("输入数据:").strip() #传递字符串
setattr(d,choice,bulk) #将bulk()外部方法,动态添加到dog类中.
d.bulk(d) #调用bulk()方法,这里要将d自己传递进去.
#--输出结果-----------------------------------
输入数据:bulk #调用成功,说明装配成功啦.
dogging 在大叫...
setattr: 动态装配属性,在外部动态装配属性,并设置默认初始值为22.
import os
import sys
def bulk(self):
print("%s 在大叫..."%self.name)
class dog(object):
def __init__(self,name):
self.name=name
def eat(self,food):
print("%s 在吃东西..."%self.name,food)
d= dog("dogging")
choice=input("输入装配变量:").strip() #输入装配变量名
setattr(d,choice,22) #设置初始值为22
print(getattr(d,choice)) #打印装配的变量值
#--输出结果-----------------------------------
输入装配变量:temp
22
delattr: 动态删除函数,以下演示动态的删除dog类中的,eat这个函数,后期再次调用会发现不存在了.
import os
import sys
class dog(object):
def __init__(self,name):
self.name=name
def eat(self):
print("%s 在吃东西..."%self.name)
d= dog("dogging")
choice=input("输入内容:").strip() #输入要删除的方法名
delattr(d,choice,eat) #通过此方法,删除eat函数
d.eat() #再次调用会错误,已经动态删除了
#--输出结果-----------------------------------
输入内容:eat
Traceback (most recent call last):
File "test.py", line 15, in <module>
delattr(d,choice,eat)
NameError: name 'eat' is not defined
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!