『Python』面向对象

释放双眼,带上耳机,听听看~!

文章目录

    1. 面向过程 vs 面向对象
  • 1.1 面向过程

    • 1.2 面向对象
    1. 类和对象
    1. 属性
  • 3.1 类属性

    • 3.2 实例属性
    1. 访问控制
    1. 方法
  • 5.1 实例方法

    • 5.2 类方法
    • 5.3 静态方法
    • 5.4 property方法
    1. 类的特殊成员
  • 6.1     doc    

    • 6.2     module     和     class    
    • 6.3     init    
    • 6.4     del    
    • 6.5     call     
    • 6.6     dict    
    • 6.7     str    和    repr    
    • 6.8     getitem     、    setitem     、    delitem    
    • 6.9     iter    
    • 6.10     new    
  • 6.10.1 传统创建类
    * 6.10.2 type创建类
    * 6.10.3     new    方法
    * 6.10.4 重写    new    方法

  • 6.10.4.1 继承不可变对象的例子
    * 6.10.4.2     new    方法与    init    方法的关系
    * 6.10.4.3 单例模式

    • 6.11 metaclass
    • 6.12     slots    
    • 6.13运算符重载
    1. 继承
  • 7.1 继承的语法

    • 7.2 super详解
  • 7.2.1 单继承
    * 7.2.2 多继承
    * 7.2.3 super是个类

    • 7.3 MRO(Method Resolution Order)——方法解析顺序
    • 7.4 C3线性算法的实现
    1. 抽象类
    1. 枚举类
    1. 反射
    1. dataclass数据类
  • 11.1 dataclass装饰器

    • 11.2 field函数
    • 11.3 继承问题

1. 面向过程 vs 面向对象

1.1 面向过程

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

  • 优点

极大的降低了程序的复杂度。

  • 缺点

一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。

  • 应用场景

一旦完成基本很少改变的场景,著名的例子有
Linux內核,
git,以及
Apache HTTP Server等。

1.2 面向对象

面向对象编程(
**Object Oriented Programming **,
OOP) 是一种解决软件复用的设计和编程方法。这种方法把软件系统中相近相似的操作逻辑和操作应用数据、状态,以类的型式描述出来,以对象实例的形式在软件系统中复用,以达到提高软件开发效率的作用。

  • 优点

解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

  • 缺点

可控性差,无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人物某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

  • 应用场景

需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

2. 类和对象

类(
class)是用来描述具有相同属性(
attribute)和方法(
method)的对象的集合,对象(
object)是类(
class)的具体实例。比如学生都有名字和分数,他们有着共同的属性。这时我们就可以设计一个学生类, 用于记录学生的名字和分数,并自定义方法打印出他们的名字和方法。


1
2
3
4
5
6
7
8
9
10
11
12
1class Student(object):
2    def __init__(self,name,age,score):
3        self._name = name
4        self._age = age
5        self._score = score
6
7    def show(self):
8        print("{}的年龄是{}岁,分数是{}分".format(self._name,self._age,self._score))
9
10aStudent = Student("瑞雯",18,99)
11
12
  • 属性:类里面用于描述所有对象共同特征的变量或数据。如_name、_age、_score
  • 方法:类里面的函数,用来区别类外面的函数, 用来实现某些功能。如show()
  • 对象(实例):根据类创建出来的,如aStudent

3. 属性

3.1 类属性

定义在类中函数体外的属性,属于类共有属性


1
2
3
4
5
6
7
8
9
10
11
12
13
1class A(object):
2    count = 0
3    def __init__(self):
4        # 类中只有通过类名.类属性访问,self.__class__返回的就是类名
5        self.__class__.count += 1  # 或 A.count += 1
6        
7a = A()
8b = A()
9# 类外既可以通过类来访问,也可通过类的实例来访问
10print(a.count)                # 2
11print(A.count)                # 2
12
13

        要修改类属性必须通过类名.属性的方式来修改,而不能通过实例,因为实例.属性这种方式其实是创建了同名的实例属性,屏蔽了类的属性,通过del 实例.属性操作后,会发现实例.属性还是之前的。

3.2 实例属性

定义在__init__()方法中,属于对象


1
2
3
4
5
6
7
8
9
1class B(object):
2    def __init__(self,name,age):
3        self.name = name
4        self.age = age
5
6a = B("易",18)
7b = B("金克斯",8)
8
9

        一般来说,对象的属性应该设为
private,不能被外界直接访问,这里暂且不管访问权限问题。当能从外界直接访问时,必须通过对象.属性来访问,这时候属性是属于对象实例的。

4. 访问控制


Python中并没有
public
protected
private这样的关键字,所以无法实现数据封装,只能从语法上来定义可见性,依靠程序员自觉遵守规约。


Python中,不存在函数的重载,因为函数名和普通变量名一样,都是引用,指向一个对象,是一对一的关系,都是标识符。
Python中就是通过标识符的命名来区分访问的可见性。

  • 字符开头的标识符,如:age

这种标识符相当于
public,可以通过对象.属性或者对象.方法来执行。

  • 双下划线开头结尾的标识符,如: init

用户最好不要自定义这种类型的标识符,因为这通常是系统调用。

  • 单下划线开头的标识符,如:_age

这种标识符相当于
protected,不过
Python中没有
protected的概念,所以被视为
private,但是,你可以按照
public用,属于推荐
private但是可以
public的。

  • 双下划线开头的标识符,如:__name

相当于
private
Python通过更改标识符名
**(改为_类名__标识符)**来实现无法访问的机制。

5. 方法

5.1 实例方法


1
2
3
4
5
6
7
8
9
1class Test(object):
2    def __init__(self,name,age):
3        self.__name = name
4        self.__age = age
5
6    def grow(self,growth):
7        self.__age += growth
8
9

只能被对象实例调用,第一个参数必须是
self,它指向调用这个方法的实例。

5.2 类方法


1
2
3
4
5
6
7
8
9
10
1class Test(object):
2    def __init__(self,name,age):
3        self.__name = name
4        self.__age = age
5
6    @classmethod
7    def getInstance(cls):
8        return cls("薇恩",16)
9
10

绑定到类的方法,必须在定义函数的上方使用
@classmethod 装饰器,同时,第一个参数必须是
cls,这个
cls 代表这个类的类型,如上面例子中的Test,所以在类方法内部可以用
cls(参数) 创建对象,也可以用它调用属于类的属性、其他可使用的方法。
对外,类方法可以通过类调用,也可以通过实例对象来调用类方法。

5.3 静态方法


1
2
3
4
5
6
7
8
9
10
1class Test(object):
2    def __init__(self,name,age):
3        self.__name = name
4        self.__age = age
5
6    @staticmethod
7    def static_func():
8        print("正在调用静态方法")
9
10

静态方法不像前两个有特定参数,比较自由,即使你强行把第一个参数名写出
self
cls,也不会有相应的作用。
静态方法可以通过类或者实例对象调用。

5.4 property方法

内置函数property()是用来使类的属性能像Java Bean那样操作的函数,它的函数定义为:

property(fget=None, fset=None, fdel=None, doc=None)

  • fget:    function to be used for getting an attribute value

  • fset:    function to be used for setting an attribute value

  • fdel:    function to be used for deleting an attribute

  • doc:    docstring


1
2
3
4
5
6
7
8
9
10
11
12
13
1class C(object):
2    def getx(self): return self._x
3    def setx(self, value): self._x = value + 1
4    def delx(self): del self._x
5    x = property(getx, setx, delx)
6
7c = C()
8c.x = 5
9print(c.x) # 6
10del c.x
11# print(c.x) AttributeError: 'C' object has no attribute '_x'
12
13

这样,既能使对象能简单调用属性,也能保证数据的封装,可是,这么写太麻烦,普遍用的形式是使用装饰器来实现Bean:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1class Hero(object):
2    def __init__(self,name):
3        self.__name = name
4
5    @property
6    def name(self):
7        print("getter方法...")
8        return self.__name
9
10    @name.setter
11    def name(self,value):
12        print("setter方法...")
13        self.__name = value
14
15    @name.deleter
16    def name(self):
17        print("deleter方法...")
18        del self.__name
19
20p = Hero("蝙蝠侠")
21p.name                      # getter方法...
22p.name = "游城十代"          # setter方法...
23del p.name                  # deleter方法...
24
25

一般
@property只用于私有属性“公有化”,并且
getter方法和
deleter方法只能有
self参数。

6. 类的特殊成员

6.1

    doc
    

  • 表示类的描述信息


1
2
3
4
5
6
1class Foo(object):
2    '''Foo的描述'''
3
4print(Foo.__doc__)                 # Foo的描述
5
6

6.2

    module
     和
    class
    

  • **module**表示当前操作的对象在那个模块

  • class表示当前操作的对象的类是什么,也就是谁创建了这个类,metaclass还是type


1
2
3
4
5
6
7
1class Foo(object):
2    pass
3
4print(Foo.__module__)                  # __main__
5print(Foo.__class__)                   # <class 'type'>
6
7

6.3

    init
    

  • 构造方法,创建对象时自动执行的初始化函数


1
2
3
4
5
6
7
1class Foo(object):
2    def __init__(self):
3        print("init方法...")
4
5f = Foo()                          # init方法...
6
7

6.4

    del
    

  • 析构方法,当对象在内存中被释放时,自动触发执行

  • 此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的


1
2
3
4
5
1class Foo:
2    def__del__(self):
3        pass
4
5

6.5

    call
    

  • 对象后面加括号,触发执行

  • 构造方法的执行是由创建对象触发的,即:对象 = 类名();而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()


1
2
3
4
5
6
7
8
9
10
11
1class Demo(object):
2    def __init__(self):
3        print("执行init...")
4
5    def __call__(self, *args, **kwargs):
6        print("执行call...")
7
8d = Demo()                         # 执行init...
9d()                                    # 执行call...
10
11

6.6

    dict
    

  • 返回类或对象中的所有成员

  • 普通字段属于对象,静态字段和方法属于类


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1class Province(object):
2    country = 'China'
3
4    def __init__(self, name, count):
5        self.name = name
6        self.count = count
7
8    def func(self, *args, **kwargs):
9        print('func')
10
11print(Province.__dict__)                  # 返回类的所有成员
12'''
13{'__module__': '__main__', 'country': 'China',
14'__init__': <function Province.__init__ at 0x0000022A0C5278C8>,
15'func': <function Province.func at 0x0000022A0C527950>,
16'__dict__': <attribute '__dict__' of 'Province' objects>,
17'__weakref__': <attribute '__weakref__' of 'Province' objects>,
18 '__doc__': None}
19'''
20
21print(Province("卡兹克",1).__dict__)     # 返回对象的成员
22'''
23{'name': '卡兹克', 'count': 1}
24'''
25
26

6.7

    str
    和
    repr
    

  • 都是更改类的显示方式

  • __str__是给用户看到的字符串,__repr__是给开发者看的,比如debug时,变量列表显示的是__repr__函数返回的内容

  • 一般情况下只用写一个__str__,然后__repr__ = str


1
2
3
4
5
6
7
8
9
10
11
12
1class Text(object):
2    def __init__(self,text):
3        self.__text = text
4
5    def __str__(self):
6        return self.__text
7    __repr__ = __str__                     # 一般都这么写偷懒
8
9t = Text("无极剑圣")
10print(t)                                  # 无极剑圣
11
12

6.8

    getitem
     、
    setitem
     、
    delitem
    

  • 用于索引操作,如字典。以上分别表示获取、设置、删除数据

  • __getitem__也可以传入切片


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1class Subject(object):
2    def __getitem__(self, item):               # 根据item返回一个值
3        print("调用getitem...")
4
5    def __setitem__(self, key, value):         # 设置key对应的值为value
6        print("调用setitem...")
7
8    def __delitem__(self, key):                    # 删除这组键值对
9        print("调用delitem...")
10
11m = Subject()
12n = m['math']                                   # 调用getitem...
13m['math'] = 100                                 # 调用setitem...
14del m['math']                                   # 调用delitem...
15
16

6.9

    iter
    

  • 用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了__iter__


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1class MyList(object):
2    def __init__(self,seq):
3        self.__seq = [x*x for x in seq]
4
5    def __iter__(self):
6        return iter(self.__seq)
7
8l = MyList([1,2,3,4,5,6])
9for x in l:
10    print(x,end=" ")
11    
12"""输出:1 4 9 16 25 36 """
13
14

可以看出,实际上是利用iter()函数把要迭代的数据转成Iterator(迭代器)

6.10

    new
    

6.10.1 传统创建类


1
2
3
4
5
6
7
1class Demo(object):
2    def __init__(self,name):
3        self.__name = name
4
5d = Demo("乐芙兰")
6
7

d是通过Demo类实例化的对象,其实不仅d是一个对象,Demo本身也是一个对象,因为在Python中,一切皆对象,d是通过执行Demo类的构造方法创建,那么Demo也应该是通过某个类的构造方法创建。


1
2
3
4
1print(type(d)) # <class '__main__.Demo'>       表示d由Demo类创建
2print(type(Demo)) # <class 'type'>             表示Demo由type类创建
3
4

所以,d对象是Demo类的一个实例,Demo类对象是 type 类的一个实例,即:Demo类对象是通过type类的构造方法创建。

6.10.2 type创建类

  • 语法:

type(‘类名’,父类的元组,成员字典)


1
2
3
4
5
6
7
8
9
10
11
12
1def init(self,name):
2    self.__name = name
3
4def show(self):
5    print(self.__name)
6
7Demo = type('Demo',(object,),{'__init__':init,'output':show,'a':3})
8d = Demo("诡术妖姬")
9print(d)                       # <__main__.Demo object at 0x0000017F3C0F32E8>
10d.output()                        # 诡术妖姬
11
12
  • 一般用类名同名的变量来接受创建的类
  • 父类只有object时,注意元组单元素时的逗号,成员字典中,成员名是字符串,对应的值可以是方法地址,可以是属性值
  • init,show这些方法可以在前面加

@classmethod 或者
@staticmethod 等,来定义类函数和静态函数

6.10.3

    new
    方法

**new**方法是类自带的一个方法,可以重写,
**new**方法在实例化的时候也会执行,并且先于
**init**方法之前执行,简单理解,创建对象和初始化对象。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1class Foo(object):
2    def __init__(self, name):
3        self.name = name
4        print("Foo __init__")
5
6    def __new__(cls, *args, **kwargs):
7        print("Foo __new__", cls, *args, **kwargs)
8        return object.__new__(cls)
9
10f = Foo("凯特琳")
11"""
12Foo __new__ <class '__main__.Foo'> 凯特琳
13Foo __init__
14"""
15
16

6.10.4 重写

    new
    方法

  • 重写时,

必须要调用父类的
    new
    方法,不然会覆盖父类的
    new方法
    ,实例创建不了

  • **new()**方法创建出该类的实例,然后返回该实例给

**init()**方法调用。
**init()**方法的self就是
**new()**方法创建返回的

  • 依照Python官方文档的说法,

**new()**方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径,还有就是实现自定义的
metaclass

6.10.4.1 继承不可变对象的例子

假如我们需要一个永远都是正数的整数类型,通过继承
int


1
2
3
4
5
6
7
8
9
10
11
1class PositiveInteger(int):
2
3    def __new__(cls, value):
4        return super(PositiveInteger, cls).__new__(cls, abs(value))
5
6
7i = PositiveInteger(-3)
8
9print(i)                                   # 3
10
11

这时候可能有的人会通过
**init**方法来使-3变为3,但是,因为继承于
int,是不可变的,
**new**创建完时,值已经确定了,再通过
**init**修改是不行的。

6.10.4.2

    new
    方法与
    init
    方法的关系


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1class A(object):
2    def __init__(self):
3        print(self)
4        print("这是__init__()方法...")
5
6    def __new__(cls):
7        print(id(cls))
8        print("这是__new__()方法...")
9        ret = super().__new__(cls)
10        print(ret)
11        return ret
12
13
14print(id(A))
15a = A()
16'''
17输出:
18  2151279654824
19  2151279654824
20  这是__new__()方法...
21  <__main__.A object at 0x000001F4E4406FD0>
22  <__main__.A object at 0x000001F4E4406FD0>
23  这是__init__()方法...
24'''
25
26

6.10.4.3 单例模式


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1class Number(object):
2    __instance = None
3
4    def __new__(cls, val):
5        if cls.__instance is None:
6            cls.__instance = super().__new__(cls)
7            cls.__instance.__value = val
8            return cls.__instance
9        else:
10            return cls.__instance
11
12    def __str__(self):
13        return str(self.__value)
14
15
16m = Number(-3)
17n = Number(-6)
18print(m)                                  # -3
19print(n)                                  # -3
20print(id(m))                              # 2199891453768
21print(id(n))                              # 2199891453768
22
23

6.11 metaclass

  • metaclass,直译为元类,简单的解释就是,当我们定义了类以后,就可以根据这个类创建出实例,所以,先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以,先定义metaclass,然后创建类,连接起来就是:

**先定义metaclass,就可以创建类,最后创建实例。**所以,metaclass允许你创建类或者修改类。
换句话说,你可以把类看成是metaclass创建出来的“实例”。

  • metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法。定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass


1
2
3
4
5
6
7
8
9
10
1# metaclass是创建类,所以必须从type类型派生:
2class ListMetaclass(type):
3    def __new__(cls, name, bases, attrs):
4        attrs['add'] = lambda self, value: self.append(value)
5        return type.__new__(cls, name, bases, attrs)
6
7class MyList(list,metaclass=ListMetaclass):
8    pass
9
10

当我们写下
metaclass = ListMetaclass语句时,魔术就生效了,它指示Python解释器在创建MyList时,要通过
**ListMetaclass.new()**来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义

测试一下MyList是否可以调用add()方法:


1
2
3
4
5
1l = MyList()
2l.add(1)
3print(l)                                   # [1]
4
5

元类中,最好只定义__new__()方法,因为其他类如果使用了“
metaclass = XXXMetaclass”,只会调用这个
metaclass
**new()**方法来创建类,也就是避免了直接创建类,在创建类之前还封装了一组操作,但是,
metaclass中其他的方法、属性等,是不会让使用
metaclass创建的类继承的,所以一般在元类中,只写
**new()**方法。
metaclass可以隐式继承到子类

6.12

    slots
    

  • 一般情况下,可以任意地给对象添加属性,这会造成很大的漏洞,影响程序安全,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的

**slots**变量,来限制该class实例能添加的属性


1
2
3
4
5
6
7
8
9
1class Student(object):
2    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
3
4s = Student()
5s.name = 'Michael'
6s.age = 25
7s.score = 99  # AttributeError: 'Student' object has no attribute 'score'
8
9

显然,这时候不能添加限制以外的属性,但要注意,
**slots**定义的属性仅对当前类实例起作用,对继承的子类的属性不做限制,除非在子类中也定义
slots,这样,子类实例允许定义的属性就是自身的
**slots**加上父类的
slots

6.13 运算符重载

7. 继承

7.1 继承的语法


1
2
3
4
5
6
7
8
9
10
11
1class Animal(object):
2    def __init__(self,name):
3        self.__name = name
4
5class Dog(Animal):
6    kind = "Dog"
7    def __init__(self,name,age):
8        super.__init__(self,name)
9        self.__age = age
10
11

可以看出,定义类时后面圆括号里的就是父类(基类),如果有多个父类或者metaclass时,用逗号隔开。只要子类成员有与父类成员同名,默认覆盖父类成员的,类似Java中的方法重写(@override)。

7.2 super详解

说到 super(), 大家可能觉得很简单呀,不就是用来调用父类方法的嘛。如果真的这么简单的话也就不会单独拿出来说了

7.2.1 单继承

在单继承中super()就像大家所想的那样,主要是用来调用父类的方法的


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1class A:
2    def __init__(self):
3        self.n = 2
4
5    def add(self, m):
6        print(f'self is {self} @A.add')
7        self.n += m
8
9    def __str__(self):
10        return "instance of A"
11
12
13class B(A):
14    def __init__(self):
15        self.n = 3
16
17    def __str__(self):
18        return "instance of B"
19
20    def add(self, m):
21        print(f'self is {self} @B.add')
22        super().add(m)
23        self.n += 3
24
25

你觉得执行下面代码后, b.n 的值是多少呢?


1
2
3
4
5
1b = B()
2b.add(2)
3print(b.n)
4
5

执行结果如下:

self is instance of B @B.add
self is instance of B @A.add
8

这个结果说明了两个问题:

  • super().add(m) 确实调用了父类 A 的 add方法
  • super().add(m)调用父类方法 def add(self, m)时, 此时父类中 self并

不是父类的实例而是子类的实例, 所以 b.add(2)之后的结果是 5 而不是 4

7.2.2 多继承

这次我们再定义一个 class C,一个 class D:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1class C(A):
2    def __init__(self):
3        self.n = 4
4
5    def __str__(self):
6        return "instance of C"
7
8    def add(self, m):
9        print(f'self is {self} @C.add')
10        super().add(m)
11        self.n += 4
12
13
14class D(B, C):
15    def __init__(self):
16        self.n = 5
17
18    def __str__(self):
19        return "instance of D"
20
21    def add(self, m):
22        print(f'self is {self} @D.add')
23        super().add(m)
24        self.n += 5
25
26

下面的代码又输出啥呢?


1
2
3
4
5
1d = D()
2d.add(2)
3print(d.n)
4
5

这次的输出如下:

self is instance of D @D.add
self is instance of D @B.add
self is instance of D @C.add
self is instance of D @A.add
19

为什么是这么样的顺序?继续往下看!

7.2.3 super是个类

当我们调用super()的时候,实际上是实例化了一个super类。你没看错, super 是个类,既不是关键字也不是函数等其他数据结构


1
2
3
4
5
6
7
8
1>>> class A:
2   pass
3
4>>> s = super(A)
5>>> type(s)
6<class 'super'>
7
8

在大多数情况下, super 包含了两个非常重要的信息: 一个 MRO 以及 MRO 中的一个类

  • 当以如下方式调用 super 时:

super(a_type, obj)并且isinstance(obj, a_type) = True
MRO 指的是 type(obj)MRO, MRO 中的那个类就是 a_type

  • 当这样调用时:

super(type1, type2)并且issubclass(type2, type1) = True
MRO 指的是 type2MRO, MRO 中的那个类就是 type1

那么,super(arg1, arg2).func()实际上做了啥呢?简单来说就是:

        根据arg2得到MRO列表,然后,查找arg1MRO列表的位置,假设为pos,然后从pos+1开始查找,找到第一个有func()的类,执行该类的func()

虽然super无返回值,但可以认为,它返回值类型和arg2同类型

7.3 MRO(Method Resolution Order)——方法解析顺序

可以通过
**类名.mro()**方法查找出来当前类的调用顺序,其顺序由C3线性算法来决定,保证每一个类只调用一次,下面举个例子就能看懂了:

MRO的C3算法计算过程——计算G的MRO列表

『Python』面向对象 C3算法核心操作是merge,也就是按照类似拓扑排序的方法,把多组mro列表合并。

为简单表示,将object写成O

『Python』面向对象

7.4 C3线性算法的实现


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
1import abc
2def mro(cls) -> list:
3    if type(cls) == type(object) or type(cls) == type(abc.ABC):
4        # object是最顶层类,如果传入的是object,那么就是[object]
5        if issubclass(object,cls):
6            return [cls]
7        else:
8            bases = cls.__bases__
9            return __merge(cls, *[mro(x) for x in bases], list(bases))
10    else:
11        raise TypeError("mro()方法需要一个位置参数cls,类型为class")
12
13def __merge(*args)->list:
14    result = [args[0]]
15    operation_list = list(args[1:])
16    """
17    any()函数:
18        Return True if bool(x) is True for any x in the iterable.
19        If the iterable is empty, return False.
20    """
21    while any(operation_list):
22        # 将空的列表删除,因为有可能传进来的是[[],[],['object']]
23        while [] in operation_list:
24            operation_list.remove([])
25
26        # 拓扑序列中的每个元素(类名列表)
27        for y in operation_list:
28            temp = y[0]
29            need = True
30            for t in operation_list:
31                if temp in t and t.index(temp) > 0:
32                    need = False
33                    break
34            if need:
35                break;
36
37      # 将这个元素添加到结果列表
38        result.append(temp)
39        # 拓扑排序的列表中删除这个元素节点
40        for p in operation_list:
41            while temp in p:
42                p.remove(temp)
43    else:
44        return result
45
46

8. 抽象类

与java一样,python也有抽象类的概念,但是同样需要借助模块实现。

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。如果一个抽象基类要求实现指定的方法,而子类没有实现的话,当试图创建子类或者执行子类代码时会抛出异常。

Python中,定义抽象类需要借助abc模块,abc模块提供了一个使用某个抽象基类声明协议的机制,并且子类一定要提供了一个符合该协议的实现。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1from abc import ABC
2import abc
3# class Animal(metaclass=abc.ABCMeta):
4class Animal(ABC):
5    @abc.abstractmethod
6    def test(cls):
7        pass
8
9    @abc.abstractmethod
10    def bark(self):
11        pass
12
13class Dog(Animal):
14    # 子类需要实现抽象方法,否则报错
15    def bark(self):
16        print("汪汪汪...")
17
18    @classmethod
19    def test(cls):
20        print("Dog的test")
21
22class Cat(Animal):
23    def bark(self):
24        print("喵喵喵...")
25
26    def test(self):
27        print("Cat的test")
28
29# a = Animal()                                # 抽象类不能实例化
30d = Dog()
31d.bark()
32Dog.test()
33
34c = Cat()
35c.bark()
36c.test()
37
38
  1. 必须导入abc模块
  2. 抽象方法用装饰器

@abc.abstractmethod,子类实现时才能确认是实例方法、类方法还是静态方法

  1. 抽象类,要么继承ABC类;要么不写父类,写metaclass=ABCMeta,由元类ABCMeta创建

9. 枚举类

例如星期、月份这些类型,它们的值是公认的,不会随意更改,所以可以事先将这些值都定义出来,用的时候直接拿过来用,这就是枚举类和枚举类型,最主要的一点是穷尽,枚举类型必须是一个枚举类的所有可能结果。这些枚举类型都是只创建一次对象的,每个人要用都是用一样的对象,是不可变的。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1from enum import Enum, unique
2
3# 创建月份的枚举类
4# 枚举类中的每个属性值,默认是从1开始递增的整数
5# 如果指定了一个属性的值,它后面的属性则会从该值开始递增
6# PS:下面这个就是个构造方法,Enum就是个类
7Month = Enum('Month',('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
8                      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
9
10# 如果想自定义属性的值可以这么写,继承Enum类
11# unique装饰器可以帮助我们检查,保证没有重复值
12@unique
13class WeekDay(Enum):
14    Sun = 0                           # Sun的value被设定为0
15    Mon = 1
16    Tue = 2
17    Wed = 3
18    Thu = 4
19    Fri = 5
20    Sat = 6
21
22# 访问枚举类型的值
23print(WeekDay.Mon)                    # WeekDay.Mon
24print(WeekDay['Tue'])                   # WeekDay.Tue
25print(WeekDay.Wed.name)               # Wed
26print(WeekDay.Wed.value)                  # 3
27print(WeekDay(4))                     # WeekDay.Thu
28
29

10. 反射

在程序开发中,常常会遇到这样的需求:在执行对象中的某个方法,或者在调用对象的某个变量,但是由于一些原因,我们无法确定或者并不知道该方法或者变量是否存在,这时我们需要一个特殊的方法或者机制来访问或操作该未知的方法或变量,这种机制就被称之为反射。

反射机制:反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块中寻找指定函数,对其进行操作。也就是利用字符串的形式去对象(模块)中操作(查找or获取or删除or添加)成员,一种基于字符串的事件驱动。

下面介绍反射机制的四个方法:

  • hasattr()函数

语法:
hasattr(object, name)
功能:判断object中是否有属性或者方法name,其中object可以是对象、可以是类、可以是模块名,存在返回True,不存在返回False。

  • getattr()函数

语法:
getattr(object, name, default=None)
功能:返回object的name属性(或方法),不存在则看default有没有传,没有就会报错,传了的话返回default值作为属性(或方法)不存在的返回值。

  • setattr()函数

语法:
setattr(object,name,value)
功能:动态给属性赋值,如果属性不存在,则先创建属性再赋值,另外,这是运行时修改,不会影响文件代码的内容。

  • delattr()函数

语法:
delattr(object,name)
功能:删除object的name属性,,属性不存在则报错。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1import functools
2
3class Student(object):
4    std_no = 1001
5    def __init__(self,name):
6        self.name =name
7    def f(self,string):
8        print(string)
9
10s = Student("芜情")
11print(hasattr(functools,"reduce"))                  # True
12print(hasattr(Student,"std_no"))                    # True
13print(hasattr(Student,"f"))                         # True
14print(hasattr(s,"name"))                            # True
15
16print(getattr(Student,"name","None"))             # None
17getattr(Student,"f","None")(s,"WCG")                # WCG
18
19setattr(Student,"sex","male")
20print(getattr(s,"sex","None"))                        # male
21setattr(Student,"f",functools.reduce)
22print(getattr(s,"f"))                               # <built-in function reduce>
23
24setattr(Student,"sex","male")
25print(getattr(s,"sex","None"))                        # male
26setattr(Student,"f",functools.reduce)
27print(getattr(s,"f"))                               # <built-in function reduce>
28
29delattr(Student,"std_no")
30print(getattr(Student,"std_no","None"))               # None
31delattr(Student,"f")
32print(getattr(Student,"f","None"))                    # None
33
34

11. dataclass数据类

11.1 dataclass装饰器

语法:


1
2
3
1dataclass(*, init = True, repr = True, eq = True, order = False, unsafe_hash = False, frozen = False)
2
3
  • 参数含义

init
默认True,则自动生成__init__()方法
repr
默认True,则自动生成__repr__()方法,格式为类名和各参数名以及参数值
eq
默认True,自动生成__eq__()方法,此方法按顺序比较属性的元组
order
默认False,如果为True,则自动生成__lt__()、le()、__g__t()、ge()方法
unsafe_hash
暂且不管,和hash值有关,一般遇不到
frozen
默认False,如果为True,则禁止更改属性值(类似Java中的final)

  • 示例

首先,要知道下面三种写法是等价的


1
2
3
4
5
1@dataclass
2@dataclass()
3@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
4
5

下面给出具体例子


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1from dataclass import dataclass
2@dataclass(order=True)
3class Student(object):
4    name:str                              
5    age:int = 18
6
7s = Student("yee",16)
8t = Student("sky")
9print(s)                                   # Student(name='yee', age=16)
10print(s==t)                                   # False
11print(s>t)                                 # True
12t.name = "疾风剑豪"                          # frozen=True时,此方法报错
13print(t)                                  # Student(name='疾风剑豪', age=18)
14
15
  • post_init

有些操作需要在初始化后进行,如分离浮点数的整数部分和小数部分:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1from dataclasses import dataclass,field
2import math
3@dataclass
4class FloatNumber:
5    val: float = 0.0
6    def __post_init__(self):                   # 方法签名固定,不能改
7        self.decimal, self.integer = math.modf(self.val)
8
9a = FloatNumber(2.2)
10print(a)                                      # FloatNumber(val=2.2)
11print(a.val)                                  # 2.2
12print(a.integer)                              # 2.0
13print(a.decimal)                              # 0.20000000000000018
14
15

11.2 field函数

语法:


1
2
3
4
5
1field(*, default=MISSING, default_factory=MISSING,
2                           init=True,repr=True,hash=None,
3                                   compare=True,metadata=None):
4
5

MISSING代表类体只有pass语句的类

  • 参数含义

default
如果提供,这将是此字段的默认值。这是必需的,因为field()调用本身取代了默认值的正常位置
default_factory
如果提供,它必须是零参数可调用,当此字段需要默认值时将调用该调用,default_factory不能和default同时出现
init
如果为true(默认值),则此字段作为参数包含在生成的__init__()方法中
repr
如果为true(默认值),则此字段包含在生成的__repr__()方法返回的字符串中
hash
一般不用
compare
如果为真(默认值),则该字段被包括在所产生的==和比较方法
metadata
这是给第三方的API,我们用不到

  • 示例


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1from dataclasses import dataclass,field
2
3@dataclass
4class Student(object):
5    Chinese:float
6    Maths:float
7    English:float
8    total:float = field(init=False)
9    def __post_init__(self):
10        self.total = self.Chinese + self.Maths + self.English
11
12s = Student(98,99,95)
13print(s)  # Student(Chinese=98, Maths=99, English=95, total=292)
14
15

11.3 继承问题

这里继承和一般的继承一样,子类会继承父类的属性和方法,按照mro列表的顺序,查找所调用的函数和属性,都找不到则报错。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++ list, vector, map, set 区别与用法比较

2022-1-11 12:36:11

安全经验

决策树算法

2021-11-28 16:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索