Infomation Security

Hash/摘要方法

文件校验

MD4

  • 附加填充bits:在末尾对消息填充,使得消息$M$长度满足 $len(M) mod 512 = 448$

    • 填充最高位位为1、其余为0
  • 分块:将填充后消息512bits分块为$M_1,M_2,\cdots,M_K$

  • 初始化MD4缓存值

    • MD4使用128bits缓存存储哈希函数中间、最终摘要
    • 将其视为4个32bits寄存器初始化为
      • A = 0x67452301
      • B = 0xefcbab89
      • C = 0x98badcfe
      • D = 0x10325476
  • 使用压缩函数迭代计算K个消息分块、上次计算结果

    • $H_{i+1} = C(H_i, M_i)$
    • 最终$H_K$即为MD4摘要值

MD5

SHA

数字签名

鉴权协议

综述

Custom Classes

用户定义类:通过类定义创建

  • 每个类通过字典对象__dict__实现独立的命名空间

    • 类属性引用被转化为在此字典中查找
    • 其中未发现属性名时,继续在基类中查找
      • 基类查找使用C3方法解析顺序,即MRO列表
    • 也存在一些钩子对象允许其他定位属性的方式
  • 当类属性引用yield类方法对象时,其将转化为__self__ 属性为当前类对象的实例方法对象

  • 当类属性引用yield静态方法对象时,其将转换为静态方法 对象所封装的对象

  • 类属性复制会更新类字典,不会更新基类字典

  • 类对象可被调用产生类实例

特殊属性

  • __bases__:包含基类的元组,依在基类列表中出现的顺序

Class Instances

类实例:通过调用类对象创建

  • 每个类实例都有一个通过字典对象__dict__实现的独立命名 空间

    • 属性引用首先在此字典中查找
    • 其中未发现属性名时,继续在对应类属性中查找

      • 用户定义函数对象:其会被转化为实例方法对象
        • __self__属性即为该实例
      • 静态方法、类方法对象:同样会被转化
      • 描述器属性有特殊处理,实际存放在类__dict__中 对象不同
    • 若未找到类属性,对象对应类具有__getattr__()方法, 将调用该方法
  • 属性赋值、删除会更新实例字典,不会更新对应类字典

    • 若类具有__setattr____delattr__方法,将调用方法 而不是直接更更新对应实例字典

特殊属性

  • __class__:实例对应类

Classes

类:类对象通常作为“工厂”创建自身实例

  • __doc__:类的文档字符串
    • 类定义第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承

Class Instances

类实例:在所属类中定义__call__()方法即成为可调用对象

属性

属性访问.

  • A.attr被解释为type(A)中__getattribute__(A, attr)

    • .的行为由python解释器定义
    • type(A)中__getattribute__用于强调不会再从 type(type(A))继续获取调用__getattibute__
  • 定义在类命名空间中函数是为实例定义

    • 要为类定义方法应该自定义元类
  • 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Meta(type):
    def __getattribute__(self, attr):
    print("--Meta--", attr)
    return super().attr

    class D(metaclass=Meta):
    def __getattribute__(self, attr):
    print("--Class--", attr)
    return super().attr
  • __getattribute__函数说明参见 cs_python/py3ref/special_methods

属性访问控制

  • python没有没有属性访问控制,不依赖语言特性封装数据,而是 遵循一定属性、方法命名规约达到效果
  • __开头属性:属性名称会被修改

    • 防止被派生类继承,此类属性无法通过继承覆盖
    • 即若清楚代码会涉及子类,且应该在子类中隐藏起来,考虑 使用双下划线开头
    • 通常是在属性名称前添加类名标记_cls
    • 但同时以__结尾属性名称不会被修改
  • _开头属性:应被视为私有属性,不应被外部访问

    • python无法真正防止访问内部名称,但是这样会导致脆弱的 代码
    • 此约定同样适用于模块名、模块级别函数

      • 默认情况下,通配符*不会导入模块私有属性,除非 在配置有__all__属性
      • 导入参见cs_python/py3ref/simple_stmt#todo
  • _结尾:避免定义的变量和保留关键字冲突

特殊属性

  • __dict__:命名空间包含的属性
  • __doc__:文档字符串
    • 第一条语句、且须为字符串字面值
    • 没有则为None
    • 不会被子类继承
  • __name__:名称
  • __qualname__qualified name,完整限定名称
    • 以点号分隔的名称
    • 显示模块全局作用域到模块中某个定义类、函数、方法的 路径
  • __module__:所属模块名称
    • 没有则为None

描述器属性

  • 描述器协议参见cs_python/py3ref/special_methods
  • 实例/类/静态方法:参见cs_python/py3ref/dm_gfuncs

@property

@property装饰器:为类的属性增加处理逻辑,如:类型检查、 合法性验证

  • property属性和普通属性实现迥异,但使用类似

    • property属性就是绑定有这些处理逻辑函数的类实例
    • 访问、赋值、解除绑定时会自动触发gettersetterdeleter处理逻辑
  • property属性(或者说有效描述器)为类属性

    • 一般需要通过在实例、或描述器命名空间 instance.__dict__中存储数据,以实现对实例操作逻辑 独立
    • 也可以实时计算属性值,此时无需为实例分别存储数据
    • 初始化时,不应该直接设置底层数据属性,会绕过setter 的参数检查
  • 过度使用@property时会降低代码可读性、效率,使用 get/set方法可能有更好的兼容性

代码实现

  • 代码是C实现,这里是python模拟,和help结果不同
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
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
# 返回描述器,可省略

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
# 返回更新`fset`的描述器,同名所以覆盖前者

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
  • @property是描述器类,接受方法返回同名资料描述器

创建property属性

  • @property[.getter]装饰getter-like方法得到同名资料 描述器

  • 返回描述器包含.setter().deleter()方法/装饰器进一步 完善描述器

    • @method.setter:为描述器完善赋值处理逻辑
    • @method.deleter:为描述器完善del处理逻辑
  • 可以直接使用已有类中函数创建property类实例,得到 property属性(描述器)

  • 派生类中property属性覆盖

    • 派生类中直接使用@property创建同名属性会覆盖基类 中property属性

      • 只有显式声明的处理逻辑被设置
      • 基类中逻辑位于基类相应同名property属性,不会 被“隐式继承”
    • @<basecls>.<method>.getter/setter/deleter单独覆盖 property属性方法

      • 但是basecls硬编码方式,必须知道定义 property属性的具体类(或其子类)
  • 描述器协议、实现参见cs_python/py3ref/special_methods

示例

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
class Student(object):

def __init__(self, value):
self.birth = value
# 使用`self.birth`而不是`self._birth`,保证即使
# 实在初始化时仍然进行参数检查

@property
# 将一个getter-like方法变为属性
# `@property`同时会创建装饰器`@method.setter`
def birth(self):
return self._birth

@birth.setter
# `@property`对应,将setter-like方法变为属性
def birth(self, value):
if not instance(value, int):
raise ValueError("birth must be an integer")
if value < 1900 or value > 2020:
raise ValueError("birth must between 1900 ~ 2020")
self._birth = value

@birth.deleter
# 同`@property`对应,在`del`时调用
def birth(self):
del(self._age)
del(self._birth)

@property
# 只设置`@property`而没有设置对应`@birth.setter`
# 这样`birth`就成了只读属性
def age(self):
return 2018 - self._birth

def get_first_name(self):
return self._first_name

def set_first_name(self):
if not instance(value, str):
raise TypeError("expected a string")
self._first_name = value

def del_first_name(self):
raise AttributeError("can't delete attribute")

name = property(get_first_name,
set_first_name,
del_first_name)
# 在已有getter-like、setter-like方法上创建property
# 注意:这里就是应该定义类属性,本身使用`@property`
# 装饰器也是相当于创建类属性
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
class Person:
def __init__(self, name):
self.name = name

@property
def name(self):
return self._name

@name.setter
def name(self, value):
if not instance(value, str):
raise TypeError("expected a string")
self._name = value

@name.deleter
def name(self):
raise AttributeError("can't delete attribute")

class SubPersonAll(Person):
# 这个类继承、扩展了`name`属性的所有功能
@property
def name(self):
print("getting name")
return super().name

@name.setter
def name(self, value):
print("Setting name to", value)
super(SubPerson, SubPerson).name.__set__(self, value)
# 使用`super(SubPerson, SubPerson)`调用父类实现
# 将控制权传递给`.name.__set__`方法,委托给父类
# 中定义的setter方法

@name.deleter
def name(self):
print("deleting name")
super(SubPerson, SubPerson).name.__delete__(self)

class SubPersonPart(Person):
# 仅修改`name`属性的某个方法
# 需要知道定义`name`属性的基类,否则重新定义property属性
# 的所有方法,并使用`super`将控制权转移给父类
@Person.name.getter
# 使用硬编码的`Person`类名,这样会把之前已经定义的
# property属性方法复制过来,而对应的`getter`、
# `setter`、`deleter`方法被替换
# 这里如果直接使用`@property`装饰,那么`setter`、
# `deleter`方法将会消失
def name(self):
print("getting name")
return super().name

类继承

  • 类继承会获得基类的所有方法
    • 类里面的方法其实真的不是给类使用的,而是给实例使用
    • 类自身使用的方法是元类中的方法

Method Resolution Order

MRO/方法解析顺序列表:包含当前类所有超类的线性顺序表

  • MRO列表顺序通过C3线性化算法实现,对每个类按以下规则合并 所有父类的MRO列表

    • 子类先于父类检查
    • 多个父类根据其在列表中的顺序被检查
    • 若对下一个类存在多个合法的选择,选择第一个父类
  • 为了实现继承,python会在MRO列表上从左到右开始查找超类, 直到第一个匹配这个属性的类为止

  • 可以通过__mro__mro()访问

super

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class super:
super()
# 等同于:`super(__class__, <first_argument>)`
# `<first_argument>`常常就是`self`
super(type)
# 返回:未绑定super对象,需要`__get__`绑定
super(type, obj)
# 返回:已绑定super对象,要求`isinstance(obj,type)`
super(type, type2)
# 返回:已绑定super对象,要求`issubclass(type2, type)`
# 此时调用方法返回是函数,不是绑定方法,不会默认传入
# `type2`作为首个参数

def __get__(self, obj, type=None):


def super(cls, inst/subcls):
mro = inst.__class__.mro()
mro = subcls.mro()
return mro[mro.index(cls) + 1]
  • 参数

    • 第一个参数:在MRO列表中定位类搜索起点(不包括)
    • 第二个参数:提供MRO列表
      • 类:直接传递MRO列表
      • 实例:传递所属类的MRO列表
  • 返回:封装有两个参数的super实例

    • 类似于返回MRO列表中某个类的实例,取决于访问的属性
  • 用途:依次遍历MRO列表(指定位置开始)中类,查找指定属性

    • 可以使用指定超类创建super实例,跳过对部分类搜索
    • 只有MRO列表中每个类中的方法都super()调用,才能保证 列表中所有类的该方法都被链式调用

说明

  • 需要注意super(cls, inst).__getattribute__("meth")中 共有两段属性访问,两次访问调用不同__getattribute__

    • super(cls, inst).__getattribute__首先调用 super.__getattribute__type(inst).mro()中寻找 some_cls.__getattribute__

    • 然后调用some_cls.__getattrbibute__("meth")访问 meth属性

  • 应使用super访问基类属性,而不是直接使用基类名称,避免 多继承中出现问题

    • 继承链super保证方法只按找MRO列表顺序调用一次
    • 多继承中硬编码基类名称调用方法可能导致方法被调用多次
  • super访问的属性路线不够明确,所以需要遵循以下原则

    • 继承体系中,所有相同名字的方法拥有可兼容的参数名, 比如:相同参数个数、名称
    • 最好确保最顶层类提供这个方法的实现,这样保证MRO上的 查找链肯定可以找到该方法

抽象类

接口、抽象类

  • 抽象类无法直接实例化

    • 目的就是让别的类继承它并实现特定的抽象方法
    • 也可以通过注册方式让某类实现抽象基类
  • 用途

    • 通过执行类型检查,确保实现为特定类型、实现特定接口
  • 类型检查很方便,但是不应该过度使用,因为动态语言目的就是 灵活性,强制类型检查让代码更复杂
  • 使用abc模块方便定义抽象类

Mixins

Mixins:把一些有用的方法包装成Mixin类,用于扩展其他类的 功能,而这些类往往又没有继承关系

  • Mixin不能直接实例化使用
  • Mixin没有自己的状态信息,即没有定义__init__方法, 没有实例属性,因此Mixin中往往会定义__slots__ = ()
  • Mixins讨论参见cs_program/program_design/inheritation

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
class LoggedMappingMixin:
__slots__ = ()
def __getitem__(self, key):
print("getting:", str(key))
return super9).__getimte__(key)

def __setitem__(self, key, value):
print("setting {} = {!r}".format(key, value))
return super().__setitem__(key, value)

def __delitem__(self, key):
print("deleting", str(key))
return super().__delitem__(key)

class SetOnceMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key), "alreay set")
return super().__setitem__(key, value)

class StringKeysMappingMixin:
__slots__ = ()
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError("keys must be strings")
return super().__setitem__(key, value)

# 单独的Mixin类使用没有意义,也无法实例化

class LoggedDict(LoggedMappingMixin, dict):
# 把混入类和其他已存在的类结合起来使用
pass

from collections import defaultdict

class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
pass

def test():
d = LoggedDict()
d["x"] = 23
print(d["x"])
del d["x"]

d = setOnceDefaultDict(list)
d["x"].append(2)

Python类用法实例

延迟计算

使用描述器类构造延迟计算属性

  • 主要目的是为了提升性能
  • 避免立即计算
  • 缓存计算结果供下次使用
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
class lazyproperty:
def __init__(self, func):
self.func = func

def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
# 计算完成之后,缓存计算结果于类实例中
return value
# 描述器仅仅定义一个`__get__`方法,比通常具有更弱的绑定
# 这里,只有被访问属性不在**实例**底层字典`__dict__`中时
# `__get__`方法才会被触发
# 描述器属性是类属性,但优先级好像是高于实例属性???

def lazyproperty_unchangable(func):
# 这个版本的延迟计算,使用property属性限制对结果的修改
name = "_lazy_" + func.__name __

@property
# 这里`@property`是在类外定义的
# 并且,这里实际上返回的是`property`类实例,也不需要
# `wraps(func)`保持原函数元信息
# 但此时所有的操作都定向到`getter`函数上,效率较低
def lazy(self):
if hasattr(self, name):
return getattr(self, name)
else:
value = func(self)
setattr(self, name, value)
return value
return lazy

import math

class Circle:
def __init__(self, radiu):
self.radius = radius

@lazyproperty
def area(self):
print("computing area")
return math.pi * self.radius ** 2
# 等价于`area = lazyproperty(area)`
# 所以是真的把描述器类实例作为类属性

@lazyproperty
def perimeter(self):
print("computing perimeter")
return 2 * math.pi * self.radius

数据模型类型约束

使用描述器在对实例某些属性赋值时进行检查

类继承方案

基础构建模块

创建数据模型、类型系统的基础构建模块

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
class Descriptor:
def __init__(self, name=None, **opts):
self.name = name
for key, value in opts.items()
setattr(self, key, value)

def __set__(self, instance, value):
if instance is None:
return self
instance.__dict__[self.name] = value

class Typed(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expected >= 0")
super().__set__(instance, value)

class Unsigned(Descriptor):
def __set__(self, instance, value):
if value < 0:
raise ValueError("expect >= 0")
super().__set__(instance, value)

class MaxSized(Descriptor):
def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size options")
super.__init__(name, **opts)

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super().__set__(instance, value)

具体数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Integer(Typed):
expected_type = int

class UsignedInteger(Integer, Unsigned):
# 描述器类是基于混入实现的
pass

class Float(Typed):
expected_type = Float

class UnsignedFloat(Float, Unsigned):
pass

class String(Typed):
expected_type = str

class SizedString(String, MaxSized):
pass

使用

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
class Stock:
name = SizedString("name", size=8)
shares = UnsignedInteger("shares")
price = UnsignedFloat("price")

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

def check_attributes(**kwargs):
def decrator(cls):
for key, value in kwargs.items():
if isinstance(value, Descriptor):
value.name = key
setattr(cls, key, value)
else:
setattr(cls, key, value(key))
return cls
return decrator

@check_attributes(name=SizedString(size=8),
shares=UnsignedInteger,
price=UnsignedFloat)
# 使用类装饰器简化版本
class Stock2:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

class checkmeta(type):
def __new__(cls, clsname, bases, methods):
for key, value in method.items():
if isinstance(value, Descriptor):
value.name = key
return type.__new__(cls, clsname, bases, methods)

class Stock3(metaclass=checkdmeta):
name = SizedString(size=8)
shares = UnsignedInteger()
price = UnsignedFloat()

def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price

装饰器类替代mixin

使用类装饰器、元类都可以简化代码,但类装饰器更加灵活

  • 类装饰器不依赖其他任何新技术
  • 类装饰器可以容易的添加、删除
  • 类装饰器能做为mixin的替代技术实现同样的效果,而且速度 更快,设置一个简单的类型属性值,装饰器要快一倍

示例1

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
def LoggedMapping(cls):
cls_getitem = cls.__getitem__
cls_setitem = cls.__setitem__
cls_delitem = cls.__setitem__
# 获取原`cls`的方法,避免死循环调用

def __getitem__(self, key):
print("getting", str(key))
return cls_getitem(self, key)
# 这里使用之前获取的方法指针调用,而不直接使用
# `cls.__getitem__`避免死循环

def __setitem__(self, key, value):
pritn("setting {} = {!r}", str(key))
return cls_set(self, key, value)

def __delitem__(self, key):
print("deleting", str(key))
return cls_delitem(self, key)

cls.__getitem__ = __getitem__
cls.__setitem__ = __setitem__
cls.__delitem__ = __delitem__

return cls

@LoggedMapping
class LoggedDict(dict):
pass

示例2

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
64
65
66
67
def Type(expected_type, cls=None):
if cls is None:
return lambda cls: Typerd(expected_type, cls)
super_set = cls.__set__

def __set__(self, instance, value):
if no instance(value, expected_type):
raise TypeError("expect " + str(expected_type))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

def Unsigned(cls):
super_set = cls.__set__

def __set__(self, instance, value):
if value < 0:
raise TypeError("missing size option")
super_set(self, name, **opts)

cls.__init__ = __set__
return cls

def MaxSized(cls):
super_init = cls.__init__

def __init__(self, name=None, **opts):
if "size" not in opts:
raise TypeError("missing size option")
super_init(self, name, **opts)

cls.__init__ = __init__

super_set = cls.__set__

def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError("size must be <" + str(self.size))
super_set(self, instance, value)

cls.__set__ = __set__
return cls

@Typed(int)
class Integer(Descriptor):
pass

@Unsigned
class UnsignedInteger(Integer):
pass

@Typed(float)
class Float(Descriptor):
pass

@Unsigned
class UnsignedFloat(Float):
pass

@Typed(str)
class String(Descriptor):
pass

@MaxSized
class SizedString(String):
pass

自定义容器

collections定义了很多抽象基类,可以用于定义自定义基类

collections.Sequence

Sequence需要实现的抽象方法有:

  • __getitem__
  • __len__
  • add

继承自其的类,支持的常用操作:索引、迭代、包含判断、切片

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
from collections import Sequence
import collections

class SortedItems(Sequence):
# 必须要实现所有抽象方法,否则报错
def __init__(self, initial=None):
self._items = sorted(initial) if initial is not None else [ ]
def __getitem__(self, index):
return self._items[index]

def __len__(self):
return len(self._items)

def add(self, item):
bisect.insort(self._items, item)
# `bisect`模块是用于在排序列表中高效插入元素
# 保证在元素插入之后仍然保持顺序

# `SortedItems`继承了`colllections.Sequence`,现在和
# 普通序列无差,支持常用操作:索引、迭代、包含判断、切片

def test():
items = SortedItems([5, 1, 3])
print(list(items))
print(items[0], items[-1])
items.add(2)
print(list(items))

if instance(items, collections.Iterable):
pass
if instance(items, collections.Sequence):
pass
if instance(items, collections.Container):
pass
if instance(items, collections.Sized):
pass
if instance(items, collections.Mapping):
pass

collections.MutableSequence

MutableSequence基类包括需要实现的抽象方法

  • __getitem__
  • __setitem__
  • __delitem__
  • __len__
  • insert

提供的可用方法包括;

  • append
  • count:统计某值出现的次数
  • remove:移除某值的元素
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
from collections import MutableSequence

class Item(collections.MutableSequence):
def __init__(self, initial=None):
self._items = list(initial) if initial is not None else [ ]

def __getitem__(self, index):
print("getting:", index)
return self._items[index]

def __setitem__(self, index):
print("setting:", index)
self._items[index] = value

def __delitem__(self, index):
print("deleting:", index)
del self._items[index]

def insert(self, index, value):
print("inserting:", index, value)
self._items.insert(index, value)

def __len__(self):
print("len")
return len(self._items)

# 基本支持几乎所有的核心列表方法:`append`、`remove`、
# `count`

def count():
a = Items([1, 2, 3])
print(len(a))
a.append(4)
# 在末尾添加元素
# 调用了`__len__`、`insert`方法
a.count(2)
# 统计值为`2`出现的次数
# 调用`__getitem__`方法
a.remove(3)
# 删除值为`3`的元素
# 调用`__getitem__`、`__delitem__`方法

属性的代理访问

代理是一种编程模式,将某个操作转移给另一个对象来实现

  • 需要代理多个方法时,可以使用__getattr__

    • __getattr__方法只在属性、方法不存在时才被调用, 所以代理类实例本身有该属性不会触发该方法,也不会代理 至被代理类

    • 如果需要管理所有对方法、属性的访问,可以定义 __getattribute__,其在对类的所有属性、访问时均会 被触发,且优先级高于__getattr__

    • __setattr____delattr__需要约定是对代理类还是 被代理类操作,通常约定只代理_开头的属性,即代理类 只暴露被代理类公共属性

    • 注意:对象的元信息直接访问能通过__getattr__代理, 但是对应的hook可能无法正常工作,如果需要,要单独为 代理类实现元信息代理方法

  • 通过自定义属性访问方法,可以用不同方式自定义代理类行为, 如:日志功能、只读访问

  • 代理有时候可以作为继承的替代方案:代理类相当于继承了被 代理类
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
class Proxy:
# 这个类用于包装、代理其他类,修改其行为
def __init__(self, obj):
self._obj = obj

def __getattr__(self, name):
print("getattr:", name)
return getattr(self._obj, name)

def __setattr__(self, name, value):
if name.startwith("_"):
# 约定只代理不以`_`开头的属性
# 代理类只暴露被代理类的公共属性
super().__setattr__(name, value)
else:
print("setattr:", name, value)
setattr(self._obj, name, value)

def __delattr__(self, name):
if name.startwith("_"):
super().__delattr__("name")
else:
print("delattr:", name)
delattr(self._obj, name)

class Spam:
def __init__(self, x):
self.x = x

def bar(self, x):
print("Spam.bar", self.x, y)

def test():
s = Spam(2)
p = Proxy(s)
p.bar(3)
p.x = 37
# 通过`p`代理`s`

p = Porxy([1, 3, 5])
# len(p)
# `len`函数直接使用会报错
p.__len__()
# `p.__len__`可以正常代理,返回代理的列表长度
# 这说明python中的钩子函数有特殊的结构?

状态机(状态模式)

为不同的状态定义对象,而不是使用过多的条件判断

  • 提高执行效率
  • 提高代码可维护性、可读性
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
64
65
66
67
68
69
70
71
72
73
class Connection:
def __init__(self):
self.new_state(ClosedConnectionState)

def new_state(self, newstate):
self._state = newstate

def read(self):
return self._state.read(self)

def write(self, data):
return self._state.write(self, data)

def open(self):
return self._state.close(self)

class ConnectionState:
@staticmethod
def read(conn):
raise NotImplementedError()

@staticmethod
def write(conn, data):
raise NotImplementedError()

@staticmethod
def open(conn):
raise NotImplementedError()

@staticmethod
def close(conn):
raise NotImplementedError()

class ClosedConnectionState(ConnectionState):
@staticmethod
def read(conn):
raise RuntimeError("not open")

@staticmethod
def write(conn):
raise RuntimeError("not open")

@staticmethod
def open(conn):
conn.new_state(OpenConnectionState)

@staticmethod
def close(conn):
raise RuntimeError("already closed")

calss OpenConnectionState(ConnectionState):
@staicmethod
def read(conn):
print("reading")

@staticmethod
def write(conn, data):
print("writing", data)

@staticmethod
def open(conn):
raise RuntimeError("already open")

@staticmethod
def close(conn):
conn.new_state(ClosedConnectionState)

def test():
c = Connection()
c.open()
c.read()
c.write("hello")
c.close()

访问者模式

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
64
65
66
67
68
69
70
71
72
73
class Node:
pass

class UnaryOperator(Node):
def __init__(self, operand):
self.operand =operand

class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right

class Add(BinaryOperator):
pass

class Sub(BinaryOperator):
pass

class Mul(BinaryOperator):
pass

class Div(BinaryOperator):
pass

class Nagate(UnaryOperator):
pass

class Number(Node):
def __init__(self, value):
self.value = value

class NodeVsistor:
def visit(self, node):
methname = "visit_" + type(node).__name__
meth = getattr(self, methname, None)
# 使用`getattr`获取相应方法,避免大量`switch`
# 子类需要实现`visit_Node`一系列方法
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_" + type(node).__name_))

class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
# 递归调用`visit`计算结果
# 因此可能超过python递归嵌套层级限制而失败

def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)

def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)

def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)

def visit_Negate(self, node):
return -node.operand

def test():
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1), t3)

e = Evaluator()
e.visit(t4)

yield消除递归

消除递归一般是使用栈、队列,在python还可以使用yield得到 更加简洁的代码

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
import types

class NodeVisitor:
def visit(self, node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
# 对`yield`实现
stack.append(last.send(last_result))
last_result = None
elif isinstance(last, Node):
# 对递归实现
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopIteration:
stack.pop()
return last_result

def _visit(self, node):
methname = "visit" + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)

def generic_visit(self, node):
raise RuntimeError("No {} method".format("visit_", type(node).__name__))

class Evaluator(NodeVisitor):
# `yield`版本不会多次递归,可以接受更多层级
def visit_Number(self, node):
return node.value

def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
# 遇到`yield`,生成器返回一个数据并暂时挂起

def visit_Sub(self, node):
yield (yield node.left) + (yield node.right)

def visit_Mul(self, node):
yield (yield node.left) * (yield node.right)

def visit_Div(self, node):
yield (yield node.left) * (yield node.right)

def visit_Nagate(self, node):
yield - (yield node.operand)

字符串调用方法

  • 可以使用getattr(instance, name)通过字符串调用方法
  • 也可以用operator.methodcaller
    • operator.methodcaller创建可调用对象,同时提供所有 必要参数
    • 调用时只需要将实例对象传递给其即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import math
from operator import methodcaller

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

def __repr__(self):
return "Point({!r}, {!r})".format(self.x, self.y)

def distance(self, x, y):
return math.hypot(self.x -x, self.y - y)

def test():
points = [
Point(1, 2),
Point(3, 0),
Point(10, -3),
Point(-5, -7)
]

points.sort(key=methodcaller("distance', 0, 0))
# `methodcaller`创建可调用对象,并提供所有必要参数

缓存实例

工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Spam:
def __init__(self, name):
self.name = name

import weakref
_spam_cache = weakref.WeakValueDictionary()
# `WeakValueDictionary`实例只保存在其他地方还被使用的
# 实例,否则从字典移除

def get_spam(name):
# 使用工厂函数修改实例创建行为
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s

def test():
a = get_spam("foo")
b = get_spam("foo")
print(a is b)

缓存管理器

将缓存代码放到单独的缓存管理器中,

  • 代码更清晰、灵活,可以增加更多的缓存管理机制
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
import weakref

class CachedSpamManager:
def __init__(self):
self._cache = weakref.WeakValueDictionary()

def get_spam(self, name):
if name not in self._cache:
s = Spam(name)
self._cache[name] = s
else:
s = self._cache[name]
return s

def clear(self):
self._cache.clear()

class Spam:
manager = CacheSpamManager()
def __init__(self, *args, **kwargs):
# `__init__`方法抛出异常,防止用户直接初始化
# 也可以将类名加上`_`,提醒用户不要实例化
raise RuntimeError("can't instantiate directly")

@classmethod
def _new(cls, name):
self = cls.__new__(cls)
self.name = name
return self

__new__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref

clas Spam:
_spam_cache = weakref.WeakValueDictionary()

def __new__(cls, name):
if name in cls._spam_cache:
return cls._spam_cache[name]
else:
self = super().__new__(cls)
cls._spam_cache[name] = self
return self

def __init__(self, name):
print("initializing Spam")
self.name = name
# 这种方式实际会多次调用`__init__`方法,即使已经结果已经
# 缓存,不是个好方法

存储格式

数值存储

  • 机器数:数在计算机中二进制表示形式,带符号
  • 真值:考虑表示规则,机器数二进制表示的真实数值
  • 大小端

    • big-little endian大小端:高位byte在低位byte
      • 更适合人理解
    • little-big endian小大端:低位byte在高位byte
      • 更适合计算机计算,大部分计算机架构
      • 方便截断操作
  • 符号(有符号数):大部分(静态)语言使用最高位表示符号

    • 0:正数
    • 1:负数
    • 动态语言如 python 的整形不对应定长数据类型,存储逻辑和 cpp/c 等语言相差较大

整形

  • 模:衡量计数系统容量的参数,代表了计数系统能表示、存储的状态数

原码、反码、补码

  • sign and magnitude 原码:二进制位表示数值本身
    • 需要额外符号位标识正负
  • ones’ complement 反码:二进制位为真值对计数系统最大值求补
    • one:表示有限位计数系统能表示的最大值,即二进制位全为 1
      • 符号位不仅标识正负值,且可作为数值位参与运算
      • 但不是对模求补
        • 存在 +0/-0 问题
        • 运算结果和实际结果相差 1
    • 考虑到二进制位全 1 性质
      • 正数:反码即为其原码
      • 负数:反码为原码符号位不变,数值位取反(因此被称反码)
  • twos’ complement 补码:二进制位为真值对计数系统模求补
    • two:表示有限位计数系统容量,即溢出 1、二进制位全为 0
      • 符号位不仅标识正负值,且可作为数值位参与运算
      • 考虑到是对模求补
        • -0 表最大负值,不存在 +0/-0 问题
        • 运算结果和实际结果相同
    • 将有限位计数系统视为加法封闭群
      • 系统中各元素是有序的,为保证加法计算,则最大正值之后必然为最大负值
      • 正、负值可视为是对群元素的人为划分
      • 可以任意值作为划分,但二进制位计算真值复杂
    • 考虑到二进制位全 0 性质
      • 正数:补码即为其原码
      • 负数:补码为反码 +1(原码也为补码数值位求反后 +1

浮点型

  • 遵循 IEEE 754 标准的系统必须支持单精度,最好支持双精度,对扩展精度则无要求
长度 符号位 指数/阶码 尾数 有效位数 指数偏移/偏阶
单精度 32bits 1bit 8bits 23bits 23bits 127(规格化)/126(非规格化)
双精度 64bits 1bit 11bits 52bits 53bits 1023(规格化)/1022(非规格化)
扩展精度 80bits 1bit 15bits 64bits 64bits 16383
  • 符号位:数值整体正负号
  • 指数/阶码:数值小数点位置,也即数值放缩
    • 阶码部分没有单独的符号位,为表示负值,则需要规定指数偏移,将 阶码减去偏移量得到实际指数值
    • 指数偏移被设置为 $2^{E-1} - 1$ 好处
      • 对称正负取值范围
      • 首位即可判断浮点值和 1 关系
  • 尾数:数值精确程度
    • 根据 IEEE 754规格化值尾数首位(整数位)必须为 1
      • 规格化值的尾数值必然大于 1
      • 单、双精度可以省略对首位的存储,有效位数比尾数长度多 1
      • 扩展精度不区分是否为规格化值
    • (单、双精度)非规格化值首位为 0,同样省略
      • 非规格化值允许尾数部分小于 1,扩展浮点数对小值的表示
      • 所以,非规格化值阶数部分必然(规定)全为 0
      • 非规格化值指数偏移量比规格化值小 1,可缩小最小规格化值与最大非规格化值间距
  • 特殊浮点值
    • 指数值全 1、尾数非 0:NaN
    • 指数值全 1、尾数为 0:正、负无穷
  • 事实上指数底数可以是任意值,但 IEEE 754 标准规定底数为 2

GCC

G++

g++:是gcc的特殊版本,链接时其将自动使用C++标准库而不是 C标准库

1
2
$ gcc src.cpp -l stdc++ -o a.out
// 用`gcc`编译cpp是可行的

C++函数

函数定义、声明

函数:被组织成具有特定名称的独立单元的代码块

  • 将某段操作代码组织起来,编写一次、多次使用,可以显著降低 程序规模,而且使程序更易于维护

  • 将大型程序分解成多个易于管理的小部分

    • 好的、独特的分解分解方法,会使得每个函数都是紧密的 单元,使得问题整体更加易于理解
    • top-down design:过程一般从主程序开始分解问题, 逐步求精
    • 即使函数只在程序中使用一次,定义函数依然值得
1
2
3
type name(parameters){
body
}
  • name:函数名
  • parameters:逗号分隔的函数形参列表

Parameters

形参:调用函数时用以传递实参的占位符

  • 类似局部变量,但是在调用时使用实参进行初始化
  • 如果需要使用形参值,可以忽略其形参名,即使是在 函数实现中也可以忽略,如:++后缀重载

Default Parameter

默认形参:具有默认值的形参,调用时可以不给其传递实参值

  • 默认形参只能出现在函数声明中,不能出现在函数定义中
  • 默认形参只能出现在形参列表末尾
  • 默认形参在C++中有滥用的问题,更倾向使用函数重载完成默认 形参的功能
  • 默认形参、函数重载同时使用可能导致编译器无法识别应该调用 何者而报错

Value Parameter

值参数:函数调用时,被调函数中值形参将获得主调函数的实参的 值拷贝

  • 被调函数中传入的实参变量值仅改变被调用函数局部形参 的值,对主调函数中实参变量的值没有影响

Reference Parameter

引用参数&:函数调用时,被调函数中引用形参获得主调函数中实参 引用

  • 主调函数、被调函数共享实参变量的统一存储空间,不需要复制 实参变量中的值,有时更高效

  • 对应引用形参的实参必须是可赋值的量,如:变量名

    • 实参可以是指针,即参数可以同时有* &

      1
      int insertAVL(BSTNode * & t, const string & key);
  • 常用于

    • 需要保存函数对参数值的修改
    • 函数需要返回多个值:并通过实参列表向函数传递、获取值

Constant Reference Parameter

常量引用调用const &:常量引用作为函数参数调用

  • 传递对象时,常量引用调用通常优于传统引用调用、值调用, 提供了引用传递的高效性、值传递的安全性

  • 需要注意参数中const关键字的位置

    1
    2
    3
    4
    5
    int strlen(const char * cptr);
    // `const`后是类型名,表明`cptr`是指向`const char`指针
    // 不能改变`cptr`指向的支付串内容
    int strlen(char * const cptr);
    // `const`后是形参名,表明`cptr`常量,其值不能改变
  • 使用常量引用需要参数类是constant correct,能够提供更多 的关于类中定义的方法的信息

Prototype

函数原型/函数声明:函数定义的首行加上分号结尾组成

  • 提供编译器大部分情况下仅仅需要的形参、返回值类型

  • 函数原型中形参名可选,但是好的形参名有助于可读性

  • 如果函数先定义后调用,可以不需要编写函数原型,但这种代码 风格和自顶向下的程序设计风格相悖

Signature

函数签名:函数的形参模型

  • 和函数原型相比,不包括返回值类型

Overloading

函数名重载:使用相同名字的不同版本函数

  • 函数名相同、函数参数列表不同是合法的,即函数签名不同即 合法(函数原型不同不一定合法,返回值类型不同)

    • 形参数量
    • 形参类型
  • 编译器遇到调用函数的函数名指代不唯一时,编译器会检查所传 实参,选择最合适的函数版本

Calling

使用函数名调用函数代码(块)的行为

  • 函数被调用后将会获得函数argument提供的值,执行函数功能

  • 返回函数调用点:记忆主调程序工作情况,以便程序 返回函数调用的确切位置是函数调用机制的主要特性之一

  • argument:实参,调用函数时的表达式,用于向函数传递信息
  • 调用函数前必须对函数提供声明或定义,以使编译器可以判断 函数调用是否与其定义兼容

函数调用步骤

  • 主调函数将实参与自己上下文中的若干局部变量绑定来 计算每个参数值

    • 实参通常为表达式,计算其值时可能涉及操作符、其他函数 调用
    • 新的函数开始执行前,主调函数会对传如的实参合法性进行 验证
  • 系统为新函数所需的所有局部变量(包括形参)创建新的存储 空间

    • 这些变量将被分配在内存中stack frame区域中
  • 每个实参值将被传入到函数相应的形参变量中

    • 对于包含多个形参的函数,实参对形参的值拷贝将按照 对应函数形参顺序执行
    • 如有必要,编译器将像变量赋值一样,执行从实参到 形参的类型转换
    • 对引用参数,栈帧会存储一个指针指向该值内存单元
  • 执被调函数体中语句,直到遇到return语句或没有多余语句

  • 如果函数有返回值,函数体内return语句表达式的值将被计算 ,作为返回值返回给主调函数

    • 如有必要,编译器将执行数值的类型转换,确保返回值 符合被调函数值的类型要求(被调函数返回之前转换)
      1
      2
      3
      4
      5
      6
      7
      8
      int rint(){
      return 9.8;
      // 返回整形
      }
      int main(){
      double j = rint();
      // `j`被初始化为`9.8`
      }
  • 删除为函数调用创建的栈帧,其中所有局部变量被系统清理

  • 将函数返回值(若存在)代入到调用函数调用点的位置

Pointer to a Function

函数指针:函数的第一条指令地址

1
2
3
4
double *g(double);
// 返回double类型指针的函数g
double (*fn)(double);
// 返回double类型的函数指针fn
  • 将函数作为数据值使用:使设计有效的接口变得容易,允许用户 像指定数据一样指定操作,即作为回调函数

    1
    2
    void mapAll(double (*fn)(double));
    // 声明使用函数指针做参数
  • C++对函数指针自动解析引用

  • 早期计算中,程序以代码、数据完全分开形式表示
  • 现代计算机,内存同时存储的数据值、硬件执行的机器指令
  • von Neumann Architecture:冯诺伊曼体系结构,将指令存储 在内存地址中作为数据值使用,使得创建函数指针成为可能

Closure

C++需要使用创建必要数据结构实现闭包

  • 需要将数据、代码封装在一个单独实体,即对象/类

  • 为使得闭包函数一般化,最好的方法是使用function class 实现,直接将实例作为“函数” (当然可以随便实现一个函数,不影响)

  • 函数类参见cppc/basics/class
  • 闭包参见program/program_design/function_design

函数类作为参数

  • 使用函数类作为参数时,没有任何明确方法声明类型,因为任何 重载()操作符都可以作为参数

  • C++使用模板函数实现,任何以函数对象作为参数的函数

    1
    2
    template <typename FunctionClass>
    void mapAll(FunctionClass fn);
    • 传给模板函数mapAll值可以是任意类型
    • 但当编译器展开其时,若参数类型不能获得期望参数, 编译器报错

CPP 类