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="\r\n", end="\r\n\r\n")
        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="\r\n", end="\r\n\r\n")
        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="\r\n\r\n")
        return object.__new__(cls)
    print("over...")

    def __call__(self, *args, **kwargs):
        print("Foo __call__", self, *args, **kwargs, end="\r\n\r\n")

print("third...")
f = Foo("Alex")
print("f",f, end="\r\n\r\n")
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",
           ]

原文地址:https://www.cnblogs.com/kuaizifeng/p/9082181.html

时间: 2024-11-08 20:46:23

python(七):元类与抽象基类的相关文章

类,抽象基类,接口类三者间的区别与联系(C++)

联系很明显,三个都是‘类’,如果读者对类的概念不清楚,可以参照wid的博文http://www.cnblogs.com/mr-wid/archive/2013/02/18/2916309.html. 下面着重解释一下区别,但此文仅是个人理解,如果觉得我说的不对的地方,还请赐教. (1)结构上的区别: 普通类:数据+方法+实现 抽象类:数据+方法(一定包含虚方法n>=1)+部分方法的实现 接口类:方法(纯虚方法) (2)概念上的区别: 普通的类和另外两个的区别很明显,普通类就是猫狗之类的,而抽象类

python 用abc模块构建抽象基类Abstract Base Classes

见代码: 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2018/08/01 16:58 4 from abc import ABCMeta, abstractmethod 5 6 7 class SClass(object): 8 __metaclass__ = ABCMeta 9 10 @abstractmethod 11 def my_print(self): 12 pass 13 14 15 class C0C

流畅python学习笔记:第十一章:抽象基类

__getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克牌的例子来看下 class FrenchDeck:     ranks=[str(n) for n in range(2,11)] + list('JQKA')     suits='spades diamonds clubs hearts'.split()     def __init__(sel

C++抽象基类

抽象类定义: 成员函数至少有一个为纯虚函数(函数声明后面加=0,但是不定义)的类为抽象基类,这个基类不能被显示创建其对象.但是这个基类的其他函数都可以正常定义,一般析构函数为虚函数用来被其他类继承. 例如: 头文件: #ifndef COMMUNICATER_H #define COMMUNICATER_H class Communicater { public: Communicater(void); virtual void run_collect() = 0; virtual ~Commu

C++ 纯虚函数与抽象基类

在C++ 中,虚函数可以很好的实现多态,然而也有一定的局限,这个局限在于,虚函数必须在基类中定义,哪怕是空的实现. 例如下面这段代码编译时就会出错: class Base { virtual void print(); }; class Derived:Base { void print() { cout << "Derived" <<endl; } }; 因为Base类中的虚函数print()没有定义. 而在实际编程时我们往往会遇到无法给基类的函数定义的情况,

C++编程实践: 抽象基类

本实例来自<C++ Primer Plus>(第六版) 第十三章 题目要求: 假设你正在开发一个图形程序,该程序会显示圆和椭圆等,需要考虑: 椭圆要包含椭圆中心坐标,半长轴,半短轴以及方向角的数据.圆要实现圆心坐标,半径等数据. 椭圆要包含移动,旋转一定角度,计算面积和缩放等方法,但是圆不需要旋转. 设计思路: 虽然圆也是一种椭圆的特殊形势,但是设计成由圆类继承椭圆类显然是十分笨拙的.比较好的办法是涉及一个基类BaseEllipse,圆和椭圆都继承此基类.这样便可以使用指针数组同时管理Circ

抽象基类

纯虚函数 和普通的虚函数不一样,一个纯虚函数无须定义.我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数.其中,=0只能出现在类内部的虚函数声明语句处. 指的注意的是,我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部.也就是说,我们不能在类的内部为一个=0的函数提供函数体. 含义纯虚函数的类是抽象基类 含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类.抽象基类负责定义接口,而后续的其他类可以覆盖该接口.我们不能(直接)创建一个抽象基类的对象.

OOP2(虚函数/抽象基类/访问控制与继承)

通常情况下,如果我们不适用某个函数,则无需为该函数提供定义.但我们必须为每个虚函数都提供定义而不管它是否被用到了,这因为连编译器也无法确定到底会适用哪个虚函数 对虚函数的调用可能在运行时才被解析: 当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数.被调用的函数是与之绑定到指针或引用上的对象的动态类型相匹配的那一个 注意:动态绑定只有当我们通过指针或引用调用虚函数时才会发生.当我们通过一个具有普通类型(非引用非指针)的表达式调用虚函数时,在编译时就会将调用

制造抽象基类--《C++必知必会》 条款33

抽象类,含有纯虚函数的类,不可以创建对象. 然而,有时我们并不需要某个函数定义为纯虚函数,但是任然希望此类像抽象类一样,不可以创建对象. 方法1:通过确保类中不存在共有构造函数来模拟抽象基类的性质.意味着,我们必须至少显示地声明一个构造函数和一个复制构造函数,防止编译器隐式公有的内联的构造函数.声明为protected ,既允许派生类的构造函数使用他们,同时阻止创建对象. class ABC{ protected: ABC(); ABC(const ABC&); }; 方法2:让一个类成为抽象基