深入理解python中的类和对象

刚开始学习python的时候或者其他的是面向对象的编程语言的时候,难免会对类和对象理解得不太清楚。所以今天和大家分享下python中的类和对象,深入理解下python中的类和对象。

1.鸭子类型

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。这个就是鸭子类型的定义,在python中,并不关心这个对象是什么类型,只关心他的行为。由行为来推断出该对象所属于的类型。就比如列表(list)、元组(tuple)、字典(dict)等等,这些类都是可迭代的,所以说他们是可迭代对象。

from collections import Iterable
l = [1, ]
t = (1, )
d = {'d': 3}
print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))

# 结果
True
True
True

2.类变量和实例变量

类变量就是在类内定义的,但是不在方法内定义的,而且前缀无self作为引用。实例变量就是有self作为引用的存在类中的变量。类变量是所有对象共享的,在类中修改时,其他的对象也会跟着变。但是需要注意的是,如果是用对象来引用类变量进行修改的话,这里只是新建了和类变量同名的实例变量,并没有修改到。下面用代码解释下。

class Student(object):
    conutry = 'China'  # 这个是类变量

    def __init__(self, name, sex):
        self.name = name  # 这个是实例变量,也就是对象变量
        self.sex = sex  # 对象变量


s1 = Student('张三', 'man')
s2 = Student('里斯', 'woman')
print(s1.conutry)
print(s2.conutry)
print(Student.conutry)

上面的结果都是三个China,这个很容易知道,用类来引用改变时

Student.conutry = 'cn'  # 这个是用类引用来进行修改

修改后打印下三个结果都是修改后的结果。但是下面这个呢?

s1.conutry = 'zhongguo'  # 用实例来引用进行修改

这次结果就不一样了,只有s1的类变量变了,其他两个都是不变的。这是为什么呢?就如上面所说,用实例引用来修改类变量的时候并不是修改,而是新建了这个变量。又由于python查找变量是由下往上查找的,所以会先查找出新建后的变量。

3.类属性和实例属性之间的访问顺序

类属性就是定义在类中的方法和变量,实例属性也是一样的。访问顺序就是由下往上查找的,用代码体会一下。

class A():
    name = 'A'
    def __init__(self):
        self.name = 'a'

a = A()
print(a.name)
# 结果
a

由于是类变量先加载,再到初始化对象,所以才会运行__init__()方法,所以结果很显然就是a。这里由于该类没有继承,就没有很复杂,但是当出现多继承,几个类之间就变得很复杂,这个时候的访问顺序就难多了。下面说下这两种情况,掌握了这两种情况,其他的基本没有问题了。

(1.适合深度优先查找

A继承了B,C,B,C分别继承了D,E。深度优先的查找是先去着A,如果A中没有该属性,就去B着,再没有就去D找。D中找不到了再去C找。这种查找情况是没有问题的,但是另一种情况就不合适了。

2)适合广度优先查找

这个是A继承了B,C,B,C都继承了D。如果这个用深度优先的算法的话,就会出现一个问题,因为深度优先查找顺序是A->B->D->C。这个是不太合理的,当C中重载了D中的一个方法后,B没有重载,如果要查找C中的该方法,用深度优先的算法就只能找到D中的原始方法,所以说这就不正确了,这时候就需要用广度优先的 算法,这个时候查找顺序就是A->B->C->D。但是当遇到上面的情况时又会出错了。这时怎么办?python3就将所有的属性搜索算法统一成了一个算法:C3算法,这里就不展开说这个算法了,因为太复杂了:)会根据对应情况而实施对应算法,下面用代码来分别体会下以上两种情况

class E():
    pass

class D():
    pass

class C(E):
    pass

class B(D):
    pass

class A(B, C):
    pass

print(A.__mro__)
# 结果
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

__mro__这个属性是获取属性的查找顺序,可以看到就是和我们上面说的一样,用了深度优先的算法。再看另一个

class D():
    pass

class C(D):
    pass

class B(D):
    pass

class A(B, C):
    pass

print(A.__mro__)
# 结果
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)

这个时候就用了广度优先算法,符合与我们刚才所说的,这就是C3算法了哈。

4.super真的是调用父类吗?

学过Java的都知道,super()这个方法就是在调用父类的方法,但是在python中就不一定了。我们先看看super的用法

class A():
    def __init__(self):
        print('A')

class B(A):
    def __init__(self):
        # python2做法是
        # super(A, self).__init__()
        super().__init__()  # 调用父类的初始化方法
        print('B')

b = B()
# 结果
A
B

上面就是用法了,python2和python3用法不一样,这里我们就只用python3了就行操作。接下来看看super真正的调用情况。

class A():
    def __init__(self):
        print('A')

class B(A):
    def __init__(self):
        super().__init__()
        print('B')

class C(A):
    def __init__(self):
        super().__init__()
        print('C')

class D(B, C):
    def __init__(self):
        super().__init__()
        print('D')

d = D()

上面这个是我们之前所说的那个适合广度优先算法的多继承,按照我们之前的理解,super调用的是父函数,那么这个结果就会是:

A B C D

显然是错误,结果是这个

是不是觉得很奇怪,但是又很熟悉?是的,这个也是按照刚才的查找顺序一样执行的,如果不信的话我们打印下__mro__就知道了

是不是刚好倒叙?因为我们是先打印父类的再打印自己的,所以顺序倒了。再看看另外一种情况也是可行的

class A():
    def __init__(self):
        print('A')

class B():
    def __init__(self):
        super().__init__()
        print('B')

class C(A):
    def __init__(self):
        super().__init__()
        print('C')

class D(B):
    def __init__(self):
        super().__init__()
        print('D')

class E(D, C):
    def __init__(self):
        super().__init__()
        print('E')

e = E()
print(E.__mro__)
# 结果
A
C
B
D
E
(<class '__main__.E'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

也是和预期一样的。总的来说,super不一定是调用父类,它的调用顺序也是遵循mro算法的,就是属性查找算法,和上文说的C3算法一致。

有任何问题欢迎在留言区提问,或者有不当的地方也欢迎指出

ps:如果觉得文章不错的话,欢迎随手点赞转发支持

原文发布于微信公众号 - 日常学python(daily_learn)

原文发表时间:2018-08-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA技术站

shell学习二数组 原

类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

521
来自专栏小小挖掘机

深入理解Python变量作用域与函数闭包

1、引言 最近遇到一个求最长回文子串的题目,于是,我写了如下的代码: class Solution(object): def longestPalind...

4746
来自专栏尾尾部落

普林斯顿大学算法公开课笔记——选择排序 普林斯顿大学算法公开课笔记——选择排序

简单选择排序的特点是交换移动次数很少(至多n-1次),其时间复杂度为 O(n²) (时间主要花在比较上,总的比较次数为N=(n-1)+(n-2)+…+1=n*(...

973
来自专栏java学习

工程师笔试题1

1.访问修饰符作用范围由大到小是( )。 A.private-protected-default-publicB.public-protected-defaul...

2854
来自专栏javathings

String 对象创建方式有哪几种?有什么区别?

两种方法有些区别。 第一种方法,仅仅是一个赋值语句,在创建的时候,JVM 会检查在字符串池中,是否已经存在该字符串,如果已经存在了,那么会返回这个字符串的引用...

3173
来自专栏猿人谷

【Objective-C】05-第一个OC的类

说明:这个Objective-C专题,是学习iOS开发的前奏,也为了让有面向对象语言开发经验的程序员,能够快速上手Objective-C。如果你还没有编程经验,...

22210
来自专栏WebDeveloper

python,详说正则表达式(对常用的关键字符的讲解)

[...]如果匹配的是个范围,可以这个写[0-9a-zA-B]表示0到9并a到z并A到B

1242
来自专栏北京马哥教育

Python 运算符,你知道多少?

糖豆贴心提醒,本文阅读时间5分钟,文末有秘密! ? 编辑 | 糖豆 图 | 来源网络 ? 什么是运算符? 本章节主要说明Python的运算符。举个简...

4424
来自专栏十月梦想

JavaScript数组元素排序

使用for循环遍历出数组;然后判断i号元素和i+1号大小,如果判断大于,存储小的元素,如果判断小于存储大的元素

903
来自专栏hbbliyong

nodejs 的序列化与反序列化

1.序列化 stringify函数的作用就是序列化对象,也就是说将对象类型转换成一个字符串类型(默认的分割符("&")和分配符("=")),先介绍它的基本用法,...

3307

扫码关注云+社区

领取腾讯云代金券