[Advanced Python] 11 - Implement a Class
元类
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实现
但,慎用单例模式!
魔术方法的实现
Ref: Python 魔术方法指南
/* implemnent */
End.
[Advanced Python] 11 - Implement a Class的更多相关文章
- Advanced Installer 11.9基于IIS打包札记(For MySQL)
原文:Advanced Installer 11.9基于IIS打包札记(For MySQL) Mysql免安装前期部署 下载绿色命令行版本的mysql,将其放入到发布的程序发布包内,执行Update批 ...
- [Advanced Python] 14 - "Generator": calculating prime
高性能编程 几个核心问题 • 生成器是怎样节约内存的?• 使用生成器的最佳时机是什么?• 我如何使用 itertools 来创建复杂的生成器工作流?• 延迟估值何时有益,何时无益? From: htt ...
- [Advanced Python] 15 - "Metaclass": ORM
From: 使用元类 动态创建类 与静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的. 一 .type()动态创建 我们说class的定义是运行时动态创建的: 而创建cl ...
- think in python 11 字典
字典 字典类似于列表,但更加通用 键值对 ,字典是 键与值之间的映射,每个键都映射到一个值上 dict可以创建一个不包含任何项的字典 eng2sp = dict() print eng2sp 还可以给 ...
- [Leetcode][Python]28: Implement strStr()
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 28: Implement strStr()https://oj.leetco ...
- selenium-webdriver(python) 11
selenium-webdriver(python) (十一) 本节重点: 控制滚动条到底部 有时候我们需要控制页面滚动条上的滚动条,但滚动条并非页面上的元素,这个时候就需要借助js是来进行操作.一般 ...
- 【Python 11】汇率兑换4.0(函数)
1.案例描述 设计一个汇率换算程序,其功能是将美元换算成人民币,或者相反. 2.0增加功能:根据输入判断是人民币还是美元,进行相应的转换计算 3.0增加功能:程序可以一直运行,知道用户选择退出 4.0 ...
- python 11
# 一.闭包 # # 判断:函数名.__closure__ # 若返回cell,则是闭包,返回None则不是闭包. # # 闭包:内层函数对外层函数非全局变量的引用就叫闭包. def func1(x) ...
- [ Python - 11 ] 多线程及GIL全局锁
1. GIL是什么? 首先需要明确的一点是GIL并不是python的特性, 它是在实现python解析器(Cpython)时所引入的一个概念. 而Cpython是大部分环境下默认的python执行环境 ...
随机推荐
- [ZJOI2011]看电影(组合数学,高精度)
[ZJOI2011]看电影 这题模型转化很巧妙.(神仙题) 对于这种题首先肯定知道答案就是合法方案除以总方案. 总方案显然是\(k^n\). 那么考虑怎么算合法方案. 当\(n>k\)的时候显然 ...
- 微服务架构 - 网关 Spring Cloud Gateway
Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特 ...
- spring data jpa 的使用
使用spring data jpa 开发时,发现国内对spring boot jpa全面介绍的文章比较少案例也比较零碎,因此写文章总结一下. spring data jpa介绍 首先了解JPA是什么? ...
- springboot的log4j配置与logback配置
log4j配置的依赖 <!-- 删除pom.xml文件中所有对日志jar包的引用--> <dependency> <groupId>org.springframew ...
- [WPF自定义控件库] 关于ScrollViewr和滚动轮劫持(scroll-wheel-hijack)
1. 什么是滚动轮劫持 这篇文章介绍一个很简单的继承自ScrollViewer的控件: public class ExtendedScrollViewer : ScrollViewer { prote ...
- 搭建SFTP服务器,允许一个或多个用户拥有一个或多个目录的rwx权限
1.引言 sftp可以为传输文件提供一种安全的网络的加密方法.sftp 与 ftp 有着几乎一样的语法和功能.SFTP 为 SSH的其中一部分,是一种传输档案至 Blogger 伺服器的安全方式.其实 ...
- vue-小爱ADMIN系列文章(二):微信微博等分享,国际化,前端性能优化,nginx服务器部署
最近在做我的小爱ADMIN后台管理系统,结合当前市场后台管理系统对相关功能的需求,我又开始新增了一些新的功能和组件,如分享功能组件,项目国际化功能:项目完成后,部署在nginx服务器,发现首次访问的速 ...
- Python--编码与字符串
为什么字符串要编码呢? 因为计算机只能处理数字,最底层的CPU只能识别0和1.所以字符串就需要编码成对应的数字. 在计算机中,最开始只有ASCII,我们开始接触计算机编程时就学了ASCII码.最早只有 ...
- [python]文档字符串
文档字符串可以在运行时访问,也可以用来自动生成文档. 输入: def foo(): print "This is a doc string" return True foo() 运 ...
- bzoj 1051 [HAOI2006]受欢迎的牛(tarjan缩点)
题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1051 题解:缩点之后判断出度为0的有几个,只有一个那么输出那个强连通块的点数,否者 ...