Monday, April 18, 2005

An introduction to python classes

#class_examples.py
#
#Theses examples were used in a talk I
#gave at the BangPipers meeting on 
#16/04/2005

#Starting with an empty class
class Greeter(object):
    pass

#Lets add a simple method
def meth(self):
    print "this is a method of class ", self.__class__
    
simpleobject = Greeter()

Greeter.meth = meth

simpleobject.meth()

Greeter.meth(simpleobject)
#Prints:
#this is a method of class  <class '__main__.Greeter'>


#An init method for our greeter class
def init(self, text):
    self.text = text

Greeter.__init__ = init

greeting = Greeter("Greetings from the great beyond")

print greeting.text
#Prints:
#Greetings from the great beyond

#Adding a greet method to print the greeting text
def greet(self):
    print self.text

Greeter.greet = greet

greeting.greet()
#Prints:
#Greetings from the great beyond

#Now lets inherit from Greeter to create
#a Hello greeting
class Hello(Greeter):
    def __init__(self):
        self.text = "Hello"

hello = Hello()

hello.greet()
#Prints:
#Hello

#Overloading __new__ to control instance creation
#__new__ gets called before __init__ during
#instance creation.
#FussyGreeter will return an insult object if
#os.platform is not "win32"
class FussyGreeter(Greeter):
    acceptedPlatform  = "win32"
    def __new__(cls, greeting):
        import sys
        if cls.acceptedPlatform != sys.platform:
            class Insult(object):
                def greet(self):
                    print "You call that an OS?!? Get a real one like", cls.acceptedPlatform
            return Insult()
        else:
            return Greeter.__new__(cls)

greet = FussyGreeter("Howdy")

greet.greet()
#Prints:
#Howdy

FussyGreeter.acceptedPlatform = "linux"

greet = FussyGreeter("Howdy")

greet.greet()
#Prints:
#You call that an OS?!? Get a real one like linux

print type(greet)
#Prints:
#<class '__main__.Insult'>

#Checking call order for __new__ and __init__
#Note:If  __new__ returns anything other than an obect
#of type SimpleClass, __init__ will not be called.
class SimpleClass(object):
    def __init__(self):
        print "calling __init__(%s)"%self
        

    def __new__(cls):
        print "calling __new__(%s)"%cls
        return object.__new__(SimpleClass)
        
simpleobject = SimpleClass()
#Prints:
#calling __new__(<class '__main__.SimpleClass'>)
#calling __init__(<__main__.SimpleClass object at 0x011AB650>)

#Here is a simple example of operator overloading
#We overload the '*' operator.
def mul(self, count):
    text = self.text
    if text[-1] == "s":
        endString = ""
    else:
        endString = "s"
    return Greeter("%i %s" % (count, text) + endString)

Greeter.__mul__ = mul

doubleHello = hello * 2

doubleHello.greet()
#Prints:
#2 Hellos

print doubleHello
#Prints:
#<__main__.Greeter object at 0x011AB330>

#We can write an __str__ method to control
#what gets printed
def str_(self):
    return self.text

Greeter.__str__ = str_

print doubleHello
#Prints:
#2 Hellos

#multiple inheritence
#mro
#super
class A(object):
    def f(self):
        print "A.f(inst)"
        
class B(A):
    def f(self):
        print "B.f(inst)"
        super(B, self).f()
        
class C(A):
    def f(self):
        print "C.f(inst)"
        super(C, self).f()

class D(B, C):
    def f(self):
        print "D.f(inst)"
        super(D, self).f()

d = D()
print d.__class__.mro();
#Prints:
#[<class '__main__.D'>, <class '__main__.B'>,
#<class '__main__.C'>, <class '__main__.A'>, <type 'object'>]

d.f()
#Prints:
#D.f(inst)
#B.f(inst)
#C.f(inst)
#A.f(inst)


#We can control attribute access for an
#object by overriding the methods
#__getattr__ and __setattr__
class attrdict(dict):
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value

d = attrdict()

d.a = 4
#Same as d.__setattr__("a", 4)

print d.a
#Same as print d.__getattr__("a")

#__getattr__ only gets called if python is
#unable to find the attribute name withen the object
#and its class
#This means that if I assign an attribute with the same name
#as one in dict

d.fromkeys = "hello"

#I wont be able to access the value
print d.fromkeys
#Prints:
#<built-in method fromkeys of type object at 0x01217828>

#We need to overload __getattribute__ to get complete controll
#over attribute lookup
def getattribute(self, name):
    return dict.__getitem__(self, name)

attrdict.__getattribute__ = getattribute

print d.fromkeys
#Prints:
#hello

#__slots__

#Class magic

#Classes in python are code objects that get executed when
#they are loaded.
#Lest look at some examples

class HelloWorld:
    print "hello world"
#Prints:
#hello world
 
#Something illegal    
class ManyVars:
    for i in range(26):
        locals()[chr(i+97)] = i
print dir(ManyVars)
#Prints:
#['__doc__', '__module__', 'a', 'b', 'c', 'd', 'e',
#'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
#'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
#'y', 'z']

#Legal using exec
class ManyVars:
    for i in range(26):
        exec "%s = %i" % (chr(i+97), i)
print dir(ManyVars)
#Prints:
#['__doc__', '__module__', 'a', 'b', 'c', 'd', 'e',
#'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
#'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
#'y', 'z']

#Metaclasses
#Meta classes are class constructors
#A meta class must accept a classname,
#a tuple of base classes and a dictionary
#of name value pairs representing the
#class's attributes and their values.

#Lets create a simple class and extract
#all its local name bindings into a global
#variable called l
class C:
    def __init__(self):
        self.text = "Namaste"
    global l
    l = locals()

print l
#Prints:
#{'__module__': '__main__', '__doc__': None,
#'__init__': <function __init__ at 0x011E03B0>}

#We can use type as a class constructor to
#construct a class called Namaste which is
#a subclass of Greeter and contains attributes
#defined in l
Namaste = type("Namaste", (Greeter,), l)

namaste = Namaste()

namaste.greet()
#Prints:
#Namaste

#Lets create a simple empty class
SimpleClass = type("SimpleClass", (), {})

simpleInstance = SimpleClass()

print simpleInstance
#Prints:
#<__main__.SimpleClass object at 0x011DE2F0>

#Metaclasses are the same as other classes
#What makes them special is that they are
#all subclasses of type

#Lets look at the call order of the __new__ and __init__
#methods of a simple metaclass
class PrintingMetaclass(type):
    def __init__(self, name, bases, dct):
        print "calling __init__"
        return type.__init__(self, name, bases, dct)
    def __new__(cls, name, bases, dct):
        print "calling __new__"
        return type.__new__(cls, name, bases, dct)

SimpleClass = PrintingMetaclass("SimpleClass", (), {})
#Prints:
#calling __new__
#calling __init__

#We can also set the metaclass for a particular
#clas int using the class attribute __metaclass__
class SimpleClass:
    __metaclass__ = PrintingMetaclass



    
#Prints:
#calling __new__
#calling __init__


#Here's an example of metaclasses in action
_securityNumbers = [1,2,3]

class SecurityMetaclass(type):
    def __new__(self, name, bases, dct):
        import types
        for (name, value) in dct.items():
            if isinstance(value, types.FunctionType):
                def func(self, securityNumber, *args, **kwargs):
                    if securityNumber in _securityNumbers:
                        return value(self, *args, **kwargs)
                    else:
                        raise Exception, "You are not authorized to call this method"
                dct[name] = func
                print dct
        return type.__new__(self, name, bases, dct)

class SecureClass(object):
    __metaclass__ = SecurityMetaclass
    def display(self, value):
        print value

secureObject = SecureClass()

secureObject.display(1"This is a secret message.")
#Prints:
#This is a secret message.

#secureObject.display(7, "This is a secret message.")
#Will throw an exception

#Descriptors
#__get__(self, obj, type)
#__set__(self, obj, value)
#__delete__(self, obj)

#Lets create a class to work with
class DiscrExp(object):pass
inst = DiscrExp()

#Here is a simple example of properties
#in python
def fset(self, value):
    self.value = value*2

def fget(self):
    return self.value
    
DiscrExp.doublingValue = property(fget, fset)

inst.doublingValue = 2
print inst.doublingValue
#Prints:
#4

#Lets create a printing discriptor to see how
#the different methods of a descriptor are
#called
class PrintingDiscriptor(object):
    def __get__(self, obj, type):
        print "__get__"
        print "obj = ", obj
        print "type = ", type

    def __set__(self, obj, value):
        print "__set__"
        print "obj=", obj
        print "value=", value

    def __delete__(self, obj):
        print "__delete__"
        print "obj=",obj

DiscrExp.pd = PrintingDiscriptor()
inst.pd
#Prints:
#__get__
#obj =  <__main__.DiscrExp object at 0x011AB890>
#type =  <class '__main__.DiscrExp'>

inst.pd = 5
#Prints:
#__set__
#obj= <__main__.DiscrExp object at 0x011AB890>
#value= 5

DiscrExp.pd
#Prints:
#__get__
#obj =  None
#type =  <class '__main__.DiscrExp'>



#Creating a static method descriptor
class StaticMethod(object):
 def __init__(self, f):
      self.f = f
 def __get__(self, obj, type):
      return self.f

def warning():
    print "This is a universal warning, for this class only"

DiscrExp.warning = staticmethod(warning)
DiscrExp.warning()

#Prints:
#This is a universal warning, for this class only

inst.warning()
#Prints:
#This is a universal warning, for this class only


#References:
#http://www.python.org/2.2.3/descrintro.html
#http://users.rcn.com/python/download/Descriptor.htm
#Python in a nutshell 2nd ed.

0 Comments:

Post a Comment

<< Home