zoukankan      html  css  js  c++  java
  • python(五):元类与抽象基类

    一、实例创建

      在创建实例时,调用__new__方法和__init__方法,这两个方法在没有定义时,是自动调用了object来实现的。python3默认创建的类是继承了object。

    class A(object):
        def __init__(self, *args, **kwargs):
            self.name, self.age, self.gender = args[:3]
        def __new__(cls, *args, **kwargs):
            print("__new__ has called.")
            return super(A, cls).__new__(cls)
            # 可写为 super().__new__(cls) 或 object.__new__(cls)
    
    a = A("Li", 27, "male")
    print(a.name, a.age, a.gender)
    
    """
    __new__ has called.
    Li 27 male
    
    """

    二、类的创建

      以class关键字开头的上下文在定义时就已经被解释执行。而函数(包括匿名函数)在没被调用时是不执行的。这个过程本质上做了一件事情:从元类type那里创建了一个名为A的类,开辟类内存空间,并读取class语句的上下文,将类属性和方法写进去。

    print("--解释器开始执行--")
    def func():
        print("what the hell?")
    
    print("--开始读取class关键字的上下文--")
    class A:
        name = "A"
        func()
        print("--上下文结束--")
        
    def fn1():
        print("--开始读取匿名函数--")
        def fn2():
            pass
        pass
        print("--读取结束--")
    print("--解释器执行结束--")
    
    """
    --解释器开始执行--
    --开始读取class关键字的上下文--
    what the hell?
    --上下文结束--
    --解释器执行结束--
    """

      " 使用class语句定义新类时,将会发生很多事情。首先,类主体将为作其自己的私有字典内的一系列语句来执行。其内容里语句的执行与正常代码中的执行过程相同,只是增加了会在私有成员(名称以__开头)上发生的名称变形。然后,类的名称、基类列表和字典将传递给元类的解构函数,以创建相应的类对象。最后,调用元类type(),这里可以自定义。在python3中,使用class Foo(metaclass=type)来显式地指定元类。如果没有找到任何__metaclass__值,Python将使用默认的元类type。"  -- <<python 参考手册(第四版)>>

    class_name = "Foo"    # 类名
    class_parents = (object, )   # 基类
    # 类主体
    class_body = """
    name = "Foo"
    def __init__(self, x):
        self.x = x
    def hello(self):
        print("Hello")
    """
    class_dict = {}
    # 在局部字典class_dict中执行类主体
    exec(class_body, globals(), class_dict)
    # 创建类对象Foo
    Foo = type(class_name, class_parents, class_dict)  # type可以指定
    Foo("X").hello()
    # Hello

      type类创建类时,指定了类的三个部分: class_name, class_parent, class_dict。这一步是在底层实现的。

    string = """name = 'Li'
    age = 2712
    """
    # 字符串必须是换行符或分号分割
    dic = {}
    exec(string, globals())  # globals表示执行字符串后的结果保存到全局命名空间中
    print(name, age)
    print(dic)
    exec(string, globals(), dic)  # locals表示执行字符串后的结果保存到局部一个映射对象中
    print(dic)
    
    """
    Li 2712
    {}
    {'name': 'Li', 'age': 2712}
    """
    exec函数用法

      我们可以用type动态地创建类。你可以用上面的方式去实现类的上下文,也可以直接定义函数并给到字典里,尽管它看起来有些"污染"全局空间:

    class_name = "A"
    class_parent = ()
    
    label = "hello world"
    
    def init(self, name, age):
        self.name = name
        self.age = age
    def hello(self):
        print("Hello, i'm %s, %s." % (self.name, self.age))
        
    A = type(class_name, class_parent, {"__init__": init, "hello": hello, "label": label})
    
    a = A("Li", 18)
    a.hello()
    print(a.label)
    
    """
    Hello, i'm Li, 18.
    hello world
    """

    三、元类的实现过程

      

    复制代码
    
    print("First...")
    class MyType(type):
        print("MyType begin ...")
        def __init__(self, *args, **kwargs):
            print("Mytype __init__", self, *args, **kwargs , sep="
    ", end="
    
    ")
            type.__init__(self, *args, **kwargs)  # 调用type.__init__
            
        def __call__(self, *args, **kwargs):
            print("Mytype __call__", *args, **kwargs)
            obj = self.__new__(self)   # 第一个self是Foo,第二个self是F("Alex")
            print("obj ",obj, *args, **kwargs)
            print(self)
            self.__init__(obj,*args, **kwargs)
            return obj
        
        def __new__(cls, *args, **kwargs):
            print("Mytype __new__", cls, *args, **kwargs, sep="
    ", end="
    
    ")
            return type.__new__(cls, *args, **kwargs)
        print("MyType end ...")
        
    print('Second...')
    class Foo(metaclass=MyType):
        print("begin...")
        def __init__(self, name):
            self.name = name
            print("Foo __init__")
            
        def __new__(cls, *args, **kwargs):
            print("Foo __new__", end="
    
    ")
            return object.__new__(cls)
        print("over...")
        
        def __call__(self, *args, **kwargs):
            print("Foo __call__", self, *args, **kwargs, end="
    
    ")
        
    print("third...")
    f = Foo("Alex")
    print("f",f, end="
    
    ")
    f()
    print("fname",f.name)
    
    """
    First...
    MyType begin ...
    MyType end ...
    Second...
    begin...
    over...
    Mytype __new__
    <class '__main__.MyType'>
    Foo
    ()
    {'__module__': '__main__', '__qualname__': 'Foo', '__init__': <function Foo.__init__ at 0x10ad89268>, '__new__': <function Foo.__new__ at 0x10ad89488>, '__call__': <function Foo.__call__ at 0x10ad86ae8>}
    
    Mytype __init__
    <class '__main__.Foo'>
    Foo
    ()
    {'__module__': '__main__', '__qualname__': 'Foo', '__init__': <function Foo.__init__ at 0x10ad89268>, '__new__': <function Foo.__new__ at 0x10ad89488>, '__call__': <function Foo.__call__ at 0x10ad86ae8>}
    
    third...
    Mytype __call__ Alex
    Foo __new__
    
    obj  <__main__.Foo object at 0x10ae2ac88> Alex
    <class '__main__.Foo'>
    Foo __init__
    f <__main__.Foo object at 0x10ae2ac88>
    
    Foo __call__ <__main__.Foo object at 0x10ae2ac88>
    
    fname Alex
    
    """

      假设MyType是type类,type有三个特殊方法__init__、__call__、__new__。

      首先, First请忽略掉吧。假设底层就这样搞了一个type类,它的名字叫MyType。

      其次,Second这一步。解释器发现class和Foo(),会知道要从元类MyType中"实例化"一个类对象。

        它会首先扫描class Foo()的整个上下文,并分成三部分,类名、基类元组,和私有字典。

        然后它会告诉解释器,马上调用MyType(就是Type)类来创建一个名为Foo的类,来开辟内存空间,把这个Foo的私有字典(包括属性和方法)给放进去。

        于是解释器执行了MyType.__new__,并继续执行MyType.__init__。来创建一个名为Foo的类对象。

      再次,Third这一步。

        首先通过Foo()来调用MyType.__call__,来实例化一个Foo类。它相当于Foo = Type()

        然后依次执行Foo.__new__和Foo.__init__,来实例化一个实例对象。

        Foo()相当于: MyType()(),而MyType()就是F。于是,在a = Foo(),实际上执行了MyType()()。前面说过,实例+()会调用所属类的__call__方法,同样地,类 + ()会调用类所属元类(MyType)的__call__方法。

        至此,一个实例就算创建完成了。

    四、抽象基类

      抽象基类有两个特点:

        1.规定继承类必须具有抽象基类指定的方法

        2.抽象基类无法实例化

      基于上述两个特点,抽象基类主要用于接口设计

      实现抽象基类可以使用内置的abc模块

    import abc
    class Human(metaclass=abc.ABCMeta):
        @abc.abstractmethod   # 规定子类必须有名为introduce的实例方法
        def introduce(self):
            pass
        
        @abc.abstractproperty  # 规定子类必须有名为country的装饰器方法
        def country(self):
            pass
        
        @abc.abstractclassmethod  # 规定子类必须有名为gender的类方法
        def gender(cls):
            pass
        @abc.abstractstaticmethod  # 规定子类必须有名为hello的静态方法
        def hello():
            pass
    class Person(Human):
        __country = "China"
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def introduce(self):
            return "I'm {}, {}.".format(self.name, self.age)
        
        @property
        def country(self):
            return Person.__country
        
        @classmethod
        def gender(cls):
            return "female"
        
        @staticmethod
        def hello():
            print("What the hell?")
    
    person = Person("Li", 24)
    print(person.introduce())
    print(person.country)
    print(Person.gender())
    person.hello()
    
    # I'm Li, 24.
    # China
    # female
    # What the hell?

       collections.abc模块收集了常用的抽象基类。感兴趣的话可以打开collections.abc查看源码。

    __all__ = ["Awaitable", "Coroutine",
               "AsyncIterable", "AsyncIterator", "AsyncGenerator",
               "Hashable", "Iterable", "Iterator", "Generator", "Reversible",
               "Sized", "Container", "Callable", "Collection",
               "Set", "MutableSet",
               "Mapping", "MutableMapping",
               "MappingView", "KeysView", "ItemsView", "ValuesView",
               "Sequence", "MutableSequence",
               "ByteString",
               ]
  • 相关阅读:
    nginx 启动相关的
    爬取豆瓣读书/文件存储数据/数据库存储数据
    python Web 开发三剑客比较
    scrapy
    爬虫自动登录抽屉
    组合搜索
    html瀑布流
    Ajax上传文件/文件预览
    Form组件
    django分页
  • 原文地址:https://www.cnblogs.com/kuaizifeng/p/9082181.html
Copyright © 2011-2022 走看看