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%的人都不得要领,一些自以为搞明白元类的人其实也是自圆其说,点到为止,从队元类的控制上来看就破绽百出,逻辑混乱: 二.什么是元类 一切源自于一 ...
随机推荐
- Fiddler_菜单栏介绍_02
Fiddler界面 [Statistics]请求的性能数据分析 [Inspectors]查看数据内容 Inspectors是用于查看会话的内容,上半部分是请求的内容,下半部分是响应的内容: [Auto ...
- json和pickle模块
import pickleimport json data = {'k1': 123, 'k2': 'Hello'}print(type(data))# p_str = pickle.dumps(da ...
- centos7 + php7
安装php7 https://www.zntec.cn/archives/centos7-lamp.html composer安装tp curl -sS https://getcomposer.org ...
- zabbix自定义监控项、添加图形、设置触发器、远程执行命令
监控项是在zabbix中手机数据的基础,没有监控项就没有数据,系统自带模板带有大量默认item,自定义item可以定义在模板中,在应用模板即可使用对应item:也可直接在host中定义 目标:自定义监 ...
- STM32 内部flash的读写程序
/* Base address of the Flash sectors */ #define ADDR_FLASH_SECTOR_0 ((uint32_t)0x08000000) /* Base ...
- springboot读取application.properties中自定义配置
假设在application-xxx.properties中配置 user.name=yuhk 一.在Controller中读取 @Value("{$user.name}") pr ...
- Linux之文件权限
在Linux系统中,root用户基本对于每个文件都有可操作性,但是普通用户可能只能查看特定的文件,这是因为文件存在的权限机制,初步掌握文件的基本权限就操作可以对一些系统文件或者自定义文件有一个操作空间 ...
- eclipse 没有web项目和server
New项目中没有web Window菜单的preference没有server 解决方法:打开help->Install new software… 在work with中找到http://do ...
- git之reset图解
https://blog.csdn.net/longintchar/article/details/81843048 1.三棵树. 此时如果我们运行 git status,会发现没有任何改动,因为现在 ...
- winsock I/O模型的分析
几种winsock I/O模型的分析 套接字是通信的基础,是支持网络协议数据通信的基本接口.Winsocket 提供了一些有趣的I/O模型,有助于应用程序通过一种“异步”方式,一次对一个或者多个套接字 ...