Python面向对象介绍(二)

复习

类的基本概念

Python提供了一些内置的类 例如 int() float() list()…

  • 类是一个可以创建对象的对象,其类型为type;
  • 类创建的对象可以称之为类的实例;
    p = Person(),p就是Person类创建出来的对象,也可以称p是Person类的实例或者p是实例化对象,Person是类对象。
  • 可以向类对象中添加变量和函数;
    在类中添加的变量称之为属性,添加的函数称之为方法。在类中添加的属性和方法都是公共的,也是是该类所有的实例化对象都可以访问。
  • 在实例化对象当中我们可以添加属性
    添加方法:对象.属性名 = 属性值
  • 注意在类中添加的方法会默认传递一个参数,默认为self,但是改为a、b、c都不会报错;
  • 实例对象为什么可以访问类中的属性和方法(属性和方法的查找流程是什么)
    当程序调用一个对象的属性时,解析器会现在当前对象中寻找是否含有该属性:
    如果有,则直接返回当前对象的属性值。
    如果没有,则去当前对象所对应的类对象中去寻找,若类对象含有该属性值则返回它,否则就会报错。
class Person:
    # 定义变量,所有实例的公共属性
    name = 'public'
    # 添加函数,所有实例的公共方法
    def speak(self):
        print('Hello!')
# 创建对象
p1 = Person()
p2 = Person()
# 访问属性
p2.name = 'John'
print('p1.name = ', p1.name)
print('p2.name = ', p2.name)
# 试图访问没有添加的属性
print('p1.sex = ', p1.sex)

运行结果:

在这里插入图片描述

从上述结果看,p1没有给自己添加name属性,打印的结果是类对象的公共属性,p2添加了name属性,其打印的结果就是自己添加的,证明属性的查找顺序先是自己当前是否有这个属性,找不到再去对应的类对象里找。p1没有添加sex属性,而类对象中也没有改属性,最后就报错。

综上,类对象和实例对象都可以保存属性和方法。
如果这个属性(方法)是所有实例共享的,则应该保存到类对象中。
如果这个属性(方法)是某个实例独有的,则应该保存到实例对象中。
一般情况下,属性保存到实例对象中,方法保存到类对象中。

1. self参数

在类中定义方法会默认传递一个参数self。当有对象调用该方法时,self传递的就是调用方法的当前实例对象。self表示的意思就是当前对象(自己)。

示例代码:

class Person:
    # 方法每次调用时解析器会自动传递一个实参
    # 一般默认将这个对象命名为self
    def speak(self):
        print(f'你好,我是{self.name}')
        print(self)

# 创建Person的实例
p1 = Person()
p2 = Person()
# 分别添加name属性
p1.name = '张无忌'
p2.name = '杨过'
# 如果是p1调用,则第一个参数就是p1对象
p1.speak()
print(p1)
# 如果是p2调用,则第一个参数就是p2对象
p2.speak()
print(p2)

运行结果:

在这里插入图片描述

2. 特殊方法

如果实例对象都必须有一个共有属性,但各不相同。此时,手动添加属性容易遗忘,而且也很麻烦。
在这样的情况下,我们希望在创建对象的时候就自动添加该属性值。要达到这样的目的,就要使用类的特殊方法。

在类中可以定义一些特殊方法(魔术方法):

  • 特殊方法__开头__结尾。
  • 特殊方法不需要自己写语句调用,会在特殊的时候自动调用。

示例代码:

class Person:
    # init 最重要的特殊方法
    def __init__(self, name):
        # 通过self向新创建的对象中初始化属性
        self.name = name

    def speak(self):
        print(f'你好,我是{self.name}')

# 创建实例对象
p1 = Person('张无忌')
p1.speak()
p2 = Person('杨过')
p2.speak()
p3 = Person('韦小宝')
p3.speak()

运行结果:

在这里插入图片描述

从上述结果看,可以得到以下结论:

  • __init__方法在实例对象创建的时候自动调用;
  • 通过调用__init__方法自动向实例对象创建公共属性,且该属性的值可以被传递的参数赋值,达到各不相同的效果。

通常,类的基本结构如下:

class 类名([父类]):
    公共属性...
    
    # 对象的初始化方法
    def __init__(self,....):
        ....
        
    # 其他的方法
    def method1(self):
        ...
    def method2(self):
        ...

3. 类的封装

3.1 封装的引入

目前我们可以直接通过对象.属性的方式来修改属性的值,这种做法非常的不安全。
通常,我们在写类的时候希望有一种方式来增强数据的安全性,其体现在:

  1. 属性不能随意修改;
  2. 即时允许修改属性,也不能修改成任意的值。
    想要达成上述两种特性,可以通过封装的方式来解决。

3.2 封装的使用

封装是面向对象的三大特性之一

  • 封装是指隐藏对象中一些不希望被外界访问到的属性和方法。
  • 将对象的属性名,修改成外界不知道的名字。

示例代码:

# 封装name、age属性
class Person:
    def __init__(self, name, age):
        self.hidden_name = name
        self.hidden_age = age
        
    def speak(self):
        print(f'大家好,我是{self.hidden_name}')

p = Person('John', 25)
p.name = 'Hunt' # 如果不知道属性的具体变量名,猜想名字修改属性
p.speak()
p.hidden_name = 'Hunt' # 知道了属性的具体变量名来修改属性
p.speak()

运行结果:

在这里插入图片描述

从上述结果看,如果用户没有源码,大概率不知晓name属性的确切变量名,就无法通过对象.属性的方式来修改属性的值,达到了一定程度的封装。但是如果知道确切的变量名,还是可以随意修改的。所以,上述代码的封装是一种软封装。

如果确实有修改属性的需求,可通过提供一个getter和setter方法,允许外部可以访问并修改属性。

class Person:
    def __init__(self, name, age):
        self.hidden_name = name
        self.hidden_age = age
           
    # get_name()这个方法用来获取对象的name属性
    def get_name(self):
        return self.hidden_name

    # set_name()这个方法用来修改对象的name属性
    def set_name(self, name):
        self.hidden_name = name

    def speak(self):
        print(f'大家好,我是{self.hidden_name}')

p = Person('John', 25)
print('通过get方法获取属性: ', p.get_name())
p.set_name('Hunt')
p.speak()

运行结果:

在这里插入图片描述

另外,即便允许修改属性,修改时也可以添加一些限制。

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

    # get_age()这个方法用来获取对象的age属性
    def get_age(self):
        print('用户读取了age属性')
        return self.hidden_age

    # set_age()这个方法用来修改对象的age属性
    def set_age(self, age):
        print('用户修改了age属性')
        # 校验传入的age参数,如小于0则防止修改属性并报错
        if age > 0:
            self.hidden_age = age
        else:
            print('年龄不能小于0, 属性修改失败')

    # get_name()这个方法用来获取对象的name属性
    def get_name(self):
        return self.hidden_name

    # set_name()这个方法用来修改对象的name属性
    def set_name(self, name):
        self.hidden_name = name

    def speak(self):
        print(f'大家好,我是{self.hidden_name}')

p = Person('John', 25)
p.set_age(-1)
print('age = ', p.get_age())

运行结果:

在这里插入图片描述

从上述结果看,当尝试修改负的年龄时,程序会给出错误提示,阻止属性的修改。

综上,使用封装,确实增加了类定义的复杂度,但是它也确保了数据的安全性:

  1. 隐藏了属性名,使调用者无法随意修改对象的属性;
  2. 增加getter和setter方法,很好的控制属性的是否只读和修改;
  3. 使用setter方法设置属性,可以增加数据的验证,确保数据的值是正确的;
  4. 可以在读取和修改属性的时候做一些其他的处理。

3.3 封装的补充

此处给出一种更彻底的封装方式:

  • 可以对对象的属性使用双下划线的方式 __xxx;
  • 其实这种封装的方式是Python自动的给属性起了一个名字;
  • 这个名字的样式是 _类名__属性名,例如__name等同于_Person__name
class Person:
    def __init__(self, name):
        self.__name = name
    # get_name()这个方法用来获取对象的name属性
    def get_name(self):
        return self.__name

    # set_name()这个方法用来修改对象的name属性
    def set_name(self, name):
        self.__name = name

    def speak(self):
        print(f'大家好,我是{self.__name}')

p = Person('John')
print('通过get方法获取属性: ', p.get_name())
p.__name = 'Hunt' # 尝试通过对象.属性修改
p.speak()
p.set_name('Hunt') # 通过提供的方法修改
p.speak()

运行结果:

在这里插入图片描述

从上述结果看,通过p.__name不能修改属性,但依旧可通过提供的get和set方法访问和修改属性。这种封装方式比之前的要更隐秘些。

4. 装饰器

@property用来修饰方法的,它的作用如下:

  1. @property来创建只读属性,会将方法转换成相同名称的属性。
  2. 可以防止属性被修改。
class Person:
    def __init__(self, name):
        self.__name = name
    # 使用装饰器将get_name方法变成属性
    @property
    def get_name(self):
        print('get方法执行了')
        return self.__name

p = Person('John')
print(p.get_name) # 此时get_name方法已经被转换成只读属性
p.get_name = 'Hunt' # 尝试修改属性

运行结果:

在这里插入图片描述

从上述结果看,被@property 修饰的方法使用时不能再加括号,而且转换成的属性是只读的。

如果想要修改name属性,python里提供了setter方法的装饰器。

class Person:
    def __init__(self, name):
        self.__name = name
    # 使用装饰器将get_name方法变成属性
    @property
    def get_name(self):
        print('get方法执行了')
        return self.__name

    # setter方法的装饰器 @属性名.setter
    # 此时提供的属性是get_name, 但是set_name也会被转换成同名的属性
    @get_name.setter
    def set_name(self, name):
        print('set方法执行了')
        self.__name = name

p = Person('John')
print(p.get_name) # 此时get_name方法已经被转换成只读属性
p.set_name = 'Hunt' # 尝试修改属性, 赋值运算符右边的内容相当于传递的参数
print('访问get_name属性: ', p.get_name)
print('访问set_name属性: ', p.set_name)

运行结果:

在这里插入图片描述

从上述结果看,set_name和get_name都被装饰器转换为同名的属性,但是@property修饰的方法转换的属性是只读的,@属性名.setter修饰的方法是可修改的。在上述代码中,set_name和get_name属性指向的都是__name

5. 类的继承

5.1 继承的引入

继承是面向对象的三大特性之一

  1. 继承提高了代码的复用性;
  2. 让类与类之间产生了关系,有了这个关系,才有了后面的多态的特性。

假如说我们已经定义一个动物类,现在又想定义一个狗类,涉及到如下问题:

  1. 如果直接修改动物类,修改会比较麻烦,会违反ocp原则;
  2. 如果直接创建个狗类,创建一个新类麻烦,需要大量的复制粘贴和修改。

狗是动物的一种,拥有动物的所有共性,定义狗类的直接想法是从Animal类中继承它的属性和方法。类继承的思想由此而来。

5.2 继承的使用

在定义类的时候,可以在类名的后面的括号中指定当前类的父类(超类、基类)。
在创建类的时候,如果省略了父类,则默认父类为object。
object是所有类的父类,所有类都继承与object,比如intfloat还有自定义的Person类都是继承object。

class Animal:
    def run(self):
        print('动物会跑')
    def sleep(self):
        print('动物睡觉')

a = Animal()
a.run()

class Dog(Animal):
    def run(self):
        print('狗跑')
    # 新增一个方法,为狗类独有
    def look_home(self):
        print('狗看家')

d = Dog()
# Dog继承了Animal类,可以调用run和sleep方法
# 如果Dog自己定义run方法,则会调用自己的
d.run()
d.sleep()
# Dog对象特有的方法
d.look_home()

运行结果:

在这里插入图片描述

从上述结果看,Dog继承了Animal类,可以调用所有Animal的方法,同时也可以调用自己独有的方法。

如果子类中有和父类同名的方法,则通过子类调用方法时,会调用子类的方法,而不是父类的方法。这个特点称之为方法的重写(覆盖,override).
上述代码中Dog对象调用的就是自己的方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值