天天看点

Python面向对象编程(Object Oriented Programming,OOP)之结构、成员和属性

面向对象结构

面向对象类中的数据大致分为两块区块:

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