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%的人都不得要领,一些自以为搞明白元类的人其实也是自圆其说,点到为止,从队元类的控制上来看就破绽百出,逻辑混乱: 二.什么是元类 一切源自于一 ...
随机推荐
- Ztree节点增加删除修改和Icheck的用法
简介 官方文档:http://www.treejs.cn/v3/api.php zTree 是一个依靠 jQuery 实现的多功能 “树插件”, 而且拥有较好的浏览器兼容性,有着丰富的功能以及可以自定 ...
- RN如何基于js代码手动打一个main.jsbundle
react-native bundle --entry-file index.js --bundle-output ./ios/bundle/main.jsbundle --platform ios ...
- 2019-04-15 Python之利用matplotlib和numpy的简单绘图
环境:win10家庭版, Anocada的 Spyder 一.简单使用 使用函数 plt.polt(x,y,label,color,width) 根据x,y 数组 绘制直,曲线 import nump ...
- iPhone手机屏幕尺寸(分辨率)
第一代iPhone2G屏幕为3.5英吋,分辨率为320*480像素,比例为3:2. 第二代iPhone3G屏幕为3.5英吋,分辨率为320*480像素,比例为3:2. 第三代iPhone3GS屏幕为3 ...
- 从零开始一起学习SLAM | 掌握g2o边的代码套路
点"计算机视觉life"关注,置顶更快接收消息! 小白:师兄,g2o框架<从零开始一起学习SLAM | 理解图优化,一步步带你看懂g2o代码>,以及顶点<从零开始 ...
- python 练习1(流控制)
#!/usr/bin/python #_*_ coding:utf-8 _*_ #练习题 #1.使用while循环输入 1 2 3 4 5 6 8 9 10 #a.定义一个变量存放数字 #b.用whi ...
- 单链表反转java代码
据说单链表反转问题面试中经常问,而链表这个东西相对于数组的确稍微难想象,因此今天纪录一下单链表反转的代码. 1,先定义一个节点类. public class Node { int index; Nod ...
- javascript学习-基本类型
javascript学习-基本类型 1.概述 javascript的数据类型大体上分两种:基本类型和对象类型.简单的区分就是基本类型是无法再分的原子级类型:对象类型是容器,可以容纳基本类型和对象类型. ...
- A Simple Chess (Lucas组合数 + 容斥)
题意:走马步,要求向右向下,不能走进禁止的点.求方案数. 思路:若是n*m比较小的话,那么可以直接DP.但是这道题目不行.不过我们仔细分析可以知道从某个点到某个点是一个组合数,但是数据太大,mod值很 ...
- python源码为何都是pass
最近看Python代码 按照一个函数递进的看下去,最后发现,遇到很多源码什么逻辑都没写,仅仅以一个pass 结尾 但却能得到应该得到的结果,这点真的很奇怪,上网查找后 觉得下面的 ...