本文主要是介绍Python3-Cookbook(第九章) - 元编程Part3,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、捕获类的属性定义顺序
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024-04-13 10:04
# @Author : Maple
# @File : 14-捕获类的属性定义顺序.pyfrom collections import OrderedDict"""
你想自动记录一个类中属性和方法定义的顺序, 然后可以利用它来做很多操作(比如序列化、映射到数据库等等)。
"""# A set of descriptors for various types
class Typed:_expected_type = type(None)def __init__(self, name=None):self._name = namedef __set__(self, instance, value):if not isinstance(value, self._expected_type):raise TypeError('Expected ' + str(self._expected_type))instance.__dict__[self._name] = valueclass Integer(Typed):_expected_type = intclass Float(Typed):_expected_type = floatclass 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 = nameorder.append(name)# 为clsdict新增了一个类属性_order,然后里面存放的是(以Stock为例):[name,shares,price]d['_order'] = order# 创建类,return type.__new__(cls, clsname, bases, d)@classmethoddef __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 = nameself.shares = sharesself.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):@classmethoddef __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_clsprint(f'Registered {name}')return new_clsclass BasePlugin(metaclass=PluginRegistryMeta):pass# 这个类将被自动注册
class MyPlugin(BasePlugin):pass# 这个类将不会被注册
class MyPrivatePlugin(BasePlugin, register=False):passif __name__ == '__main__':# Registered BasePlugin# Registered MyPluginprint(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, Parameterdef 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 1y 2z 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-类上强制使用编程规约.pyfrom 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# 获取父类的中定义的方法,比如本例中的fooprev_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):passclass A(Root):def foo(self, x, y):passdef spam(self, x, *, z):pass# Class with redefined methods, but slightly different signatures
class B(A):def foo(self, a, b):passdef spam(self,x,z):passif __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 = nameself.shares = sharesself.price = pricedef cost(self):return self.shares * self.pricecls_dict = {'__init__' : __init__,'cost' : cost,
}# Make a class
import typesclass 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 dictdef __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 classcls = 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 clsif __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_tupleDog = 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 operatorclass 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) # ACMEtry: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 typesclass MultiMethod:'''Represents a single multimethod.'''def __init__(self, name):self._methods = {}self.__name__ = namedef register(self, meth):'''Register a new method as a multimethod'''sig = inspect.signature(meth)# Build a type signature from the method's annotationstypes = []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)] = methtypes.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)] = methdef __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 selfclass 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 callablecurrent_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))@classmethoddef __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 = funcdef match(self, *types):def register(func):ndefaults = len(func.__defaults__) if func.__defaults__ else 0for 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]] = funcreturn selfreturn registerdef __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 selfclass Spam2:@multimethod2def bar(self, *args):# Default method called if no matchraise 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 = nameself.age = age@propertydef name(self):return self._name@name.setterdef name(self, value):if not isinstance(value, str):raise TypeError('name must be a string')self._name = value@propertydef age(self):return self._age@age.setterdef 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@propertydef prop(self):return getattr(self, storage_name)@prop.setterdef 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 = nameself.age = ageif __name__ == '__main__':p = Person('Maple',12)print(p.name) # Mapleprint(p.age) #12print('******************')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-在局部变量域中执行代码.pyimport time
from contextlib import contextmanager"""
实现一个新的上下文管理器的最简单的方法就是使用 contexlib 模块中的 @contextmanager 装饰器。
"""# 上下文管理器的常规写法
import timeclass timethis:def __init__(self, label):self.label = labeldef __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__()方法执行yieldprint('yield之后的代码')# yield 之后的代码会作为 __exit__() 方法执行。 如果出现了异常,异常会在yield语句那里抛出finally:end = time.time()print('{}: {}'.format(label, end - start))@contextmanager
def list_transaction(orig_list):working = list(orig_list)yield workingorig_list[:] = workingif __name__ == '__main__':# 案例1"""yield之前的代码yield之后的代码counting: 0.5752782821655273"""with timethis('counting'):n = 10000000while n > 0:n -= 1# 案例2items = [1,2,3]with list_transaction(items) as working:"""1. items赋值给working,停在working2. 执行working.append3. 将working从新赋值给items"""working.append(4)working.append(5)print(items) # [1, 2, 3, 4, 5]
这篇关于Python3-Cookbook(第九章) - 元编程Part3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!