元类


Ref: 使用元类

动态创建一个类

  • 动态创建一个类 by type()

type()函数既可以返回一个对象的类型,又可以创建出新的类型。所以,不仅可以动态创建一个“对象”,也可以创建一个“类”。

比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:

>>> def fn(self, name='world'):   # 先预先定义好函数
... print('Hello, %s.' % name) >>> Hello = type('Hello', (object,), dict(hello=fn))   # 创建Hello class

使用这个动态创建的新类:Hello

>>> h = Hello()
>>> h.hello()
Hello, world. >>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
  • 控制创建行为 by metaclass

先定义metaclass,就可以创建类,最后创建实例。

应用价值

  • 增强传统模式

给我们自定义的MyList增加一个add方法,作为加强版的list。

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs) # 继承了list类,在此基础上再做metaclass操作
# 它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建
# 在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
class MyList(list, metaclass=ListMetaclass):
pass

>>> L = MyList()
>>> L.add(1)
>>> L.add(2)
>>> L
[1, 2]

  • 实现ORM框架

要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

 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') ######################
# 编写类的模板 metaclass
###################### class ModelMetaclass(type): def __new__(cls, name, bases, attrs): if name=='Model':
return type.__new__(cls, name, bases, attrs) # 加强 dict
print('Found model: %s' % name)
mappings = dict() for k, v in attrs.items():
# 利用了“父类”代理权的特性
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v # Found mapping: email ==> <StringField:email>
# Found mapping: password ==> <StringField:password>
# Found mapping: id ==> <IntegerField:uid>
# Found mapping: name ==> <StringField:username> for k in mappings.keys():
attrs.pop(k) attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致 return type.__new__(cls, name, bases, attrs) ######################
# 加强版的 dict: Model
###################### class Model(dict, metaclass=ModelMetaclass): def __init__(self, **kw):
super(Model, self).__init__(**kw) 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 # 可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。
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.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args)) ############################################
# client 如何使用,示范如下
############################################ class User(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password') # 创建一个实例,保留了dict的初始化特性
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd') # 保存到数据库:
u.save()

ORM 示范

装饰器类


Ref: https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p08_define_decorators_as_part_of_class.html

一、使用类中的函数

之前是直接使用函数,这里只是变为:使用类或者对象中的函数,没有本质区别。

from functools import wraps

class A:
# Decorator as an instance method
def decorator1(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper # Decorator as a class method
@classmethod
def decorator2(cls, func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper

一个是实例调用,一个是类调用。

# As an instance method
a = A()
@a.decorator1
def spam():
pass
# As a class method
@A.decorator2
def grok():
pass

给类或静态方法提供装饰器是很简单的,不过要确保装饰器在 @classmethod 或 @staticmethod 之前。

也就是@classmethod 或 @staticmethod写在靠上的位置。

二、类作为装饰器

目的:为某个函数添加新的属性/方法,例如 “被调用” 的次数。

这个代码逻辑复杂,只能当作模板去背下了。

import types
from functools import wraps class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0 def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs) def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)

效果:函数成了类,或者说带有了“chain"的感觉。

@Profiled
def add(x, y):
return x + y class Spam:
@Profiled
def bar(self, x):
print(self, x) # 在交互环境中的使用示例:
>>> add(2, 3)
5
>>> add(4, 5)
9
>>> add.ncalls
2
>>> s = Spam()
>>> s.bar(1)
<__main__.Spam object at 0x10069e9d0> 1
>>> s.bar(2)
<__main__.Spam object at 0x10069e9d0> 2
>>> s.bar(3)
<__main__.Spam object at 0x10069e9d0> 3
>>> Spam.bar.ncalls
3

三、函数装饰某个类

先写装饰器,如下。因为增强类中的这个函数,所以修改之前,需要利用 orig_getattribute 先保存一下。

def log_getattribute(cls):
# 备份下类中原有的某个函数
orig_getattribute = cls.__getattribute__ # 定义替代品
def new_getattribute(self, name):
print('getting:', name) # 在原有的函数基础上,在之前先打印日志
return orig_getattribute(self, name) # 重新赋予新的替代品
cls.__getattribute__ = new_getattribute
return cls

增强这个类中的内置函数。

# Example use
@log_getattribute
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass

下面是使用效果:

>>> a = A(42)
>>> a.x
getting: x

>>> a.spam()
getting: spam
>>>

实战分析


datetime实现剖析

  • 小白鼠选型

In [52]: import datetime                                                                                                           

In [53]: datetime.__file__
Out[53]: '/usr/local/anaconda3/lib/python3.7/datetime.py'
  • 类初始化

自然地,也包括之后的@classmethod实现一部分构造函数,以及@property读取成员变量。

class datetime(date):
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) The year, month and day arguments are required. tzinfo may be None, or an
instance of a tzinfo subclass. The remaining arguments may be ints.
"""
# (1) 成员变量继承限制
__slots__ = date.__slots__ + time.__slots__ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
# (2) 类型checker
if (isinstance(year, (bytes, str)) and len(year) == 10 and 1 <= ord(year[2:3])&0x7F <= 12):
# Pickle support
if isinstance(year, str):
try:
year = bytes(year, 'latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a datetime object. "
"pickle.load(data, encoding='latin1') is assumed.") # (3) 技巧得到self
self = object.__new__(cls)
self.__setstate(year, month)
self._hashcode = -1
return self
year, month, day = _check_date_fields(year, month, day)
hour, minute, second, microsecond, fold = _check_time_fields(hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._tzinfo = tzinfo
self._hashcode = -1
self._fold = fold
return self

新特性:__new__

Ref: python的__new__方法

最重要的 “有了返回值”,就是返回了self。

class Person(object):

    def __new__(cls, name, age):
print '__new__ called.'
return super(Person, cls).__new__(cls, name, age) def __init__(self, name, age):
print '__init__ called.'
self.name = name
self.age = age def __str__(self):
return '<Person: %s(%s)>' % (self.name, self.age)

if __name__ == '__main__':
name = Person('xxx', 24)
print(name)

什么时候用呢?

new方法主要是当你 继承一些不可变的class时 (比如 int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。

具体我们可以用int来作为一个例子:这是一个不可变的类,但我们还想继承它的优良特性,怎么办?

class PositiveInteger(int):
def __new__(cls, value):
return super(PositiveInteger, cls).__new__(cls, abs(value)) i = PositiveInteger(-3)
print i

单例模式

单例模式,需要在类的”创建部分“做一些手脚,也就自然和__new__扯上关系。

因为__new__中的 object.__new__(cls),才会有了__init__中的self的赋值。

 class Singleton(object):
__instance = None
__first_init = True def __new__(cls, age, name):
if not cls.__instance:
cls.__instance = object.__new__(cls)
return cls.__instance def __init__(self, age, name):
if self.__first_init:
self.age = age
self.name = name
Singleton.__first_init = False a = Singleton(18, "xxx")
b = Singleton(8, "xxx")  # (1) 其实是同一个对象 print(id(a))  # (2) 此处相等,也说明了是"同一个对象"
print(id(b))
139953926130600
139953926130600 print(a.age)
print(b.age)  # (3) 因为是同一个对象,即使b的__init__没做什么,b依然可以拿来去用,因为b实际上就是a的引用
18
18
a.age = 19
print(b.age)
19

Singleton实现

但,慎用单例模式!

对程序架构而言,单例意味着没有隐藏,插入到程序的任何组件都可以随时修改它,这客观上违背了面向对象的最小公开法则,程序健壮性安全性骤降。
对程序扩展性而言,单例意味着很难被继承重写!  当你在一个单例中尝试覆盖它的某些功能时,编译器会报错,这时候你哭去吧。或者,你会奇怪的发现,咦?怎么还是基类的功能!
对程序逻辑而言,单例,顾名思义,仅有其一,但你的程序在发展着,你确定只有一个实例即可?某天突然发现,业务变了,需要扩展,那些数不清的用单例调用的代码怎么处理?尤其是已经发布到顾客手中的代码?
对效率而言,每次访问它的时候,都会查看是否被创建(多增加了一次判断),这对于一些需要高性能的程序是非常不合适的。
对多线程而言,在多线程环境下,实现单例是需要技巧的。否则,单例不单!
对对象生命周期而言,单例只有自己持有真正的引用,,如何销毁何时销毁都是问题,可能还会造成指针悬挂。
对开发者而言,一个类不能new! 这还会带来更多的疑问和混淆。
  • 魔术方法的实现

Ref: Python 魔术方法指南

/* implemnent */

End.

[Advanced Python] 11 - Implement a Class的更多相关文章

  1. Advanced Installer 11.9基于IIS打包札记(For MySQL)

    原文:Advanced Installer 11.9基于IIS打包札记(For MySQL) Mysql免安装前期部署 下载绿色命令行版本的mysql,将其放入到发布的程序发布包内,执行Update批 ...

  2. [Advanced Python] 14 - "Generator": calculating prime

    高性能编程 几个核心问题 • 生成器是怎样节约内存的?• 使用生成器的最佳时机是什么?• 我如何使用 itertools 来创建复杂的生成器工作流?• 延迟估值何时有益,何时无益? From: htt ...

  3. [Advanced Python] 15 - "Metaclass": ORM

    From: 使用元类 动态创建类 与静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的. 一 .type()动态创建 我们说class的定义是运行时动态创建的: 而创建cl ...

  4. think in python 11 字典

    字典 字典类似于列表,但更加通用 键值对 ,字典是 键与值之间的映射,每个键都映射到一个值上 dict可以创建一个不包含任何项的字典 eng2sp = dict() print eng2sp 还可以给 ...

  5. [Leetcode][Python]28: Implement strStr()

    # -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 28: Implement strStr()https://oj.leetco ...

  6. selenium-webdriver(python) 11

    selenium-webdriver(python) (十一) 本节重点: 控制滚动条到底部 有时候我们需要控制页面滚动条上的滚动条,但滚动条并非页面上的元素,这个时候就需要借助js是来进行操作.一般 ...

  7. 【Python 11】汇率兑换4.0(函数)

    1.案例描述 设计一个汇率换算程序,其功能是将美元换算成人民币,或者相反. 2.0增加功能:根据输入判断是人民币还是美元,进行相应的转换计算 3.0增加功能:程序可以一直运行,知道用户选择退出 4.0 ...

  8. python 11

    # 一.闭包 # # 判断:函数名.__closure__ # 若返回cell,则是闭包,返回None则不是闭包. # # 闭包:内层函数对外层函数非全局变量的引用就叫闭包. def func1(x) ...

  9. [ Python - 11 ] 多线程及GIL全局锁

    1. GIL是什么? 首先需要明确的一点是GIL并不是python的特性, 它是在实现python解析器(Cpython)时所引入的一个概念. 而Cpython是大部分环境下默认的python执行环境 ...

随机推荐

  1. python+unittest框架第四天unittest之断言(一)

    unittest中的测试断言分两天总结,hhh其实内容不多,就是懒~ 断言的作用是什么?  答:设置测试断言以后,能帮助我们判断测试用例执行结果. 我们先看下unittest支持的断言有哪些: 对上面 ...

  2. h5中div边距去除

    style样式里面加上 <style> *{ margin:0 ;//外边距为0 padding:0;//内边距为0 } </style>

  3. springBoot项目配置日志打印管理(log4j2)

    1.修改pom文件引用log4j2相关jar包 依赖代码: <!-- log4j2 start --><!-- Spring Boot log4j2依赖 --><depe ...

  4. 解决ionic 上拉加载组件 ion-infinite-scroll自动调用多次的问题

    ionic 中一个上拉刷新的组件 ion-infinite-scroll,如果页面未填充满页面高度,会自动检测并无限调用多次加载更多的函数:当然,主要会导致首次调用的时候,会执行几次加载更多的函数: ...

  5. 命令行通过入参调用jar包

    命令行通过入参调用jar包 最近因为项目需要,需要实现一个功能,即定时执行服务器上的一个脚本去对数据库的数据进行业务处理,要操作的数据库有很多种,mysql.db2.oracle.sqlserver等 ...

  6. 《阿里巴巴Java开发手册1.4.0》阅读总结与心得(三)

      (六)工程结构 (一)应用分层 1. [推荐]图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web 层,也可以直接依赖于 Service 层,依此类推:  开放接口层: ...

  7. HDU 5919 - Sequence II (2016CCPC长春) 主席树 (区间第K小+区间不同值个数)

    HDU 5919 题意: 动态处理一个序列的区间问题,对于一个给定序列,每次输入区间的左端点和右端点,输出这个区间中:每个数字第一次出现的位子留下, 输出这些位子中最中间的那个,就是(len+1)/2 ...

  8. Aizu-2224Save your cats并查集+最小生成树

    Save your cats 题意:存在n个点,有m条边( input中读入的是 边的端点,要先转化为边的长度 ),做一个最小生成树,使得要去除的边的长度总和最小: 思路:利用并查集和求最小生成树的方 ...

  9. lightoj 1036 - A Refining Company(简单dp)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1036 题解:设dp[i][j]表示处理到(i,j)点时的最大值然后转移显然是 ...

  10. 一台Linux服务器可以负载多少个连接?

    首先我们来看如何标识一个TCP连接?系统是通过一个四元组来识别,(src_ip,src_port,dst_ip,dst_port)即源IP.源端口.目标IP.目标端口.比如我们有一台服务192.168 ...