跳转至

复习

只有我看得懂的复习笔记.Hhh

类的定义

定义类 类似于 `import 模块名`
导入模块会创建一个namescope. 通过 模块名.属性名 从namescope中取属性/变量.
同理! 从上往下运行到class定义的类代码,会开辟一个类的namescope,将类中的变量和方法/函数往namescope中丢.
(类中的代码/类体代码 在定义阶段就执行啦!!)
★ 记住: 执行py文件、执行导入模块、类定义、类的实例化/类的调用、函数调用的代码时,会开辟namescope.
       特别注意,类中函数体的代码要在函数被调用时才会执行!
        ---------
       | 数据属性 |
类名 -->|       | 函数属性 |
        ---------  类名指向类的namescope
查看类的namescope: 类名.__dict__
类名.属性名 等同于 类名.__dict__["属性名"]
★ So,对namescope的CURD的本质就是在操作字典!! 
(类的实例是一样的! 查看实例的namescope: 实例名.__dict__)

类的调用/类实例化对象

`类名([__init__的形参])`
step 1> 造实例 
          -- 调用类创建一个空对象/实例,会为该实例开辟一个新的namescope
step 2> 初始化实例 
          -- 创建完实例后,该实例会自动调用类中的__init__功能的执行,并将自己作为第一个参数self的实参传入
强调:
   __init__是用来初始化实例的(为实例定制独有属性的),不是造实例的
   __init__的函数体中可以有任意代码,但唯独不能return非None的返回值
★ 类的namescope是实例们共享的!!
  实例的namescope中只有自个儿独有的属性,只有自己看得见
★ 类的实例化对象是数据与方法高度整合的产物!

属性的查找/属性引用

 属性引用规则:
  先从对象自己的名称空间找, 没有则去类(-父类-直到顶级父类Object类)中找
  若都没有, 则报错此对象没有该属性 -- 不会去全局空间找!
 类中变量引用的规则:
  1> 未绑定的局部变量将在全局命名空间中查找
  2> 在类代码块中定义的名称的作用域/namescope作用范围,不会扩展到方法的代码块中
     包括列表推导式和生成器表达式 
     即作用域嵌套规则适用于LEGB,不适用于类与类中的函数.
     Ps:可以通过 obj.属性 -- 即属性引用的方式来进行访问

f1 = 0
a = 1

def m():
    print(234)
class Foo:
    b = 2
    print(f1)   # 0
    print(Foo)  # -- NameError: name 'Foo' is not defined
    def f1(self):
        print(b)
    def f2(self):
        m()     # -- 局部中没有m变量,会在全局中找
        self.m()
    @staticmethod  # -- 静态方法,意味着不会自动绑定类实例化对象
    def m():
        print(123)

print(Foo.a)    # -- AttributeError: type object 'Foo' has no attribute 'a'
print(Foo().a)  # -- AttributeError: 'Foo' object has no attribute 'a'
Foo().f1()      # -- NameError: name 'b' is not defined
Foo().f2()      # 234
                # 123

 这样记!! Foo.a  .前面属于<变量引用>,NameError;.的后面属于<属性引用>,AttributeError!!

绑定方法与非绑定方法

self   @classmethod与cls   @staticmethod

★ 虽然不严谨,但我们可以大胆的说: 
1> 实例方法 -- 实例对象调用  
   类中不加装饰器的函数<默认>是绑定给实例化对象使用的 该函数必须要有个形参,约定是self
   实例调用时会将实例自己作为函数第一个形参self的实参"自动传入"
2> 类方法 -- 类对象调用  
   类中被装饰器@classmethod装饰的函数<约定>是绑定给类对象使用的 该函数必须要有个形参,约定是cls
   类调用时会将类自己作为函数第一个形参cls的实参"自动传入"
3> 静态方法 -- 都可调用
   类中被装饰器@staticmethod装饰的函数,<意味>着类对象和实例都可以调用
   但此函数就是一个普通函数!!调用时该函数,该函数有多少形参就对应传多少实参

▲ 何为函数何为方法?
函数:有几个值就传几个值.
方法:会自动传值,绑定方法、静态方法(严格意义上来将静态方法是函数,我们这样称呼罢了!)

★ 注意:
类调用实例方法,就是在调用普通的函数
实例对象调用类方法,第一个参数传入的仍然是类对象

★ 打印不同对象访问类中方法属性(实例方法、类方法、静态方法)的结果:
bound method -- 实例和类访问类方法、实例访问实例方法 -- 属性引用"引用的是PyMethodObject<方法对象>"
function -- 实例和类访问静态方法、类访问实例方法 -- 属性引用"引用的是PyFunctionObject<函数对象>"

★ 即!
函数定义: PyCodeObject + def == PyFunctionObject"函数对象"
类定义:
"self"特征的PyFunctionObject 将与类实例化对象进行绑定 进化为 PyMethodObject"方法对象"
"@classmethod"特征的PyFunctionObject 将与类对象进行绑定 进化为 PyMethodObject"方法对象"
PyMethodObject和PyFunctionObject在调用时进化成PyFrameObject,开辟一块命名空间,执行函数体代码.

Ps: 类方法的一个应用 -- 可以让类调用类方法在类方法体里完成对类的实例化!!

类与类型的统一

★ 在python3中统一了类与类型的概念,类就是类型 即自定义类就是自定义数据类型

打印实例 -- <__main__.类名 object at 内存地址>
打印类、type(实例) -- <class '__main__.类名'>

Ps: isinstance(obj,cls) 检查obj是否是类 cls 的一个实例
    issubclass(sub, super) 检查sub类是否是 super 类的派生类/子类

当调用a.f1(*args)的时候其实是调用了A.f1(a, *args)
d = {'x': 1}  # -- 本质上是 d = dict({'x':1})  
my_list = [1, 2, 3]  # -- 本质上是 my_list = list([1, 2, 3])  my_list是类dict的一个实例
                     #    列表的append、extend等是my_list对象的实例方法!!
print(isinstance(d, dict)) # -- 不建议用`type(d) is dict`来判断

魔法方法

魔法方法在类中, 某种特定条件下就会触发它的执行!

__init__ -- 调用类产生空的实例对象后会自动触发该方法完成对实例对象的初始化
__new__  -- 是创建这个空实例对象的方法
__str__  -- 当self"即实例对象"被打印时,自动触发
__del__  -- 析构方法.在对象被del删除时自动触发  可以用于手动回收系统资源
__call__ -- 当对象被调用时会自动触发该方法! obj()

继承

类名.__bases__  -- 列出当前类继承的所有父类.

 组合 -- 详看 对应小节的举例.

菱形继承 -- 新式类>>广度优先

在子类派生出来的功能中重用父类功能的方式有两种:
1> 指名道姓访问某一个类的函数: 该方式与继承无关 -- 类名.函数名()
     eg: 前面的代码 OldboyPeople.__init__(self, name, age, sex)
 2> super() -- 参数一不写就是当前类,参数二指self所在类的mro继承链  

类名.mro() 等同于 类名.__mro__()

多态

列表元组字符串 计算长度的方法都是 len

封装

在属性名前面加双下划线__  在类的内部可以直接访问,而在类的外部无法直接访问
因为在类定义阶段发生了一次变形 _类名__属性名 变形后的名字丢进了namescope
应用: 如果不想让子类的方法覆盖父类的, 可以将该方法名前加一个__开头.

1>隐藏数据属性: 
  让类外部的使用者通过接口来间接地操作隐藏的属性,在接口之上附加任意的逻辑,从而严格控制使用者对属性的操作!
2> 隐藏函数属性:
  在接口内去调用隐藏的功能,隔离了复杂度,用户只需要调用这个接口就可以实现一系列步骤.

对隐藏的数据属性,需要写接口来间接CURD. 但我们不想改变未隐藏时的对数据属性的操作习惯.用property装饰器!
property 该装饰器用于将被装饰的方法 伪装成一个数据属性
@property -- 
@name.setter -- 
@name.deleter -- 

★反射

通过字符串来操作类或者对象的属性

1> hasattr(p, "xxx") -- 返回True或者False
   不仅局限于判断 "xxx" in p.__dict__ ,是在判断能否通过属性查找规则对该属性进行引用
2> getattr(p, "xxx", "设置没找到的返回值,通常设置为None.")  # 不设置默认返回值,没找的话会报错.
3> setattr(p, "age", 18) 等同于 p.age = 18
4> delattr(p, "name") 等同于 del p.name

应用场景: 比如input输入的是字符串,输入字符串反射到对象具体的方法上面

元类

★ 详细的分析过程 直接看 元类.md 中的属性查找和模版分析 两部分内容!

class Mymeta(type):
    # -- 控制自定义类的创建过程 People = Mymeta('People',..,..)
    def __init__(self, class_name, class_bases, class_dic):  # -- 该self是People
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

    # -- 控制自定义类的调用 p = People('dc',18) 
    def __call__(self, *args, **kwargs):  # -- 该self是People
        obj = self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj

"""
# -- 自定义类的创建过程
class关键字对三要素进行了封装. 
People = Mymate('People',(object,),{...}) 即People是Mymate(...,...,...)实例化出来的
Mymate元类的调用会触发type中__call__方法的执行.
里面有行代码 `self.__init__(obj, *args, **kwargs)` 即Mymate.__init__(People,*args,**kwargs)
"""
class People(object, metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

元类中的三个魔法方法.      
__new__ -- self指元类 该方法不是绑定方法
__init__  __call__ -- self指类 
对class_dic的操作在__new__中进行 __init__不会对class_dic进行有所操作
创建某个实例作为类的一个属性(即实现单例)在__init__中进行 即为类创建独有的属性
__call__ 是创建实例的 会经历 创建空对象、空对象初始化两个步骤

调用类产生实例 需要执行类中的__new__方法产生一个空实例对象,再执行类中的__init__方法实现对该实例对象初始化,也就是为该实例对象定制独有的属性; 类比过来, 调用元类创建类,元类中的__new__方法已经实现了对类的创建, __init__是为了给创建的类添加属性(可以用于实现单例)
注意,new里必须返回创建的实例,不然new返回啥,实例就变成了啥

metaclass对类操作需要在元类new里操作