面向对象的使用

Posted mChenys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象的使用相关的知识,希望对你有一定的参考价值。

目录

1. 类和对象的概念

类 是对一群具有 相同 特征 或者 行为 的事物的一个统称,是抽象的,不能直接使用

  • 类的 特征 被称为 属性
  • 类的 行为 被称为 方法

对象

对象 是由类创建出来的一个具体存在,可以直接使用,由哪一个类创建出来的对象,就拥有在 哪一个类 中定义的属性和方法

类和对象的关系

  • 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
  • 类 只有一个,而 对象 可以有很多个
  • 类 中定义了什么 属性和方法,对象 中就有什么属性和方法

2. 定义简单的类(只包含方法)

在 Python 中要定义一个只包含方法的类,语法格式如下:

class 类名:

    def 方法1(self, 参数列表):
        pass
    
    def 方法2(self, 参数列表):
        pass

方法 的定义格式和之前学习过的函数 几乎一样,区别在于第一个参数必须是 self,稍后会介绍 self

注意

  • 类名 的 命名规则 要符合 大驼峰命名法
  • 调用类的方法时,不需要传递 self 参数

例如:

class Cat:
    """这是一个猫类"""

    def eat(self):
        print("小猫在吃鱼")

    def drink(self):
        print("小猫在喝水")

3. 创建对象

当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:

对象变量 = 类名()

例如上面定义的Cat类之后创建对象可以这样操作:

tom = Cat()
tom.drink()
tom.eat()

注意:
在 Python 中使用类 创建对象之后,tom 变量中 仍然记录的是 对象在内存中的地址,也就是 tom 变量 引用 了 新建的Cat对象。
使用 print 输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示),例如:
<main.Cat object at 0x1031a0a00>

4. self参数

在 Python 中,要 给对象设置属性,非常的容易, 只需要 对象名 . 属性名 =xxx 的形式就可以设置一个属性了, 例如:

tom = Cat()
# 设置对象的属性 name
tom.name = "Tom"
print(tom.name)

但是这种在类的外部定义属性的方式是不推荐使用, 因为不利于维护, 另外在运行时,没有找到属性,程序会报错, 通常会将属性定义在类的内部, 稍后会介绍。

当调用对象的方法时, 方法的第一个参数self就指代当前调用者对象, 例如:

class Cat:
    """这是一个猫类"""

    def eat(self, food):
        print("self==> %s, 正在吃:%s" % (self, food))


tom = Cat()
tom.eat("鱼")
print("tom==>", tom)

输出结果:

self==> <__main__.Cat object at 0x102c74a00>, 正在吃:鱼
tom==> <__main__.Cat object at 0x102c74a00>

从结果可以看出self和tom的输出的引用指向的地址都是同一个对象, 由此也可以证明self代表的就是调用该方法的对象。
既然self指代的是当前类的对象, 那么自然可以通过self来访问该对象的 属性和方法

5. 类的初始化方法

当使用 类名() 创建对象时,会 自动 执行以下操作:

  • 为对象在内存中 分配空间 —— 创建对象
  • 为对象的属性 设置初始值 —— 初始化方法(init), 这个 初始化方法 就是 __init__ 方法,__init__ 是对象的内置方法, 它是专门用于给对象初始化属性使用的

在初始化方法内部定义属性

  • __init__方法内部使用 self.属性名 = 属性的初始值 就可以 定义属性
  • 在定义属性时,如果 不知道设置什么初始值,可以设置为 None,None 关键字 表示 什么都没有,可以将 None 赋值给任何一个变量
  • 定义属性之后,再使用 Cat 类创建的对象,都会拥有该属性
class Cat:

    def __init__(self):
        print("这是一个初始化方法")
        # 定义用 Cat 类创建的猫对象都有一个 name 的属性
        self.name = "Tom"
        self.color = "黑色"

    def eat(self):
        print("%s 爱吃鱼" % self.name)

    def print_info(self):
        print("内部调用,name:%s,color%s" % (self.name, self.color))


# 使用类名()创建对象的时候,会自动调用初始化方法 __init__
tom = Cat()
tom.eat()
tom.print_info()

# 外部修改属性的值
tom.name = "Jack"
tom.color = "白色"
# 在类外面访问对象的属性
print("外部调用,name:%s,color:%s" % (tom.name, tom.color))

输出结果:

这是一个初始化方法
Tom 爱吃鱼
内部调用,name:Tom,color黑色
外部调用,name:Jack,color:白色

在初始化方法内部接收参数定义属性

如果希望在 创建对象的同时,就设置对象的属性,那么需要给__init__方法添加参数, 然后在创建对象时,使用 类名(属性1, 属性2...) 创建, 例如:

class Cat:

    def __init__(self, name, color):
        # 定义属性并使用参数赋值
        self.name = name
        self.color = color

    def print_info(self):
        print("当前对象:%s,name:%s,color%s" % (self, self.name, self.color))


# 使用初始化参数创建对象
tom = Cat("Tom", "黑色")
tom.print_info()

jack = Cat("Jack", "白色")
jack.print_info()

输出结果:

当前对象:<__main__.Cat object at 0x105304a00>,name:Tom,color黑色
当前对象:<__main__.Cat object at 0x105319160>,name:Jack,color白色

6. 类的内置方法使用

__del__ 方法

当一个 对象被从内存中销毁 前,会 自动 调用__del__方法, 一个对象的生命周期开始是从调用 类名() 创建时,而生命周期的结束就是__del__回调的时候, 对象销毁后就不能再使用了.

class Cat:

    def __init__(self, name):
        # 定义属性并使用参数赋值
        self.name = name
        print("%s 创建了" % self.name)

    def __del__(self):
        print("%s 销毁了" % self.name)


# tom 是一个全局变量
tom = Cat("Tom")

# del 关键字可以删除一个对象
del tom

# 对象删除后,就不能再使用了,会报错
print(tom)

输出结果:

Tom 创建了
Tom 销毁了

Traceback (most recent call last):
  File "/Users/chenyousheng/workspace/python/Learn/day01/main.py", line 128, in <module>
    print(tom)
NameError: name 'tom' is not defined

__str__ 方法

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

class Cat:

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

    def __str__(self):
        return "这是一只肥猫:%s" % self.name


tom = Cat("Tom")
print(tom)

输出结果:

这是一只肥猫:Tom

7. 身份运算符

身份运算符用于 比较 两个对象的 内存地址 是否一致 —— 是否是对同一个对象的引用
在 Python 中针对 None 比较时,建议使用 is 判断

is 与 == 区别:

is 用于判断 两个变量 引用对象是否为同一个
== 用于判断 引用变量的 是否相等

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a 
False
>>> b == a
True

8. 私有属性和方法

在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线,定义的就是 私有 属性或方法

  • 私有属性 就是 对象 不希望公开的 属性
  • 私有方法 就是 对象 不希望公开的 方法
class Women:

    def __init__(self, name):
        self.name = name
        # 私有属性赋值
        self.__age = 18
    
    # 方法私有
    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiaofang = Women("小芳")
# 私有属性,外部不能直接访问
# print(xiaofang.__age)

# 私有方法,外部不能直接调用
# xiaofang.__secret()

有什么办法可以强制访问私有属性和方法?
在调用的时候在属性或者方法前加上_类名, 例如上面代码中改成如下方式调用就可以访问了
xiaofang._Women__age
xiaofang._Women__secret()

因此,Python 中,并没有 真正意义 的 私有。

9. 继承

面向对象三大特性:

  • 封装 根据 职责 将 属性 和 方法 封装 到一个抽象的 类 中
  • 继承 实现代码的重用,相同的代码不需要重复的编写
  • 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度

单继承

继承的概念:子类 拥有 父类 的所有 方法 和 属性

继承的语法

class 子类名(父类名):
  • 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
  • 子类 中应该根据 职责,封装 子类特有的 属性和方法

继承的传递性

C 类从 B 类继承,B 类又从 A 类继承, 那么 C 类就具有 B 类和 A 类的所有属性和方法

方法的重写

当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
重写 父类方法有两种情况:

  • 覆盖 父类的方法
    如果父类的方法实现 和 子类的方法实现,完全不同,就可以使用 覆盖 的方式,在子类中 重新编写 父类的方法实现, 重写之后,在运行时,只会调用 子类中重写的方法,而不再会调用 父类封装的方法。
    具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法

  • 对父类方法进行 扩展
    如果子类的方法实现 中 包含 父类的方法实现 ,父类原本封装的方法实现 是 子类方法的一部分,就可以使用 扩展 的方式

    1. 在子类中 重写 父类的方法
    2. 在需要的位置使用 super().父类方法 来调用父类方法的执行
    3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现

关于 super

在 Python 中 super 是一个 特殊的类,super() 就是使用 super 类创建出来的对象, 常用在重写父类方法时,调用父类中封装的方法实现

在 Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
这种方式,目前在 Python 3.x 还支持这种方式,但是这种方法 不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改

# 父(基)类
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self, food):
        print("%s正在吃%s" % (self.name, food))

    def drink(self):
        print("%s正在喝水" % self.name)

    def sleep(self):
        print("%s正在睡觉" % self.name)

    def run(self):
        print("%s正在奔跑" % self.name)


# 子类
class Dog(Animal):
    def bark(self):
        print("%s正在吠..." % self.name)

    # 重写父类的方法
    def sleep(self):
        print("%s趴在地上睡觉" % self.name)


# 孙子类
class XiaoTianDog(Dog):
    # 扩展父类的方法
    def bark(self):
        print("%s往天上飞去" % self.name)
        super().bark()


dog = Dog("旺财")
# 调用父类方法
dog.eat("热狗")
# 调用重载父类的方法
dog.sleep()
# 调用自己的方法
dog.bark()

xiao_tian_dog = XiaoTianDog("哮天犬")
# 调用扩展父类的方法
xiao_tian_dog.bark()

输出结果:

旺财正在吃热狗
旺财趴在地上睡觉
旺财正在吠...
哮天犬往天上飞去
哮天犬正在吠...

父类的 私有属性 和 私有方法

子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性 或 私有方法
子类对象 可以通过 父类 的 公有方法 间接 访问到父类的 私有属性 或 私有方法

多继承

子类 可以拥有 多个父类,并且具有 所有父类 的 属性 和 方法, 语法如下:

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

多继承的使用注意事项

如果 不同的父类 中存在 同名的方法和属性,子类对象 在调用时,会调用 哪一个父类的呢?
Python 中针对 类 提供了一个 内置属性 __mro__ 可以查看 方法 和 属性 的搜索顺序, MRO是 method resolution order,主要用于 在多继承时判断 方法、属性 的调用 路径

class A:
    def __init__(self):
        self.name = "A"

    def test(self):
        print("A test run...")


class B:
    def __init__(self):
        self.name = "B"

    def test(self):
        print("B test run...")


class C(A, B):
    # 就是一个空语句,不做任何事情,一般用做占位语句
    pass


c = C()
c.test()
print(c.name)
# 查看C类的方法和属性的搜索顺序
print(C.__mro__)

输出结果:

A test run...
A
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

从最后一个结果也可以看到此时C类的方法和属性的搜索顺序是: C>A>B

  • 在搜索时,是按照 mro 的输出结果 从左至右 的顺序查找的
  • 如果在当前类中 找到,就直接执行,不再搜索
  • 如果 没有找到,就查找下一个类 中是否有,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到,程序报错

注意:
在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object 作为该类的 基类 而Python 2.x则不会, 因此为了保证编写的代码能够同时在 Python 2.x 和 Python 3.x 运行!如果没有父类,建议统一继承自 object
class 类名(object):

查看object类的内置方法

class D(object):
    pass


print(dir(D()))

输出结果:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

10. 多态

多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!
多态是以 继承重写父类方法 为前提的

class Dog(object):

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

    def game(self):
        print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(Dog):

    def game(self):
        print("%s 飞到天上去玩耍..." % self.name)


class Person(object):

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

    def game_with_dog(self, dog):
        print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))

        # 让狗玩耍
        dog.game()


# 1. 创建狗对象
wangCai = Dog("旺财")
xiaoTian = XiaoTianDog("哮天犬")

# 2. 创建一个小明对象
xiaoming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangCai)
xiaoming.game_with_dog(xiaoTian)

输出结果:

小明 和 旺财 快乐的玩耍...
旺财 蹦蹦跳跳的玩耍...
小明 和 哮天犬 快乐的玩耍...
哮天犬 飞到天上去玩耍...

11. 类的结构

  • 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
  • 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部

类对象

Python 中 一切皆对象:

  • class AAA: 定义的类属于 类对象,在程序运行时,类 同样会被加载到内存,在程序运行时,类对象 在内存中 只有一份,使用 一个类 可以创建出 很多个对象实例
  • obj1 = AAA() 属于 实例对象

类对象除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法

  • 类属性
  • 类方法
    通过 类名. 的方式可以 访问类的属性 或者 调用类的方法

类属性的使用

类属性 不会用于记录 具体对象的特征,通常用来记录 与这个类相关 的特征

class Tool(object):
    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

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

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 查看实例对象创建的个数
print("现在创建了 %d 个工具" % Tool.count)

输出结果:

现在创建了 3 个工具

属性的获取机制

在 Python 中 属性的获取 存在一个 向上查找机制, 首先会在实例对象内部查找,如果没有找到就会向上查找类对象的属性
因此,要访问类属性有两种方式:

  1. 类名.类属性
  2. 对象.类属性 (不推荐)

注意:
使用 对象.类属性 = 值 赋值语句,只会 给对象添加一个属性,而不会影响到 类属性的值

还是上面的Tool类为例:

# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
# 这种方式只会添加一个实例属性,而不会修改类属性
tool3.count = 100
# 查看实例对象创建的个数
print("现在创建了 %d 个工具" % Tool.count)
print("tool1-->count:%s" % tool1.count)
print("tool2-->count:%s" % tool2.count)
print("tool3-->count:%s" % tool3.count)

输出结果:

现在创建了 3 个工具
tool1-->count:3
tool2-->count:3
tool3-->count:100

注意:

  • 当对象的实例属性和类属性同名时, 通过实例对象调用那么会优先调用实例属性, 使用类名调用会优先调用类属性
  • 当存在继承关系时, 优先查找子类, 搜索属性如下:
    子类实例属性>父类实例属性>子类类属性>父类类属性
class A:
    name = "classA"

    def __init__(self):
        self.name = "A"
 

class B(A):
    name = "classB"

    def __init__(self):
        super().__init__()
        self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

B
classB

修改如下:

class A:
    name = "classA"

    def __init__(self):
        self.name = "A" 


class B(A):
    name = "classB"

    def __init__(self):
        super().__init__()
        # self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

A
classB

再次修改:

class A:
    name = "classA"

    def __init__(self):
        # self.name = "A"
        pass


class B(A):
    name =以上是关于面向对象的使用的主要内容,如果未能解决你的问题,请参考以下文章

python面向对象开发的自我理解

Python开发——9.面向对象编程

Python简介

Python面向对象编程总结(上)

window里面安装python

PYTHON面向对象编程指南pdf