Friday, December 6, 2024
HomePythonInheriting From record vs UserList – Actual Python

Inheriting From record vs UserList – Actual Python


Sooner or later in your Python coding journey, chances are you’ll must create customized list-like lessons with modified habits, new functionalities, or each. To do that in Python, you’ll be able to inherit from an summary base class, subclass the built-in record class instantly, or inherit from UserList, which lives within the collections module.

On this tutorial, you’ll discover ways to:

  • Create customized list-like lessons by inheriting from the built-in record class
  • Construct customized list-like lessons by subclassing UserList from the collections module

You’ll additionally write some examples that’ll enable you to determine which dad or mum class, record or UserList, to make use of when creating your customized record lessons.

To get probably the most out of this tutorial, try to be aware of Python’s built-in record class and its customary options. You’ll additionally must know the fundamentals of object-oriented programming and perceive how inheritance works in Python.

Creating Listing-Like Lessons in Python

The built-in record class is a basic knowledge kind in Python. Lists are helpful in lots of conditions and have tons of sensible use circumstances. In a few of these use circumstances, the usual performance of Python record could also be inadequate, and chances are you’ll must create customized list-like lessons to deal with the issue at hand.

You’ll usually discover at the least two causes for creating customized list-like lessons:

  1. Extending the common record by including new performance
  2. Modifying the usual record’s performance

It’s also possible to face conditions wherein it’s good to each lengthen and modify the record’s customary performance.

Relying in your particular wants and ability stage, you should use a couple of methods to create your personal customized list-like lessons. You may:

There are a couple of concerns while you’re choosing the suitable technique to make use of. Preserve studying for extra particulars.

Constructing a Listing-Like Class From an Summary Base Class

You may create your personal list-like lessons by inheriting from an acceptable summary base class (ABC), like MutableSequence. This ABC gives generic implementations of most record strategies aside from .__getitem__(), .__setitem__(), .__delitem__, .__len__(), and .insert(). So, when inheriting from this class, you’ll should implement these strategies your self.

Writing your personal implementation for all these particular strategies is a good quantity of labor. It’s error-prone and requires superior information of Python and its knowledge mannequin. It may additionally indicate efficiency points since you’ll be writing the strategies in pure Python.

Moreover, suppose it’s good to customise the performance of some other customary record technique, like .append() or .insert(). In that case, you’ll should override the default implementation and supply an acceptable implementation that fulfills your wants.

The primary benefit of this technique for creating list-like lessons is that the dad or mum ABC class will warn you if you happen to miss any required strategies in your customized implementation.

On the whole, it’s best to embrace this technique provided that you want a list-like class that’s essentially completely different from the built-in record class.

On this tutorial, you’ll give attention to creating list-like lessons by inheriting from the built-in record 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 record Class

For a very long time, it was unattainable to inherit instantly from Python varieties applied in C. Python 2.2 mounted this problem. Now you’ll be able to subclass built-in varieties, together with record. This modification has introduced a number of technical benefits to the subclasses as a result of now they:

The primary merchandise on this record could also be a requirement for C code that expects a Python built-in class. The second merchandise lets you add new performance on prime of the usual record habits. 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 lessons, say that you just want an inventory that robotically shops all its objects as strings. Assuming that your customized record will retailer numbers as strings solely, you’ll be able to create the next subclass of record:

# string_list.py

class StringList(record):
    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, kind(self)):
            tremendous().lengthen(different)
        else:
            tremendous().lengthen(str(merchandise) for merchandise in different)

Your StringList class subclasses record instantly, which signifies that it’ll inherit all of the performance of a typical Python record. Since you need your record to retailer objects as strings, it’s good to modify all of the strategies that add or modify objects within the underlying record. These strategies embody the next:

  • .__init__ initializes all the category’s new cases.
  • .__setitem__() lets you assign a brand new worth to an current merchandise utilizing the merchandise’s index, like in a_list[index] = merchandise.
  • .insert() lets you insert a brand new merchandise at a given place within the underlying record utilizing the merchandise’s index.
  • .append() provides a single new merchandise on the finish of the underlying record.
  • .lengthen() provides a collection of things to the top of the record.

The opposite strategies that your StringList class inherited from record work simply wonderful as a result of they don’t add or replace objects in your customized record.

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? If you create a brand new occasion of StringList, the category’s initializer takes care of the conversion.

If 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 manner, your record will at all times retailer its objects as string objects.

Subclassing UserList From collections

One other solution 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 record kind. It was designed for creating list-like objects again when it wasn’t attainable to inherit from the built-in record class instantly.

Despite the fact that the necessity for this class has been partially supplanted by the potential for instantly subclassing the built-in record class, UserList continues to be obtainable within the customary library, each for comfort and for backward compatibility.

The distinguishing characteristic of UserList is that it provides you entry to its .knowledge attribute, which may facilitate the creation of your customized lists since you don’t want to make use of tremendous() on a regular basis. The .knowledge attribute holds an everyday Python record, 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, kind(self)):
            self.knowledge.lengthen(different)
        else:
            self.knowledge.lengthen(str(merchandise) for merchandise in different)

On this instance, gaining access to the .knowledge attribute lets you code the category in a extra easy method by utilizing delegation, which signifies that the record in .knowledge takes care of dealing with all of the requests.

Now you virtually don’t have to make use of superior instruments like tremendous(). You simply must name this perform within the class initializer to stop issues in additional inheritance eventualities. In the remainder of the strategies, you simply make the most of .knowledge, which holds an everyday Python record. Working with lists is a ability that you just most likely have already got.

This new model works the identical as your first model of StringList. Go forward and run the next code to attempt 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 characteristic of UserList, as you’ve already realized. This attribute can simplify your lessons since you don’t want to make use of tremendous() on a regular basis. You may simply make the most of .knowledge and use the acquainted record interface to work with this attribute.

Coding Listing-Like Lessons: Sensible Examples

You already know use record and UserList when it’s good to create customized list-like lessons that add or modify the usual performance of record.

Admittedly, while you consider making a list-like class, inheriting from record most likely appears extra pure than inheriting from UserList as a result of Python builders find out about record. They won’t pay attention to the existence of UserList.

You additionally know that the principle distinction between these two lessons is that while you inherit from UserList, you have got entry to the .knowledge attribute, which is an everyday record that you may manipulate by means of the usual record interface. In distinction, inheriting from record requires superior information about Python’s knowledge mannequin, together with instruments just like the built-in tremendous() perform and a few particular strategies.

Within the following sections, you’ll code a couple of sensible examples utilizing each lessons. After writing these examples, you’ll be higher ready to pick the appropriate instrument to make use of when it’s good to outline customized list-like lessons in your code.

A Listing That Accepts Numeric Knowledge Solely

As a primary instance of making a list-like class with customized habits, say that you just want an inventory that accepts numeric knowledge solely. Your record ought to retailer solely integer, float, and advanced numbers. In case you attempt to retailer a worth of some other knowledge kind, like a string, then your record ought to elevate a TypeError.

Right here’s an implementation of a NumberList class with the specified performance:

# number_list.py

class NumberList(record):
    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, kind(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, obtained {kind(worth).__name__}"
        )

On this instance, your NumberList class inherits instantly from record. Which means your class shares all of the core performance with the built-in record class. You may iterate over cases of NumberList, entry and replace its objects utilizing their indices, name widespread record strategies, and extra.

Now, to make sure that each enter merchandise is a quantity, it’s good to validate every merchandise in all of the strategies that assist operations for including new objects or updating current objects within the record. The required strategies are the identical as within the StringList instance again within the Inheriting From Python’s Constructed-In record class part.

To validate the enter knowledge, you employ a helper technique referred to as ._validate_number(). This technique makes use of the built-in isinstance() perform to test if the present enter worth is an occasion of int, float, or advanced, that are the built-in lessons representing numeric values in Python.

If the enter worth is an occasion of a numeric knowledge kind, then your helper perform returns the worth itself. In any other case, the perform raises a TypeError exception with an acceptable 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, obtained 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, obtained 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, obtained 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 robotically validate the enter to make sure that solely numeric values are accepted. In case 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, kind(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, obtained {kind(worth).__name__}"
        )

On this new implementation of NumberList, you inherit from UserList. Once more, your class will share all of the core performance with an everyday record.

On this instance, as an alternative of utilizing tremendous() on a regular basis to entry strategies and attributes within the dad or mum class, you employ the .knowledge attribute instantly. To some extent, utilizing .knowledge arguably simplifies your code in comparison with utilizing tremendous() and different superior instruments like particular strategies.

Observe that you just solely use tremendous() within the class initializer, .__init__(). This can be a finest apply while you’re working with inheritance in Python. It lets you correctly initialize attributes within the dad or mum 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 an everyday Python record. Your class also needs to present some further performance borrowed from the Array knowledge kind of JavaScript. For instance, you’ll must have strategies like the next:

  • .be a part of() concatenates all of the record’s objects in a single string.
  • .map(motion) yields new objects that outcome from making use of an motion() callable to every merchandise within the underlying record.
  • .filter(predicate) yields all of the objects that return True when calling predicate() on them.
  • .for_each(func) calls func() on each merchandise within the underlying record to generate some aspect impact.

Right here’s a category that implements all these new options by subclassing record:

# custom_list.py

class CustomList(record):
    def be a part of(self, separator=" "):
        return separator.be a part of(str(merchandise) for merchandise in self)

    def map(self, motion):
        return kind(self)(motion(merchandise) for merchandise in self)

    def filter(self, predicate):
        return kind(self)(merchandise for merchandise in self if predicate(merchandise))

    def for_each(self, func):
        for merchandise in self:
            func(merchandise)

The .be a part of() technique in CustomList takes a separator character as an argument and makes use of it to concatenate the objects within the present record object, which is represented by self. To do that, you employ str.be a part of() with a generator expression as an argument. This generator expression converts each merchandise right into a string object utilizing str().

The .map() technique returns a CustomList object. To assemble this object, you employ a generator expression that applies motion() to each merchandise within the present object, self. Observe that the motion might be any callable that takes an merchandise as an argument and returns a reworked merchandise.

The .filter() technique additionally returns a CustomList object. To construct this object, you employ a generator expression that yields the objects for which predicate() returns True. On this case, predicate() have to be a Boolean-valued perform that returns True or False relying on sure situations utilized to the enter merchandise.

Lastly, the .for_each() technique calls func() on each merchandise within the underlying record. This name doesn’t return something however triggers some unintended 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 a part of()
'Howdy, 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)
Howdy,
Pythonista!
Welcome
to
Actual
Python!

In these examples, you first name .be a part of() on phrases. This technique returns a singular string that outcomes from concatenating all of the objects within the underlying record.

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 technique works fairly equally to the built-in map() perform. The primary distinction is that as an alternative of returning an inventory, the built-in map() perform returns an iterator that yields reworked objects lazily.

The .filter() technique takes a lambda perform as an argument. Within the instance, this lambda perform makes use of str.startswith() to pick these phrases that begin with the "Py" prefix. Observe that this technique works equally to the built-in filter() perform, which returns an iterator as an alternative of an inventory.

Lastly, the decision to .for_each() on phrases prints each phrase to the display as a aspect impact of calling print() on every merchandise within the underlying record. Observe that the perform handed to .for_each() ought to take an merchandise as an argument, however it shouldn’t return any fruitful worth.

It’s also possible to implement CustomList by inheriting from UserList relatively than from record. On this case, you don’t want to alter the interior implementation, simply the bottom class:

# custom_list.py

from collections import UserList

class CustomList(UserList):
    def be a part of(self, separator=" "):
        return separator.be a part of(str(merchandise) for merchandise in self)

    def map(self, motion):
        return kind(self)(motion(merchandise) for merchandise in self)

    def filter(self, predicate):
        return kind(self)(merchandise for merchandise in self if predicate(merchandise))

    def for_each(self, func):
        for merchandise in self:
            func(merchandise)

Observe that on this instance, you simply modified the dad or mum class. There’s no want to make use of .knowledge instantly. Nonetheless, you should 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 a part of(self, separator=" "):
        return separator.be a part of(str(merchandise) for merchandise in self.knowledge)

    def map(self, motion):
        return kind(self)(motion(merchandise) for merchandise in self.knowledge)

    def filter(self, predicate):
        return kind(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 modification makes your code extra specific.

Contemplating Efficiency: record vs UserList

Up so far, you’ve realized create your personal list-like lessons by inheriting from both record or UserList. You additionally know that the one seen distinction between these two lessons is that UserList exposes the .knowledge attribute, which may facilitate the coding course of.

On this part, you’ll contemplate a side that may be necessary with regards to deciding whether or not to make use of record or UserList to create your customized list-like lessons. That’s efficiency!

To guage if there are efficiency variations between lessons that inherit from record 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(record):
    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, kind(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, kind(self)):
            self.knowledge.lengthen(different)
        else:
            self.knowledge.lengthen(str(merchandise) for merchandise in different)

These two lessons work the identical. Nonetheless, they’re internally completely different. StringList_list inherits from record, and its implementation relies on tremendous(). In distinction, StringList_UserList inherits from UserList, and its implementation depends on the interior .knowledge attribute.

To check the efficiency of those two lessons, it’s best to start by timing customary record operations, resembling 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 be helpful. For instance, you’ll be able to test 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 check, you employ the timeit module together with the min() perform to measure the execution time of a chunk 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 record and the category primarily based on UserList is usually nonexistent on this instance.

Typically, while you create a customized list-like class, you’d anticipate subclasses of record to carry out higher than subclasses of UserList. Why? As a result of record 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 to be like this assumption isn’t utterly proper. Because of this, to determine which superclass is finest in your particular use case, make certain to run a efficiency check.

Efficiency apart, inheriting from record is arguably the pure method in Python, largely as a result of record is instantly obtainable to Python builders as a built-in class. Moreover, most Python builders shall be aware of lists and their customary options, which is able to permit them to jot down list-like lessons extra shortly.

In distinction, the UserList class lives within the collections module, which means that you just’ll should 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 may facilitate the creation of customized list-like lessons.

Conclusion

You’ve now realized create customized list-like lessons with modified and new behaviors. To do that, you’ve subclassed the built-in record class instantly. Instead, you’ve additionally inherited from the UserList class, which is accessible within the collections module.

Inheriting from record and subclassing UserList are each appropriate methods for approaching the issue of making your personal list-like lessons in Python.

On this tutorial, you realized :

  • Create list-like lessons by inheriting from the built-in record class
  • Construct list-like lessons by subclassing UserList from the collections module

Now you’re higher ready to create your personal customized lists, permitting you to leverage the total energy of this convenient and commonplace knowledge kind in Python.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments