元类


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. [ZJOI2011]看电影(组合数学,高精度)

    [ZJOI2011]看电影 这题模型转化很巧妙.(神仙题) 对于这种题首先肯定知道答案就是合法方案除以总方案. 总方案显然是\(k^n\). 那么考虑怎么算合法方案. 当\(n>k\)的时候显然 ...

  2. 微服务架构 - 网关 Spring Cloud Gateway

    Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特 ...

  3. spring data jpa 的使用

    使用spring data jpa 开发时,发现国内对spring boot jpa全面介绍的文章比较少案例也比较零碎,因此写文章总结一下. spring data jpa介绍 首先了解JPA是什么? ...

  4. springboot的log4j配置与logback配置

    log4j配置的依赖 <!-- 删除pom.xml文件中所有对日志jar包的引用--> <dependency> <groupId>org.springframew ...

  5. [WPF自定义控件库] 关于ScrollViewr和滚动轮劫持(scroll-wheel-hijack)

    1. 什么是滚动轮劫持 这篇文章介绍一个很简单的继承自ScrollViewer的控件: public class ExtendedScrollViewer : ScrollViewer { prote ...

  6. 搭建SFTP服务器,允许一个或多个用户拥有一个或多个目录的rwx权限

    1.引言 sftp可以为传输文件提供一种安全的网络的加密方法.sftp 与 ftp 有着几乎一样的语法和功能.SFTP 为 SSH的其中一部分,是一种传输档案至 Blogger 伺服器的安全方式.其实 ...

  7. vue-小爱ADMIN系列文章(二):微信微博等分享,国际化,前端性能优化,nginx服务器部署

    最近在做我的小爱ADMIN后台管理系统,结合当前市场后台管理系统对相关功能的需求,我又开始新增了一些新的功能和组件,如分享功能组件,项目国际化功能:项目完成后,部署在nginx服务器,发现首次访问的速 ...

  8. Python--编码与字符串

    为什么字符串要编码呢? 因为计算机只能处理数字,最底层的CPU只能识别0和1.所以字符串就需要编码成对应的数字. 在计算机中,最开始只有ASCII,我们开始接触计算机编程时就学了ASCII码.最早只有 ...

  9. [python]文档字符串

    文档字符串可以在运行时访问,也可以用来自动生成文档. 输入: def foo(): print "This is a doc string" return True foo() 运 ...

  10. bzoj 1051 [HAOI2006]受欢迎的牛(tarjan缩点)

    题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1051 题解:缩点之后判断出度为0的有几个,只有一个那么输出那个强连通块的点数,否者 ...