python——类的装饰器/元类

一、类的装饰器

装饰器并不是在调用该函数/对象的时候调用的,而是在代码编译的过程中已经返回函数/对象,当改函数/对象被调用的时候,调用的是已经返回的函数/对象

类作为一个对象,也可以被装饰。

——通过给类添加装饰器的方式,给类增加类属性

def wrap(obj):
    print("装饰器-----")
    obj.x = 1
    obj.y = 3
    obj.z = 5
    return obj
​
@wrap  #将Foo类作为一个参数传入装饰器函数wrap,返回同时返回该对象,把新对象重新命名为Foo
#即 Foo = wrap(Foo)
class Foo:
    pass

print(Foo.__dict__) #输出结果可以看到,新的Foo类新增了x,y,z属性
>>> 装饰器-----
>>> {'__module__': '__main__', '__init__': <function Foo3.__init__ at 0x0000029C089ABBF8>, '__dict__': <attribute '__dict__' of 'Foo3' objects>, '__weakref__': <attribute '__weakref__' of 'Foo3' objects>, '__doc__': None, 'x': 1, 'y': 3, 'z': 5}
​

函数可以作为一个对象,也有__dict__方法

def wrap(obj):
    print("装饰器-----")
    obj.x = 1
    obj.y = 3
    obj.z = 5
    return obj
​
@wrap #test = wrap(test)
def test():
    print("test-----")
test.x = 10  #test的x属性被重新赋值
print(test.__dict__) #输出结果可以看到,test作为一个函数也有__dict__方法,
# 新的test函数新增了x,y,z属性

>>>{'x': 10, 'y': 3, 'z': 5}

类的装饰器应用——通过给类加带参数的装饰器,给类增加一个类属性,该类属性是描述符类的实例对象

class Type:
​
    def __init__(self,key,except_type):  #People对象的key,和期望的数据类型
        self.key = key
        self.except_type = except_type
​
    def __get__(self, instance, owner):
        return isinstance.__dict__[self.key]
​
    def __set__(self, instance, value):
        print("instance---",instance)
        if not isinstance(value,self.except_type):
            print("您输入的类型不是%s"%self.except_type)
            raise TypeError
        instance.__dict__[self.key] = value
​
    def __delete__(self, instance):
        isinstance.__dict__.pop(self.key)
​
def deco(**kwargs):       ##这个装饰器的作用相当于给类添加了name=Type('name',str) age=Type('age',int)
    def wrapper(obj):      #类的装饰器
        for key,val in kwargs.items():
            setattr(obj,key,Type(key,val))  #设置people类对象的每个参数的描述符
        return obj
    return wrapper
​
@deco(name=str,age=int)
class People:
​
    def __init__(self,name,age):
        self.name = name
        self.age = age
​
​
if __name__=='__main__':
    print('start1')
    p = People("nick", 18)
    print(p.__dict__)
    print('start2')
    p1 = People("jerry", 20)
    print(p1.__dict__)
    print(p.__dict__)

#输出
______
start1
instance--- <__main__.People object at 0x000001BCBE39FD30>
instance--- <__main__.People object at 0x000001BCBE39FD30>
{'name': 'nick', 'age': 18}
start2
instance--- <__main__.People object at 0x000001BCBE39FB38>
instance--- <__main__.People object at 0x000001BCBE39FB38>
{'name': 'jerry', 'age': 20}
{'name': 'nick', 'age': 18}

二、自定义property

装饰器也可以是一个类,在自定义property中要使用一个类作为装饰器

class LazyProperty:

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

    def __get__(self, instance, owner):
        print("执行__get__")
        if not instance:  # 如果是用原类.属性来调用,,这时instance(对象)值为None,直接返回描述符对象
            return self
        res = self.func(instance)  # 执行传入的函数属性,并把原对象作为参数传入
        return res

class Room:

    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width

    @LazyProperty  # 这里相当于执行了area  = LazyProperty(area),这里的azyProperty(area)其实是非数据描述符,相当于给这个类新增了一个类属性,该类属性是数据描述符类LazyProperty的实例对象
    # 新的area已经是经过类LazyProperty装饰过的函数地址
    def area(self):
        return self.length * self.width

if __name__=='__main__':

    print(Room.__dict__)
    r = Room("nick", 18, 10)
    print(r.area)
    print(r.__dict__)
    print(Room.__dict__)

输出:
{'__module__': '__main__', '__init__': <function Room.__init__ at 0x0000024858FF6048>, 'area': <__main__.LazyProperty object at 0x0000024858F7F6A0>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}
执行__get__
180
{'name': 'nick', 'length': 18, 'width': 10}
{'__module__': '__main__', '__init__': <function Room.__init__ at 0x0000024858FF6048>, 'area': <__main__.LazyProperty object at 0x0000024858F7F6A0>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}
class LazyProperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        print("执行__get__")
        if not instance:  # 如果是用原类.属性来调用,这时instance(对象)值为None,直接返回描述符对象
            return self

        value = self.func(instance)  # 执行传入的函数属性,并把原对象作为参数传入
        setattr(instance,self.func.__name__,value) #将每次调用的函数属性名字和值存入对象的__dict__,相当于给instace(实例)新增实例属性area,由于LazyProperty是个非数据描述类,所有实例属性优先级更高
        # self.func.__name__是获取被调用函数属性的名字
        return value


class Room:
   def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width
    @LazyProperty  # 这里相当于执行了area  = LazyProperty(area),这里的azyProperty(area)其实是非数据描述符,
    # 新的area已经是经过类LazyProperty装饰过的函数地址
    def area(self):
        return self.length * self.width

r = Room("nick", 18, 10)
print(r.__dict__)
print(r.area)
print(r.area)   #这里将不再调用__get__

setattr(instance,self.func.__name__,value) #将每次调用的函数属性名字和值存入对象的__dict__,相当于给instace(实例)新增实例属性area,由于LazyProperty是个非数据描述类,所有实例属性优先级更高。当第二次调用area时,调用实例属性而不是非数据描述类的__get__

三、property补充

一个静态属性property本质就是实现了get,set,delete三种方法

用语法糖可以实现property的类似属性的设置和删除,与一般的属性设置删除没有区别

class People:
​
    def __init__(self):
        self.study = "8h"
​
    @property
    def study(self):
        print("获取study,执行描述符的__get__方法")
        return self.val
        # return self.study  #无线递归
​
    @study.setter
    def study(self,value):
        print("执行__set__方法")
        self.val = value
​
    @study.deleter
    def study(self):
        print("执行__delete__方法")
        del self.val
​
p = People()
print(p.study)  #获取对象的study属性 ,self.study实际上是存在self.val里
p.study = "10h"  #执行property描述符的__set__方法,设置对象的属性,
print(p.__dict__)
del p.study  #执行property描述符的__delete_方法,删除对象的属性
print(p.__dict__)

具体也可直接看https://blog.csdn.net/zangba9624/article/details/106249735

四、元类

 eval函数


函数的作用:

计算指定表达式的值。也就是说它要执行的Python代码只能是单个运算表达式(注意eval不支持任意形式的赋值操作),而不能是复杂的代码逻辑,这一点和lambda表达式比较相似。

函数定义:

eval(expression, globals=None, locals=None)

参数说明:

  • expression:必选参数,可以是字符串,也可以是一个任意的code对象实例(可以通过compile函数创建)。如果它是一个字符串,它会被当作一个(使用globals和locals参数作为全局和本地命名空间的)Python表达式进行分析和解释。
  • globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。
  • locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。如果该参数被忽略,那么它将会取与globals相同的值。
  • 如果globals与locals都被忽略,那么它们将取eval()函数被调用环境下的全局命名空间和局部命名空间

返回值:

  • 如果expression是一个code对象,且创建该code对象时,compile函数的mode参数是'exec',那么eval()函数的返回值是None;
  • 否则,如果expression是一个输出语句,如print(),则eval()返回结果为None;
  • 否则,expression表达式的结果就是eval()函数的返回值;

实例:

x = 10

def func():
    y = 20
    a = eval('x + y')
    print('a: ', a)
    b = eval('x + y', {'x': 1, 'y': 2})
    print('b: ', b)
    c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('c: ', c)
    d = eval('print(x, y)')
    print('d: ', d)

func()

输出结果:

a:  30
b:  3
c:  4
10 20
d:  None

对输出结果的解释:

  • 对于变量a,eval函数的globals和locals参数都被忽略了,因此变量x和变量y都取得的是eval函数被调用环境下的作用域中的变量值,即:x = 10, y = 20,a = x + y = 30
  • 对于变量b,eval函数只提供了globals参数而忽略了locals参数,因此locals会取globals参数的值,即:x = 1, y = 2,b = x + y = 3
  • 对于变量c,eval函数的globals参数和locals都被提供了,那么eval函数会先从全部作用域globals中找到变量x, 从局部作用域locals中找到变量y,即:x = 1, y = 3, c = x + y = 4
  • 对于变量d,因为print()函数不是一个计算表达式,没有计算结果,因此返回值为None

exec()函数

exec 执行储存在字符串或文件中的 Python 语句,相比于 eval,exec可以执行更复杂的 Python 代码。

exec(object[, globals[, locals]])

参数

  • object:必选参数,表示需要被指定的 Python 代码。它必须是字符串或 code 对象。如果 object 是一个字符串,该字符串会先被解析为一组 Python 语句,然后再执行(除非发生语法错误)。如果 object 是一个 code 对象,那么它只是被简单的执行。
  • globals:可选参数,表示全局命名空间(存放全局变量),如果被提供,则必须是一个字典对象。
  • locals:可选参数,表示当前局部命名空间(存放局部变量),如果被提供,可以是任何映射对象。

返回值:

exec函数的返回值永远为None.

需要说明的是在Python 2中exec不是函数,而是一个内置语句(statement),但是Python 2中有一个execfile()函数。可以理解为Python 3把exec这个statement和execfile()函数的功能够整合到一个新的exec()函数中去了:

eval()函数与exec()函数的区别:

  • eval()函数只能计算单个表达式的值,而exec()函数可以动态运行代码段。
  • eval()函数可以有返回值,而exec()函数返回值永远为None。
x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
def func():
    y = 20
    exec(expr)
    exec(expr, {'x': 1, 'y': 2})
    exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    
func()
60
33
34

对输出结果的解释:

前两个输出跟上面解释的eval函数执行过程一样,不做过多解释。关于最后一个数字34,我们可以看出是:x = 1, y = 3是没有疑问的。关于z为什么还是30而不是4,这其实也很简单,我们只需要在理一下代码执行过程就可以了,其执行过程相当于:

x = 1
y = 2

def func():
    y = 3
    z = 4
    
    z = 30
    sum = x + y + z
    print(sum)

func()

理解类也是对象


在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立

但是,Python中的类还远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。下面的代码段:

class ObjectCreator(object):
     pass

将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作:
你可以将它赋值给一个变量, 你可以拷贝它, 你可以为它增加属性, 你可以将它作为函数参数进行传递。

动态地创建类


1、通过return class动态的构建需要的类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。

def choose_class(name):
    if name == 'foo':
        class Foo(object):
            pass
        return Foo     # 返回的是类,不是类的实例
    else:
        class Bar(object):
            pass
        return Bar
MyClass = choose_class('foo')

print MyClass              # 函数返回的是类,不是类的实例
#输出:<class '__main__.Foo'>

print MyClass()            # 你可以通过这个类创建类实例,也就是对象
#输出:<__main__.Foo object at 0x1085ed950

(2)通过type函数构造类

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:

print type(1)
#输出:<type 'int'>
print type("1")
#输出:<type 'str'>
print type(ObjectCreator)
#输出:<type 'type'>
print type(ObjectCreator())
#输出:<class '__main__.ObjectCreator'>

这里,type有一种完全不同的能力,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)准备工作:

创建类主要分为三部分

  a 类名

  b 类的父类

  c 类体

type的语法:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

class MyShinyClass(object):
    pass
MyShinyClass = type('MyShinyClass', (), {})  # 返回一个类对象

接下来我们通过一个具体的例子看看type是如何创建类的,范例:

1、构建Foo类
#构建目标代码
class Foo(object):
    bar = True
#使用type构建
Foo = type('Foo', (), {'bar':True})

2.继承Foo类
#构建目标代码:
class FooChild(Foo):
    pass
#使用type构建
FooChild = type('FooChild', (Foo,),{})

print FooChild
#输出:<class '__main__.FooChild'>
print FooChild.bar   # bar属性是由Foo继承而来
#输出:True

3.为Foochild类增加方法
def echo_bar(self):
    print self.bar

FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
hasattr(Foo, 'echo_bar')
#输出:False
hasattr(FooChild, 'echo_bar')
#输出:True
my_foo = FooChild()
my_foo.echo_bar()
#输出:True

什么是元类

在python中,一切皆对象,一般的类也是一个类的对象,即这种起源、开始的类就称作元类。

在Python当中万物皆对象,我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类

元类可以简称为类的类,元类的主要目的是为了控制类的创建行为.

MyClass = MetaClass()    #元类创建
MyObject = MyClass()     #类创建实例
实际上MyClass就是通过type()来创创建出MyClass类,它是type()类的一个实例;同时MyClass本身也是类,也可以创建出自己的实例,这里就是MyObject

type是Python的一个内建元类,用来直接控制生成类,在python当中任何class定义的类其实都是type类实例化的结果。只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类,自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程.

type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。

age = 35
age.__class__
#输出:<type 'int'>
name = 'bob'
name.__class__
#输出:<type 'str'>
def foo(): pass
foo.__class__
#输出:<type 'function'>
class Bar(object): pass
b = Bar()
b.__class__
#输出:<class '__main__.Bar'>

对于任何一个__class__的__class__属性又是什么呢?
a.__class__.__class__
#输出:<type 'type'>
age.__class__.__class__
#输出:<type 'type'>
foo.__class__.__class__
#输出:<type 'type'>
b.__class__.__class__
#输出:<type 'type'>

因此,元类就是创建类这种对象的东西, type就是Python的内建元类,当然了,你也可以创建自己的元类。

__class__

用于获取当前对象的类定义,其为每个类实例的内置属性。

 __class__功能与用法:

  • __class__功能和type()函数一样,都是查看对象所在的类。
  • __class__可以套用
class Student(object):
    def __init__(self,name):
        self.name = name
stu = Student("tom")
print(type(stu),type(Student))
print(stu.__class__, Student.__class__, stu.__class__.__class__)
'''结果如下:
<class '__main__.Student'> <class 'type'>
<class '__main__.Student'> <class 'type'> <class 'type'>
'''

元类实例化创建一般的类有三个参数

1、类名class_name="xxx"

2、基类们class_bases=(object,),要继承的父类名,用元组

3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的,就是类的属性字典

调用type时会依次传入以上三个参数

__metaclass__属性

你可以在写一个类的时候为其添加__metaclass__属性,定义了__metaclass__就定义了这个类的元类。

class Foo(object):   #py2
    __metaclass__ = something…


class Foo(metaclass=something):   #py3
    __metaclass__ = something…

例如:当我们写如下代码时 :

class Foo(Bar):
    pass

在该类并定义的时候,它还没有在内存中生成,知道它被调用。Python做了如下的操作:
1)Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧跟我的思路)。
2)如果Python没有找到__metaclass__,它会继续在父类中寻找__metaclass__属性,并尝试做和前面同样的操作。
3)如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
4)如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?
答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东西都可以。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类,定制类。

我们可以这样理解: 把类的创建过成想象成一个管道, 如果遍历完继承链+模块层次都没有发现__metaclass__属性, 那么用type元类来创建该类. 这种情况下我们在类中怎样定义属性, 最终生成的类对象也会具有怎样的属性, 如下图所示:

类创建时的样子---(type)-->类最终的样子(由于是用type元类, 所以类最终样式与类创建时样式一样)

如果遍历过程中找到了__metaclass__属性, 那么就会用自定义元类来创建该类, 如下图所示:

类创建时的样子---(__metaclass__属性指向的元类)--->类最终的样子

到底什么时候才会用到元类呢? 通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

1、使用函数当做元类

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
    # 将它们转为大写形式
    uppercase_attr = dict((name.upper(), value) for name, value in attrs)
    #通过'type'来做类对象的创建
    return type(future_class_name, future_class_parents, uppercase_attr)#返回一个类

class Foo(object,metaclass=upper_attr):
    __metaclass__ = upper_attr
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出:True
f = Foo()
print(f.BAR)
# 输出:'bip'

2、使用class来当做元类

由于__metaclass__必须返回一个类.

请记住,'type'实际上是一个类,就像'str'和'int'一样。所以,你可以从type继承
__new__ 是在__init__之前被调用的特殊方法,__new__是用来创建对象并返回之的方法,__new__()是一个类方法
而__init__只是用来将传入的参数初始化给对象,它是在对象创建之后执行的方法。
你很少用到__new__,除非你希望能够控制对象的创建。这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
如果你希望的话,你也可以在__init__中做些事情。还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用,下面我们可以单独的讨论这个使用
class UpperAttrMetaClass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)#返回一个对象,但同时这个对象是一个类

但是,这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。现在让我们这样去处理:

class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法。由于type是元类也就是类,因此它本身也是通过__new__方法生成其实例,只不过这个实例是一个类.
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

如果使用super方法的话,我们还可以使它变得更清晰一些。

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

__new__、__init__、__call__的介绍

__new__方法负责创建一个实例对象,在对象被创建的时候调用该方法它是一个类方法。__new__方法在返回一个实例之后,会自动的调用__init__方法,对实例进行初始化。如果__new__方法不返回值,或者返回的不是实例,那么它就不会自动的去调用__init__方法。

__init__ 方法负责将该实例对象进行初始化,在对象被创建之后调用该方法,在__new__方法创建出一个实例后对实例属性进行初始化。__init__方法可以没有返回值。

__call__方法其实和类的创建过程和实例化没有多大关系了,定义了__call__方法才能被以函数的方式执行。

在Python当中,简述__call__,__new__,__init__三者之间的关系

在类实例化的过程当中,哪个对象加()就寻找产生这个对象的类的__call__方法,只要是__call__方法,一定会做三件事情:

第一:调用__new__方法,构造新的对象,相当于Java当中的构造函数.(对象自己的__new__)

第二:调用__init__方法,去初始化这个对象(对象自己的__init__)

第三:返回这个对象.

注意:__new__更像是其他语言当中的构造函数,必须有返回值,返回值为实例化的对象,如果__new__方法不返回值,或者返回的不是实例,那么它就不会自动的去调用__init__方法。__init__只是初始化构造函数,必须没有返回值,仅仅只是初始化功能,并不能new创建对象.

也就是说,一个类在实例化的时候实际上是做了三件事情:

第一:触发元类中(造出这个类的类)的__call__方法   

第二:通过__new__产生一个空对象

第三:通过__init__初始化这个对象

第四:返回这个对象

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

class MyMetaClass(type):
    def __call__(cls, *args, **kwargs):
        print('--自动触发元类当中的__call__方法---')
        #调用__new__产生一个空对象obj
        obj = cls.__new__(cls)
        print(obj)
        print(obj.__dict__)   #此时名称空间为空.
        #调用__init__初始化这个对象.
        cls.__init__(obj,*args,**kwargs)
        #返回初始化的对象
        print(obj.__dict__)

        return obj

class Person(object, metaclass=MyMetaClass):  # 这一行: Person = MyMetaClass('Person',(object,),{...})

    country = 'China'

    def __init__(self, name, age):
        print('-----Person--init-----')
        self.name = name
        self.age = age

    def tell_info(self):
        print('%s 的年龄是:%s' % (self.name, self.age))

    def __new__(cls, *args, **kwargs):
        print('---------new--------')
        return super().__new__(cls)

print(callable(Person))
person = Person('wtt',25)   #Person这个类之所以可以被调用,肯定是因为造出Person的类:MyMetaClass当中含有__call__方法.
print(person)
运行结果:

True
--自动触发元类当中的__call__方法---
---------new--------
<__main__.Person object at 0x0000000001F6D390>
{}
-----Person--init-----
{'name': 'wtt', 'age': 25}
<__main__.Person object at 0x0000000001F6D390>

Process finished with exit code 0

类在实例化对象的时候函数的调用顺序依次是__call__==>__new__==>__init__.

到这里你是否感觉元类很高级:既可以控制创建类的行为,也可以控制类的实例化行为

上面其实介绍的都是普通类的__new__, init, __call__方法,实际上元类这三个方法的含义和普通类是一样的,只是元类的实例是类,所以元类这三个方法的作用依次是
new:创建元类对象(类实例)
init:对元类对象做初始化,这里的初始化和普通类的初始化不太一样后面会介绍
call:其类实例创建对象实例时被调用,返回类实例的对象实例

class MetaClass(type):

    # 第一步
    def __new__(mcs, *args, **kwargs):
        print('MetaClass __new__')
        return super(MetaClass, mcs).__new__(mcs, *args, **kwargs)

    # 第二步
    def __init__(cls, *args, **kwargs):
        print('MetaClass __init__')
        super(MetaClass, cls).__init__(*args, **kwargs)

    # 第三步
    def __call__(cls, *args, **kwargs):
        print('MetaClass __call__')
        return super(MetaClass, cls).__call__(*args, **kwargs)


class Person(metaclass=MetaClass):

    gender = 'man'

    # 第四步
    def __new__(cls, *args, **kwargs):
        print('Demo __new__')
        return super(Person, cls).__new__(cls)

    # 第五步
    def __init__(self, name, age):
        print('Demo __init__')
        self.name = name
        self.age = age

    # 第六步
    def __call__(self, behavior: str):
        print('Demo __call__ : ' + behavior)

    def say(self):
        pass


person = Person(name='perter', age=18)
person('hello')

输出:
MetaClass __new__
MetaClass __init__
MetaClass __call__
Demo __new__
Demo __init__
Demo __call__ : hello

第一步:创建类实例,并返回类实例 .相当于通过 MetaClass __new__()创建Person类,例如修改类名,增加或删除类属性等
第二步:初始化类实例。MetaClass __init__

第一步和第二步是在代码中Person类执行完后执行
第三步:执行Person(name=‘perter’, age=18)时被调用,创建类实例的对象实例,并返回对象实例
第四步:其实第三步是通过第四步和第五步来执行的,第四步是创建对象实例
第五步:对象实例初始化,添加person,age属性
第六步:执行person(‘hello’)被调用

总结

元类的__new__和__init__影响的是创建类对象(相当于创建普通类)的行为,父元类的__call__控制对子元类的 __new__,__init__的调用,就是说控制类对象的创建和初始化。父元类的__new__和__init__由更上层的控制,一般来说,原始type是最初的父元类,其__new__和__init__是具有普遍意义的,即应该是分配内存、初始化相关信息等

元类的__new__和__init__参考:https://blog.csdn.net/u012062455/article/details/107454081

元类__call__影响的是创建类的实例对象的行为,Person(name=‘perter’, age=18) 此时如果类自定义了__new__和__init__就可以控制类的对象实例的创建和初始化

普通类的__new__和__init__ 影响的是创建对象的行为,当这些函数在元类中时,影响创建的是类;同理,当这俩个函数在普通类中时,影响创建的是普通的对象实例。

普通类__call__ 影响对象的()调用行为,例如person('hello') 。如果是普通类实例化对象,调用的是普通类的__call__

NOTE:通过元类的__new__()和__call__()虽然都能实现某个目的,但是__new__()影响类,对所有对象都有效。__call__()只是影响当前创建的对象。

元类实现ORM

1. ORM是什么

ORM 是 python编程语言后端web框架 Django的核心思想,“Object Relational Mapping”,即对象-关系映射,简称ORM。顾名思义就是把关系转化成对象的框架,关系这个词我们在哪里用的最多呢?

显然应该是数据库。之前我们在分布式的文章介绍关系型数据库和非关系型数据库的时候就着重介绍过关系的含义。我们常用的MySQL就是经典的关系型数据库,它存储的形式是表,但是表承载的数据其实是两个实体之间的"关系"。比如学生上课这个场景,学生和课程是两个主体(entity),我们要记录的是这两个主体之间的关系,也就是学生上课这件事。

而ORM框架做的事情是将这些关系映射成类,这样我们可以将这张表当中增删改查的功能抽象成类当中的方法。这样我们就可以通过调用类的方式来操作数据库了,从而达到高度抽象业务逻辑、降低用户使用难度的目的
一个句话理解就是:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应MySQL语句

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

User类代表了数据库当中的一张表,它有4个字段:id, name, email和password,我们在定义字段的同时也通过类别指定了它们的类型。这个应该不难理解,上面的这个类等价于我们在数据库当中执行了这么一段建表的SQL:

create table if not exists user (
 id int,
    name string,
    email string,
    password string
)

我们定义了表字段之后,接下来要做的就是根据字段创建数据了,其实也就是根据类创建实例。我们希望User类型的实例就对应User表当中的一条记录,并且我们可以通过调用实例当中的方法,来操作这张表进行增删改查。

# 创建一个实例:
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
# 保存到数据库:
u.save()

具体实现https://zhuanlan.zhihu.com/p/150782725

# -*- coding: utf-8 -*-

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<{}:{}>'.format(self.__class__.__name__, self.name)


class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')


class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')


class ModelMetaclass(type):

    def __new__(mcs, name, bases, attrs):
        #  创建model类的时候不做任何处理
        if name == 'Model':
            return super(ModelMetaclass, mcs).__new__(mcs, name, bases, attrs)
        #  打印表名的信息
        print('Found model: %s' % name)
        #  mapping用来存储字段信息
        mapping = dict()
        for k, v in attrs.items():
            # 判断v的类型,只有Filed的子类才会存储起来
            if isinstance(v, Field):
                print('Found mapping:%s ==>%s' % (k, v))
                mapping[k] = v
        #  将mapping当中的数据从类属性当中移除,防止关键字冲突
        for k in mapping.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mapping  # 保存属性和列的映射关系
        attrs['__table__'] = name  # 假设表名和类名一致
        return super(ModelMetaclass, mcs).__new__(mcs, name, bases, attrs)


class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        #  因为Model的基类是dict,所有创造Model的字段会被解析成dict的构造函数
        #  也就是说字段名和字段值的映射会存储在dict中
        super(Model, self).__init__(**kwargs)

    def __getattr__(self, key):
        # 只有在使用点调用属性且属性不存在的时候才会触发,调用存在的属性或者方法时不触发
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            #  fields存储字段名
            fields.append(v.name)
            #  params填充问号
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (
            self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))


class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')


# 创建一个实例:
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
# 保存到数据库:
u.save()

猜你喜欢

转载自blog.csdn.net/zangba9624/article/details/109787929