In some unspecified time in the future in your Python coding journey, chances are you’ll have to create customized list-like courses with modified conduct, new functionalities, or each. To do that in Python, you possibly can inherit from an abstract base class, subclass the built-in listing
class straight, or inherit from UserList
, which lives within the collections
module.
On this tutorial, you’ll discover ways to:
- Create customized list-like courses by inheriting from the built-in
listing
class - Construct customized list-like courses by subclassing
UserList
from thecollections
module
You’ll additionally write some examples that’ll assist you determine which mother or father class, listing
or UserList
, to make use of when creating your customized listing courses.
To get probably the most out of this tutorial, you need to be aware of Python’s built-in list
class and its normal options. You’ll additionally have to know the fundamentals of object-oriented programming and perceive how inheritance works in Python.
Creating Listing-Like Courses in Python
The built-in list
class is a basic knowledge sort in Python. Lists are helpful in lots of conditions and have tons of sensible use instances. In a few of these use instances, the usual performance of Python listing
could also be inadequate, and chances are you’ll have to create customized list-like courses to handle the issue at hand.
You’ll sometimes discover no less than two causes for creating customized list-like courses:
- Extending the common listing by including new performance
- Modifying the usual listing’s performance
It’s also possible to face conditions by which you might want to each lengthen and modify the listing’s normal performance.
Relying in your particular wants and talent degree, you need to use a couple of methods to create your individual customized list-like courses. You’ll be able to:
There are a couple of concerns if you’re choosing the suitable technique to make use of. Maintain studying for extra particulars.
Constructing a Listing-Like Class From an Summary Base Class
You’ll be able to create your individual list-like courses by inheriting from an applicable summary base class (ABC), like MutableSequence
. This ABC gives generic implementations of most listing
strategies aside from .__getitem__()
, .__setitem__()
, .__delitem__
, .__len__()
, and .insert()
. So, when inheriting from this class, you’ll need to implement these strategies your self.
Writing your individual implementation for all these special methods is a good quantity of labor. It’s error-prone and requires superior information of Python and its data model. It might probably additionally indicate efficiency points since you’ll be writing the strategies in pure Python.
Moreover, suppose you might want to customise the performance of another normal listing methodology, like .append()
or .insert()
. In that case, you’ll need to override the default implementation and supply an appropriate implementation that fulfills your wants.
The primary benefit of this technique for creating list-like courses is that the mother or father ABC class will provide you with a warning should you miss any required strategies in your customized implementation.
Basically, you need to embrace this technique provided that you want a list-like class that’s essentially totally different from the built-in listing
class.
On this tutorial, you’ll deal with creating list-like courses by inheriting from the built-in listing
class and the UserList
class from the standard-library collections
module. These methods appear to be the quickest and most sensible ones.
Inheriting From Python’s Constructed-in listing
Class
For a very long time, it was unimaginable to inherit straight from Python sorts applied in C. Python 2.2 fastened this subject. Now you possibly can subclass built-in types, together with listing
. This variation has introduced a number of technical benefits to the subclasses as a result of now they:
The primary merchandise on this listing could also be a requirement for C code that expects a Python built-in class. The second merchandise permits you to add new performance on prime of the usual listing conduct. Lastly, the third merchandise will allow you to limit the attributes of a subclass to solely these attributes predefined in .__slots__
.
To kick issues off and begin creating customized list-like courses, say that you just want an inventory that routinely shops all its objects as strings. Assuming that your customized listing will retailer numbers as strings solely, you possibly can create the next subclass of listing
:
# string_list.py
class StringList(listing):
def __init__(self, iterable):
tremendous().__init__(str(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
tremendous().__setitem__(index, str(merchandise))
def insert(self, index, merchandise):
tremendous().insert(index, str(merchandise))
def append(self, merchandise):
tremendous().append(str(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
tremendous().lengthen(different)
else:
tremendous().lengthen(str(merchandise) for merchandise in different)
Your StringList
class subclasses listing
straight, which implies that it’ll inherit all of the performance of a typical Python listing
. Since you need your listing to retailer objects as strings, you might want to modify all of the strategies that add or modify objects within the underlying listing. These strategies embody the next:
.__init__
initializes all the category’s new cases..__setitem__()
permits you to assign a brand new worth to an current merchandise utilizing the merchandise’s index, like ina_list[index] = merchandise
..insert()
permits you to insert a brand new merchandise at a given place within the underlying listing utilizing the merchandise’s index..append()
provides a single new merchandise on the finish of the underlying listing..lengthen()
provides a collection of things to the top of the listing.
The opposite strategies that your StringList
class inherited from listing
work simply tremendous as a result of they don’t add or replace objects in your customized listing.
Be aware: If you need your StringList
class to assist concatenation with the plus operator (+
), you then’ll additionally have to implement different particular strategies, equivalent to .__add__()
, .__radd__()
, and .__iadd__()
.
To make use of StringList
in your code, you are able to do one thing like this:
>>> from string_list import StringList
>>> knowledge = StringList([1, 2, 2, 4, 5])
>>> knowledge
['1', '2', '2', '4', '5']
>>> knowledge.append(6)
>>> knowledge
['1', '2', '2', '4', '5', '6']
>>> knowledge.insert(0, 0)
>>> knowledge
['0', '1', '2', '2', '4', '5', '6']
>>> knowledge.lengthen([7, 8, 9])
>>> knowledge
['0', '1', '2', '2', '4', '5', '6', '7', '8', '9']
>>> knowledge[3] = 3
>>> knowledge
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
Your class works as anticipated. It converts all of the enter values into strings on the fly. That’s cool, isn’t it? Whenever you create a brand new occasion of StringList
, the category’s initializer takes care of the conversion.
Whenever you append, insert, lengthen, or assign new values to the category’s cases, the strategies that assist every operation will care for the string conversion course of. This fashion, your listing will at all times retailer its objects as string objects.
Subclassing UserList
From collections
One other method to create a customized list-like class is to make use of the UserList
class from the collections
module. This class is a wrapper across the built-in listing
sort. It was designed for creating list-like objects again when it wasn’t doable to inherit from the built-in listing
class straight.
Despite the fact that the necessity for this class has been partially supplanted by the potential for straight subclassing the built-in listing
class, UserList
remains to be out there within the standard library, each for comfort and for backward compatibility.
The distinguishing function of UserList
is that it provides you entry to its .knowledge
attribute, which might facilitate the creation of your customized lists since you don’t want to make use of super()
on a regular basis. The .knowledge
attribute holds a daily Python listing
, which is empty by default.
Right here’s how one can reimplement your StringList
class by inheriting from UserList
:
# string_list.py
from collections import UserList
class StringList(UserList):
def __init__(self, iterable):
tremendous().__init__(str(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
self.knowledge[index] = str(merchandise)
def insert(self, index, merchandise):
self.knowledge.insert(index, str(merchandise))
def append(self, merchandise):
self.knowledge.append(str(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
self.knowledge.lengthen(different)
else:
self.knowledge.lengthen(str(merchandise) for merchandise in different)
On this instance, accessing the .knowledge
attribute permits you to code the category in a extra simple manner by utilizing delegation, which implies that the listing in .knowledge
takes care of dealing with all of the requests.
Now you nearly don’t have to make use of superior instruments like tremendous()
. You simply have to name this operate within the class initializer to stop issues in additional inheritance situations. In the remainder of the strategies, you simply benefit from .knowledge
, which holds a daily Python listing. Working with lists is a talent that you just most likely have already got.
Be aware: Within the above instance, you’ll be okay should you reuse the unique inner implementation of StringList
from the previous section however change the mother or father class from listing
to UserList
. Your code will work the identical. Nonetheless, utilizing .knowledge
can facilitate the method of coding list-like courses.
This new model works the identical as your first model of StringList
. Go forward and run the next code to strive it out:
>>> from string_list import StringList
>>> knowledge = StringList([1, 2, 2, 4, 5])
>>> knowledge
['1', '2', '2', '4', '5']
>>> knowledge.append(6)
>>> knowledge
['1', '2', '2', '4', '5', '6']
>>> knowledge.insert(0, 0)
>>> knowledge
['0', '1', '2', '2', '4', '5', '6']
>>> knowledge.lengthen([7, 8, 9])
>>> knowledge
['0', '1', '2', '2', '4', '5', '6', '7', '8', '9']
>>> knowledge[3] = 3
>>> knowledge
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
Exposing .knowledge
is probably the most related function of UserList
, as you’ve already realized. This attribute can simplify your courses since you don’t want to make use of tremendous()
on a regular basis. You’ll be able to simply benefit from .knowledge
and use the acquainted listing
interface to work with this attribute.
Coding Listing-Like Courses: Sensible Examples
You already know the right way to use listing
and UserList
when you might want to create customized list-like courses that add or modify the usual performance of listing
.
Admittedly, if you consider making a list-like class, inheriting from listing
most likely appears extra pure than inheriting from UserList
as a result of Python builders find out about listing
. They may not concentrate on the existence of UserList
.
You additionally know that the primary distinction between these two courses is that if you inherit from UserList
, you’ve got entry to the .knowledge
attribute, which is a daily listing which you can manipulate by the usual listing
interface. In distinction, inheriting from listing
requires superior information about Python’s knowledge mannequin, together with instruments just like the built-in tremendous()
operate and a few particular strategies.
Within the following sections, you’ll code a couple of sensible examples utilizing each courses. After writing these examples, you’ll be higher ready to pick out the correct instrument to make use of when you might want to outline customized list-like courses in your code.
A Listing That Accepts Numeric Knowledge Solely
As a primary instance of making a list-like class with customized conduct, say that you just want an inventory that accepts numeric knowledge solely. Your listing ought to retailer solely integer, float, and complex numbers. When you attempt to retailer a price of another knowledge sort, like a string, then your listing ought to elevate a TypeError
.
Right here’s an implementation of a NumberList
class with the specified performance:
# number_list.py
class NumberList(listing):
def __init__(self, iterable):
tremendous().__init__(self._validate_number(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
tremendous().__setitem__(index, self._validate_number(merchandise))
def insert(self, index, merchandise):
tremendous().insert(index, self._validate_number(merchandise))
def append(self, merchandise):
tremendous().append(self._validate_number(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
tremendous().lengthen(different)
else:
tremendous().lengthen(self._validate_number(merchandise) for merchandise in different)
def _validate_number(self, worth):
if isinstance(worth, (int, float, advanced)):
return worth
elevate TypeError(
f"numeric worth anticipated, acquired sort(worth).__name__"
)
On this instance, your NumberList
class inherits straight from listing
. Because of this your class shares all of the core performance with the built-in listing
class. You’ll be able to iterate over cases of NumberList
, entry and replace its objects utilizing their indices, name widespread listing
strategies, and extra.
Now, to make sure that each enter merchandise is a quantity, you might want to validate every merchandise in all of the strategies that assist operations for including new objects or updating current objects within the listing. The required strategies are the identical as within the StringList
instance again within the Inheriting From Python’s Built-In list
class part.
To validate the enter knowledge, you utilize a helper methodology known as ._validate_number()
. This methodology makes use of the built-in isinstance()
operate to examine if the present enter worth is an occasion of int
, float
, or advanced
, that are the built-in courses representing numeric values in Python.
Be aware: A extra generic method to examine whether or not a price is a quantity in Python can be to make use of Number
from the numbers
module. This can help you validate Fraction
and Decimal
objects too.
If the enter worth is an occasion of a numeric knowledge sort, then your helper operate returns the worth itself. In any other case, the operate raises a TypeError
exception with an applicable error message.
To make use of NumberList
, return to your interactive session and run the next code:
>>> from number_list import NumberList
>>> numbers = NumberList([1.1, 2, 3j])
>>> numbers
[1.1, 2, 3j]
>>> numbers.append("4.2")
Traceback (most up-to-date name final):
...
TypeError: numeric worth anticipated, acquired str
>>> numbers.append(4.2)
>>> numbers
[1.1, 2, 3j, 4.2]
>>> numbers.insert(0, "0")
Traceback (most up-to-date name final):
...
TypeError: numeric worth anticipated, acquired str
>>> numbers.insert(0, 0)
>>> numbers
[0, 1.1, 2, 3j, 4.2]
>>> numbers.lengthen(["5.3", "6"])
Traceback (most up-to-date name final):
...
TypeError: numeric worth anticipated, acquired str
>>> numbers.lengthen([5.3, 6])
>>> numbers
[0, 1.1, 2, 3j, 4.2, 5.3, 6]
In these examples, the operations that add or modify knowledge in numbers
routinely validate the enter to make sure that solely numeric values are accepted. When you add a string worth to numbers
, you then get a TypeError
.
Another implementation of NumberList
utilizing UserList
can look one thing like this:
# number_list.py
from collections import UserList
class NumberList(UserList):
def __init__(self, iterable):
tremendous().__init__(self._validate_number(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
self.knowledge[index] = self._validate_number(merchandise)
def insert(self, index, merchandise):
self.knowledge.insert(index, self._validate_number(merchandise))
def append(self, merchandise):
self.knowledge.append(self._validate_number(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
self.knowledge.lengthen(different)
else:
self.knowledge.lengthen(self._validate_number(merchandise) for merchandise in different)
def _validate_number(self, worth):
if isinstance(worth, (int, float, advanced)):
return worth
elevate TypeError(
f"numeric worth anticipated, acquired sort(worth).__name__"
)
On this new implementation of NumberList
, you inherit from UserList
. Once more, your class will share all of the core performance with a daily listing
.
On this instance, as a substitute of utilizing tremendous()
on a regular basis to entry strategies and attributes within the mother or father class, you utilize the .knowledge
attribute straight. To some extent, utilizing .knowledge
arguably simplifies your code in comparison with utilizing tremendous()
and different superior instruments like particular strategies.
Be aware that you just solely use tremendous()
within the class initializer, .__init__()
. It is a finest observe if you’re working with inheritance in Python. It permits you to correctly initialize attributes within the mother or father class with out breaking issues.
A Listing With Further Performance
Now say that you just want a list-like class with all the usual performance of a daily Python listing
. Your class must also present some further performance borrowed from the Array knowledge sort of JavaScript. For instance, you’ll have to have strategies like the next:
.be part of()
concatenates all of the listing’s objects in a single string..map(motion)
yields new objects that outcome from making use of anmotion()
callable to every merchandise within the underlying listing..filter(predicate)
yields all of the objects that returnTrue
when callingpredicate()
on them..for_each(func)
callsfunc()
on each merchandise within the underlying listing to generate some side effect.
Right here’s a category that implements all these new options by subclassing listing
:
# custom_list.py
class CustomList(listing):
def be part of(self, separator=" "):
return separator.be part of(str(merchandise) for merchandise in self)
def map(self, motion):
return sort(self)(motion(merchandise) for merchandise in self)
def filter(self, predicate):
return sort(self)(merchandise for merchandise in self if predicate(merchandise))
def for_each(self, func):
for merchandise in self:
func(merchandise)
The .be part of()
methodology in CustomList
takes a separator character as an argument and makes use of it to concatenate the objects within the present listing object, which is represented by self
. To do that, you utilize str.join()
with a generator expression as an argument. This generator expression converts each merchandise right into a string object utilizing str()
.
The .map()
methodology returns a CustomList
object. To assemble this object, you utilize a generator expression that applies motion()
to each merchandise within the present object, self
. Be aware that the motion might be any callable that takes an merchandise as an argument and returns a remodeled merchandise.
The .filter()
methodology additionally returns a CustomList
object. To construct this object, you utilize a generator expression that yields the objects for which predicate()
returns True
. On this case, predicate()
have to be a Boolean-valued function that returns True
or False
relying on sure circumstances utilized to the enter merchandise.
Lastly, the .for_each()
methodology calls func()
on each merchandise within the underlying listing. This name doesn’t return something however triggers some unwanted side effects, as you’ll see beneath.
To make use of this class in your code, you are able to do one thing like the next:
>>> from custom_list import CustomList
>>> phrases = CustomList(
... [
... "Hello,",
... "Pythonista!",
... "Welcome",
... "to",
... "Real",
... "Python!"
... ]
... )
>>> phrases.be part of()
'Good day, Pythonista! Welcome to Actual Python!'
>>> phrases.map(str.higher)
['HELLO,', 'PYTHONISTA!', 'WELCOME', 'TO', 'REAL', 'PYTHON!']
>>> phrases.filter(lambda phrase: phrase.startswith("Py"))
['Pythonista!', 'Python!']
>>> phrases.for_each(print)
Good day,
Pythonista!
Welcome
to
Actual
Python!
In these examples, you first name .be part of()
on phrases
. This methodology returns a singular string that outcomes from concatenating all of the objects within the underlying listing.
The decision to .map()
returns a CustomList
object containing uppercased phrases. This transformation outcomes from making use of str.higher()
to all of the objects in phrases
. This methodology works fairly equally to the built-in map()
operate. The primary distinction is that as a substitute of returning an inventory, the built-in map()
operate returns an iterator that yields remodeled objects lazily.
The .filter()
methodology takes a lambda
operate as an argument. Within the instance, this lambda
operate makes use of str.startswith()
to pick out these phrases that begin with the "Py"
prefix. Be aware that this methodology works equally to the built-in filter()
operate, which returns an iterator as a substitute of an inventory.
Lastly, the decision to .for_each()
on phrases
prints each phrase to the display as a facet impact of calling print()
on every merchandise within the underlying listing. Be aware that the operate handed to .for_each()
ought to take an merchandise as an argument, nevertheless it shouldn’t return any fruitful worth.
It’s also possible to implement CustomList
by inheriting from UserList
quite than from listing
. On this case, you don’t want to vary the inner implementation, simply the bottom class:
# custom_list.py
from collections import UserList
class CustomList(UserList):
def be part of(self, separator=" "):
return separator.be part of(str(merchandise) for merchandise in self)
def map(self, motion):
return sort(self)(motion(merchandise) for merchandise in self)
def filter(self, predicate):
return sort(self)(merchandise for merchandise in self if predicate(merchandise))
def for_each(self, func):
for merchandise in self:
func(merchandise)
Be aware that on this instance, you simply modified the mother or father class. There’s no want to make use of .knowledge
straight. Nonetheless, you need to use it if you’d like. The benefit is that you just’ll present extra context to different builders studying your code:
# custom_list.py
from collections import UserList
class CustomList(UserList):
def be part of(self, separator=" "):
return separator.be part of(str(merchandise) for merchandise in self.knowledge)
def map(self, motion):
return sort(self)(motion(merchandise) for merchandise in self.knowledge)
def filter(self, predicate):
return sort(self)(merchandise for merchandise in self.knowledge if predicate(merchandise))
def for_each(self, func):
for merchandise in self.knowledge:
func(merchandise)
On this new model of CustomList()
, the one change is that you just’ve changed self
with self.knowledge
to make it clear that you just’re working with a UserList
subclass. This variation makes your code extra specific.
Contemplating Efficiency: listing
vs UserList
Up thus far, you’ve realized the right way to create your individual list-like courses by inheriting from both listing
or UserList
. You additionally know that the one seen distinction between these two courses is that UserList
exposes the .knowledge
attribute, which might facilitate the coding course of.
On this part, you’ll contemplate a facet that may be necessary on the subject of deciding whether or not to make use of listing
or UserList
to create your customized list-like courses. That’s efficiency!
To guage if there are efficiency variations between courses that inherit from listing
vs UserList
, you’ll use the StringList
class. Go forward and create a brand new Python file containing the next code:
# efficiency.py
from collections import UserList
class StringList_list(listing):
def __init__(self, iterable):
tremendous().__init__(str(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
tremendous().__setitem__(index, str(merchandise))
def insert(self, index, merchandise):
tremendous().insert(index, str(merchandise))
def append(self, merchandise):
tremendous().append(str(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
tremendous().lengthen(different)
else:
tremendous().lengthen(str(merchandise) for merchandise in different)
class StringList_UserList(UserList):
def __init__(self, iterable):
tremendous().__init__(str(merchandise) for merchandise in iterable)
def __setitem__(self, index, merchandise):
self.knowledge[index] = str(merchandise)
def insert(self, index, merchandise):
self.knowledge.insert(index, str(merchandise))
def append(self, merchandise):
self.knowledge.append(str(merchandise))
def lengthen(self, different):
if isinstance(different, sort(self)):
self.knowledge.lengthen(different)
else:
self.knowledge.lengthen(str(merchandise) for merchandise in different)
These two courses work the identical. Nonetheless, they’re internally totally different. StringList_list
inherits from listing
, and its implementation is predicated on tremendous()
. In distinction, StringList_UserList
inherits from UserList
, and its implementation depends on the inner .knowledge
attribute.
To check the efficiency of those two courses, you need to start by timing normal listing operations, equivalent to instantiation. Nonetheless, in these examples, each initializers are equal, so they need to carry out the identical.
Measuring the execution time of latest functionalities can also be helpful. For instance, you possibly can examine the execution time of .lengthen()
. Go forward and run the next code:
>>> import timeit
>>> from efficiency import StringList_list, StringList_UserList
>>> init_data = vary(10000)
>>> extended_list = StringList_list(init_data)
>>> list_extend = min(
... timeit.repeat(
... stmt="extended_list.lengthen(init_data)",
... quantity=5,
... repeat=2,
... globals=globals(),
... )
... ) * 1e6
>>> extended_user_list = StringList_UserList(init_data)
>>> user_list_extend = min(
... timeit.repeat(
... stmt="extended_user_list.lengthen(init_data)",
... quantity=5,
... repeat=2,
... globals=globals(),
... )
... ) * 1e6
>>> f"StringList_list().lengthen() time: list_extend:.2f μs"
'StringList_list().lengthen() time: 4632.08 μs'
>>> f"StringList_UserList().lengthen() time: user_list_extend:.2f μs"
'StringList_UserList().lengthen() time: 4612.62 μs'
On this efficiency take a look at, you utilize the timeit
module together with the min()
operate to measure the execution time of a bit of code. The goal code consists of calls to .lengthen()
on cases of StringList_list
and StringList_UserList
utilizing some pattern knowledge.
The efficiency distinction between the category primarily based on listing
and the category primarily based on UserList
is generally nonexistent on this instance.
Typically, if you create a customized list-like class, you’d count on subclasses of listing
to carry out higher than subclasses of UserList
. Why? As a result of listing
is written in C and optimized for efficiency, whereas UserList
is a wrapper class written in pure Python.
Nonetheless, within the above instance, it seems like this assumption isn’t utterly proper. For that reason, to determine which superclass is finest in your particular use case, make certain to run a efficiency take a look at.
Efficiency apart, inheriting from listing
is arguably the pure manner in Python, principally as a result of listing
is straight out there to Python builders as a built-in class. Moreover, most Python builders will likely be aware of lists and their normal options, which is able to permit them to put in writing list-like courses extra shortly.
In distinction, the UserList
class lives within the collections
module, that means that you just’ll need to import it if you wish to use it in your code. Moreover, not all Python builders are conscious of the existence of UserList
. Nonetheless, UserList
can nonetheless be a great tool due to the comfort of accessing the .knowledge
attribute, which might facilitate the creation of customized list-like courses.
Conclusion
You’ve now realized the right way to create customized list-like courses with modified and new behaviors. To do that, you’ve subclassed the built-in listing
class straight. As a substitute, you’ve additionally inherited from the UserList
class, which is out there within the collections
module.
Inheriting from listing
and subclassing UserList
are each appropriate methods for approaching the issue of making your individual list-like courses in Python.
On this tutorial, you realized the right way to:
- Create list-like courses by inheriting from the built-in
listing
class - Construct list-like courses by subclassing
UserList
from thecollections
module
Now you’re higher ready to create your individual customized lists, permitting you to leverage the complete energy of this handy and commonplace knowledge sort in Python.