Skip to content

Latest commit

History

History
291 lines (230 loc) · 6.83 KB

File metadata and controls

291 lines (230 loc) · 6.83 KB

Contents | Previous (4.2 Inheritance) | Next (4.4 Exceptions)

4.3 Special Methods

Various parts of Python's behavior can be customized via special or so-called "magic" methods. This section introduces that idea. In addition dynamic attribute access and bound methods are discussed.

Introduction

Classes may define special methods. These have special meaning to the Python interpreter. They are always preceded and followed by __. For example __init__.

classStock(object):
def__init__(self):
...
def__repr__(self):
...

There are dozens of special methods, but we will only look at a few specific examples.

Special methods for String Conversions

Objects have two string representations.

>>>fromdatetimeimportdate>>>d=date(2012, 12, 21)
>>>print(d)
2012-12-21>>>ddatetime.date(2012, 12, 21)
>>>

The str() function is used to create a nice printable output:

>>>str(d)
'2012-12-21'>>>

The repr() function is used to create a more detailed representation for programmers.

>>>repr(d)
'datetime.date(2012, 12, 21)'>>>

Those functions, str() and repr(), use a pair of special methods in the class to produce the string to be displayed.

classDate(object):
def__init__(self, year, month, day):
self.year=yearself.month=monthself.day=day# Used with `str()`def__str__(self):
returnf'{self.year}-{self.month}-{self.day}'# Used with `repr()`def__repr__(self):
returnf'Date({self.year},{self.month},{self.day})'

Note: The convention for __repr__() is to return a string that, when fed to eval(), will recreate the underlying object. If this is not possible, some kind of easily readable representation is used instead.

Special Methods for Mathematics

Mathematical operators involve calls to the following methods.

a+ba.__add__(b)
a-ba.__sub__(b)
a*ba.__mul__(b)
a/ba.__truediv__(b)
a//ba.__floordiv__(b)
a%ba.__mod__(b)
a<<ba.__lshift__(b)
a>>ba.__rshift__(b)
a&ba.__and__(b)
a|ba.__or__(b)
a^ba.__xor__(b)
a**ba.__pow__(b)
-aa.__neg__()
~aa.__invert__()
abs(a) a.__abs__()

Special Methods for Item Access

These are the methods to implement containers.

len(x) x.__len__()
x[a] x.__getitem__(a)
x[a] =vx.__setitem__(a,v)
delx[a] x.__delitem__(a)

You can use them in your classes.

classSequence:
def__len__(self):
...
def__getitem__(self,a):
...
def__setitem__(self,a,v):
...
def__delitem__(self,a):
...

Method Invocation

Invoking a method is a two-step process.

  1. Lookup: The . operator
  2. Method call: The () operator
>>>s=Stock('GOOG',100,490.10)
>>>c=s.cost# Lookup>>>c<boundmethodStock.costof<Stockobjectat0x590d0>>>>>c() # Method call49010.0>>>

Bound Methods

A method that has not yet been invoked by the function call operator () is known as a bound method. It operates on the instance where it originated.

>>>s=Stock('GOOG', 100, 490.10) >>>s<Stockobjectat0x590d0>>>>c=s.cost>>>c<boundmethodStock.costof<Stockobjectat0x590d0>>>>>c()
49010.0>>>

Bound methods are often a source of careless non-obvious errors. For example:

>>>s=Stock('GOOG', 100, 490.10)
>>>print('Cost : %0.2f'%s.cost)
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>TypeError: floatargumentrequired>>>

Or devious behavior that's hard to debug.

f=open(filename, 'w')
...
f.close# Oops, Didn't do anything at all. `f` still open.

In both of these cases, the error is cause by forgetting to include the trailing parentheses. For example, s.cost() or f.close().

Attribute Access

There is an alternative way to access, manipulate and manage attributes.

getattr(obj, 'name') # Same as obj.namesetattr(obj, 'name', value) # Same as obj.name = valuedelattr(obj, 'name') # Same as del obj.namehasattr(obj, 'name') # Tests if attribute exists

Example:

ifhasattr(obj, 'x'):
x=getattr(obj, 'x'):
else:
x=None

*Note: getattr() also has a useful default value arg.

x=getattr(obj, 'x', None)

Exercises

Exercise 4.9: Better output for printing objects

Modify the Stock object that you defined in stock.py so that the __repr__() method produces more useful output. For example:

>>>goog=Stock('GOOG', 100, 490.1)
>>>googStock('GOOG', 100, 490.1)
>>>

See what happens when you read a portfolio of stocks and view the resulting list after you have made these changes. For example:

>>> import report
>>> portfolio = report.read_portfolio('Data/portfolio.csv')
>>> portfolio
... see what the output is ...
>>>

Exercise 4.10: An example of using getattr()

getattr() is an alternative mechanism for reading attributes. It can be used to write extremely flexible code. To begin, try this example:

>>>importstock>>>s=stock.Stock('GOOG', 100, 490.1)
>>>columns= ['name', 'shares']
>>>forcolnameincolumns:
print(colname, '=', getattr(s, colname))
name=GOOGshares=100>>>

Carefully observe that the output data is determined entirely by the attribute names listed in the columns variable.

In the file tableformat.py, take this idea and expand it into a generalized function print_table() that prints a table showing user-specified attributes of a list of arbitrary objects. As with the earlier print_report() function, print_table() should also accept a TableFormatter instance to control the output format. Here's how it should work:

>>>importreport>>>portfolio=report.read_portfolio('Data/portfolio.csv')
>>>fromtableformatimportcreate_formatter, print_table>>>formatter=create_formatter('txt')
>>>print_table(portfolio, ['name','shares'], formatter)
nameshares--------------------AA100IBM50CAT150MSFT200GE95MSFT50IBM100>>>print_table(portfolio, ['name','shares','price'], formatter)
namesharesprice------------------------------AA10032.2IBM5091.1CAT15083.44MSFT20051.23GE9540.37MSFT5065.1IBM10070.44>>>

Contents | Previous (4.2 Inheritance) | Next (4.4 Exceptions)