python 面向对象编程

面向对象编程概念(OOP)

面向过程是编年史: 按照时间先后顺序, 一条一条的记录发生的事情.
面向对象是记传史: 按照每个王侯将相的维度, 记录这个人是谁, 有哪些特点, 做过哪些事情
所以史记其实是中国古代的一个非常成功的应用了 OOP 思想的著作

OOP是一种编程思想. 所谓 “思想”, 指的是解决一类问题时,
使用的一系列的方法手段, 一套组合拳.
我们看看这套组合拳都有哪些招式

抽象
抽象是指对现实世界问题和实体的本质表现, 行为, 特征进行建模;
抽象的反义词是 "具体“

面向对象的本质是:
把我们现实生活中的一些东西变成计算机访问代码
这个过程就是抽象过程

举例:
class student 整个学生类,一个学生类想要让其代表一个学生
现实生活中的学生的属性:姓名 年龄 老师 父母 职业
根据实际情况选择我们关注的信息,你就表示什么信息
提取你想要的核心信息

抽象和具体的差异:信息量的多少

抽象的本质, 是抓住我们重点关注的主体, 而忽略一些我们不需要关注的细节;
写程序也是一样, 我们不可能把一个现实事物所有的信息都在程序中表示出来,
而是只表示我们需要用到的


类和实例

实例: 就是对象

  1. 类是施工图纸, 里面有房子的重要信息(户型, 面积, 朝向, 层高等等).
  2. 实例是造好的房子, 房子造好了, 才能住进去, 才能娶到媳妇.
    如果房子烂尾了, 美好生活就只是在YY~
  3. 通过同一张图纸可以建造出N个相同格局的房子,
    那么这N个实例就都是属于同一个类(回忆下我们的type函数).
  4. 通过同一张图纸可以建造出不同的房子, 每一个房子都有一个房号,
    5号楼1单元101这种, 其实就是实例的id(回忆下我们的id函数).
  5. 这N个房子里, 住的人也不相同, 比如101是我住,
    隔壁102住的是老王. 这个就是实例的值不同

总结:
对象就是类的实例, 是一个客观事物的抽象.
把多个对象按一定的规则归一归类, 就成为了类.
Python中的对象包含三个部分: id, 类型, 值


封装/接口 (接口:广义的接口)

1.封装描述了对数据/信息进行隐藏的观念, 对象的使用者只能通过接口来访问/修改对象的数据.
2.封装的好处是减轻调用者的使用负担, 调用者只需要知道接口如何使用, 而不需要知道对象的具体实现.
3.封装这个概念当然不是只存在于面向对象思想中. 回忆计算机网络协议栈的分层结构, 也是为了达到封装的效果.

只要通话双方能听懂对方的语言, 就可以正常使用电话通话. 而不需要理解电话的任何实现细节

封装涉及两种不同的角色:
1.类的实现者
2.类的调用者(使用者)

封装的含义:
让调用者不必知道实现者是实现类的细节,
只要知道类怎么使用就行了(类提供的接口)

为什么要封装? <<代码大全>>
<<代码大全>>开篇就在讨论这件事情,
开篇第一章:软件开发的本质
本质叫管理程序的复杂程度,不能让程序太复杂,
复杂-> 没办法维护->代码写不下去了->就会出现问题
如何管理代码的复杂程度?
封装
把很多不必要的细节隐藏起来,然后让使用者更加简单


组合/包含

多个小的类组合成一个大的类, 来解决一个相对复杂的问题.
组合表示 has-a 语义

例如, 我们用代码表示一个 “学校” , 那么可以先创建一个学校类.
学校类中又包含了教师类, 学生类, 行政人员类, 后勤人员类等.

组合的目的是代码重用

包含:(has-a)
实现一个类的过程中,用其他类的示例来作为这个类的成员

继承:(is-a)
继承表示的是一个什么东西,包含表示的是有一个什么东西


派生/继承-

1.基于一个现有的类, 通过继承这个类创建一个新的类.
2.如果基于类A用继承的方式创建了类B,
我们就将这个过程成为类A派生出了类B.
类A称为父类(或超类), 类B称为子类.
3.子类对象包含了父类对象的所有属性和操作.
4.继承表示了 is-a 这样的语义

继承的目的还是代码重用


多态

多态是OOP中比较难理解的概念, 我们先看一个 花木兰替父从军 的例子

花木兰替父(父亲名叫花弧)从军, 我们先创建这么两个类

父类对象:
数据:
姓名: 花弧
性别: 男
年龄: 60
方法
冲锋杀敌(花弧精通降龙十八掌)
子类对象:
数据:
姓名: 花木兰
性别: 女
年龄: 28
方法
冲锋杀敌(木兰精通打狗棒法)
涂脂抹粉(这个是子类具备的方法)

1.木兰先去军营报道, 将军的花名册上写着花弧的名字. 相当于是一个父类对象的引用. 而木兰替父从军, 相当
于父类引用指向一个子类的对象.
2.两军交战了, 将军发号施令: 花弧听令, 冲锋杀敌! 将军是对象的使用者, 将军通过父类的引用, 让子类执
行"冲锋杀敌"这个动作, 实际上阵杀敌的是木兰而不是花弧, 因此木兰肯定是按照自己熟悉的武功招式来杀敌.
3.将军并不知道 “花弧” 这个引用是父类的还是子类的, 将军只知道这个类有冲锋杀敌的方法, 而不知道这个
“花弧” 其实还有涂脂抹粉的动作
4.战争结束, 木兰衣锦还乡, 这时候亲朋好友都知道木兰的底细, 使用"花木兰"这个子类对象的引用来称呼. 这
时, 木兰就可以涂脂抹粉了

总结一下:
1.本质上讲 多态是在运行时能够自动的识别出对象的类型, 并使用对应类型的方法来完成一定的动作.
2.多态也是一种 “信息隐藏”, 让调用者了解的信息越少, 那么使用起来负担就越低.
3.刚才我们说的封装, 只是让调用者不知道这个类里面的实现; 而我们的多态, 是让调用者连这个类究竟是哪
个类都不需要关心

1.多态对我们简化代码有非常大的意义. 我们先看一段C语言代码. 这个代码表示的是某品牌路由器检查所有网口状态的代码.
2.每一种型号的路由器的端口数目/端口类型可能都不同, 因此每一个或几个这样的类型必须提供一个专门的函数

switch(board_type) {
case Board2304:
case Board2306:
CheckPortStatus1();
break;
case Board3300:
case Board3302:
CheckPortSatus2();
break;
case Board5300:
CheckPortStatus3();
break;
case Board6300:
CheckPortStatus4();
break;
...
}

每新增一种型号, 都意味着需要将这些 switch-case 语句都修改一遍. 而且每个地方都要判定所有的板类型.
如果使用多态机制, 每一个新的类型就新增一种子类, 只需要在需要的地方使用父类的对象调用即可

board.CheckPortStatus();

自省/反射
自省和反射是一个意思. 顾名思义, 就是认清自己是谁. 指对象在程序运行时能获取到一些自己的信息

1.dir/id/type/name/doc 这些内置属性和方法就是自省的具体体现.
2.其实C++中也有typeid这样的方法来进行一部分的"自省"的功能. 只不过是和C++的设计理念背道而驰, 因
此不推荐使用

回忆我们之前的dir函数

def Hello():
print('hello')
print(dir(Hello))
# 执行结果
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__',
'__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__',
'__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure',
'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
print(Hello.func_name)
# 执行结果
Hello

dir 可以把一个对象都包含哪些信息,所有的主要特征,都能获取到


属性和方法

创建类
使用class关键字创建一个类. 创建类的语法和创建函数很像

创建一个函数

def FunctionName(arg):
    'function doc string' # 文档字符串
    function_suite # 函数体

创建一个类

class ClassName(object):
    'class doc string' # 文档字符串
    class_suite # 类体

1.创建一个类, 实际上是创建了一个自定制的类型. 其实Python中, 类和类型是统一的. 而不像很多其他编程
语言那样, 区分内置类型和"类类型".
2.类的定义语句一般出现在模块的顶级缩进; 但是Python并不强制要求. 类的定义也可以出现在类/函数, 或者
其他语句块中. 当然作用域也就不一样了

延伸:
创建一个列表
a = []
等价于:
a = list()
这里的list也是一个类,通过这样的方式我们可以创建列表
这个类的示例,也就是一个列表对象

python内置垃圾回收机制,所以创建完一个对象之后
不用了也不用手动释放,都会有解释器自己回收

()你当前的这个类继承自那个副类
也可以不写这个括号
如果你不写,默认继承:object类
python 支持多继承所以括号内可以几个,用逗号隔开

类里面要有的东西:
属性(成员变量)
方法(成员函数)

注意:
类里面的def 函数的参数必须带一个self
并且 self 是第一个参数
可以写成this,但是不推荐

class Test:
    """
    这是一个用来测试的类
    """
    def Print(self):
        """
        这是一个方法(java) 成员函数(C++) 实例方法(python)
        self必须是第一个参数,表示当前这个实例的引用,类似于this
        """
        print('hehe')

如何创建对象
Test 表示类名,加上一个括号表示创建一个具体的实例
t 实例的对象的引用
python中不用写new

t =  Test() #常见
t.Print()
hehe

t 就是实例,这个实例我们默认传到print这个第一个参数里面

很少写成下面这样
t.Print() 等价于 Test.Print(t)
将t当作 self 这个参数 传递进去

class Test:
    """
    这是一个用来测试的类
    """
    # 实例属性在不同方法之间都可以分别创建
    def Init(self):
        self.x = 10

    def Print(self):
        """
        这是一个方法(java) 成员函数(C++) 实例方法(python)
        self必须是第一个参数,表示当前这个示例的引用,类似于this
        """
        # 实例属性必须在示例方法中创建
        print('hehe',self.x)

t =  Test() #常见
t.Init()
t.Print()
hehe 10

如果 t.Init() 没有调用
报错,x 找不到

问题:我们每次都要调用这个Init()会不会太复杂
类似于构造函数,我们创建一个特殊的函数
就会在类被实例化过程中,自动被调用

修改后:

class Test:
    def __init__(self):
        """
        通常我们把一个类的实例属性都写到__init__里面
        """
        self.x = 10

    def Print(self):
        print('hehe',self.x)

t =  Test() 
t.Print()
hehe 10

和其他函数一样,这个也可以传参数
同理支持函数的一切功能

class Test:
    def __init__(self,num):
        """
        通常我们把一个类的实例属性都写到__init__里面
        """
        self.x = num

    def Print(self):
        print('hehe',self.x)

t =  Test(11) 
t.Print()
hehe 11

除了成员变量和成员函数,还有一些情况
静态成员函数和静态成员变量

class Test:
    # 构建一个静态的成员变量    静态属性,类属性
    val = 10
    def __init__(self,num):
        self.x = num

    def Print(self):
        print('hehe',self.x)

print(Test.val)
10

class Test:
    val = 10
    def __init__(self,num):
        self.x = num

    def Print(self):
        print('hehe',self.x)
    
    def StaticPrint():
        print('hehe')
    StaticPrint = staticmethod(StaticPrint)

Test.StaticPrint()
hehe

staticmethod 是一个函数,本质上是一个类,视为一个函数
类似于放函数

修改,美化:

class Test:
    val = 10
    def __init__(self,num):
        self.x = num

    def Print(self):
        print('hehe',self.x)
    
    # 装饰器
    @staticmethod
    def StaticPrint():
        print('hehe')

Test.StaticPrint()

新问题: 我们知道静态函数可以直接访问静态变量–不能

class Test:
    val = 10
    def __init__(self,num):
        self.x = num

    def Print(self):
        print('hehe',self.x)

    @staticmethod
    def StaticPrint():
        print('hehe',val)

Test.StaticPrint()
NameError: name 'val' is not defined

修改:

class Test:
    val = 10
    def __init__(self,num):
        self.x = num

    def Print(self):
        print('hehe',self.x)
    
    @staticmethod
    def StaticPrint():
        """
        静态方法 != C++静态成员函数
        此时无法访问静态成员变量(类属性)
        """
        print('hehe')
    
    # 能将类的实例扩展过来
    @classmethod
    def ClassPrint(cls):
        """
        类方法 == C++静态成员函数
        cls表示当前类
        """
        print('hehe',cls.val)

print(Test.val)
Test.ClassPrint()
10
hehe 10

总结:
python进行定义类的时候要考虑:
1.实例属性(非静态成员变量)
2.实例方法(非静态成员函数)
3.类属性(静态成员变量)
4.类方法(静态成员函数)
5.静态方法


属性的访问权限

学过C++/Java的同学们知道, 使用关键字public/private/protected等关键字, 可以对属性的访问权限进行控制. 这也是
“封装” 思想的一种具体体现.
Python中使用双下划线前缀( __ )来表示私有成员

class Test:
    def __init__(self):
        self.__x = 10

t = Test()
print(t.__x)
AttributeError: 'Test' object has no attribute '__x'

访问不到,为什么?
他被当成了私有,只有你内部才能使用


类之间的关系

在设计一个程序时, 需要设计多个有一定关系的类.
而这种类和类之间的关系, 最主要有两种. 组合和继承

组合
就是让一个类里面包含其他类的实例

class Student:
    pass

class Teacher:
    pass

组合

class School:
    def __init__(self) :
        self.students = [Student(),Student()]
        self.teachers = [Teacher(),Teacher()]

继承:

class Cat:
    def __init__(self):
        self.name = '嘿 '
        self.type = '猫'

    def Speak(self):
        print('喵')

class AshortCat(Cat):
    # 子类中如果出现和父类同名的函数或者变量
    # 就会相互覆盖,尤其是需要注意 __init__
    # def __init__(self):
    #     Cat.__init__(self)
    # 或者:
    def __init__(self):
        super().__init__(self)


cat = AshortCat()
cat.Speak()
print(cat.name)
# 喵
# 嘿 

理解Python的多态

Python中的多态和继承没有直接关系

C++里面的多态,你必须要有一个副类的指针,或者副类的引用
他指向一个子类的实例,然后调用副类里面的某个虚函数,
然后他自动调用到子类的方法

def add(x,y):
    return x+y

print(add(10,20))
print(add('hallo','world'))
print(add([1,2,3,],[4,5,6]))
30
halloworld
[1, 2, 3, 4, 5, 6]

多态本质是封装的更进一步
封装指的是调用你这不必知道使用的对象内部的细节
多态值得是调用者连这个使用的类是啥都不需要知道,
只要知道这个类支持某个操作就行了


类和实例的常用内建函数

issubclass
判定一个类是不是另外一个类的子类.

class Parent(object):
    pass
class Child(Parent):
    pass
print issubclass(Child, Parent)
# 执行结果
True

isinstance

判定一个对象是不是一个类的实例.
class C(object):
    pass
c = C()
print(isinstance(c, C))
# 执行结果
True

hasattr, getattr, setattr, delattr
用来对一个类/对象的属性进行操作和判定

class Test:
    val = 10

print(getattr(Test,'val')) #10

删除

print(delattr(Test,'val')) #10
print(Test.val) #AttributeError: type object 'Test' has no attribute 'val'

class C(object):
    x = 100
print hasattr(C, 'x')
print getattr(C, 'x')
setattr(C, 'x', 200)
print(C.x)
delattr(C, 'x')
# 执行结果
True
100
200

dir
查看类/对象的所有属性和方法

class C(object):
pass
print(dir(C))
# 执行结果
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

vars
和dir类似. dir只是返回了有哪些属性, 但是没显示属性的值. vars将值也取了出来

class C(object):
pass
print vars(C)
# 执行结果
{'__dict__': <attribute '__dict__' of 'C' objects>, '__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc_None}

super
获取到当前类/对象的父类
super本质也是一个工厂函数, 创建了一个父类的对象


使用特殊方法定制类

特殊方法的作用
我们前面学的 init 只是其中一个特殊方法. 其实类还有很多特殊方法, 通过这些特殊方法, 可以完成
运算符重载
模拟标准类型

class Time:
    def __init__(self, hour,min):
        self.hour = hour
        self.min = min

t = Time(13,30)
print(t)
<__main__.Time object at 0x000002790F051760>
打印的是这个对象的他的一些属性信息

修改:

class Time:
    def __init__(self, hour,min):
        self.hour = hour
        self.min = min
    
    # 这个函数返回一个字符串,相当于描述我们这个对象
    # 如何被序列化成一个字符串
    def __str__(self):
        return f"{self.hour}:{self.min}"

t = Time(13,30)
print(t)
13:30

print 这个内建函数先把对象转成字符串
先把对象序列化,转成字符串 然后打印字符串的结果
如果不__str__
就按照默认方法进行打印,打印这个id

例子:

class Time:
    def __init__(self, hour,min):
        self.hour = hour
        self.min = min
    
    def __str__(self):
        return f"{self.hour}:{self.min}"
    
    def __len__(self):
        return len(self.__str__())

t = Time(13,30)
print(t)
print(len(t))
13:30
5  #13:30的长度

我们以 str 为例写一个简单的代码. 更多的特殊方法, 同学们课后自己学习

class Time(object):
    def __init__(self, h, m):
        self.hour = h
        self.minute = m
    def __str__(self):
        return '[%02d:%02d]' % (self.hour, self.minute)
t = Time(13, 5)
print(t)
# 执行结果
[13:05]
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值