元编程

1 装饰器 附加额外操作

装饰器 接受一个函数作为参数并返回一个新的函数

@timethis
def countdown(n):
    pass
    
跟像下面这样写其实效果是一样的:

def countdown(n):
    pass
countdown = timethis(countdown)

普通装饰器的定义

import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'{func.__name__} cost: { end-start}')
        return result
    return wrapper


@timethis
def foo():
    time.sleep(0.5)

foo()  # timethis(foo)()

内置的装饰器比如 @staticmethod, @classmethod, @property 原理也是一样的


class A:
    @classmethod
    def method(cls):
        pass

class B:
    # Equivalent definition of a class method
    def method(cls):
        pass
    method = classmethod(method)

带参数的装饰器


# 1

from functools import wraps
import logging

def logged(level, name=None):
    """
    Add logging to a function. level is the logging
    level, name is the logger name, and message is the
    log message. If name and message aren't specified,
    they default to the function's module and name.
    """
    def decorate(func):
        log_name = name if name else func.__module__
        log = logging.getLogger(log_name)
        @wraps(func)
        def inner(*args, **kw):
            try:
                ret = func(*args, **kw)
            except Exception:
                import traceback
                log.log(level, traceback.print_exc())
            else:
                return ret

        return inner

    return decorate


@logged(logging.WARNING)
def foo():
    1/0
    print('in')

foo()  # logged(logging.WARNING)(func)()



# 2

from functools import wraps
from functools import partial
import logging



def wrapper(level, name, func):
    log_name = name if name else func.__module__
    print(log_name)
    log = logging.getLogger(log_name)
    @wraps(func)
    def inner(*args, **kw):
        try:
            ret = func(*args, **kw)
        except Exception:
            import traceback
            log.log(level, traceback.print_exc())
        else:
            return ret

    return inner


def logged(level, name=None):
    return partial(wrapper, level, name)



@logged(logging.WARNING)
def foo():
    1/0
    print('in')

foo()  # logged(logging.WARNING)(func)()



装饰器类


import types
from functools import wraps

class Profiled:

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        ...
        print("in")
        ret = self.func(*args, **kwargs)
        return ret

    def __get__(self, instance, owner):
        """
        在装饰类方法时 
        返回绑定实例的方法 bound instance
        
        # Spam().bar = Profiled(bar) 使用__get__ 进行Spam实例  与 Profiled实例 的绑定
        """
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)



@Profiled
def add(x, y):
    return x + y


class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)


# Spam().bar = Profiled(bar) 使用__get__ 进行Spam实例  与 Profiled实例 的绑定

# 不使用描述器 会抛出错误: 

# TypeError: bar() missing 1 required positional argument: 'x'


类装饰器 扩展类的功能 (替代元类的简洁方案)

类装饰器通常可以作为其他高级技术比如混入元类的一种非常简洁的替代方案


# 获取attr
def log_attr(cls):

    origin_attr = cls.__getattribute__

    def new_attr(self, name):
        print(f"getting: {name}")
        return origin_attr(self, name)

    cls.__getattribute__ = new_attr

    return cls


@log_attr
class A:
    def __init__(self, x):
        self.x = x

    def spam(self):
        ...

a = A(1)
a.x



# 单例模式


def single_class(cls):

    cls._instance = None

    def new_(cls, *args):
        if cls._instance:
            return cls._instance
        cls._instance = cv = object.__new__(cls)
        return cv

    cls.__new__ = new_

    return cls



@single_class
class A(object):
    def __init__(self, name):
        self.name = name

a = A("dasd")
b = A("saads")

print(a is b)

使用元类继承复用


class SingleMeta(type):


    def __init__(cls, *args, **kwargs):
        print("cls")
        cls._instance = None
        super(SingleMeta, cls).__init__(*args, **kwargs)


    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            return cls._instance
        cls._instance = super(SingleMeta, cls).__call__(*args, **kwargs)

        return cls._instance

# 1 class A(object, metaclass=SingleMeta) , 元类__init__, 创建 A类对象
# 2 A(),调用 元类的 __call__ 方法, __new__一个新对象

class A(object, metaclass=SingleMeta):
    ...

a = A()
b = A()

print(a is b)

2 使用__prepare__ 返回映射对象

在开始 定义类 和它的父类的时候被执行, 返回映射对象以便在类定义体中被使用到


from collections import OrderedDict
# py3.6 默认order_dict

class Typed:

    _expected_type = type(None)

    def __init__(self, name=None):
        self._name = name


    def __set__(self, instance, value):
        if not isinstance(value, self._expected_type):
            raise TypeError(f"Expected: {str(self._expected_type)}")
        instance.__dict__[self._name] = value



class Integer(Typed):
    _expected_type = int


class Float(Typed):
    _expected_type = float


class String(Typed):
    _expected_type = str



class OrderMeta(type):
    def __new__(cls, cls_name,  bases, cls_dict):
        d = dict(cls_dict)
        order = []
        for name, value in cls_dict.items():
            if isinstance(value, Typed):
                value._name = name
                order.append(name)
        d['_order'] = order
        return type.__new__(cls, cls_name, bases, d)


    @classmethod
    def __prepare__(mcs, cls_name, bases):
        # print(cls_name, bases)
        # return OrderedDict()
        return dict()

    # __prepare__() must return a mapping, 以便在类定义体中被使用到



class Structure(metaclass=OrderMeta):
    def as_csv(self):
        return ','.join(str(getattr(self, name)) for name in self._order)



# Example use
class Stock(Structure):
    name = String()
    shares = Integer()
    price = Float()
    # 描述器, 用来__set__, 判断初始化的类型

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



s = Stock('GOOG', 100, 490.1)

print(Stock.__dict__)
# {'__module__': '__main__', 'name': <__main__.String object at 0x000002E125D3E4F0>, 
# 'shares': <__main__.Integer object at 0x000002E1279AE2E0>, 'price': <__main__.Float object at 0x000002E1279AE3A0>, '__init__': <function Stock.__init__ at 0x000002E127AE2AF0>, 
# '_order': ['name', 'shares', 'price'], '__doc__': None}

print(s._order)    # ['name', 'shares', 'price']
print(s.as_csv())  # GOOG,100,490.1


扩展,防止重复定义 cls_dict

from collections import OrderedDict

class NoDupOrderedDict(OrderedDict):
    def __init__(self, clsname):
        self.clsname = clsname
        super().__init__()
        
    def __setitem__(self, name, value):
        if name in self:
            # 防止重复定义
            raise TypeError('{} already defined in {}'.format(name, self.clsname))
        super().__setitem__(name, value)

class OrderedMeta(type):
    def __new__(cls, clsname, bases, clsdict):
        d = dict(clsdict)
        d['_order'] = [name for name in clsdict if name[0] != '_']
        return type.__new__(cls, clsname, bases, d)

    @classmethod
    def __prepare__(cls, clsname, bases):
        return NoDupOrderedDict(clsname)

3 类与元类的关键字参数应该保持一致

为了使元类支持这些关键字参数,你必须确保__prepare__() , __new__() __init__() 方法中 都使用强制关键字参数

class MyMeta(type):
    # Optional
    @classmethod
    def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
        # Custom processing
        pass
        return super().__prepare__(name, bases)

    # Required
    def __new__(cls, name, bases, ns, *, debug=False, synchronize=False):
        # Custom processing
        pass
        return super().__new__(cls, name, bases, ns)

    # Required
    def __init__(self, name, bases, ns, *, debug=False, synchronize=False):
        # Custom processing
        pass
        super().__init__(name, bases, ns)
        

class Spam(metaclass=MyMeta, debug=True, synchronize=True):
    pass
    
    
# 或者
class MyMeta(type):
    # Optional
    @classmethod
    def __prepare__(cls, name, bases):
        # Custom processing
        pass
        return super().__prepare__(name, bases)

    # Required
    def __new__(cls, name, bases, ns):
        # Custom processing
        pass
        return super().__new__(cls, name, bases, ns)

    # Required
    def __init__(self, name, bases, ns):
        # Custom processing
        pass
        super().__init__(name, bases, ns)


class Spam(metaclass=MyMeta):
    debug = True
    synchronize = True
    pass


s = Spam()

将这些属性定义为参数的好处在于它们不会污染类的名称空间, 这些属性仅仅只从属于类的创建阶段,
而不是类中的语句执行阶段。 另外,它们在 __prepare__() 方法中是可以被访问的,因为这个方法会在所有类主体执行前被执行。
 但是类变量只能在元类的 __new__() 和 __init__() 方法中可见。

4 通过定义一个元类在类上进行约束,监控类的定义

元类的一个关键特点是它允许你在定义的时候检查类的内容

class NoMixedCaseMeta(type):
    def __new__(cls, cls_name, bases, cls_dict):
        # 检查内容
        for name in cls_dict:
            if name.lower() != name:
                raise TypeError(f"Bad Attribute name: {name}")
        return super(NoMixedCaseMeta, cls).__new__(cls, cls_name, bases, cls_dict)

class Root(metaclass=NoMixedCaseMeta):
    ...

class A(Root):
    def foo_bar(self): # Ok
        ...

class B(Root):
    def fooBar(self): # TypeError
        ...

面向对象的程序中,通常将类的定义放在元类中控制是很有用的

__new__() 方法在类创建之前被调用,通常用于通过某种方式(比如通过改变类字典的内容)修改类的定义

__init__() 方法是在类被创建之后被调用,当你需要完整构建类对象的时候会很有用。

5 以函数编程的方式定义类

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


def cost(self):
    return self.shares * self.price


cls_dict = {
    '__init__': __init__,
    'cost': cost,
}

import types

Stock = types.new_class("Stock", (), {}, lambda options: options.update(cls_dict))
Stock.__module__ = __name__
# 确保这个属性也设置正确
# 每次当一个类被定义后,它的 __module__ 属性包含定义它的模块名
# 这个名字用于生成 __repr__() 方法的输出。它同样也被用于很多库

s = Stock("ac", 1, 4)
print(s.cost())
print(s)


# 想创建的类需要一个不同的元类,可以通过 types.new_class() 第三个参数传递给它
import abc 
Stock = types.new_class('Stock', (), {'metaclass': abc.ABCMeta}, lambda options: options.update(cls_dict))


# 增加参数
class Spam(Base, debug=True, typecheck=False):
    pass
    
Spam = types.new_class('Spam', (Base,),
                        {'debug': True, 'typecheck': False},
                        lambda ns: ns.update(cls_dict))

6 解析与分析Python源码 抽象语法树(AST)

ast 模块能被用来将Python源码编译成一个可被分析的抽象语法树(AST)


import ast

ex = ast.parse('2 + 3*4 + x', mode='eval')
print(ex)
dump_ret = ast.dump(ex)
print(dump_ret)

# Expression(
#   body=BinOp(
#         left=BinOp(left=Constant(value=2, kind=None), op=Add(),
#                    right=BinOp(left=Constant(value=3, kind=None), op=Mult(),
#                                right=Constant(value=4, kind=None))), op=Add(),
#        right=Name(id='x', ctx=Load())
#       )
#)


分析源码树需要你自己更多的学习,它是由一系列AST节点组成的。 分析这些节点最简单的方法就是定义一个访问者类,实现很多 visit_NodeName() 方法, NodeName() 匹配那些你感兴趣的节点。

下面是这样一个类,记录了哪些名字被加载、存储和删除的信息

import ast

class CodeAnalyzer(ast.NodeVisitor):
    def __init__(self):
        self.loaded = set()
        self.stored = set()
        self.deleted = set()

    def visit_Name(self, node):
        if isinstance(node.ctx, ast.Load):
            self.loaded.add(node.id)
        elif isinstance(node.ctx, ast.Store):
            self.stored.add(node.id)
        elif isinstance(node.ctx, ast.Del):
            self.deleted.add(node.id)

# Sample usage
if __name__ == '__main__':
    # Some Python code
    code ='''
for i in range(10):
    print(i)
del i
'''

    # Parse into an AST
    top = ast.parse(code, mode='exec')

    # Feed the AST to analyze name usage
    c = CodeAnalyzer()
    c.visit(top)
    print('Loaded:', c.loaded)
    print('Stored:', c.stored)
    print('Deleted:', c.deleted)
    
# Loaded: {'i', 'range', 'print'}
# Stored: {'i'}
# Deleted: {'i'}

7 dis反编译成低级的字节码 查看它底层的工作机制

dis 模块可以被用来输出任何Python函数的反编译结果

Buy me a 肥仔水!