08 Python Object-Oriented Advanced Features
By Kevin Song
- 08-01 __slots__
- 08-02 @property
- 08-03 multiple inheritance
- 08-04 custom class
- 08-05 enumeration class
- 08-06 meta class
08-01 __slots__
Binding attributes and methods to objects
class Student(object):
pass
Binding properties to objects
>>> s = Student()
>>> s.name = 'Kevin' # Dynamic binding of an attribute to an instance
>>> print(s.name)
Kevin
Object Binding Method
>>> def set_age(self, age): # Define a function as an instance method
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # Binding a method to an instance
>>> s.set_age(25) # Call instance method
>>> s.age # test result
25
Binding an instance does not work for another instance
>>> s2 = Student() # Create a new instance
>>> s2.set_age(25) # Try calling method
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
Binding methods to class es, all instances are available
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
Restrict binding properties and methods to objects
When defining a class, define a special u slots_ variable to restrict the attributes that the class instance can add.
class Student(object):
__slots__ = ('name', 'age') # Define attribute names that allow binding with tuple
Only name and age can be bound
>>> s = Student() # Create a new instance
>>> s.name = 'Michael' # Binding property'name'
>>> s.age = 25 # Binding property'age'
>>> s.score = 99 # Binding property'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
Note: u slots_u Defined attributes only work for the current class instance and not for inherited subclasses
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
08-02 @property
It is not logical to modify attributes directly because they can be modified directly.
s = Student()
s.score = 9999
In order to limit the scope of score, you can use set_score() method to set the score, and get_score() to get the score.
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
The above invocation method is slightly more complex than using attributes directly, so use the @property decorator to change the method into attributes
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
- property: Turn a getter method into an attribute
- @score.setter: Turn a setter method into an attribute
>>> s = Student()
>>> s.score = 60 # OK, actually converted to s.set_score(60)
>>> s.score # OK, actually converted to s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
You can also define a read-only property, a getter method only, and a read-only property if you don't define a setter method:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
08-03 multiple inheritance
class Animal(object):
pass
# Major categories:
class Mammal(Animal):
pass
class Bird(Animal):
pass
# Various animals:
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
Define Runnable and Flyable classes
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
Add Runnable to Dog and Flyable to Bat
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
MixIn
To better see the inheritance relationship: Runnable and Flyable changed to Runnable MixIn and Flyable MixIn
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
08-04 custom class
Output custom information: u str_ and u repr_()
Print custom information: u str_u
Define Student classes, print instances
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>
Print custom instance information with u str_u
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
Direct display of variable customization information: u repr_()
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
... __repr__ = __str__
...
>>> s = Student('Michael')
>>> s
Student object (name: Michael)
__iter__
If a class wants to be used for... An in loop, like a list or tuple, must implement an iter() method that returns an iterated object
The for loop calls the next() method of the iteration object to get the next value of the loop until it exits when a StopIteration error occurs.
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # Initialize two counters a, b
def __iter__(self):
return self # The instance itself is an iteration object, so it returns to itself
def __next__(self):
self.a, self.b = self.b, self.a + self.b # Calculate the next value
if self.a > 100000: # Conditions for exit from the loop
raise StopIteration()
return self.a # Returns the next value
__getattr__
When calling a method or attribute of a class, if it does not exist, an error will be reported
class Student(object):
def __init__(self):
self.name = 'Michael'
>>> s = Student()
>>> print(s.name)
Michael
>>> print(s.score)
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'score'
To avoid this error, write a getattr() method that dynamically returns an attribute
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
When calling non-existent attributes, such as score, the Python interpreter attempts to call getattr(self,'score') to try to obtain attributes.
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
Note: getattr is called only if no attribute is found. Existing attributes, such as name, are not found in getattr.
The return function is perfectly OK.
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
>>> s.age()
25
__call__
Call instances directly
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
>>> s = Student('Kevin')
>>> s() # Do not pass in the self parameter
My name is Kevin.
Determine whether an object can be called (is it a Callable object)?
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
08-05 enumeration class
Constants defined in capitals are variables.
JAN = 1
FEB = 2
MAR = 3
Solution: Enum class (define a class type for such an enumeration type, and then each constant is a unique instance of class)
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
Use Month.Jan directly to refer to a constant
>>> Month.Jan
<Month.Jan: 1>
Enumerate all members
>>> for name, member in Month.__members__.items():
... print(name, '=>', member, ',', member.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
>>>
More precise control of enumeration types can derive custom classes from Enum
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun's value is set to 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
The @unique decorator can help us check to ensure that there are no duplicate values
There are several ways to access these enumeration types
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
08-06 meta class
type()
Function 1: Look at the type of a type or variable
Define a Hello class
class Hello(object):
def hello(self, name='world'):
print('Hello, %s.' % name)
>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class 'hello.Hello'>
- Hello is a class whose type is type.
- h is an instance, and its type is class Hello
Function 2: Create a class object
- The type() function passes in three parameters in turn
- The name of class
- A collection of inherited parent classes (Python supports multiple inheritance, and if there is only one parent class, it needs to be written in tuple's single element)
- class method name and function binding
>>> def fn(self, name='world'): # Define function first
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # Create Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
metaclass
Definition: Classes of classes (classes as instances of metaclasses)
Role: Controls class creation behavior