@
前言
通过这篇笔记,来达成两个目的:
一、帮助自己巩固一下python中和类有关的一些基础知识点,明晰在定义类的过程中各部分代码(主要是魔法方法)所起的作用。
二、了解类的继承到底是继承了什么?super()函数起了什么作用?调用父类方法时python的执行顺序是什么?——以此加深自己对继承的理解。
阅读正文前应掌握的两个工具
魔法属性__dict__
类名._dict_,返回一个字典,由类中所有属性、方法的键值对组成。
实例的__dict__仅存储与该实例相关的实例属性,并不包含该实例的所有有效属性。类的__dict__存储所有实例共享的变量和函数(类属性,方法等),类的__dict__并不包含其父类的属性。
class A():
z = 100
def __init__(self, a, b):
self.a = a
self.b = b
def foo(self):
print('hello')
a = A(4, 6)
print(a.__dict__) #{'a': 4, 'b': 6}
print(A.__dict__)
# {'__module__': '__main__', 'z': 100,
'__init__': <function A.__init__ at 0x000002127989ED30>,
'foo': <function A.foo at 0x000002127989EDC0>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
super()函数
格式:super(cls,self).方法名(参数)
cls是类名,self代指当前类的实例。(你在为哪个类写方法,这个类就是“当前类”)
python3之后,super().方法名(参数)等同于super(当前类,self).方法名(参数)。(见下方示例)
代码运行解析:类super实例化。这一实例包含了两个信息:一是以当前类为基础的MRO列表,二是处于该MRO列表中的类cls。还有一个条件:当前类必须是cls的子类。(当前类也是自身的子类,所以cls可以是当前类)
【当前类的MRO列表可以通过“print(当前类.mro())”查看】
作用:搜索在类A的MRO列表中处于类cls右方的类,找到某一方法然后调用它。例如[A,B,C,D,cls,E,F,G,object],从类E、类F、类G、类object中搜索。
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
class B(A):
def __init__(self, a, b, c):
super().__init__(a, b) #等同于super(B,self).__init__(a,b)
#在这里可以使用“A.__init__(a,b)”代替上行代码
self.c = c
print('this is B')
B(1, 2, 3)
#this is A
#this is B
这里有个问题:虽然python3之后将super(当前类,self).方法名(参数)省略为super().方法名(参数),但并未强制我们将super()的第一个参数写成当前类的标识符。
class A():
def __init__(self):
print('this is A')
class B():
def __init__(self):
print('this is B')
class C(B, A):
def __init__(self):
super().__init__() #等同于super(C,self)
C() #将类C实例化。或者c=C()也是一样的。目的是触发类C的__init__()
#this is B
——类C的MRO列表为“[<class 'main.C'>, <class 'main.B'>, <class 'main.A'>, <class 'object'>]”。
显然,这里super()函数会调用类B的__init__方法。
但是,类C同样是类B和类A的子类。
我们可以将代码改成super(B,self)._init_() 或者super(A,self)._init_(a,b),然后创建类C的实例,因为这会触发类C的__init__方法。让我们看看会发生什么?
class A():
def __init__(self):
print('this is A')
class B():
def __init__(self):
print('this is B')
class C(B, A):
def __init__(self):
super(B,self).__init__()
C()
#this is A
class A():
def __init__(self):
print('this is A')
class B():
def __init__(self):
print('this is B')
class C(B, A):
def __init__(self):
super(A, self).__init__()
C() #
还记得类C的MRO列表吗?
[<class 'main.C'>, <class 'main.B'>, <class 'main.A'>, <class 'object'>]
当我们使用super()._init_()时,实际上是运行了代码“super(C,self)._init_()”,它会调用类B的_init_(),打印出‘this is B’;
当我们使用super(B,self)._init_()时,它会调用类A的_init_(),打印出‘this is A’;
当我们使用super(A,self)._init_()时,它会调用object的_init_(),所以最后什么也没打印出来。
类与实例
以“类名(属性1,属性2,…)”的格式来创建类的实例时,会触发该类的__new__()与__init__()。
创建类A的实例a:“a=A(属性1,属性2,…)”
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
A() #TypeError: __init__() missing 2 required positional arguments: 'a' and 'b'
A(1, 2) #this is A
a=A(1,2) #this is A
注意,以上三个结果是单独执行时得到的。
魔法方法简介
__new__()
new() 方法是在类准备将自身实例化时调用。默认是调用该类的直接父类的__new__()方法来构造该类的实例。如果其父类没有定义__new__()方法,则在其父类的父类中寻找,一直找到object类的__new__()方法为止。
new() 方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
new()至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供。
new()必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例。__init__有一个参数self,就是这个__new__返回的实例。
@staticmethod
def __new__(cls, *args, **kwargs):
print("__new__方法被调用")
return 父类名.__new__(cls)#或者return object.__new__(cls)
__init__()
首先,_new_()将返回的实例传给_init_()的self参数。
__init__定义了类的所有实例共有的一些属性,接收创建实例时传来的数据用以给这些属性(除第一个参数外的所有参数)赋值。
def __init__(self, attr_1, attr_2):
self.attr_1 = attr_1
self.attr_2 = attr_2
__call__()
“实例名()”时调用该方法,无默认定义。
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
a=A(1,2)
a()
#this is A
#TypeError: 'A' object is not callable
输出‘this is A’是因为代码“a=A(1,2)”的存在,它触发了类A的__new__()和__init__();报错是因为‘a()’触发了类A的__call__(),可类A没有定义它。
由这一例子可知,“实例名()”会直接触发__call__(),并不会调用类A的其他方法。
__getattribute_()与__getattr_()
访问属性的值时,_getattribute_()方法将得到调用。若其标识符存在于实例或其类的__dict__中,则return object._ getattribute_(self,item) 或者使用super()._getattribute_(item);若未找到,则报错并调用__getattr__()方法。若__getattr__未被定义,则抛出AttributeError异常。
__setattr__()
“实例名.属性=”时自动调用该方法.默认执行属性赋值操作。
__delattr__()
“del 实例名.属性”时自动调用该方法。默认执行删除属性操作。
__hash__()
hash(实例名)时自动调用该方法,默认返回一个哈希值。
__eq_()、__ge_()、__gt_()、__le_()和__lt__()
_eq_:使用“==”时自动调用,默认返回True或者False。
_ge_:使用“>=”时自动调用,默认返回True或者False。
_le_:使用<=”时自动调用,默认返回True或者False。
_gt_:使用“>”时自动调用,默认返回True或者False。
_lt_:使用<”时自动调用,默认返回True或者False。
__getitem_()、__setitem_()和__delitem__()
_getitem_():访问实例名[key]时调用,定义返回的值。
_setitem_():“实例名[key]=value”时自动调用,定义接下来的操作。
_delitem_():“del 实例名[key]”时自动调用,定义接下来的操作。
__str_()和__repr_()
_str_():改变对象的字符串显示。定义时必须返回一个字符串,否则报错。
_repr_():如果__str__没有被定义,那么就会使用__repr__来代替输出。定义时必须返回一个字符串,否则报错。
__del__()
当对象在内存中被释放时调用该方法。
__get_()、__set_()和__delate__()
_get_():调用一个属性时触发
_set_():为一个属性赋值时触发
_delate_():采用del删除属性时触发
__slot__()
不可被继承。当你定义__slots__后,slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个__dict,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。
类属性和实例属性的定义、区别?
类属性:直接定义在类中的变量。
实例属性:以“self.属性名 = 值”形式定义在__init__方法中的变量。
区别:类属性的值不受实例影响。
class A():
z=100
def __init__(self, a, b):
self.a = a
self.b = b
#z是类属性,a、b是实例属性
类中的方法有哪些?
实例对象能够调用类方法:它拥有__class__属性,在其中存储着类对象的地址。
类对象无法直接调用实例方法:尽管实例方法的定义存储在类对象中,但当类对象访问方法时,它会先寻找@classmethod、@staticmethod。经修饰器修饰过的方法才能被类对象调用,即类对象只能调用类方法和静态方法。
实例方法
⾄少有self(写法不强制,约定俗成)参数。执⾏实例⽅法时,⾃动将调
⽤该⽅法的对象赋值给self。
它可以访问类属性、实例属性。
调用格式:实例名.方法名()或者类名.方法名(实例名)
【类名.方法名(),默认self=类名,无法调用实例方法。然而,在括号中填入实例名相当于指定self=实例名,因此可以调用实例方法。】
类方法
上方有@classmethod装饰器,⾄少有cls(写法不强制,约定俗成)参数。执⾏类⽅法时,⾃动将调⽤该⽅法的类赋值给cls。
它可以访问类属性,但无法访问实例属性。
调用格式:类名.方法名()或者实例名.方法名()
静态方法
上方有@staticmethod装饰器,不具有表示自身的参数。
它无法访问类属性、实例属性。(毕竟它没有表示自身的参数)
相当于一个放在类的作用域里的函数,具有独立性。
调用格式:类名.方法名()或者实例名.方法名()
实例方法如何取得类属性的值?
class A():
z = 100 # 类属性
def foo(self):
print(z)
a = A()
a.foo()
#NameError: name 'z' is not defined
错误提示:变量z尚未定义。有三种更改方案:self.z、A.z、a.z,它们都能正确输出z的值。(self.z和a.z性质一样)
class A():
z = 100 # 类属性
def foo(self):
print(self.z)
a = A()
a.foo() #100
print(A.z) #100
print(a.z) #100
总结:实例方法可以通过“self.类属性名”的形式来访问类属性。另外,在类外我们可以通过“类名/实例名.类属性名”的形式来访问类属性。
如果实例方法没有self参数?
self参数实际上是类定义方法时写的第一个参数,python会自动将调用这一方法的类赋值给这一参数。所以,没有self参数,几乎等同于没有参数。
class A():
z = 100 #类属性
def __init__(self, a, b):
self.a = a
self.b = b
def foo(): #括号内应加上self参数
print(self.a+self.b)
print(self.z)
a = A(4, 6)
a.foo()
#TypeError: foo() takes 0 positional arguments but 1 was given
方法__init__定义了self变量,但是它和方法foo()处于平行的两个代码块中,foo()自己没有定义变量self,却使用了self,这显示是错误的。
但是,示例报错并不是因为我们试图打印和变量self有关的值。
class A():
z = 100
def __init__(self, a, b):
self.a = a
self.b = b
def foo(self):
print('hello') #没有访问任何属性
a = A(4, 6)
a.foo()
#TypeError: foo() takes 0 positional arguments but 1 was given
仍然是同一种错误,大意是‘’foo()没有设置参数接收,我们却给了一个参数‘’。我们并没有传值进去,看样子是python自动传入的。结合上面内容,我们有理由推测,python往参数里传递了类名,但我们没有设置接收参数,(原本第一个参数会接收类名),因此而报错。
总结:实例方法必须要提供一个形参来代表自身。
类与子类
继承的格式:class 类名(父类名)
若不写父类名,则默认父类名为“object”,即对象类。
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
class B(A):
pass
类B会继承类A包括__init__()在内的所有方法,因此类B在实例化时,必须要传入两个参数,因为类A定义了a、b这两个形参。
类B可以定义自己的方法,但当它的方法名和类A相同时,便会覆盖从类A继承来的方法。例如:
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
class B(A):
def __init__(self):
pass
类B重写了从类A继承来__init__(),如今类B实例化可以直接“b=B()”。但是,这也导致B失去了类A的__init__代码。如果B想要设置a、b两个传参入口,就不得不重新写一遍。
举个例子,类A设置了十个传参入口,类B想在它的基础上增加一个传参入口,怎么办呢?
答案便是super()。
class A():
def __init__(self, a, b, c, d, e, f, g, h, i, j):
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
self.f = f
self.g = g
self.h = h
self.i = i
self.j = j
class B(A):
def __init__(self, a, b, c, d, e, f, g, h, i, j, k):
super().__init__(a, b, c, d, e, f, g, h, i, j)
self.k = k
如何改写父类的方法
先覆盖(重新定义),再继承[利用super()函数],最后加上自己的代码。
父类和各子类之间__dict__的区别
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
def run(self):
return self.a+self.b
def say(self):
print('hello')
class B(A):
pass
class C(A):
def __init__(self, a, b,c):
super().__init__()
self.c=c
print('this is C')
class D(A):
def __init__(self):
print('this is D')
class E(A):
def run(self):
super().run()
def say(self):
print('hello')
def hug(self):
print('hug')
查看它们的__dict__属性,结果如下:
print(A.__dict__)
#{'__module__': '__main__',
'__init__': <function A.__init__ at 0x00000296A6D0ED30>,
'run': <function A.run at 0x00000296A6D0EDC0>,
'say': <function A.say at 0x00000296A6D0EE50>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
print(B.__dict__)
#{'__module__': '__main__',
'__doc__': None}
print(C.__dict__)
#{'__module__': '__main__',
'__init__': <function C.__init__ at 0x0000019CCD47EEE0>, '__doc__': None}
print(D.__dict__)
#{'__module__': '__main__',
'__init__': <function D.__init__ at 0x0000017598F8EF70>,
'__doc__': None}
print(E.__dict__)
#{'__module__': '__main__',
'run': <function E.run at 0x0000018031A1B040>,
'say': <function E.say at 0x0000018031A1B0D0>,
'hug': <function E.hug at 0x0000018031A1B160>, '__doc__': None}
可以看出,父类和子类的__dict__中都记录了__module__和__doc__以及它们各自定义过的方法。并且,super()不是引用了父类的方法,而是开辟了新的空间来储存相同的方法。
如何调用父类的属性
父类的属性分两种,一是类属性,一是实例属性。
关于实例属性,在“类与子类”的开头已经举过例子,这里不再复述。
关于类属性,代码如下:
class A():
z = 100 #类属性
def __init__(self, a, b):
self.a = a
self.b = b
def run(self):
return self.a+self.b
def say(self):
print('hello')
class B(A):
def foo(self):
print(self.z) #或者A.z,或者B.z。直接打印z会报错。
B(2,1).foo() #100
我的疑惑:在实例方法内为什么不能直接调用“z”,而是必须加上前缀?按理说,在局部搜索不到变量,系统应该会去外层搜索,而类属性就在实例方法的外层。
以上是在类内调用父类属性的方法,倘若在类外:
class A():
z = 100
def __init__(self, a, b):
self.a = a
self.b = b
def run(self):
return self.a+self.b
def say(self):
print('hello')
class B(A):
pass
a = A(2, 1)
b = B(2, 1)
print(A.z)
print(B.z)
print(a.z)
print(b.z)
#100
#100
#100
#100
至于直接print(z)自然是不行的,因为在外部,z还没被定义。
如何调用父类的实例方法
首先,我们使用“class B(A)”的格式来让类B继承类A。现在,类B已经能够使用父类方法了。
使用格式:子类实例名.父类方法名()
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
def run(self):
return self.a+self.b
def say(self):
print('hello')
class B(A):
pass
b = B(2, 3)
b.say()
#this is A
#hello
输出的第一行是因为'B(2,3)'这个代码触发了类B的__init__(),而类B的__init__()与类A的__init__()一致,所以打印出了‘this is A’
输出的第二行则是调用了B的say()。B的确没定义过say(),但就像它也没定义过__init__()一样,它继承了类A的say()。
因此,要调用父类的方法,首要便是别编写同名方法,以免覆盖了父类的方法;倘若不得不编写同名方法,你也可以通过super()函数将父类的代码继承回来;倘若你既覆盖了父类的方法,又不想使用super()函数,解决方法如下:
class A():
def __init__(self, a, b):
self.a = a
self.b = b
print('this is A')
def run(self):
return self.a+self.b
def say(self):
print('hello')
class B(A):
def say(self):
print('sorry')
A(2, 3).say()
直接创建类A的实例,然后调用……的确没什么意义,或许还有其他方法,这里就不深究了。
补充一点:类方法的使用格式是类名.方法名(),不需要先实例化。
后记
这篇笔记用了好几天时间,主要重心放在了__init__()、super()的使用以及理解类的各种参数的意义上面。语言文字不够简练、准确,小节划分太潦草(不同节内容可能有一些重复的地方)。最苦恼的大概就是markdown的下划线了——它有时没办法直接显示出来,加上斜杠也不尽人意……
最后总结一下遗留的困惑:
实例方法内为什么不能仅用变量名调用类属性?