从C到Py:面向对象程序设计

面向对象思想

我们先来介绍一下两大编程思想,其一是面向过程,它强调功能上的封装,运用的是简单的线性思维,二是面向对象,它主要是对属性和行为上的封装,处理复杂的事物。

面向过程的典型语言是C语言,面向对象的典型代表就是Python和Java。

面向过程和面向对象都是解决实际问题的一种思维方式,二者是相辅相成的。通过面向对象我们可以从宏观上把握事物之间复杂的关系,方便我们分析整个系统,而具体到每个微观操作,仍旧要用面向过程的方法。

类与对象

接下来讲解一下类的定义:由多个对象抽取出“像”的属性和行为从而归纳总结出来的一种类别

Python中一切皆对象!

1、自定义数据类型的语法结构是:

class 类名():

        pass

这里的类名是自己取定的,注意首字母要大写。类名后的小括号可以省略。

2、创建对象的语法架构为:

对象名=类名()

实际地来理解,类就是模板,而对象则是具体实例。看几个简单的例子:

class Person():  # 自定义类
    pass


class Student():
    pass


per=Person()  # 创建对象
stu=Student()
print(type(per))
print(type(stu))
# 输出结果:
# <class '__main__.Person'>
# <class '__main__.Student'>

类的组成

现在我们详细说明一下类的组成:

1、类属性直接定义在类中,方法外的变量

2、实例属性定义在__init__方法中,使用self打点的变量

3、实例方法定义在类中的函数,且自带参数self

4、静态方法使用装饰器@staticmethod修饰的方法

5、类方法使用装饰器@classmethod修饰的方法

下面我们开一个定义的比较完整的类及其调用:

class Student:
    # 类属性,定义在类中,方法外的变量
    school='上海university'

    # 初始化方法
    def __init__(self, xm, age):  # xm,age是方法的参数,是局部变量,xm,age的作用域是整个__init__方法
        self.name=xm  # =左侧是实例属性,xm是局部变量,将局部变量的值xm赋值给实例属性self.name
        self.age=age  # 实例的名称和局部变量的名称可以相同

    # 类中的函数称为方法
    def show(self):
        print(f'我叫:{self.name}.今年:{self.age}岁了')

    # 静态方法
    @staticmethod
    def sm():
        print('这是一个静态方法,不能使用实例属性和实例方法')
        # 这部分的代码若使用例如self.name将会报错

    # 类方法
    @classmethod
    def cm(cls):
        print('这是一个类方法,不能使用实例属性和实例方法')


# 创建类的对象
stu=Student('abc', 18)  # 传两个参数是因为__init__方法中有两个形参
# 实例属性,使用对象名进行打点调用
print(stu.name, stu.age)
# 类属性,直接使用类名打点调用
print(Student.school)
# 实例方法,使用对象名打点调用
stu.show()
# 类方法,类名打点调用
Student.cm()
# 静态方法,类名打点调用
Student.sm()

接下来再补充一个用类模板创建多个对象的方法:

# 这里我们要继续使用上面定义的student类(不重复展示)
stu1=Student('abc', 18)
stu2=Student('Peter', 25)
stu3=Student('march', 17)
stu4=Student('xing', 100)
print(type(stu1))  # 类型都是一样的
Student.school='×××大学'
lst=[stu1, stu2, stu3, stu4]
for item in lst:
    item.show()
# 输出:
# <class '__main__.Student'>
# 我叫:abc.今年:18岁了
# 我叫:Peter.今年:25岁了
# 我叫:march.今年:17岁了
# 我叫:xing.今年:100岁了

动态绑定属性与方法

使用类创建多个对象时,虽然每个对象的属性名称相同,但是属性值不同。我们可以为每个对象绑定独有的属性或者方法

举个简单的例子,我们继续使用上文定义的Student类,假设我们创建一个对象:

stu=Student('abc', 18) 

现在想要多加一个性别的属性,可以直接 stu.gender='man' 此时就能直接调用stu.gender。

即:可直接进行 stu.属性/方法 = ......  即是这个属性或方法未在类中定义

面向对象的三大特征

现在我们介绍这一部分的主要知识:面向对象的三大特征。希望以此与面向过程区别开来。

①封装隐藏内部细节,对外提供操作方式②继承:是在函数调用时,使用“形参名称=值”的方式进行传参,传递参数顺序可以与定义时参数的顺序不同③多态:是在函数定义时,直接对形式参数进行赋值,在调用时如果该参数不传值,将使用默认值,如果该参数传值,则使用传递的值

一、封装。

讲到封装,即隐藏内部细节,就要讲到外界访问的权限控制。Python中的权限控制,是对属性/方法添加单下划线、双下划线、首尾双下划线来实现的

单下划线开头:表示protected的成员,被视为仅供内部使用,允许类本身和子类访问(子类的部分将在后面说明)。PS:实际上也还是可以被外部访问的

双下划线开头:表示private的成员,只允许定义该属性或方法的类本身访问。

首尾双下划线:一般表示特殊的方法。

这里我们再重新定义的一个student类来展示一下权限控制:

class Student():
    def __init__(self, name, age, gender):
        self._name=name  # 受保护,只能本类,子类访问
        self.__age=age  # 私有的,只能类本身访问
        self.gender=gender  # 普通的实例属性

    def _fun1(self):
        print('子类及本身可以访问')

    def __fun2(self):
        print('只有定义的类可以访问')

    def show(self):
        self._fun1()
        self.__fun2()
        print(self._name)
        print(self.__age)


stu=Student('abc', 18, 'nan')
print(stu._name)
# print(stu.__age)  # 报错AttributeError: 'Student' object has no attribute '__age'. Did you mean: '_name'?

stu._fun1()
# stu.__fun2()  # 报错AttributeError: 'Student' object has no attribute '__fun2'. Did you mean: '_fun1'?

# 私有的强行访问
print(stu._Student__age)

stu._Student__fun2()

print(dir(stu))  # 这里dir()函数可用于展示对象中所有的属性和方法

上述代码中使用了 print(stu._Student__age)去强行访问该类的私有属性,这种方法并不十分可取,在Python中我们可以使用装饰器来改善这一做法,这里就讲到Python中的属性设置

①使用@property修改方法将方法转化成属性使用。举例说明:

class Student:
    def __init__(self, name, gender):
        self.name=name
        self.__gender=gender  # 这里的self.__gender是私有的实例属性

    # 使用@property修改方法,将方法转成属性使用
    @property
    def gender(self):
        return self.__gender


stu=Student('abc', 'man')
print(stu.name, '的性别是:', stu.gender)  # stu.gender就会执行stu.gender()

如果尝试修改属性值,例如stu.gender='woman' 将会报错AttributeError,但可以——

@属性.setter 将属性设置为可写属性。

现在只需要在上述定义的类中加入下段代码,之后可以修改gender属性:

# 将gender这个属性设置为可写属性
    @gender.setter
    def gender(self, value):
        if value != 'man' and value != 'woman':
            print('性别有误,默认为man')
            self.__gender='man'
        else:
            self.__gender=value

二、继承

首先说明程序设计中子类与父类的概念:被继承的类称为父类(或者基类),新的类称为子类(或者派生类),子类如若继承了父类,就用拥有了父类所有的公有成员以及受保护的成员。

Python中的继承有如下三个特点:①在Python中一个子类可以继承多个父类;②一个父类可拥有多个子类;③若一个类未继承任何类,默认继承object类

继承的语法结构为:

class 类名(父类1, 父类2,...,父类N):

        pass

这里我们用一个例子展示一下继承(实际是单一继承)的代码:

class Person:  # 默认继承了object
    def __init__(self, name, age):
        self.name=name
        self.age=age

    def show(self):
        print(f'大家好,我叫:{self.name},我今年:{self.age}岁')


# Student继承Person类
class Student(Person):
    # 初始化方法
    def __init__(self, name, age, stuno):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.stuno=stuno  # 进行赋值


# doctor继承Person类
class Doctor(Person):
    def __init__(self, name, age, department):
        super().__init__(name, age)
        self.department=department


# 创建子类对象
stu=Student('abc', 18, '0731')
stu.show()

doctor=Doctor('zbc', 48, '外科')
doctor.show()

 与其他编程语言不一样的一点,是Python中单一子类可继承多个父类(用逗号隔开),这即是Python的多继承

class FatherA():
    def __init__(self, name):
        self.name=name

    def showA(self):
        print('父类A中的方法')


class FatherB():
    def __init__(self, name):
        self.name = name

    def showB(self):
        print('父类B中的方法')


# 多继承
class Son(FatherA, FatherB):
    def __init__(self, name, age, gender):
        # 需要调用两个父类的初始化方法
        FatherA.__init__(self, name)
        FatherB.__init__(self, name)
        self.gender=gender


son=Son('abc', 19, '男')  # 调用Son类中的__init__执行
son.showA()
son.showB()

子类继承了父类就拥有了父类中公有成员和受保护的成员,但父类的方法不一定完全适合子类的需要,此时子类也可以重写父类的方法。这即是Python的方法重写

子类在重写父类的方法时,要求方法的名称必须与父类方法的名称相同,在子类重写后的方法中可以通过super().xxx()调用父类中的方法

我们再看到继承的例子,实际上上述代码中学生的学号、医生的科室是没有被展示出来的,因为父类的方法中没有第三个参数的展示,此时可以方法重写:

class Person:  # 默认继承了object
    def __init__(self, name, age):
        self.name=name
        self.age=age

    def show(self):
        print(f'大家好,我叫:{self.name},我今年:{self.age}岁')


# Student继承Person类
class Student(Person):
    # 初始化方法
    def __init__(self, name, age, stuno):
        super().__init__(name, age)  # 调用父类的初始化方法
        self.stuno=stuno  # 进行赋值

    def show(self):
        super().show()  # 调用父类中的方法
        print(f'我来自***大学,学号是:{self.stuno}')


# doctor继承Person类
class Doctor(Person):
    def __init__(self, name, age, department):
        super().__init__(name, age)
        self.department=department

    def show(self):
        # 此处同样可选择调用父类中的方法
        print(f'大家好,我叫:{self.name},我今年{self.age}岁,我的工作科室是:{self.department}')


# 创建第一个子类对象
stu=Student('abc', 18, '0731')
stu.show()  # 调用的都是子类自己的show()方法

三、多态

多态指的就是“多种形态”,即便不知道一个变量所引用的对象到底是什么类型,任然可以通过这个变量调用对象的方法。

在程序运行过程中我们可以根据所引用对象的数据类型,动态决定调用哪个对象中的方法。

Python语言在的多态,根本不关心对象的数据类型,也不关心类之间是否存在继承关系,只关心对象的行为(方法)。只要不同的类中有相同的方法,即可实现多态。

class Person():
    def eat(self):
        print('人,吃五谷杂粮')


class Cat():
    def eat(self):
        print('猫,喜欢吃鱼')


class Dog():
    def eat(self):
        print('狗,喜欢吃骨头')


# 这三个类都有一个同名的方法
# 编写一个函数
def fun(obj):  # obj是函数的形参,定义时不知道它的数据类型
    obj.eat()  # 直接通过变量obj(对象)调用eat方法


# 创建三个类的对象
per=Person()
cat=Cat()
dog=Dog()
# 调用fun函数,Python中的多态,不关心对象的数据类型,只关心是否具有同名方法
fun(per)
fun(cat)
fun(dog)

object类

接着我们讲解一下上文已经多次出现过的object类。在Python中,它是所有类直接或间接的父类所有类都拥有object类的属性和方法。先介绍几个object类中的特殊方法:

object类中的特殊方法功能描述
__new__()由系统调用,用于创建对象
__init__()创建对象时手动调用,用于初始化对象属性值
__str__()对象的描述,返回值是str类型,默认输出对象的内存地址

 下面我们用dir()函数展示一下object类的所有属性和方法:

class Person(object):
    def __init__(self, name, age):
        self.name=name
        self.age=age

    def show(self):
        print(f'大家好,我叫:{self.name},我今年:{self.age}岁了')


# 创建Person类的对象
per=Person('abc', 18)  # 创建对象时会自动调用__init__方法
print(dir(per))
print(per)  # 自动调用了__str__方法
# 输出结果:
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
#  '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__',
#  '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
#  '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'show']
# <__main__.Person object at 0x000001646EA2F110>

 有关__str__()对对象的描述,格式可能无法达到我们的需求,此时涉及到方法重写:

# str方法重写
class Person(object):
    def __init__(self, name, age):
        self.name=name
        self.age=age

    # 方法重写
    def __str__(self):
        return '这是一个人,具有name和age两个实例属性'


# 创建Person类的对象
per=Person('abc', 18)
print(per)  # 不再输出内存地址,直接输出对象名,实际是调用__str__方法
print(per.__str__())  # 手动调用的写法,但实际是默认调用的

对象的特殊方法、属性

在Python中首尾双下划线的方法都是特殊方法,实际上还有很多运算或者说运算符也可以用特殊方法实现,下面我们来总结一些:

运算符特殊方法功能描述
+__add__()执行加法运算
-__sub__()执行减法运算
<, <=, ==__lt__(), __le__(), __eq__()执行比较运算
>, >=, !=__gt__(), __ge__(), __ne__()执行比较运算
*, /__mul__(), __truediv__()执行乘法运算,非整除运算
%, //__mod__(), __floordiv__()执行取余运算,整除运算
**__pow__()执行幂运算

以下代码可以简单进行演示:

a=10
b=20
print(dir(a))  # Python中一切皆对象
print(a+b)
print(a.__add__(b))
print(a.__sub__(b))
print(a.__lt__(b))
print(a.__le__(b))
print(a.__eq__(b))
print(a.__gt__(b))
print(a.__ge__(b))
print(a.__ne__(b))
print(a.__mul__(b))
print(a.__truediv__(b))
print(a.__mod__(b))
print(a.__floordiv__(b))
print(a.__pow__(b))

同样,首尾双下划线定义的属性称为特殊属性:

特殊属性功能描述

obj.__dict__

对象的属性字典
obj.__class__对象所属的类
class.__bases__类的父类元组
class.__base__类的父类
class.__mro__类的层次结构
class.__subclasses__()类的子类列表

依旧给出一段代码用作演示,读者可自己尝试查看结果:

class A:
    pass


class B:
    pass


class C(A, B):
    def __init__(self, name, age):
        self.name=name
        self.age=age


a=A()
b=B()
c=C('abc', 18)

print('对象a的属性字典:', a.__dict__)
print('对象b的属性字典:', b.__dict__)
print('对象c的属性字典:', c.__dict__)

print('对象a所属的类:', a.__class__)
print('对象b所属的类:', b.__class__)
print('对象c所属的类:', c.__class__)

print('A类的父类元组:', A.__bases__)
print('B类的父类元组:', B.__bases__)
print('C类的父类元组:', C.__bases__)

print('A的父类:', A.__base__)
print('B的父类:', B.__base__)
print('C的父类:', C.__base__)  # 如果父类继承了很多,结果只显示第一个父类

# 层次结构,C类继承了A类,B类,间接继承了object类
print('A类的层次结构:', A.__mro__)
print('B类的层次结构:', B.__mro__)
print('C类的层次结构:', C.__mro__)

类的深拷贝与浅拷贝

最后我们来讲一下类的深拷贝与浅拷贝,为了理解这一部分内容,先要说明变量的赋值变量赋值时,只是形成两个变量,实际上还是指向同一个对象。下面给出演示:

# 两个相互独立的类
class CPU():
    pass


class Disk():
    pass


class Computer():
    # 计算机有CPU和硬盘
    def __init__(self, cpu, disk):
        self.cpu=cpu
        self.cpu=cpu
        self.disk=disk


cpu=CPU()  # 创建了一个CPU对象
disk=Disk()  # 创建了一个硬盘对象
# 创建了一个计算机对象
com=Computer(cpu, disk)
# 变量(对象)的赋值
com1=com
print(com, '子对象的内存地址', com.cpu, com.disk)
print(com1, '子对象的内存地址', com1.cpu, com1.disk)

 可以得到输出结果是:

<__main__.Computer object at 0x000002E81C24F410> 子对象的内存地址 <__main__.CPU object at 0x000002E81C24F470> <__main__.Disk object at 0x000002E81C24F3E0>
<__main__.Computer object at 0x000002E81C24F410> 子对象的内存地址 <__main__.CPU object at 0x000002E81C24F470> <__main__.Disk object at 0x000002E81C24F3E0>

发现对com和赋了com值的com1,自身及子对象的地址都是相同的(更进一步理解,创建对象时,com和com1所指向的内存空间是一样的)

浅拷贝:

拷贝时,对象包含的子对象内容不拷贝,因此,源对象与拷贝对象会引用同一个子对象。

深拷贝

使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同

下部分演示代码需要先导入模块copy,使用的CPU对象等沿用上文代码。

# 类对象的浅拷贝
import copy
com2=copy.copy(com)  # com2是新产生的对象,com2的子对象,cpu,disk不变
print(com, '子对象的内存地址', com.cpu, com.disk)
print(com2, '子对象的内存地址', com2.cpu, com2.disk)
# 输出后可以看到com和com2的地址不同,而子对象的仍然是相同的

# 类对象的深拷贝
print('-'*30)
com3=copy.deepcopy(com)  # com3是新产生的对象,com3的子对象,cpu,disk也会重新创建
print(com, '子对象的内存地址', com.cpu, com.disk)
print(com3, '子对象的内存地址', com3.cpu, com3.disk)
# 输出后可以看到com和com3及子对象的地址均不同

总结一下,就是变量的赋值不会产生新对象,浅拷贝会产生新对象,但子对象不变,深拷贝不仅会创建新对象,子对象也会重新创建

以上就是Python中面向对象的全部基本内容了。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值