0
点赞
收藏
分享

微信扫一扫

​​Python从门到精通(八):元类-01-元类

本章主要说一点高级的编程技巧,python中也算是比较常用的一种技巧--元类。有点类似于拦截器或AOP的功能。

一、控制类的创建

对象的类型叫作类,类的类型叫作元类。实例对象由类创建,而类则是由元类创建。类进行创建时会优先执行以下几个方法,通过复写这几个方法就可以控制类的创建过程,规范化代码,然后在类定义时使用metaclass来定义行为,以下三个方法会按顺序执行;

  1. __prepare__(定义类)
  2. __new__(实例创建之前)
  3. __init__(实例创建时)
  4. __call__(实例创建时)

1.1、基础

class NoInstances(type):
def __call__(self, *args, **kwargs):
raise TypeError("Can't instantiate directly")

# Example,这里最主要的是metacclass的运用
class Spam(metaclass=NoInstances):
@staticmethod
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):
@abstractmethod
def read(self, maxsize=None):
pass

@abstractmethod
def write(self, data):
pass


class MyMeta(type):
# Optional
@classmethod
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)

@classmethod
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)

@classmethod
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):
pass

4.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 attribute

4.2、编码相关

4.2.1、通用的属性定义方法

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(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 = age

4.2.2、定义上下文管理器

from contextlib import contextmanager
import time
@contextmanager
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')

举报

相关推荐

0 条评论