@


前言

通过这篇笔记,来达成两个目的:
一、帮助自己巩固一下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的下划线了——它有时没办法直接显示出来,加上斜杠也不尽人意……

最后总结一下遗留的困惑:
实例方法内为什么不能仅用变量名调用类属性?