Chapter 8: Special Functions in Python¶
Python offers a rich set of special functions that allow developers to leverage advanced features of the language.
These functions, often referred to as magic methods
, enable Python programmers to create more intuitive and Pythonic code by customizing the behavior of objects.
In this chapter, we'll explore these special functions, focusing on sequence-related methods and their applications in real-world scenarios.
1. Understanding Magic Methods¶
- Magic methods in Python are special methods that start and end with double underscores (
__
). - They are designed to be invoked automatically by Python in various contexts, providing a way for objects to integrate seamlessly with Python's language features.
1.1 Common Magic Methods¶
__init__(self, ...)
: Constructor method for initializing new objects.__str__(self)
: Defines the "informal
" or nicely printable string representation of an object.__repr__(self)
: Defines the "official
" string representation of an object, which can be used to recreate the object.__len__(self)
: Returns the length of the container. Called by thelen()
function.__getitem__(self, key)
: Allowsindexing
andslicing
operations on the object.__setitem__(self, key, value)
: Assigns a value to a key or index within the object.__delitem__(self, key)
: Removes an item from the object given a key or index.__iter__(self)
: Returns an iterator for the object. This allows iteration over the object.
1.2 Example¶
__repr__
- If you use
print()
functions to call an object, it would call it's__repr__()
__repr__
returns class name + object at memory address as default- rewrite
__repr__
as class name[field1 = xxx, field2 = xxx,...]
- If you use
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
# Create Item
im = Item('Mouse',29.8)
print(im)
print(im.__repr__())
<__main__.Item object at 0x0000016022CBF990> <__main__.Item object at 0x0000016022CBF990>
class Apple:
# constructor
def __init__(self,color,weight):
self.color = color
self.weight = weight
# re-write __repr__()
def __repr__(self):
return 'Apple[color = '+ self.color + ',weight =' + str(self.weight) + ']'
a = Apple('red',5.65)
print(a)
Apple[color = red,weight =5.65]
- Destructor
__del__
- for the opposite of
__init__()
,__del__
using for destroy an object - when you do not need an object anymore, please use it to do the
Garbage Collector(GC)
- for the opposite of
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
def __del__(self):
print('Delete Object!')
im = Item('mouse',29.8)
x = im
del im
print('----------')
----------
Since x
also refers to object im
, so the del im
would not call the __del__()
- python will do Garbage collection automatically
x = Item('mouse',29.8)
print('----------')
Delete Object! ----------
__dir__()
- It is similar to
dir(object)
- It is similar to
class Item:
def __init__(self,name,price):
self.name = name
self.price = price
im = Item('mouse',29.8)
print(im.__dir__())
print("\n--------------------------\n")
print(dir(im))
['name', 'price', '__module__', '__init__', '__dict__', '__weakref__', '__doc__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__'] -------------------------- ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'price']
__dict__
__dict__
is using for check all the variables and their value within an object, and return them as a dict
print(im.__dict__)
{'name': 'keyboard', 'price': 32.8}
print(im.__dict__['name'])
print(im.__dict__['price'])
im.__dict__['name'] = 'keyboard'
im.__dict__['price'] = 32.8
print(im.name)
print(im.price)
mouse 29.8 keyboard 32.8
_getattr__
and__setattr__
, etc.
For manipulating(access, set, delete) the properties of an object, Python would call the following functions:
__getattribute__(self,name)
: when access a property__getattr__(self,name)
: call a property but do not exist, So it is only for some Synthetic attributes__setattr__(self,name,value)
: set a value of a property__delattr__(self,name)
: delete a property
class Rectangle:
def __init__(self,width,height):
self.width = width
self.height = height
def __setattr__(self,name,value):
print('----set %s property----' % name)
if name == 'size':
self.width,self.height = value
else:
self.__dict__[name] = value
def __getattr__(self,name):
print('----read %s property----' % name)
if name == 'size':
return self.width, self.height
else:
raise AttributeError
def __delattr__(self,name):
print('----delte %s property' %name)
if name == 'size':
self.__dict__['width'] = 0
self.__dict__['height'] = 0
rect = Rectangle(3,4)
print(rect.size)
----set width property---- ----set height property---- ----read size property---- (3, 4)
rect.size = 6,8
print(rect.width)
----set size property---- ----set width property---- ----set height property---- 6
del rect.size
print(rect.size)
----delte size property ----read size property---- (0, 0)
Those methods is very useful for checking the legality of input
- But now, we use
@property
to do the input check
class User:
def __init__(self,name,age):
self.name = name
self.age = age
def __setattr__(self,name,value):
# check the input
if name == 'name':
if 2 < len(value) <= 8:
self.__dict__['name'] = value
else:
raise ValueError('the length of name must between 2~8')
elif name == 'age':
if 10< value <60:
self.__dict__['age'] = value
else:
raise ValueError('age must between 10~60')
u = User('fkit',24)
print(u.name)
print(u.age)
u.age = 65
fkit 24
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[13], line 21 19 print(u.name) 20 print(u.age) ---> 21 u.age = 65 Cell In[13], line 16, in User.__setattr__(self, name, value) 14 self.__dict__['age'] = value 15 else: ---> 16 raise ValueError('age must between 10~60') ValueError: age must between 10~60
class User:
def __init__(self,name,age):
self.name = name
self.age = age
@property
def age(self):
return self.__age
@age.setter
def age(self,value):
if 10< value <60:
self.__age = value
else:
raise ValueError('age must between 10~60')
u = User('fkit',24)
print(u.name)
print(u.age)
u.age = 65
fkit 24
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[14], line 20 18 print(u.name) 19 print(u.age) ---> 20 u.age = 65 Cell In[14], line 15, in User.age(self, value) 13 self.__age = value 14 else: ---> 15 raise ValueError('age must between 10~60') ValueError: age must between 10~60
__call__
- Use
__call__
to determine if it is a property or method
- Use
class User:
def __init__(self,name,password):
self.name = name
self.password = password
def validLogin(self):
print('Verify the indentity of %s' %self.name)
u = User('yyl','admin')
print(hasattr(u.name,'__call__'))
print(hasattr(u.validLogin,'__call__'))
False True
2. Dynamic Programming¶
class Comment:
def __init__(self,detail,view_times):
self.detail = detail
self.view_times = view_times
def info():
print('This is a comment, content is %s' %self.detail)
c = Comment('Crazy Python sold out!',20)
# Check if has the following properties
print(hasattr(c,'detail'))
print(hasattr(c,'info'))
print(hasattr(c,'author'))
# Get the value of properties
print(getattr(c,'detail'))
print(getattr(c,'view_times'))
# Set the value to properties
setattr(c,'detail','Good weather')
print(c.detail)
True True False Crazy Python sold out! 20 Good weather
Add additional Properties
c.test = 'new property'
print(c.test)
new property
3. Sequence-related Methods¶
- Sequence-related magic methods enable objects to emulate
container types
likelists
,tuples
, anddictionaries
. - These methods are essential for creating custom sequences that behave like standard Python sequences.
3.1 Common sequence related magic methods¶
__len__(self)
: return the number of elements__getitem__(self,key)
: return the corresponding element, the key should be an integer or slice object__contains__(self,item)
__setitem__(self,key,value)
__delitem__(self,key)
3.2 Build a character sequence¶
- elements in this sequence has 3 digits
- every digits composed with a character from A to Z
def check_key(key):
'''
This method using for checking if the input index(key) is integer or not, if not, raise a TypeError
and the value could not be negative, otherwise raise a IndexError
'''
if not isinstance(key,int): raise TypeError('index must be an integer')
if key <0 : raise IndexError('index must be a positive number')
class StringSeq:
def __init__(self):
self.__changed = {}
self.__deleted = {}
def __len__(self):
return 26 ** 3
def __getitem__(self,key):
'''
get the element from a list
'''
check_key(key)
if key in self.__changed:
return self.__changed[key]
if key in self.__deleted:
return None
three = key // (26 * 26)
two = (key - three * 26 * 26) // 26
one = key % 26
return chr(65 + three) + chr(65 + two) + chr(65 +one)
def __setitem__(self,key,value):
check_key(key)
self.__changed[key] = value
def __delitem__(self,key):
check_key(key)
if key not in self.__deleted: self.__deleted.append(key)
if key in self.__changed: del self.__changed[key]
sq = StringSeq()
print(len(sq))
print(sq[26*26])
print(sq[1])
sq[1] = 'Python'
print(sq[1])
17576 BAA AAB Python
3.3 Applications¶
52 cards deck It includes thirteen ranks in each of the four French suits:
- Clubs (♣)
- Diamonds (♦)
- Hearts (♥)
- Spades (♠)
def check_key(key):
if not isinstance(key,int): raise TypeError('index must be an integer')
if key < 0: raise IndexError('index must be a positive number')
if key > 52: raise IndexError('index must less than 52')
suit_lst = ['♣','♦','♥','♠']
rank_lst = list(range(2,11)) + ['J','Q','K','A']
class PlayingCards:
def __len__(self):
return 52
def __getitem__(self,key):
check_key(key)
suit = suit_lst[key // 13]
rank = rank_lst[key % 13]
return suit + str(rank)
pc = PlayingCards()
for ele in pc:
print(ele,end = ' ')
♣2 ♣3 ♣4 ♣5 ♣6 ♣7 ♣8 ♣9 ♣10 ♣J ♣Q ♣K ♣A ♦2 ♦3 ♦4 ♦5 ♦6 ♦7 ♦8 ♦9 ♦10 ♦J ♦Q ♦K ♦A ♥2 ♥3 ♥4 ♥5 ♥6 ♥7 ♥8 ♥9 ♥10 ♥J ♥Q ♥K ♥A ♠2 ♠3 ♠4 ♠5 ♠6 ♠7 ♠8 ♠9 ♠10 ♠J ♠Q ♠K ♠A