Szabó Péter <pts@fazekas.hu> 2009-05-27 budapest.py # --- célok class Foo(object): @abstract def F(self): pass # dobjon AbstractMethodError-t híváskor @override # dobjon kivételt definiáláskor def G(self, x): print x @final def H(self, x): print x class Bar(Foo): def H(self, x): # dobjon kivételt definiáláskor print x # Egyeb megvalósítandó szolgáltatások: # @nosuper: @override ellentéte # @finalim: alosztályban csak @classmethod vagy @staticmethod lehet # --- eszköz: dekorátor (decorator): def Change(f, a, b): return a * b @Change(6, 7) def F(x, y, z): assert 0, (x, y, z) print F #: 42 print F(2, 3, 4) #: hiba: F nem függvény @Change #: SyntaxError: csak def-et lehet dekorálni class Foo(object): pass # --- hasznos beépített dekorátorok (1/2) class Rectangle(object): def __init__(self, width, height): self._width = self.CheckPositiveDimension(width) self._height = self.CheckPositiveDimension(height) @staticmethod def CheckPositiveDimension(x): x = float(x) assert x > 0 return x @property def width(self): return self._width @property def height(self): return self._height print Rectangle.CheckPositiveDimension(42) #: 42.0 print Rectangle(8, 9).width #: 8 # --- hasznos beépített dekorátorok (2/2) class LandscapeRectangle(Rectangle): def __init__(self, width, height): self.CheckLandscape(width, height) Rectangle.__init__(self, width, height) @classmethod def CheckLandscape(cls, width, height): width = cls.CheckPositiveDimension(width) height = cls.CheckPositiveDimension(height) assert width >= height LandscapeRectangle.CheckLandscape(6, 7) # AssertionError # * A @property egy ún. descriptor-t hoz létre, lásd még # http://users.rcn.com/python/download/Descriptor.htm # * A def mindig függvényt hoz létre, ami osztály definiálásakor # instancemethod-dá alakul. Más típusúak nem alakulnak át. # * Amit a @classmethod és a @staticmethod visszaad, az már nem # függvény (hanem classmethod ill. staticmethod típusú), ezért # nem is alakul át osztály definiálásakor. # --- @abstract megvalósítása dekorátorral class AbstractMethodError(Exception): pass def abstract(func): def AbstractFunction(self): raise AbstractMethodError('%s.%s.%s' % (self.__module__, self.__name__, func.func_name)) return AbstractFunction # --- @override megvalósítási ötlete def override(func) import sys assert [s for s in GetSuperClasses(sys._getframe().f_back) if hasattr(s, func.func_name)], '@override mismatch' return func def GetSuperClasses(f) # f: frame objektum while f: print (f.f_code.co_name, f.f_code.co_filename, f.f_lineno, sorted(f.f_locals), sorted(f.f_globals), sorted(f.f_builtins)) f = f.f_back return ... # --- GetSuperClasses megvalósítása def GetSuperClasses(f) # f: frame objektum import linecache # az f a metódus def-je # az f.f_back az osztálydefiníció (class) file_name = f.f_back.f_code.co_filename line_number = f.f_back.f_lineno linecache.checkcache(file_name) line = linecache.getline(file_name, line_number) assert line import re match = re.match(r'\s*class\s+...', line): assert match return [...] # --- a @final problémája class Foo(object): @final def H(self, x): print x class Bar(Foo): def H(self, x): # dobjon kivételt print x # a Bar.H-ban a Foo.H @final dekorátorának kéne a kivételt dobnia # --- metaclass class MyMeta(type): def __new__(cls, class_name, bases, dict_obj): print (class_name, bases, sorted(dict_obj)) return type.__new__(cls, class_name, bases, dict_obj) class A(object): def M(self, x): print x class B(object): __metaclass__ = MyMeta def M(self, x): print x class C(B): def M(self, x): print x def L(self, x): print x #: ('B', (<type 'object'>,), ['M', '__metaclass__', '__module__']) #: ('C', (<class '__main__.B'>,), ['L', 'M', '__module__']) print type(A) #: <type 'type'> print type(B) #: <class '__main__.MyMeta'> print type(C) #: <class '__main__.MyMeta'> # --- @final megvalósítása metaclass-szal def final(func): func._is_final = True return func class ClassCheckMeta(type): def __new__(cls, class_name, bases, dict_obj): for s in bases: assert not [a for a in dir(s) and name in dict_obj and getattr(s, '_is_final', None)] return type.__new__(cls, class_name, bases, dict_obj) class Foo(object): __metaclass__ = ClassCheckMeta @final def H(self, x): print x class Bar(Foo): # kivételt dob def H(self, x): print x # --- ha a felhasználó elfelejti a __metaclass__-t class Foo(object): @final def H(self, x): print x class Bar(Foo): # sajnos átcsúszik kivétel nélkül def H(self, x): print x # Jó megoldási ötlet: def final(func): import sys f = sys._getframe().f_back f.f_locals['__metaclass__'] = ClassCheckMeta # általánosítandó func._is_final = True return func # Ugyan a metaclass kiváltja az @override-hoz szükséges # linecache-t, viszont linecache a final általánosításához # kelleni fog. # --- hogy ne kelljen @pobjects.override-ot írni # pobjects.py-ban: __builtins__['final'] = final __builtins__['finalim'] = finalim __builtins__['nosuper'] = nosuper __builtins__['abstract'] = abstract __builtins__['override'] = override # example.py-ban: import pobjects class Foo: @override # a közös __builtins__-ből véve def G(self, x): print x """Vége."""