DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

  • 第一次学drf

  • 第二次学drf

  • 温故知新

    • 学习计划
    • 常见问题及配置
    • 数据表设计经验
    • Django精简总结
    • 源码知识点
      • ☆ 源码py知识点
      • 痛的领悟
      • 常见的魔法方法
        • 构建及初始化
        • 序列操作
        • 属性访问
        • 对象显示
      • @property & 描述符
        • 封装+暴露方法
        • 封装+@property
        • 描述符
      • 类变量
        • 类变量值是函数
        • 计数器
      • 动态导入
        • import_module
        • 反射的应用
      • 偏函数
      • 装饰器
      • 伪装术
      • 延迟加载
      • 抽象类
      • 元类
      • 单例模式
      • 其它的八股文
    • drf通篇概览
    • drf-前篇
    • drf-后篇
  • flask

  • 后端
  • 温故知新
DC
2024-05-11
目录

源码知识点

该篇博文归纳在剖析源码过程中, 遇到的/需掌握的 py知识点.
我想做减法来着, 结果发现总结的过程中一边减也在一边增加(¯﹃¯)

# ☆ 源码py知识点

一定一定要理清楚self到底是谁!!

我们现阶段旨在学习py知识点, 不用特别深究为何这样设计, 等项目经验多了, 再学习设计模式, 回头来看应该会有不一样的体会!

- 一般是 import 模块 ; from xx.xx.模块 import 模块中的成员
  若是 import 包 我们可以在__init__.py文件中将本模块的所有文件都导入,在外部直接导入包就相当于导入了其中定义的所以 文件/模块
- 属性查找规则 self-父类-..-object
  变量查找规则 作用域LEGB依次找 <不同的作用域相同的变量名不会引起冲突>
- 类方法、实例方法 前者自动传值cls,后者自动传值self;实例可调用类方法,自动传cls、类调用实例方法,需手动传self
- 静态方法就是一普通函数
- 闭包 函数嵌套,内部函数引用外部函数的变量,并将内部函数返回
- super(不写就是当前类,谁的继承链self/cls).__init__() super是会自动传值的
  简而言之,super不一定就是父类,而是要看里面的self是谁. super(xxx, self)一定是type(self)对应的mro中, xxx的下一个类.
- 反射
  通过字符串动态获取对象的类型、属性和方法等信息  getattr、setattr 写的是字符串,可设置默认值  
- 形参 **args接收额外的位置参数并以元祖的形式给args;**kwargs接收额外的关键字参数并以字典的形式给kwargs
  形参 位置形参 eg: def func1(a):pass  可以func1(1)或func1(a=1)
- 类实例化时 会自动调用 __init__
- 封装 就是在前面加"__",eg: 类变量__country,__init__里的实例属性self.__name,实例方法__run
  效果: 在类内部这些否可以相互调用,但在外面 类.它们 实例.它们 皆会报错.
  场景: 
    关于封装好的实例属性,比如外部想修改它,可以提供一个接口让使用者间接修改,对修改进行一点限制.
    关于封装好的实例方法,以取款为例,使用者只需知道取款接口即可,取款这个接口里 会依次调用封装好的 插卡、输入密码、输入取款金额等.
- oop的继承,属性查找顺序 实例自身-实例所在的类-父类--直到object
- 多态 抽象类可约束 多个类中名字相同的函数
- 组合 将A实例作为B实例的一个属性 、在B实例所在类的__init__方法中实现
- 对象.属性 或者 getattr(对象,"属性") 时
  "自动"调用__getattribute__,该方法会按照属性查找顺序找,找到就返回,没找到的话会抛出AttributeError的异常
  若未在__getattribute__内主动捕获该错误,后续该异常后续会"自动"触发__getattr__函数的执行!!当然,主动捕获了就不触发了呗.
  注意:"主动"调用__getattribute__,抛出的异常后不会有__getattr__函数兜底.
- __setattr__() 
  在对类实例化对象的属性进行 赋值/修改 实例.属性=值 时,首先会调用该方法
  在该方法中会默认将属性名和属性值添加到实例的 __dict__ 属性中
- raise向上抛出异常
- @property 将方法变成属性,不加括号就能被调用!
  @property装饰user方法 >> 调用时自动执行; 
      "★"细品一个巧妙的设计!相当于把结果存储了起来,第二次调用request.user时,直接取就好! 这种方式的学名: "延迟加载"!!
  @user.setter装饰user方法 >> 赋值时自动执行 -- self.user, self.auth = user_auth_tuple
- 这个关乎源码中不进行认证、不进行权限的情况时,对源码的剖析理解!!
  my_list = []      # -- 1
  for i in my_list: # -- 2
      print(i)
  print("Hello")    # -- 3
- "★" 若很多视图类都需要重写某方法来自定义或逻辑, 那么可以通过继承来简化代码!!
- for-else else子句在for循环正常完成时执行
- ※ 元类
- 实例.__dict__  类.__dict__ 分别得到的是实例和类的属性字典, 里面的值都可通过 self. 的方式进行获取!
- type(obj) 找到obj的直接父类
  isinstance(obj,cls) 检查obj是否是 类cls或类cls的子类 的一个实例, 是, 则返回True;
  issubclass(sub,super) 检查sub类是否是 super类的 子类, 是, 则返回True. 
- is和==的区别, is在判断两个对象的id是否相同
- 代码从上往下执行,遇到class关键字类定义代码,类中的代码在类定义阶段就会执行.



<了解即可>
※ 解释器具有退化功能, 会优先寻找某个方法, 但如果没有, 那么会退化寻找替代方法.   
eg: Python的for循环本质上会调用内部的`__iter__`, 但如果内部没有定义, 那么解释器会退化寻找`__getitem__`..
  
  

def func1(*args, **kwargs):  # 不定长关键字形参接受多余的关键字实参 kwargs是字典-->{'id':2,'age':20}
    return func2(*args, **kwargs)  # **kwargs拆包-->id=2,age=20
def func2(id, **kwargs):
    return id, kwargs
res = func1(id=2, age=20)
print(res)  # (2, {'age': 20})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

# 痛的领悟

内心OS: 下午的时候, 我独自一人坐在客厅沙发上, 发呆似的, 回顾整个drf源码流程, 就突然感觉某某设计很奇妙, 就继承而言, 我一直是大抵知道这个东西的, 但在沙发上回顾时, 似乎对 继承 就有了不一样的理解和顿悟. 飘忽不定的一种感觉吧. 于是乎, 我走进了书房, 想抓它们, 所以有了 "痛的领悟".

- 路由层那, UserInfo.as_view() 本质跟 "装饰器语法糖"是一样的.
- UserInfo继承APIView, APIView里放了好多好多的 功能(方法) - 继承基类的方法,并且做出自己的改变或者扩展(代码重用)
  "你可以想象,UserInfo可以直接把这些方法全部拿过来放到自己类里,就不用再继承APIView了." - 属性查找的规则 
- 二次封装的request 新的旧的都可以用,新的里面没有就用旧的  组合 + __getattr__ 来实现  - 江湖人称作:"伪装术"
- drf 认证组件的入口 代码 `request.user` - 背后的技术 "@property+延迟加载"
1
2
3
4
5

# 常见的魔法方法

# 构建及初始化
魔法方法 作用
__new__ 创建实例化对象, 该方法的第一个参数传递过来的实例一般是 类!
__init__ 为实例定制独有的特征.
__del__ 析构函数, 当一个实例对象被销毁之后会调用该函数. 如果没有销毁, 那么程序结束时也会调用.
class Girl:

    # 创建Girl的实例,So,cls是Girl
    def __new__(cls, *args):
        print("__new__")
        return object.__new__(cls)

    def __init__(self):
        print("__init__")

    def __del__(self):
        print("__del__")


Girl()

"""
__new__
__init__
__del__
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 序列操作

可以让我像操作字典一样, 操作实例对象

class Test:
    def __getitem__(self, item):
        print(item)

    def __setitem__(self, key, value):
        print(key, value)

    def __delitem__(self, key):
        print(key)


t = Test()
t["xx"]  # xx
t["xx"] = 20  # xx 20
del t["xx"]  # xx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 属性访问
魔法方法 作用
__getattribute__ 类的实例对象.属性 时, 会触发它, 按照属性查找顺序找
__getattr__ 类的实例对象.属性 按照属性查找规则都找不到时, 触发它!
__setattr__ 类的实例对象.属性 = 值 进行赋值(增加/修改)时, 触发它!
__delattr__ def obj.attr 时, 触发它
class A:
    def __getattr__(self, item):
        print("__getattr__ >>", item)

    def __setattr__(self, key, value):
        print(key, value)
        """注:
        -- 错误的方式.
           反射setattr的本质是 self.key = value
           所以会递归的调用魔法方法__setattr__
        -- 正确的方式一: 通过操作属性字典!
           self.__dict__[key] = value
        -- 正确的方式二: 在子类派生出来的功能中重用父类功能,下面两个皆可
           1> 类来调用对象的绑定方法,有几个值就传几个值
              A.__setattr__(self, key, value)
           2> super()
              super().__setattr__(key, value)
        """

    def __delattr__(self, item):
        print(item)

    def __getattribute__(self, item):
        print("__getattribute__ >>", item)
        return super().__getattribute__(item)


a = A()
a.name
a.name = "夏色祭"
del a.age

"""
__getattribute__ >> name
__getattr__ >> name
name 夏色祭
age
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 对象显示
__str__  在对象self被打印时,自动触发 在顶级父类object中有__str__方法,返回的是self的内存地址
1

# @property & 描述符

需求: 我想限制用户对属性的操作行为. 比如:年龄不能输入负数.

原先 >> 用户通过 `self.age` `self.age = 10` 可直接进行取值和赋值.
方式一: [封装age属性 -- __age] + [暴露两个方法用于取值和赋值] >> 改变了用户原来的取值和赋值的方式
方式二: [封装age属性 -- __age] + @property  
    - 用户的取值和赋值方式未改变
    - 但 类中有n个这样的属性,就要写2n个方法,类会变得很臃肿!需要进行解耦操作.
方式三: 描述符

你可以再细想下.
方式一不封装,暴露的两方法压根没用,就跟没改变一样;
方式二不封装,你在@property装饰的age方法里 return self.age, 不就无限递归了嘛!也不得劲.
PS: 封装属性只是语法上的变形,只要不是故意找事,就不碍事.
1
2
3
4
5
6
7
8
9
10
11

先来看一个例子:

class People:
    def __init__(self):
        self.age = 3
 

if __name__ == '__main__':
    dc = People()
    print(dc.age)  # 3
    dc.age = 100
    print(dc.age)  # 100
    dc.age = -10
    print(dc.age)  # -10
1
2
3
4
5
6
7
8
9
10
11
12

类 People 的实例 dc 有一个实例属性 age , 表示实例的年龄, 我们可以任意设置其年龄, 即使有些数字根本就不合理.
这就是我们这篇文章要解决的问题, 如何在不改变操作习惯的前提下限制对属性的合理赋值范围?, 例如年龄age不能为负值!

# 封装+暴露方法

既然要限制用户对属性操作的行为, 一般我们都会想到 封装该属性 , 然后暴露两个方法分别用于赋值和取值 给用户.
用户通过这两个方法间接的对封装好的属性进行操作, 我们在暴露的方法中实现对用户操作的限制!

★ 何为封? 属性对外是隐藏的, 对内是开放的.

class People:
    def __init__(self):
        self.__age = 3

    def set_age(self, value):
        if value >= 0:
            self.__age = value
        else:
            print('Age can not be negative value')

    def get_age(self):
        return self.__age


if __name__ == '__main__':
    dc = People()
    print(dc.get_age())  # 3
    dc.set_age(-10)  # Age can not be negative value
    print(dc.get_age())  # 3
    dc.set_age(10)
    print(dc.get_age())  # 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

咦, 看样子我们已经完美解决了这个问题. No,no,no. 远远不够.
第一点: 改变了用户操作习惯 需要格外记住两个方法来 取值和赋值.
第二点: 大家都知道 python的封装是个笑话, 只是 变换了属性名/语法上的变形操作 曲线救国罢了.

print(dc.__dict__)  # {'_People__age': 10}
print(dc._People__age)  # 10
dc._People__age = -20
print(dc._People__age)  # -20
1
2
3
4

根据测试结果, 可以看出, 可以直接对变形的属性名进行操作, 暴露的两个方法压根没有用.

# 封装+@property

通过 @property 装饰器将其中一个方法变成了属性, 方法的名字变成了属性可以被直接访问.
还对一个同样名字的方法用装饰器变成了setter, 当向该属性赋值的时候会调用该方法.

class People:
    def __init__(self):
        self.__age = 3

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if value >= 0:
            self.__age = value
        else:
            print('Age can not be negative value')


if __name__ == '__main__':
    dc = People()
    print(dc.age)  # 3
    dc.age = -10  # Age can not be negative value
    print(dc.age)  # 3
    dc.age = 100
    print(dc.age)  # 100

    print(dc.__dict__)  # {'_People__age': 100}
    print(dc._People__age)  # 100
    dc._People__age = -20
    print(dc._People__age, dc.age)  # -20 -20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

通过打印的结果看, 恢复了原先的属性的调用方式, 而不再是两个难记的方法.
所以, 使用 @property装饰器 后能很好的解决 前面那个方式的 第一点 的弊端. 用户的操作习惯是没有改变的. (*≧ω≦)

尽管用户还是可以通过__dict__查看到真正的属性, 但是只要不是有人故意找事, @property 的方式已经比较优雅啦!

对于我们开发者而言, 通过对两个方法添加装饰器来达到对外地统一接口, 使用起来很舒服.
但假设有10个这种属性, 就得创造20个这种方法, 这么臃肿的一个类想想就很头疼.

如何是好? 必须得把这两种方法单独提出来, 去耦合!

# 描述符

描述符到底是什么? 先来看一个示例简单了解下!

class Descriptor:
    def __get__(self, instance, owner):
        print("__get__", instance, owner)

    def __set__(self, instance, value):
        print("__set__", instance, value)


class Cls:
    age = Descriptor()

    def __init__(self, name, age):
        self.name = name
        self.age = age


if __name__ == '__main__':
    c = Cls("dc", 16)  # __set__ <__main__.Cls object at 0x10d770fa0> 16
    print(c.__dict__)  # {'name': 'dc'}
    c.age  # __get__ <__main__.Cls object at 0x10d770fa0> <class '__main__.Cls'>
    c.age = 10  # __set__ <__main__.Cls object at 0x10d02afa0> 10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

一个类中, 只要出现了__get__ 或者 __set__ 方法, 就被称之为描述符. 上述程序中, Descriptor类就是描述符!
age = Descriptor() age是描述符的实例,也可以说age属性被描述符给代理啦.
具体体现在,c.__dict__ 中没有age,证明执行self.age = age语句时,并没有把值设置到 self 的属性字典里面.
而是执行了描述符的 __set__ 方法,参数 instance 是调用的实例对象, 也就是我们这里的 c.
c.age 根据打印结果可以看到,由于 age 属性被代理了, 那么获取的时候, 会触发描述符的 __get__ 方法.
c.age = 10 同理, 会触发描述符的 __set__ 方法!

至此, 描述符最基本的使用我们就掌握了, 那么 如何实现 年龄输入不能为付数呢? 看下面的程序!

class Descriptor:
    def __init__(self, key):
        self.key = key

    def __get__(self, instance, owner):
        # print("__get__", instance, owner)
        return instance.__dict__[self.key]

    def __set__(self, instance, value):
        # print("__set__", instance, value)
        if value >= 0:
            instance.__dict__[self.key] = value
        else:
            print('Negative value is not allowed')


class Cls:
    age = Descriptor("age")

    def __init__(self, name, age):
        self.name = name
        self.age = age


if __name__ == '__main__':
    c = Cls("dc", 16)
    print(c.__dict__)  # {'name': 'dc', 'age': 16}
    print(c.age)  # 16
    c.age = 10
    print(c.age)  # 10
    c.age = -10  # Negative value is not allowed
    print(c.age)  # 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

描述符更深层次的使用, 请阅读参考文档:
https://www.cnblogs.com/traditional/p/11714356.html
https://blog.csdn.net/Victor2code/article/details/107368696


# 类变量

# 类变量值是函数
# 当时我突然思考一个问题,若每次类实例化都需要调用一个很复杂的函数得到其返回的值,eg: self.xx = foo()
# 那么该foo函数的执行其实可以放到类变量中,执行类定义代码时,该函数是会自动执行的.因为 Foo = type(Foo,object,{xx:func1()})
def func1():
    return 123

class Foo:
    xx = func1()

print(Foo.__dict__.get("xx", "无"))  # 123
1
2
3
4
5
6
7
8
9
# 计数器

需求: 表明 这些实例 进行类实例化 的 先后顺序.
思路: Field类每实例化一次, 其类变量count的值就会加1!! 所有的对象都看得到的且值都是一样的,这得是个类属性.

class Field:
    count = 0

    def __init__(self, read_only=False, write_only=False):
        self.count = Field.count
        Field.count += 1


class IntegerField(Field):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class CharField(Field):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)


if __name__ == '__main__':
    f = Field()
    print(f.count, Field.count)  # 0 1
    f = Field()
    print(f.count, Field.count)  # 1 2


    class TextSerializer:
        f_int = IntegerField()
        f_char = CharField()


    t = TextSerializer()
    print(t.f_int.count, Field.count)  # 2 4
    print(t.f_char.count, Field.count)  # 3 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 动态导入

需求: 动态的导入自己写的插件! -- import_module + 反射 即可解决.
采用动态导入使得程序的可扩展性变得很高, 插件的加减完全由配置文件来控制,不需要对业务代码进行任何修改, 符合开闭原则!!

# import_module
# -- 补充一个知识点,便于看include的源码.
path = "apps.api.urls"
import importlib
md = importlib.import_module(path)  # 等同于 from apps.api imort urls  动态导入模块!!
md.app_name  # 等同于 urls.app_name
1
2
3
4
5
# 反射的应用

image-20240620171458593

在Django中如何使用呢?

-1- 首先在配置文件settings.py中定义如下列表, 分别将两个插件类的地址放在这里, 注意是 用点号来连接路径

PLUGINS = [
    'plugins.plugin1.Calc1',
    'plugins.plugin2.Calc2',
]
1
2
3
4

-2- 然后修改业务代码如下

from importlib import import_module
import settings

if __name__ == '__main__':
    for plugin in settings.PLUGINS:
        module_name, class_name = plugin.rsplit('.', maxsplit=1)
        module = import_module(module_name)
        if hasattr(module, class_name):
            obj = getattr(module, class_name)()
            obj.process()
            
# 注: 值得注意的是, 因为采用了动态导入, 不需要再导入plugins模块了, 也就避免了__init__.py文件书写错误导致的模块导入问题.
1
2
3
4
5
6
7
8
9
10
11
12

# 偏函数

# -- 学会偏函数,便于看源码eg:path和re_path -- 包裹一个函数,并默认传递一个值
from functools import partial

def _xx(a, b):
    return a + b

xx = partial(_xx, b=100)
print(xx(1))  # 101
1
2
3
4
5
6
7
8

# 装饰器

下面知识装饰器的最简单的样子, 高级用法相见 附录:装饰器!★

# -- 回顾下装饰器的原理. @auth语法糖的本质就是 test=auth(test)
def auth(func):
    def inner(*args, **kwargs):
        print('装饰器开始啦!')
        res = func(*args, **kwargs)
        return res
    return inner

@auth
def test():
    print('hello')

def test_1():
    print('hello')

test()
print('-' * 10)
test_1 = auth(test_1)
test_1()

"""
装饰器开始啦!
hello
----------
装饰器开始啦!
hello
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 伪装术

class HttpRequest:
    def __init__(self):
        pass

    def v1(self):
        print("v1")

    def v2(self):
        print("v2")


class Request:
    def __init__(self, req, xx):
        self._request = req
        self.xx = xx

    def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)
        except AttributeError:
            # 抛出异常说的是HttpRequest的request中没有,写了后,变成了Request的request中没有.
            return self.__getattribute__(attr)


request = HttpRequest()
request.v1()
request.v2()

request = Request(request, 111)
# 伪装了一下,看似v1是request的成员,实则是request._request的成员!! crazy.
request.v1()  # request._request.v1()
request.v2()  # request._request.v2()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 延迟加载

延迟加载 - 资源仅在第一次调用时才会载入内存

class Lazy1(object):
    @property
    def first(self):
        if not hasattr(self, '_first'):
            self._load('Hello World')
        return self._first

    @first.setter
    def first(self, v):
        self._first = v

    def _load(self, name):
        self.first = name


lazy1 = Lazy1()
print(lazy1.first)  # Hello World
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Django里的延迟加载

import datetime
from django.utils.functional import cached_property

class Person:
    def __init__(self, birth_year):
        self.birth_year = birth_year

    @cached_property
    def age(self):
        print("Calculating age...")
        current_year = datetime.datetime.now().year
        return current_year - self.birth_year


john = Person(1990)
print(john.age)  # 第一次访问,需要计算年龄,输出 "Calculating age..." 和计算结果
print(john.age)  # 第二次访问,直接返回缓存的结果/return的值,无需再次计算

"""
Calculating age...
33
33
"""
# 要注意一点,我访问`http://127.0.0.1:8000/publish/`,底层多次用到了@cached_property修饰的A方法.A方法只会执行一次.
# 但是,我多次访问`http://127.0.0.1:8000/publish/`这个接口,每次访问都是一个新的请求,都会执行一下@cached_property修饰的方法!
# So,一定要注意,是否是同一个请求!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 抽象类

抽象类中有抽象方法, 该类不能被实例化, 只能被继承, 且子类必须实现抽象方法!!

首先, dict是MutableMapping的"虚拟子类", 所以 isinstance(a,MutableMapping) 结果为True.

>>> from collections.abc import Mapping, MutableMapping
>>> a = dict()
>>> isinstance(a,MutableMapping)
True
>>> isinstance(a,Mapping)
True
MutableMapping 类要求我们说明如何获取、删除和设置项,如何进行迭代以及如何获取字典的长度。但一旦我们完成这些,我们将免费获得 pop 、 clear 、 update 和 setdefault 方法!
1
2
3
4
5
6
7

我们可以写个类继承 抽象类 MutableMapping, 来自定义一个字典! 需求: 想让对象具备 dict -like的功能.

>>> from collections.abc import MutableMapping
>>> class TwoWayDict(MutableMapping):
	pass

>>> d = TwoWayDict()
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    d = TwoWayDict()
TypeError: Can't instantiate abstract class TwoWayDict with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__
  
※ 注:
不同于 dict, TwoWayDictde的实例d
使用 `update`、`setdefault`方法会调用`__setitem__`方法, `pop`、`clear`方法会调用`__delitem__`方法!!
1
2
3
4
5
6
7
8
9
10
11
12
13

报错告诉我们, 继承MutableMapping需要实现__delitem__、__getitem__、__iter__、__len__和__setitem__这些抽象方法!!


# 元类

image-20240617121107033

题干: MyType()得到类A, A()得到实例a
1> 我们熟知 - python处处皆对象! 对象"加括号"调用会自动触发 "创建出该对象的类" 中的__call__方法!
   (一个对象能否被调用,取决于它的类对象中是否定义了__call__)
   因为 MyType = type(MyType, object, {}) , 所以 MyType()自动调用type中的__call__
   因为 A = MyType(A, object, {}) , 所以 A()自动调用MyType中的__call__
2> __call__方法中的代码逻辑决定了 - "☆ 类加括号实例化会依次执行该类中的__new__、__init__" <无需深究>
   eg: A() 依次执行 A中的 __new__'创建A的实例,so,cls是A'、__init__
       MyType() 依次执行 MyType中的 __new__'创建MyType的实例,so,cls是MyType'、__init__
   
・_・; 必知必会:
   - __new__ 中必须将类A的实例对象返回, 才会执行__init__, 并且执行的时候会自动将__new__ 的返回值作为参数传给self.
   - 一个对象是什么, 取决于其类型对象的__new__返回了什么.
   注: 你在MyType的__new__中对attr进行一点改变,影响了A这个实例,但在MyType的__init__中,你会发现attr并未改变.
       因为MyType的__new__和__init__的参数值都来自于type中的__call__!
    
    
在代码截图里
- 执行语句A()时,我们找到创建出A的是MyType,那么A()会自动触发MyType中的__call__方法,并将A自动传递该__call__的第一个参数!
  你别看MyType中的__call__方法的第一个参数是cls,只是参数名变了,它依旧是实例方法!
  此处__call__方法里的代码逻辑,注意哦: ★ 类调用类里的实例方法,需手动传self
- 同理!MyType(A, object, {...})会自动执行MyType里的__new__和__init__
- 提醒: 从上往下运行到class定义的类代码,会立刻开辟一个类的namescope,将类中的变量和方法/函数往namescope中丢.
  ★ (类中的代码/类体代码 在定义阶段就执行啦!!)
注:无需纠结MyType和A中的__new__方法体里的代码为何不一样.<我纠结过,无果,当做固定写法即可Hhh>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 单例模式

方式一

class Singleton:
    _instance = None  # 平替到 元类的__init__里实现

    def __new__(cls, *args, **kwargs):  # 平替到 元类的__call__里实现
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance


# 测试
singleton1 = Singleton(1, 2)
singleton2 = Singleton(1, 3)
print(singleton1 is singleton2)  # 输出: True
1
2
3
4
5
6
7
8
9
10
11
12
13

方式二

# metaclass 用于继承没办法解决的问题的. type创建类;object创建实例
import random


class M(type):
    def __new__(cls, name, bases, attrs):
        """class A 类定义阶段调用 该方法是返回创建的类; 在该方法里可操作attr/对类里的成员进行操作"""
        for key in attrs.keys():
            if key.startswith("_test"):
                raise ValueError(f"{name}类中不能出现以_test开头的方法")
        return type.__new__(cls, name, bases, attrs)  # return super().__new__(cls, name, bases, attrs)

    def __init__(self, name, bases, attrs):
        """class A 类定义阶段调用 此时有创建好的类啦,可以对新建的这个class做一点手脚eg:给新建的类添加几个类变量"""
        type.__init__(self, name, bases, attrs)  # super().__init__(name, bases, attrs)
        self.random_id = random.randint(0, 100)  # 它等价于在A类里直接写这一行代码.
        self._instance = None  # 单例

    def __call__(cls, *args, **kwargs):
        """A()时调用/新建立的这个类在尝试产生实例的时候调用"""
        if cls._instance is None:  # 单例
            cls._instance = type.__call__(cls, *args, **kwargs)  # return super().__call__(*args, **kwargs)
        return cls._instance


class C: pass


class B(C):
    pass


class A(B, metaclass=M):  # A = M(A,object,{k:v})
    # def _test(self):
    #     pass
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def __init__(self, *args, **kwargs):
        self.name = "xxx"


if __name__ == '__main__':
    a1 = A()
    a2 = A()
    print(a1 == a2)

    print(isinstance(a1, type))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

rbac的项目里“用户-角色-权限”,可以用django的中间件做登陆认证和权限的校验.
设置白名单,比如登陆接口,process_request和process_view直接返回None
登陆过程中会查询数据库将该用户的所有能访问的接口,登陆成功,将结果存放到session中.
   比如:{"per_url_name":[...能访问的url的name]}
访问其他接口时,同样要经过中间件,
   - process_request里判断session里有没有"per_url_name"这个key,有,则证明已经登陆成功啦;
   - 路由匹配成功,到process_view时,request中已经有所有路由信息啦!!
     request.resolver_match.url_name取到当前匹配成功的这个路由的name.
     看该路由的name  是否在request.session["per_url_name"]中,有,该用户则有权限访问该接口!!
     ▼ 注:路由匹配不成功,不会执行process_view
1
2
3
4
5
6
7
8
9
10

# 其它的八股文

关于《程序员的自我素养》
尽管我很讨厌八股文, 但还是得复习下, 不求探究的多么深, 但起码在脑海里放着, 知道它是个啥. (´・Д・)」.
多问自己, 它是个啥, 听别人讲 哪怕听懂了,也跟自己想自己理解出来的 是两个概念..

Q:面试官问:你理解的装饰器是咋样的,你如何回答?

先抛出结论 装饰器的本质就是一个callable,可调用对象.

以函数装饰器举例, 语法糖@dec装饰my_func函数, 我们就认为dec就是decorator.
@dec + 被装饰的my_func函数 完全等价于 my_func = dec(my_func)
也就是说 decorator 是一个输入是函数,输出也是函数的一个函数. 
有个细节,输出的函数的参数要和被装饰函数的参数一样,通常我们使用*args,**kwargs指代.
再进行一点扩展, 带参数的装饰器是怎么一回事呢? 
其实就是在多嵌套了一层函数,返回的函数是 前面提到的无参装饰器.
eg: 计算程序运行n次的时间 my_func = @dec(1000)(my_func)
1
2
3
4
5
6
7
8
9

Q:面试官问:你理解的元类是咋样的,你如何回答?

类实例化创建对象,会调用类的new和init方法; new申请了一片内存用于创建对象,init用于为对象添加属于自己的属性.
同理,元类实例化创建类,会调用元类的new和init方法; new用于创建类,init用于为类添加类变量. (处处皆对象嘛
那么我们就可以重载元类中的new方法,在元类创建类的过程中动一点手脚.
eg:比如不允许类中出现 test开头的方法名. 
原理 >> A=type(A,object,{}) -- MetaClass=M -- A=M(A,object,{}) -- new里取attr值进行判断

   - __new__ 中必须将类A的实例对象返回, 才会执行__init__, 并且执行的时候会自动将__new__ 的返回值作为参数传给self.
   - 一个对象是什么, 取决于其类型对象的__new__返回了什么.
   - __new__里面的参数一定要和__init__是匹配的, 除了第一个参数之外


类实例化创建的对象,若想加括号进行调用,则在类中得有__call__方法
同理,元类创建的类,若想加括号进行调用,则需要在元类里有__call__方法,该方法使得类实例化创建对象时,先new后init!

类里的new、init、call方法第一个参数分别是 当前类、当前类的实例、当前类的实例

- 注: 一个类在没有指定的metaclass的时候,如果它的父类指定了,那么这个类的metaclass等于父类的metaclass!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Q: 面试官问: 简单阐述下计算机的三层架构. 你如何回答?

冯诺依曼模型定义了计算机由什么构成(硬件)= 运算器 + 控制器 + 存储器 + 输入设备 + 输出设备
★ "运算器和控制器都在CPU里,前者负责运算(数学运算和逻辑运算)、后者计算机的指挥系统,负责控制其他硬件的运行."

计算机的三层架构: (用户)- 应用程序 - 操作系统 - 计算机硬件 为何如此设计呢? -- 总结:简化应用程序对计算机硬件的操作!
硬件CPU是其它所有硬件的老大,CPU内部集成的二进制指令集用于操作其它计算机硬件.
但CPU不会自发的控制其他硬件的运行.因为cpu本身也是硬件, 硬件都会受软件的支配 
- OS这个软件分为内核(内核态)和系统接口(用户态)
  "内核里是CPU内部集成的数不清的二进制指令集";系统接口将其进行封装 (做个比喻:OS内核功能十几万个,系统接口封装完可能就几万个
- 上层应用程序(eg:shell解释器),将OS系统接口进一步封装.(比如:shell解释器的命令 mkdir
★ 综上,说白了,用软件的某一个功能:
  就是执行了某个程序(一堆代码eg:python代码open/shell命令eg:touch),进而去调用OS对应的系统接口,完成对计算机硬件的操作. 
  > 所以说, <应用程序调用硬件> 是需要经过操作系统的. 所有的应用程序都是运行在操作系统之上的.

我们通常听到的32/64位,是指cpu一个指令能处理多大的数据,即 每次/一次性 能从内存里读取的32/64个二进制数
我们规定32/64位会组成一个完整的指令,CPU有成千上万数不清的指令集 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Q: 面试官问:python解释器是如何运行一个py文件的 / python程序执行过程, 你如何回答?

将需用到的py源代码/需执行的py文件 --(进行编译)-- PyCodeObject对象,里面包含字节码 --(通过python的PVM虚拟机逐行解释字节码)

Python解释器 = Python编译器 + Python虚拟机
-1- 某段代码需要运行,先将该段代码加载进内存.
-2- Python编译器负责将Python源代码编译成字节码(包括文件读取、分词、建立AST、编译成字节码);
-3- Python虚拟机负责执行这些字节码,完成相应的功能.

何为字节码? 使用dis模块可以看到一段程序的字节码,是以 一堆 人类能看懂的英文单词 呈现的.
注:字节码可以跨平台,因为不同的平台都有自己适配的Python解释器.

(・_・; 再说细一点:
编译的结果"字节码"会放到内存的PyCodeObject中,若需持久化保存,会写入pyc文件
PyCodeObject对象中有一个成员co_code,其指向的就是该段程序的字节码.
什么时候需要pyc?
    import的时候,会将import的模块编译成.pyc文件,便于下次运行加快效率
    .pyc文件的过期:会跟import的模块最后的修改日期进行对比,若py文件/模块修改了,.pyc文件就会重新生成

注:语言的演变
机器码(二进制0101的高低电平) - 汇编语言(将一串串二进制数对应成英文单词) - 高级语言(通常会有编译的过程)
 
--- --- ---

※ 当时我在想,Python虚拟机本质就是一个软件,它负责执行这些字节码,那么py虚拟机执行字节码的过程如何与计算机的三层架构联系起来呢?
  哇,当时我就懵了,努力回顾操作系统的五大功能,进程管理、存储管理、设备管理、文件管理、作业管理.
  还是没明白,都怪自己上大学时没好好听讲,书到用时方恨少,暂且略过吧!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Q: 面试官问: frame、CodeObject、bytecode是啥, 你如何回答?

当我们在python中所谓定义一个函数的时候,我们只是新建了一个变量,然后这个变量里面保存了一个函数对象,也就是 `function object`!
每一个function object也都会有它对应的一个CodeObject..

所有的python代码在编译器都会被编译成CodeObject.

code object里有啥? 有该段程序的bytecode(字节码)、有位置参数关键字参数的数量、自由变量、局部变量等信息.

- 当你 [每一次调用函数] 或者 [刚开始运行python] 的时候,都会建立一个新的frame
- 在这个frame的环境下,你会一条条的运行bytecode,每一条bytecode在c语言里都有相应的代码去执行它!
- 在每一个frame里,python会维护一个stack(栈)
  bytecode跟这个栈进行交互,当然也会跟codeobject里保存的那些乱七八糟的东西进行交互.
  接着就是进行计算,拿到结果,返回,继续.
这一套类似于汇编,只不过汇编可以直接运行在硬件上,而python的bytecode需要运行在c语言上!
1
2
3
4
5
6
7
8
9
10
11
12
13

Django精简总结
drf通篇概览

← Django精简总结 drf通篇概览→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式