6.1 对象

对象(object)

内存中专门用来存储数据的一块区域
id、type、value

面向对象(oop)

- python是一门面向对象的编程语言
- 所谓的面向对象语言:语言中的所有操作都是通过对象来进行的
- 面向过程的编程语言:
    - 面向过程指的是将我们的程序的逻辑分解成一个一个的步骤
        通过对每个步骤的抽象,来完成程序
    - 例子:
        - 孩子上学
            1、妈妈起床
            2、妈妈上厕所
            3、妈妈洗漱
            4、妈妈做早饭
            5、妈妈叫孩子起床
            6、孩子上厕所
            7、孩子吃饭
            8、孩子背着书包上学校
    - 面向过程的编程思想将一个功能分解成一个一个小的步骤
        我们通过完成这些小步骤来完成一个程序(符合人类思维,编写比较简单)
    - 但是这种方式编写代码的往往只适用于一个功能
        但如果要实现别的功能,即使功能相差极小,也往往要重新编写代码,可复用性比较低
- 面向对象的编程语言:
    - 面向对象的编程语言,关注的是对象,而不关注过程
    - 对于面向对象的语言来说,一切都是对象  
    - 例子:
        1、孩他妈起床教孩子上学
    - 面向对象的编程思想,将所有的功能统一保存到对应的对象中
        要使用某种功能,直接找到对应的对象即可
    - 这种方式编写的代码,比较容易阅读,并且易于维护,容易复用
    - 但是这种方式的编写,不太符合常规思维,编写起来稍微麻烦
- 简单归纳:面向对象的思想
    1、找对象
    2、搞对象

面向对象的三大特征

- 封装
- 继承
- 多态

类(class)

- 我们目前所学对象都是python内置对象
- 但是内置对象不能满足所有的需求,所以在开发中经常需要自定义一些对象
- 类,简单理解它相当于一个图纸,在程序中我们需要根据<类>来创建对象
- 类就是对象的图纸!(一切皆对象,类也是一个对象)
- 对象是类是实例
- 如果多个对象是通过一个类创建的,则称这些对象是一类对象
- 如int()、float()、bool()...等等,这些都是类
- a = int(10) # 创建一个int类的实例,等价于 a = 10
- 我们自定义的类都需要使用大写字母开头,使用大驼峰命名法(帕斯卡命名法)来对类命名
    - class MyClass():
例子:
    a = int(10) # 创建一个int类的实例
    b = str('hello') # 创建一个str类的实例

类的定义及对象创建

- 使用class关键字来定义类,语法和函数很像!
语法:
    class 类名([父类]):
        pass
    
例子:
    class MyClass():
        pass
        
    print(MyClass) --->   <class '__main__.MyClass'>
- 使用类MyClass来创建一个对象
    # 使用类来创建对象,就像调用一个函数一样
    mc = MyClass() # mc就是通过MyClass创建的对象,对象mc就是类MyClass的实例
    mc_2 = MyClass()
    mc_3 = MyClass()  ---> mc、mc_2、mc_3都是MyClass的实例,他们都是一类对象
- isinstance(a, b)用来检查一个对象a是否是一个类b的实例    
    result = isinstance(mc, MyClass) ---> resul = True
    
    print(mc) --->   <__main__.MyClass object at 0x052FB230> 
    print(type(mc)) --->   <class '__mian__.MyClass'>
    print(MyClass) --->   <class '__mian__.MyClass'> 
    print(type(MyClass)) --->   <class 'type'>

对象的创建流程

- 一切皆对象,类也是一个对象!
- 类就是一个用来创建对象的对象
- 类是type类型的对象,定义类实际上就是定义了一个type类型的对象 
- 现在我们通过MyClass这个类创建的对象都是一个空对象,什么都没有,是一个空盒子
- 可以向对象中添加变量,对象中的变量称为属性
    语法:  
        对象.属性名 = 属性值
    例:
        mc.name = '孙悟空'
        mc_2.name = '猪八戒'
    调用:
        print(mc.name)  ---> '孙悟空'
总结: 
    1、创建一个变量
    2、在内存中创建一个新对象
    3、将对象的id赋值给变量

类的定义

- 类和对象都是对现实生活中或者程序中的内容的抽象
- 实际上所有的事物都有两部分组成:
    1、数据(属性)
    2、行为(方法)
例:
# 定义一个表示人的类
class Person :   # 括号可写可不写 class Person():,并且类名首字母大写,
                # 如果多个单词拼在一起则每个单词开头大写,如SchoolBoy(所谓大驼峰命名法)
    # 在类的代码块中,可以定义变量和函数
    # 在类中所定义的变量,将会成为所有实例的公共属性
    name = '孙悟空' # 公共属性,所有实例都可以访问
    
    # 在类中也可以定义函数,类中定义的函数被称为方法
    # 这些方法可以通过该类的所有实例来访问
    def say_hello(self):
        #方法每次调用时,解析器都会自动传递第一个实参
        第一个实参,就是调用方法的对象本身,如:
            <__main__.Person object at 454643464>
            如果是p1调用,则第一个参数就是p1对象
            如果是p2调用,则第一个参数就是p2对象
            所以,我们一般将这个形参命名为self(因为该形参代表的是对象本身)
        # say_hello这个方法,可以显示如下格式:
            你好!我是XXX, 但是在方法中不能直接访问类中的属性(如name = '孙悟空')
        可以这么做来实现:
        print('你好!我是%s' % self.name) # p1调用的时候,self.name返回的是p1.name;
        p2调用的时候,self.name返回的是p2.name...
        print('hello')
        
# 创建person的实例
p1 = Person()
p2 = Person()

# 调用公共属性:   对象.属性名
print(p1.name)  ---> 孙悟空

# 调用方法:  对象.方法名()
# 方法调用和函数调用的区别:
    方法调用:默认传递一个参数,所以方法中至少要定义一个形参(形参名字无所谓,但是会有规范命名,一般该形参命名为self)
    函数调用:传递几个参数,就会有几个实参
p1.say_hello()   ---> 虽然括号里没有参数,但是解析器会默认传递一个实参
                      (因此定义方法时至少要有一个形参)
p2.say_hello()

- 属性和方法查找的流程:
    当我们调用一个对象的属性时,解析器会事先在当前对象中寻找是否含有该属性,
        如果有,则直接返回当前对象的属性值
        如果没有,则会到该对象所属的类中寻找,若有则返回类对象的属性值
        如果再没有则报错!
- 属性的删除     
    del p2.name ---> 删除p2的name属性
- 类对象和实例对象都可以保存属性和方法
    - 如果这个属性(方法)是所有实例共享的,则应该保存在类对象中
    - 如果这个属性(方法)是某个实例独有的,则应该保存在实例对象中
- 一般情况下,属性保存在实例对象中,方法保存在类对象中

类的特殊方法init

目前来讲,对于Person类来说name属性是必须要定义的,并且每个对象中的name属性基本上都是不同的
而我们现在是在定义完对象之后,将name属性手动添加到对象中,这种方式容易出现错误
我们希望,在创建对象时,必须设置name属性,如果不设置对象则无法创建
    并且属性的创建应该是自动完成的,而不是在创建对象以后手动完成
而这个时候我们可以定义一些特殊方法(也称魔术方法)来满足我们的需求
- 特殊方法都是以__开头,以__结尾的方法(均为双下划线)
- 特殊方法不需要我们自己调用,不要尝试去调用特殊方法
- 特殊方法将会在特殊时刻自动调用(解析器调用)
- 学习特殊方法:
    1、特殊方法什么时候调用
    2、特殊方法有什么作用(init:初始化)
- 创建对象的流程
- p1 = Person()的运行流程
    1、创建一个变量
    2、在内存中创建一个新对象
    3、__init__(self)方法执行
    4、将对象的id赋值给变量
- __init__会在对象创建以后立即执行
- __init__可以用来向新创建的对象中初始化属性
例:
class Person():
    self.name = '孙悟空' # 此属性在类对象中(是所有根据该类创建的实例共有的)
    # 调用类创建对象时,类后边的所有参数都会因此传递到init()中
    def __init__(self, name):
        # 通过self向新建对象中初始化属性
        self.name = name # 此属性在实例对象中,与类对象中属性有本质区别
    
    def say_hello(self):
        print('大家好, 我是%s' % self.name)

由于__init__(self, name)方法有两个形参,第一个形参self解析器会自动传递实参
但是第二个形参需要我们手动传入实参(不传入实参就会报错)
当我们根据类对象来创建实例时,需要传入self之后的参数:
p1 = Person('孙悟空') ---> p1.name = '孙悟空' # 不需要传入参数self
# 调用Person里的方法say_hello():
p1.say_hello() ---> 大家好, 我是孙悟空
同理,
p2 = Person('猪八戒') ---> p2.name = '猪八戒'
# 调用Person里的方法say_hello():
p2.say_hello() ---> 大家好, 我是猪八戒

类的基本结构

class 类名([父类]): # 如果没有父类名,则括号可以不写, []表示可选的意思
    
    # docstring,即编写文档,在这里指的是类的文档(docstring可以写在三个地方:模块或包、对象、函数)
    # 通过help()函数即可输出一份有格式的文档
    ''' docstring for ClassName '''
    
    公共属性...  # 如 gender = 'male'
    
    # 特殊方法
    def __init__(self, ...)  ---> 初始化狗的一些基本属性
        ...
    
    # 其他的方法
    def method_1(self, ...)
        ...
    
    def method_1(self, ...)
        ...
    
    ...
    
练习:
    尝试自定义一个表示狗的类(Dog)
        属性:
            name
            age
            gender
            height
            weight
            ...
        方法:
            yell()
            run()
            bite()
            jump()
            cry()
            ...
参考答案:
class Dog():
    '''
        表示狗的类
    '''
    def __init__(self, name, age, gender, height):
        self.name = name
        self.age = age
        self.gender = gender
        self.height = height
    
    def yell(self):
        '''
            狗叫的方法
        '''
        print('汪汪汪~~~')
    
    def bite(self):
        '''
            狗咬的方法
        '''
        print('我咬你')
    
    def run(self):
        print('%s在快乐地奔跑者~~~' % self.name)
        
# 创建实例
dog_1 = Dog('旺财', 8, 'male', 30) ---> 当类中有__init__方法时,创建实例时要传入参数
dog_1.run()
dog_1.bite()
dog_1.yell()

注意:目前我们可以通过 对象.属性 的方式来修改属性的值
    这个方式导致对象中的属性可以随意修改,很不安全
dog_1.name = '阿黄'
dog_1.age = -10
- 现在我们就需要一种方式来增强数据的安全性
    1、属性不能随意修改(让你改你才能改)
    2、属性不能修改为任意的值(比如年龄不能是负数)

在类中使用<递归函数>时的注意事项

class MyClass(object):
    def IterationFunction(self, name):
        # 基线条件
        if ...
            return ...
            
        # 递归条件(--调用函数本身时要加上self--)
        # return IterationFunction(name[1:-1]) ---> 错误    
        return self.IterationFunction(name[1:-1]) ---> 正确

封装

封装简介

- 封装是面向对象的三大特性之一
- 封装指的是隐藏对象中不希望被外部所访问到的属性或方法
- 如何隐藏一个对象中的属性?
    - 将对象的属性名,修改为一个外部不知道的名字(此方法防君子不防小人)
    如:
    class Dog():
        def __init__(self, name):
            self.hidden_name = name
        
        def say_hello(self):
            print('大家好,我是%s' % self.hidden_name)
    
    dog_2 = Dog('阿黄')
    dog_2.name = '小黑' # 此方法不能修改狗的姓名了,因为狗的姓名属性是hidden_name而不是name
    # 若一定要改狗的名字也可以:
    dog_2.hidden_name = '小黑' 
    (因此说此方法是防君子不防小人,如果你执意要改,是可以修改的)
- 如何获取(修改)对象中的隐藏属性?
    - 需要提供一个getter和setter方法使外部可以访问到属性(通用方法)
    - getter 获取对象中的指定属性(get_属性名)
    - setter 设置对象的指定属性(set_属性名)
例子:
    class Dog():
        def __init__(self, name):
            self.hidden_name = name # 不一定要命名为hidden_name
        
        def say_hello(self):
            print('大家好,我是%s' % self.hidden_name)
        
        def get_name(self):
            """
                get_name()用来获取对象的name属性
            """
            return self.hidden_name
            
        def set_name(self, name):
            """
                set_name()用来修改对象的name属性
            """
            self.hidden_name = name
        
        
        
    dog_3 = Dog('旺财')
    
    # 调用 get_name()方法来获取name属性值
    dog_3.get_name()
    # 调用 set_name()方法来修改name属性值
    dog_3.set_name('阿花')

- 使用封装,确实增加了类的定义的复杂度,但也确保了数据的安全性
    1、隐藏了属性名,使调用者无法随意修改对象中的属性
    2、增加了gettere和setter方法,很好地控制了对象的属性是否是<只读>的
        如果希望是只读的,则可以直接去掉setter方法
        如果希望属性不能被外部访问,则可以去掉getter方法
    3、使用setter方法设置属性,可以增加数据的验证,确保数值是正确的
        如:可以在set_name()中增加一些逻辑判断
        def set_name(self, age):
            if age > 0:
                self.hidden_age = age
    4、使用getter方法获取属性,使用setter方法设置属性
        可以在获取属性和修改属性的同时做一些其他的处理,如:
        def set_name(self, age):
            if age > 0:
                print('用户修改了属性')
                self.hidden_age = age
    5、使用getter方法也可以做一些计算

隐藏类中的属性

- 可以为对象的属性使用双下划线, __XXX
- 双下划綫的属性,是对象的隐藏属性(只能在类的内部访问,无法通过对象访问)  
- 其实隐藏属性只不过是Python自动修改为属性改了个名字
    实际上是将名字修改为:_类名__属性名,如 __name --> _Person__name
    所以当属性名为_name时,实际上属性名为_Person_name,所以通过_Person__name可以修改属性值
    - 因此所谓隐藏是假隐藏(本质上跟我们定义的hidden_name是一样的)
        防君子不防小人,在Python中约定俗成不要修改此类__xxx变量
- 由于__开头的属性,实际上依然可以在外部访问,所以这种方式我们一般不用
    一般我们会将一些私有属性(不希望被外部访问)以_开头(单下划线)
    一般情况下,使用_开头的属性都是私有属性,没有特殊需要不要修改私有属性(全靠人的自觉)

装饰器

- 我们调用getter和setter方法时,需要加括号,如dog_1.get_name(),就显得很麻烦
    能不能有办法,使得调用方法跟调用属性一样不需要加括号呢?
- 这就需要装饰器
举例子:
class Person():
    def __init__(self, name, age):
        self._name = name # 加单下划线_name标明该变量不希望被修改,但是靠人自觉
        self._age = age
        
    # property装饰器,用来将一个getter方法,转换为对象的属性
    # 添加完property装饰器后,就可以像调用属性一样使用getter方法
    # 使用property装饰器的方法的名称,必须和属性名是一样的
    @property
    def name(self):
        print('getter方法调用了~')
        return self._name
    
    # setter方法的装饰器: @属性名.setter
    @name.setter
    def name(self, name):
        print('setter方法调用了~')
        self._name = name
    
    @property
    def age(self):
        print('getter方法调用了~')
        return self._age
    
    # setter方法的装饰器: @属性名.setter
    @age.setter
    def age(self, age):
        print('setter方法调用了~')
        self._age = age

# 注意:如果不提供@property,则不能有@属性名.setter
    (两者有递进关系,试想一下:不能读取怎么可能设置)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值