Python类元编程初探
在《流畅的Python》一书中提到:
Classes are first-class object in Python, so a function can be used to create a new class at any time, without using the class keyword.
在Python中,声明一个类可以有两种方法:
>>> class X:
... a = 1
...
>>> X = type('X', (object,), dict(a=1))
可见类X
也是一个type类型的对象。
Import time和runtime
在import一个模块的时候,可以看出有哪些模块中的语句会执行。比如有模块a.py
:
A = 'A'
class ClassA:
print('print ClassA')
def printf():
print('print printf)
当我们执行import a
的时候:
>>> import a
print ClassA
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <module 'a' from 'D:\\Workspace\\a.py'>}
可以看到,类ClassA
中的语句会被执行,可以根据代码所在模块中的层级,将一些在顶级的代码称作top-level code。这类代码会在import time被执行。很显然,类中的代码块也会被执行。为什么模块a.py
中的函数printf
不会被执行呢?根据文章开头提到的,类也是一种对象。声明一个类,相当于执行type创建一个type对象。所以类声明语句会被执行,需要确定声明的类有哪些属性和方法。
__new__和__init__方法
官方文档给出的说明:
object.new(cls[, ...]), called to create a new instance of class cls.
object.init(self[, ...]), called after the instance has been created (by new()), but before it is returned to the caller.
new() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
在实例化一个类的对象的时候,首先会调用__new__
来确定会实例化哪个类,然后在调用__init__
来初始化具体的对象。一般很少使用这种特性。但是在编写一些框架的时候却很有用,比如编写ORM框架。
metaclass的具体使用
上文提到的,可以用type
来创建一个类。当type
不能满足需求的时候,就需要自定义type
方法了。
在廖雪峰的使用元类一文中,需要实现一个简单的ORM。比如定义了一个模型User
,它对应数据库中的users表,有四个字段,分别是id、name、email和password:
class User(Model):
# 定义类的属性到列的映射:
__tablename__ = 'users'
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
类属性(id、name、email和password)如何和表中的字段关联?如何只是声明一个这样的类,没有任何作用,解释器会调用type('User', (Model, ), {'id': IntegerField('id')})
来创建type对象。很显然默认的type
方法不满足需要。所以需要自定义一个type
方法:
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s==>%s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
print(cls)
attrs['__table__'] = attrs['__tablename__']
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
print('ModelMetaclass.__new__')
return type.__new__(cls, name, bases, attrs)
这样就可以调用ModelMetaclass('User', (Model,), {'id': IntegerField('id')})
来创建一个类对象。粘贴复制加魔改廖雪峰博文中的代码,省略部分代码:
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<%s:%s>' % (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__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s==>%s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
print(cls)
attrs['__table__'] = name # 假设表名和类名一致
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
print('ModelMetaclass.__new__')
print(attrs)
return type.__new__(cls, name, bases, attrs)
class Model(dict):
def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__tablename__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
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
User = ModelMetaclass('User', (Model, ), {'__tablename__': 'users', 'id': IntegerField('id')})
在上面的代码中,我们使用ModelMetaclass来动态的创建User
类,这和廖雪峰博文中在声明类的时候指明metaclass=ModelMetaclass
是一样的效果。sqlalchemy
中就是使用这种方式来动态的创建类的:
def declarative_base(
bind=None,
metadata=None,
mapper=None,
cls=object,
name="Base",
constructor=_declarative_constructor,
class_registry=None,
metaclass=DeclarativeMeta,
):
lcl_metadata = metadata or MetaData()
if bind:
lcl_metadata.bind = bind
if class_registry is None:
class_registry = weakref.WeakValueDictionary()
bases = not isinstance(cls, tuple) and (cls,) or cls
class_dict = dict(
_decl_class_registry=class_registry, metadata=lcl_metadata
)
if isinstance(cls, type):
class_dict["__doc__"] = cls.__doc__
if constructor:
class_dict["__init__"] = constructor
if mapper:
class_dict["__mapper_cls__"] = mapper
return metaclass(name, bases, class_dict)
Python类元编程初探的更多相关文章
- Python类元编程
类元编程是指在运行时创建或定制类.在Python中,类是一等对象,因此任何时候都可以使用函数创建新类,而无需用class关键字.类装饰器也是函数,不过能够审查.修改,甚至把被装饰的类替换成其他类.元类 ...
- Python的元编程案例
Python的元编程案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是元编程 元编程概念来自LISP和smalltalk. 我们写程序是直接写代码,是否能够用代码来生成 ...
- python之元编程
一.什么是元编程 元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此你可以在运行时对它进行内省.生成和/或修改. Python在语言层面对函数.类等基本类型提供了内省及实时创建和修改的 ...
- python之元编程(元类实例)
本实例是元类实例,功能是记录该的子类的类名,并以树状结构展示子类的类名. RegisterClasses继承自type,提供的功能是在__init__接口,为类创建了childrens的集合,并类名保 ...
- Python元编程
简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>> ...
- python类:描述器Descriptors和元类MetaClasses
http://blog.csdn.net/pipisorry/article/details/50444769 描述器(Descriptors) 描述器决定了对象属性是如何被访问的.描述器的作用是定制 ...
- python 通过元类控制类的创建
一.python中如何创建类? 1. 直接定义类 class A: a = 'a' 2. 通过type对象创建 在python中一切都是对象 在上面这张图中,A是我们平常在python中写的类,它可以 ...
- 转---一文读懂 python 的元类
译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...
- Python之元类详解
一.引子 元类属于Python面向对象编程的深层魔法,99%的人都不得要领,一些自以为搞明白元类的人其实也是自圆其说,点到为止,从队元类的控制上来看就破绽百出,逻辑混乱: 二.什么是元类 一切源自于一 ...
随机推荐
- HTTP中Post与Put的区别
PUT请求是向服务器端发送数据的,从而改变信息,该请求就像数据库的update操作一样,用来修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同. POST请求 ...
- 2018-2019-2 网络对抗技术 20165236 Exp5 MSF基础应用
2018-2019-2 网络对抗技术 20165236 Exp5 MSF基础应用 一. 实践内容(3.5分) 本实践目标是掌握metasploit的基本应用方式,重点常用的三种攻击方式的思路.具体需要 ...
- PyAutoGUI——让所有GUI都自动化
2015-08-17:输入中文bug没有解决,目前的解决方案是Python 2.X环境下安装pyperclip和pyautogui,用复制粘贴来实现. In [ ]: import pyperclip ...
- MVC5+EF6 完整教程17--升级到EFCore2.0(转)
MVC5+EF6 完整教程17--升级到EFCore2.0 2017年08月22日 14:48:12 linux12a 阅读数:2814 EF Core 2.0上周已经发布了,我们也升级到core ...
- Gartner容器市场指南中国语境:容器成为新常态,灵雀云等本地厂商在选择中占据优势
在2019年2月“ China Summary Translation: 'Market Guide for Container Management Software'”的报告中,Gartner认为 ...
- luogu P1578 奶牛浴场
很好的一道题 王知昆爷爷的论文(讲的特别清楚) https://wenku.baidu.com/view/bc8311f69e314332396893f7.html 先贴上AC代码 #include& ...
- caffe常用
1. 关闭模型froward信息 os.environ[' #注意要在improt caffe之前 2. 学习率 step: 配合stepsize,迭代次数达到stepsize的整数倍改变一次, ba ...
- mysql知识点汇总
1. 数据库的安装 2. 数据库设计需要注意什么 3. SQL语句优化 4. 怎样处理慢查询? 5. 怎样更好的利用数据库索引? 6. 事务隔离级别有哪些?怎么实现的? 7. 数据库锁有哪些? 8. ...
- MB SD Connect 5 vs 2017 FVDI2 Commander
Both MB SD C5 and FVDI II are diagnostic and Programmer tools for Mercedes Benz Cars & Trucks.Th ...
- 解决 flannel.1 interface state DOWN
ip a 查看结果 flannel.1 i state UNKNOWN 并且五 inet 发现日志 device (flannel.1): state change: unmanaged -> ...