零基础入门学Python(十一)—— 类和对象

零基础入门学Python系列内容的学习目录 → \rightarrow 零基础入门学Python系列内容汇总

  需要学习的基础知识有:对象、继承、组合、类、类对象、实例对象、绑定等。

1. 简单介绍一下对象

  我们之前已经听说过封装的概念,把乱七八糟的数据扔进列表里边,是数据层面的封装;把常用的代码段打包成一个函数,是语句层面的封装;对象其实也是一种封装的思想,对象的来源是模拟真实世界,把数据和代码都封装在了一起。
  举个例子,乌龟就是真实世界的一个对象,那么通常应该如何来描述这个对象呢?是不是可以把它分为两部分来说?
    1)可以从静态的特征来描述,例如绿色的、10kg重、有四条腿,有外壳等,这是静态方面的描述。
    2)还可以从动态的行为来描述,例如会爬、会缩头等,这些都是从行为方面进行描述的。

2. 对象 = 属性 + 方法

  Python 中的对象也是如此,一个对象的特征称为“属性”,一个对象
的行为称“方法”。
  如果把“乌龟”写成代码,可以是下边这样:

class Turtle: 
     # Python中的类名约定以大写字母开头
     # 特征的描述称为属性,在代码层面来看其实就是变量
    color = 'green'
    weight = 10
    legs = 4
    shell = True
    
     # 方法实际就是函数,通过调用这些函数来完成某些工作
    def climb(self):
        print("我正在很努力地向前爬")
    def shrink(self):
        print("我要把头缩回去了")

  以上代码定义了对象的特征(属性)和行为(方法),但还不是一个完整的对象,将定义的这些称为(Class)。需要使用类来创建一个真正的对象,这个对象就叫作这个类的一个实例(Instance),也叫实例对象(Instance Objects)。
  创建一个对象,也叫类的实例化。下面我们来创建一个真正的实例对象:

  >>> # 先运行上面编写的关于“乌龟”的类代码
  >>> tt = Turtle()
  >>>

  注意,类名后边跟着的小括号,这跟调用函数是一样的,所以在Python中,类名约定用大写字母开头,函数用小写字母开头,这样更容易区分。另外赋值操作并不是必需的,但如果有把创建好的实例对象赋值给一个变量,那这个对象就没办法使用,因为没有任何引用指向这个实例,最终会被Python的垃圾收集机制自动回收。
  如界要调用对象里的方法,使用点操作符(.)即可,其实我们早已经开始使用了:

  >>> tt.climb()
  我正在很努力地向前爬
  >>> tt.shrink()
  我要把头缩回去了

3. 面向对象编程

3.1 self是什么

  经过前面的例子会发现对象的方法都会有一个self参数,那这个self到底是个什么东西呢?Python的self其实就相当于C++的this指针。Python的self参数就是同一个道理,由同一个类可以生成无数对象,当一个对象的方法被调用的时候,对象会将自身的引用作为第一个参数传给该方法,那么Python就知道需要操作哪个对象的方法了。
  举个例子:

class Ball:
    def setName(self, name):
        self.name = name
    def kick(self):
        print("我叫%s, " % self.name)

  >>> a = Ball()
  >>> a.setName(“小明”)
  >>> a.kick()
  我叫小明
  >>>

3.2 Python的魔法方法

  据说,Python 的对象天生拥有一些神奇的方法,它们是面向对象的Python 的一切。它们是可以给我们的类增加魔力的特殊方法,如果我们的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被Python所调用,而这一切都是自动发生的。
  Python的这些具有魔力的方法,总是被双下划线所包围,其中一个最基本的特殊方法:_ _init_ _()。通常把_ _init_ _()方法称为构造方法 ,它的魔力体现在只要实例化一个对象,这个方法就会在对象被创建时自动调用。
  其实,实例化对象时是可以传入参数的,这些参数会自动传入_ _init_ _()方法中,可以通过重写这个方法来自定义对象的初始化操作。举个例子:

class Potato:
    def __init__(self, name):
        self.name = name
    def kick(self):
        print("我叫%s " % self.name)

  >>> p = Potato(“土豆”)
  >>> p.kick()
  我叫土豆
  >>>

3.3 公有和私有

  一般面向对象的编程语言都会区分公有和私有的数据类型,在Python中默认对象的属性和方法都是公开的,可以直接通过点操作符(.)进行访问:

  example1: >>> class Person:
           name = “小明”

       >>> p = Person()
       >>> p.name
       ‘小明’

  为了实现类似私有变量的特征,Python内部采用了一种叫name mangling(名字改编)的技术,在Python中定义私有变量只需要在变量名或函数名前加上“_ _”两个下划线,那么这个函数或变量就会成为私有的了:

  example2:>>> class Person:
           _ _name = “小明”

       >>> p = Person()
       >>> p. _ _name
       Traceback (most recent call last):
         File “<pyshell#19>”, line 1, in < module >
          p. _ _name
       AttributeError: ‘Person’ object has no attribute ’ _ _name’

  这样就在外部将变量名“隐藏”起来了,理论上如果要访问的话就需要从内部进行:

  example3:>>> class Person:
           def _ _ init _ _(self, name):
             self. _ _ name = name
           def getName(self):
             return self. _ _ name

       >>> p = Person(“小明”)
       >>> p. _ _name
       Traceback (most recent call last):
        File “<pyshell#31>”, line 1, in < module >
         p. _ _name
       AttributeError: ‘Person’ object has no attribute ’ _ _ name’
       >>> p.getName()
       ‘小明’

  其实认真琢磨一下这个技术的名字name mangling(名字改编),不难发现其实Python只是动了一下手脚,把双下横线开头的变量进行了改名而已。实际上在外部使用“_类名_ _变量名”即可访问双下横线开头的私有变量了:

       >>> p. _ Person_ _name
       ‘小明’

  所以可以说,Python目前的私有机制其实是伪私有,Python的类是没有权限控制的,所有变量都是可以被外部调用的。

4. 继承

  关于继承的灵魂三问:

  • 什么是继承?
      继承是一种创建新的类的方式,新创建的叫子类,继承的叫父类、超类、基类。
      特点:子类可以使用父类的属性(特征、技能)。
      继承是类与类之间的关系。
  • 为什么要继承?
      减少代码冗余、提高重用性。
  • 如何用继承?
      单继承、多继承。

  继承的语法:
    class类名(被继承的类):
  被继承的类称为基类、父类或超类;继承者称为子类,一个子类可以继承它的父类的任何属性和方法。举个例子:

  example1:>>> class Parent:
           def hello(self):
             print(“正在调用父类的方法…”)

       >>> class Child(Parent):
           pass

       >>> p = Parent()
       >>> p.hello()
       正在调用父类的方法…
       >>> c = Child()
       >>> c.hello()
       正在调用父类的方法…

  需要注意的是,如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性:

       >>> class Child(Parent):
           def hello(self):
             print(“正在调用子类的方法…”)

       >>> c = Child()
       >>> c.hello()
       正在调用子类的方法…

  下面举个例子,如果要对鱼类进行细分,有金鱼(Goldfish)、鲤鱼(Carp)、三文鱼(Salmon),还有鲨鱼(Shark)。我们尝试写一下代码:

import random as r

class Fish:
    def __init__(self):
        self.x = r.randint(0,10)
        self.y = r.randint(0,10)
        
    def move(self):
         # 这里主要演示类的继承机制,就不考虑检查场景边界和移动方向的问题
         # 假设所有鱼都是一路向西游
        self.x -= 1
        print("我的位置是:", self.x, self.y)
        
class Goldfish(Fish):
    pass

class Carp(Fish):
    pass

class Salmon(Fish):
    pass

# 上边几个都是食物,食物不需要有个性,所以直接继承Fish类的全部属性和方法即可
# 下边定义鲨鱼类,这是个吃货,除了继承Fish类的属性和方法之外,还要添加个吃法

class Shark(Fish):
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print("吃货的梦想就是天天有得吃^_^")
            self.hungry = False
        else:
            print("太撑了,吃不下了!")

  运行上述代码:
  >>> fish = Fish()
  >>> fish.move()
  我的位置是: 6 7
  >>> goldfish = Goldfish()
  >>> goldfish.move()
  我的位置是: 2 0
  >>> goldfish.move()
  我的位置是: 1 0
  >>> goldfish.move()
  我的位置是: 0 0
  >>> shark = Shark()
  >>> shark.eat()
  吃货的梦想就是天天有得吃_
  >>> shark.eat()
  太撑了,吃不下了!
  >>> shark.move()
  Traceback (most recent call last):
    File “<pyshell#26>”, line 1, in < module >
     shark.move()
   File “C:\Users\Administrator\Desktop\fish.py”, line 11, in move
   self.x -= 1
  AttributeError: ‘Shark’ object has no attribute ‘x’

  运行上面的代码会发现一个问题:同样是继承于Fish类,为什么金鱼(goldfish)可以移动,而鲨鱼(shark)一移动就报错呢?
  其实在抛出的异常里已经说得很清楚了:Shark对象没有x属性。原因是这样的:在Shark类中,重写了魔法方法_ _init_ _,但新的_ _init_ _方法里边没有初始化鲨鱼的x坐标和y坐标,因此调用move方法就会出错。解决这个问题的方案是应该在鲨鱼类中重写_ _init_ _方法的时候先调用基类Fish_ _init_ _方法。
  下面介绍两种可以实现鲨鱼移动的技术:
     - 调用未绑定的父类方法;
     - 使用super函数。

4.1 调用未绑定的父类方法

  调用未绑定的父类方法,将鲨鱼类的代码做如下修改:

class Shark(Fish):
    def __init__(self):
        Fish.__init__(self)
        self.hungry = True
    def eat(self):
        if self.hungry:
            print("吃货的梦想就是天天有得吃^_^")
            self.hungry = False
        else:
            print("太撑了,吃不下了!")

  再运行下发现鲨鱼也可以成功移动了:

  >>> shark= Shark()
  >>> shark.move()
  我的位置是:6 1
  >>> shark.move()
  我的位置是:5 1

  这里需要注意的是这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,所以这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象代替即可。

4.2 使用super函数

  super函数能够帮助自动找到基类的方法,而且还为我们传入了self参数:
  使用super函数,将鲨鱼类的代码做如下修改:

class Shark(Fish):
    def __init__(self):
        super().__init__()
        self.hungry = True
    def eat(self):
        if self.hungry:
            print("吃货的梦想就是天天有得吃^_^")
            self.hungry = False
        else:
            print("太撑了,吃不下了!")

  运行后得到相同的结果:

  >>> shark = Shark()
  >>> shark.move()
  我的位置是: 5 0
  >>> shark.move()
  我的位置是: 4 0

  super函数的“超级”之处在于我们不需要明确给出任何基类的名字,它会自动帮我们找出所有基类以及对应的方法。由于不用给出基类的名字,这就意味着如果需要改变类继承关系,只要改变class语句里的父类即可,而不必在大量代码中去修改所有被继承的方法。

5. 多重继承

  除此之外,Pyhton还支持多继承,就是可以同时继承多个父类的属性和方法:
    class 类名(父类1, 父类2, 父类3, …):

  example1:>>> class Base1:
           def foo1(self):
             print(“我是foo1,我在Base1中…”)

       >>> class Base2:
           def foo2(self):
             print(“我是foo2,我在Base2中…”)

       >>> class C(Base1, Base2):
           pass

       >>> c = C()
       >>> c.foo1()
       我是foo1,我在Base1中…
       >>> c.foo2()
       我是foo2,我在Base2中…

  上面就是基本的多重继承语法。但多重继承其实很容易导致代码混乱,所以当你不确定是否真的必须使用多重继承的时候,请尽量避免使用它,因为有些时候会出现不可预见的BUG。

6. 组合

  什么是组合:组合指的是一个对象中,包含另一个对象。
  为什么要用组合:减少代码冗余。
  假设我们已经有了乌龟类、鱼类,现在要求定义一个水池类,水池里要有乌龟和鱼。用多重继承就显得很奇怪,因为水池和乌龟、鱼是不同物种,那要怎样才能把它们组合成一个水池的类呢?在Python里其实很简单,直接把需要的类放进去实例化就可以了,这就叫组合。

class Turtle:
    def __init__(self, x):
        self.num = x

class Fish:
    def __init__(self, x):
        self.num = x

class Pool:
    def __init__(self, x, y):
        self.turtle = Turtle(x)
        self.fish= Fish(y)
    def print_num(self):
        print("水池里总共有乌龟%d只,小鱼%d条!" %(self.turtle.num, self.fish.num))

  运行上述代码:
  >>> pool = Pool(1,10)
  >>> pool.print_num()
  水池里总共有乌龟1只,小鱼10条!

  关于继承与组合的总结:
  继承:继承是类与类的关系,子类继承父类的属性,子类与父类是一种“从属”关系。
  组合:组合是对象与对象的关系,一个对象拥有另一个对象中的属性,是一种你有我也有的关系。

7. 类、类对象和实例对象

  先来分析一段代码:

  example1:>>> class C:
           count = 0

       >>> a = C()
       >>> b = C()
       >>> c = C()
       >>> print(a.count, b.count, c.count)
       0 0 0
       >>> c.count += 10
       >>> print(a.count, b.count, c.count)
       0 0 10
       >>> C.count += 100
       >>> print(a.count, b.count, c.count)
       100 100 10

  从上面的例子可以看出,对实例对象ccount属性进行赋值后,就相当于覆盖了类对象Ccount属性。如图1所示,如果没有赋值覆盖,那么引用的是类对象的count属性。
在这里插入图片描述

图1 类、类对象和实例对象

  将具有相似属性和方法的对象总结抽象为类对象;实例对象是通过类对象创建的。需要注意的是,类中定义的属性是静态变量,也就是相当于C语言中加上static关键字声明的变量,类的属性是与类对象进行绑定,并不会依赖任何它的实例对象。
  另外,如果属性的名字跟方法名相同,属性会覆盖方法:

  example2:>>> class C:
           def x(self):
             print(‘Xman’)

       >>> c = C()
       >>> c.x()
       Xman
       >>> c.x = 1
       >>> c.x
       1
       >>> c.x()
       Traceback (most recent call last):
         File “<pyshell#70>”, line 1, in < module >
          c.x()
       TypeError: ‘int’ object is not callable

  为了避免名字上的冲突,大家应该遵守一些约定俗成的规矩:

  • 类的定义要“少吃多餐”,不要试图在一个类里边定义出所有能想到的特性和方法,应该利用继承和组合机制来进行扩展。
  • 用不同的词性命名,如属性名用名词、方法名用动词,并使用骆驼命名法等。

  骆驼式命名法(Camel-Case)又称驼峰命名法,是电脑程式编写时的一套命名规则(惯例),正如它的名称Camel-Case所表示的那样,是指混合使用大小写字母来构成变量和函数的名字,程序员们为了自己的代码能更容易在同行之间交流,所以多采取统一的可读性比较好的命名方式。

  关于继承与组合的总结:
  继承:继承是类与类的关系,子类继承父类的属性,子类与父类是一种“从属”关系。
  组合:组合是对象与对象的关系,一个对象拥有另一个对象中的属性,是一种你有我也有的关系。

8. 到底什么是绑定

  Python严格要求方法需要有实例才能被调用,这种限制其实就是Python所谓的绑定概念。如果如下尝试的话,发现也可以调用:

  example1:>>> class BB:
           def printBB():
             print(“no zuo no die”)

       >>> BB.printBB()
       no zuo no die

  但这样做会有一个问题,就是根据类实例化后的对象根本无法调用里边的函数:

       >>> bb = BB()
       >>> bb.printBB()
       Traceback (most recent call last):
         File “<pyshell#77>”, line 1, in < module >
          bb.printBB()
       TypeError: printBB() takes 0 positional arguments but 1 was given

  实际上由于Python的绑定机制,这里自动把bb对象作为第一个参数传入,所以才会出现TypeError。改成下面这样即可正确运行:

       >>> class BB:
           def printBB(self):
             print(“no zuo no die”)

       >>> bb = BB()
       >>> bb.printBB()
       no zuo no die

  为了更好地理解,看下面一个例子:

  example2:>>> class CC:
           def setXY(self, x, y):
             self.x = x
             self.y = y
           def printXY(self):
             print(self.x, self.y)

       >>> dd =CC()

  可以使用_ _ dict _ _查看对象所拥有的属性:

       >>> dd._ _ dict _ _
       {}
       >>> CC._ _ dict __
       mappingproxy({’ _ _ module _ ': ’ _ _ main _ _ ', ‘setXY’ : < function CC.setXY at 0x000001555B06D620 > , ‘printXY’: <function CC.printXY at 0x000001555B06D6A8>, ’ _ _ dict _ _ ': <attribute ’ _ dict _ _ ’ of ‘CC’ objects>, ‘_ _ weakref _ _’:<attribute ’ _ _ weakref _ _ ’ of ‘CC’ objects>, ’ _ _ doc _ _ ': None})

  _ _ dict _ _属性是由一个字典组成,字典中仅有实例对象的属性,不显示类属性和特殊属性,键表示的是属性名,值表示属性相应的数据值。

       >>>dd.setXY(4, 5)
       >>> dd._ _dict
       {‘x’:4, ‘y’:5}

  现在实例对象dd有了两个新属性,而且这两个属性仅属于实例对象的:为什么会这样呢?完全是归功于self参数:当实例对象d去调用setXY方法的时候,它传入的第一个参数就是dd,那么self.x=4self.y=5也就相当于dd.x=4dd.y=5,所以你在实例对象,甚至类对象中都看不到×y,因为这两个属性是只属于实例对象dd的。
  如果把类实例删除掉,实例对象dd还能否调用printXY方法?答案是可以的。

       >>>del CC
       >>> dd.printXY()
       4 5

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值