Python/FAQ/Классы, Объекты и связи
Материал из Wiki.crossplatform.ru
Версия от 14:26, 3 декабря 2008; Root (Обсуждение | вклад)
· Python · |
[править] Introduction
#----------------------------- # Inside a module named 'Data' / file named 'Data.py' class Encoder(object): pass #----------------------------- obj = [3, 5] print type(obj), id(obj), ob[1] ## Changing the class of builtin types is not supported ## in Python. #----------------------------- obj.Stomach = "Empty" # directly accessing an object's contents obj.NAME = "Thag" # uppercase field name to make it stand out (optional) #----------------------------- encoded = object.encode("data") #----------------------------- encoded = Data.Encoder.encode("data") #----------------------------- class Class(object): def __init__(self): pass #----------------------------- object = Class() #----------------------------- class Class(object): def class_only_method(): pass # more code here class_only_method = staticmethod(class_only_method) #----------------------------- class Class(object): def instance_only_method(self): pass # more code here #----------------------------- lector = Human.Cannibal() lector.feed("Zak") lector.move("New York") #----------------------------- # NOTE: it is rare to use these forms except inside of # methods to call specific methods from a parent class lector = Human.Cannibal() Human.Cannibal.feed(lector, "Zak") Human.Cannibal.move(lector, "New York") #----------------------------- print>>sys.stderr, "stuff here\n"
[править] Constructing an Object
#----------------------------- class Class(object): pass #----------------------------- import time class Class(object): def __init__(self): self.start = time.time() # init data fields self.age = 0 #----------------------------- import time class Class(object): def __init__(self, **kwargs): # Sets self.start to the current time, and self.age to 0. If called # with arguments, interpret them as key+value pairs to # initialize the object with self.age = 0 self.__dict__.update(kwargs) #-----------------------------
[править] Destroying an Object
#----------------------------- import time class Class(object): def __del__(self): print self, "dying at", time.ctime() #----------------------------- ## Why is the perl code introducing a cycle? I guess it's an ## example of how to keep from calling the finalizer self.WHATEVER = self #-----------------------------
[править] Managing Instance Data
#----------------------------- # It is standard practice to access attributes directly: class MyClass(object) def __init__(self): self.name = "default" self.age = 0 obj = MyClass() obj.name = "bob" print obj.name obj.age += 1 # If you later find that you need to compute an attribute, you can always # retrofit a property(), leaving user code untouched: class MyClass(object): def __init__(self): self._name = "default" self._age = 0 def get_name(self): return self._name def set_name(self, name): self._name = name.title() name = property(get_name, set_name) def get_age(self): return self._age def set_age(self, val): if val < 0: raise ValueError("Invalid age: %s" % val) self._age = val age = property(get_age, set_age) obj = MyClass() obj.name = "bob" print obj.name obj.age += 1 # DON'T DO THIS - explicit getters and setters should not be used: class MyClass(object): def __init__(self): self.name = "default" def get_name(self): return self.name def set_name(self, name): self.name = name.title() obj = MyClass() obj.set_name("bob") print obj.get_name() #----------------------------- ## DON'T DO THIS (It's complex, ugly, and unnecessary): class MyClass(object): def __init__(self): self.age = 0 def name(self, *args): if len(args) == 0: return self.name elif len(args) == 1: self.name = args[0] else: raise TypeError("name only takes 0 or 1 arguments") def age(self, *args): prev = self.age if args: self.age = args[0] return prev # sample call of get and set: happy birthday! obj.age(1 + obj.age()) #----------------------------- him = Person() him.NAME = "Sylvester" him.AGE = 23 #----------------------------- # Here's another way to implement the 'obj.method()' is a getter # and 'obj.method(value)' is a settor. Again, this is not a # common Python idiom and should not be used. See below for a # more common way to do parameter checking of attribute assignment. import re, sys def carp(s): sys.stderr.write("WARNING: " + s + "\n") class Class: no_name = [] def name(self, value = no_name): if value is Class.no_name: return self.NAME value = self._enforce_name_value(value) self.NAME = value def _enforce_name_value(self, value): if re.search(r"[^\s\w'-]", value): carp("funny characters in name") if re.search(r"\d", value): carp("numbers in name") if not re.search(r"\S+(\s+\S+)+", value): carp("prefer multiword name") if not re.search(r"\S", value): carp("name is blank") return value.upper() # enforce capitalization #----------------------------- # A more typical way to enforce restrictions on a value # to set class Class: def __setattr__(self, name, value): if name == "name": value = self._enforce_name_value(value) # Do any conversions self.__dict__[name] = value # Do the default __setattr__ action def _enforce_name_value(self, value): if re.search(r"[^\s\w'-]", value): carp("funny characters in name") if re.search(r"\d", value): carp("numbers in name") if not re.search(r"\S+(\s+\S+)+", value): carp("prefer multiword name") if not re.search(r"\S", value): carp("name is blank") return value.upper() # enforce capitalization #----------------------------- class Person: def __init__(self, name = None, age = None, peers = None): if peers is None: peers = [] # See Python FAQ 6.25 self.name = name self.age = age self.peers = peers def exclaim(self): return "Hi, I'm %s, age %d, working with %s" % \ (self.name, self.age, ", ".join(self.peers)) def happy_birthday(self): self.age += 1 return self.age #-----------------------------
[править] Managing Class Data
#----------------------------- ## In the module named 'Person' ... def population(): return Person.body_count[0] class Person(object): body_count = [0] # class variable - shared across all instances def __init__(self): self.body_count[0] += 1 def __del__(self): # Beware - may be non-deterministic (Jython)! self.body_count[0] -= 1 # later, the user can say this: import Person people = [] for i in range(10): people.append(Person.Person()) print "There are", Person.population(), "people alive." #=> There are 10 people alive. #----------------------------- him = Person() him.gender = "male" her = Person() her.gender = "female" #----------------------------- FixedArray.max_bounds = 100 # set for whole class alpha = FixedArray.FixedArray() print "Bound on alpha is", alpha.max_bounds #=>100 beta = FixedArray.FixedArray() beta.max_bounds = 50 # still sets for whole class print "Bound on alpha is", alpha.max_bounds #=>50 #----------------------------- # In the module named 'FixedArray' class FixedArray(object): _max_bounds = [7] # Shared across whole class def __init__(self, bounds=None): if bounds is not None: self.max_bounds = bounds def get_max_bounds(self): return self._max_bounds[0] def set_max_bounds(self, val): self._max_bounds[0] = val max_bounds = property(get_max_bounds, set_max_bounds) #-----------------------------
[править] Using Classes as Structs
#----------------------------- # There isn't the severe separation between scalar, arrays and hashs # in Python, so there isn't a direct equivalent to the Perl code. class Person: def __init__(self, name=None, age=None, peers=None): if peers is None: peers = [] self.name = name self.age = age self.peers = peers p = Person("Jason Smythe", 13, ["Wilbur", "Ralph", "Fred"]) # or this way. (This is not the prefered style as objects should # be constructed with all the appropriate data, if possible.) p = Person() # allocate an empty Person p.name = "Jason Smythe" # set its name field p.age = 13 # set its age field p.peers.extend( ["Wilbur", "Ralph", "Fred" ] ) # set its peers field p.peers = ["Wilbur", "Ralph", "Fred"] p.peers[:]= ["Wilbur", "Ralph", "Fred"] # fetch various values, including the zeroth friend print "At age %d, %s's first friend is %s." % \ (p.age, p.name, p.peers[0]) #----------------------------- # This isn't very Pythonic - should create objects with the # needed data, and not depend on defaults and modifing the object. import sys def carp(s): sys.stderr.write("WARNING: " + s + "\n") class Person: def __init__(self, name = "", age = 0): self.name = name self.age = age def __setattr__(self, name, value): if name == "age": # This is very unpythonic if not isinstance(value, type(0)): carp("age '%s' isn't numeric" % (value,)) if value > 150: carp("age '%s' is unreasonable" % (value,)) self.__dict__[name] = value class Family: def __init__(self, head = None, address = "", members = None): if members is None: members = [] self.head = head or Person() self.address = address self.members = members folks = Family() dad = folks.head dad.name = "John" dad.age = 34 print "%s's age is %d" % (folks.head.name, folks.head.age) #----------------------------- class Card: def __init__(self, name=None, color=None, cost=None, type=None, release=None, text=None): self.name = name self.color = color self.cost = cost self.type = type self.release = release self.type = type #----------------------------- # For positional args class Card: _names = ("name", "color", "cost", "type", "release", "type") def __init__(self, *args): assert len(args) <= len(self._names) for k, v in zip(self._names, args): setattr(self, k, None) #----------------------------- # For keyword args class Card: _names = ("name", "color", "cost", "type", "release", "type") def __init__(self, **kwargs): for k in self._names: # Set the defaults setattr(self, k, None) for k, v in kwargs.items(): # add in the kwargs assert k in self._names, "Unexpected kwarg: " + k setattr(self, k, v) #----------------------------- class hostent: def __init__(self, addr_list = None, length = None, addrtype = None, aliases = None, name = None): self.addr_list = addr_list or [] self.length = length or 0 self.addrtype = addrtype or "" self.aliases = aliases or [] self.name = name or "" #----------------------------- ## XXX What do I do with these? #define h_type h_addrtype #define h_addr h_addr_list[0] #----------------------------- # make (hostent object)->type() same as (hostent object)->addrtype() # # *hostent::type = \&hostent::addrtype; # # # make (hostenv object)-> # addr() # same as (hostenv object)->addr_list(0) #sub hostent::addr { shift->addr_list(0,@_) } #----------------------------- # No equivalent to Net::hostent (Python uses an unnamed tuple) #package Extra::hostent; #use Net::hostent; #@ISA = qw(hostent); #sub addr { shift->addr_list(0,@_) } #1; #-----------------------------
[править] Cloning Objects
#----------------------------- class Class(Parent): pass #----------------------------- ## Note: this is unusual in Python code ob1 = SomeClass() # later on ob2 = ob1.__class__() #----------------------------- ## Note: this is unusual in Python code ob1 = Widget() ob2 = ob1.__class__() #----------------------------- # XXX I do not know the intent of the original Perl code # Do not use this style of programming in Python. import time class Person(possible,base,classes): def __init__(self, *args, **kwargs): # Call the parents' constructors, if there are any for baseclass in self.__class__.__bases__: init = getattr(baseclass, "__init__") if init is not None: init(self, *args, **kwargs) self.PARENT = parent # init data fields self.START = time.time() self.AGE = 0 #-----------------------------
[править] Calling Methods Indirectly
#----------------------------- methname = "flicker" getattr(obj, methname)(10) # calls obj->flicker(10); # call three methods on the object, by name for m in ("start", "run", "stop"): getattr(obj, m)() #----------------------------- methods = ("name", "rank", "serno") his_info = {} for m in methods: his_info[m] = getattr(ob, m)() # same as this: his_info = { 'name': ob.name(), 'rank': ob.rank(), 'serno': ob.serno(), } #----------------------------- fnref = ob.method #----------------------------- fnref(10, "fred") #----------------------------- obj.method(10, "fred") #----------------------------- # XXX Not sure if this is the correct translation. # XXX Is 'can' special? if isinstance(obj_target, obj.__class__): obj.can('method_name')(obj_target, *arguments) #-----------------------------
[править] Determining Subclass Membership
#----------------------------- isinstance(obj, mimetools.Message) issubclass(obj.__class__, mimetools.Message) if hasattr(obj, "method_name"): # check method validity pass #----------------------------- ## Explicit type checking is needed fewer times than you think. his_print_method = getattr(obj, "as_string", None) #----------------------------- __version__ = (3, 0) Some_Module.__version__ # Almost never used, and doesn't work for builtin types, which don't # have a __module__. his_vers = obj.__module__.__version__ #----------------------------- if Some_Module.__version__ < (3, 0): raise ImportError("Some_Module version %s is too old, expected (3, 0)" % (Some_Module.__version__,)) # or more simply assert Some_Module.__version__ >= (3, 0), "version too old" #----------------------------- __VERSION__ = '1.01' #-----------------------------
[править] Writing an Inheritable Class
#----------------------------- # Note: This uses the standard Python idiom of accessing the # attributes directly rather than going through a method call. # See earlier in this chapter for examples of how this does # not break encapsulation. class Person: def __init__(self, name = "", age = 0): self.name = name self.age = age #----------------------------- # Prefered: dude = Person("Jason", 23) dude = Person() dude.name = "Jason" dude.age = 23 print "%s is age %d." % (dude.name, dude.age) #----------------------------- class Employee(Person): pass #----------------------------- # Prefered: empl = Employee("Jason", 23) emp = Employee() empl.name = "Jason" empl.age = 23 print "%s is age %d." % (empl.name, empl.age) #-----------------------------
[править] Accessing Overridden Methods
#----------------------------- # This doesn't need to be done since if 'method' doesn't # exist in the Class it will be looked for in its BaseClass(es) class Class(BaseClass): def method(self, *args, **kwargs): BaseClass.method(self, *args, **kwargs) # This lets you pick the specific method in one of the base classes class Class(BaseClass1, BaseClass2): def method(self, *args, **kwargs): BaseClass2.method(self, *args, **kwargs) # This looks for the first method in the base class(es) without # specifically knowing which base class. This reimplements # the default action so isn't really needed. class Class(BaseClass1, BaseClass2, BaseClass3): def method(self, *args, **kwargs): for baseclass in self.__class__.__bases__: f = getattr(baseclass, "method") if f is not None: return f(*args, **kwargs) raise NotImplementedError("method") #----------------------------- self.meth() # Call wherever first meth is found Where.meth(self) # Call in the base class "Where" # XXX Does Perl only have single inheritence? Or does # it check all base classes? No directly equivalent way # to do this in Python, but see above. #----------------------------- import time # The Perl code calls a private '_init' function, but in # Python there's need for the complexity of 'new' mechanism # so it's best just to put the '_init' code in '__init__'. class Class: def __init__(self, *args): # init data fields self.START = time.time() self.AGE = 0 self.EXTRA = args # anything extra #----------------------------- obj = Widget(haircolor = "red", freckles = 121) #----------------------------- class Class(Base1, Base2, Base3): def __init__(self, *args, **kwargs): for base in self.__class__.__bases__: f = getattr(base, "__init__") if f is not None: f(self, *args, **kwargs) #-----------------------------
[править] Generating Attribute Methods Using AUTOLOAD
#----------------------------- # NOTE: Python prefers direct attribute lookup rather than # method calls. Python 2.2 will introduce a 'get_set' which # *may* be equivalent, but I don't know enough about it. So # instead I'll describe a class that lets you restrict access # to only specific attributes. class Private: def __init__(self, names): self.__names = names self.__data = {} def __getattr__(self, name): if name in self.__names: return self.__data[name] raise AttributeError(name) def __setattr__(self, name, value): if name.startswith("_Private"): self.__dict__[name] = value return if name in self.__names: self.__data[name] = value return raise TypeError("cannot set the attribute %r" % (name,)) class Person(Private): def __init__(self, parent = None): Private.__init__(self, ["name", "age", "peers", "parent"]) self.parent = parent def new_child(self): return Person(self) #----------------------------- dad = Person() dad.name = "Jason" dad.age = 23 kid = dad.new_child() kid.name = "Rachel" kid.age = 2 print "Kid's parent is", kid.parent.name #=>Kid's parent is Jason
[править] Solving the Data Inheritance Problem
#----------------------------- ## XXX No clue on what this does. For that matter, what's ## "The Data Inheritance Problem"?
[править] Coping with Circular Data Structures
#----------------------------- node.NEXT = node #----------------------------- # This is not a faithful copy of the Perl code, but it does # show how to have the container's __del__ remove cycles in # its contents. Note that Python 2.0 includes a garbage # collector that is able to remove these sorts of cycles, but # it's still best to prevent cycles in your code. class Node: def __init__(self, value = None): self.next = self self.prev = self self.value = value class Ring: def __init__(self): self.ring = None self.count = 0 def __str__(self): # Helpful when debugging, to print the contents of the ring s = "#%d: " % self.count x = self.ring if x is None: return s values = [] while True: values.append(x.value) x = x.next if x is self.ring: break return s + " -> ".join(map(str, values)) + " ->" def search(self, value): node = self.ring while True: if node.value == value: return node node = node.next if node is self.ring: break def insert_value(self, value): node = Node(value) if self.ring is not None: node.prev, node.next = self.ring.prev, self.ring self.ring.prev.next = self.ring.prev = node self.ring = node self.count += 1 def delete_value(self, value): node = self.search(value) if node is not None: self.delete_node(node) def delete_node(self, node): if node is node.next: node.next = node.prev = None self.ring = None else: node.prev.next, node.next.prev = node.next, node.prev if node is self.ring: self.ring = node.next self.count -= 1 def __del__(self): while self.ring is not None: self.delete_node(self.ring) COUNT = 1000 for rep in range(20): r = Ring() for i in range(COUNT): r.insert_value(i) #-----------------------------
[править] Overloading Operators
#----------------------------- import UserString class MyString(UserString.UserString): def __cmp__(self, other): return cmp(self.data.upper(), other.upper()) class Person: def __init__(self, name, idnum): self.name = name self.idnum = idnum def __str__(self): return "%s (%05d)" % (self.name.lower().capitalize(), self.idnum) #----------------------------- class TimeNumber: def __init__(self, hours, minutes, seconds): assert minutes < 60 and seconds < 60 self.hours = hours self.minutes = minutes self.seconds = seconds def __str__(self): return "%d:%02d:%02d" % (self.hours, self.minutes, self.seconds) def __add__(self, other): seconds = self.seconds + other.seconds minutes = self.minutes + other.minutes hours = self.hours + other.hours if seconds >= 60: seconds %= 60 minutes += 1 if minutes >= 60: minutes %= 60 hours += 1 return TimeNumber(hours, minutes, seconds) def __sub__(self, other): raise NotImplementedError def __mul__(self, other): raise NotImplementedError def __div__(self, other): raise NotImplementedError t1 = TimeNumber(0, 58, 59) sec = TimeNumber(0, 0, 1) min = TimeNumber(0, 1, 0) print t1 + sec + min + min # 1:01:00 #----------------------------- # For demo purposes only - the StrNum class is superfluous in this # case as plain strings would give the same result. class StrNum: def __init__(self, value): self.value = value def __cmp__(self, other): # both <=> and cmp # providing <=> gives us <, ==, etc. for free. # __lt__, __eq__, and __gt__ can also be individually specified return cmp(self.value, other.value) def __str__(self): # "" return self.value def __nonzero__(self, other): # bool return bool(self.value) def __int__(self, other): # 0+ return int(self.value) def __add__(self, other): # + return StrNum(self.value + other.value) def __radd__(self, other): # +, inverted return StrNum(other.value + self.value) def __mul__(self, other): # * return StrNum(self.value * other) def __rmul__(self, other): # *, inverted return StrNum(self.value * other) def demo(): # show_strnum - demo operator overloading x = StrNum("Red") y = StrNum("Black") z = x + y r = z * 3 print "values are %s, %s, %s, and %s" % (x, y, z, r) if x < y: s = "LT" else: s = "GE" print x, "is", s, y if __name__ == "__main__": demo() # values are Red, Black, RedBlack, and RedBlackRedBlackRedBlack # Red is GE Black #----------------------------- #!/usr/bin/env python # demo_fixnum - show operator overloading # sum of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 52 # product of STRFixNum: 40 and STRFixNum: 12 is STRFixNum: 480 # STRFixNum: 3 has 0 places # div of STRFixNum: 40 by STRFixNum: 12 is STRFixNum: 3.33 # square of that is STRFixNum: 11.11 # This isn't excatly the same as the original Perl code since # I couldn't figure out why the PLACES variable was used. #----------------------------- import re _places_re = re.compile(r"\.(\d+)") default_places = 0 class FixNum: def __init__(self, value, places = None): self.value = value if places is None: # get from the value m = _places_re.search(str(value)) if m: places = int(m.group(1)) else: places = default_places self.places = places def __add__(self, other): return FixNum(self.value + other.value, max(self.places, other.places)) def __mul__(self, other): return FixNum(self.value * other.value, max(self.places, other.places)) def __div__(self, other): # Force to use floating point, since 2/3 in Python is 0 # Don't use float() since that will convert strings return FixNum((self.value+0.0) / other.value, max(self.places, other.places)) def __str__(self): return "STR%s: %.*f" % (self.__class__.__name__, self.places, self.value) def __int__(self): return int(self.value) def __float__(self): return self.value def demo(): x = FixNum(40) y = FixNum(12, 0) print "sum of", x, "and", y, "is", x+y print "product of", x, "and", y, "is", x*y z = x/y print "%s has %d places" % (z, z.places) if not z.places: z.places = 2 print "div of", x, "by", y, "is", z print "square of that is ", z*z if __name__ == "__main__": demo()
[править] Creating Magic Variables with tie
# You can't tie a variable, but you can use properties. import itertools class ValueRing(object): def __init__(self, colours): self.colourcycle = itertools.cycle(colours) def next_colour(self): return self.colourcycle.next() colour = property(next_colour) vr = ValueRing(["red", "blue"]) for i in range(6): print vr.colour, print # Note that you MUST refer directly to the property x = vr.colour print x, x, x #------------------------------------- # Ties are generally unnecessary in Python because of its strong OO support - # The resulting code is MUCH shorter: class AppendDict(dict): def __setitem__(self, key, val): if key in self: self[key].append(val) else: super(AppendDict, self).__setitem__(key, [val]) tab = AppendDict() tab["beer"] = "guinness" tab["food"] = "potatoes" tab["food"] = "peas" for key, val in tab.items(): print key, "=>", val #------------------------------------- class CaselessDict(dict): def __setitem__(self, key, val): super(CaselessDict, self).__setitem__(key.lower(), val) def __getitem__(self, key): return super(CaselessDict, self).__getitem__(key.lower()) tab = CaselessDict() tab["VILLAIN"] = "big " tab["herOine"] = "red riding hood" tab["villain"] = "bad wolf" for key, val in tab.items(): print key, "is", val #=>villain is bad wolf #=>heroine is red riding hood #------------------------------------- class RevDict(dict): def __setitem__(self, key, val): super(RevDict, self).__setitem__(key, val) super(RevDict, self).__setitem__(val, key) tab = RevDict() tab["red"] = "rojo" tab["blue"] = "azul" tab["green"] = "verde" tab["evil"] = ("No Way!", "Way!") for key, val in tab.items(): print key, "is", val #=>blue is azul #=>('No Way!', 'Way!') is evil #=>rojo is red #=>evil is ('No Way!', 'Way!') #=>azul is blue #=>verde is green #=>green is verde #=>red is rojo #------------------------------------- import itertools for elem in itertools.count(): print "Got", elem #------------------------------------- # You could use FileDispatcher from section 7.18 tee = FileDispatcher(sys.stderr, sys.stdout) #-------------------------------------