python面向对象基本思想

面向对象的思想

面向过程:—侧重于怎么做?
1.把完的成某一个需求的 所有步骤 从头到尾 逐步实现
2.根据开发要求,将某些功能独立的代码封装成一个又一个函数
3.最后完成的代码,就是顺序的调用不同的函数
特点:
1.注重步骤与过程,不注重职责分工
2.如果需求复杂,代码会变得很复杂
3.开发复杂项目,没有固定的套路,开发难度很大
面向对象:–谁来做?
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法
1.在完成某一个需求前,首先确定职责–要做的事(方法)
2.根据职责确定不同的对象,在对象内部封装不同的方法(多个)
3.最后完成代码,就是顺序的让不同的对象调用不同的方法
特点:
1.注重对象和职责,不同的对象承担不同的职责
2.更加适合对复杂的需求变化,是专门应对复杂项目的开发,提供的固定套路
3.需要在面向过程的基础上,再学习一些面向对象的语法

一 面向对象的两个核心概念

1 类的概念

类:是对一群具有相同特征或行为的事物的一个统称,是抽象的,不能直接使用(就好比,飞机图纸不能直接飞上天)
特征:被称为属性
行为:被称为方法
类 就相当于制造飞机时的图纸,是一个模板,是负责创建对象的

1.1 类的创建

1.类名:满足这类事物的名字(大驼峰命名法)
大驼峰命名法:
1.每个单词的首字母大写
2.单词与单词之间没有下划线
2.属性:这个类创建出来的对象有什么样的特征
3.方法:这个类创建出来的对象有什么样的行为
类名的确定:
名词提炼法:分析整个业务流程,出现的名词,通常就是找到的类名
定义一个只包含方法的类
class 类名:
def 方法1(self,参数列表):
pass
def 方法2(self,参数列表):
pass
方法的定义格式和函数的几乎一样
区别在于第一个参数必须是self

示例:定义一个动物类()

class Animal:            #类名
    def eat(self):       #方法1
        pass

    def dirnk(self):     #方法2
        pass

    def run(self):       #方法3
        pass

    def sleep(self):     #方法4
        pass

2 对象的概念

对象:是由类创建出来的一个具体的存在,可以直接使用(图纸创造出来的飞机,可以飞上天)
由哪一个类创建出来的对象,就拥有哪一个类的定义的属性和方法
“对象就相当于用图纸创造出来的飞机”
在程序开发中,应该先有类,再有对象
创建对象
当一个类定义完成之后,要使用这个类来创建对象
对象变量 = 类名()

示例:结合上一个例子

class Animal:
    def eat(self):
        print '吃'

    def dirnk(self):
        print '喝'

    def run(self):
        print '跑'

    def sleep(self):
        print '睡'


dog = Animal()     #创建了一个狗的对象
dog.eat()          #依次调用类中的方法
dog.dirnk()
dog.run()
dog.sleep()

#注意:当使用类名()创建对象的时候,python为对象在内存中分配空间,这也是这个对象生命周期的开始

二 面向对象中引用的概念

1 引用和对象的关系

python中变量名和对象是分离的

首先从python的变量进行理解

python中,如果要使用一个变量,不需要提前进行声明,只需要在用的时候,给这个变量赋值即可 (这个和C语言等静态类型语言不同,和python为动态类型有关)。
类比到面向对象:例如上面的创建dog这个对象的时候‘dog = Animal()‘ Animal()就是对象,dog就是引用。

第一个示例:

class Cat:
    def eat(self):
        print '小猫爱吃鱼'
    def drink(self):
        print '小猫爱喝水'

tom = Cat()
print tom  #<__main__.Cat instance at 0x7fd641cf7518> 
           #tom引用指向猫这个对象,tom变量中记录的是猫这个对象在内存的地址(16位)
addr = id(tom)  #python中的内置函数,以十进制方式显示对象的地址。
print addr     ##140558203843864

这个示例说明:引用中存储的就是对象的内存地址
第二个示例:

class Cat:
    def eat(self):
        print '小猫爱吃鱼'

    def drink(self):
        print '小猫爱喝水'


tom = Cat()       #之前的猫
lazy_cat = Cat()  #现在的猫
print tom          # <__main__.Cat instance at 0x7fd641cf7518>
print lazy_cat     # <__main__.Cat instance at 0x7fd641cf7560>
addr = id(tom)      
addr1 = id(lazy_cat)  
print addr           #  140558203843864
print addr1           # 140558203843936
分析这个示例的时候,可能都会有一个疑问?就是既然引用中存储是对象的地址,那么lazy_cat中存储的对象的地址就应该和之前那个tom里面地址是一样的。
但是实际的数值是不一样的,这就有两点原因:
1 说明了内存分配地址的时候,就是你创建对象的时候。之前tom引用这个对象的时候,内存给这个对象分配了空间,tom就记录这个地址,而在lazy_cat引用这个对象的时候,内存又重新给这个对象重新分配了地址,lazy_cat记录新的地址。
2 说明了同一个类可以创建不同的对象,尽管两个引用的地址不同,但是还是指向的同一个类。那有人会觉得这样会不会矛盾,不矛盾!python的解释机制就是翻译一行解释一行,解释一行,输出一行。那么在重新分配的时候tom里面内存其实已经被系统回收了!

三 内置的方法

1 初始化方法

初始化方法:
类名() 就可以创建一个对象
当使用类名()创建对象的时候,python解释器会自动执行以下操作
1.为对象在内存中分配空间 —创建对象
2.调用初始化方法
在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对_ init _方法进行改造
1.把希望设置的属性值,定义成_ init _ 方法的参数
2.在方法内部使用self.属性名 = 形参 接收外部传递的参数
3.在创建对象时,使用类名(属性1,属性2..)调用

class Cat:                             
    def __init__(self, new_name):        

    # self.属性名 = 属性的初始值                  
    # self.name = 'Tom'                  
        self.name = new_name             
    # 在类中 任何方法都可以使用self.name             
    def eat(self):                       
        print '%s 爱吃鱼' % self.name       


tom = Cat('Tom')                         
print tom                              
print tom.name    #Cat对象name属性                       
tom.eat()        # Cat对象的eat行为

这里写图片描述
这里写图片描述

以上的结果就可以看出,self和tom拥有同一个地址,self可以理解为是tom的引用。注意: 只有需要从外界传递的参数, 才需要把这些参数定义成初始化方法的形参

在继承关系中,父类初始化已经好设置属性,子类也需要设置属性,那就必须在子类的初始化下,重新调用父类的初始化。

class Bird:
    def __init__(self):
        self.hungry = True

    def eat(self):
        if self.hungry:
            print 'Aaaaa~'
            self.hungry =False
        else:
            print 'No Thanks~'

class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
        Bird.__init__(self)    #子类中重新调用父类的初始化
    def sing(self):
        print self.sound

bird = Bird()
bird.eat()

littlebird = SongBird()
littlebird.eat()
littlebird.sing()

这里写图片描述
没有在子类重新调用父类初始化时,会报错未找到父类初始化属性
这里写图片描述

2_del_方法 和 _ str_方法,_ new _ 方法

2.1_del_方法用法

在python中
当使用类名()创建对象时,为对象分配完空间后,自动调用_ init_方法
当一个对象被从内存中销毁前(把这个对象从内存中删除掉),会自动调用 _ del_方法
应用场景
_ init_改造初始化方法,可以让创建对象更加灵活
_ del_如果希望在对象被销毁前,再做一些事情,可以考虑一下_ del_方法

class Cat():
    def __init__(self, new_name):
        self.name = new_name
        print '%s 来了' % self.name

    def __del__(self):
        print '%s 走了' % self.name

# tom 是一个全局变量(s所以当我们的代码全部执行完之后,系统才会对tom 这个对象进行回收)
tom = Cat('Tom')
print tom.name
print '*' * 50     

这里写图片描述

结果可以看出,系统在执行所有程序之后,会自动调去析构方法_ del _

class Cat():
    def __init__(self, new_name):
        self.name = new_name
        print '%s 来了' % self.name

    def __del__(self):
        print '%s 走了' % self.name

# tom 是一个全局变量(s所以当我们的代码全部执行完之后,系统才会对tom 这个对象进行回收)
tom = Cat('Tom')
print tom.name
# del 关键字 可以删除一个对象 del关键字自己调用__del__方法
del tom
print '*' * 50

这里写图片描述
结果可以看出,出现del关键字会立刻调用_ del _ 方法。

有了_ del _ 这个概念之后,对象的生命周期就可以理解了。
生命周期(出生到死亡)                  
一个对象从调用类名()创建,生命周期开始         
一个对象的__del__方法一但被调用,生命周期结束   
在对象的生命周期内,可以访问对象属性,或者让对象调用方法 

2.2_ str _ 方法

在python中,使用python输出对象变量,默认情况下,会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
希望使用print输出对象变量时,能够打印自定义的内容,就可以利用_ str_ 这个内置方法了

class Cat():                  
    def __init__(self,name):  
        self.name = name         
tom = Cat('Tom')              
print tom                     

这里写图片描述

class Cat():
    def __init__(self,name):
        self.name = name
    def __str__(self):
        # 必须返回一个字符串
        return  '我是mini'
tom = Cat('Tom')
print tom

这里写图片描述
2.3 _ new _ 方法

使用类名()创建对象时,python的解释器首先会调用new方法为对象分配空间
_ new _是一个由object基类提供的内置的静态方法,主要有两个作用:
在内存中为对象分配空间
返回对象的引用
python的解释器获得对象的引用后,将引用作为第一个参数,传递给_ init _方法。
_ new _:负责给对象分配空间 _ init _(初始化方法)负责给对象初始化

那么问题来了,如何重写_ new _ 方法呢?
我们之前的实验可以看出使用一个类创建对象的时候,创建不同的对象,系统就会为这个对象分配不同的地址,那如何让每次创建的对象都分配到相同的地址呢或者让_ init _ 只执行一次?
举一个例子:

class MusicPlsyer(object):
    instance = None            
    def __new__(cls, *args, **kwargs):
        # 第一个参数cls:哪一个类调用就传递哪一个类
        # 第二个参数*args:多值的元组参数
        # 第三个参数**kwargs:多值的字典参数
        # 1.创建对象的时候,new方法会被自动调用
        print '创建对象,分配空间' # 重写了父类的方法
        # 2.为对象分配空间
        # 注意:__new__方法是一个静态方法,在调用的时候,第一个参数为cls
        if cls.instance is None:
            # 调用父类的方法,为第一个对象分配空间
            cls.instance = object.__new__(cls)
        # 3.返回对象的引用
        return cls.instance
    def __init__(self):
        print '初始化播放器'

# 创建播放器对象
player1 = MusicPlsyer()
print player1
player2 = MusicPlsyer()
print player2

这里写图片描述

举第二个例子:如何让初始化方法也执行一次

class MusicPlayer(object):
    instance = None
    init_flag = False

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls)
        return cls.instance

    def __init__(self):
        # 1.判断是否执行过初始化动作
        if MusicPlayer.init_flag:
            return
        # 2.如果没有执行过,再执行初始化动作
        print '初始化方法'
        MusicPlayer.init_flag = True


player1 = MusicPlayer()
print player1
player2 = MusicPlayer()
print player2

这里写图片描述

3 map 函数

map():python内置函数,接收两个参数,一个是函数,一个是序列
map依次将序列传递给函数,并把结果作为新的列表返回

def f(x):
    return x*x

print map(lambda x:x*x,[1,2,3,4,5])

这里写图片描述

4 私有属性和私有方法

在实际开发中,对象的某些属性或方法可能只希望在对象的内部使用,而不希望在外部被访问到
私有属性 就是 对象 不希望公开的 属性
私有方法 就是 对象 不希望公开的 方法

4.1 普通类的私有属性和私有方法
class Men:
    def __init__(self, name):
        self.name = name
        self.__age = 19   ##双下划线表示私有属性

    def __secret(self):    ##双下划线表示私有方法
        print '%s的年龄是%d' % (self.name, self.__age)


Bob = Men('Bob')
Bob.secret() #私有方法,不允许在外界直接访问

这里写图片描述

class Men:
    def __init__(self, name):
        self.name = name
        self.__age = 19   ##双下划线表示私有属性

    def _secret(self):    ##在对象的方法内部,是可以访问对象的私有属性的
        print '%s的年龄是%d' % (self.name, self.__age)


Bob = Men('Bob')
Bob._secret()

这里写图片描述

4.2 继承私有属性和方法

如果有A和B两个类,A是父类,B是子类,如果需要从子类来读取被隐藏在父类的代码,怎么办?
你会发现:
1 在子类的对象方法中,不能访问父类的私有属性
2 在子类的对象方法中,不能调用父类的私有方法
3 在外界可以访问父类的公有属性和调用公有方法
4 在外界不能直接访问对象的私有属性/调用私有方法
5 在子类方法的内部能访问父类的公有属性和调用父类的公有方法
现在就有一种思想:
我们就可以在父类A中建立一个共有方法,在这个共有方法里面调用父类A中的私有属性和方法。然后再通过子类B来调用A类的共有方法来解决这个问题

示例:

class A(object):
    def __init__(self):
        self.num1 = 100
        self.__num2 = 200    #私有属性

    def __text(self):         #私有方法
        print '私有方法%d %d' % (self.num1, self.__num2)

    def test(self):            #建立的共有方法
        print '父类的公有方法 %d' % self.__num2
        self.__text()


class B(A):
    def demo(self):
        print '子类方法 %d' % self.num1
        self.test()          #子类调用


b = B()
print b
b.demo()

这里写图片描述
4.3 类属性和类方法

一切皆对象
类也是一个特殊的对象 –类对象
类对象可以拥有自己的属性和方法
1.类属性
2.类方法

4.3.1 类属性

类属性和实例属性
概念和使用
类属性就是给类对象定义的属性
通常用来记录与这个类相关的特征
类属性不会用于记录具体对象的特征

比如有这样一个需求:
定义一个工具类
每件工具都有自己的name
知道使用这个类,创建了多少个工具对象
示例:

class Tool(object):
    # 1.使用赋值语句定义类属性,记录所有的工具数量
    count = 0
    def __init__(self,name):
        self.name = name
        # 让类属性的值 +1
        Tool.count += 1

# 创建工具对象(对象在创建的时候,会自动调用初始化方法)
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')

# 输出工具对象的总数
# 使用 类名.count 来获取属性名
print Tool.count

这里写图片描述

4.3.2 类方法

类属性就是针对类对象定义的属性
使用赋值语句在class关键字下方可以定义类属性
类属性用于记录与这个类相关的特性
类方法就是针对类对象定义的方法
在类方法内部就可以直接访问类属性或调用类方法

class Toy(object):
    # 使用赋值语句定义类属性,记录所有玩具的数量
    count = 0

    @classmethod
    def show_toy_count(cls):
        # 在类方法的内部,可以直接访问类属性或调用类方法
        print '玩具对象的数量 %d' % cls.count

    def __init__(self,name):
        self.name = name
        Toy.count += 1

# 创建玩具对象
toy1 = Toy('乐高')
toy2 = Toy('玩具车')

# 调用类方法
Toy.show_toy_count()

这里写图片描述
4.4 静态方法

在开发的时候,如果需要在类中封装一个方法,这个方法
既不需要访问实例属性或者调用实例方法
也不需要访问那类属性或者调用类方法
这个时候可以把这个方法封装成一个静态方法

class Cat(object):
    @staticmethod
    # 不访问实例属性/类属性
    # 静态方法不需要传递第一个参数self
    def call():
        print 'skr!'

# 通过类名. 调用静态方法
# 不需要创建对象,就可以直接调用
Cat.call()

5 新式类和旧式类

5.1 定义

object是Python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看
新式类:以object为基类的类,推荐使用
经典类:不以object为基类的类,不推荐使用
在python3.X中定义的类时,如果没有指定父类,会默认使用object作为基类–python3.x中定义的类都是新式类
在python2.x中定义类时,如果没有指定父类,则不会以object作为基类

class A(object):
    pass


a = A()
print dir(a)  # 查看内置的属性和方法


class B:
    pass


b = B()
print dir(b)

这里写图片描述

新式类旧式类最明显的区别在于继承搜索的顺序发生了改变
即:
经典类多继承搜索顺序(深度优先):
先深入继承树左侧查找,然后再返回,开始查找右侧
新式类多继承搜索顺序(广度优先):
先在水平方向查找,然后再向上查找。

猜你喜欢

转载自blog.csdn.net/yangkaiorange/article/details/82454198