PYTHON深入类与对象(上中下)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lzy98/article/details/88819183

这次开始深入了解类与对象,再此之前,先把以前的知识点温习一遍吧。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. 类变量与实例的变量,是两个不同的属性

类属性和实例属性查找顺序

属性:在内部定义的方法或者变量

使用代码:

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

    def run(self):
        return self.x

m = magic('xx')
m.a = 'LANGZILANGZI'
print(m.a)
# 查找实例的属性
print(magic.a)
# 查找类的属性
print(m.x)
# 查找实例的属性

返回结果:

'LANGZILANGZI'
'langzi'
'xx'

查找顺序是由下而上的查找顺序,init是初始化实例的属性,要记住这里使用magic.x是会报错的,因为init是初始化实例,这个实例成为m,并不在属于magic。

可能这样举例子不清晰,重新再看看代码

class magic:
    name = '浪子'
    def __init__(self,name):
        self.name = name

m = magic('langzi')
print(m.name)
print(magic.name)

返回结果:

langzi
浪子

这样是不是就比较清晰了,类中的name='浪子’是属于magic类当中的,实例m.name是属于实例m当中的,是在类中init初始化的属性。

他们的查找顺序是这样:

  1. 找m.name的时候,由下而上,会先找到m。
  2. 找到m的初始话对象,从init开始查找,就会找到初始化传入的name
  3. 如果init中没有name的话,就会往上走,查找类中是不是存在name

就好像这样的代码:

class magic:
    # def __init__(self,name):
    #     self.name = name
    name = '浪子'


m = magic()
print(m.name)
print(magic.name)

返回结果:

浪子
浪子

三大方法

python类的调用有三大方法,分别是类方法,静态方法,实例方法。这三个方法在曾经的文章有说过。Python面向对象编程

这里就不多做代码重写,去原链接查看学习即可。

数据封装与私有属性

私有属性,也叫私有方法,是以双下划线开头的命名变量。

无法通过实例方法和子类来调用的,只能通过类中写的方法来调用。

比如:

class magic:
    __user = '浪子'

a = magic
print(magic.__user)
print(a.__user)

加了双下划线的user就是私有属性,是没法通过下面两种方式进行调用的,唯一可以调用这个私有属性的方法就是使用类方法。

class magic:
    __user = '浪子'
    @classmethod
    def run(cls):
        print(cls.__user)

magic.run()

只有这样才能调用类的私有属性,这也就是对数据做了一个很好的封装。

但是这样的封装并不是完全安全的,比如你看下面的代码:

class magic:
    __user = '浪子'

m = magic
print(m._magic__user)

这样能直接调用user,说白了这是一个小技巧python把私有变量偷偷隐藏起来变成了这样子。

python的自省机制

自省:通过一定的机制,查询到对象的内部结构

使用__dict__方法查询类的属性:

class magic:
    '我是注释'
    user = '浪子'
    def run(age):
        print(age)

for x,y in magic.__dict__.items():
    print(x,':',y)

返回结果:

__module__ : __main__
__doc__ : 我是注释
user : 浪子
run : <function magic.run at 0x0000018AB312CC80>
__dict__ : <attribute '__dict__' of 'magic' objects>
__weakref__ : <attribute '__weakref__' of 'magic' objects>

通过dict来获取类的内部结果,记住通过magic.__dict__[‘user’]='小桃红’也可以动态操作属性。

不仅仅是dict,通过dir可以更加强大的列出该类的所有属性。

super函数

super函数,调用父类。在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用 super 来实现。

class A:
    def __init__(self,name):
        self.name = name
    def run(self):
        print(self.name+'6666666')

class B(A):
    def __init__(self,name):
        self.name = name
    def run(self):
        print(self.name+'7777777')
        super().run()
		#这里是调用父类的run()方法
		# 还可以这样super().__init__(name=name)这样的格式

c = B('浪子')
c.run()

返回对象:

浪子7777777
浪子6666666

事实上,super 和父类没有实质性的关联,super(cls, inst) 获得的是 cls 在 inst 的 MRO 列表中的下一个类。MRO则是python中类继承关系选择的一种,有点像是广度优先原则,可以print m.__mro__查看。

mixin继承

多继承会造成关系混乱,导致MRO算法出现许多预料不到的问题,一般都是推荐MIXIN继承,最小继承,只继承一个类。

上下文管理器

with上下文管理器,这个用过很多次,比如在执行mysql语句的时候要先链接数据库,获取游标,执行sql语句,关闭连接。又或者是文本内容的读写,打开文本写入内容关闭文本。

如果每次执行一条语句都要做这么多操作,就会产生大量重复的代码,这个时候使用上下文管理器即可美观又轻松的解决这个问题。

with管理上下文的作用是对一些重复的代码简单化,并且能优化try/except/finally的写法。

上下文的实现是通过两个魔法函数enter和exit实现,后来更新再造后,使用contextlib提供的API可以更加方便的完成。

概念和功能都明白后,最重要的还是如何实现。

举个例子:浪子去买猫饼干,每次买的时候都要做这些动作,掏出钱包,花出x元,收回钱包。每次都要重复掏钱包收钱包这个动作有些麻烦,并且万一忘了掏钱包就不能付钱,忘了收钱包的话,钱包就掉了。每次掏钱包收钱包都要写代码,有啥简介的方式嘛?

使用enter和exit实现(1)

class wallet(object):
    def __init__(self,man):
        self.man=man

    def __enter__(self):
        print(self.man + '放心大胆的掏出了钱包')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.man + '小心翼翼的收起来钱包')

def use_money(man):
    return wallet(man)

with use_money('langzi') as a:
    print('花了600块钱')

返回结果:

langzi放心大胆的掏出了钱包
花了600块钱
langzi小心翼翼的收起来钱包

使用enter和exit实现(2)

当然也可以这么写:

class wallet(object):
    def __init__(self,man):
        self.man=man

    def __enter__(self):
        print(self.man + '放心大胆的掏出了钱包')
        return self
    # return self 这一步非常重要,作用是返回实例

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.man + '小心翼翼的收起来钱包')

    def use_money(self,money):
        print(self.man + '花了' + money +'元')

with wallet('浪子')as a:
    a.use_money('600')

运行结果:

浪子放心大胆的掏出了钱包
浪子花了600元
浪子小心翼翼的收起来钱包

使用contextlib实现

import contextlib

@contextlib.contextmanager
def use_money(man):
    try:
        print(man + '十分放心大胆的掏出钱包')
        yield None
		# yield生成器,运行到这里会返回一个值(你随便写一个就行)
    finally:
        print(man + '万分谨慎的收起了钱包')

with use_money('langzi')as a:
    print('花了1块钱')

返回结果:

langzi十分放心大胆的掏出钱包
花了1块钱
langzi万分谨慎的收起了钱包

这只是一个创建上下文管理器的方法,大家记住格式就行。

猜你喜欢

转载自blog.csdn.net/lzy98/article/details/88819183