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%的人都不得要领,一些自以为搞明白元类的人其实也是自圆其说,点到为止,从队元类的控制上来看就破绽百出,逻辑混乱: 二.什么是元类 一切源自于一 ...
随机推荐
- vue 父组件给子组件传值,子组件给父组件传值
父组件如何给子组件传值 使用props 举个例子: 子组件:fromTest.vue,父组件 app.vue fromTest.vue <template> <h2>{{tit ...
- 为多维数组添加一列以及reshape用法注意
https://blog.csdn.net/orangefly0214/article/details/80934008参考这个了链接 下面是我自己用到的代码,亲测可用 # data = pd.rea ...
- Node.js、npm、vue-cli 的安装配置环境变量
我安装node.js是为了学习vue,需要用到npm,所以就把node.js安装了,安装node.js会带有npm的安装. 在安装node.js之前,我们需要了解以下三个内容. npm: Nodejs ...
- nc_netcat命令
它可以打开TCP连接.发送UDP数据包.监听任意TCP和UDP端口,进行端口扫描,处理IPv4和IPv6 这个命令适用于写脚本 nc -z -w 10 ip port -z:指定NC只应扫描侦听守护 ...
- 将分支推送到远程存储库时遇到错误: Git failed with a fatal error. TaskCanceledException encountered.
解决:https://blog.csdn.net/dw33xn/article/details/79951714 修改下配置文件即可
- ORACLE——将多行文本数据转换成一行
前几天项目中有个到处文件的功能,但是有一个字段存进的数据存在换行情况,导出来的数据会出现错乱的情况,所以需要把换行的数据转换成一行.当然也可以在java程序中实现. 数据库中的数据为: SQL'; A ...
- mipush ionic3 线上push
<dict> <key>aps-environment</key> <string>production</string> </dic ...
- Excle中的使用小技巧
关于从数据库中拷贝来的数字,拷贝到excle中,那些数字都变成了科学计算法. 步骤1,鼠标右键选中的列,选择“设置单元格格式(F)” 2.从这里面选中这些是否有小数,如果没有小数就把这个改成0
- mybatis增强
MyBatis SQL参数传递(掌握) SQL映射器Mapper接口(掌握)Myb atis批量操作(理解掌握) (多对一)关联映射(掌握) (一对多,多对多)集合映射 MyBatis原理回顾(Obj ...
- hadoop集群的规划和搭建
1.操作系统版本:CentOS 6 CM版本:CM5.x CDH版本:CDH5.x 2.安装操作系统,对系统盘做 RAID1: 配置静态IP.hostname信息:vim /etc/sysconfig ...