元编程

1. type

通过type(对象实例的引用)我们能拿到对象实际的类型,而我们的的class可以为type类型的实例,如果调用type(class)我们回去的是一个type类型。 比如我们下面的例子:

class A(object):
    pass


a = A()
print(type(a))     # 输出: <class '__main__.A'> , 是我们自定义的类A, 这里的模块名称是__main__因为我们使用当前文件启动,否则的话模块名应该是文件名
print(type(A))     # 输出: <class 'type'> ,说明我们的class本身是```type```类型的实例
1.1 手工创建class类型

type构造函数接收3个参数: 类名、父类的元组、属性和方法的dict,通过type构造函数和我们通过class定义出来的类完全一样。 但是能运行我们动态创建。

def get_age(self, age):
    return 3 * age;


Student = type('Student', (object,), dict(get_age=get_age))
s = Student()
print(type(s))        # 输出: <class '__main__.Student'>
print(s.get_age(10))  # 输出: 30

2. metaclass

类似创建实例的模板,你可以将metaclass理解为创建类的模板,metaclass能过动态的修改引用元类(metaclass)的类绑定的属性和方法。

Python中class的默认metaclass是type,定义metaclass也是通过继承type类实现的。

让我们先可以一个入门的测试: 给引用metaclass的类自动添加一个hello()方法:

#!/usr/bin/env python
# -*-coding: UTF-8 -*-


class HelloMeta(type):

    def hello(cls):
        print("%s, %s, a HelloMeta type class" % (type(cls()), str(cls)))  # 这里拿到的是一个class对象引用cls,如果想要访问实例变量怎么做?

    def __call__(self, *args, **kwargs):
        cls = type.__call__(self, *args, **kwargs)   # 这里的self是Metaclass实例,这个例子中就是HelloMeta的实例,创建的cls是指定了Metaclass的类的实例
        setattr(cls, "hello", self.hello)           # 然后我们把metaclass的方法赋值给新加的class,后面class的实例就能调用这个方法了
        print(type(self))
        return cls


class TryHello(object, metaclass=HelloMeta):
    def greate(self):
        self.hello()


h = TryHello()
h.greate()

业务开发中metaclass可能没多大用处,但是创建各种框架都大量使用了metaclass,比如我们场景的Django、SQLAlchemy、Flask等等。

2.1 实现原理

之前我们已经了解到实例的type是class,class的type是type,甚至type本身的type都是type自己。

>>> class Student(object):
...   pass
...

>>> s = Student()
>>> type(s)
<class '__main__.Student'>

>>> type(Student)
<class 'type'>

>>> type(type(Student))
<class 'type'>

在Python3中定义新的class时, 可以指定一个metaclass, 这个metaclass的值默认是type。 我们可以复写type里的特殊方法来自定义类的生成规则:

方法 说明 参数
__prepare__ _new__的attr字段的默认初始化值 接口定义:
@classmethod
def prepare(mcls, name, bases):
    return odict()
__new__ 用于创建新类的实例 def __new__(mcls, name:str, bases:tuple, attr, **kwargs)
mcls: 元类的实例引用
name:新类的类名
bases:新类的父类,tuple里每个元素都是class实例的引用
attr:类的名称空间,包括类的所有属性,由__prepare__返回

通常我们只修改参数,继续传递给父类__new__创建类实例:
return super(Final, cls).__new__(mcls, name, bases, attr)
__init__ 通过__new__创建新类的实例后,传递给该方法做初始化 def __init__(cls, name, bases, attrs, **kwargs)
cls: 新类的class对象的引用
name:新类名称
bases:新类父类tuple
attrs:新类名称空间,包括各种属性和方法
通常我们只需要修改name、bases、attr,调用父类方法:
def __init__(self, name, bases, attrs, **kwargs):
    super(Final, self).__init__(name, bases, attrs)
init方法没有返回值
__call__ 新类构造函数调用之前调用 接口定义:
def call(cls, *args, **kw)
修改参数、缓存逻辑,在必要的情况下调用父类的构造器完成初始化。
2.2 decorators实现AOP

在metaclass出现之前,我们都是通过decorators修改函数的行为的。 下面是一个decorators的使用实例,使用类似于Java里的AOP,但是会比AOP要强大、简单的多 :

from functools import wraps


def logs(fn):
    @wraps(fn)                               
    # 为了记住fn的名字,如果没有这一行新的函数通过__name__放函数名返回的是wrapper
    def wrapper(*args, **kwargs):
        print("log: %s, %s" % (str(args), str(kwargs)))
        return fn(*args, **kwargs)

    return wrapper


@logs
def test(a, b, c):
    return a + b + c


class A(object):
    @logs
    def sayHello(self, name):
        return "hello %s" % (name,)


print(test(1, 2, 3))
print(test.__name__)

a = A()
print(a.sayHello("zhangsan"))
2.3 metaclass实现decorators

decorators从某种程度上说已经足够好用了,但是假设我们有一个类,类下有多个方法,我想对所有方法都做函数转换,这个时候在每个方法上去加@log就略显麻烦了,这个时候metaclass的优势就体现出来了。

我们可以拷到decorators的log方法可以不加修改的继续使用,通过复写__new__我们筛选出所有的types.FunctionTypetypes.MethodType,将对应方法封装为新方法:

from functools import wraps
import types


def logs(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        print("log: %s, %s" % (str(args), str(kwargs)))
        return fn(*args, **kwargs)

    return wrapper


class LogMetaclass(type):

    # def __new__(cls, *args, **kwargs):
    def __new__(cls, name, parents, attrs, *args, **kwargs):                
    # cls: Metaclass的类, name: 新类名称, parents: 新类的父类tuple, attrs: 新类的属性和方法dict
        for k, v in attrs.items():
            if type(v) is types.FunctionType or type(v) is types.MethodType:
                attrs[k] = logs(v)

        return super(LogMetaclass, cls).__new__(cls, name, parents, attrs)  # 所有不影响的属性继续传递个父类(type类本身)的__new__方法创建


class TryHello(object, metaclass=LogMetaclass):

    def say_hello(self, name):
        print("hello %s" % (name,))


h = TryHello()
h.say_hello("zhangsan")
2.4 metaclass禁止继承

通过type(class)返回的是metaclass信息,因为多数类不指定metaclass,默认值是```type``。

我们通过定义元类Final,在Dog类初始化时,通过bases我们会拿到它的父类Animal,而Animal标记了使用metaclass=Final,通type(Animal)我们获取到Final,这个时候raise RuntimeException,做到了任何父类以Final做为metaclass的情况下都不创建class

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Final(type):
    def __new__(cls, name, bases, attr):
        # Final cannot be subclassed
        # check that a Final class has not been passed as a base
        # if so, raise error, else, create the new class with Final attributes
        type_arr = [type(x) for x in bases]
        for i in type_arr:
            if i is Final:
                raise RuntimeError("You cannot subclass a Final class")
        return super(Final, cls).__new__(cls, name, bases, attr)

class Animal(metaclass=Final):
    pass

class Dog(Animal):
    pass

a = Animal()
d = Dog()
2.5 实现class单例模式
#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Singleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if not cls.instance:              # 拦截构造器调用,如果实例存在的话直接返回
             cls.instance = super(Singleton, cls).__call__(*args, **kw)
        return cls.instance

class ASingleton(object, metaclass=Singleton):
    # __metaclass__ = Singleton
    pass

a = ASingleton()
b = ASingleton()
print(a is b)                             # 这里输出是True
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335