目录
__getitem__、__setitem__、__delitem__
十五.多继承的继承顺序 - mro
调用父类方式不同导致结果不同
单独调用父类的方法
class Parent:
def __init__(self, name):
print('parent的init开始被调用...')
self.name = name
print('parent的init结束被调用...')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用...')
self.age = age
Parent.__init__(self, name)
print('Son1的init结束被调用...')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用...')
self.gender = gender
Parent.__init__(self, name)
print('Son2的init结束被调用...')
class GrandSon(Son1, Son2):
def __init__(self, name, age, gender):
print('GrandSon的init开始被调用...')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('GrandSon的init结束被调用...')
gs = GrandSon('grandson', 12, '男')
print('-' * 30)
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
运行结果:
GrandSon的init开始被调用...
Son1的init开始被调用...
parent的init开始被调用...
parent的init结束被调用...
Son1的init结束被调用...
Son2的init开始被调用...
parent的init开始被调用...
parent的init结束被调用...
Son2的init结束被调用...
GrandSon的init结束被调用...
------------------------------
多继承中super调用被重写的父类方法
class Parent:
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用...')
self.name = name
print('parent的init结束被调用...')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用...')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用...')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用...')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用...')
class GrandSon(Son1, Son2):
def __init__(self, name, age, gender):
print('GrandSon的init开始被调用...')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(GrandSon, self).__init__(name, age, gender)
super().__init__(name, age, gender)
print('GrandSon的init结束被调用...')
gs = GrandSon('grandson', 12, '男')
print('-' * 30)
print(GrandSon.__mro__) # 输出继承顺序
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
运行结果:
GrandSon的init开始被调用...
Son1的init开始被调用...
Son2的init开始被调用...
parent的init开始被调用...
parent的init结束被调用...
Son2的init结束被调用...
Son1的init结束被调用...
GrandSon的init结束被调用...
------------------------------
(<class '__main__.GrandSon'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
姓名: grandson
年龄: 12
性别: 男
上述两种调用父类的方法是有区别的:
- 如果两个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了两次
- 如果两个子类中都继承了父类,当在子类中通过super调用时,parent被执行了一次
单继承中的super
class Parent:
def __init__(self, name):
print('parent的init开始被调用...')
self.name = name
print('parent的init结束被调用...')
class Son(Parent):
def __init__(self, name, age):
print('Son的init开始被调用...')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son的init结束被调用...')
class Grandson(Son):
def __init__(self, name, age, gender):
print('GrandSon的init开始被调用...')
self.gender = gender
super().__init__(name, age) # 单继承不能提供全部参数
print('GrandSon的init结束被调用...')
gs = Grandson('grandson', 12, '男')
print('-' * 30)
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
运行结果:
GrandSon的init开始被调用...
Son的init开始被调用...
parent的init开始被调用...
parent的init结束被调用...
Son的init结束被调用...
GrandSon的init结束被调用...
------------------------------
姓名: grandson
年龄: 12
性别: 男
简单总结
- super().__init__相对于类名.__init__,在单继承上用法基本无差。
- 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果。
- 多继承时,使用super方法,对父类的传参数,由于super的算法导致的原因,必须把参数全部传递,否则会报错。
- 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错。
- 多继承时,相对于使用类名.__init__()方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因。
面试题
以下代码将会输出什么?
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
输出结果:
1 1 1
1 2 1
3 2 3
使你困惑或是惊奇的是关于最后一行的输出是3 2 3而不是3 2 1。为什么改变了Parent.x的值还会改变Child2.x的值,但是同时Child1.x值却没有改变?
这个答案的关键是,在Python中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到(如果这个被引用的变量名既没有在自己所在的类又没有在祖先类中找到,会引发一个AttributeError异常 )。
因此,在父类中设置x = 1会使得类变量x在引用该类和其任何子类中的值为1。这就是因为第一个print语句的输出是1 1 1。
随后,如果任何它的子类重写了该值(例如,我们执行语句Child1.x = 2),然后,该值仅仅在子类中被改变。这就是为什么第二个print语句的输出是1 2 1。
最后,如果该值在父类中被改变(例如,我们执行语句Parent.x = 3),这个改变会影响到任何未重写该值的子类当中的值(在这个示例中被影响的子类是Child2)。这就是为什么第三个print输出是3 2 3
十六.魔术方法
魔术方法概述
Python中的魔术方法是一组预定义的方法,它们以双下划线开头和结尾(例如__init__、__str__ 等)。它们有特别的意义,因为Python的解释器会在某些内置行为发生时自动调用它们。魔术方法允许开发者定义或修改类的默认行为。
Python3中定义的类都是新式类,无论是否写明一个类继承object,都会间接或直接继承object。
class Person(object):
pass
In [2]: dir(Person)
Out[2]:
['__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'__weakref__']
Python2默认不继承object。
# Python2中无继承父类,称之经典类。Python3中已默认继承object
class Person:
pass
Python 2.7.16 (default, Mar 25 2021, 18:52:10)
[GCC 4.2.1 Compatible Apple LLVM 10.0.1 (clang-1001.0.37.14)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class Person:
... pass
...
>>> dir(Person)
['__doc__', '__module__']
>>>
魔术方法概览
方法名称 |
说明 |
触发方式 |
|
构造初始化函数 |
创建实例后赋值时使用,在 |
|
创建类的实例 |
创建实例时被调用 |
|
实例所在的类的地址 |
|
|
返回实例对象的字符串信息 |
|
|
返回实例对象的字符串信息 |
|
|
析构(对象被删除前做清理工作) |
|
|
实例自定义属性 |
|
|
类文档说明,子类不继承 |
|
|
属性访问拦截器 |
访问实例属性时 |
|
获取类所有的直接父类:不能获取父类的父类 |
|
__getattribute__属性
__getattribute__功能很强大:能够完成属性访问时进行拦截
class Student:
country = '中国'
def __init__(self, name, age, address):
self.name = name
self.age = age
self.__address = address
def info(self):
print(self.name, self.age, self.__address, Student.country) # 使用类对象获取国家
def __info_method(self):
print(self.name, self.age, self.__address, self.country) # 使用实例对象获取国家
def __getattribute__(self, item): # item传入的是属性名而不是属性值
print('开始校验属性或方法:', item)
return object.__getattribute__(self, item) # 返回属性值
stu = Student('安娜', 18, '长沙')
print('实例对象打印属性: ', stu.name, stu.age, stu.country) # 获取属性会触发__getattribute__
print('-' * 30)
stu.info() # 调用实例方法
print('-' * 30)
stu._Student__info_method() # 调用私有方法
print('-' * 30)
print('类对象打印属性:', Student.country) # 使用类对象获取类属性不会触发__getattribute__
'''
1.直接使用类名.类属性的形式调用类属性不会触发__getattribute__方法
2.如果在当前这个类中调用了方法则__getattribute__会获取当前这个方法的名称
'''
运行结果:
开始校验属性或方法: name
开始校验属性或方法: age
开始校验属性或方法: country
实例对象打印属性: 安娜 18 中国
------------------------------
开始校验属性或方法: info
开始校验属性或方法: name
开始校验属性或方法: age
开始校验属性或方法: _Student__address
安娜 18 长沙 中国
------------------------------
开始校验属性或方法: _Student__info_method
开始校验属性或方法: name
开始校验属性或方法: age
开始校验属性或方法: _Student__address
开始校验属性或方法: country
安娜 18 长沙 中国
------------------------------
类对象打印属性: 中国
__getattribute__注意事项
class Person:
def __getattribute__(self, item):
print("查询指定的属性或方法是否存在...")
if item.startswith("h"):
return "world"
else:
return self.set_attribute_method() # 会出现递归调用
# try:
# return super().__getattribute__(item)
# except AttributeError:
# self.set_attribute_method()
@staticmethod
def set_attribute_method():
return "模拟设置属性的方法..."
p = Person()
# 返回world
print(p.hello)
# 会让程序死掉
# print(p.python)
"""
不要在__getattribute__方法中使用self关键字
只要self关键字使用类中的属性和方法就会触发__getattribute__
"""
常用的魔术方法
__doc__
表示类的描述信息
class Foo:
""" 描述类信息,这是一个类的简单描述 """
def func(self):
pass
print(Foo.__doc__)
__module__和__class__
- __module__表示当前操作的对象在哪个模块
- __class__表示当前操作的对象的类是谁
test.py文件
class Person:
def __init__(self):
self.name = '安娜'
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
__init__
构造方法:通过类创建对象时,自动触发执行
class Person:
def __init__(self, name):
self.name = name
self.age = 18
obj = Person('安娜') # 自动执行类中的 __init__ 方法
__del__
当对象在内存中被释放之前,自动触发执行
注:此方法一般无须定义。因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以__del__的调用是由解释器在进行垃圾回收前自动触发,执行一些完善工作。
class Foo:
def __del__(self):
pass
__call__
对象后面加括号,触发执行
注:__init__方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或者 类()()。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
__dict__
类或对象中的所有属性
类的实例属性属于对象,类中的类属性和方法等属于类。
class Province:
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
__str__
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值
class Foo:
def __str__(self):
return '双双'
obj = Foo()
print(obj)
# 输出:双双
__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo:
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = '安娜' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__