零基础学习Python(三)

1. 多重继承

一个子类可以继承多个父类,这与一些编程语言的规则不通。

如果多个父类中有同名的变量和方法,子类访问的顺序是按照继承时小括号里书写的顺序进行访问的。

可以用issubclass(B, A)方法判断B是否为A的子类。

2. 绑定

类中的方法通过参数self与对象绑定,通过参数cls与类绑定,如果不用self或者cls来访问类中的变量或者方法,会报错找不到变量或者方法,或者达不到预期效果。

class C:
    x = 100
    def set_x(self, v):
        x = v

c = C()
c.set_x(520)

上述代码既不会改变对象c的属性x,也不会改变类C的属性x,set_x方法只是在函数内部建立了一个临时变量。

3. 重写与钻石继承

先定义父类:

class C:
    def __init__(self, x, y):
        self.x = x
        self.y =y

    def add():
        return self.x + self.y

    def mul():
        return self.x * self.y

定义子类,并且重写父类的方法,同时调用父类的方法:

class D(C):
    def __init__(self, x, y, z):
        C.__init__(self, x, y)
        self.z = z

    def add():
        return C.add(self) + self.z

    def mul():
        return C.mul(self) * self.z

如果是钻石继承,这种直接通过类名调用类里面方法的方式可能会出现问题。什么是钻石继承?就是有两个类同时继承了同一个父类,然后另外一个类继承了这两个类,继承关系拓扑图类似于一个钻石(菱形):

钻石继承的问题如下:

可以发现,类A初始化了两次!使用super()函数可以解决上述问题,类B1、类B2、类C的代码修改如下:

class B1(A):
    def __init__(self):
        super().__init__()
        print("哈喽,我是B1~")

class B2(A):
    def __init__(self):
        super().__init__()
        print("哈喽,我是B2~")

class C(B1, B2):
    def __init__(self):
        super().__init__()
        print("哈喽,我是C~")

使用super()方法调用__init__方法,不用传递self参数,因为super方法会自动解决。super函数的原理依赖于Python的MRO(Method Resolution Order:方法解析顺序),这可以通过类的方法mro()或者变量__mro__查看:

关于MRO顺序的一个案例:

class Displayer:
    def display(self, msg):
        print(msg)

class LoggerMixin:
    def log(self, msg, filename):
        with open(filename, 'a') as f:
            f.write(msg)

    def display(self, msg):
        super().display(msg)
        self.log(msg)

class MySubClass(LoggerMixin, Displayer):
    def log(self, msg):
        super().log(msg, filename="subclasslog.txt")

subcls = MySubClass()
subcls.display("This is a test.")
        

运行结果是打印This is a test,并且生成subclasslog.txt,里面内容是This is a test。查看MySubClass的MRO顺序:

 

那么调用subcls的display方法,先去LoggerMixin里面找display方法,找到了,但是第一句是super().display(msg),此时不要以为会去LoggerMixin类的父类object里面找display方法,而是按照MRO顺序去Displayer里面找display方法,从而打印出This is a test,然后执行LoggerMixin力的display方法的第二句:self.log(msg),即调用MySubClass里面的log方法,发现没有,按照MRO顺序去LoggerMixin类里找log方法,找到了,即写入文件内容为msg。

4. __slots__属性

对象的属性除了直接obj.x = valueX这种方式设置外,还可以通过其字典__dict__的方式设置,比如:

c.__dict__['z'] = 666

 

Python中对象是可以随意添加任何属性的,这些属性存放在字典中,虽然字典访问效率高,但是浪费了很多空间,为了避免空间浪费,Python引入__slots__属性,通过__slots__属性设置一个类的对象只能拥有固定属性:

class C:
    __slots__ = ["x", "y"]
    def __init__(self, x)
        self.x = x

 

除了动态添加属性不行,在类的方法(包括构造方法)内添加属性也不行:

父类中的slots属性是不会在子类中生效的(但是子类仍然继承了父类的slots属性,只是不起作用了):

5. 魔法方法

__new__方法是在__init__方法之前调用的,常见的self对象就是它返回的。创建一个类,使得传给他的字符串始终为大写:

class CapStr(str):
    def __new__(cls, string):
        string = string.upper()
        return super().__new__(cls, string)

 

因为CapStr继承自str,所以str有的方法它也有:

对象被销毁时会调用__del__方法:

class C:
    def __init__(self):
        print("我来了~")
    
    def __del__(self):
        print("我走了~")

 

注意,不是使用了del语句就会调用__del__这个魔法方法,而是对象被销毁前才会调用,也就是说调用del语句并不会立即销毁对象,只是销毁对象的引用,只有当对象的引用都被销毁时,才会去销毁对象。比如将c再赋值给d,调用del c不会触发__del__魔法方法,接着调用del d才会触发。 

既然__del__方法可以在对象被销毁前动手动脚,那么可以通过将即将要销毁的对象赋值给全局变量的方式,实现对象的重生。但是使用全局变量可能会污染命名空间,那么可以通过闭包的形式将对象传递给函数的参数,从而实现永久保存:

class E:
    def __init__(self, name, func):
        self.name = name
        self.func = func
    
    def __del__(self):
        self.func(self)

def outer():
    x = 0
    def inner(y=None):
        nonlocal x
        if y:
            x = y
        else
            return x
    return inner

 

重写add方法可以实现自定义的加法功能,比如字符串的相加不再是拼接,而是字符长度的相加:

 

注意,加号调用的是左边对象的__add__方法,s1 + s2相当于s1.__add__(s2)。

__radd__方法的调用原则:如果加号两边的数据类型不同,并且左侧对象没有定义__add__方法,或者__add__方法实现为NotImplemented,那么Python就会去右侧对象找__radd__方法。

__iadd__方法不仅进行加法运算,还会将运算后的结果赋值给左侧对象,调用时机是使用了运算符+=:

__index__魔法方法是当对象作为索引值才会去调用,而不是对象的索引访问触发:

class C:
    def __index__(self):
        print("被拦截了~")
        return 3

关于属性的方法:hasattr、getattr:

class C:
    def __init(self, name, age):
        self.name = name
        self.__age = age

c = C("小甲鱼", 18)
hasattr(c, "name") # 返回True
getattr(c, "name") # 返回小甲鱼

对于私有属性,依然可以访问:

getattr(c, "_c__age") # 返回18

设置属性:

setattr(c, "_c__age", 19) # 返回18

删除属性:

delattr(c, "_c__age")

魔法方法__getattribute__会拦截getattr方法: 

class C:
    def __init(self, name, age):
        self.name = name
        self.__age = age
    
    def __getattribute__(self, attrname):
        print("拿来吧你~")
        return super().getattribute__(attrname)

c = C("小甲鱼", 18)
getattr(c, "name")

 

魔法方法__getattr__只有尝试去获取不存在的属性时才会去触发:

class C:
    def __init(self, name, age):
        self.name = name
        self.__age = age
    
    def __getattribute__(self, attrname):
        print("拿来吧你~")
        return super().getattribute__(attrname)

    def __getattr__(self, attrname):
        if attrname == 'FishC':
            print("I love FishC")
        else
            raise AttributeError(attrname)

c = C("小甲鱼", 18)

 

给属性赋值对应的魔法方法是__setattr__,但是这个方法里面的实现不能是self.attrname=value,因为这个语句又会调用魔法方法__setattr__,所以会死循环,正确做法是:

class D:
    def __setattr(self, attrname, value):
        self.__dict__[attrname] = value

同理,del语句删除属性时,也不能在魔法方法__del__中直接使用del self.attrname,否则也会无限循环,正确做法依然是操作self.__dict__这个字典:

class D:
    def __setattr(self, attrname, value):
        self.__dict__[attrname] = value

    def __delattr(self, attrname):
        self.__dict__[attrname]

​​​​​​​ 

相关推荐

  1. Python基础快速入门学习笔记

    2024-07-18 18:38:04       24 阅读
  2. 一个月学会Python基础入门数据分析

    2024-07-18 18:38:04       45 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 18:38:04       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 18:38:04       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 18:38:04       58 阅读
  4. Python语言-面向对象

    2024-07-18 18:38:04       69 阅读

热门阅读

  1. 数据库:SQL 函数有哪些?

    2024-07-18 18:38:04       25 阅读
  2. C++中const修饰指针的范围

    2024-07-18 18:38:04       19 阅读
  3. X86架构和ARM架构的区别

    2024-07-18 18:38:04       19 阅读
  4. C# 使用模式匹配的好处,因为好用所以推荐~

    2024-07-18 18:38:04       25 阅读
  5. 大语言模型系列:Transformer

    2024-07-18 18:38:04       20 阅读
  6. SpringBoot日常:常用数据类型比较

    2024-07-18 18:38:04       21 阅读
  7. 如何查看Linux中某个项目是否在Docker中运行

    2024-07-18 18:38:04       18 阅读
  8. 如何发掘孩子的兴趣特长

    2024-07-18 18:38:04       18 阅读
  9. Oracle数据泵和RMAN异机备份还原速度对比

    2024-07-18 18:38:04       21 阅读
  10. 2024年对网络安全专业的观点解析

    2024-07-18 18:38:04       19 阅读