本章主要说一点高级的编程技巧,python中也算是比较常用的一种技巧--元类。有点类似于拦截器或AOP的功能。
一、控制类的创建
对象的类型叫作类,类的类型叫作元类。实例对象由类创建,而类则是由元类创建。类进行创建时会优先执行以下几个方法,通过复写这几个方法就可以控制类的创建过程,规范化代码,然后在类定义时使用metaclass来定义行为,以下三个方法会按顺序执行;
- __prepare__(定义类)
- __new__(实例创建之前)
- __init__(实例创建时)
- __call__(实例创建时)
1.1、基础
class NoInstances(type):
    def __call__(self, *args, **kwargs):
        raise TypeError("Can't instantiate directly")
# Example,这里最主要的是metacclass的运用
class Spam(metaclass=NoInstances):
    
    def grok(x):
        print('Spam.grok')
Spam.grok(30)
s = Spam() #会报错1.2、单例
class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            return self.__instance
# Example
class Spam(metaclass=Singleton):
    def __init__(self):
        print('Creating Spam')二、控制类型
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
    
    def read(self, maxsize=None):
        pass
    
    def write(self, data):
        pass
class MyMeta(type):
    # Optional
    
    def __prepare__(cls, name, bases, *, debug=False, synchronize=False):
        # Custom processing
        pass
        return super().__prepare__(name, bases)
    # Required
    def __new__(cls, name, bases, ns, *, debug=False, synchronize=False):
        # Custom processing
        pass
        return super().__new__(cls, name, bases, ns)
    # Required
    def __init__(self, name, bases, ns, *, debug=False, synchronize=False):
        # Custom processing
        pass
        super().__init__(name, bases, ns)class Spam(metaclass=MyMeta, debug=True, synchronize=True):
    pass
class Spam(metaclass=MyMeta):
    debug = True
    synchronize = True
    pass三、控制属性
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(f'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, cls_name, bases, cls_dict):
        d = dict(cls_dict)
        order = []
        for name, value in cls_dict.items():
            if isinstance(value, Typed):
                value._name = name
                order.append(name)
        d['_order'] = order
        return type.__new__(cls, cls_name, bases, d)
    
    def __prepare__(cls, cls_name, bases):
        return OrderedDict()
class Structure(metaclass=OrderedMeta):
    def as_csv(self):
        return ','.join(str(getattr(self,name)) for name in self._order)
# Example use
class Course(Structure):
    course_name = String()
    total_class = Integer()
    score = Float()
    def __init__(self, course_name, total_class, score):
        self.course_name = course_name
        self.total_class = total_class
        self.score = score
course = Course('python', 30, 0.3)
print(f'course name: {course.course_name}')
print(f'course as csv: {course.as_csv()}')
# err_ = Course('python','total class', 0.3)
from collections import OrderedDict
class NoDupOrderedDict(OrderedDict):
    def __init__(self, cls_name):
        self.cls_name = cls_name
        super().__init__()
    def __setitem__(self, name, value):
        if name in self:
            raise TypeError(f'{name} already defined in {self.cls_name}')
        super().__setitem__(name, value)
class OrderedMeta(type):
    def __new__(cls, cls_name, bases, cls_dict):
        d = dict(cls_dict)
        d['_order'] = [name for name in cls_dict if name[0] != '_']
        return type.__new__(cls, cls_name, bases, d)
    
    def __prepare__(cls, cls_name, bases):
        return NoDupOrderedDict(cls_name)四、应用
4.1、框架类
4.1.1、强制类型签名
from inspect import Signature, Parameter
parm_list = [Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),
             Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42),
             Parameter('z', Parameter.KEYWORD_ONLY, default=None)]
sig = Signature(parm_list)
print(f'sig is: {sig}')
def func(*args, **kwargs):
    bound_values = sig.bind(*args, **kwargs)
    for name, value in bound_values.arguments.items():
        print(f'name is: {name}, value is: {value}')
func(1, 2, z=3)
func(1)
func(1, z=3)
func(y=2, x=1)
# func(1, 2, 3, 4)
# func(y=2)
# func(1, y=2, x=3)
from inspect import Signature, Parameter
def make_sig(*names):
    parm_list = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
            for name in names]
    return Signature(parm_list)
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 Course(Structure):
    __signature__ = make_sig('course_name', 'total_class', 'score')
class Point(Structure):
    __signature__ = make_sig('x', 'y')
import inspect
print(f'Course signature: {inspect.signature(Course)}')
course_1 = Course('python', 30, 0.3)
# course_2 = Course('python', 30)
# course_3 = Course('python', 30, 0.3, total_class=30)
from inspect import Signature, Parameter
def make_sig(*names):
    parms = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
            for name in names]
    return Signature(parms)
class StructureMeta(type):
    def __new__(cls, cls_name, bases, cls_dict):
        cls_dict['__signature__'] = make_sig(*cls_dict.get('_fields',[]))
        return super().__new__(cls, cls_name, bases, cls_dict)
class Structure(metaclass=StructureMeta):
    _fields = []
    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 Course(Structure):
    _fields = ['course_name', 'total_class', 'score']
class Point(Structure):
    _fields = ['x', 'y']
import inspect
print(f'course signature: {inspect.signature(Course)}')
print(f'point signature: {inspect.signature(Point)}')4.1.2、强制编码规范
class MyMeta(type):
    def __new__(cls, cls_name, bases, cls_dict):
        # cls_name is name of class being defined
        # bases is tuple of base classes
        # cls_dict is class dictionary
        return super().__new__(cls, cls_name, bases, cls_dict)
class MyMeta(type):
    def __init__(self, cls_name, bases, cls_dict):
        super().__init__(cls_name, bases, cls_dict)
        # cls_name is name of class being defined
        # bases is tuple of base classes
        # cls_dict is class dictionary
class Root(metaclass=MyMeta):
    pass
class A(Root):
    pass
class B(Root):
    pass
class NoMixedCaseMeta(type):
    def __new__(cls, cls_name, bases, cls_dict):
        for name in cls_dict:
            if name.lower() != name:
                raise TypeError('Bad attribute name: ' + name)
        return super().__new__(cls, cls_name, bases, cls_dict)
class Root(metaclass=NoMixedCaseMeta):
    pass
class A(Root):
    def foo_bar(self):
        pass
class B(Root):
    def fooBar(self):
        pass
from inspect import signature
import logging
class MatchSignaturesMeta(type):
    def __init__(self, cls_name, bases, cls_dict):
        super().__init__(cls_name, bases, cls_dict)
        sup = super(self, self)
        for name, value in cls_dict.items():
            if name.startswith('_') or not callable(value):
                continue
            # Get the previous definition (if any) and compare the signatures
            # prev_dfn = getattr(sup,name,None)
            if (prev_dfn := getattr(sup,name,None)):
                prev_sig = signature(prev_dfn)
                val_sig = signature(value)
                if prev_sig != val_sig:
                    logging.warning(f'Signature mismatch in {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):
        pass4.1.3、控制初始化
import operator
class StructTupleMeta(type):
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        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(f'{len(cls._fields)} arguments required')
        return super().__new__(cls,args)
class Course(StructTuple):
    _fields = ['course_name', 'total_class', 'score']
class Point(StructTuple):
    _fields = ['x', 'y']
course = Course('python', 30, 0.3)
print(f'course is: {course}')
print(f'course[0] = {course[0]}')
print(f'course.course_name = {course.course_name}')
print(f'course total_score = {course.total_class * course.score}')
course.total_class = 20
course = Course('python', 30, 0.3)
# course = Course(('python', 30, 0.3))course is: ('python', 30, 0.3)
course[0] = python
course.course_name = python
course total_score = 9.0
Traceback (most recent call last):
  File "/Users/liudong/PycharmProjects/pythonProject/app/chapter9/init_cls.py", line 30, in <module>
    course.total_class = 20
AttributeError: can't set attribute4.2、编码相关
4.2.1、通用的属性定义方法
class Person:
    def __init__(self, name ,age):
        self.name = name
        self.age = age
    
    def name(self):
        return self._name
    .setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('name must be a string')
        self._name = value
    
    def age(self):
        return self._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
    
    def prop(self):
        return getattr(self, storage_name)
    .setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(f'{name} must be a {expected_type}')
        setattr(self, storage_name, value)
    return prop
class Person:
    name = typed_property('name', str)
    age = typed_property('age', int)
    def __init__(self, name, age):
        self.name = name
        self.age = age4.2.2、定义上下文管理器
from contextlib import contextmanager
import time
def time_use(label):
    print("1")
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        print(f'{label}: {end - start} s')#4
with time_use('counting'):
    print("2")
    n = 10000000
    while n > 0:
        n -= 1
    print("3")等价如下:
class time_use:
    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(f'{self.label}: {end - self.start} s')









