一、捕获类的属性定义顺序
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 10:04
# @Author : Maple
# @File : 14-捕获类的属性定义顺序.py
from collections import OrderedDict
"""
你想自动记录一个类中属性和方法定义的顺序, 然后可以利用它来做很多操作(比如序列化、映射到数据库等等)。
"""
# A set of descriptors for various types
class Typed:
_expected_type = type(None)
def __init__(self, name=None):
self._name = name
def __set__(self, instance, value):
if not isinstance(value, self._expected_type):
raise TypeError('Expected ' + str(self._expected_type))
instance.__dict__[self._name] = value
class Integer(Typed):
_expected_type = int
class Float(Typed):
_expected_type = float
class String(Typed):
_expected_type = str
# Metaclass that uses an OrderedDict for class body
class OrderedMeta(type):
def __new__(cls, clsname, bases, clsdict):
d = dict(clsdict)
order = []
for name, value in clsdict.items():
if isinstance(value, Typed):
value._name = name
order.append(name)
# 为clsdict新增了一个类属性_order,然后里面存放的是(以Stock为例):[name,shares,price]
d['_order'] = order
# 创建类,
return type.__new__(cls, clsname, bases, d)
@classmethod
def __prepare__(cls, clsname, bases):
return OrderedDict()
class Structure(metaclass=OrderedMeta):
def as_csv(self):
# Structure类中有一个类属性:_order,并且里面存放的是[name,shares,price]
# self._order --> 通过实例访问类属性
return ','.join(str(getattr(self,name)) for name in self._order)
# Example use
class Stock(Structure):
name = String()
shares = Integer()
price = Float()
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
二、定义有可选参数的元类
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 10:23
# @Author : Maple
# @File : 15-定义有可选参数的元类.py
"""
为了使元类支持关键字参数,你必须确保在 __prepare__() , __new__() 和 __init__() 方法中都使用强制关键字参数
"""
class Mymeta(type):
@classmethod
def __prepare__(metacls, name, bases,*,debug=False, synchronize=True):
return super().__prepare__(name,bases)
def __new__(cls, name, bases,ns,*,debug=False, synchronize=True):
return super().__new__(cls,name, bases,ns)
def __init__(cls,name,bases,ns,*,debug=False,synchronize=True):
super(Mymeta, cls).__init__(name,bases,ns)
class Spam(metaclass=Mymeta,debug= True,synchronize=False):
def __init__(self,name):
self.name = name
"""
应用举例:
假设我们在开发一个插件系统,希望能够自动注册所有插件类,而不需要在代码中显式注册它们。我们可以使用元类来捕获类的创建,并根据传入的额外参数决定是否注册这个类
PluginRegistryMeta 元类检查创建类时传入的 register 参数,如果为 True(默认),则将新创建的类注册到一个字典中
这样,我们就可以在不需要每次手动注册每个类的情况下,自动管理插件系统中的所有插件类
"""
class PluginRegistryMeta(type):
registry = {}
def __new__(mcs, name, bases, attrs, register=True, **kwargs):
new_cls = super().__new__(mcs, name, bases, attrs)
if register:
mcs.registry[name] = new_cls
print(f'Registered {name}')
return new_cls
class BasePlugin(metaclass=PluginRegistryMeta):
pass
# 这个类将被自动注册
class MyPlugin(BasePlugin):
pass
# 这个类将不会被注册
class MyPrivatePlugin(BasePlugin, register=False):
pass
if __name__ == '__main__':
# Registered BasePlugin
# Registered MyPlugin
print(PluginRegistryMeta.registry) # {'BasePlugin': <class '__main__.BasePlugin'>, 'MyPlugin': <class '__main__.MyPlugin'>}
三、args和kwars的强制参数签名
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 20:26
# @Author : Maple
# @File : 16-args和kwars的强制参数签名.py
import inspect
from inspect import Signature,Parameter
"""
你有一个函数或方法,它使用*args和**kwargs作为参数,这样使得它比较通用
但有时候你想检查传递进来的参数是不是某个你想要的类型。
"""
# 1. 定义标签对象
parms = [ Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42),
Parameter('z', Parameter.KEYWORD_ONLY, default=None) ]
sig = Signature(parms)
# 2. 使用标签对象绑定参数
def fun(*args,**kwargs):
bound_valus = sig.bind(*args,**kwargs)
for name,value in bound_valus.arguments.items():
print(name,value)
# 3.利用参数签名,强制函数遵循特定的规则
def make_sig(*names):
parms = [Parameter(name,Parameter.POSITIONAL_OR_KEYWORD) for name in names]
return Signature(parms)
# 定义基类
class Structure:
__signature__ = make_sig()
def __init__(self,*args,**kwargs):
bound_values = self.__signature__.bind(*args,**kwargs)
for name,value in bound_values.arguments.items():
setattr(self,name,value)
class Stock(Structure):
__signature__ = make_sig('name','shares','price')
class Point(Structure):
__signature__ = make_sig('x', 'y')
# 通过元类方式实现
from inspect import Signature, Parameter
def make_sig(*names):
parms = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
for name in names]
return Signature(parms)
"""
当我们自定义签名的时候,将签名存储在特定的属性 __signature__ 中通常是很有用的
这样的话,在使用 inspect 模块执行内省的代码就能发现签名并将它作为调用约定
"""
class StructureMeta(type):
def __new__(cls, clsname, bases, clsdict):
# 为要创建的类添加一个__signature__属性,然后它的值从_fields属性中获取
# 说明:clsdict是创建类时 传递的类中的属性和方法键值对,它会扫描创建类的定义体,然后获取对应的值
# 比如本例的Stock类,定义了一个类属性_fields,其值为['name', 'shares', 'price']
# 因此Stock.__signature__中会存放 Signature([Parameter(name,Parameter.POSITIONAL_OR_KEYWORD....])
clsdict['__signature__'] = make_sig(*clsdict.get('_fields',[]))
return super().__new__(cls, clsname, bases, clsdict)
class Structure(metaclass=StructureMeta):
_fields = []
def __init__(self, *args, **kwargs):
# 通过实例访问类属性__signature__,获取存放在里面的值,然后绑定参数
bound_values = self.__signature__.bind(*args, **kwargs)
for name, value in bound_values.arguments.items():
setattr(self, name, value)
# Example
class Stock2(Structure):
_fields = ['name', 'shares', 'price']
class Point2(Structure):
_fields = ['x', 'y']
if __name__ == '__main__':
# 1.标签测试
print(sig) #(x, y=42, *, z=None)
# 2.标签对象绑定参数测试
"""
x 1
y 2
z 5
"""
fun(1,2,z= 5)
# 3.强制签名测试
s1 = Stock('ACME',100,490.1)
try:
s2 = Stock('ACME', 100)
except Exception as e:
print(e) # missing a required argument: 'price'
# 4. 元类方式强制签名测试
print('*******4. 元类方式强制签名测试*********************')
s3 = Point2(1, 2)
# 签名存储在特定的属性 __signature__中,inspect模块执行内省的代码就能发现签名
print(inspect.signature(Stock2)) # (name, shares, price)
print(s3) # <__main__.Point2 object at 0x0000012CC7D995B0>
try:
s4 = Point2('1')
except Exception as e:
print(e) # missing a required argument: 'y'
四、类上强制使用编程规约
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 21:40
# @Author : Maple
# @File : 17-类上强制使用编程规约.py
from inspect import signature
import logging
"""
你的程序包含一个很大的类继承体系,你希望强制执行某些编程规约(或者代码诊断)来帮助程序员保持清醒。
"""
class MatchSignaturesMeta(type):
def __init__(self, clsname, bases, clsdict):
super().__init__(clsname, bases, clsdict)
# super() 的调用形式(即传递相同的参数两次)在元类中比较少见,因为它实际上创建了一个绑定到当前类的super代理对象
# 这个对象将会从【父类】(也因此这段逻辑必须写在__init__中,而不能写在__new__中,因为new方法中,类还没有被创建)-开始查找方法或属性
# 在这段代码中,sup 被用来访问在当前类的基类中已定义的方法
# self是值当前要创建的类,因此super(self, self)就是当前要创建类的父类
sup = super(self, self)
# name和value是当前类的方法名和方法
for name, value in clsdict.items():
if name.startswith('_') or not callable(value):
continue
# 获取父类的中定义的方法,比如本例中的foo
prev_dfn = getattr(sup,name,None)
if prev_dfn:
# 父类的方法签名
prev_sig = signature(prev_dfn)
# 当前类的方法签名
val_sig = signature(value)
if prev_sig != val_sig:
logging.warning('Signature mismatch in %s. %s != %s',
value.__qualname__, prev_sig, val_sig)
# Example
class Root(metaclass=MatchSignaturesMeta):
pass
class A(Root):
def foo(self, x, y):
pass
def spam(self, x, *, z):
pass
# Class with redefined methods, but slightly different signatures
class B(A):
def foo(self, a, b):
pass
def spam(self,x,z):
pass
if __name__ == '__main__':
"""WARNING:root:Signature mismatch in B.foo. (self, x, y) != (self, a, b)
WARNING:root:Signature mismatch in B.spam. (self, x, *, z) != (self, x, z)
"""
pass
五、以编程方式定义类
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 22:01
# @Author : Maple
# @File : 18-以编程方式定义类.py
import abc
import operator
import sys
"""
以使用函数 types.new_class() 来初始化新的类对象
你需要做的只是提供类的名字、父类元组、关键字参数,以及一个用成员变量填充类字典的回调函数
"""
# stock.py
# Example of making a class manually from parts
# Methods
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
cls_dict = {
'__init__' : __init__,
'cost' : cost,
}
# Make a class
import types
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__() #这里不能传参
cls.debug = kwargs.get('debug', False)
cls.typecheck = kwargs.get('typecheck', True)
def named_tuple(classname, fieldnames):
# 1. operator.itemgetter(n) 返回的是一个函数
# 2. 该函数作用于一个对象(如元组、列表或任何支持索引操作的对象)并返回该对象的第 n 个元素
# 3. 本例对类的对象的每个属性封装了一个 property,当通过对象obj.name访问属性时,实际上会调用该property
# 如上所属,该描述器会返回对象(元组等)的第n个位置的元素
cls_dict = { name: property(operator.itemgetter(n))
for n, name in enumerate(fieldnames) }
# Make a __new__ function and add to the class dict
def __new__(cls, *args):
# 这里实例化对象(并不是创建类,不要搞混了)
if len(args) != len(fieldnames):
raise TypeError('Expected {} arguments'.format(len(fieldnames)))
# 使用父类(tuple)的__new__方法创建类
return tuple.__new__(cls, args)
cls_dict['__new__'] = __new__
# Make the class
cls = types.new_class(classname, (tuple,), {},
lambda ns: ns.update(cls_dict))
# sys._getframe(1).f_globals['__name__'] 返回当前模块的名字
cls.__module__ = sys._getframe(1).f_globals['__name__']
return cls
if __name__ == '__main__':
# 测试1
# 第三个参数为{},相当于是创建了一个新的命名空间
"""
类的命名空间是一个字典,它存储了类中定义的所有属性和方法。
这包括类变量、类方法、静态方法、实例方法等。当类被定义时,所有在类块内部定义的函数和变量都会被加入到这个命名空间中。
"""
# 第四个参数用来更新命名空间
"""
本例中该函数的作用是将 cls_dict 字典中的__init__和cost方法添加到类 Stock 的命名空间中。
# 备注:ns表示namespace
"""
Stock = types.new_class('Stock', (), {}, lambda ns: ns.update(cls_dict))
# __module__ 属性包含定义它的模块名
print(Stock.__module__) # types
#测试2: 如果想创建的类需要一个不同的元类,可以通过 types.new_class() 第三个参数传递给它
print('*************测试2*******************')
Stock2 = types.new_class('Stock', (), {'metaclass': abc.ABCMeta},
lambda ns: ns.update(cls_dict))
print(Stock2.__dict__) # {..'_abc_impl': <_abc._abc_data object at 0x0000018428913B40>}}
print(type(Stock2)) #<class 'abc.ABCMeta'>
# 测试3:第三个参数还可以包含其他的关键字参数
"""
比如要创建如下的类:
class Spam(Base, debug=True, typecheck=False):
pass
"""
Spam = types.new_class('Spam', (Base,), {'debug': True, 'typecheck': False}, lambda ns: ns.update(cls_dict))
print('Span',Spam) # <class 'types.Spam'>
# 测试4:自定义named_tuple
Dog = named_tuple('Dog',['name','age'])
print(Dog.__module__) # __main__
dog = Dog('Lily',20)
print(dog) # ('Lily', 20)
六、定义时初始化成员
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-14 9:52
# @Author : Maple
# @File : 19-定义时初始化成员.py
"""
类被定义的时候就初始化一部分类的成员,而不是要等到实例被创建后
"""
import operator
class StructTupleMeta(type):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
# 在类定义的时候,就为其属性进行一些初始化操作
# 此例是为每个属性设置一个描述器,该描述器实现`按照位置返回元组对象中的元素`功能
# 关于property(operator.itemgetter(n))可参考18-以编程方式定义类
for n, name in enumerate(cls._fields):
setattr(cls, name, property(operator.itemgetter(n)))
class StructTuple(tuple, metaclass=StructTupleMeta):
_fields = []
def __new__(cls, *args):
if len(args) != len(cls._fields):
raise ValueError('{} arguments required'.format(len(cls._fields)))
# 创建实例对象
return super().__new__(cls,args)
class Stock(StructTuple):
_fields = ['name', 'shares', 'price']
class Point(StructTuple):
_fields = ['x', 'y']
if __name__ == '__main__':
# 1. Stock测试
stock = Stock('ACME',20,200)
print(stock[0]) #ACME
# 会调用描述器property(operator.itemgetter(0)),然后该描述器返回stock的第一个元素
print(stock.name) # ACME
try:
stock.name = 'maple'
except Exception as e:
print(e) # property of 'Stock' object has no setter
# 2. Point测试
p = Point(1,2)
print(p.x)
print(p.y)
七、利用函数注解实现方法重载
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-14 13:11
# @Author : Maple
# @File : 20-利用函数注解实现方法重载.py
# multiple.py
import inspect
import types
class MultiMethod:
'''
Represents a single multimethod.
'''
def __init__(self, name):
self._methods = {}
self.__name__ = name
def register(self, meth):
'''
Register a new method as a multimethod
'''
sig = inspect.signature(meth)
# Build a type signature from the method's annotations
types = []
for name, parm in sig.parameters.items():
if name == 'self':
continue
"检查比如方法bar中的参数x,后面是否带了int"
if parm.annotation is inspect.Parameter.empty:
raise TypeError(
'Argument {} must be annotated with a type'.format(name)
)
"检查比如方法bar中的参数x后面定义的数据类型(int,str等),是否为type的子类"
if not isinstance(parm.annotation, type):
raise TypeError(
'Argument {} annotation must be a type'.format(name)
)
if parm.default is not inspect.Parameter.empty:
self._methods[tuple(types)] = meth
types.append(parm.annotation)
"""
比如对于Spam中的两个bar方法
self._methods((<class 'int'>,<class 'int'>)] = <function Spam at OxOOO1....>
self._methods((<class 'str'>,<class 'int'>)] = <function Spam at OxOOO2....>
"""
self._methods[tuple(types)] = meth
def __call__(self, *args):
'''
Call a method based on type signature of the arguments
'''
# 以调用s.bar(2,3)为例:(<class 'int'>, <class 'int'>)
types = tuple(type(arg) for arg in args[1:])
meth = self._methods.get(types, None)
if meth:
return meth(*args)
else:
raise TypeError('No matching method for types {}'.format(types))
def __get__(self, instance, cls):
'''
self是MultiMethod实例,instance是Spam实例,types.MethodType的作用是将MultiMethod的实例做为一个属性绑定到后者上
'''
if instance is not None:
# 1.将 MultiMethod 的实例(比如m)绑定到instance(比如Spam实例s)上,这样可以通过s.m访问到m实例
# 2.因为s的属性bar中存放的其实是就是MultiMethod实例(register过程),所以s.bar本质上执行的就是s.m
# 3.同时m实现了call方法,m(2,3)会触发该方法,进而根据传递进来的参数类型,执行m中对应的bar方法
# 以调用s.bar(2,3)为例,返回一个绑定方法: <bound method bar of <__main__.Spam object at 0x000002134309EFF0>>
return types.MethodType(self, instance)
else:
return self
class MultiDict(dict):
'''
Special dictionary to build multimethods in a metaclass
'''
def __setitem__(self, key, value):
"""
1. 第一个bar注册的时候key not in self .self:{'__module__':'__main__','__qualname__':'Spam'},因此设置属性
设置完成后self:{...,'bar':<function Spam at OxOOOO....>
2. 第二个bar注册时候,key in self,但此时 current_value 是一个Spam.bar类型对象,因此先通过MultiMethod(key)实例化一个 MultiMethod
对象,然后调用该对象的register方法,分别注册上一个bar方法和这一个bar方法,最后再setitem,此时self:{....,'bar':'<MultiMethod object...>'}
3. 如果有第三个bar注册,key in self,此时 current_value 是MultiMethod对象,因此会在该对象中继续注册新的bar方法
注意: 上述三个bar是重载的3个不同方法
"""
if key in self:
# If key already exists, it must be a multimethod or callable
current_value = self[key]
if isinstance(current_value, MultiMethod):
current_value.register(value)
else:
mvalue = MultiMethod(key)
mvalue.register(current_value)
mvalue.register(value)
super().__setitem__(key, mvalue)
else:
super().__setitem__(key, value)
class MultipleMeta(type):
'''
Metaclass that allows multiple dispatch of methods
'''
def __new__(cls, clsname, bases, clsdict):
return type.__new__(cls, clsname, bases, dict(clsdict))
@classmethod
def __prepare__(cls, clsname, bases):
return MultiDict()
class Spam(metaclass=MultipleMeta):
def bar(self, x:int, y:int):
print('Bar 1:', x, y)
def bar(self, s:str, n:int = 0):
print('Bar 2:', s, n)
# 描述器实现方式
class multimethod2:
def __init__(self, func):
self._methods = {}
self.__name__ = func.__name__
self._default = func
def match(self, *types):
def register(func):
ndefaults = len(func.__defaults__) if func.__defaults__ else 0
for n in range(ndefaults+1):
# types[:len(types) - n]表示截取从开始位置- len(types) - n位置的元素(但不包括 len(types) - n 这个位置的元素)
# 如果方法有默认值,比如本例中的bar2,_methods中会存放两个键值对,分别是self._methods[(<class 'str'>,<class 'int'>)]
# 和self._methods[(<class 'str'>)]
self._methods[types[:len(types) - n]] = func
return self
return register
def __call__(self, *args):
types = tuple(type(arg) for arg in args[1:])
meth = self._methods.get(types, None)
if meth:
return meth(*args)
else:
return self._default(*args)
def __get__(self, instance, cls):
if instance is not None:
return types.MethodType(self, instance)
else:
return self
class Spam2:
@multimethod2
def bar(self, *args):
# Default method called if no match
raise TypeError('No matching method for bar')
@bar.match(int, int)
def bar(self, x, y):
print('Bar 1:', x, y)
@bar.match(str, int)
def bar(self, s, n = 0):
print('Bar 2:', s, n)
if __name__ == '__main__':
s = Spam()
s.bar(2,3)
print('*******************')
s2 = Spam2()
s2.bar('maple',3)
八、避免重复属性的方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-14 17:41
# @Author : Maple
# @File : 21-避免重复属性的方法.py
# 需要写很多重复代码
class Person:
def __init__(self, name ,age):
self.name = name
self.age = age
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('name must be a string')
self._name = value
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError('age must be an int')
self._age = value
# 定义一个生成描述器的函数
def typed_property(name, expected_type):
storage_name = '_' + name
@property
def prop(self):
return getattr(self, storage_name)
@prop.setter
def prop(self, value):
if not isinstance(value, expected_type):
raise TypeError('{} must be a {}'.format(name, expected_type))
setattr(self, storage_name, value)
return prop
# Example use
class Person:
name = typed_property('name', str)
age = typed_property('age', int)
def __init__(self, name, age):
# self.name会调用描述器的setter方法
self.name = name
self.age = age
if __name__ == '__main__':
p = Person('Maple',12)
print(p.name) # Maple
print(p.age) #12
print('******************')
try:
p2 = Person('Maple','12')
except Exception as e:
print(e) # age must be a <class 'int'>
九、在局部变量域中执行代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-14 17:51
# @Author : Maple
# @File : 22-在局部变量域中执行代码.py
import time
from contextlib import contextmanager
"""
实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。
"""
# 上下文管理器的常规写法
import time
class timethis:
def __init__(self, label):
self.label = label
def __enter__(self):
self.start = time.time()
def __exit__(self, exc_ty, exc_val, exc_tb):
end = time.time()
print('{}: {}'.format(self.label, end - self.start))
# 利用装饰器实现上下文管理器
@contextmanager
def timethis(label):
print('yield之前的代码')
start = time.time()
try:
# yield之前的代码会在上下文管理器中作为__enter__()方法执行
yield
print('yield之后的代码')
# yield 之后的代码会作为 __exit__() 方法执行。 如果出现了异常,异常会在yield语句那里抛出
finally:
end = time.time()
print('{}: {}'.format(label, end - start))
@contextmanager
def list_transaction(orig_list):
working = list(orig_list)
yield working
orig_list[:] = working
if __name__ == '__main__':
# 案例1
"""
yield之前的代码
yield之后的代码
counting: 0.5752782821655273
"""
with timethis('counting'):
n = 10000000
while n > 0:
n -= 1
# 案例2
items = [1,2,3]
with list_transaction(items) as working:
"""
1. items赋值给working,停在working
2. 执行working.append
3. 将working从新赋值给items
"""
working.append(4)
working.append(5)
print(items) # [1, 2, 3, 4, 5]