深入类与对象-上

即使那么孤独地活在这个世界上,也从未偏离自己的方向,即便对着空无一人的屋子,也会大声说,“我回来了!”

这次开始深入了解类与对象,再此之前,先把以前的知识点温习一遍吧。Python面向对象编程

多态与鸭子函数

类的三大特征,封装继承与多态,前面两个都好理解,关于多态则是本次要重点研究的对象。

多态:根据对象类型的不同以不同的方式进行处理。

定义总是抽象枯燥的,上代码演示演示

class Animal(object):
    def gaga():
        print('gagagagagagagaga')

class dog(Animal):
    pass
dog.gaga()

返回结果:

gagagagagagagaga

通过实例来分析研究运行原理:

  1. 首先定义一个类Animal,具有一个gaga方法
  2. 然后定义一个类dog,继承Animal
  3. 运行dog.gaga()
  4. 这个时候会调用父类Animal的gaga方法

ok,这个就是类的特性之继承,学会了的老铁刷个飞机鼓励鼓励~~

封装是啥,就是你的类方法都统一定义好提供一个API进行调用,双击666啊~~

重点是多态,这是一个极其非常十分相当抽象但是不是很抽象的概念,概念虽然有些难理解,但是代码上一遍就能理解了。

class dog:
    def gaga():
        print('汪汪汪汪汪~')
class cat:
    def gaga():
        print('喵喵喵喵喵喵喵~')
def you_say(you):
    you.gaga()
    you.gaga()
you_say(dog)
print('-'*10)
you_say(cat)

返回结果:

汪汪汪汪汪~
汪汪汪汪汪~
----------
喵喵喵喵喵喵喵~
喵喵喵喵喵喵喵~

要不要问下神奇的海螺这是什么情况?这就是多态,是python如此简介性的重要基础。尝试来分析一下:

  1. 定义两个类cat与dog,他们都有同一个方法,会发出娇声(叫声)
  2. 然后定义一个函数,参数为you,作用是调用you.gaga(),还调用两次
  3. 然后运行,传入的是dog类,此时的you继承的是dog类,那么就能直接使用dog类的gaga()方法
  4. 同理喵喵喵也是这样的。

是不是有些似成相似的感觉?给你看看下面的代码你就会恍然大悟了。

s = 'langzi'
b  = [1,2,3,4,5,6,7,8,9]
print(len(s))
print(len(b))

结果就不输出了。同样是len函数,但是传入的确实两种完完全全不同类型的数值,却能正常运行….神奇吗?

再次回到多态的定义:根据对象类型的不同以不同的方式进行处理。

是不是有点感觉了?没错,你已经窥视到了python最基础核心的东西,多态,对传入的不同类型按照不同方式处理,从而进一步的简化面向对象编程。就像说到了函数的作用域就会引申出闭包,说到了多态的概念就会引出python核心之鸭子函数。

鸭子函数:如果它像鸭子一样走路,像鸭子一样叫,那么它就是一只鸭子。

鸭子函数也有几个高大上的名字,比较高端的方式是叫做「隐式类型」或者「结构式类型」。

在举个例子来回顾一下刚刚学习到的知识:

d = [0]
s = [1,2,3,4,5]
b = (6,7,8,9)
d.extend(s)
d.extend(b)
print(d)

返回结果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

extend函数什么意思,以前一直以为就是合并列表,但是没想到吧,能把列表和元组都能合并呢~按ctrl+alt+b查看extend函数的源码:

def extend(self,iterable):
    pass

可以看到该函数接受的是一个可迭代对象,那能不能合并一个字符串呢?答案是可以的。

尝试理解和想象一下extend与列表,字符串,元组的关系。是不是就像最上面的喵喵喵和汪汪汪?

字符串,元组,列表都是可迭代对象,他们的构成类中都有iter这个魔法函数,就好像dog类和cat类都有gaga函数一样,然后extend会调用这个iter魔法函数,就好像you_say调用继承的类的gaga函数一样?

这是不是说,只要一个类中具有iter或者getitem这个魔法函数,这个类具有可迭代功能,就能使用extend函数呢?看看代码:

class magic:
    def __init__(self,num):
        self.num = num
    def __getitem__(self, item):
        return self.num[item]

a = magic([1,2,3,4,5])
b = [666]
b.extend(a)
# 想一想为啥不能用a.extend(b)
print(b)

返回结果:

[666, 1, 2, 3, 4, 5]

鹅妹子嘤,如果你突然有一种奇妙的感觉,恭喜你,你已经不小心窥探到了python的密码了。Python 不检查传入的对象的类型,使用起来更加灵活。

有如下的一段代码:

class A(object):
    def show(self):
        print 'base show'

class B(A):
    def show(self):
        print 'derived show'

obj = B()
obj.show()

如何调用类A的show方法了。
方法如下:

obj.__class__ = A
obj.show()

__class__方法指向了类对象,只用给他赋值类型A,然后调用方法show,但是用完了记得修改回来。

抽象基类

默认情况下,Python解析器不强制检查对抽象类的继承,即抽象类的子类可能没有实现其中的抽象方法,但是Python并不会报错。

为了避免这种情况,从Python 3.4/2.6开始,Python标准库中提供了abc模块(Abstract Base Classes),为定义Python的抽象基类提供了公共基础。参考来源

抽象基类的使用:

1. 直接继承:直接继承抽象基类的子类就没有这么灵活,抽象基类中可以声明”抽象方法“和“抽象属性”,只有完全覆写(实现)了抽象基类中的“抽象”内容后,才能被实例化,而虚拟子类则不受此影响。
2. 虚拟子类:将其他的类”注册“到抽象基类下当虚拟子类(调用register方法),虚拟子类的好处是你实现的第三方子类不需要直接继承自基类,可以实现抽象基类中的部分API接口,也可以根本不实现,但是issubclass(), issubinstance()进行判断时仍然返回真值。

抽象基类有两个特点:

    1.规定继承类必须具有抽象基类指定的方法(强制子类具有某个方法)

    2.抽象基类无法实例化

基于上述两个特点,抽象基类主要用于接口设计

实现抽象基类可以使用内置的abc模块(所有的抽象基类继承的maeacalss=ABCMeta,这个暂时我也不会,等学到元类编程的时候据说就会了。)

在python中有hasattr函数,用来检查一个类是否具有某个接口功能,举个例子:

import requests
print(hasattr(requests,'get'))

返回结果:

True

关于抽象基类的实际运用,用代码来解释一番。

class magic(object):
    def run(self):
        return self + ':Run'

    def stop(self):
        return self + ':Stop'

a = magic
print(a.run('a'))
print(a.stop('a'))
print('-'*10)
class magics(magic):
    def run(self,name):
        return name + ':runnging'

b = magics()
print(b.run('浪子'))

返回结果:

a:Run
a:Stop
----------
浪子:runnging

很显然,这个想让你明白这个就是类方法的重写,然而重点并不在这里。

这一章节讲的是抽象基类,他的作用是:你写的类A,B。其中A是一个非常非常核心的类,具有一些非常非常牛逼的功能,B不过是一个小垃圾,做一些简单的事情,但是在实际情况中你调用的是B类,其中B继承A。

但是吧,你希望你在写B类的时候,要拥有A类的全部方法,这个就是抽象基类的概念。(A是B的基类)

但是在前面的鸭子类型中说到过,python类传递参数的时候,是不会检查他的类型的,只有在运行的时候发生了报错你才知道问题所在。

那么如何实现强制让自己写的类B在继承A的自己想要并且必须要有的功能(有人会问,这有啥用?其实不然,这个我个人认为涉及到语言层面设计问题,在某些相同功能的代码领域,为了保持相同代码功能的统一性,或者说提供相同的API),这个就是抽象基类的作用了。

继续回到例子,我希望我写的magics类必须要有magic类中的run和stop方法,那该怎么弄呢?

import abc
class magic(object,metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def run(self):
        return self + ':Run'

    @abc.abstractmethod
    def stop(self):
        return self + ':Stop'

a = magic
print(a.run('a'))
print(a.stop('a'))
print('-'*10)

class magics(magic):
    def run(self,name):
        return name + ':runnging'
    def stop(self):
        pass

b = magics()
print(b.run('浪子'))

这样就好辣,不要问我abc.ABCMeta是什么意思,这些会在后面的元类编程中慢慢学,是要是被@abc.abstractmethod装饰器装饰的函数,都需要保证你自己写的类中有这个函数功能哦,不然就会要你好看。

最后在梳理一下:抽象基类可以让你做接口的强制规定。

isinstance与type区别

isinstance的功能在前面的文章提到过,就是判断传入对象的类型,相关的还有hasattr,issubclass,但是这里主要讲的是isinstance的原理。

看看代码:

class A:
    pass

class B(A):
    pass

print(issubclass(B,A))
print(isinstance(B,A))

返回结果:

True
False

这个很好理解对吧,再继续往下走

class A:
    pass

class B(A):
    pass

print(issubclass(B,A))
print(isinstance(B,A))
print('-'*10)
c = A()
d = B()
print(isinstance(c,A))
print(isinstance(c,B))
print(isinstance(d,A))
print(isinstance(d,B))

返回结果:

True
False
----------
True
False
True
True

这个其实也很好理解对吧。

继续走:

class A:
    pass

class B(A):
    pass

d = B()
print(type(B))
# 打印类B的类型:<class 'type'>
print(type(d))
# 打印实例化对象d的类型:<class '__main__.B'>
print(B)
# 打印类B:<class '__main__.B'>
print(d)
# 打印实例化对象d:<__main__.B object at 0x000001FD129C1860>
print(type(d) is B)
# 判断实例化对象d的类型是不是就是B:True

返回结果:

<class 'type'>
<class '__main__.B'>
<class '__main__.B'>
<__main__.B object at 0x000001FD129C1860>
True

那么type(d)是否is A呢?答案是False,因为这个时候d已经指向了类B,虽然B继承了A,但是他们是不一样的。

但是使用isinstance的时候,返回的确实True,因为isinstance会根据类的继承关系找到最初的基类,从而判断是否属于一个类型的。

这里穿插一下is 与 == 的区别,在这一开头就介绍了类的三大特性,类型(type),值(value),身份(id)

is:只要id相同,就返回True
==:只要value相同,就返回True

比如 1 == 1.0就返回True,1 is 1.0 就返回False。

梳理:type只能稍微判断检查一些你这个对象的类型,不能进一步的跟踪及继承关系,isinstance却可以进一步的跟踪继承关系。

类变量与对象变量

这个很好理解吧,有过面向对象基础的同学一看就知道是啥,对象变量也叫实例的变量。

这里很有必要回顾一下变量的作用域与面对对象编程的一些基础知识。

作用域:在作用域的内部可以访问这个变量,但是在外部没办法访问这个作用域里面的变量。

Python中,函数的作用域是最低级的作用域,函数内部的变量只能在函数内部起作用。

python的四层作用域

  1. 局部作用域
  2. 闭包函数外的函数中
  3. 全局作用域
  4. 内建作用域

变量的作用域

面向对象编程基础

来上代码:

class magic:
    a = 'langzi'
    def __init__(self,x):
        self.x = x
        # 这里传入的x已经属于这个对象

    def run(self):
        return self.x

m = magic('浪子')
print(m.a)
print(m.run())

返回结果:

langzi
浪子

这里的a属于类的变量,通过调用m.a或者magic.a都可以调用,self.x是实例的变量,这里影藏了一个小坑,看代码:

class magic:
    a = 'langzi'
    def __init__(self,x):
        self.x = x
        # 这里传入的x已经属于这个对象

m = magic('xx')
m.a = 'LANGZILANGZI'
print(m.a)
print(magic.a)
print('-'*10)
magic.a = '浪子浪子'
print(m.a)
print(magic.a)

返回结果:

LANGZILANGZI
langzi
----------
LANGZILANGZI
浪子浪子

通过m.a和magic.a两种方法对类变量进行修改,都是查看类变量,但是结果却不一样,看看这里面做了什么。

  1. 当magic类实例成m对象的时候,这个时候magic.a是保持不变的
  2. 当使用m.a调用赋值的时候,会新建一个m.a属性(是新建一个哦),放在m实例的一个属性值当中
  3. 所以说m.a和magic.a是独立开来的
  4. 使用mgic.a修改的是magic类的属性
  5. 当使用m.a时候,会优先从对象m中查找是否有m.a这个新的变量
  6. 但是magic类的变量还是之前原来的
  7. 类变量与实例的变量,是两个不同的属性
坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%