1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
> Infatti, ho passato una serata interessante a farlo. Vorrei mettere
> nei credits anche David (e magari Phil Eby?), hai loro email
> "correnti"?
Sė, mertz@gnosis.cx e pje@telecommunity.com. Il criticismo di Eby
soprattutto e' stato importante. Tutta la storia e' documentata
in questo thread:
http://groups.google.com/groups?hl=en&lr=&threadm=25b5433d.0306081048.1d9ad5dd%40posting.google.com&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26q%3Deby%2Bmetaclass%2Bconflict%26btnG%3DSearch%26meta%3Dgroup%253Dcomp.lang.python.*
> Sarebbe carino avere qualcosa del genere in Python 2.5, effettivamente.
> Ma e` da scrivere in C e in una parte gia` complicata, quindi la
> semplicita` e chiarezza per partire sono cruciali. Poi bisogna vedere
> se occorre qualche indicatore sintattico, hmmm...
Infatti sarebbe da scrivere in C ma io pensavo ad un prototipo in
puro Python. Tanto non e' che tipicamente le classi si creino a
migliaia, l'efficienza non e' un vero problema. Il vero problema
e' scrivere una soluzione generale che sia nello stesso tempo
comprensibile e manutenibile :-(.
Ho trovato tra le mie note un'altra versione della ricetta che fa
uso di una funzione 'remove_redundant', che avevo introdotto
appunto per migliorare la leggibilita'. Ti mando quella versione piu'
altri cambiamenti: nota in particolare il cambiamento
nella segnatura di classmaker.
Sarebbe anche da mettere una nota sul fatto che
noconflict.classmaker() ritorna una funzione che genera la classe
usando la metaclasse corretta, ma NON ritorna direttamente la
metaclasse corretta (insomma il right hand side of __metaclass__
non deve necessariamente essere una metaclasse).
Tra l'altro, ho visto che l'implementazione di string.Template usa
metaclasse non banale, e' la prima volta che ne vedo una nella libreria
standard. Sei a conoscenza di altri esempi?
########################### noconflict.py #################################
import sets
def remove_redundant(classes):
"""Given a sequence of (meta)classes, removes the redundant ones,
i.e. the (meta)classes implied (in the sense of being superclasses
or duplicated classes) by the others."""
remove_set = sets.Set()
ls = list(classes)
for c in classes:
is_super_class_of_at_least_one = True in [
(issubclass(C, c) and c is not C) for C in ls]
if c in remove_set or is_super_class_of_at_least_one:
ls.remove(c) # if c is less specific or duplicated
else:
remove_set.add(c)
return tuple(ls)
memoized_metaclasses_map = {}
def _get_noconflict_metaclass(bases, left_metas, right_metas):
# make the tuple of all needed metaclasses in specified priority order
metas = left_metas + tuple(map(type, bases)) + right_metas
non_trivial_metas = remove_redundant(metas)
# return existing confict-solving meta if any
if non_trivial_metas in memoized_metaclasses_map:
return memoized_metaclasses_map[non_trivial_metas]
# nope: compute, memoize and return needed conflict-solving meta
if not non_trivial_metas: # wee, a trivial case, happy us
meta = type
elif len(non_trivial_metas) == 1: # another trivial case
meta = non_trivial_metas[0]
else: # nontrivial, gotta work...
metaname = '_' + ''.join([m.__name__ for m in non_trivial_metas])
meta = classmaker()(metaname, non_trivial_metas, {})
memoized_metaclasses_map[non_trivial_metas] = meta
return meta
def classmaker(inject_left = (), inject_right = ()):
def make_class(name, bases, adict):
metaclass = _get_noconflict_metaclass(bases, inject_left, inject_right)
return metaclass(name, bases, adict)
return make_class
__test__ = dict(
ex1 = """
>>> class Meta_A(type): pass
...
>>> class Meta_B(type): pass
...
>>> class A: __metaclass__ = Meta_A
...
>>> class B: __metaclass__ = Meta_B
...
>>> class C(A, B): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
>>> import __main__ as noconflict
>>> class Meta_C(Meta_A, Meta_B): pass
...
>>> class C(A, B): __metaclass__ = Meta_C
...
>>> class C(A, B): __metaclass__ = noconflict.classmaker()
...
>>> class D(A):
... __metaclass__ = noconflict.classmaker(inject_left=(Meta_B,))
""" ,
remove_redundant="""
>>> class C1(object): pass
...
>>> class C2(C1): pass
...
>>> from __main__ import remove_redundant
>>> remove_redundant((C1, C2, C1))
(<class '__main__.C2'>,)
""")
if __name__ == "__main__":
import doctest,__main__
doctest.testmod(__main__)
|