Python 元编程

Decorator装饰器

装饰器就是函数,接受函数作为参数并返回新的函数

  • 装饰器不会修改原始函数签名、返回值,但是大部分返回的 新函数不是原始函数,看起来像是函数元信息发生改变
  • 新函数也可能是单纯的修改原函数的元信息、然后返回

装饰器设计

保留函数元信息

装饰器作用在函数上时,原函数的重要元信息会丢失

  • 名字:func.__name__
  • 文档字符串:func.__doc__
  • 注解:
  • 参数签名:func.__annotations__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
from functools import wraps

def timethis(func):
@wraps(func)
def wrapper(*args, **kargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper

@timethis
def countdown(n):
while n > 0:
n -= 1

def countdown(n):
pass
countdown = timethis(countdown)
# 这个和上面装饰器写法效果一致
  • @wraps能够复制原始函数的元信息,并赋给装饰器返回的函数 ,即被@wraps装饰的函数

  • 装饰后函数拥有__wrapped__属性

    • 直接用于访问被包装函数,即解除装饰器
      • 有多个包装器__wrapped__的行为是不可预知的, 可能会因为python版本有差,应该避免
    • 让装饰器函数正确暴露底层参数签名信息
    1
    2
    3
    countdown.__wrapped__(10000)
    from inspect import signature
    print(signature(countdown))

自定义属性

在装饰器中引入访问函数,访问函数中使用nolocal修改内部 变量t

  • 访问函数可在多层装饰器间传播,如果所有的装饰中的wrapper 都使用了@functools.wraps注解(装饰)
  • 可以使用lambda匿名函数,修改访问函数属性改变其行为
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
from functools import wraps
import logging

def logged(level, name=None, message=None):
def decorator(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)

@attach_wrapper(wrapper)
# attach setter function
def set_level(newlevel):
nonlocal level
level = newlevel

@attach_wrapper(wrapper)
def set_message(newmsg):
nonlocal logmsg
logmsg = newmsg

return wrapper
return decorator

@logged(logging.DEBUG)
def add(x, y):
return x + y

@logged(logging.CRITICAL, "example")
def spam():
print("Spam")

@timethis
@logged(logging.DEBUG)
# 使用多层装饰器,访问函数可以在多层装饰器间传播
def countdown(n):
while n > 0:
n -= 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
30
31
32
33
34
35
36
37
from functools import wraps
import logging

def logged(level, name=None, message=None):

r"""Decorator that allows logging
:param level: logging level
:param name: logger name, default function's module
:param message: log message, default function's name
:return :decorator
"""

def decorator(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper

return decorator

@logged(logging.DEBUG)
def add(x, y):
return x + y

@logged(loggin.CRITICAL, "example")
def spam():
print("spam")

def spam():
pass
spam = logged(logging.CRITICAL, "example")(spam)
# 这样调用和之前的装饰器语句效果相同

functools.partial

partial接受一个函数作参数,并返回设置了部分参数默认值的 函数,而最外层函数就只是用于“获取”参数,因此可以使用此技巧 减少一层函数嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import partial

def attach_wrapper(obj, func=None):

r"""Decorator to attach function to obj as an attr
:param obj: wapper to be attached to
:param func: function to be attached as attr
:return: wrapper
"""

if func is None:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func

参数可选

三层函数

这种形式的带可选参数的装饰器,即使不传递参数,也必须使用 调用形式装饰

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
from functools import wraps
import logging

def logged(level=logging.DEBUG, name=None, message=None):

r"""Decorator that allows logging
:param level: logging level
:param name: logger name, default function's module
:param message: log message, default function's name
:return :decorator
"""

def decorator(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper

return decorator

@logged()
# 这种方式实现的默认参数装饰器必须使用`@logged()`调用的
# 形式,从装饰器形式就可以看出,必须调用一次才能返回内部
# 装饰器
# 这种形式的装饰器不符合用户习惯,不用传参也必须使用的
# 调用形式
def add(x, y):
return x + y

@logged(level=logging.CRITICAL, name="example")
def add(x, y):
return x + y
partial

这种形式的装饰器,不传参时可以像无参数装饰器一样使用

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
from functools import wraps, partial
import logging

def logged(
func=None,
*,
level=logging.DEBUG,
name=None,
message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)

return wrapper

@logged
# 使用`partial`函数形式的带默认参数的装饰器,可以不用
# 调用形式
def add(x, y):
return x + y

@logged(level=logging.CRITICAL, name="example")
def spam():
print("spam")

用途示例

强制检查参数类型

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
def typeasssert(*ty_args, **ty_kwargs):

r"""Decorator that assert the parameters type
:param *ty_args: parameters type indicated with position
:param **ty_kwargs: parameters type indicated with keywords
:return: wrapper
"""

def decorator(func):
# if in optimized mode, disable type checking
if not __debug__:
return func

sig = signature(func)
# map function argument names to asserted types
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

@wraps(func)
def wrapper(*args, **kwargs):
# map function argument names to paraments
bound_values = sig.bind(*args, **kwargs).argument
for name, val in bound_values.items():
if name in bound_types:
if not isinstance(value, bound_types[name]):
raise TypeError(
"Argument {} must be {}.".format(name, bound_types[name])

return func(*args, **kwargs)
return wrapper
return decorator

装饰器类

为了定义类装饰器,类需要实现__call____get__方法,然后 就可以当作普通的的装饰器函数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import types
from functools improt wraps

class Profiled:
def __init__(self, func):
wraps(func)(self)
# 获取`func`的元信息赋给实例
self.ncalls = 0

def __call__(self, *args, **kwargs):
self.nacalls += 1
return self.__wrapped__(*args, **kwargs)
# 解除装饰器

def __get__(self, instance, cls):
if intance is None:
return self
else:
return types.MethodType(self, instance)

装饰器方法

在类中定义装饰器方法,可以将多个装饰器关联于同一个类的实例, 方便在装饰器中记录、绑定、共享信息

  • @property装饰器类:可以将类方法method_atrr“转变”为 属性

    • 设置完成之后,会创建2个以方法名开头的装饰器 method_attr.settermethod_attr.deleter用于装饰 同名的方法
    • 分别对应其中包含setterdeletergetter@property自身)三个方法

    详情查看clsobj

装饰类、静态方法

装饰器必须放在@staticmethod@classmethod装饰器之前 (内层),因为这两个装饰器实际上并不创建callable对象,而是 创建特殊的描述器对象

添加参数

为原函数“添加”KEYWORD_ONLY参数,这种做法不常见,有时能避免 重复代码

  • KEYWORD_ONLY参数容易被添加进*args**kwargs
  • KEYWORD_ONLY会被作为特殊情况挑选出来,并且不会用于调用 原函数
  • 但是需要注意,被添加的函数名称不能与原函数冲突
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from functools import wraps
import inspect
def optional_debug(func):
if "debug" in inspect.getargspect(func).args:
raise TypeError("Debug argument already defined")
# 防止原函数参数中参数与新增`debug`冲突

@wraps(func)
def wrapper(*args, debug=False, **kwargs):
# 给原函数“增加”了参数
if debug:
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper

装饰器扩充类功能

可以通过装饰器修改类某个方法、属性,修改其行为

  • 可以作为其他高级技术:mixin、metaclass的简洁替代方案
  • 更加直观
  • 不会引入新的继承体系
  • 不依赖super函数,速度更快

注意:和装饰函数不同,装饰类不会返回新的类,而是修改原类, 因此装饰器的顺序尤为重要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def log_getattribute(cls):
orig_getattribute = cls.__getattribute__

def new_getattribute(self, name):
print("getting:", name)
return orig_getattribute(self, name)

cls.__getattribute = new_getattribute
return cls

@log_getattribute
class A:
def __init__(self, x):
self.x = x
def spam(self):
pass

Metaclass元类

用途示例

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__intance = None
super().__init__(*args, **kwargs)

def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance

class Spam(metaclass=Singleton):
def __init__(self):
print("Creating Spam")

缓存实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import weakref
class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionar()

def __call__(self, *args):
if args in self.__cache:
return self.__cache[args]
else:
obj = super().__call__(*args)
self.__cache[args] = obj
return obj

class Spam(metaclass=Cached):
def __init__(self, name):
print("creating spam({!r})".format(name))
self.name = name

Python Readme

函数书写声明

1
2
3
4
5
6
return = func(
essential(type),
optional=defaults/type,
*args,
**kwargs
)

格式说明

参数

  • essential参数:essential(type)

    • 没有=
    • 在参数列表开头
    • (type):表示参数可取值类型
  • optional参数:optional=defaults/type

    • defaults若为具体值,表示默认参数值
      • 默认值首字母大写
    • 默认参数值为None
      • 函数内部有默认行为
      • None(默认行为等价参数值)
    • type:之后表示可能取值类型
  • *args参数:[参数名]=defaults/type

    • 首参数值为具体值表示函数默认行为(不是默认值,args 参数没有默认值一说)
    • 其后为可取参数值类型
    • 说明
      • 参数名仅是标记作用,不能使用关键字传参
      • []:代表参数“可选”
  • **kwargs参数:[param_name=defaults/type]

    • 参数默认为可选参数,格式规则类似args参数
  • POSITION_ONLY参数:[param_name](defaults/type)

    • POSITION_ONLY参数同样没有默认值一说,只是表示默认 行为方式(对应参数值)
  • 参数名后有?表示参数名待确定
  • 参数首字母大写表示唯一参数

返回值

返回值类型由返回对象名称蕴含

对象类型

  • obj(type)type表示包含元素类型

Pandas Readme

常用参数说明

  • 函数书写声明同Python全局
  • 以下常用参数如不特殊注明,按此解释

DataFrame

  • axis=0/1/"index"/"columns"

    • 含义:作用方向(轴)
    • 默认:0/"index",一般表示row-wise(行变动)方向
  • inplace=False/True

    • 含义:是否直接在原对象更改
    • 默认:False,不更改,返回新DF对象(为True时无返回值)
    • 其他
      • 大部分df1.func()类型函数都有这个参数
  • level=0/1/level_name...

    • 含义:用索引层级
    • 默认:部分默认为0(顶层级)(也有默认为底层级), 所以有时会如下给出默认值
      • t(top):顶层级0(仅表意)
      • b(bottom):底层级-1(仅表意)
      • 默认值为None表示所有层级

Pandas非必须依赖包

文件相关

  • Excel
    • xlrd/xlwtxls格式读写,速度较快
    • openpyxlxlsx格式读写,速度较慢

Pandas版本

  • 0.22.x

    • flaot类型可作为Categorical Index成员,不能被用于 loc获取值
  • l.1.1

    • astype方法不支持pd.Timestamp类型,只能用 "datetime64"替代
  • ALL

    • Category Series作为groupby聚集键时,类别元素都会 出现在聚集结果中,即使其没有出现在seris值中
    • setfrozenset被认为是list-like的indexer,所以 在索引中的frozenset无法用一般方法获取

      1
      df.iloc[list(df.index).index(fronzenset)]

Keras Readme

常用参数说明

  • 函数书写声明同Python全局说明
  • 以下常用参数如不特殊注明,按照此解释

Common

  • seed=None/int

    • 含义:随机数种子
  • padding="valid"/"same"/"causal"

    • 含义:补0策略
      • “valid”:只进行有效有效卷积,忽略边缘数据,输入 数据比输出数据shape减小
      • “same”:保留边界处卷积结果,输入数据和数据shape 相同
      • “causal”:产生膨胀(因果卷积),即output[t] 不依赖input[t+1:],对不能违反时间顺序的时序 信号建模时有用
    • 默认:valid

Layers

  • input_shape=None/(int,...)

    • 含义:输入数据shape
      • Layers只有首层需要传递该参数,之后层可自行推断
      • 传递tuple中None表示改维度边长
    • 默认:None,由Layers自行推断
  • data_format=None/"channels_last"/"channels_first"

    • 含义:通道轴位置
      • 类似于dim_ordering,但是是Layer参数
    • 默认
      • 大部分:None由配置文件(默认”channels_last”) 、环境变量决定
      • Conv1DXX:”channels_last”
      • 其实也不一定,最好每次手动指定
  • dim_ordering=None/"th"/"tf"

    • 含义:中指定channals轴位置(thbatch后首、tf尾)
    • 默认:None以Keras配置为准
    • 注意:Deprecated,Keras1.x中使用

Conv Layers

  • filters(int)

    • 含义:输出维度
      • 对于卷积层,就是卷积核数目,因为卷积共享卷积核
      • 对于局部连接层,是卷积核组数,不共享卷积核 ,实际上对每组有很多不同权重
  • kernel_size(int/(int)/[int])

    • 含义:卷积核形状,单值则各方向等长
  • strides(int/(int)/[int])

    • 含义:卷积步长,单值则各方向相同
    • 默认:1移动一个步长
  • dilation_rate(int/(int)/[int])

    • 含义:膨胀比例
      • 即核元素之间距离
      • dilation_ratestrides最多只能有一者为1, 即核膨胀、移动扩张最多只能出现一种
    • 默认:1不膨胀,核中个元素相距1
  • use_bias=True/False

    • 含义:是否使用偏置项
    • 默认:True使用偏置项
  • activation=str/func

    • 含义:该层激活函数
      • str:预定义激活函数字符串
      • func:自定义element-wise激活函数
    • 默认:None不做处理(即线性激活函数)
  • kernel_initializer=str/func

    • 含义:权值初始化方法
      • str:预定义初始化方法名字符串 (参考Keras Initializer)
      • func:初始化权重的初始化器
    • 默认:glorot_uniform初始化为平均值
  • bias_initializer=str/func

    • 含义:偏置初始化方法
      • str:预定义初始化方法名字符串
      • func:初始化权重的初始化器
    • 默认:zeros初始化为全0
  • kernel_regularizer=None/obj

    • 含义:施加在权重上的正则项 (参考Keras Regularizer对象)
    • 默认:None不使用正则化项
  • bias_regularizer=None/obj

    • 含义:施加在偏置上的正则项 (参考Keras Regularizer对象)
    • 默认:None不使用正则化项
  • activity_regularizer=None/obj

    • 含义:施加在输出上的正则项 (参考Keras Regularizer对象)
    • 默认:None不使用正则化项
  • kernel_constraint=None/obj

    • 含义:施加在权重上的约束项 (参考Keras Constraints)
    • 默认:None不使用约束项
  • bias_constraint=None

    • 含义:施加在偏置上的约束项 (参考Keras Constraints)
    • 默认:None不使用约束项

激活函数

  • 通过设置单独的激活层实现

    1
    2
    3
    4
    from keras.layers import Activation, Dense

    model.add(Dense(64))
    model.add(Activation('tanh'))
  • 也可以在构造层对象时通过传递activation参数实现

    1
    model.add(Dense(64, activation='tanh'))
  • 可以通过传递一个逐元素运算的Theano/TensorFlow/CNTK函数 来作为激活函数

    1
    2
    3
    4
    from keras import backend as K

    model.add(Dense(64, activation=K.tanh))
    model.add(Activation(K.tanh))

    softmax

1
2
3
4
softmax(
x(tensor),
axis=-1/int
)

Softmax激活函数

  • 参数

    • x:张量
    • axis:整数,代表softmax所作用的维度
  • 返回值:softmax变换后的张量。

  • 异常:

    • ValueError: In case dim(x) == 1

elu

1
2
3
4
elu(
x,
alpha=1.0/num
)

指数线性单元

selu

1
selu(x)

可伸缩的指数线性单元(SELU)。

  • 参数

    • x: 一个用来用于计算激活函数的张量或变量。
  • 返回值:可伸缩线性指数激活

    • 可伸缩的指数线性激活:scale * elu(x, alpha)scale, alpha应该是预定义常数?)
  • 注意

    • 与「lecun_normal」初始化方法一起使用。
    • 与 dropout 的变种「AlphaDropout」一起使用。
    • 只要正确初始化权重(参见 lecun_normal 初始化方法) 并且输入的数量「足够大」(参见参考文献获得更多信息) ,选择合适的alphascale的值,就可以在两个连续层 之间保留输入的均值和方差
  • 参考文献

softplus

1
softplus(x)

Softplus 激活函数

  • 返回值:$log(exp(x) + 1)$

softsign

1
softsign(x)

Softsign 激活函数

  • 返回值:$x / (abs(x) + 1)$

relu

1
2
3
4
5
relu(
x,
alpha=0.0,
max_value=None
)

线性修正单元。

  • 参数

    • alpha:负数部分的斜率。默认为 0。
    • max_value:输出的最大值
  • 返回值:线性修正单元激活

    • x > 0:返回值为 x
    • x < 0:返回值为 alpha * x
    • 如果定义了max_value,则结果将截断为此值

tanh

1
tanh(x)

双曲正切激活函数

sigmoid

1
sigmoid(x)

Sigmoid 激活函数

hard_sigmoid

1
hard_sigmoid(x)

Hard sigmoid 激活函数。

  • 说明

    • 计算速度比 sigmoid 激活函数更快。
  • 返回值:Hard sigmoid 激活:

    • x < -2.5:返回 0
    • x > 2.5:返回 1
    • -2.5 <= x <= 2.5:返回 0.2 * x + 0.5

linear

1
linear(x)

线性激活函数(即不做任何改变)