Ticket #21681: metatest3.py

File metatest3.py, 6.0 KB (added by SimonKing, 5 years ago)

Attempt to do the same tricks in Python3

Line 
1"""
2SageMath metaclass framework
3
4Any metaclass used in Sage is supposed to be instance (not sub-class)
5of SageMetaclass. This is achieved by putting `__metaclass__ = sage_metaclass`
6into the metaclass definition.
7"""
8
9try:
10    import copyreg
11except:
12    import copy_reg as copyreg
13
14from operator import attrgetter
15   
16class SageMetaclass(type):
17    def __call__(mcls, name, bases, namespace):
18        basemetacls = [type(X) for X in bases]
19        basemetacls = tuple(sorted(set([mcls]+[X for X in basemetacls if
20                                             not(issubclass(mcls,X))]),
21                                   key=attrgetter('__name__')))
22        if len(basemetacls)>1:
23            mcls = sage_metaclass(None, basemetacls, {})
24        out = mcls.__new__(mcls,name,bases,namespace)
25        if mcls.__init__ is not type.__init__:
26            mcls.__init__(out, name, bases, namespace)
27        return out
28
29class DynamicSageMetaclass(SageMetaclass):
30    # This is automatically created. Pickling onls works
31    # under the assumption that all the methods are
32    # inherited from the bases.
33    def __reduce__(mcls):
34        return sage_metaclass, (mcls.__name__, mcls.__bases__, {})
35
36copyreg.pickle(DynamicSageMetaclass, DynamicSageMetaclass.__reduce__)
37
38
39_cache = {}
40def sage_metaclass(name, bases, namespace):
41    #print "new", name, bases, namespace
42    # There are two cases: If the namespace is empty,
43    # then we consider a dynamically created metaclass.
44    # It is uniquely determined by name and bases.
45    # If it isn't empty, then we assume that the metaclass
46    # is defined in a module.
47    if not namespace:
48        if name is None:
49            name = ''.join([X.__name__.split('Metaclass')[0] for X in bases])+'Metaclass'
50        try:
51            return _cache[name,bases]
52        except KeyError:
53            pass
54        mcls = DynamicSageMetaclass(name, bases, namespace)
55        for X in reversed(mcls.__mro__):
56            try:
57                metainit = X.__dict__['__metainit__']
58            except (AttributeError, KeyError):
59                continue
60            metainit(mcls, name, bases, namespace)
61        _cache[name, bases] = mcls
62        return mcls
63    else:
64        if not bases:
65            bases = (type,)
66        return SageMetaclass(name, bases, namespace)
67
68###################################################################
69#  Examples
70###################################################################
71
72#
73# Unique representation
74
75class ClasscallMetaclass(metaclass=sage_metaclass):
76    def __call__(cls, *args, **opts):
77        try:
78            classcall = cls.__classcall__
79        except AttributeError:
80            return type.__call__(cls, *args, **opts)
81        return classcall(cls, *args, **opts)
82
83unique_repr_cache = {}
84
85class MyUniqueRepresentation(metaclass=ClasscallMetaclass):
86    def __classcall__(cls, *args, **opts):
87        try:
88            return unique_repr_cache[cls,args,tuple(sorted(opts.items()))]
89        except KeyError:
90            pass
91        out = super(cls,cls).__new__(cls, *args, **opts)
92        cls.__init__(out,*args,**opts)
93        out._reduction = (type(out), args, opts)
94        unique_repr_cache[cls,args,tuple(sorted(opts.items()))] = out
95        return out
96    def __hash__(self):
97        return id(self)
98    def __reduce__(self):
99        return unreduce, self._reduction
100
101def unreduce(cls, args, opts):
102    #print cls, args, opts
103    return cls(*args,**opts)
104
105#
106# Nested pickling --- of course not a full replacemant
107# for sage.misc.nested_class.nested_pickle
108
109def nested_pickle(cls):
110    import sys
111    mod_name = cls.__module__
112    module = sys.modules[mod_name]
113    cls_name = cls.__name__+'.'
114    for (name, v) in cls.__dict__.items():
115        if isinstance(v, NestedClassMetaclass):
116            v_name = v.__name__
117            if getattr(v, '_modified', False):
118                continue
119            if v_name==name and v.__module__==mod_name and getattr(module, v_name, None) is not v:
120                setattr(module, cls_name+v_name, v)
121                v.__name__ = cls_name+v_name
122                nested_pickle(v)
123
124class NestedClassMetaclass(metaclass=sage_metaclass):
125    def __init__(cls, name, bases, namespace):
126        nested_pickle(cls)
127
128class MyNestedClass(metaclass=NestedClassMetaclass):
129    def __init__(self, a,b):
130        self.a = a
131        self.b = b
132    class Test(MyUniqueRepresentation):
133        def bla(self):
134            return 5
135
136#
137# Dynamic class
138class DynamicClassMetaclass(metaclass=sage_metaclass):
139    def __init__(cls, name, bases, namespace):
140        if '_reduction_data' not in namespace:
141            cls._reduction_data = (generic_unpickle, (cls.__module__,
142                                                      cls.__name__))
143    @staticmethod
144    def __metainit__(mcls, name, bases, namespace):
145        copyreg.pickle(mcls, DynamicClassMetaclass.__reduce__)
146    def __reduce__(cls):
147        red_data = getattr(cls,'_reduction_data', None)
148        if red_data is None:
149            clsmod = __import__(cls.__module__)
150            return getattr, (clsmod, cls.__name__)
151        return red_data
152
153def generic_unpickle(mod,name):
154    clsmod = __import__(mod)
155    return getattr(clsmod, name)
156
157copyreg.pickle(DynamicClassMetaclass, DynamicClassMetaclass.__reduce__)
158
159dynamic_cache = {}
160def my_dynamic_class(name, bases, extra_class=None, reduction=None):
161    try:
162        return dynamic_cache[name, bases,extra_class,reduction]
163    except KeyError:
164        pass
165    if reduction is None:
166        reduction = (my_dynamic_class, (name, bases, extra_class, reduction))
167    if extra_class is not None:
168        methods = dict(extra_class.__dict__)
169        if methods.has_key("__dict__"):
170            methods.__delitem__("__dict__")
171        bases = extra_class.__bases__ + bases
172    else:
173        assert bases != ()
174        extra_class = bases[0]
175        methods = {}
176    methods['_reduction_data'] = reduction
177    methods['__module__'] = extra_class.__module__
178    dynamic_cache[name, bases,extra_class,reduction] = DynamicClassMetaclass(name, bases, methods)
179    return dynamic_cache[name, bases,extra_class,reduction]