继承

继承的语法

class Animal(object):
def __init__(self,name):
self.__name = name class Dog(Animal):
kind = "Dog"
def __init__(self,name,age):
super.__init__(self,name)
self.__age = age

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

super详解

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

单继承

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

class A:
def __init__(self):
self.n = 2 def add(self, m):
print(f'self is {self} @A.add')
self.n += m def __str__(self):
return "instance of A" class B(A):
def __init__(self):
self.n = 3 def __str__(self):
return "instance of B" def add(self, m):
print(f'self is {self} @B.add')
super().add(m)
self.n += 3

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

b = B()
b.add(2)
print(b.n)

执行结果如下:

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

多继承

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

class C(A):
def __init__(self):
self.n = 4 def __str__(self):
return "instance of C" def add(self, m):
print(f'self is {self} @C.add')
super().add(m)
self.n += 4 class D(B, C):
def __init__(self):
self.n = 5 def __str__(self):
return "instance of D" def add(self, m):
print(f'self is {self} @D.add')
super().add(m)
self.n += 5

下面的代码又输出啥呢?

d = D()
d.add(2)
print(d.n)

这次的输出如下:

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

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

super是个类

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

>>> class A:
pass >>> s = super(A)
>>> type(s)
<class 'super'>

在大多数情况下, 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同类型

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

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

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

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

为简单表示,将object写成O

C3线性算法的实现

import abc
def mro(cls) -> list:
if type(cls) == type(object) or type(cls) == type(abc.ABC):
# object是最顶层类,如果传入的是object,那么就是[object]
if issubclass(object,cls):
return [cls]
else:
bases = cls.__bases__
return __merge(cls, *[mro(x) for x in bases], list(bases))
else:
raise TypeError("mro()方法需要一个位置参数cls,类型为class") def __merge(*args)->list:
result = [args[0]]
operation_list = list(args[1:])
"""
any()函数:
Return True if bool(x) is True for any x in the iterable.
If the iterable is empty, return False.
"""
while any(operation_list):
# 将空的列表删除,因为有可能传进来的是[[],[],['object']]
while [] in operation_list:
operation_list.remove([]) # 拓扑序列中的每个元素(类名列表)
for y in operation_list:
temp = y[0]
need = True
for t in operation_list:
if temp in t and t.index(temp) > 0:
need = False
break
if need:
break; # 将这个元素添加到结果列表
result.append(temp)
# 拓扑排序的列表中删除这个元素节点
for p in operation_list:
while temp in p:
p.remove(temp)
else:
return result

抽象类

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

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

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

from abc import ABC
import abc
# class Animal(metaclass=abc.ABCMeta):
class Animal(ABC):
@abc.abstractmethod
def test(cls):
pass @abc.abstractmethod
def bark(self):
pass class Dog(Animal):
# 子类需要实现抽象方法,否则报错
def bark(self):
print("汪汪汪...") @classmethod
def test(cls):
print("Dog的test") class Cat(Animal):
def bark(self):
print("喵喵喵...") def test(self):
print("Cat的test") # a = Animal() # 抽象类不能实例化
d = Dog()
d.bark()
Dog.test() c = Cat()
c.bark()
c.test()
  1. 必须导入abc模块
  2. 抽象方法用装饰器 @abc.abstractmethod,子类实现时才能确认是实例方法、类方法还是静态方法
  3. 抽象类,要么继承ABC类;要么不写父类,写metaclass=ABCMeta,由元类ABCMeta创建

枚举类

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

from enum import Enum, unique

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

反射

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

反射机制:反射就是通过字符串的形式,导入模块;通过字符串的形式,去模块中寻找指定函数,对其进行操作。也就是利用字符串的形式去对象(模块)中操作(查找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属性,,属性不存在则报错。
import functools

class Student(object):
std_no = 1001
def __init__(self,name):
self.name =name
def f(self,string):
print(string) s = Student("芜情")
print(hasattr(functools,"reduce")) # True
print(hasattr(Student,"std_no")) # True
print(hasattr(Student,"f")) # True
print(hasattr(s,"name")) # True print(getattr(Student,"name","None")) # None
getattr(Student,"f","None")(s,"WCG") # WCG setattr(Student,"sex","male")
print(getattr(s,"sex","None")) # male
setattr(Student,"f",functools.reduce)
print(getattr(s,"f")) # <built-in function reduce> setattr(Student,"sex","male")
print(getattr(s,"sex","None")) # male
setattr(Student,"f",functools.reduce)
print(getattr(s,"f")) # <built-in function reduce> delattr(Student,"std_no")
print(getattr(Student,"std_no","None")) # None
delattr(Student,"f")
print(getattr(Student,"f","None")) # None

dataclass数据类

dataclass装饰器

语法:

dataclass(*, init = True, repr = True, eq = True, order = False, unsafe_hash = False, frozen = False)

参数含义

  • 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)

示例

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

@dataclass
@dataclass()
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)

下面给出具体例子

from dataclass import dataclass
@dataclass(order=True)
class Student(object):
name:str
age:int = 18 s = Student("yee",16)
t = Student("sky")
print(s) # Student(name='yee', age=16)
print(s==t) # False
print(s>t) # True
t.name = "疾风剑豪" # frozen=True时,此方法报错
print(t) # Student(name='疾风剑豪', age=18)

__post_init__

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

from dataclasses import dataclass,field
import math
@dataclass
class FloatNumber:
val: float = 0.0
def __post_init__(self): # 方法签名固定,不能改
self.decimal, self.integer = math.modf(self.val) a = FloatNumber(2.2)
print(a) # FloatNumber(val=2.2)
print(a.val) # 2.2
print(a.integer) # 2.0
print(a.decimal) # 0.20000000000000018

field函数

语法:

field(*, default=MISSING, default_factory=MISSING,
init=True,repr=True,hash=None,
compare=True,metadata=None):

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

参数含义

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

示例

from dataclasses import dataclass,field

@dataclass
class Student(object):
Chinese:float
Maths:float
English:float
total:float = field(init=False)
def __post_init__(self):
self.total = self.Chinese + self.Maths + self.English s = Student(98,99,95)
print(s) # Student(Chinese=98, Maths=99, English=95, total=292)

继承问题

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

『Python』面向对象(二)的更多相关文章

  1. 『Python』面向对象(一)

    类和对象 类(class)是用来描述具有相同属性(attribute)和方法(method)的对象的集合,对象(object)是类(class)的具体实例.比如学生都有名字和分数,他们有着共同的属性. ...

  2. 『Python』__getattr__()特殊方法

    self的认识 & __getattr__()特殊方法 将字典调用方式改为通过属性查询的一个小class, class Dict(dict): def __init__(self, **kw) ...

  3. 第八章:Python基础の面向对象(二)

    本課主題 面向对象的多态 面向对象的成员 成员修饰符 特殊成员 面向对象其他应用 异常处理 设计模式与单例模式 面向对象的多态 指定参数类型只是多态的一种表现 另外一种是允许自己类型和自己的子类型(典 ...

  4. Python之面向对象二

    面向对象的三大特性: 继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类 python中类的继承分为:单继承和多继承 c ...

  5. 『Python』为什么调用函数会令引用计数+2

    一.问题描述 Python中的垃圾回收是以引用计数为主,分代收集为辅,引用计数的缺陷是循环引用的问题.在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. sys.g ...

  6. 『Python』装饰器

    一.参考 作者:zhijun liu 链接:https://www.zhihu.com/question/26930016/answer/99243411 来源:知乎 建议大家去原答案浏览 二.装饰器 ...

  7. 『Python』内存分析_list和array

    零.预备知识 在Python中,列表是一个动态的指针数组,而array模块所提供的array对象则是保存相同类型的数值的动态数组.由于array直接保存值,因此它所使用的内存比列表少.列表和array ...

  8. 『Python』VS2015编译源码注意事项

    一.2.5.6版本源码编译 解压 Python-2.5.6.tgz 进入 Pcbuild8 文件夹,使用 vs 2013 打开 pybuild.sln (vs 解决方案),进入 vs2015IDE 环 ...

  9. 『Python』源码解析_源码文件介绍

    本篇代码针对2.X版本,与3.X版本细节不尽相同,由于两者架构差别不大加之本人能力有限,所以就使用2.X体验python的底层原理了. 一.主要文件夹内容 Include :该目录下包含了Python ...

随机推荐

  1. VS+Qt+Halcon——显示图片,实现鼠标缩放、移动图片

    摘要 本篇博文记录一下,用VS+Qt+Halcon实现对图片的读取以及鼠标缩放,移动(鼠标事件调用了halcon自带的算子)的过程.以及遇到的坑..... 先来看一下动态效果图: 主要控件: 添加一个 ...

  2. spring学习日志二

    一.spring依赖注入的方式 1.通过set方法来完成注入 <bean id="student" class="com.zhiyou100.xz.spring.S ...

  3. C语言定义常量

     /* #define 标识符 #define day main中day=10;仅一次赋值*/        错误 #define 标识符 常量值 #define day 10;

  4. tensorflow summary demo with linear-model

    tf.summary + tensorboard 用来把graph图中的相关信息,如结构图.学习率.准确率.Loss等数据,写入到本地硬盘,并通过浏览器可视化之. 整理的代码如下: import te ...

  5. Hadoop及Hbase部署

    原文转自:https://www.cnblogs.com/itzgr/p/10233932.html作者:木二 目录 一 环境准备 1.1 相关环境 1.2 网络环境 二 基础环境配置 2.1 配置相 ...

  6. openresty lua-resty-redis 封装 a wrapper for lua-resty-redis 优雅一点点

    搜了一下别人的封装代码,感觉不够优雅,主要是 set_keepalive 的调用时机不太好 我自己下面的代码是利用 coroutine, 每次当前 phase 结束后自动调用 set_keepaliv ...

  7. Nginx反向代理之巨坑underscores_in_headers

    一.背景 因为项目需求,在做Windows的相关的事情:基本架构就是Nginx--> Nginx --> IIS,在Linux机器上通过Nginx做反向代理到Windows的IIS:然后遇 ...

  8. RHCA-openstack学习部署笔记-CL210

    笔记说明:目前考试是基于RHELOSP13(32G)来进行环境部署,引入了容器,而学习是基于RHELOSP6来安装部署的,基于Openstack. 讨论:openstack.虚拟化和云计算的关系以及区 ...

  9. 推荐一款编程字体:Iosevka

    最近发现一款很好用的编程字体:Iosevka.它是一款现代化的编程字体集合,除了等宽.oO0 iIl1明显区分等基本特性外,还有很多非常现代的特性,比如: 多种风格:有非常多的字形可供选择,衬线/非衬 ...

  10. Fastjson反序列化漏洞基础

    Fastjson反序列化漏洞基础 FastJson是alibaba的一款开源JSON解析库,可用于将Java对象转换为其JSON表示形式,也可以用于将JSON字符串转换为等效的Java对象. 0x0 ...