|
Python/FAQ/Классы, Объекты и связи
Материал из Wiki.crossplatform.ru
[править] 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)
#-------------------------------------
|