元类编程

你没有听过那句话,说其实这个世界上有20000个女孩是你会一见钟情的,只是很多人的一生中连她们中的一个都遇不到。
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。”  —— Python界的领袖 Tim Peters

想要python中创建一个了类使用class关键词即可创建,并且在python中一切皆对象,类也是一个对象。你创建类的目的就是为了创建类的实例对象,然后调用这些对象,实例化的对象是被类创建的,类是被元类创建的。而type就是Python在背后用来创建所有类的元类。

就好比你知道了类其实是能够创建出类实例的对象,事实上,类本身也是实例,当然,它们是元类的实例。

getattr与getattribute 魔法函数

这两者都是获取类的属性时候执行,但是稍有区别:

__getattr__:在查找不到属性的时候调用
__getattribute__:更加高级,无条件优先进入getattribute魔法函数

getattr代码举例:

class magic:
    def run(self):
        return ('666')
    def __getattr__(self, item):
        return ('magic类无{}属性'.format(item))
a = magic()
print(a.run())
print(a.runxxx)

返回结果:

666
magic类无runxxx属性

当然他更加优秀的用法在于:

class magic:
    def __init__(self,info):
        self.info = info
    def __getattr__(self, item):
        return (self.info[item])
a = magic({'user':'浪子','age':18})
print(a.age)

返回结果:

18

这样可以更加方便的设计类的结构功能。

getattribute代码举例

class magic:
    def __init__(self,info):
        self.info = info
    def __getattribute__(self, item):
        return '你想让我干啥子嘛'

a = magic({'user':'浪子','age':18})
print(a.age)
print(a.ageafa)
print(a.ag124125ad)

返回结果:

你想让我干啥子嘛
你想让我干啥子嘛
你想让我干啥子嘛

梳理:getattr是当类调用一个不存在的属性时才会调用getattr魔法函数,他传入的值item就是你这个调用的不存在的值。
getattribute则是无条件的优先执行,所以如果不是特殊情况最好不要用getattribute。

属性描述符

属性描述符是一个强大的通用协议。它是properties, methods, static methods, class methods 和super()的调用原理。它贯穿整个Python,并且用来实现2.2版本中引进的新式类。属性描述符简化了底层的C代码,还为日常Python编程提供了新的工具集。

属性描述符协议

属性描述符是实现了特定协议的类,只要实现了__get__,__set__和__delete__三个方法中的任意一个,这个类就是描述符,它能实现对多个属性运用相同存取逻辑的一种方式,通俗来说就是:创建一个实例,作为另一个类的类属性。

代码说明:

class magic:
    def __get__(self, instance, owner):
        # 用于访问属性。它返回属性的值,或者在所请求的属性不存在的情况下出现 AttributeError 异常。
        # owner参数,它存储的是托管类的引用
        pass
    def __set__(self, instance, value):
        # 将在属性分配操作中调用。不会返回任何内容。
        pass
    def __delete__(self, instance):
        # 控制删除操作。不会返回内容。
        pass
    # tips:需要注意,描述符被分配给一个类,而不是实例。修改此类,会覆盖或删除描述符本身,而不是触发它的代码。

使用类方法创建描述符

# -*- coding:utf-8 -*-
class magic:
    def __init__(self):
        self.user = '浪子'
    def __get__(self, instance, owner):
        # 用于访问属性。它返回属性的值,或者在所请求的属性不存在的情况下出现 AttributeError 异常。
        print('正在进行【获取】属性,内容为:{}'.format(owner))
        return self.user + '真帅'
    def __set__(self, instance, value):
        # 将在属性分配操作中调用。不会返回任何内容。
        print('正在进行【设置】属性,内容为:{}'.format(value))
        self.user = value
    def __delete__(self, instance):
        # 控制删除操作。不会返回内容。
        print('正在进行【删除】属性')
        del self.user
    # tips:需要注意,描述符被分配给一个类,而不是实例。修改此类,会覆盖或删除描述符本身,而不是触发它的代码。

class person:
    person = magic()

user = person()
print(user.person)
print('*'*25)
user.person = '小桃红'
print('*'*25)
print(user.person)
print('*'*25)
del user.person

返回结果:

正在进行【获取】属性,内容为:<class '__main__.person'>
浪子真帅
*************************
正在进行【设置】属性,内容为:小桃红
*************************
正在进行【获取】属性,内容为:<class '__main__.person'>
小桃红真帅
*************************
正在进行【删除】属性

这种方法是通过类方法创建描述符,通过定义magic类(magic类因为使用了get,set,delete方法,就变成了一个描述符),然后另一个类使用这个描述符。

使用属性类型创建描述符

除了使用类当作一个属性描述符,通过使用 property(),可以轻松地为任意属性创建可用的描述符。创建 property() 的语法是 property(fget=None, fset=None, fdel=None, doc=None),其中:

fget:属性获取方法
fset:属性设置方法
fdel:属性删除方法
doc:docstring

通过代码演示:

class magic:
    def __init__(self):
        self.user = '浪子'
    def fget(self):
        print('正在进行【获取】属性,内容为:{}'.format(self.user))
        return self.user + '真帅'
    def fset(self,value):
        print('正在进行【设置】属性,内容为:{}'.format(value))
        self.user = value
    def fdel(self):
        print('正在进行【删除】属性')
        del self.user
    name = property(fget, fset, fdel,'我也能实现属性描述符,6666666')

user = magic()
print(user.user)
print('-------------------')
print(user.name)
print('-------------------')
user.name = '小桃红'
print('-------------------')
print(user.user)
print(user.name)

返回结果:

浪子
-------------------
正在进行【获取】属性,内容为:浪子
浪子真帅
-------------------
正在进行【设置】属性,内容为:小桃红
-------------------
小桃红
正在进行【获取】属性,内容为:小桃红
小桃红真帅

注意,fget、fset 和 fdel 方法是可选的,但是如果没有指定这些方法,那么将在尝试各个操作时出现一个 AttributeError 异常。例如,声明 name 属性时,fset 被设置为 None,然后开发人员尝试向 name 属性分配值。这时将出现一个 AttributeError 异常。

使用属性修饰符创建描述符

property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性,他常用在对类的属性修改检查,比如你修改类的属性的时候,会对你传入的值进行检查。

class magic:
    def __init__(self):
        self._user = ''
    # 接受传入参数

    @property
    def login(self):
        return '用户:{}登陆成功,欢迎您!'.format(self._user)

    # 这里返回的是一个字符串,使用property装饰后,这个函数就代表这个字符串
    # 具体:使用property后,这个login函数调用方式就变成了
    # m = magic()
    # print(m.login) # 这里不能有m.login()括号
    # >> 用户:登陆成功,欢迎您!

    # 一旦login函数被property装饰,就拥有可读的属性 >> print(m.login)
    # 但是不能修改


    @login.setter
    # 如果想要拥有可修改的功能,就需要@login.setter装饰
    # 被装饰的checks函数,具有可以写的功能
    def checks(self,value):
        if value == '浪子':
            self._user = value
        else:
            raise ValueError('用户:{} 不许登陆'.format(value))
        self._user = value

if __name__ == '__main__':
    m = magic()
    # 实例化对象
    m.checks = '浪子'
    # 传入值
    print(m.login)
    # 打印值

返回结果:

用户:浪子登陆成功,欢迎您!

使用属性修饰符创建描述符其实就是通过@property装饰函数,然后使用.setter与.deleter方法进行设置。

覆盖型与非覆盖型描述符

Python存取属性的方式并不是对等的:通过实例读取属性时,通常返回的是实例中定义的属性,如果没有这个属性,再到所属的类中去找;但为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类。

这种不对等也影响到了描述符。根据是否定义__set__方法,描述符被分成了两大类:定义了__set__方法的描述符是覆盖型描述符,否则是费覆盖型描述符。可以分为以下三种情况(再次提醒,描述符是类属性):

如果描述符实现了__get__和__set__方法,描述符会覆盖同名实例属性,即属性的存取值过程都会被描述符接管。这说得通,毕竟两个方法都定义了;
如果描述符只实现了__set__方法,描述符“半覆盖”同名实例属性,即存值过程会被接管,而取值过程不会被接管。这也说得通,毕竟没有定义__get__方法;
如果描述符只实现了__get__方法,描述符不会覆盖同名实例属性,即存取值过程都不会被接管!这就蹊跷了,明明定义了__get__方法,但它不起作用。

如果一个对象同时定义了__get__和__set__方法,它被称做数据描述符(data descriptor)。只定义__get__方法的对象则被称为非数据描述符(non-data descriptor,一般用在函数方法上,其他用法也是可能的)。
数据和非数据描述符的区别在于如果某个实例属性字典中有项和描述符同名,那么属性访问的优先级是不同的。数据描述符的优先级比实例字典中项的高,非数据描述符则相反。

属性的查找过程

通过属性描述符的介绍,大致明白了该属性值的获取,修改方法,那么在python中,是如何查找到属性的呢?通过例子代码进行剖析解答:

class magic:
    # 定义一个属性描述符的类
    def __init__(self):
        self.user = '浪子'
    # 初始化,当前用户名为 浪子
    def __get__(self, instance, owner):
    # 属性描述符 之 【获取】
        return self.user
    def __set__(self, instance, value):
    # 属性描述符 之 【设置】
        self.user=value + '大帅哥'

查找过程:

运行前提:首先调用__getattribute魔法函数__,如果定义了__getattr__魔法函数,那么在__getattribute__魔法函数抛出异常的时候,就会直接调用__getattr__魔法函数。对于属性描符__get__的调用是发生在__getattribute__内部的

代码解析(按执行优先级排名):

1.如果’person’是出现在类Person的__dict_\中,并且’person’是data descriptor,那么调用属性描述符定义的__get__方法。(如上代码。’person’出现在Person的__dict__中,并且magic()中有定义get与set方法,属于data descriptor,优先调用自己定义好的方法获取属性)

比如:

class Person:
    person = magic()

p = Person()
p.person = '超级无敌浪子'
print(p.person)

返回结果:

超级无敌浪子大帅哥

2.如果’person’出现在对象p的dict中,那么直接返回p.dict[‘person’]

比如:

class Person:
    person = '1'

p = Person()
print(p.person)

返回结果:

1

3.如果’person’出现在类Person的dict中,并且’person’是non data descriptor,就调用其get方法,如果’person’没出现在类Person的dict中否则返回 dict[‘person’]

第一种情况:

class magic:
    def __init__(self):
        self.user = '浪子'
    def __get__(self, instance, owner):
        return self.user


class Person:
    person = magic()

p = Person()
print(p.person)

返回结果:

浪子

第二种情况:

class Person:
    person = magic()

p = Person()
p.personsssssss = '超级无敌浪子子子子子子子子子子'
print(p.personsssssss)

返回结果:

超级无敌浪子子子子子子子子子子

4.如果类Person有getattr方法,则调用getattr方法,否则抛出AttributeError。

比如:

class Person:
    def __getattr__(self, item):
        return ('不管你多牛逼,反正要调用老子')
    person = magic()

p = Person()
print(p.personsssssss)

返回结果:

不管你多牛逼,反正要调用老子

NEW 与 INIT 区别

1. __new__
2. __init__

说到init你会想到创建类的时候实例的对象,但是new是啥?

依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。

是不是感觉有点迷?没关系,用例子来说明就好了。

class magic(object):
    def __init__(self,nums):
        self.nums = nums
        print '666'
        print 'init:' + self.nums

    def __new__(cls, nums):
        cls.nums = nums
        print 'new:'+cls.nums

a = magic('A')

返回结果:

new:A

这里看到只输出了new的对象,并且注意magic类继承了object,在上一篇文章中有说道,元类(即类的类)都必须要继承自object,因为记住了,继承自object的新类才有__new__

重新修改一下代码,让init也执行下呢?

class magic(object):
    def __init__(self,nums):
        self.nums = nums
        print '666'
        print 'init:' + self.nums

    def __new__(cls, nums):
        cls.nums = nums
        print 'new:'+cls.nums
        # 执行到这里会打印内容
        return object.__new__(cls)
        # 这里会返回一个内容
        # 返回的内容会传递到init中的self中去
a = magic('A')

返回结果:

new:A
666
init:A

大家注意看,这里返回了object的new方法,然后init就执行了,这证明上面说的,new会先于init执行,并且new方法返回的值就是init方法中的self。

继续举例子:

class magic(object):
    def __init__(self,nums):
        self.nums = self.nums
        self.langzi = 'langzi'
        print '666'
        print 'init:' + self.nums

    def __new__(cls, nums):
        print nums
        # 这里打印出传递进来的数值
        cls.nums = nums+'BCDEFG'
        cls.langzi = '浪子'
        print 'new:'+cls.nums
        return object.__new__(cls)


a = magic('A')
print '-'*10
print a.langzi
print a.nums

先猜一猜会输出什么呢?

返回结果:

A
new:ABCDEFG
666
init:ABCDEFG
----------
langzi
ABCDEFG

来分析一下

  1. 打印传递进来的A
  2. 随后赋值给cls.nums,打印new:ABCDEFG,同时定义cls.langzi=浪子
  3. 继续执行,new方法返回object对象,这个时候会执行到init
  4. 这里的self其实就是new中返回的对象
  5. 然后定义self.nums和self.langzi=langzi(这个时候这个类的langzi对象就变成了langzi,不再是浪子)
  6. 打印666
  7. 打印出init:ABCDEFG
  8. 打印—————-
  9. 验证这个类中的属性值a.langzi和a.nums

通过分析这个实例步骤,来进一步研究new和init的关系:

通过上面代码的执行结果我们可以发现程序首先执行了new,之后执行的init,这说明,在类中,如果newinit同时存在会优先调用new

new方法会返回所构造的对象,init则不会。init无返回值。

new至少要有一个参数cls,代表要实例化的类(类对象),此参数在实例化时由Python解释器自动提供。

new必须要有返回值,返回实例化出来的实例,这点在自己实现,new时要特别注意,可以return父类new出来的实例或者直接是object的new出来的实例。

init有一个参数self,就是这个new返回的实例,initnew的基础上可以完成一些其它初始化的动作,init不需要返回值

我们可以将类比作制造商,new方法就是前期的原材料购买环节,init方法就是在有原材料的基础上,加工,初始化商品环节

总而言之就是:

  1. new:创建对象时调用,会返回当前对象的一个实例,new是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法,常用于允许继承不可变类型(str,int, tuple)
  2. init:创建完对象后调用,对当前对象的一些实例初始化,无返回值,init是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
  3. new是控制对象的生成过程,init是完善对象,如果new方法不返回对象,则不会调用init魔法函数。

下面这段代码输出什么?

class B(object):
    def fn(self):
        print 'B fn'
    def __init__(self):
        print "B INIT"


class A(object):
    def fn(self):
        print 'A fn'

    def __new__(cls,a):
            print "NEW", a
            if a>10:
                return super(A, cls).__new__(cls)
            return B()

    def __init__(self,a):
        print "INIT", a

a1 = A(5)
a1.fn()
a2=A(20)
a2.fn()

返回结果:

NEW 5
B INIT
B fn
NEW 20
INIT 20
A fn

使用new方法,可以决定返回那个对象,也就是创建对象之前,这个可以用于设计模式的单例、工厂模式。init是创建对象是调用的。

元类

python中一切皆对象,类也是对象,由type创建类。当我们使用class定义对象的时候,解释器内部会帮我们调用type()函数,完成创建对象工作。为什么平时我们定义对象大多是采用class,而不采用type()函数呢?因为type()函数一点都不优美,看上去缺少整体性,代码不易读,写起来也很麻烦。

比如我使用class实现一个类

class magic(object):
    def __init__(self):
        self.name = '浪子'
    def say(self):
        return '浪子66666666'
m = magic()
print(m.name)
print(m.say())
print(m)

返回结果:

浪子
浪子66666666
<__main__.magic object at 0x0000012E49C288D0>

使用type动态生成一个类

def say(*args):
    return '浪子66666666'
magic = type('随便起个名',(object,),{'name':'浪子','say':say})
# 这一行非常重要
# type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
m = magic()
print(m.name)
print(m.say())
print(m)

返回结果:

浪子
浪子66666666
<__main__.随便起个名 object at 0x0000012E49DCE438>

元类就是创建类的类,凡是继承了type的类,都是元类,python中类的实例化过程中,首先会在这个类中寻找是否继承Metaclass,如果找到了就通过Metaclass创建类,如果找不到才会通过type创建类。元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。这个类的功能可以控制其他类实例化的过程,这些概念比较绕,看代码就明白了:

class Meta(type):
    # 随便起一个类,继承type
    # 这个类就是元类
    # Magic类继承的metaclass=Meta
    # Magic实例化过程由Meta来实现
    def __new__(cls, *args,**kwargs):
        print('浪子6666666')
        # 你大可在这里做一些判断之类
        return super().__new__(cls,*args,**kwargs)


class Magic(metaclass=Meta):
    # 继承Meta,Meta控制Magic实例化的过程
    def __init__(self):
        pass
m = Magic()
print(m)

返回结果:

浪子6666666
<__main__.Magic object at 0x000002575420E518>

在该类并定义的时候,它还没有在内存中生成,知道它被调用。Python做了如下的操作:

  1. Magic中有metaclass这个属性吗?如果是,Python会在内存中通过metaclass创建一个名字为Magic的类对象
  2. 如果Python没有找到metaclass,它会继续在父类中寻找metaclass属性,并尝试做和前面同样的操作。
  3. 如果Python在任何父类中都找不到metaclass,它就会在模块层次中去寻找metaclass,并尝试做同样的操作。
  4. 如果还是找不到metaclass,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在metaclass中放置些什么代码呢?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东西都可以。
s

上面的代码仅仅演示元类创建类的过程,创建过程由Meta接管创建,下面的内容都会基于一个这样的metaclass:它为要创建的类自动添加一个属性 user。这个 metalcss 没有任何实际的意义,但可以帮助理解 metaclss 。

使用new方法实现:

class MyMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 这里必须有4个参数
        print('传入的类:'+name)
        print('传入的类继承的父类:'+str(bases))
        print('传入类的属性:'+str(attrs))
        attrs['user'] = "浪子"
        # 传入类的属性中 新加一个user
        return super().__new__(cls, name, bases, attrs)

class Foo(object,metaclass=MyMetaclass):
    pass

foo = Foo()
print(foo.user)

返回结果:

传入的类:Foo
传入的类继承的父类:(<class 'object'>,)
传入类的属性:{'__module__': '__main__', '__qualname__': 'Foo'}
浪子

当然你可以用init方法实现:

class MyMetaclass(type):
    def __init__(cls, nameee, basesss, attrs):
        cls.user = '浪子'
        print(basesss)
        super().__init__(nameee, basesss, attrs)
class Foo(object,metaclass=MyMetaclass):
    pass

foo = Foo()
print(foo.user)

返回结果:

(<class 'object'>,)
浪子

参考链接 1

参考链接 2

参考链接 3

参考链接 4

参考链接 5

参考链接 6

坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%