面向对象结构
面向对象类中的数据大致分为两块区块:
class Demo:
name = '张三' # 第一部分:静态字段(静态变量)
age = 18 # 第一部分:静态字段(静态变量)
address = '北京' # 第一部分:静态字段(静态变量)
def __init__(self): # 第二部分:动态字段(方法)部分
pass
def func(self): # 第二部分:动态字段(方法)部分
pass
结论:分为静态字段(变量)和动态字段(方法)两部分
每个部分还可以分为多个小部分:
class Demo:
idNum = '111111111' # 静态变量
__address = '北京' # 私有静态变量
def __init__(self, name, age, height): # 构造方法,也属于普通方法
self.name = name
self.__age = age
self.__height = height
def func1(self): # 普通方法
pass
def __func2(self): # 私有方法
pass
@classmethod
def class_func(cls): # 类方法,至少含有一个cls参数
print("类方法")
@staticmethod # 静态方法,无默认参数
def static_func():
print("静态方法")
@property # 属性
def property_func(self):
return "属性"
类有这么多的成员,那么我们先从那些地方研究呢? 可以从私有与公有部分,方法的详细分类两个方向去研究。
私有与公有
对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问。
- 私有成员,只有在类的内部才能方法。
私有和公有成员访问权限不同:
静态字段(静态变量)
- 公有静态字段:类可以访问;内部类可以访问;派生类(子类)可以访问。
- 私有静态字段:仅内部类可以访问。
class DemoA:
name1 = '公有字段'
__name2 = '私有字段'
def func1(self): # 内部类访问
print((DemoA.name1)) # 可以访问
print(DemoA.__name2) # 可以访问
class DemoB(DemoA):
def show1(self): # 派生类访问
print(DemoA.name1) # 可以访问
# print(DemoA.__name2) # 不可以访问
c1 = DemoA()
c1.func1()
c2 = DemoB()
c2.show1()
结果:
公有字段
私有字段
公有字段Process finished with exit code 0
普通字段(对象属性)
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问。
- 私有普通字段:仅类内部可以访问。
class DemoA:
def __init__(self):
self.foo1 = '公有字段'
self.__foo2 = '私有字段'
def func1(self): # 内部类访问
print(self.foo1) # 可以访问
print(self.__foo2) # 可以访问
class DemoB(DemoA):
def show1(self): # 派生类访问
print(self.foo1) # 可以访问
# print(self.__foo2) # 不可以访问,报错
c1 = DemoA()
c1.func1()
c2 = DemoB()
c2.show1()
结果:
公有字段
私有字段
公有字段Process finished with exit code 0
方法:
- 公有方法:对象可以访问;类内部可以访问;派生类中可以访问。
- 私有方法:仅类内部可以访问。
class DemoA:
def __init__(self):
pass
def add1(self):
print("超类公有方法")
def __add2(self):
print("超类私有方法")
def add3(self):
self.__add2() # 本类调用自己的私有方法
class DemoB(DemoA):
def show1(self):
print("派生类公有方法")
def __show2(self):
print("派生类私有方法")
# def show3A_add2(self):
# self.__add2 # 派生类不能引用超类私有方法,报错
def show4B_show2(self):
self.__show2() # 派生类可以引用自身的私有方法,属于自己调用
c1 = DemoA()
c1.add1() # 调用自身公有方法
# c1.add2() # 外部无法调用私有方法,报错
c1.add3() # 外部间接调用类中私有方法
c2 = DemoB()
c2.add1() # 派生类调用超类公有方法
# c2.add2() # 派生类无法调用超类私有方法,报错
c2.add3() # 派生类调用自身公有方法
c2.show1() # 派生类调用自身公有方法
# c2.show2() # 派生类在外部无法调用自身私有方法,报错
# c2.show3A_add2() # 派生类不能引用超类私有方法,报错
c2.show4B_show2() # 派生类间接调用父类私有方法
结果:
超类公有方法
超类私有方法
超类公有方法
超类私有方法
派生类公有方法
派生类私有方法Process finished with exit code 0
总结:
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用。
成员
字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同。
普通字段属于对象,静态字段属于类。
class Demo:
country = '中国' # 静态字段
def __init__(self, name):
self.name = name # 普通字段
obj = Demo('北京')
# 直接访问普通字段
print(obj.name)
print(obj.country)
# 直接访问静态字段
print(Demo.country)
结果:
北京
中国
中国Process finished with exit code 0
结论:
普通字段需要通过对象来访问,也可以使用使用对象来访问。
静态字段通过类访问,类不能直接访问普通字段。
注意:静态字段在内存中只保存一份,普通字段在每个对象中都要保存一份。
方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self。
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls。
- 静态方法:由类调用,无默认参数。
class Foo:
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义普通方法,至少有一个self参数 """
print('普通方法')
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数,cls表示这个类 """
print('类方法')
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
# 调用普通方法
f = Foo(1) # 随便写一个实参,无实际意义
f.ord_func()
# 调用类方法
Foo.class_func()
# 调用静态方法
Foo.static_func()
结果:
普通方法
类方法
静态方法Process finished with exit code 0
这些方法的差异:
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
属性(property)
具有访问它时会执行一段功能(函数)然后返回值的特性。
例:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解) 成人的BMI数值:
- 过轻:低于18.5
- 正常:18.5-23.9
- 过重:24-27
- 肥胖:28-32
- 非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m),例:70kg÷(1.75×1.75)=22.86。
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
return self.weight / (self.height ** 2)
p1 = People('张三', 70, 1.75)
print(p1.bmi)
结果:
22.857142857142858
Process finished with exit code 0
ps:这是本人的BIM值,唉~又胖了啊。
使用属性的好处:
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。
由于新式类(Python3中全是新式类)中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除。
class people: # 定义一个人的类
def __init__(self, name, sex):
self.name = name
self.sex = sex # p1.sex = "male",遇到property,优先用property
@property # 查看sex的值
def sex(self):
return self.__sex # 返回正真存值的地方
@sex.setter # 修改sex的值
def sex(self, value):
if not isinstance(value, str): # 在设定值之前进行类型检查
raise TypeError("性别必须是字符串类型") # 不是str类型时,主动抛出异常
self.__sex = value # 类型正确的时候,直接修改__sex的值,这是值正真存放的地方
# 这里sex前加"__",对sex变形,隐藏。
@sex.deleter # 删除sex
def sex(self):
del self.__sex
p1 = people("张三", "男") # 实例化对象p1
print(p1.sex) # 查看p1的sex,此时要注意self.sex的优先级
p1.sex = "女" # 修改sex的值
print(p1.sex) # 查看修改后p1的sex
print(p1.__dict__) # 查看p1的名称空间,此时里面有sex
del p1.sex # 删除p1的sex
print(p1.__dict__) # 查看p1的名称空间,此时发现里面已经没有sex了
结果:
男
女
{'name': '张三', '_people__sex': '女'}
{'name': '张三'}Process finished with exit code 0