21.方法重写
重写是指子类重写父类的成员方法。子类可以改变父类方法所实现的功能, 但子类中重写的方法必须与父类中对应的方法具有相同的方法名。也就是说 要实现重写,就必须存在继承。1
class Person():
def print_info(self):
print("*************")
class ChinesePerson(Person):
def print_info(self):
#子类重写父类的print_info方法
print("________")
如果想在子类里面调用父类的同名方法:1
class Person():
def __init__(self,name):
self.name = name
def set_name(self,name):
if len(name)>5:
return
self.name = name
class ChinesePeople(Person):
def __init__(self,name,nation):
Person.__init__(self,name)
self.nation = nation
def set_name(self,name):
#子类中明确的调用父类中被重写的方法
Person.set_name(self,name)
#写法一
#super(ChinesePeople,self).set_name(name)
#写法二
#super.set_name()
#写法三
如果想用子类的实例调用父类被重写的方法,我们可以使用__class__
p = ChinesePeople("吴老师","汉")
p.__class__ = Person
p.set_name("abcd")
p.__class__ = ChinesePeople
# 调用完记得修改回来
__init__
,__new__
,__class__
的其他说明参见2、3、4
22.函数重载
函数重载指的是有多个同名的函数,但是它们的签名或实现却不同。当调用一个重载函数 fn 时,程序会检验传递给函数的实参/形参,并据此而调用相应的实现。5
int area(int length, int breadth) {
return length * breadth;
}
float area(int radius) {
return 3.14 * radius * radius;
}
在以上例子中(用
c++
编写),函数area
被重载了两个实现。第一个函数接收两个参数(都是整数),表示矩形的长度和宽度,并返回矩形的面积。另一个函数只接收一个整型参数,表示圆的半径。
当我们像area(7)
这样调用函数area
时,它会调用第二个函数,而area(3,4)
则会调用第一个函数。5
python
是没有重载的,第二个同名的函数会把第一个覆盖掉。因为:1
python
参数不指定类型python
参数可变
python
不需要函数重载
函数重载主要是为了解决两个问题。
- 可变参数类型。
- 可变参数个数。
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
好吧,那么对于情况1
,函数功能相同,但是参数类型不同,python
如何处理?答案是根本不需要处理,因为python
可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在python
中很可能是相同的代码,没有必要做成两个不同函数。
那么对于情况2
,函数功能相同,但参数个数不同,python
如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。
好了,鉴于情况1
跟 情况2
都有了解决方案,python
自然就不需要函数重载了。6
但是在某些情况当中,我们又是需要重载的,例如上文的面积计算函数。对于这种情形,我们可以使用singledispatch
装饰器:7
from functools import singledispatch
from collections import abc
@singledispatch
def show(obj):
print (obj, type(obj), "obj")
# 参数字符串
@show.register(str)
def _(text):
print (text, type(text), "str")
# 参数int
@show.register(int)
def _(n):
print (n, type(n), "int")
# 参数元祖或者字典均可
@show.register(tuple)
@show.register(dict)
def _(tup_dic):
print (tup_dic, type(tup_dic), "int")
show(1)
show("xx")
show([1])
show((1,2,3))
show({"a":"b"})
# 1 <class 'int'> int
# xx <class 'str'> str
# [1] <class 'list'> obj
# (1, 2, 3) <class 'tuple'> int
# {'a': 'b'} <class 'dict'> int
singledispatch
主要针对的是函数,但对于方法不友好,现在可以用singledispatchmethod
来做。8
class Negator:
@singledispatchmethod
@classmethod
def neg(cls, arg):
raise NotImplementedError("Cannot negate a")
@neg.register
@classmethod
def _(cls, arg: int):
return -arg
@neg.register
@classmethod
def _(cls, arg: bool):
return not arg
关于singledispatch
的底层原理解释详见9,其机制类似于下文我们自行实现的函数重载。
我们也可以用装饰器自行实现函数重载,详见5:
# 模块:overload.py
from inspect import getfullargspec
class Function(object):
"""Function is a wrap over standard python function
An instance of this Function class is also callable
just like the python function that it wrapped.
When the instance is "called" like a function it fetches
the function to be invoked from the virtual namespace and then
invokes the same.
"""
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
"""Overriding the __call__ function which makes the
instance callable.
"""
# fetching the function to be invoked from the virtual namespace
# through the arguments.
fn = Namespace.get_instance().get(self.fn, *args)
if not fn:
raise Exception("no matching function found.")
# invoking the wrapped function and returning the value.
return fn(*args, **kwargs)
def key(self, args=None):
"""Returns the key that will uniquely identifies
a function (even when it is overloaded).
"""
if args is None:
args = getfullargspec(self.fn).args
return tuple([
self.fn.__module__,
self.fn.__class__,
self.fn.__name__,
len(args or []),
])
class Namespace(object):
"""Namespace is the singleton class that is responsible
for holding all the functions.
"""
__instance = None
def __init__(self):
if self.__instance is None:
self.function_map = dict()
Namespace.__instance = self
else:
raise Exception("cannot instantiate Namespace again.")
@staticmethod
def get_instance():
if Namespace.__instance is None:
Namespace()
return Namespace.__instance
def register(self, fn):
"""registers the function in the virtual namespace and returns
an instance of callable Function that wraps the function fn.
"""
func = Function(fn)
specs = getfullargspec(fn)
self.function_map[func.key()] = fn
return func
def get(self, fn, *args):
"""get returns the matching function from the virtual namespace.
return None if it did not fund any matching function.
"""
func = Function(fn)
return self.function_map.get(func.key(args=args))
def overload(fn):
"""overload is the decorator that wraps the function
and returns a callable object of type Function.
"""
return Namespace.get_instance().register(fn)
from overload import overload
@overload
def area(length, breadth):
return length * breadth
@overload
def area(radius):
import math
return math.pi * radius ** 2
@overload
def area(length, breadth, height):
return 2 * (length * breadth + breadth * height + height * length)
@overload
def volume(length, breadth, height):
return length * breadth * height
@overload
def area(length, breadth, height):
return length + breadth + height
@overload
def area():
return 0
print(f"area of cuboid with dimension (4, 3, 6) is: {area(4, 3, 6)}")
print(f"area of rectangle with dimension (7, 2) is: {area(7, 2)}")
print(f"area of circle with radius 7 is: {area(7)}")
print(f"area of nothing is: {area()}")
print(f"volume of cuboid with dimension (4, 3, 6) is: {volume(4, 3, 6)}")
23.钻石继承
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
A.__init__(self)
print("B init")
class C(A):
def __init__(self):
A.__init__(self)
print("C init")
class D(B, C):
def __init__(self):
B.__init__(self)
C.__init__(self)
print("D init")
d = D()
# 运行结果为:
# A init
# B init
# A init
# C init
# D init
# 从运行结果可以看到A被初始化了两次
class A(object):
def __init__(self):
print("A init")
class B(A):
def __init__(self):
super(B, self).__init__()
print("B init")
class C(A):
def __init__(self):
super(C, self).__init__()
print("C init")
class D(B, C):
def __init__(self):
super(D, self).__init__()
print("D init")
d = D()
# 运行结果为:
# A init
# C init
# B init
# D init
# 可以看到使用super调用父类的构造函数A只初始化了一次
Python
使用super
方法解决钻石继承问题。其中super
的内核是MRO(Method Resolution Order,函数解析顺序)
,MRO
使用C3
线性算法生成,通过类名.__mro__
获取。一般来说,该顺序为当前类、继承类的继承序列、继承类序列的父类序列···
,按照上文的例子就是[D, B, C, A]
。当子类的多个父类有同一个方法,而子类使用时未指定,即该方法在子类中未找到时,解析器就按照MRO
的列表查找父类是否包含该方法,查找到的第一个结果即为返回结果,这也就是上文当中提到的方法遮蔽
问题。关于MRO
等的其他解释详见10,C3
线性算法的解释详见11、12
24.MixIn混入类
Mix-In
是一种拼积木的思想,是多重继承的最后一层。12
class Animal(object):
def __init__(self, name):
self.name = name
def eat(self):
print('%s正在吃东西' % self.name)
def breath(self):
print('%s正在呼吸' % self.name)
class Person(Animal):
def __init__(self, name, money):
super().__init__(name)
self.money = money
def speak(self):
print('%s说他有%s人民币' % (self.name, self.money))
class Spider(Animal):
def climb(self):
print('%s正在攀岩' % self.name)
def tusi(self):
print('%s正在吐丝' % self.name)
class Spiderman(Person, Spider):
pass
Spiderman = Spiderman('Spiderman', 10)
Spiderman.tusi()
Spiderman.climb()
Spiderman.eat()
Spiderman.breath()
# Spiderman正在吐丝
# Spiderman正在攀岩
# Spiderman正在吃东西
# Spiderman正在呼吸
Mix-In
的设计也是一种Duck Type
的类型14
25.多态
调用不同对象的相同方法,表现不一样,这就是多态。
import abc
class Animal(metaclass=abc.ABCMeta):
#同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
# raise AttributeError('子类必须实现这个方法')
# 上述代码子类是约定俗称的实现这个方法
# 加上@abc.abstractmethod装饰器后要求子类必须实现这个方法
# 但其虚拟子类不用强制实现
class Cat(Animal):
#动物的形态之一:猫
def talk(self):
print('say miaomiao')
class Dog(Animal):
#动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal):
#动物的形态之三:猪
def talk(self):
print('say aoao')
c = Cat()
d = Dog()
p = Pig()
def func(obj):
obj.talk()
func(c)
func(d)
func(p)
# ------------------------------
#
# >>> say miaomiao
# >>> say wangwang
# >>> say aoao
调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,这是多态的重要应用场景。而在
python
中,因为鸭子类型(duck typing)
使得其多态不是那么酷。
鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由James Whitcomb Riley
提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”16
class Duck():
def walk(self):
print('I walk like a duck')
def swim(self):
print('i swim like a duck')
class Person():
def walk(self):
print('this one walk like a duck')
def swim(self):
print('this man swim like a duck')
可以很明显的看出,
Person
类拥有跟Duck
类一样的方法,当有一个函数调用Duck
类,并利用到了两个方法walk()
和swim()
。我们传入Person
类也一样可以运行,函数并不会检查对象的类型是不是Duck
,只要他拥有walk()
和swim()
方法,就可以正确的被调用。
再举例,如果一个对象实现了getitem
方法,那python
的解释器就会把它当做一个collection
,就可以在这个对象上使用切片,获取子项等方法;如果一个对象实现了iter
和next
方法,python
就会认为它是一个iterator
,就可以在这个对象上通过循环来获取各个子项。16
其详细解释参见16
Mix-In
的设计也是一种Duck Type
的类型14
关于abc.ABCMeta
,上述代码子类是约定俗称的实现这个方法,加上@abc.abstractmethod
装饰器后严格控制子类必须实现这个方法。17
abc.ABCMeta
是一个metaclass
,用于在Python
程序中创建抽象基类。抽象基类可以不实现具体的方法(当然也可以实现,只不过子类如果想调用抽象基类中定义的接口需要使用super())
而是将其留给派生类实现。抽象基类可以被子类直接继承,也可以将其他的类”注册“(register
)到其门下当虚拟子类,虚拟子类的好处是你实现的第三方子类不需要直接继承自基类但是仍然可以声称自己子类中的方法实现了基类规定的接口(issubclass()
,issubinstance()
)!18
抽象方法表示基类的一个方法,没有实现,所以基类不能实例化,子类实现了该抽象方法才能被实例化。19
26.__str__
和__repr__
举例如下:20
>>> class Name:
... def __init__(self,name):
... self.name = name
... def __repr__(self):
... return 'Name: %s' % self.name
...
>>> student = Name('Jack')
>>> student
Name: Jack
>>> print(student)
Name: Jack
仅重构__repr__
的情况下,直接输入对象名和调用打印方法输出的都是我们自定义的提示。如果一个对象没有__str__
函数,而Python
又需要调用它的时(调用print
),解释器会用__repr__
作为替代。21
>>> class Name:
... def __init__(self,name):
... self.name = name
... def __str__(self):
... return 'Name: %s' % self.name
...
>>> student = Name('Jack')
>>> student
<__main__.Name object at 0x00000207D3949DC8>
>>> print(student)
Name: Jack
仅重构__str__
的情况下,直接输入对象名,返回的是内存地址;调用打印方法输出的都是我们自定义的提示
>>> class Name:
... def __init__(self,name):
... self.name = name
... def __str__(self):
... return '__str__'
... def __repr__(self):
... return '__repr__'
...
>>> student = Name('Jack')
>>> student
__repr__
>>> print(student)
__str__
当两者均被重构时,直接在终端输入对象名,调用的是__repr__
;使用打印函数,调用的是__str__
函数
另外,repr()
函数会默认调用该对象的__repr__
,对应的str()
会调用__str__
,同时,%r
调用的是__repr__
,%s
调用的是__str__
。22举例如下:
import datetime
today = datetime.datetime.today()
print(str(today))
>>> 2019-10-20 20:59:47.003003
print(repr(today))
>>> datetime.datetime(2019, 10, 20, 20, 59, 47, 3003)
__str__
的返回结果可读性强。也就是说,__str__
的意义是得到便于人们阅读的信息,就像上面的'2019-10-20 20:59:47.003003'
一样。
__repr__
的返回结果应更准确。怎么说,__repr__
存在的目的在于调试,便于开发者使用。将__repr__
返回的方式直接复制到命令行上,是可以直接执行的。
推荐类中至少添加一个__repr__
方法,因为__str__
的默认实现就是调用__repr__
方法。23
27.新类和旧类
- 相关定义
Python
中的类分为新类和旧类。旧类是Python3
之前的类,旧类并不是默认继承object
类,而是继承type
类。
Python2
中的旧类如下面代码所示:class oldStyleClass: # inherits from 'type' pass
Python2
中定义一个新类:class newStyleClass(object): # explicitly inherits from 'object' pass
在
Python3
中所有的类均默认继承object
,所以并不需要显式地指定object
为基类。
以object
为基类可以使得所定义的类具有新类所对应的方法(methods
)和属性(properties
)。24
经典类:
classic class
新式类:new-style class
python2.2
之前并没有新式类python2.2-2.7
新式类与经典类并存, 默认使用经典类, 除非显式继承object
python3.X
中去除了经典类, 用户定义的所有类都隐式继承自object
25
- 统一类(
class
)和类型(type
)
在
2.2
之前,比如2.1
版本中,类和类型是不同的,如a
是ClassA
的一个实例,那么a.__class__
返回class __main__.ClassA
,type(a)
返回总是<type 'instance'>
。而引入新类后,比如ClassB
是个新类,b
是ClassB
的实例,b.__class__
和type(b)
都是返回class '__main__.ClassB'
,这样就统一了。
引入新类后,还有其他的好处,比如更多的内置属性将会引入,描述符的引入,属性可以来计算等等。
为了向前兼容,默认情况下用户定义的类为经典类,新类需要继承自所有类的基类object
或者继承自object
的新类。26class oldClass: #经典类 def __init__( self ): pass class newClass(object): #新类 def __init__( self ): pass c1 = oldClass() c2 = newClass() c1.__class__ # 输出-> <class __main__.oldClass at 0x0137BF10> type(c1) # 输出-> <type 'instance'> c2.__class__ # 输出-><class '__main__.newClass'> type(c2) # 输出-><class '__main__.newClass'>
前文提到,在
Python 3.x
中,所有的类都显式或隐式的派生自object
类,type
类也不例外。类型自身派生自object
类,而object
类派生自type
,二者组成了一个循环的关系。27
通过以下代码来验证isinstance(object, type) # True isinstance(type, object) # True
MRO
算法的区别
- 旧式类
MRO
算法:从左往右,采用深度优先搜索(DFS
),从左往右的算法,称为旧式类的MRO
- 新式类
MRO
算法:自Python 2.2
版本开始,新式类在采用深度优先搜索算法的基础上,对其做了优化C3
算法:自Python 2.3
版本,对新式类采用了C3
算法;由于Python 3.x
仅支持新式类,所以该版本只使用C3
算法
C3
算法的部分结果与广度优先一致,但其顺序并不是广度优先,举例如下:28
class NewStyleClassA(object):
var = 'New Style Class A'
class NewStyleClassB(NewStyleClassA):
pass
class NewStyleClassC(object):
var = 'New Style Class C'
class SubNewStyleClass(NewStyleClassB, NewStyleClassC):
pass
print(SubNewStyleClass.mro())
print(SubNewStyleClass.var)
# [
# <class '__main__.SubNewStyleClass'>,
# <class '__main__.NewStyleClassB'>,
# <class '__main__.NewStyleClassA'>,
# <class '__main__.NewStyleClassC'>,
# <class 'object'>
# ]
# New Style Class A
如果按照广度优先应该是Sub->B->C->A->Object
关于C3
算法的解释详见29
__new__
和__init__
的区别
__new__
和__init__
的主要区别在于:__new__
是用来创造一个类的实例的(constructor
),而__init__
是用来初始化一个实例的(initializer
)。
__new__
所接收的第一个参数是cls
,而__init__
所接收的第一个参数是self
。这是因为当我们调用__new__
的时候,该类的实例还并不存在(也就是self
所引用的对象还不存在),所以需要接收一个类作为参数,从而产生一个实例。而当我们调用__init__
的时候,实例已经存在,因此__init__
接受self
作为第一个参数并对该实例进行必要的初始化操作。这也意味着__init__
是在__new__
之后被调用的。
Python
的旧类中实际上并没有__new__
方法。因为旧类中的__init__
实际上起构造器的作用。
总结:
- __init__不能有返回值
- __new__函数直接上可以返回别的类的实例。如上面例子中的returnExistedObj类的__new__函数返回了一个int值。
- 只有在__new__返回一个新创建属于该类的实例时当前类的__init__才会被调用。
详见24
- 内置属性
__slots__
25
而除了继承顺序的差异, 新式类还添加了内置属性
__slots__
一般来说, 每个实例都有一个字典来管理实例的属性, 我们可以用__dict__
来查看(__dict__
并不保存类属性),它允许我们动态地修改实例的属性, 但是这也意味着每个实例都会有1个独立的字典需要我们去维护, 当我们需要创建大量的实例时, 这个操作是十分消耗内存的.
当我们在定义类时添加了__slots__
属性后, 对象在实例化时就不会创建字典来管理实例属性, 而实例只能定义在__slots__
里边已经设定好的属性名, 不允许动态添加其他未在__slots__
里定义的属性class Student(object): __slots__ = ('id', 'name', 'gender') def exam(self): pass s1 = Student() '__dict__' in dir(s1) # False s1.id = 10001 s1.class = 1 # AttributeError: 'Student' object has no attribute 'class' def func(): pass s1.exam = func # AttributeError: 'Student' object attribute 'f' is read-only
使用
__slots__
后我们不再能够动态地修改实例的属性, 那么使用__slots__
究竟有什么好处呢?
优点:
- 节省内存
- 提高属性访问速度
缺点:
- 不能动态修改实例属性
当然, 除了继承顺序和__slots__
, 新式类添加了__getattribute__
方法, 还修改了实例的类型
28.MRO
关于C3
线性算法详见11、29等,此处暂且不表,只需记得不是广度优先,后面有时间再补充这块。